Skip to content

Commit 99a1ca6

Browse files
Chat UX Enhancements (#3039)
* feat(chat): render subagent activity inline as collapsible tool wrapper Remove SubagentExecutionMessage from the bottom-pinned section so subagent tool calls render inline within AssistantMessage via the existing SubagentToolCall collapsible component, consistent with all other tool calls. * fix(chat): add gradient fade-out and bottom padding to input footer Removes top padding from ChatInputDropZone so content is flush with the bottom of the scroll area, and adds a CSS pseudo-element gradient that fades the conversation content into the background above the input bar. * feat(ui): redesign ToolInput/ToolOutput/ToolHeader — compact, monospaced - ToolInput/ToolOutput: replace CodeBlock with plain <pre>, remove p-4 padding, switch to bg-muted/30 backgrounds, rename labels to "Input"/"Output", apply font-mono throughout - ToolHeader: reduce px-2.5 → px-1, add rounded-b-md, swap chevron direction on hover (right=collapsed, down=expanded), add open prop - Tool: add font-mono to outer Collapsible wrapper * fix(ui): stop scroll anchor when clicking tool call triggers Expanding a tool call was causing the scroll container to jump. Detect clicks on [data-tool-trigger] elements and call stopScroll() so the stick-to-bottom behaviour doesn't fight the user interaction. * feat(ui): add ToolCallRow and refactor tool components Introduces a shared ToolCallRow component that encapsulates the common collapsible row pattern: icon/chevron, ShimmerLabel title, muted description, status slot, left-border content area, and an optional headerExtra element for out-of-trigger action buttons. Refactors BashTool, WebSearchTool, WebFetchTool, and FileDiffTool to use ToolCallRow, removing duplicated hover/chevron/collapsible logic from each. All tools now have consistent: font-mono, px-1 header padding, rounded-b-md on hover, ml-2.5 border-l content alignment, and chevron-right/down hover behaviour. * feat(desktop): refactor tool call components to use shared ToolCallRow Migrates GenericToolCall, SupersetToolCall, SubagentToolCall, and ReadOnlyToolCall to use the new ToolCallRow component, eliminating per-component chevron/hover/collapsible boilerplate. Also updates ToolCallBlock dispatch: web_search tool variants without parsed results now fall through to GenericToolCall with a globe icon instead of WebSearchTool (which requires results to be expandable). Renames "Subagent" label to "Agent" with the agent type as a muted description. * feat(chat): replace inline question UI with footer overlay - Add QuestionInputOverlay component: numbered options, "Something else" free-text row with pencil icon, Skip button, cross-fade to submit on type - Refactor AskUserQuestionToolCall to plain ToolCallRow (no inline answer UI) - Wire pendingQuestion/handleQuestionResponse/stopActiveResponse to footer in both ChatPane variants instead of ChatMessageList - Remove PendingQuestionMessage from ChatMessageList and clean up its props * feat(ui): replace tool call header indicators with braille spinner and left-side icons - Add BrailleSpinner component (braille chars, amber, matches sidenav style) - Show spinner in icon slot while pending, red X on error, icon on complete - Remove right-side status slot (no spinner, checkmark, or X on the right) - Remove title shimmer animation * feat(chat): hide Question tool row while active, show with description after answered/interrupted - Thread isStreaming through ToolCallBlock to AskUserQuestionToolCall - Return null only when isPending && still streaming (overlay is active) - Show collapsed row with question text as description once answered or interrupted * feat(chat): improve tool call UX and styling - Align tool call icon with chat text using -mx-1 negative margin - Remove overflow-hidden from MessageContent to avoid clipping - Hide description in tool call header when expanded - Show query field as plain text with Query/Response labels instead of raw JSON - Add subtle focus-visible ring to collapsible trigger button - Adjust content padding (pl-3 py-1) to align with heading text - Use "Type your answer..." placeholder when question has no options * fix(chat): keep answered question messages visible during active turn Extract hasAnsweredQuestionToolCall to a shared utility and use it in withoutActiveTurnAssistantHistory / getVisibleMessages so that assistant messages containing an answered ask_user_question are kept visible in the message list while a session is still running. Previously all assistant messages from the active turn were hidden, causing questions and their answers to disappear from chat history until the turn completed. * fix(chat): refactor question tool call — answer bubble, skip badge, plain-string fallback Replace the inline Q&A markdown block with a right-aligned answer bubble (styled like a user message) shown after the question is answered, and a "Question skipped" badge when the result carries no answers. Add a fallback for backends that return a plain string result (result.text / result.answer) rather than a structured answers map. Remove the isPending spinner from the ToolCallRow since the overlay in the footer already indicates active state. * fix(chat): overlay freezes in place on submit, skip sends answer, scroll on question changes - Fire onRespond without awaiting so the overlay never shows a separate "Waiting for response..." state. The overlay stays frozen (same size and content) until pendingQuestion updates from the server, at which point React remounts the component via key={pendingQuestion.questionId}. - Highlight the chosen option and replace its number badge with a spinner; show a spinner on the pencil icon when the answer came from the text input. - Skip now sends "skip" as the answer instead of aborting the agent, so the LLM can continue. The X button still stops the session. - Fix isQuestionSubmitting hardcoded to false — now passes questionResponsePending. - Add footerScrollTrigger so the chat scrolls to the bottom whenever the question overlay appears, updates, or disappears. * feat(chat): question overlay max-height with scrollable options and pinned header/footer * fix(chat): stabilize useFocusPromptOnPane effect dependency * feat(chat): show question tool call with status and collapsible answer Rewrites AskUserQuestionToolCall to use the shared ToolCallRow component. - Supports both ask_user (singular question/options) and ask_user_question (array of questions) tool schemas - Shows AWAITING RESPONSE / ANSWERED / CANCELLED inline status description - Expands to reveal the question text + submitted answer when answered - Extracts answer from result.content "User answered: <x>" format - ToolCallRow now uses cursor-text when the row has no expandable content * fix(chat): keep question tool calls visible during active assistant turn getVisibleMessages() filtered out all assistant messages while a turn was in progress. Added hasPendingQuestionToolCall() to also pass through any assistant message that contains an unanswered question tool call, so the "AWAITING RESPONSE" tool call row remains visible in the message list. * feat(chat): optimistically hide question overlay on answer submit Tracks the most recently answered question ID in ChatPaneInterface and passes it to ChatUploadFooter so the overlay disappears immediately on submit without waiting for the server round-trip. Also threads pendingQuestion and answeredQuestionId down to ChatMessageList to suppress the ThinkingMessage spinner while a question is awaiting a response. * feat(chat): scroll to bottom on message send, question arrival, and answer Adds a ScrollAnchor component inside the Conversation (StickToBottom) context that handles three cases: - isAwaitingAssistant becomes true: re-pins scroll so Thinking and the streaming response are always visible after sending any message - pendingQuestion.questionId changes: scrolls to bottom when a new question arrives so the overlay doesn't cover streaming content - answeredQuestionId changes (10ms delay): the overlay hide causes the footer to shrink and the scroll container to grow; the library interprets the resulting scrollTop clamp as "user scrolled up" via a 1ms setTimeout, so we run after it with a 10ms delay to restore the pin * feat(chat): show cancelled status when question is aborted - Question tool call now shows CANCELLED status in the header instead of nothing when aborted (output-error state or Mastracode isError: true) - Error result content is no longer mistaken for an answer, fixing the ANSWERED status shown on revisit after an abort - Expanded view shows the question text and an "Aborted by the user" label with a red CircleX icon - INTERRUPTED / Response stopped footer is suppressed when the interruption was caused by an aborted question * fix(chat): keep bottom-pinned scroll when expanding a tool call When the chat is pinned to the bottom and the user opens a collapsible tool call, skip stopScroll() so stick-to-bottom's resize handler auto-scrolls to reveal the expanded content instead of hiding it behind the prompt input. * fix(chat): exclude scrollbar column from input footer gradient * fix(chat): stop scroll jump when expanding any tool call Remove the overly-broad "scroll to bottom if last trigger" heuristic that used a DOM query to find the last [data-tool-trigger] in the container. This was wrong — it would fire even when there were messages below the tool call being expanded. Now ConversationContent simply unpins from bottom on any tool trigger click, preventing the resize handler from jumping the scroll position. Nothing more. * feat(chat): always use ask_user tool for questions in Superset Two-pronged approach to ensure the LLM never asks questions as plain text (which bypasses the question overlay) and always uses the ask_user tool: 1. AGENTS.md — adds a project-level override rule that loads into the mastracode system prompt for any session in the Superset workspace. 2. host-service — writes a managed ~/.mastracode/AGENTS.md with the same rule, applied globally to every workspace opened in the desktop app. Uses a managed-by marker to avoid overwriting user-authored files. Root cause: the default mastracode tool guidance says "Don't use this for simple yes/no — just ask in your text response." These rules override that by being appended to the system prompt after the base instructions. * feat(chat): pending question drives workspace nav status and native notification - When ask_user tool fires, emit PendingQuestion lifecycle event from the harness subscriber (same pipeline as PermissionRequest/Start/Stop) - useAgentHookListener maps PendingQuestion → "permission" pane status, showing the orange dot in the workspace nav immediately, even when the tab is not focused - NotificationManager plays sound and shows "Awaiting Response" native toast for PendingQuestion events with visibility suppression - Cancel tooltip added to QuestionInputOverlay X button - Cancelled question tool calls now show question text + "Aborted by the user" immediately on stop, without requiring a page reload - isInterrupted prop threaded through MessagePartsRenderer → ToolCallBlock → AskUserQuestionToolCall so pending questions show CANCELLED status when the run is interrupted - INTERRUPTED badge is always shown alongside cancelled question state * fix(chat): clear orange dot on answer submit, focus prompt on dismiss, no green dot when tab is focused - Clear pane status to idle immediately when user submits a question answer - Focus prompt textarea when question overlay dismisses (rAF to let overlay unmount and browser focus settle) - Fix Stop event idle/review determination: read URL from hash (not pathname, which is always the file path in hash-routed Electron app), and add focusedPaneIds as a reliable fallback so panes the user is actively interacting with don't receive a spurious green dot - Remove MarkdownToggleContent in favour of always-on MessageResponse for subagent output * feat(chat): render subagent task prompt with markdown via MessageResponse Render the subagent task text through MessageResponse so markdown formatting (lists, bold, code spans) is applied consistently with the response text below it. * fix(chat): scale down headings and fix list layout in subagent output Headings were rendering at full browser size (text-2xl/3xl) inside the compact xs subagent block. Override h1-h6 to text-sm/xs with tighter margins, and remove the top margin on first-child paragraphs inside list items to fix ordered list numbers appearing on a separate line. * feat(chat): render read file tool output with syntax-highlighted code viewer - ReadOnlyToolCall fetches file content directly via tRPC filesystem.readFile (same path as the file pane) instead of parsing MCP tool output, eliminating metadata artifacts like line-number prefixes and byte-count headers - Uses shared detectLanguage() for syntax highlighting language detection - Renders with CodeBlock (Shiki) with a filename + line-range header styled like table headers (bg-muted/50), showLineNumbers, and colorize=false for plain white text - Adds colorize prop to CodeBlock to suppress syntax colors while keeping line numbers at reduced opacity - Passes workspaceId/workspaceCwd from ToolCallBlock to ReadOnlyToolCall - Fixes withoutActiveTurnAssistantHistory to preserve completed prior-phase assistant messages (e.g. read-file before a question answer) by keeping messages that have a stopReason and a different id from currentMessage, preventing tool calls from disappearing after answering a question * feat(chat): improve tool call error and task_write UX - ToolCallRow: replace XIcon with "ERROR" label + XCircleIcon in description slot on error - SupersetToolCall: add subtitle prop, render output content via MessageResponse instead of raw JSON - TaskWriteToolCall: new component for task_write — "Update Tasks" title with ListTodoIcon and semantic description (task count + status breakdown) * fix(chat): add vertical padding to read file tool content area * feat(chat): add LspInspectToolCall with ActivityIcon and file subtitle * fix(chat): handle mastra_workspace_lsp_inspect tool name alias * fix(chat): use FileSearchIcon for LSP Inspect tool call * feat(chat): show input/output content in LSP Inspect tool call * fix(chat): use SearchCheckIcon for LSP Inspect tool call * fix(chat): extract TOOL_CALL_MD_CLASSNAME for global compact markdown in tool calls Adds inline code (text-xs) fix alongside existing heading overrides. SubagentToolCall and SupersetToolCall now share the same constant so future patches only need to happen in one place. * feat(chat): improve subagent tool call display and share read-file component - Filter empty messages (step-start/source-only) from chat history - Add expandable content with subtitles to subagent inner tool calls (Read, List Files, Search, Write, Edit, Web) - Extract shared ReadFileTool component to packages/ui for reuse across main and subagent tool calls - Subagent read tool now shows styled CodeBlock with syntax highlighting, matching main agent display - Thread workspaceId/workspaceCwd/onOpenFileInPane through SubagentToolCall → SubagentInnerToolCall so subagent read calls show the open-in-pane button * fix(chat): forward workspace props to ReadOnlyToolCall in MessagePartsRenderer workspaceId and workspaceCwd were available in MessagePartsRenderer but not forwarded to ReadOnlyToolCall, silently disabling the disk-read feature for read_file tool calls rendered via that path. * fix(chat): remove dead code from MessageList - Remove interruptedByAbortedQuestion which was computed but never referenced in JSX or logic - Move hasRenderableParts to after imports (was inserted between them) - Remove unused getToolName, normalizeToolName, ToolPart imports - Add comment explaining the runtime-only "error" part type cast * fix(chat): fix type safety, path normalization, and memoize in SubagentInnerToolCall - Replace as never with as BundledLanguage for language prop type safety - Replace naive path concatenation with normalizeWorkspaceFilePath to handle ./, ../, file:// and workspace boundary validation (matches ReadOnlyToolCall) - Rename shadowed normalized variable to resolvedPath in openInPane closure - Memoize parseReadFileResult call to avoid re-parsing on every render * fix(chat): add stale time and loading state to ReadOnlyToolCall file query - Add staleTime: Infinity so completed read-file tool calls don't refetch on remount (prevents IPC burst when scrolling long conversations) - Show a spinner row while the disk read is in flight instead of flashing the raw ToolInput/ToolOutput view * feat(chat): replace text input with Tiptap editor for slash commands and file mentions - Add TiptapPromptEditor with ProseMirror-based rich text input - Slash command chips (/command) as inline atom nodes, insertable anywhere in message - File mention chips (@path) anchored to cursor position via virtual float - SlashCommandMenu width matches prompt input via --radix-popover-trigger-width - Selecting a command inserts a chip node instead of immediately submitting - Popover closes on editor blur, reopens on focus - Tab no longer auto-selects commands (only Enter selects) - serializeEditorToText serializes chip nodes to /name and @path for submission * feat(chat): add skill preload — /command chips trigger skill tool calls before LLM - Add SkillToolCall component (ZapIcon, Skill(name) title, success/error state) - Register SkillToolCall in ToolCallBlock for tool names 'skill' and 'load_skill' - In ChatPaneInterface.handleSend: extract custom command chip names from content, strip leading / from message text, pass names as metadata.skills to sendMessage - Add skills?: string[] to sendMessageInput metadata schema (zod.ts) - Pass preloadSkills to harness.sendMessage in service.ts - Add ChatSendMessageInput.metadata.skills type field - Add docs/skill-preload-feature.md with implementation state and setup instructions Requires superset-sh/mastra#9 for harness.sendMessage preloadSkills support and .claude/commands/ being included in skillPaths. * feat(ui): update shared AI element components for chat UX - braille-spinner: improve animation timing - code-block: add copy button and syntax highlight tweaks - message: simplify prose class handling - prompt-input: add focusShortcutText prop support - tool-call-row: tighten collapsible layout and spacing - input-group: support rounded-full variant - globals.css: add chat-specific scrollbar and prose overrides * fix(chat): improve tool call display components - AskUserQuestionToolCall: redesign option layout with better button styling - SupersetToolCall: render markdown content in tool output - SubagentToolCall/SubagentInnerToolCall: tighten display, fix edge cases - TaskWriteToolCall: simplify status rendering - ReadOnlyToolCall: add workspace prop forwarding and memoize file query - QuestionInputOverlay: improve option button layout - MessageList: remove unused prop * fix(chat): update message list and subagent execution display - ChatMessageList: update subagent message grouping and rendering - SubagentExecutionMessage: improve tool call display during subagent runs - messageListHelpers: refine pending/streaming message detection - use-chat-display: minor hook cleanup - screens/main ChatPaneInterface: propagate workspace props * fix(chat): suppress empty assistant message wrappers When an assistant message has no renderable content (e.g. redacted_thinking or unrecognized AI SDK step markers), return null instead of rendering empty Message/MessageContent divs that cause blank gaps between messages. * feat(chat): show styled "Not Configured" state for LSP inspect when LSP is absent Detect the "LSP is not configured for this workspace" error and surface it as a red "Not Configured" badge in the tool call row rather than triggering the generic error styling. * feat(chat): universal "not configured" warning on tool call rows Detect "not configured" errors in getGenericToolCallState and surface them as a filled amber warning triangle with a "Not configured" tooltip in the ToolCallRow status area. File name description is preserved. GenericToolCall passes isNotConfigured through so the treatment applies to all tool calls, not just LSP Inspect. * fix(chat): move not-configured warning icon inline after description Show the outlined amber TriangleAlertIcon next to the file name in the description area instead of in the right-side status slot. * feat(chat): clickable file names on file-related tool call rows Replace the standalone open-in-pane icon button with a hover-underline treatment directly on the filename. Applies to Read, Check file, Write, Edit, Delete, and Smart Edit tool call rows. Extracts a shared ClickableFilePath component (span[role=button]) that nests safely inside CollapsibleTrigger without invalid nested-button HTML. * feat(ui): add ShowCode component with expand/collapse, copy, and startLine support Adds a new ShowCode component that unifies all code display surfaces — tool call file views and markdown code fences — into a single block with: - Filename/language header with optional clickable file path and line range - Expand/collapse toggle (appears when content exceeds ~15 lines) - Copy and open-in-pane action buttons in the header - startLine offset for partial-file display (line numbers count from the correct offset rather than always starting at 1) - Language fallback in highlightCode: unknown Shiki languages silently retry as "text" instead of throwing * refactor: replace legacy syntax highlighters with ShowCode - ReadFileTool: swap inline CodeBlock + duplicated header/button JSX for a single <ShowCode> — removes the fragile [&>div>div]:max-h-[300px] deep selector and the duplicated open-in-pane button pattern flagged in code review - MarkdownRenderer/CodeBlock (desktop): replace react-syntax-highlighter (Prism) with ShowCode, aligning markdown code fences with the shared Shiki-based highlighter used throughout the chat UI * fix(chat): address PR review feedback - Move useMemo hooks before early returns in AskUserQuestionToolCall and SubagentInnerToolCall to fix React Rules of Hooks violations - Use hasFileContent (content !== undefined) guard in ReadOnlyToolCall so empty files render in the code viewer instead of falling back - Tighten @mention regex to require word-boundary before @ so email addresses and decorators are not rewritten as file mentions on round-trip - Add trigger to ScrollAnchor useEffect deps so footerScrollTrigger bumps actually retrigger the scroll effect - Add pendingQuestion to bumpFooterScroll useEffect deps in ChatPaneInterface so the chat scrolls when the question overlay appears or disappears - Roll back optimistic answeredQuestionId and pane status if respondToQuestion RPC fails in legacy ChatPaneInterface - Guard Shiki highlightCode fallback so it does not recurse infinitely when language === "text" already - Add aria-label to icon-only buttons (expand/collapse, open, copy) in ShowCode - Remove dead _interruptedByAbortedQuestion useMemo from legacy ChatMessageList * fix(chat): address second round of PR review feedback - Add hasPendingQuestionToolCall to workspace path messageListHelpers so assistant messages with active ask_user calls stay visible during a run - Reset QuestionInputOverlay state (customText, submittedLabel) when the question prop changes identity, not just on mount - Fix Enter/Tab in file-mention mode only consuming the event when a file is actually selected; falls through to normal submit when results are empty - Keep isError indicator visible in ToolCallRow status slot when the row is expanded (previously the error icon disappeared on open) * fix(chat): address third round of PR review feedback - Fix colorize=false fading first code token when line numbers are disabled: add a shiki-line-number class to gutter spans and target that class instead of span:first-child in the CSS selector - Add e.preventDefault() to Space key handler in ClickableFilePath so activating via keyboard does not also scroll the container - Fix file mention round-trip for paths containing spaces: serializer now emits @"path with spaces" and parser handles both quoted and unquoted forms - Add null guard in FileMentionNode so malformed/pasted content with a missing path attr does not crash rendering * fix(chat): address fourth round of PR review feedback - Fix ReadOnlyToolCall lineRange: disk read always returns the whole file so always display 1–N (trimming trailing newline before counting) - Fix Shiki fallback to render escaped plain text instead of empty strings when codeToHtml fails for the "text" language itself - Trim trailing newline before computing lineCount in ShowCode so files ending with \n do not trigger isOverflowing one line early * chore: formatting and cleanup * fix(chat): close mention popup before falling through Enter when results empty * fix(chat): address fifth round of PR review feedback - Re-add inputValue to SlashCommandPreviewPopover anchor effect deps so the virtual anchor re-measures when typing shifts the chip's position - Clamp slash menu selectedIndex in onUpdate when filtered results shrink, matching the existing mention-menu clamping behavior - Return true (consume event) when Enter/Tab closes empty mention popup so the event does not propagate to insert a paragraph break - Mirror focus-on-dismiss effect in v2 workspace ChatInputFooter so the editor regains focus after the question overlay unmounts - Fix trailing-slash paths rendering empty label in ClickableFilePath by using || instead of ?? for the basename fallback * chore: rebuild bun.lock after rebase * Fix typecheck * refactor(chat): align skill handling with upstream mastra Removes the fork-dependent preload wiring (metadata.skills → preloadSkills pass-through) that was a silent no-op on upstream mastracode. Keeps the SkillToolCall renderer so load_skill tool calls emitted by upstream's native skills system render with their own UI. Rewrites docs/skill-preload-feature.md to describe the upstream agent-autonomous model (SKILL.md discovery in .claude/skills, .agents/skills, .mastracode/skills). * chore(deps): bump mastra to 0.15.0-alpha.3 / 1.26.0-alpha.3 Brings in upstream mastra's native skills system (search_skills + load_skill tools, SKILL.md discovery via skillPaths) which the SkillToolCall renderer in this PR now consumes for free. - mastracode: 0.14.0 → 0.15.0-alpha.3 - @mastra/core: 1.25.0 → 1.26.0-alpha.3 - @mastra/mcp: 1.3.1 → 1.5.1-alpha.1 Applied in apps/desktop, packages/chat, packages/host-service. * chore: alphabetize @tiptap/pm in desktop package.json Auto-applied by biome/sherif. * fix(chat): cut display polling to 4fps and restore query cache defaults (#3562) Memory leak and CPU spiral root-caused to `staleTime: 0, gcTime: 0` + 60fps polling: React Query can't dedupe or GC anything, and the render path churns allocations every 16ms. Restoring the React Query defaults (5min gcTime) fixes the leak. Server poll rate is independent of perceived stream smoothness — StreamingMessageText already reveals text client-side at 60fps from whatever buffer the server delivers. 4fps polling keeps that buffer fed with plenty of headroom. Also removes the `isRunning` invalidation effect — redundant when the query is polling. Builds on and supersedes #3170 by @thepathmakerz, which diagnosed the same root cause. This version takes the subtractive path (-21 lines) instead of adaptive polling (+36). Closes #3049 * feat(chat): slash command chip UX enhancements - Argument editing inline in chip: auto-focus on insert, right-arrow to exit, double-click to re-enter - Commands without argumentHint hide the colon/input entirely - Model command shows a dropdown of available models (no free-form text) - Chip input auto-sizes as user types (shrinks to content width) - Dropdown positioned above chip (side="top"), ArrowUp/Down navigate options, Tab/Enter commit selection - Menu reopens automatically when deleting value back to empty - Preview popover and select dropdown are mutually exclusive (preview only on hover/node-select, never while arg input is focused) - Focus shortcut hint moved inside TiptapPromptEditor (accepts focusShortcutText prop) * chore: refresh bun.lock after pull * test(chat): drop shallow ChatMessageList snapshot tests These tests replace every child component with a mock placeholder and assert on literal strings appearing in the rendered HTML. That tested mock plumbing, not behavior — every SUT import change broke them regardless of whether the actual render output changed, and the 'SUBAGENT_EXECUTION_MESSAGE' assertion was checking for a component this PR intentionally inlined. The useful bit (filter/ordering logic in messageListHelpers) is better covered by a direct unit test — leaving as a follow-up.
1 parent db0cd20 commit 99a1ca6

83 files changed

Lines changed: 4488 additions & 2108 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

AGENTS.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Superset Monorepo Guide
22

3+
## Question Tool
4+
5+
When you need to ask the user ANY question — including simple yes/no, confirmations, and clarifications — ALWAYS use the `ask_user` tool. Never ask questions in plain text. The Superset UI renders `ask_user` calls as an interactive overlay with clickable option buttons; plain-text questions will not be surfaced to the user in the same way.
6+
37
Guidelines for agents and developers working in this repository.
48

59
## Structure

apps/desktop/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@
7272
"@hono/node-server": "^1.14.1",
7373
"@hookform/resolvers": "^5.2.2",
7474
"@lezer/highlight": "^1.2.3",
75-
"@mastra/core": "1.25.0",
75+
"@mastra/core": "1.26.0-alpha.3",
7676
"@parcel/watcher": "^2.5.6",
7777
"@pierre/diffs": "1.1.3",
7878
"@radix-ui/react-dialog": "^1.1.15",
@@ -130,6 +130,7 @@
130130
"@tiptap/extension-task-list": "^3.17.1",
131131
"@tiptap/extension-text": "^3.17.1",
132132
"@tiptap/extension-underline": "^3.17.1",
133+
"@tiptap/pm": "^3.17.1",
133134
"@tiptap/react": "^3.17.1",
134135
"@tiptap/starter-kit": "^3.17.1",
135136
"@tiptap/suggestion": "^3.17.1",
@@ -182,7 +183,7 @@
182183
"lowdb": "^7.0.1",
183184
"lowlight": "^3.3.0",
184185
"lucide-react": "^0.563.0",
185-
"mastracode": "0.14.0",
186+
"mastracode": "0.15.0-alpha.3",
186187
"nanoid": "^5.1.6",
187188
"node-addon-api": "^7.1.0",
188189
"node-pty": "1.1.0",
@@ -209,6 +210,7 @@
209210
"semver": "^7.7.3",
210211
"shell-env": "^4.0.3",
211212
"shell-quote": "^1.8.3",
213+
"shiki": "^3.21.0",
212214
"simple-git": "^3.30.0",
213215
"streamdown": "2.5.0",
214216
"strip-ansi": "^7.1.2",

apps/desktop/src/main/lib/notifications/notification-manager.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,16 @@ export class NotificationManager {
6464
const title = this.deps.getNotificationTitle(event);
6565

6666
const isPermissionRequest = event.eventType === "PermissionRequest";
67+
const isPendingQuestion = event.eventType === "PendingQuestion";
6768
const notification = this.deps.createNotification({
68-
title: isPermissionRequest
69-
? `Input Needed — ${workspaceName}`
70-
: `Agent Complete — ${workspaceName}`,
71-
body: isPermissionRequest
72-
? `"${title}" needs your attention`
73-
: `"${title}" has finished its task`,
69+
title:
70+
isPermissionRequest || isPendingQuestion
71+
? `Awaiting Response — ${workspaceName}`
72+
: `Agent Complete — ${workspaceName}`,
73+
body:
74+
isPermissionRequest || isPendingQuestion
75+
? `"${title}" is waiting for your reply`
76+
: `"${title}" has finished its task`,
7477
silent: true,
7578
});
7679

apps/desktop/src/renderer/components/Chat/ChatInterface/components/ChatInputFooter/ChatInputFooter.tsx

Lines changed: 103 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,25 @@ import {
33
PromptInputAttachment,
44
PromptInputAttachments,
55
type PromptInputMessage,
6-
PromptInputTextarea,
6+
usePromptInputController,
77
} from "@superset/ui/ai-elements/prompt-input";
88
import type { ThinkingLevel } from "@superset/ui/ai-elements/thinking-toggle";
99
import type { ChatStatus, FileUIPart } from "ai";
1010
import type React from "react";
1111
import type { ReactNode } from "react";
12-
import { useCallback, useRef, useState } from "react";
12+
import { useCallback, useEffect, useRef, useState } from "react";
1313
import { useFocusPromptOnPane } from "renderer/components/Chat/ChatInterface/hooks/useFocusPromptOnPane";
1414
import { useHotkeyDisplay } from "renderer/hotkeys";
1515
import type { SlashCommand } from "../../hooks/useSlashCommands";
1616
import type { ModelOption, PermissionMode } from "../../types";
1717
import { IssueLinkCommand } from "../IssueLinkCommand";
18-
import { MentionAnchor, MentionProvider } from "../MentionPopover";
19-
import { SlashCommandInput } from "../SlashCommandInput";
18+
import { TiptapPromptEditor } from "../TiptapPromptEditor";
2019
import { ChatComposerControls } from "./components/ChatComposerControls";
2120
import { ChatInputDropZone } from "./components/ChatInputDropZone";
2221
import { ChatShortcuts } from "./components/ChatShortcuts";
2322
import { FileDropOverlay } from "./components/FileDropOverlay";
2423
import { LinkedIssues } from "./components/LinkedIssues";
25-
import { SlashCommandPreview } from "./components/SlashCommandPreview";
24+
import { QuestionInputOverlay } from "./components/QuestionInputOverlay";
2625
import type { LinkedIssue } from "./types";
2726
import { getErrorMessage } from "./utils/getErrorMessage";
2827

@@ -48,7 +47,14 @@ interface ChatInputFooterProps {
4847
onSubmitEnd?: () => void;
4948
onSend: (message: PromptInputMessage) => Promise<void> | void;
5049
onStop: (e: React.MouseEvent) => void;
51-
onSlashCommandSend: (command: SlashCommand) => void;
50+
pendingQuestion?: {
51+
questionId: string;
52+
question: string;
53+
options?: { label: string; description?: string }[];
54+
} | null;
55+
isQuestionSubmitting?: boolean;
56+
onQuestionRespond?: (questionId: string, answer: string) => Promise<void>;
57+
onQuestionCancel?: () => void;
5258
}
5359

5460
export function ChatInputFooter({
@@ -73,9 +79,27 @@ export function ChatInputFooter({
7379
onSubmitEnd,
7480
onSend,
7581
onStop,
76-
onSlashCommandSend,
82+
pendingQuestion,
83+
isQuestionSubmitting,
84+
onQuestionRespond,
85+
onQuestionCancel,
7786
}: ChatInputFooterProps) {
7887
useFocusPromptOnPane(isFocused);
88+
89+
// Focus the prompt when the question overlay dismisses (pendingQuestion → null).
90+
// Uses rAF so the editor has time to mount, register its ref, and browser
91+
// focus-stealing from the unmounting overlay has settled.
92+
const { textInput } = usePromptInputController();
93+
const prevPendingQuestionRef = useRef(pendingQuestion);
94+
useEffect(() => {
95+
const prev = prevPendingQuestionRef.current;
96+
prevPendingQuestionRef.current = pendingQuestion;
97+
if (prev != null && pendingQuestion == null) {
98+
const id = requestAnimationFrame(() => textInput.focus());
99+
return () => cancelAnimationFrame(id);
100+
}
101+
}, [pendingQuestion, textInput]);
102+
79103
const [issueLinkOpen, setIssueLinkOpen] = useState(false);
80104
const [linkedIssues, setLinkedIssues] = useState<LinkedIssue[]>([]);
81105
const inputRootRef = useRef<HTMLDivElement>(null);
@@ -115,7 +139,7 @@ export function ChatInputFooter({
115139
);
116140

117141
return (
118-
<ChatInputDropZone className="bg-background px-4 py-3">
142+
<ChatInputDropZone className="relative bg-background px-4 pb-3 before:pointer-events-none before:absolute before:left-0 before:right-3 before:-top-8 before:h-8 before:bg-gradient-to-t before:from-background before:to-transparent">
119143
{(dragType) => (
120144
<div className="mx-auto w-full max-w-[680px]">
121145
{errorMessage && (
@@ -126,82 +150,78 @@ export function ChatInputFooter({
126150
{errorMessage}
127151
</p>
128152
)}
129-
<SlashCommandInput
130-
onCommandSend={onSlashCommandSend}
131-
commands={slashCommands}
132-
>
133-
<MentionProvider cwd={cwd}>
134-
<MentionAnchor>
135-
<div
136-
ref={inputRootRef}
137-
className={
138-
dragType === "path"
139-
? "relative opacity-50 transition-opacity"
140-
: "relative"
153+
{pendingQuestion && onQuestionRespond && onQuestionCancel ? (
154+
<QuestionInputOverlay
155+
question={pendingQuestion}
156+
isSubmitting={isQuestionSubmitting ?? false}
157+
onRespond={onQuestionRespond}
158+
onCancel={onQuestionCancel}
159+
/>
160+
) : (
161+
<div
162+
ref={inputRootRef}
163+
className={
164+
dragType === "path"
165+
? "relative opacity-50 transition-opacity"
166+
: "relative"
167+
}
168+
>
169+
<PromptInput
170+
className="[&>[data-slot=input-group]]:rounded-[13px] [&>[data-slot=input-group]]:border-[0.5px] [&>[data-slot=input-group]]:shadow-none [&>[data-slot=input-group]]:bg-foreground/[0.02]"
171+
onSubmitStart={onSubmitStart}
172+
onSubmitEnd={onSubmitEnd}
173+
onSubmit={handleSend}
174+
multiple
175+
maxFiles={5}
176+
maxFileSize={10 * 1024 * 1024}
177+
globalDrop
178+
>
179+
<ChatShortcuts
180+
isFocused={isFocused}
181+
setIssueLinkOpen={setIssueLinkOpen}
182+
/>
183+
<IssueLinkCommand
184+
open={issueLinkOpen}
185+
onOpenChange={setIssueLinkOpen}
186+
onSelect={addLinkedIssue}
187+
/>
188+
<FileDropOverlay visible={dragType === "files"} />
189+
<PromptInputAttachments>
190+
{renderAttachment ??
191+
((file) => <PromptInputAttachment data={file} />)}
192+
</PromptInputAttachments>
193+
<LinkedIssues
194+
issues={linkedIssues}
195+
onRemove={removeLinkedIssue}
196+
/>
197+
<TiptapPromptEditor
198+
cwd={cwd}
199+
slashCommands={slashCommands}
200+
availableModels={availableModels}
201+
placeholder="Ask to make changes, @mention files, run /commands"
202+
focusShortcutText={
203+
showFocusHint ? focusShortcutText : undefined
141204
}
142-
>
143-
{showFocusHint && (
144-
<span className="pointer-events-none absolute top-3 right-3 z-10 text-xs text-muted-foreground/50 [:focus-within>&]:hidden">
145-
{focusShortcutText} to focus
146-
</span>
147-
)}
148-
<PromptInput
149-
className="[&>[data-slot=input-group]]:rounded-[13px] [&>[data-slot=input-group]]:border-[0.5px] [&>[data-slot=input-group]]:shadow-none [&>[data-slot=input-group]]:bg-foreground/[0.02]"
150-
onSubmitStart={onSubmitStart}
151-
onSubmitEnd={onSubmitEnd}
152-
onSubmit={handleSend}
153-
multiple
154-
maxFiles={5}
155-
maxFileSize={10 * 1024 * 1024}
156-
globalDrop
157-
>
158-
<ChatShortcuts
159-
isFocused={isFocused}
160-
setIssueLinkOpen={setIssueLinkOpen}
161-
/>
162-
<IssueLinkCommand
163-
open={issueLinkOpen}
164-
onOpenChange={setIssueLinkOpen}
165-
onSelect={addLinkedIssue}
166-
/>
167-
<FileDropOverlay visible={dragType === "files"} />
168-
<PromptInputAttachments>
169-
{renderAttachment ??
170-
((file) => <PromptInputAttachment data={file} />)}
171-
</PromptInputAttachments>
172-
<LinkedIssues
173-
issues={linkedIssues}
174-
onRemove={removeLinkedIssue}
175-
/>
176-
<SlashCommandPreview
177-
cwd={cwd}
178-
slashCommands={slashCommands}
179-
/>
180-
<PromptInputTextarea
181-
placeholder="Ask to make changes, @mention files, run /commands"
182-
className="min-h-10"
183-
/>
184-
<ChatComposerControls
185-
availableModels={availableModels}
186-
selectedModel={selectedModel}
187-
setSelectedModel={setSelectedModel}
188-
modelSelectorOpen={modelSelectorOpen}
189-
setModelSelectorOpen={setModelSelectorOpen}
190-
permissionMode={permissionMode}
191-
setPermissionMode={setPermissionMode}
192-
thinkingLevel={thinkingLevel}
193-
setThinkingLevel={setThinkingLevel}
194-
canAbort={canAbort}
195-
submitStatus={submitStatus}
196-
submitDisabled={submitDisabled}
197-
onStop={onStop}
198-
onLinkIssue={() => setIssueLinkOpen(true)}
199-
/>
200-
</PromptInput>
201-
</div>
202-
</MentionAnchor>
203-
</MentionProvider>
204-
</SlashCommandInput>
205+
/>
206+
<ChatComposerControls
207+
availableModels={availableModels}
208+
selectedModel={selectedModel}
209+
setSelectedModel={setSelectedModel}
210+
modelSelectorOpen={modelSelectorOpen}
211+
setModelSelectorOpen={setModelSelectorOpen}
212+
permissionMode={permissionMode}
213+
setPermissionMode={setPermissionMode}
214+
thinkingLevel={thinkingLevel}
215+
setThinkingLevel={setThinkingLevel}
216+
canAbort={canAbort}
217+
submitStatus={submitStatus}
218+
submitDisabled={submitDisabled}
219+
onStop={onStop}
220+
onLinkIssue={() => setIssueLinkOpen(true)}
221+
/>
222+
</PromptInput>
223+
</div>
224+
)}
205225
<div className="py-1.5" />
206226
</div>
207227
)}

0 commit comments

Comments
 (0)