story-kit: merge 160_story_constrain_thinking_trace_height_in_agent_stream_ui

This commit is contained in:
Dave
2026-02-24 18:03:08 +00:00
parent 95fc5aba49
commit 464b1e5530
4 changed files with 351 additions and 5 deletions

View File

@@ -14,12 +14,66 @@ interface AgentState {
agentName: string;
status: AgentStatusValue;
log: string[];
/** Accumulated thinking text for the current turn. */
thinking: string;
/** True once regular output has been received after thinking started. */
thinkingDone: boolean;
sessionId: string | null;
worktreePath: string | null;
baseBranch: string | null;
terminalAt: number | null;
}
/** Fixed-height thinking trace block that auto-scrolls to bottom as text arrives. */
function ThinkingBlock({ text }: { text: string }) {
const scrollRef = useRef<HTMLDivElement>(null);
// Auto-scroll to bottom whenever text grows
useEffect(() => {
const el = scrollRef.current;
if (el) {
el.scrollTop = el.scrollHeight;
}
}, [text]);
return (
<div
data-testid="thinking-block"
ref={scrollRef}
style={{
maxHeight: "96px",
overflowY: "auto",
background: "#161b22",
border: "1px solid #2d333b",
borderRadius: "6px",
padding: "6px 10px",
fontSize: "0.78em",
fontFamily: "monospace",
color: "#6e7681",
fontStyle: "italic",
whiteSpace: "pre-wrap",
wordBreak: "break-word",
lineHeight: "1.4",
}}
>
<span
style={{
display: "block",
fontSize: "0.8em",
color: "#444c56",
marginBottom: "4px",
fontStyle: "normal",
letterSpacing: "0.04em",
textTransform: "uppercase",
}}
>
thinking
</span>
{text}
</div>
);
}
const formatTimestamp = (value: Date | null): string => {
if (!value) return "";
return value.toLocaleTimeString([], {
@@ -117,6 +171,8 @@ export function AgentPanel({ configVersion = 0 }: AgentPanelProps) {
agentName: a.agent_name,
status: a.status,
log: [],
thinking: "",
thinkingDone: false,
sessionId: a.session_id,
worktreePath: a.worktree_path,
baseBranch: a.base_branch,
@@ -160,6 +216,8 @@ export function AgentPanel({ configVersion = 0 }: AgentPanelProps) {
agentName,
status: "pending" as AgentStatusValue,
log: [],
thinking: "",
thinkingDone: false,
sessionId: null,
worktreePath: null,
baseBranch: null,
@@ -183,12 +241,23 @@ export function AgentPanel({ configVersion = 0 }: AgentPanelProps) {
},
};
}
case "thinking":
return {
...prev,
[key]: {
...current,
thinking: current.thinking + (event.text ?? ""),
},
};
case "output":
return {
...prev,
[key]: {
...current,
log: [...current.log, event.text ?? ""],
// Receiving text output signals thinking phase is over
thinkingDone:
current.thinking.length > 0 ? true : current.thinkingDone,
},
};
case "done":
@@ -240,6 +309,11 @@ export function AgentPanel({ configVersion = 0 }: AgentPanelProps) {
}
};
// Agents that have streaming content to show (thinking or log)
const activeAgents = Object.values(agents).filter(
(a) => a.thinking.length > 0 || a.log.length > 0,
);
return (
<div
style={{
@@ -396,6 +470,36 @@ export function AgentPanel({ configVersion = 0 }: AgentPanelProps) {
</div>
)}
{/* Per-agent streaming output: thinking trace + regular text */}
{activeAgents.map((agent) => (
<div
key={`stream-${agent.agentName}`}
data-testid={`agent-stream-${agent.agentName}`}
style={{
display: "flex",
flexDirection: "column",
gap: "4px",
}}
>
{agent.thinking.length > 0 && <ThinkingBlock text={agent.thinking} />}
{agent.log.length > 0 && (
<div
data-testid={`agent-output-${agent.agentName}`}
style={{
fontSize: "0.8em",
fontFamily: "monospace",
color: "#ccc",
whiteSpace: "pre-wrap",
wordBreak: "break-word",
lineHeight: "1.5",
}}
>
{agent.log.join("")}
</div>
)}
</div>
))}
{actionError && (
<div
style={{