Add [aryx oauth] log messages throughout the proactive auth flow so
failures are visible in the Electron main process console (DevTools →
Main Process or terminal output). Logs cover: probe start, HTTP status,
PRM discovery, token skip, flow start, and success/failure.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When a user enables an HTTP MCP server for a session, proactively probe
the server URL. If it returns 401 and has discoverable OAuth metadata
(RFC 9728), automatically trigger the full OAuth 2.1 + PKCE flow and
open the browser for consent — matching VS Code's behavior.
- Add requiresOAuth() probe: GET server URL → check 401 → check PRM
- Add probeAndAuthenticateHttpMcpServers() in AryxAppService
- Call proactive probe from updateSessionTooling (fire-and-forget)
- Skip servers that already have stored tokens
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Create mcpTokenStore: in-memory token storage keyed by normalized server URL
with automatic expiry checking
- Create mcpOAuthService: full OAuth 2.1 + PKCE flow implementation
- Protected Resource Metadata discovery (RFC 9728)
- Authorization Server Metadata fetch (RFC 8414)
- Dynamic Client Registration (RFC 7591) when no static client ID
- PKCE S256 code challenge generation
- Local HTTP callback server for auth code receipt
- Browser-based consent via Electron shell.openExternal
- Authorization code to token exchange
- Add startSessionMcpAuth IPC channel and handler to trigger OAuth flow
- Inject stored OAuth tokens as Authorization headers in buildRunTurnToolingConfig
- Update McpAuthBanner with 'Authenticate in browser' button and loading state
- Add tests for token store (7 tests) and token injection (3 tests)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add McpOauthRequiredEvent and McpOauthStaticClientConfigEvent to sidecar contracts
- Add PendingMcpAuthRecord domain type with status tracking (pending/authenticating/failed/done)
- Add pendingMcpAuth field to SessionRecord for session-level auth state
- Wire onMcpOAuthRequired callback through sidecarProcess, runTurnPending, and AryxAppService
- Handle mcp-oauth-required events: set session pendingMcpAuth, clear on turn finalize/cancel
- Add dismissSessionMcpAuth IPC channel with preload binding and handler registration
- Create McpAuthBanner component (amber-themed, shows server name/URL, dismiss button)
- Integrate McpAuthBanner into ChatPane with placeholder text and App.tsx callback
- Update test fixtures for new RunTurnPendingCommand.onMcpOAuthRequired field
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Move plan mode toggle from pills row to composer send button area
- Plan toggle sits next to the send button as a mode switch icon
- Send button turns emerald when plan mode is active
- Add mode indicator strip below composer when plan mode is on
- Replace 'Implement this plan' auto-send button with instructional
guidance text that tells the user to send a follow-up message
- Keep Dismiss button to clear the plan review banner
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add InteractionMode type and ExitPlanModeRequestedEvent to sidecar contracts
- Add PendingPlanReviewRecord domain type and interactionMode to SessionRecord
- Wire onExitPlanMode callback through SidecarClient and RunTurnPendingCommand
- Pass session interaction mode to RunTurnCommand for sidecar consumption
- Handle exit-plan-mode-requested events in AryxAppService
- Add setSessionInteractionMode and dismissSessionPlanReview IPC methods
- Create PlanReviewBanner component with summary, markdown content, and actions
- Add plan mode toggle pill in ChatPane composer area
- Wire implement action as follow-up message (graceful degradation)
- Clear pending plan review on new turn, turn completion, and cancellation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a builtin tool label registry mapping all ~30 official SDK tool IDs
to human-readable display names. Update listApprovalToolDefinitions to
resolve labels from the registry instead of showing raw tool IDs like
read_bash or fetch_copilot_cli_documentation. Expand the fallback
runtime tool catalog from 6 to 23 non-internal standard tools.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Wire the sidecar user-input-requested protocol event through the main
process, IPC layer, and renderer UI so agents can ask the user
interactive questions with choices and freeform input.
- Add UserInputRequestedEvent and ResolveUserInputCommand to sidecar
protocol types
- Add resolveUserInput method to SidecarClient and onUserInput callback
to runTurn
- Create PendingUserInputRecord domain type and add pendingUserInput to
SessionRecord
- Add handleUserInputRequested and resolveSessionUserInput to
AryxAppService with handle management
- Register sessions:resolve-user-input IPC channel with preload bridge
- Create UserInputBanner component with choice buttons and freeform
input
- Integrate UserInputBanner into ChatPane with header indicator
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use createRequire for node:child_process so Bun on macOS can load gitService without ESM export-shape issues.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Resolve execFile from named or default child_process exports so macOS Bun can load gitService in tests.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Generalize the scratchpad-only model/reasoning-effort selector to work
in any session whose pattern has exactly one agent. This enables model
switching above the chat input for project 1-on-1 chats, not just
scratchpads.
- Rename ScratchpadSessionConfig -> SessionModelConfig across domain,
contracts, IPC, preload, and renderer
- Replace isScratchpadProject guard with agents.length === 1 check in
EryxAppService.updateSessionModelConfig and ChatPane pills gate
- Apply session model config in buildEffectivePattern whenever present,
regardless of project type
- Seed sessionModelConfig on session creation for any single-agent
pattern
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Clear only active agent statuses when a session reaches idle, and keep the terminal cancelled/error run cleanup in place. Add regression coverage for idle cleanup after cancellation-like flows.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Clear only still-active side-panel agent states when a run ends as cancelled or error, while preserving completed agent statuses. Add a renderer regression test for cancelled runs.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds cancel-turn IPC channel, SidecarClient.cancelTurn(), TurnCancelledError,
EryxAppService.cancelSessionTurn(), finalizeCancelledTurn(), stop button in
ChatPane, and cancelled run status throughout the run timeline.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Resolve chat tooling from the selected session's project instead of the
separately tracked selectedProjectId so project-scoped discovered MCPs do
not appear for the wrong session. Also synchronize selectedProjectId when
a session is selected and add regression tests for both behaviors.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move the Tool Auto-Approval Defaults section into the tool-call
ApprovalCheckpointRow card so the tool list appears directly under
the toggle instead of in a disconnected section below. Extend
ApprovalCheckpointRow with an optional children prop rendered
inside the card when the checkpoint is enabled.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a Troubleshooting section under a Support nav group in Settings:
- Open App Data Folder: reveals the user-data directory in the OS file
explorer via shell.openPath.
- Reset Local Workspace: destructive action that deletes workspace.json
and scratchpad contents, clears the in-memory cache, and reseeds
defaults. Auth secrets are preserved. Requires two-step confirmation.
New IPC channels, ElectronApi methods, and preload bridge added for
openAppDataFolder and resetLocalWorkspace. Existing electron mock in
tests updated to include shell.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove .pill-trigger CSS class and restore the idiomatic Tailwind v4
@theme approach: --text-pill in @theme generates the text-pill utility.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The @theme --text-pill token and text-[8px] arbitrary value both
failed to apply at dev time due to @tailwindcss/vite not generating
the utility rule during HMR. Use a plain .pill-trigger CSS class
in styles.css instead, which is always available regardless of
Tailwind's JIT scanner behavior.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The arbitrary text-[8px] class was not being generated by the
Tailwind v4 Vite plugin at dev time. Register --text-pill in
@theme so text-pill becomes a first-class utility that is always
generated. Uses 0.5rem (8px) with line-height 1.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add explicit leading-none alongside text-[8px] so both font-size
and line-height shrink, making the size reduction visually apparent.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Reduce font to text-[8px], restore icons to size-2.5 for legibility,
and bump padding to px-1.5 py-0.5 so icons aren't flush with the
pill border.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Shrink padding (px-2 py-1 → px-1.5 py-0.5), font (12px → 11px),
gap (1.5 → 1), icon size (3 → 2.5), and model name max-width
(140px → 120px) on all four pill trigger buttons. Dropdown popovers
remain unchanged.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The useCallback for handleCreateScratchpad was placed after the early
return for the loading state, causing React to see a different number
of hooks between the initial render (workspace undefined) and subsequent
renders. Move it before the guard and add a null check inside instead.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PrismJS's CJS global state is fundamentally incompatible with Vite's ESM
dev mode, causing missing language support (~40% of dropdown languages)
and fragile loading hacks (optimizeDeps, global assignment).
Replace the entire Prism integration with highlight.js/lib/common which
is ESM-native, has no global state, and covers all 22 dropdown languages
out of the box (~37 common languages included).
Implementation:
- Custom CodeHighlightPlugin using hljs.highlight() for tokenization
- HTML parser extracts flat tokens from hljs output (handles nested spans)
- Token type mapping from hljs scopes to Lexical codeHighlight theme keys
- Selection preservation via absolute character offset save/restore
- Re-entrancy guard prevents infinite transform loops
- Removed @lexical/code-prism import and Vite optimizeDeps workaround
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove the prismSetup module that imported prismjs directly — it
caused Vite to pre-bundle prismjs into a separate chunk from
@lexical/code-prism, breaking the CJS require chain. Language
components tried to call Prism.languages.extend() before the base
grammars were available, crashing the app.
Instead, add optimizeDeps.include for prismjs and @lexical/code-prism
so Vite pre-bundles them together, preserving the correct CJS
evaluation order within a single chunk.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The previous approach imported prismjs component files directly in
MarkdownComposer.tsx. In Vite dev mode, each component is served
as a separate ESM module whose IIFE references a bare 'Prism' global
that doesn't exist yet, crashing the app.
Fix: create a dedicated prismSetup module that imports prismjs and
assigns globalThis.Prism, then import it from the renderer entry
point (main.tsx) before any component that loads @lexical/code-prism.
ESM evaluation order guarantees the global is set before the
tokenizer captures its reference.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Register CodeHighlightNode in the editor node set
- Add CodeHighlightPlugin that calls registerCodeHighlighting
- Map 20+ Prism token types to mc-tok-* CSS classes with dark theme colors
- Explicitly import prismjs + language components and set globalThis.Prism
before @lexical/code-prism loads, fixing a Vite dev-mode race where
the CJS shim could initialize the tokenizer before the global was set
- Added @types/prismjs for type checking
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The imperative approach inserted overlay elements directly into
Lexical-managed CodeNode DOM elements. Lexical's MutationObserver
detected the foreign DOM nodes, reconciled them away, which triggered
update listeners that re-inserted them — causing an infinite loop
that froze the app.
The new approach renders overlay elements via React, positioned
absolutely in a pointer-events:none container that sits over the
content editable. Overlay positions are computed from each code
element's bounding rect and updated via requestAnimationFrame on
editor updates and scroll events. This avoids all Lexical DOM
mutation and eliminates the freeze.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Language selector:
- Replaced toolbar dropdown with an imperative overlay rendered
directly inside each code block's DOM element
- Overlay uses contentEditable='false' + position:absolute at the
top-right of the code block, styled as a compact native select
- Plugin re-inserts overlays after each Lexical reconciliation to
survive DOM diffing
Code block insertion:
- toggleCodeBlock now inserts a trailing paragraph after the code
block when it would otherwise be the last element, so the user
can always type regular text below a code block
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When the cursor is inside a code block, the formatting toolbar shows
a language dropdown (22 languages). Selecting a language updates the
CodeNode, which the markdown serializer writes as the fenced code
block language tag (e.g. \\\ ypescript).
A CodeBlockLabelPlugin syncs a data-code-language attribute on each
code block element so CSS ::before shows the friendly language name
as a small label at the top of the block.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Increase editable min-height from 40px to 52px and padding from 8px
to 10px so the send button (32px + 8px bottom offset) has 12px
clearance from the toolbar border instead of 0px
- Add display: block to .mc-code-block — Lexical CodeNode renders as
<code> which is inline by default, causing border/padding to split
visually across lines instead of forming a proper block
- Revert empty code block to code.select() without synthetic TextNode
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Move send button into MarkdownComposer children slot so it positions
relative to the editor content area, not the full container
- Always create a text node when inserting a code block to prevent
Lexical from rendering a split/empty code block artifact
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the plain <textarea> in ChatPane with a full WYSIWYG
markdown composer built on Lexical 0.42.0.
Composer features:
- Rich text editing with inline formatting (bold, italic, code)
- Block-level elements: headings, bullet/numbered lists, code blocks,
blockquotes, links via markdown shortcuts (e.g. ## , - , \\\)
- Formatting toolbar with active-state indicators
- Markdown paste auto-import when the editor is empty
- Enter to send, Shift+Enter for newline (matches prior behavior)
- Disabled state syncs with session busy/config states
- Serializes to markdown on submit via the backend helpers
Files:
- New: MarkdownComposer.tsx (Lexical editor, toolbar, internal plugins)
- Modified: ChatPane.tsx (swap textarea for MarkdownComposer, simplify state)
- Modified: styles.css (add markdown composer theme classes)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>