Feat/conversation UI assistant UI - #265
Merged
Merged
Conversation
…pliance plugin Plugin SDK: ctx.hooks.on(eventName, handler) for registering hook handlers. Plugin-host MCP transport: hooks/list + hooks/invoke JSON-RPC methods with 5s per-handler timeout and first-wins semantics. Go server: pluginhook.Invoker spawns plugin-host, calls hooks/invoke via JSON-RPC. Integrated into run_stream.go event loop — intercepts EventPermissionRequest before persistence. Auto-deny/allow decisions skip interaction creation and auto-submit back to the daemon. New event kinds: permission.auto_denied, permission.auto_allowed — persisted with hook_reason/hook_plugin metadata. SSE wire: permission events carry hook_decision field so the frontend can skip the approval card for auto-decided requests. Frontend: RunsPage timeline shows ShieldAlert/ShieldCheck icons for auto-decided permissions. i18n keys added (en-US + zh-CN). Example: @internal/compliance-approval plugin demonstrates: - rm -rf, DROP TABLE → auto-deny - kubectl delete, curl|bash → OA escalation + ask_human - ls, cat, git status → auto-allow - default → ask_human (normal flow) Verified: make check passes, end-to-end JSON-RPC test confirms all decision paths produce correct results.
Replace the hand-rolled ChatStream/MessageRow/ComposerForm (~800 LOC) with assistant-ui's ExternalStoreRuntime backed by a custom adapter. New architecture: - parsar-chat-runtime.ts: bridges timeline API + SSE into assistant-ui - ParsarChatView.tsx: orchestrator (header, runtime provider, interactions) - ParsarThread.tsx: Claude-style thread (grouped reasoning, tool calls, inline composer with Send/Stop toggle, auto-scroll) - ParsarToolCallCard.tsx: collapsible tool call card Key improvements: - Streaming markdown rendering (react-markdown via assistant-ui) - Smart auto-scroll with detach on manual scroll-up - Per-message rendering isolation (no full-tree re-render per token) - Chain-of-thought grouping for reasoning + tool calls - Proper SSE error handling (completed-before-subscribe, user-cancelled, late-error-with-content all collapse silently) - Timeline polling paused during active streaming - Conversation switch properly resets stream state Backend: zero changes. All existing SSE/REST APIs consumed as-is. Dependencies added: - @assistant-ui/react ^0.15.16 - @assistant-ui/react-markdown ^0.14.12
- fix(capabilities): marketplace tab now shows PublishedMarketplaceTab instead of MCPDirectory when no item is selected. Switching to the marketplace tab with mcp filter no longer incorrectly displays the MCP directory catalog. - fix(agents): sort agents list by created_at descending (newest first) both in the SQL query and as a frontend fallback sort. - feat(web): add @tailwindcss/typography plugin so prose classes render proper markdown formatting (paragraph spacing, list bullets, code blocks, headings) in the conversation thread.
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.
What & why
Migrate the conversation UI from a hand-rolled ChatStream/MessageRow/ComposerForm (~800 LOC) to
@assistant-ui/react's ExternalStoreRuntime, achieving Claude/Codex-grade streaming chat UX. Also fixes the marketplace tab showing MCP directory data incorrectly, adds proper markdown typography, and sorts agents by newest-first.How
Conversation UI (core change):
parsar-chat-runtime.ts: bridges existing timeline REST API + SSE stream into assistant-ui'suseExternalStoreRuntime. Handles message conversion, SSE lifecycle (delta/tool/thinking/permission/done/error), smart error suppression (completed-before-subscribe, user-cancelled, late-error).ParsarThread.tsx: Claude-style layout usingThreadPrimitive+MessagePrimitive.GroupedPartsfor chain-of-thought grouping, inline composer with Send/Stop toggle viaComposerPrimitive.ParsarChatView.tsx: orchestrator replacing ChatStream — wires runtime, interaction cards, working steps, queued run indicators.ParsarToolCallCard.tsx: expandable tool call card with icon dispatch, args summary, elapsed time.ConversationsPage.tsxandapi-conversations.ts.Backend zero changes for conversation streaming — all existing SSE/REST APIs consumed as-is.
Additional fixes:
MarketplaceTab.tsx: default toPublishedMarketplaceTabinstead ofMCPDirectorywhen no item selected.AgentsPage.tsx+store.sql: sort agents bycreated_at DESC(newest first).style.css: add@tailwindcss/typographyplugin for proper prose markdown rendering.Trade-offs:
useToolCallElapsedreturns undefined for persisted messages (streamingWorkingStepsstill works correctly).Verification
pnpm typecheck— 0 errorseslint— 0 errorsvite build— passes (367ms)go build ./server/...— passesmake sqlc-generate— regeneratedNotes for reviewers
@tailwindcss/typographyaddition is required for markdown prose classes to render — without it allprosestyling is no-op.useAgentRunStreamwas intentionally deleted fromapi-conversations.ts— its logic now lives insideparsar-chat-runtime.ts.--include-partial-messagesflag); older versions emit a single delta with the full response.