feat: Story 8 - Collapsible tool outputs + autonomous coding improvements
Implemented Story 8: Collapsible Tool Outputs - Tool outputs now render in <details>/<summary> elements, collapsed by default - Summary shows tool name with key argument (e.g., ▶ read_file(src/main.rs)) - Added arrow rotation animation and scrollable content (max 300px) - Enhanced tool_calls display to show arguments inline - Added CSS styling for dark theme consistency Fixed: LLM autonomous coding behavior - Strengthened system prompt with explicit examples and directives - Implemented triple-reinforcement system (primary prompt + reminder + message prefixes) - Improved tool descriptions to be more explicit and action-oriented - Increased MAX_TURNS from 10 to 30 for complex agentic workflows - Added debug logging for Ollama requests/responses - Result: GPT-OSS (gpt-oss:20b) now successfully uses write_file autonomously Documentation improvements - Created MODEL_SELECTION.md guide with recommendations - Updated PERSONA.md spec to emphasize autonomous agent behavior - Updated UI_UX.md spec with collapsible tool output requirements - Updated SDSW workflow: LLM archives stories and performs squash merge Cleanup - Removed unused ToolTester.tsx component
This commit is contained in:
@@ -17,7 +17,7 @@ pub struct ProviderConfig {
|
||||
pub enable_tools: Option<bool>,
|
||||
}
|
||||
|
||||
const MAX_TURNS: usize = 10;
|
||||
const MAX_TURNS: usize = 30;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_ollama_models(base_url: Option<String>) -> Result<Vec<String>, String> {
|
||||
@@ -53,6 +53,16 @@ pub async fn chat(
|
||||
// 3. Agent Loop
|
||||
let mut current_history = messages.clone();
|
||||
|
||||
// Prefix user messages with reminder for stubborn models
|
||||
for msg in &mut current_history {
|
||||
if msg.role == Role::User && !msg.content.starts_with("[AGENT DIRECTIVE]") {
|
||||
msg.content = format!(
|
||||
"[AGENT DIRECTIVE: You must use write_file tool to implement changes. Never suggest code.]\n\n{}",
|
||||
msg.content
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Inject System Prompt
|
||||
current_history.insert(
|
||||
0,
|
||||
@@ -64,6 +74,17 @@ pub async fn chat(
|
||||
},
|
||||
);
|
||||
|
||||
// Inject aggressive reminder as a second system message
|
||||
current_history.insert(
|
||||
1,
|
||||
Message {
|
||||
role: Role::System,
|
||||
content: "CRITICAL REMINDER: When the user asks you to create, modify, or implement code, you MUST call the write_file tool with the complete file content. DO NOT output code in markdown blocks. DO NOT suggest what the user should do. TAKE ACTION IMMEDIATELY using tools.".to_string(),
|
||||
tool_calls: None,
|
||||
tool_call_id: None,
|
||||
},
|
||||
);
|
||||
|
||||
let mut new_messages: Vec<Message> = Vec::new();
|
||||
let mut turn_count = 0;
|
||||
|
||||
@@ -91,8 +112,8 @@ pub async fn chat(
|
||||
|
||||
current_history.push(assistant_msg.clone());
|
||||
new_messages.push(assistant_msg);
|
||||
// Emit history excluding system prompt (index 0)
|
||||
app.emit("chat:update", ¤t_history[1..])
|
||||
// Emit history excluding system prompts (indices 0 and 1)
|
||||
app.emit("chat:update", ¤t_history[2..])
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
// Execute Tools
|
||||
@@ -110,8 +131,8 @@ pub async fn chat(
|
||||
|
||||
current_history.push(tool_msg.clone());
|
||||
new_messages.push(tool_msg);
|
||||
// Emit history excluding system prompt (index 0)
|
||||
app.emit("chat:update", ¤t_history[1..])
|
||||
// Emit history excluding system prompts (indices 0 and 1)
|
||||
app.emit("chat:update", ¤t_history[2..])
|
||||
.map_err(|e| e.to_string())?;
|
||||
}
|
||||
} else {
|
||||
@@ -126,8 +147,8 @@ pub async fn chat(
|
||||
// We don't push to current_history needed for next loop, because we are done.
|
||||
new_messages.push(assistant_msg.clone());
|
||||
current_history.push(assistant_msg);
|
||||
// Emit history excluding system prompt (index 0)
|
||||
app.emit("chat:update", ¤t_history[1..])
|
||||
// Emit history excluding system prompts (indices 0 and 1)
|
||||
app.emit("chat:update", ¤t_history[2..])
|
||||
.map_err(|e| e.to_string())?;
|
||||
break;
|
||||
}
|
||||
@@ -200,11 +221,11 @@ fn get_tool_definitions() -> Vec<ToolDefinition> {
|
||||
kind: "function".to_string(),
|
||||
function: ToolFunctionDefinition {
|
||||
name: "read_file".to_string(),
|
||||
description: "Reads the content of a file in the project.".to_string(),
|
||||
description: "Reads the complete content of a file from the project. Use this to understand existing code before making changes.".to_string(),
|
||||
parameters: json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": { "type": "string", "description": "Relative path to the file" }
|
||||
"path": { "type": "string", "description": "Relative path to the file from project root" }
|
||||
},
|
||||
"required": ["path"]
|
||||
}),
|
||||
@@ -214,12 +235,12 @@ fn get_tool_definitions() -> Vec<ToolDefinition> {
|
||||
kind: "function".to_string(),
|
||||
function: ToolFunctionDefinition {
|
||||
name: "write_file".to_string(),
|
||||
description: "Writes content to a file. Overwrites if exists.".to_string(),
|
||||
description: "Creates or completely overwrites a file with new content. YOU MUST USE THIS to implement code changes - do not suggest code to the user. The content parameter must contain the COMPLETE file including all imports, functions, and unchanged code.".to_string(),
|
||||
parameters: json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": { "type": "string", "description": "Relative path to the file" },
|
||||
"content": { "type": "string", "description": "The full content to write" }
|
||||
"path": { "type": "string", "description": "Relative path to the file from project root" },
|
||||
"content": { "type": "string", "description": "The complete file content to write (not a diff or partial code)" }
|
||||
},
|
||||
"required": ["path", "content"]
|
||||
}),
|
||||
@@ -229,11 +250,11 @@ fn get_tool_definitions() -> Vec<ToolDefinition> {
|
||||
kind: "function".to_string(),
|
||||
function: ToolFunctionDefinition {
|
||||
name: "list_directory".to_string(),
|
||||
description: "Lists files and directories at a path.".to_string(),
|
||||
description: "Lists all files and directories at a given path. Use this to explore the project structure.".to_string(),
|
||||
parameters: json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": { "type": "string", "description": "Relative path to list (use '.' for root)" }
|
||||
"path": { "type": "string", "description": "Relative path to list (use '.' for project root)" }
|
||||
},
|
||||
"required": ["path"]
|
||||
}),
|
||||
@@ -243,12 +264,12 @@ fn get_tool_definitions() -> Vec<ToolDefinition> {
|
||||
kind: "function".to_string(),
|
||||
function: ToolFunctionDefinition {
|
||||
name: "search_files".to_string(),
|
||||
description: "Searches for text content across all files in the project."
|
||||
description: "Searches for text patterns across all files in the project. Use this to find functions, variables, or code patterns when you don't know which file they're in."
|
||||
.to_string(),
|
||||
parameters: json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": { "type": "string", "description": "The string to search for" }
|
||||
"query": { "type": "string", "description": "The text pattern to search for across all files" }
|
||||
},
|
||||
"required": ["query"]
|
||||
}),
|
||||
@@ -258,18 +279,18 @@ fn get_tool_definitions() -> Vec<ToolDefinition> {
|
||||
kind: "function".to_string(),
|
||||
function: ToolFunctionDefinition {
|
||||
name: "exec_shell".to_string(),
|
||||
description: "Executes a shell command in the project root.".to_string(),
|
||||
description: "Executes a shell command in the project root directory. Use this to run tests, build commands, git operations, or any command-line tool. Examples: cargo check, npm test, git status.".to_string(),
|
||||
parameters: json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": {
|
||||
"type": "string",
|
||||
"description": "The command to run (e.g., 'git', 'cargo', 'ls')"
|
||||
"description": "The command binary to execute (e.g., 'git', 'cargo', 'npm', 'ls')"
|
||||
},
|
||||
"args": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "Arguments for the command"
|
||||
"description": "Array of arguments to pass to the command (e.g., ['status'] for git status)"
|
||||
}
|
||||
},
|
||||
"required": ["command", "args"]
|
||||
|
||||
Reference in New Issue
Block a user