Skip to content

LOU-P: chat with agents - #21

Merged
LinuxDevil merged 3 commits into
mainfrom
loushy/lou-p
Sep 29, 2026
Merged

LinuxDevil merged 3 commits into
mainfrom
loushy/lou-p

Conversation

@LinuxDevil

Copy link
Copy Markdown
Owner

Summary

  • Real chat surface for talking to a running agent, on top of the existing run/stream infra (not a second parallel pipeline).
  • Server: runRegistry.ts gains 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() (or starts fresh). chatReconcile.ts reconciles the transcript against the authoritative ExecutionResult.messages on every run settle. chatStore.ts persists 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.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 chat-inline and Topbar approval UI share one component (verified both call sites).
  • SDK fix included in scope: while verifying this epic, found that AgentExecutor.runAgentLoop()'s no-more-tool-calls exit path set finalText/result.text but never pushed the corresponding assistant message onto currentMessages, so result.messages silently omitted the agent's own final reply whenever a turn ended without a tool call — a real bug affecting every SDK consumer that treats result.messages as 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 in runRegistry.ts is now a documented, provably-redundant no-op guard rather than removed outright.
  • 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.

Test plan

  • Root: tsc --noEmit, npm run lint (0 errors), test:coverage — 748 passed / 5 pre-existing failures (all packages/create-loushy-agent, same pattern as every prior epic)
  • src/execution suite (incl. the SDK fix): 191/191 passed
  • apps/agent-forge server suite: 49/49 passed
  • apps/agent-forge client suite: 74/74 passed
  • apps/agent-forge typecheck (app + server), lint (app + server), vite build
  • Manually traced chatReconcile.ts, chatStore.ts path handling (agent id / session id validated against path traversal via existing isValidAgentId middleware), and the 409 continuation UX (input disabled + inline approval card, not a silent drop)

🤖 Generated with Claude Code

LinuxDevil and others added 3 commits September 29, 2026 15:18
…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>
@LinuxDevil
LinuxDevil merged commit 4340dfe into main Sep 29, 2026
1 check passed
@LinuxDevil
LinuxDevil deleted the loushy/lou-p branch September 29, 2026 12:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant