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:
44
src/App.css
44
src/App.css
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user