feat(KB-159): complete Step 4 — update frontend for streaming display

This commit is contained in:
gsxdsm
2026-03-30 08:55:07 -07:00
parent 9085e8d867
commit c12428e996
7 changed files with 639 additions and 25 deletions

View File

@@ -634,17 +634,32 @@ async function continueAgentConversation(session: Session, message: string): Pro
try {
// Clear thinking output for this turn
const previousThinking = session.thinkingOutput;
session.thinkingOutput = "";
// Send message to agent - it will stream thinking via onThinking callback
const response = await session.agent.session.send(message);
// Send message to agent using .prompt() - it will stream thinking via onThinking callback
await session.agent.session.prompt(message);
// Combine any thinking output with the response text
const fullResponse = session.thinkingOutput + (response?.text || "");
// Get the response text from the agent's state
const lastMessage = session.agent.session.state.messages
.filter(m => m.role === "assistant")
.pop();
let responseText = session.thinkingOutput;
if (lastMessage?.content) {
// Handle both string and array content types
if (typeof lastMessage.content === "string") {
responseText = lastMessage.content;
} else if (Array.isArray(lastMessage.content)) {
// Extract text from content blocks
responseText = lastMessage.content
.filter((c): c is { type: "text"; text: string } => c.type === "text")
.map(c => c.text)
.join("");
}
}
// Parse the JSON response
const parsed = parseAgentResponse(fullResponse);
const parsed = parseAgentResponse(responseText);
if (parsed.type === "question") {
session.currentQuestion = parsed.data;

View File

@@ -2169,6 +2169,44 @@ export function createApiRoutes(store: TaskStore, options?: ServerOptions): Rout
}
});
/**
* POST /api/planning/start-streaming
* Start a new planning session with AI agent streaming.
* Body: { initialPlan: string }
* Returns: { sessionId: string }
*
* After receiving sessionId, connect to GET /api/planning/:sessionId/stream
* for real-time thinking output and questions.
*/
router.post("/planning/start-streaming", async (req, res) => {
try {
const { initialPlan } = req.body;
if (!initialPlan || typeof initialPlan !== "string") {
res.status(400).json({ error: "initialPlan is required and must be a string" });
return;
}
if (initialPlan.length > 500) {
res.status(400).json({ error: "initialPlan must be 500 characters or less" });
return;
}
const ip = req.ip || req.socket.remoteAddress || "unknown";
const rootDir = store.getRootDir();
const { createSessionWithAgent, RateLimitError } = await import("./planning.js");
const sessionId = await createSessionWithAgent(ip, initialPlan, rootDir);
res.status(201).json({ sessionId });
} catch (err: any) {
if (err.name === "RateLimitError") {
res.status(429).json({ error: err.message });
} else {
res.status(500).json({ error: err.message || "Failed to start planning session" });
}
}
});
/**
* POST /api/planning/respond
* Submit a response to the current planning question.
@@ -2322,7 +2360,8 @@ export function createApiRoutes(store: TaskStore, options?: ServerOptions): Rout
// Subscribe to session events
const unsubscribe = planningStreamManager.subscribe(sessionId, (event) => {
try {
res.write(`event: ${event.type}\ndata: ${JSON.stringify(event.data ?? {})}\n\n`);
const data = (event as { data?: unknown }).data;
res.write(`event: ${event.type}\ndata: ${JSON.stringify(data ?? {})}\n\n`);
// End stream on complete or error
if (event.type === "complete" || event.type === "error") {