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:
Dave
2025-12-25 15:18:12 +00:00
parent c493da2f2a
commit 990441dfc1
17 changed files with 471 additions and 172 deletions

View File

@@ -114,3 +114,47 @@ button {
background-color: #0f0f0f69;
}
}
/* Collapsible tool output styling */
details summary {
cursor: pointer;
user-select: none;
}
details summary::-webkit-details-marker {
display: none;
}
details[open] summary span:first-child {
transform: rotate(90deg);
display: inline-block;
transition: transform 0.2s ease;
}
details summary span:first-child {
transition: transform 0.2s ease;
}
/* Markdown body styling for dark theme */
.markdown-body {
color: #ececec;
}
.markdown-body code {
background: #2f2f2f;
padding: 2px 6px;
border-radius: 3px;
font-family: monospace;
}
.markdown-body pre {
background: #1a1a1a;
padding: 12px;
border-radius: 6px;
overflow-x: auto;
}
.markdown-body pre code {
background: transparent;
padding: 0;
}

View File

@@ -299,20 +299,40 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
{msg.role === "user" ? (
msg.content
) : msg.role === "tool" ? (
<div>
<strong
<details style={{ cursor: "pointer" }}>
<summary
style={{
display: "block",
marginBottom: "4px",
color: "#aaa",
fontSize: "0.9em",
marginBottom: "8px",
listStyle: "none",
display: "flex",
alignItems: "center",
gap: "6px",
}}
>
<span style={{ fontSize: "0.8em" }}></span>
<span>
Tool Output
{msg.tool_call_id && ` (${msg.tool_call_id})`}
</span>
</summary>
<pre
style={{
maxHeight: "300px",
overflow: "auto",
margin: 0,
padding: "8px",
background: "#1a1a1a",
borderRadius: "4px",
fontSize: "0.85em",
whiteSpace: "pre-wrap",
wordBreak: "break-word",
}}
>
Tool Output
</strong>
<div style={{ maxHeight: "300px", overflow: "auto" }}>
{msg.content}
</div>
</div>
</pre>
</details>
) : (
<div className="markdown-body">
{/* Assuming global CSS handles standard markdown styling now */}
@@ -332,28 +352,47 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
gap: "8px",
}}
>
{msg.tool_calls.map((tc, i) => (
<div
key={i}
style={{
display: "flex",
alignItems: "center",
gap: "8px",
fontFamily: "monospace",
}}
>
<span style={{ color: "#888" }}>Running:</span>
<span
{msg.tool_calls.map((tc, i) => {
// Parse arguments to extract key info
let argsSummary = "";
try {
const args = JSON.parse(tc.function.arguments);
const firstKey = Object.keys(args)[0];
if (firstKey && args[firstKey]) {
argsSummary = String(args[firstKey]);
// Truncate if too long
if (argsSummary.length > 50) {
argsSummary = argsSummary.substring(0, 47) + "...";
}
}
} catch (e) {
// If parsing fails, just show empty
}
return (
<div
key={i}
style={{
background: "#333",
padding: "2px 6px",
borderRadius: "4px",
display: "flex",
alignItems: "center",
gap: "8px",
fontFamily: "monospace",
}}
>
{tc.function.name}
</span>
</div>
))}
<span style={{ color: "#888" }}></span>
<span
style={{
background: "#333",
padding: "2px 6px",
borderRadius: "4px",
}}
>
{tc.function.name}
{argsSummary && `(${argsSummary})`}
</span>
</div>
);
})}
</div>
)}
</div>

View File

@@ -1,69 +0,0 @@
import { useState } from "react";
import { invoke } from "@tauri-apps/api/core";
export function ToolTester() {
const [output, setOutput] = useState<string>("Ready.");
const runCommand = async (name: string, args: Record<string, unknown>) => {
setOutput(`Running ${name}...`);
try {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const res = await invoke(name, args);
setOutput(JSON.stringify(res, null, 2));
} catch (e) {
setOutput(`Error: ${e}`);
}
};
return (
<div
style={{ marginTop: "20px", border: "1px solid #ccc", padding: "10px" }}
>
<h3>Tool Tester</h3>
<div style={{ display: "flex", gap: "10px", flexWrap: "wrap" }}>
<button onClick={() => runCommand("list_directory", { path: "." })}>
List Root
</button>
<button
onClick={() =>
runCommand("read_file", { path: ".living_spec/README.md" })
}
>
Read Spec
</button>
<button onClick={() => runCommand("search_files", { query: "Story" })}>
Search "Story"
</button>
<button
onClick={() =>
runCommand("exec_shell", { command: "ls", args: ["-F"] })
}
>
Shell: ls -F
</button>
<button
onClick={() =>
runCommand("exec_shell", { command: "git", args: ["status"] })
}
>
Shell: git status
</button>
</div>
<pre
style={{
marginTop: "10px",
background: "#333",
color: "#fff",
padding: "10px",
borderRadius: "5px",
overflowX: "auto",
textAlign: "left",
fontSize: "12px",
}}
>
{output}
</pre>
</div>
);
}