feat: Virtuosos — multi-account multiplexing and capacity-aware routing#391
feat: Virtuosos — multi-account multiplexing and capacity-aware routing#391openasocket wants to merge 60 commits intoRunMaestro:mainfrom
Conversation
Add foundational type definitions for the account multiplexing system: - AccountProfile, AccountUsageSnapshot, AccountAssignment for core data models - AccountSwitchConfig and AccountSwitchEvent for auto-switching behavior - AccountCapacityMetrics for capacity planning - AccountId, AccountStatus, AccountAuthMethod, MultiplexableAgent type aliases - ACCOUNT_SWITCH_DEFAULTS and DEFAULT_TOKEN_WINDOW_MS constants - accountId and accountName optional fields on Session interface Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements the persistence layer for account multiplexing: - AccountStoreData schema type for electron-store - maestro-accounts store instance in initializeStores() - AccountRegistry class with full CRUD, assignment management, round-robin/least-used selection, and switch config - 37-test suite for AccountRegistry covering all operations - Updated instances.test.ts store count assertion (8→9) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tion) - Bump STATS_DB_VERSION from 3 to 4 - Add migration v4: account_id + token/cost columns to query_events, account_usage_windows table, account_throttle_events table - Extend QueryEvent interface with accountId, token counts, and costUsd - Update insertQueryEvent INSERT to include new columns - Add account-usage.ts module with upsertAccountUsageWindow, getAccountUsageInWindow, insertThrottleEvent, getThrottleEvents - Wire new module into StatsDB class following delegated pattern - Update row-mappers for new QueryEventRow fields - Fix test expectations for version 4 and new INSERT parameters Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Registers 16 IPC handlers for the accounts: namespace covering CRUD, assignments, usage queries, throttle events, switch config, and account selection. Extends the preload accounts API with corresponding invoke methods alongside the existing event listeners from ACCT-MUX-04. Wires the account usage listener into the process listener module for real-time usage tracking and limit notifications. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ation Adds account-setup.ts with directory creation, symlink management, account discovery, email extraction, and SSH remote validation. Registers 9 IPC handlers and preload bridge methods for the full account setup lifecycle. Includes 25 unit tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds Accounts tab to Settings modal with: - Account list with status badges, default indicators, and inline config - Discover existing accounts and import them - Create new account flow with login command generation - Per-account token limit, window duration, and auto-switch toggle - Symlink validation and repair buttons - Global switch configuration (thresholds, strategy, prompt toggle) - CLAUDE_CONFIG_DIR conflict detection banner for manual overrides - MaestroAPI type declaration for window.maestro.accounts Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix TypeScript errors in AccountUsageDashboard (TS2783 duplicate property overwrites), add 'accounts' to DashboardSkeleton viewMode type, remove unused lucide imports, and update UsageDashboardModal tests for the new Accounts tab (tab count 4→5, wrap-around nav, accounts IPC mocks). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add AccountThrottleHandler that bridges rate-limit error detection with account switching. When rate_limited or auth_expired errors are detected on sessions with account assignments, records the throttle event in stats DB, marks the account as throttled, and notifies the renderer with switch recommendations (prompt or auto-switch depending on config). Also adds auto-recovery from throttle state when the usage window advances, and preload bridge events for throttle/switch/status-changed notifications. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nd resume Implements the core account switch mechanism (ACCT-MUX-10): - AccountSwitcher service: kill process → reassign account → respawn with new CLAUDE_CONFIG_DIR → re-send last prompt - Prompt recording in process:write handler for seamless resume after switch - IPC handlers for execute-switch and cleanup-session - Renderer respawn handler with toast notifications for switch lifecycle events - Preload bridge with typed events (switch-started, switch-respawn, switch-completed, switch-failed) - Session cleanup on agent deletion (removes account assignment and prompt tracking) - Type declarations in global.d.ts for all new accounts API methods - Comprehensive test suite for AccountSwitcher (7 tests) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nt selector Implements the UI layer for account multiplexing switches: - AccountSwitchModal: confirmation modal for prompted switches (throttle/limit events) with reason-based headers, account status display, and action buttons - AccountSelector: compact dropdown in InputArea footer for manual account switching per session, with status dots, usage bars, and Manage Accounts link - App.tsx: onSwitchPrompt listener shows modal, onSwitchCompleted toast notification - Added ACCOUNT_SWITCH priority (1005) to modalPriorities.ts - Added 'accounts' to SettingsTab type for View All Accounts navigation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wire CLAUDE_CONFIG_DIR injection into every code path that spawns Claude Code agents: standard process:spawn, Group Chat participants/moderators/synthesis, Context Grooming, and session resume. Add missing onAssigned type declaration to global.d.ts to fix lint error. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…y for account multiplexing Ensures account assignments survive app restarts by reconciling accounts on session restore. Adds typed accountId to spawn config, reconcileAssignments() to AccountRegistry, and an accounts:reconcile-sessions IPC handler called after session restore to validate/correct account state and CLAUDE_CONFIG_DIR. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds CLI-side account reading, --account and --account-rotation flags for the playbook command, CLAUDE_CONFIG_DIR injection in agent-spawner, round-robin account rotation in batch-processor, and a new 'accounts' command to list configured accounts from the terminal. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ations Wire up account multiplexing support for the MergeSessionModal and all merge-related operations: - Display source session account (green dot indicator) in token preview - Show target session account in tab list items during selection - Warn when merging across different accounts (info note about symlinks) - Propagate accountId/accountName to new sessions created by merge - Thread accountId through the full context grooming chain: renderer → preload → IPC handler → context-groomer utility - Add getAccountRegistry to ContextHandlerDependencies Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…and session UI surfaces - ProcessMonitor: account badge on process list items and account info card in detail view - SymphonyModal: account badge next to session name link on contribution cards - SessionList: account info in tooltip content and non-clickable account entry in context menu - All account UI gracefully hidden when no account is assigned Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tests cover: timer-based recovery of throttled accounts past their window, IPC event broadcasting (status-changed + recovery-available), multi-account recovery with correct counts, edge cases (zero lastThrottledAt, missing tokenWindowMs), and start/stop lifecycle including idempotency. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add `selectByRemainingCapacity()` method that routes to the account with the most remaining token capacity in its current usage window - Extract shared `getWindowBounds()` utility to `account-utils.ts` (was duplicated in throttle handler, usage listener, and IPC handlers) - Accept optional `AccountUsageStatsProvider` in `selectNextAccount()` for capacity-aware routing; falls back to LRU when unavailable - Apply 50% capacity penalty to recently-throttled accounts (within 2 windows) to prevent ping-ponging - Accounts without configured limits are deprioritized behind accounts with known remaining capacity - Round-robin strategy remains deterministic and ignores usage stats - Add 7 new tests covering capacity-aware selection scenarios Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ty-aware routing All call sites that invoke selectNextAccount() now pass the optional statsDB parameter, enabling capacity-aware account selection when the stats database is available. This allows the least-used strategy to route to accounts with the most remaining token capacity rather than just LRU ordering. Callers updated: - account-env-injector: accepts optional getStatsDB function, passes to selectNextAccount - account-throttle-handler: passes its existing getStatsDB to selectNextAccount - accounts IPC handler (accounts:select-next): passes getStatsDB singleton - process handler (process:spawn): passes getStatsDB via injectAccountEnv Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… Auto Run recovery indicator - Subscribe to account:throttled events with noAlternatives flag to pause Auto Runs with a specific rate_limited error when all accounts exhausted - Add accounts:check-recovery IPC handler for manual recovery poll trigger - Wire AccountRecoveryPoller into registerAccountHandlers dependencies - Add recovery-specific UI in AutoRun.tsx: pulsing accent indicator with "Waiting for account recovery — will auto-resume" and "Check Now" button - Subscribe to account:recovery-available for auto-resume of paused runs - Add 6 tests for accounts:check-recovery handler (all passing) - All 19,490 tests pass, lint clean Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…s and promote to own modal Rename all user-facing "Accounts" terminology to "Virtuosos" with "AI Account Providers" subtitle. Move Virtuosos panel from Settings tab to its own top-level modal accessible from the hamburger menu. Internal code (variable names, IPC channels, interfaces) unchanged. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…lector, and SessionItem badge Create shared useAccountUsage hook with real-time IPC subscription, derived metrics (burn rate, time-to-limit), and adaptive countdown (30s default, 5s when any account is within 5 minutes of window reset). - AccountsPanel: usage bar, token count, cost, queries, reset timer, burn rate per account - AccountSelector: fix hardcoded 0% usage bar with live data and compact stats - SessionItem: enhance badge tooltip to show usage percentage - Add accounts mock to test setup for complete IPC coverage - 17 new tests covering hook, formatTimeRemaining, and formatTokenCount Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…rends, and plan presets Implements ACCT-MUX-20: per-account daily/monthly aggregation queries, IPC handlers for historical usage, P90-weighted predictive analysis with exponential decay, plan preset picker (Pro/Max5/Max20), collapsible historical usage view with 7d/30d/monthly modes, throttle frequency tracking, and window history caching for predictions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
AccountSwitcher was fully implemented but never instantiated in index.ts, leaving manual account switches from the renderer completely broken. This adds the instantiation with correct dependencies and passes the getter to both registerAccountHandlers and registerProcessHandlers, enabling the accounts:execute-switch IPC and process:write recordLastPrompt paths. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ounds - Add AccountAuthRecovery class: automatic OAuth re-authentication when agents encounter expired tokens (kill → login → respawn) - Add 21 unit tests covering happy path, timeout, credential sync fallback, concurrent recovery guard, spawn errors, and edge cases - Wire auth recovery into error-listener for auth_expired errors - Consolidate getWindowBounds() duplicate: remove local copy from account-usage-listener.ts, import from account-utils.ts - Update error-listener, process-listeners/index, types for auth recovery dependency injection - Add syncCredentialsFromBase fallback in account-setup.ts - Update renderer components and hooks for auth recovery state Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace blocking alert() calls with toast notifications in AccountsPanel, add console.warn to 5 silent catch blocks for Sentry breadcrumb visibility, replace all hardcoded color hex values with theme.colors.error/warning, tighten 7 loosely-typed Record<string, unknown> event handlers in global.d.ts with specific payload interfaces, and expand test coverage for AccountSelector (8 tests) and AccountSwitchModal (9 tests). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Convert VirtuososModal from single-panel to two-tab layout: - Configuration tab: existing AccountsPanel (unchanged) - Usage tab: new VirtuosoUsageView with aggregate summary, per-account usage cards, predictions section, historical expandables, and throttle event timeline - Keyboard navigation (Cmd/Ctrl+Shift+[/]) following UsageDashboardModal pattern - Dynamic modal width (720px config, 900px usage) - Sessions prop passed from App.tsx for session assignment display Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The usage view was collapsing all token types into a single totalTokens value. The backend already stored inputTokens, outputTokens, cacheReadTokens, and cacheCreationTokens separately but the renderer never extracted or displayed them. Additionally, real-time usage-update events were only broadcast for accounts with token limits configured, leaving unlimited accounts with stale data. Key fixes: - Broadcast account:usage-update for ALL accounts, not just those with limits - Add token breakdown fields to AccountUsageUpdate interface and IPC event - Propagate inputTokens/outputTokens/cacheReadTokens/cacheCreationTokens through useAccountUsage hook and AccountUsageMetrics interface - Add per-account token breakdown grid (In/Out/Cache R/Cache W) in usage cards - Add stacked color-coded bars with legend in AccountUsageHistory - Fix daily/monthly SQL to query account_usage_windows instead of query_events (which lacked account_id population) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…e metrics to Virtuosos usage view - Add RateMetrics interface and calculateRateMetrics() to useAccountUsage hook with tokens/hr, tokens/day, tokens/week, period-over-period deltas, and linear regression trend detection - Create AccountTrendChart SVG component with full chart and compact sparkline modes - Create AccountRateMetrics panel component with rate display and trend indicator - Integrate 7-day sparklines into per-account usage cards - Add burn rate trend arrows (up/down unicode indicators) - Add new Trends & Analytics section between Predictions and Historical Usage - Increase window history fetch from 40 to 68 for 2-week delta calculations Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…onent Removes the unnecessary default React import that caused TS6133 since the project uses the modern JSX transform. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ccountUsageDashboard Integrates the useAccountUsage hook into the main Usage Dashboard's Accounts tab to surface burn rate, TTL, prediction confidence, 7-day sparklines, and daily trend arrows alongside existing overview cards and bar charts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add migration tracking fields (migratedFromSessionId, migratedToSessionId, migratedAt, archivedByMigration, migrationGeneration) to the Session interface for Virtuosos provider switching provenance chains. Add ProviderSwitchConfig interface and DEFAULT_PROVIDER_SWITCH_CONFIG to shared/account-types.ts for automated provider failover configuration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/shared/account-types.ts (1)
132-132: Move import statement to the top of the file.The import of
ToolTypeis placed after export statements. Standard convention is to place all imports at the top of the file for readability and consistency.Proposed fix
Move line 132 to the top of the file:
/** * Account multiplexing types for managing multiple Claude Code accounts. * Supports usage monitoring, limit tracking, and automatic account switching. */ + +import type { ToolType } from './types'; /** Unique identifier for an account (generated UUID) */ export type AccountId = string;And remove from line 132:
export const DEFAULT_TOKEN_WINDOW_MS = 5 * 60 * 60 * 1000; -import type { ToolType } from './types'; - /** * Configuration for automated provider failover (Virtuosos vertical swapping).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/shared/account-types.ts` at line 132, Move the import of ToolType so all imports are grouped at the top of the file: locate the existing import statement "import type { ToolType }" (currently after export statements) and cut it to place alongside the other imports at the top of src/shared/account-types.ts, ensuring no duplicate imports remain and preserving the "type" import modifier and any ESLint/max-import-order rules.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/shared/account-types.ts`:
- Line 132: Move the import of ToolType so all imports are grouped at the top of
the file: locate the existing import statement "import type { ToolType }"
(currently after export statements) and cut it to place alongside the other
imports at the top of src/shared/account-types.ts, ensuring no duplicate imports
remain and preserving the "type" import modifier and any ESLint/max-import-order
rules.
…r identity carry-over Extend CreateMergedSessionOptions with identity (nudgeMessage, bookmarked, sessionSshRemoteConfig, autoRunFolderPath) and provenance (migratedFromSessionId, migratedAt, migrationGeneration) fields. Apply them in createMergedSession so sessions created via provider switching are born complete. Create useProviderSwitch orchestrator hook that reuses the existing context transfer pipeline (extractTabContext, contextGroomingService, createMergedSession) but differs from useSendToAgent: preserves original session name, carries full identity, pre-loads context in tab logs, and sets provenance fields. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/renderer/hooks/agent/useProviderSwitch.ts`:
- Around line 195-205: The try/catch around
window.maestro.agents.get(targetProvider) in useProviderSwitch.ts currently
swallows all errors; update the catch to distinguish expected "agent not
available" logic from unexpected IPC/infrastructure failures by reporting
unexpected errors to Sentry (or the app's error-tracking) and rethrowing (or
returning a failed state) so they don't silently continue; keep the existing
availability check using agentStatus and getAgentDisplayName(targetProvider) but
if agentCheckError looks like an infrastructure/IPC error, call the app's error
reporter (e.g., captureException) with agentCheckError and then throw the error
to fail fast.
- Around line 39-50: The ProviderSwitchRequest type includes archiveSource but
the switchProvider implementation in useProviderSwitch never uses it; either
remove archiveSource from the ProviderSwitchRequest interface or implement
archiving logic inside switchProvider (or the useProviderSwitch hook) that, when
archiveSource is true, calls the existing session-archiving routine (e.g.,
invoke the archiveSession/archiveCurrentTab function or dispatch the archive
action used elsewhere) after switching providers; update any callers or tests
accordingly to reflect the chosen approach and keep ProviderSwitchRequest and
switchProvider signatures consistent.
| export interface ProviderSwitchRequest { | ||
| /** Source session to switch from */ | ||
| sourceSession: Session; | ||
| /** Tab ID within source session (active tab) */ | ||
| sourceTabId: string; | ||
| /** Target provider to switch to */ | ||
| targetProvider: ToolType; | ||
| /** Whether to groom context for target provider */ | ||
| groomContext: boolean; | ||
| /** Whether to auto-archive source session after switch */ | ||
| archiveSource: boolean; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
rg -n 'archiveSource' src/renderer/hooks/agent/useProviderSwitch.tsRepository: RunMaestro/Maestro
Length of output: 119
🏁 Script executed:
sed -n '90,110p' src/renderer/hooks/agent/useProviderSwitch.tsRepository: RunMaestro/Maestro
Length of output: 772
🏁 Script executed:
sed -n '140,200p' src/renderer/hooks/agent/useProviderSwitch.tsRepository: RunMaestro/Maestro
Length of output: 1751
🏁 Script executed:
rg -n 'archive' src/renderer/hooks/agent/useProviderSwitch.tsRepository: RunMaestro/Maestro
Length of output: 236
Unused parameter: archiveSource is defined but never used.
The archiveSource field is defined in ProviderSwitchRequest but is not destructured or used anywhere in the switchProvider function implementation. Either implement the archive logic within this hook, or remove the field if archiving is handled by the caller.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/renderer/hooks/agent/useProviderSwitch.ts` around lines 39 - 50, The
ProviderSwitchRequest type includes archiveSource but the switchProvider
implementation in useProviderSwitch never uses it; either remove archiveSource
from the ProviderSwitchRequest interface or implement archiving logic inside
switchProvider (or the useProviderSwitch hook) that, when archiveSource is true,
calls the existing session-archiving routine (e.g., invoke the
archiveSession/archiveCurrentTab function or dispatch the archive action used
elsewhere) after switching providers; update any callers or tests accordingly to
reflect the chosen approach and keep ProviderSwitchRequest and switchProvider
signatures consistent.
| try { | ||
| const agentStatus = await window.maestro.agents.get(targetProvider); | ||
| if (!agentStatus?.available) { | ||
| throw new Error( | ||
| `${getAgentDisplayName(targetProvider)} is not available. Please install and configure it first.` | ||
| ); | ||
| } | ||
| } catch (agentCheckError) { | ||
| // If we can't check, log warning but continue | ||
| console.warn('Could not verify agent availability:', agentCheckError); | ||
| } |
There was a problem hiding this comment.
Swallowed exception may mask infrastructure failures.
The catch block logs a warning but continues execution. If window.maestro.agents.get() fails due to IPC issues or other infrastructure problems, the operation will proceed and fail later with a less informative error.
Consider either letting the error propagate (fail fast) or explicitly handling expected failure cases while reporting unexpected ones to Sentry.
🛡️ Suggested fix to report unexpected errors
try {
const agentStatus = await window.maestro.agents.get(targetProvider);
if (!agentStatus?.available) {
throw new Error(
`${getAgentDisplayName(targetProvider)} is not available. Please install and configure it first.`
);
}
} catch (agentCheckError) {
- // If we can't check, log warning but continue
- console.warn('Could not verify agent availability:', agentCheckError);
+ // Only continue if it's a timeout or network error; otherwise fail fast
+ if (agentCheckError instanceof Error && agentCheckError.message.includes('timeout')) {
+ console.warn('Agent availability check timed out, continuing:', agentCheckError);
+ } else {
+ throw agentCheckError;
+ }
}As per coding guidelines: "Do NOT silently swallow exceptions with try-catch-console.error blocks. Let unhandled exceptions bubble up to Sentry for error tracking."
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| try { | |
| const agentStatus = await window.maestro.agents.get(targetProvider); | |
| if (!agentStatus?.available) { | |
| throw new Error( | |
| `${getAgentDisplayName(targetProvider)} is not available. Please install and configure it first.` | |
| ); | |
| } | |
| } catch (agentCheckError) { | |
| // If we can't check, log warning but continue | |
| console.warn('Could not verify agent availability:', agentCheckError); | |
| } | |
| try { | |
| const agentStatus = await window.maestro.agents.get(targetProvider); | |
| if (!agentStatus?.available) { | |
| throw new Error( | |
| `${getAgentDisplayName(targetProvider)} is not available. Please install and configure it first.` | |
| ); | |
| } | |
| } catch (agentCheckError) { | |
| // Only continue if it's a timeout or network error; otherwise fail fast | |
| if (agentCheckError instanceof Error && agentCheckError.message.includes('timeout')) { | |
| console.warn('Agent availability check timed out, continuing:', agentCheckError); | |
| } else { | |
| throw agentCheckError; | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/renderer/hooks/agent/useProviderSwitch.ts` around lines 195 - 205, The
try/catch around window.maestro.agents.get(targetProvider) in
useProviderSwitch.ts currently swallows all errors; update the catch to
distinguish expected "agent not available" logic from unexpected
IPC/infrastructure failures by reporting unexpected errors to Sentry (or the
app's error-tracking) and rethrowing (or returning a failed state) so they don't
silently continue; keep the existing availability check using agentStatus and
getAgentDisplayName(targetProvider) but if agentCheckError looks like an
infrastructure/IPC error, call the app's error reporter (e.g., captureException)
with agentCheckError and then throw the error to fail fast.
Add the confirmation modal for Virtuosos provider switching (VSWITCH-03). Includes agent detection, radio-button provider selection with availability badges, groom context and archive source checkboxes, token estimation, and keyboard navigation. Uses Modal base component with layer stack registration for Escape handling. PROVIDER_SWITCH priority placed at 1003, near ACCOUNT_SWITCH at 1005. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/renderer/components/SwitchProviderModal.tsx`:
- Around line 239-240: The div with the onKeyDown handler (the element using
handleKeyDown) cannot receive keyboard events because it lacks focusability; add
tabIndex={0} to that div and add the "outline-none" class to preserve visual
styling (remove the eslint disable comment afterwards) so keyboard navigation
works and handleKeyDown will fire as intended.
- Around line 108-110: In SwitchProviderModal's catch block (the one that
currently does console.error('Failed to detect agents for provider switch:',
err)), replace the silent console logging with a call to our Sentry reporting
utility (e.g., reportError or captureException) and pass the caught error along
with contextual metadata (provider IDs, selected provider, user action such as
"switch provider", and any relevant state from detectAgentsForProvider or
detectAgents call) so errors are recorded in Sentry with context for debugging;
keep console.error only if you want local logs but ensure the Sentry call is the
primary error reporting mechanism.
| } catch (err) { | ||
| console.error('Failed to detect agents for provider switch:', err); | ||
| } |
There was a problem hiding this comment.
Silent exception swallowing violates error handling guidelines.
The catch block logs to console.error but doesn't report to Sentry. Per coding guidelines, use Sentry utilities for error reporting with context rather than silently swallowing exceptions.
🛡️ Proposed fix to report error to Sentry
+import { captureException } from '../utils/sentry'; } catch (err) {
- console.error('Failed to detect agents for provider switch:', err);
+ captureException(err, {
+ tags: { component: 'SwitchProviderModal' },
+ extra: { sourceToolType: sourceSession.toolType },
+ });
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/renderer/components/SwitchProviderModal.tsx` around lines 108 - 110, In
SwitchProviderModal's catch block (the one that currently does
console.error('Failed to detect agents for provider switch:', err)), replace the
silent console logging with a call to our Sentry reporting utility (e.g.,
reportError or captureException) and pass the caught error along with contextual
metadata (provider IDs, selected provider, user action such as "switch
provider", and any relevant state from detectAgentsForProvider or detectAgents
call) so errors are recorded in Sentry with context for debugging; keep
console.error only if you want local logs but ensure the Sentry call is the
primary error reporting mechanism.
| {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */} | ||
| <div className="flex flex-col gap-4" onKeyDown={handleKeyDown}> |
There was a problem hiding this comment.
Missing tabIndex prevents keyboard navigation from working.
The <div> with onKeyDown handler lacks tabIndex={0}, so it cannot receive focus and keyboard events won't fire. The eslint-disable comment acknowledges this accessibility gap. Per coding guidelines, add tabIndex={0} and outline-none class to ensure focus works correctly.
⌨️ Proposed fix to enable keyboard focus
- {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
- <div className="flex flex-col gap-4" onKeyDown={handleKeyDown}>
+ <div
+ className="flex flex-col gap-4 outline-none"
+ onKeyDown={handleKeyDown}
+ tabIndex={0}
+ ref={(el) => el?.focus()}
+ >📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */} | |
| <div className="flex flex-col gap-4" onKeyDown={handleKeyDown}> | |
| <div | |
| className="flex flex-col gap-4 outline-none" | |
| onKeyDown={handleKeyDown} | |
| tabIndex={0} | |
| ref={(el) => el?.focus()} | |
| > |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/renderer/components/SwitchProviderModal.tsx` around lines 239 - 240, The
div with the onKeyDown handler (the element using handleKeyDown) cannot receive
keyboard events because it lacks focusability; add tabIndex={0} to that div and
add the "outline-none" class to preserve visual styling (remove the eslint
disable comment afterwards) so keyboard navigation works and handleKeyDown will
fire as intended.
… Agent modal - Add "Switch Provider..." menu item to SessionContextMenu (after Edit Agent) - Thread onSwitchProvider callback through SessionList and useSessionListProps - Add "Switch..." button to Edit Agent modal's provider section - Thread onSwitchProviderFromEdit through AppSessionModals and AppMasterModals - All entry points gated behind optional callbacks (hidden when Virtuosos disabled) - Actual SwitchProviderModal wiring deferred to VSWITCH-05 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add provider switching UI integration: - Import SwitchProviderModal and useProviderSwitch hook - Add switchProviderSession state for tracking which session is being switched - Create handleSwitchProvider and handleConfirmProviderSwitch callbacks - Pass handleSwitchProvider to useSessionListProps (gated behind encoreFeatures.virtuosos) - Pass onSwitchProviderFromEdit to AppModals for EditAgentModal integration - Render SwitchProviderModal in JSX near AccountSwitchModal and VirtuososModal Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sessions with archivedByMigration render at 40% opacity, show a grey hollow status dot (no animation), and display "Provider switched — archived" indicator text below the session name. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…er config, and migration history - Extend VirtuososModal from 2 tabs to 3: Accounts, Providers, Usage - Rename "Configuration" tab to "Accounts" for clarity - Create ProviderPanel component with three sections: 1. Provider Status Grid showing detected agents with availability and session counts 2. Failover Configuration with toggles, thresholds, and ordered fallback list 3. Migration History timeline of past provider switches - Update tests for new tab structure and add Providers tab coverage Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…PC API, and App.tsx wiring Implements VSWITCH-08: sliding-window error tracker that monitors consecutive agent errors per session and emits failover suggestions when the configured threshold is reached. The renderer subscribes to these events and opens the SwitchProviderModal (or shows a toast for auto-switch). - ProviderErrorTracker class with configurable threshold, window, and fallback list - Only counts recoverable, provider-level errors (rate_limited, network_error, agent_crashed, auth_expired) — not token_exhaustion or session_not_found - IPC handlers (providers:get-error-stats, get-all-error-stats, clear-session-errors) - Preload API (window.maestro.providers namespace) - Error listener integration: feeds agent errors into tracker - Query-complete listener: resets error count on successful response - App.tsx: failover suggestion subscription gated by encoreFeatures.virtuosos - Clears provider error state on successful provider switch - 21 unit tests covering all tracker behaviors Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… health cards Replace the basic Provider Status Grid in ProviderPanel with an enriched health dashboard showing per-provider health cards with status badges, error stats, health bars, and auto-refresh. Add useProviderHealth hook for live data with 10s polling and immediate failover event updates. Add health badge indicator on the Providers tab in VirtuososModal. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…hain walking Implements findArchivedPredecessor() to walk the migration provenance chain backwards and find archived sessions matching a target provider. Adds merge-back orchestration to useProviderSwitch that reactivates archived sessions instead of creating new ones during round-trip provider switches. Includes 13 tests covering chain walking, cycle detection, identity preservation, and context log appending. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…lth Dashboard Extends useProviderHealth hook with StatsTimeRange state and per-provider usage stats aggregation. Adds totals summary bar above health cards showing combined queries, tokens, and cost. Moves auto-refresh indicator and refresh button to a footer row with time range dropdown (Today/Week/Month/Quarter/All). Updates health cards to show queries, tokens, and cost metrics. Subscribes to onStatsUpdate for immediate dashboard refresh on new query events. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… useProviderDetail hook Clicking a health card or its "Details →" link transitions to a full-width detail view for that provider. The view shows 12 summary metrics (queries, tokens, cost, reliability, error rate, sessions, source/location split, avg/p95 response times), active sessions list, and migration history. Back button and Escape key return to the card grid. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…reshold, and type fixes - Add errorsByType field to ProviderErrorStats and compute in ProviderErrorTracker - Fix getStats return type in preload and global.d.ts to include token fields - Replace hardcoded failover threshold with config-driven value from providerSwitchConfig - Remove unnecessary (e as any) type casts in useProviderDetail Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add ProviderDetailCharts.tsx with query volume trend, response time trend (with p95 band), 24-hour activity heatmap, token breakdown stacked bar, source/location donut charts, and reliability gauge with error type badges. Integrated into ProviderDetailView below summary metrics. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… timeline to Provider Detail View Adds ComparisonBar with query/cost share progress bars and response time/reliability rankings across providers, enhances Active Sessions list with click-to-navigate (closes modal and sets active session), and extracts MigrationTimeline as a dedicated sub-component with ArrowRightLeft icons and generation badges. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…add unarchive context menu - Remove duplicate reliability gauge from ProviderDetailCharts (was shown both as MetricCard and as Chart 6); move error breakdown inline to ProviderDetailView - Fix empty Activity by Hour chart by adding byAgentByHour SQL aggregation, matching the working byAgentByDay pattern instead of client-side queryEvents - Add "Unarchive" option to agent right-click menu for archived sessions (gated behind Virtuosos), with conflict detection modal when another active agent of the same provider exists (offers archive or delete of conflicting agent) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix false-positive conflicts by requiring both name AND toolType match when checking for existing agents during unarchive. Previously only toolType was checked, causing unrelated agents (e.g., AGENMONITORWIDGET) to block unarchiving a different agent on the same provider. Fix archive button contrast on UnarchiveConflictModal — use theme.colors.bg instead of hardcoded #ffffff for text on accent background. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extend account system from Claude-only to all supported providers: - Widen MultiplexableAgent type to include codex, opencode, factory-droid, and gemini-cli alongside claude-code - Expand discoverExistingAccounts() to scan for ~/.codex, ~/.opencode, ~/.gemini config directories with provider-specific auth detection - Group accounts by provider in AccountsPanel with labeled section headers and count badges (e.g., "Claude Code — 2 accounts") - Add provider selector dropdown to the Create New Virtuoso form - Pass agentType through IPC add/discover pipeline (registry, handler, preload, global.d.ts) - Fix unarchive conflict detection to match on name + toolType - Fix archive button accessibility (theme.colors.bg for contrast) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Multi-Provider Account Support (commits acc91b5, 29e087f)What changed1. Multi-provider account discovery ( The Rewrote it using a
Currently discovers:
2. Provider-grouped accounts UI ( Previously rendered a flat list of accounts. Now groups them by provider type with section headers (e.g., "Claude Code — 2 accounts", "OpenAI Codex — 1 account"). Uses a local 3. Provider selector in account creation "Create New Virtuoso" form now includes a provider dropdown so you can register accounts for any supported provider, not just Claude Code. 4. Type system updates
5. Unarchive conflict fix ( Was matching on 6. Modal accessibility ( Archive button had white text on teal accent background — poor contrast. Changed to ReasoningThe whole point of Virtuosos is multi-provider orchestration. Having account management locked to Claude Code only undermined the provider switching, failover, and health monitoring features. With this change, a user with 2 Claude accounts, 1 Codex account, and 1 Gemini CLI install sees all of them in one place, grouped by provider, discoverable with a single click. |
Ran npm install to ensure lock file is consistent with main's dependencies. Removed 33 stale packages. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Align frontend ThrottleEvent interfaces with backend: rename totalTokens to tokensAtThrottle, remove phantom recoveryAction field, add missing id and sessionId fields. Fixes broken/undefined token display and unused Recovery column in throttle event tables. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ex and auto-focus
The onKeyDown handler div lacked tabIndex, so keyboard events (arrow
keys, Enter, Space) never fired. Added tabIndex={0}, outline-none class,
and auto-focus ref so the modal receives focus on open.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… in account flows
Replace console.error calls and silently-swallowed .catch(() => {}) with
Sentry.captureException reporting with contextual metadata across all
account/provider flows: reconciliation, account switching, provider
switching, session merging, and account assignment.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…face The archiveSource field was defined in ProviderSwitchRequest but never used by the useProviderSwitch hook. Archiving is handled entirely by App.tsx after the hook returns, using the modal callback's own request parameter. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…fast Separated the IPC call from the availability check in useProviderSwitch so infrastructure failures (window.maestro.agents.get throwing) are reported to Sentry with context metadata and re-thrown as a user-facing error, instead of being silently swallowed by console.warn. The !agentStatus?.available check now correctly propagates to the outer error handler. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When accountName is missing but accountId exists, display accountId as fallback in the agent tooltip. Matches the existing pattern used in the context menu (line 258). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Review Fixes AppliedAll CodeRabbit review items addressed (2 critical, 1 major, 2 minor) plus conflict resolution verified across 18 files. Branch is fully rebased on main with no conflicts. Critical Fixes
Major Fix
Minor Fixes
Conflict Resolution (18 files)Branch rebased cleanly onto main with no conflict markers. Verified integration of both sides across all 18 originally-conflicting files:
Verification
|
Summary
Virtuosos is a multi-account multiplexing and capacity-aware routing system for Maestro. This PR implements the full stack — from account registry and usage tracking through provider switching, health monitoring, and detailed analytics.
Core Features
Technical Highlights
Recent Fixes
Test Plan
Closes #408
🤖 Generated with Claude Code