feat(workflows): extract ChatSessionHost and compose into stage chat - #1026
Conversation
Extract a reusable chat session host from interactive mode and wire workflow stage chat through stage-scoped UI brokers.\n\nUpdate workflow TUI rendering, input schema previews, docs, mockups, and tests for the embedded chat surface. Assistant-model: GPT-5.5
Count only connected MCP servers in the status bar and fall back to plain text when theme colorization is unavailable.\n\nEscape callback authorization errors and retain a stable completed UI session handle when storing session messages. Assistant-model: GPT-5.5
Assistant-model: GPT-5.5
PR Review: feat(workflows): embed custom UI in stage chat hostsThis is a sizable but well-structured refactor. The extraction of A few items below — most are non-blocking, but the executor lifetime change in particular is worth discussing before merging. Bugs / correctness1. Stage AgentSession is never disposed on normal settle (
|
Code ReviewNice extraction. The 🚨 Likely resource leak: stage SDK sessions no longer disposed
Net effect: every stage's Predictable tmpfile path in
|
Assistant-model: GPT-5.5
PR ReviewSubstantial refactor (4.4k+ / -1.7k across 64 files) that extracts a reusable Security
However: no test covers the new XSS hardening. For a defense-in-depth security fix, a small unit test asserting Bugs / correctness
const tmpFile = path.join(os.tmpdir(), `pi-editor-${Date.now()}.pi.md`);Two concurrent invocations within the same millisecond would collide and one would clobber the other's file. The neighbouring
return \`stage-ui-\${Date.now().toString(36)}-\${Math.random().toString(36).slice(2, 8)}\`;Only ~6 chars of randomness. Collisions within the same ms are possible and would cause
Also, this is a behaviour change: previously, an attached chat pane kept the live handle until the user detached. Now the handle drops as soon as the SDK queue drains — even if a pane is still attached. The PR description doesn't call this out; users attached to a completed stage may notice a switch to read-only behaviour they didn't trigger. Style / code quality
Test coverage
Minor
Overall: solid refactor — abstractions are well-named and tests give confidence in the core flows. Main asks: cover the XSS fix with a test, swap |
Assistant-model: GPT-5.5
Code review — PR #1026 (refactor/workflow-chat-host-parity)Reviewed against Strengths
Findings
Conventions / housekeeping
Suggested follow-ups (non-blocking)
Net: this is a strong refactor with good test scaffolding and several real bug fixes mixed in. The findings above are mostly minor; (1) and (2) are the only ones that could plausibly cause observable issues in production, and both are easily addressed. |
Assistant-model: GPT-5.5
PR #1026 review — embed stage-scoped custom UI in chat session hostsThanks for the big and otherwise well-factored refactor — extracting Bugs / behavior
Code quality
Security
TestsNew coverage for
Nit
Overall this is a strong refactor — the size mostly comes from the legitimate code-motion of extracting Reviewed by Claude (Opus 4.7). |
|
ping test |
|
Review: feat(workflows) embed stage-scoped custom UI in chat session hosts Solid refactor with meaningful security hardening alongside it. The extraction of ChatSessionHost is a real win — stage-chat-view.ts shedding ~500 net lines is the kind of duplication-collapse you want — and the StageUiBroker is well-shaped (single-owner pending request, host swap, abort wiring all covered by tests). Likely bugs / behavior to revisit
Security — what landed is good
One small follow-up: parseEditorCommand does not reject EDITOR values that contain shell metacharacters before the Windows shell path. Low-risk (user owns their EDITOR), but if you want defense-in-depth, sanitize before shell true. Performance
Tests Coverage is strong for this size of change:
Gaps worth filling:
Style / CLAUDE.md alignment
Verdict Approve with the editor-extension-typo fix (item 1) addressed; items 2 through 4 are good follow-up tickets but not blockers given the scope. Nice work on collapsing the duplication and tightening the MCP surfaces in the same PR. Generated with Claude Code (https://claude.com/claude-code) |
23064c5 to
495763b
Compare
Code ReviewSolid refactor — extracting Bugs / correctness1. PR description doesn't match the MCP callback fix (medium). The description says “OAuth callback error template made dynamic; error messages are HTML-escaped ( 2. Dead variables in const errorMsg = "Missing required state parameter - potential CSRF attack"
res.writeHead(400, { "Content-Type": "text/html" })
res.end(renderCallbackErrorHtml()) // errorMsg dropped on the floorThis compiles under 3. 4. Code quality5. 6. CLAUDE.md says avoid 7. Security (positive)
Test coverageGood. The new tests cover request-id collision resistance, duplicate-pending rejection, abort propagation, host replacement, and stage-scoped pending prompts. Two gaps worth adding:
Nits
Overall the refactor looks worth merging once #1/#3 are addressed (or #3 is explicitly opted into in the changelog). |
495763b to
3a3b0de
Compare
Code Review — PR #1026Substantial refactor — the Bugs (verified)1. 2. 3. 4. 5. 6. Concerns7. PR description overstates the MCP "reflected XSS" fix. 8. No 9. Slash commands bypass the compaction guard — 10. 11. Test gaps
Verified clean / good
Priority order for follow-up
|
3a3b0de to
167e3cf
Compare
Review — feat(workflows): embed stage-scoped custom UI in chat session hostsSkimmed the extraction, the broker, the executor lifecycle change, and the MCP hardening. Overall this looks like a careful, well-tested refactor. A few things worth flagging. Behavior change worth confirming intent
Net effect for users: a chat pane that's still attached to a completed stage will transition from interactive → read-only as soon as the SDK queue empties, instead of waiting for the user to press Ctrl+D. If the user is in the middle of a conversation after the workflow finished, the composer can go cold under their cursor. The PR description doesn't call this out explicitly — is this the intended product behavior, or is the policy still meant to wait for detach when a pane is attached? If intentional, please mention it in the PR description; if not, the predicate at StageUiBroker — description / implementation mismatch
Brittle marker for "exclude workflows from child sessions"
function stageBuiltinPackagePaths(paths: readonly string[]): string[] {
return paths.filter((path) => basename(path) !== "workflows");
}The fix itself (avoid recursive Cheap improvement: have MCP hardening — minor
ChatSessionHost extraction & tests
Worth a follow-up, not a blocker
Things that look right
Nice work overall. |
167e3cf to
2fd0a8b
Compare
Review: PR #1026 —
|
2fd0a8b to
cd24204
Compare
Code Review —
|
Assistant-model: GPT-5.5
Code ReviewThorough refactor — the Major1. 2. 3. 4. Re-registering the same host while a request is pending re-invokes 5. 6. 7. 8. Timers can fire (and re-arm) after Minor
Nits
Test gaps worth filling
Conventions
Overall: ship-worthy after #1, #3, #4, #5 are addressed; #2 and #6-#8 should land as a follow-up with regression tests. |
Render workflow inputs as a single ask-style active field with tab status, footer rules, and list-style select/boolean choices. Normalize TUI key handling through typed key ids and shared action constants, and cover the updated navigation/rendering behavior in unit tests. Assistant-model: GPT-5.5
Review:
|
…1026) * feat(workflows): embed stage chat custom UI hosts Extract a reusable chat session host from interactive mode and wire workflow stage chat through stage-scoped UI brokers.\n\nUpdate workflow TUI rendering, input schema previews, docs, mockups, and tests for the embedded chat surface. Assistant-model: GPT-5.5 * fix(mcp): harden status and callback handling Count only connected MCP servers in the status bar and fall back to plain text when theme colorization is unavailable.\n\nEscape callback authorization errors and retain a stable completed UI session handle when storing session messages. Assistant-model: GPT-5.5 * fix(workflows): preserve workflow name in empty inputs output Assistant-model: GPT-5.5 * fix(workflows): release idle completed stage sessions Assistant-model: GPT-5.5 * fix(workflows): freeze background widget timers Assistant-model: GPT-5.5 * fix(workflows): pause stage chat on escape Assistant-model: GPT-5.5 * fix: address PR review security and lifecycle bugs * feat(workflows): align inline forms with question UI Assistant-model: GPT-5.5 * feat(workflows): align inputs picker with ask UI Render workflow inputs as a single ask-style active field with tab status, footer rules, and list-style select/boolean choices. Normalize TUI key handling through typed key ids and shared action constants, and cover the updated navigation/rendering behavior in unit tests. Assistant-model: GPT-5.5
Summary
Extracts a reusable
ChatSessionHostcomponent from the interactive-mode chat surface and composes it intoStageChatView, eliminating ~800 lines of duplicated interactive-mode logic from stage chat.ctx.ui.customrequests are routed to the active stage node via a newStageUiBroker, and MCP session lifecycle and security are hardened throughout.New abstractions
ChatSessionHost(packages/coding-agent/src/modes/interactive/components/chat-session-host.ts, ~1281 lines) — reusable component encapsulating the full interactive chat surface (slash commands, bash execution, external editor, image paste, queued messages, compaction, retries);interactive-mode.tsretains its legacy implementation — migration is follow-up workStageUiBroker(packages/workflows/src/shared/stage-ui-broker.ts) — routesctx.ui.customfactory requests to the correct active stage chat node with host registration, duplicate concurrent request rejection,AbortSignalsupport, and cleanup for throwing hostschat-input-actions.ts— standalone helpers for clipboard image paste, external editor launch, and queued-message coalescing; shared between interactive mode and stage chatstage-chat-layout.ts— extracted frame-fitting and viewport helpers for the stage chat surfaceWorkflow stage chat
StageChatViewnow composesChatSessionHostinstead of duplicating interactive-mode logicrecordStagePendingPrompt,resolveStagePendingPrompt,awaitStagePendingPrompt) with lifecycle cleanup on run end or removalreleaseLiveHandleWhenIdle), preventing premature session collapse while messages are still in flight; a subscription plus a documented 250 ms defensive poll covers the silent SDK drain pathisDisposedgetter on the live stage-control handle for accurate lifecycle queriesrender-inputs-schemamigrated to the same rendererMCP hardening
"connected"connections (previously counted all map entries regardless of status)colorizeStatusTexthelper guards against themes that don't expose a.fg()function, preventing runtime errors on non-standard themesrenderCallbackErrorHtmlfunction; raw OAuth query-string errors are no longer interpolated into HTML — they remain available only through rejection/logging pathssrcattribute HTML-escaped viaescapeHtmlAttributebefore injectionTests
test/unit/chat-session-host.test.ts(543 lines) — full host lifecycle, slash commands, bash, compaction, retry, custom entry renderingtest/unit/mcp-init-statusbar.test.ts(82 lines) — connected-count filtering and theme-fallback safetytest/unit/stage-ui-broker.test.ts,store-pending-prompt.test.ts,workflow-attach-pane.test.ts,mcp-security.test.tsstage-chat-view,wiring-adapters,overlay-graph,prompt-card,dispatch-confirm,inputs-picker,executor,store-widget-installer, and 5+ other unit test filesValidation
🤖 Generated with Claude Code