Repository navigation
LOU-P: chat with agents - #21
Merged
Merged
Conversation
…all exit AgentExecutor.runAgentLoop()'s no-more-tool-calls exit path set finalText/result.text but never appended a corresponding assistant message onto currentMessages, so result.messages silently omitted the agent's own final reply whenever a turn ended without a tool call - the common case for a plain chat exchange. Any caller that treats result.messages as the authoritative conversation history to seed a follow-up turn (as LOU-P's chat continuation does) would silently lose the agent's last reply. Found while wiring up LOU-P chat continuation semantics, and fixed at the source since this is a general SDK contract bug affecting every consumer, not just the agent-forge app. Updates the delegate-tool and resume tests whose expectations encoded the old (buggy) behavior. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds a real chat surface for talking to a running agent, built on top
of the existing run/stream infrastructure rather than a second
parallel pipeline:
- Server: runRegistry.ts grows per-agent ChatMessage[] transcript
tracking and sendMessage() (backing POST /agents/:id/message), with
documented continuation semantics - 409 while running, 409 while
paused for a tool approval, otherwise continues the real prior
Message[] history via AgentExecutor.execute() (skipSystemPromptInjection:
true) or starts fresh. chatReconcile.ts reconciles the transcript
against the authoritative ExecutionResult.messages on every run
settle, rather than building it incrementally from execution events.
chatStore.ts persists transcripts to .loushy/agents/<id>/chats/<sessionId>.json
so they survive a server restart. New routes: POST /agents/:id/message,
GET /agents/:id/chat, POST /agents/:id/chat/new, GET /agents/:id/chats,
GET /agents/:id/chats/:sessionId. wsServer.ts forwards {type:'chat'}
over the existing WS channel.
- Client: ChatPanel.tsx (bubbles, avatars, timestamps, typing indicator,
tool-call cards, inline approval card, disabled input with a clear
reason while running/paused). ApprovalCard.tsx factored out of
Topbar.tsx so the chat-inline and Topbar approval UI share one
component.
- Streaming is message-level, not token-level, by construction:
AgentExecutor.runAgentLoop() only ever calls provider.generate(),
never .stream().
Known limitation: a newChat() session isn't auto-recovered as
"current" after a server restart (only the default agentId-named
session is) - left for a follow-up epic.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
apps/* workspaces are already excluded from test discovery here (test.exclude) since each has its own vitest config and test suite, but coverage.exclude didn't mirror that - so every apps/agent-forge source file was counted in this config's coverage denominator at a permanent 0% (no test in this run ever touches it), which is not a real coverage gap, just dead weight in the report. This was latent since LOU-L first added apps/agent-forge, but LOU-P's growth (new server chat plumbing + ChatPanel/ApprovalCard, all actually covered by apps/agent-forge's own 49/49 server + 74/74 client suites) tipped the denominator enough to drag the global lines/ statements ratio under the 61% threshold and fail CI. Excluding apps/** from coverage restores the real, already-passing baseline (measured locally at ~75% after this fix). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
runRegistry.tsgains per-agentChatMessage[]transcript tracking andsendMessage()backingPOST /agents/:id/message, with documented continuation semantics — 409 while running, 409 while paused for a tool approval, otherwise continues the real priorMessage[]history viaAgentExecutor.execute()(or starts fresh).chatReconcile.tsreconciles the transcript against the authoritativeExecutionResult.messageson every run settle.chatStore.tspersists transcripts to.loushy/agents/<id>/chats/<sessionId>.json. New routes:POST /agents/:id/message,GET /agents/:id/chat,POST /agents/:id/chat/new,GET /agents/:id/chats,GET /agents/:id/chats/:sessionId.wsServer.tsforwards{type:'chat'}over the existing WS channel.ChatPanel.tsx(bubbles/avatars/timestamps/typing indicator/tool-call cards, inline approval card, disabled input with a clear reason while running/paused).ApprovalCard.tsxfactored out ofTopbar.tsxso chat-inline and Topbar approval UI share one component (verified both call sites).AgentExecutor.runAgentLoop()'s no-more-tool-calls exit path setfinalText/result.textbut never pushed the corresponding assistant message ontocurrentMessages, soresult.messagessilently omitted the agent's own final reply whenever a turn ended without a tool call — a real bug affecting every SDK consumer that treatsresult.messagesas the authoritative conversation (exactly what this epic's chat continuation does). Fixed at the source (src/execution/AgentExecutor.ts), not worked around downstream, since it's a general contract bug, not an agent-forge-specific one. Updated the delegate-tool and resume tests whose expectations encoded the old (buggy) behavior; the app-side "synthesize the missing message" logic inrunRegistry.tsis now a documented, provably-redundant no-op guard rather than removed outright.newChat()session isn't auto-recovered as "current" after a server restart (only the defaultagentId-named session is) — left for a follow-up.Test plan
tsc --noEmit,npm run lint(0 errors),test:coverage— 748 passed / 5 pre-existing failures (allpackages/create-loushy-agent, same pattern as every prior epic)src/executionsuite (incl. the SDK fix): 191/191 passedapps/agent-forgeserver suite: 49/49 passedapps/agent-forgeclient suite: 74/74 passedapps/agent-forgetypecheck (app + server), lint (app + server),vite buildchatReconcile.ts,chatStore.tspath handling (agent id / session id validated against path traversal via existingisValidAgentIdmiddleware), and the 409 continuation UX (input disabled + inline approval card, not a silent drop)🤖 Generated with Claude Code