Files
adiuvAI/AI_REFACTOR_PLAN.md

34 KiB

AI Refactor Plan — Adiuva Electron App

Objective: Transform the Electron app into a hybrid-first multi-agent client. The user controls where data is stored (local / cloud / sync), which AI provider to use (BYOK multi-provider), and which automations to run — either custom batch agents built with the LLM-powered Batch Builder, or pre-built plugins from the marketplace. All data access is opt-in, transparent, and auditable.

Backend: Lives in a separate repository. See ../adiuva-api/BACKEND_PLAN.md for the API contract and backend implementation guide.

Protocol: Execute steps sequentially. Each step is atomic and committable. Mark [x] when done.


Phase 0 — API Contracts & Types

Step 0.1 — Define backend API contract types

  • Create src/shared/api-types.ts with all interfaces the Electron app needs to communicate with the backend:
    • ExecutionPlan, PlanStep, PlanAction (action types: create_record, update_record, delete_record, index_document, send_notification, call_agent)
    • ChatRequest (message, context, execution_mode: 'direct' | 'plan')
    • ChatResponse (response, actions)
    • ChatContext (user_profile, relevant_documents, recent_tasks, conversation_history)
    • AgentManifest (name, description, permissions, schedule)
    • PermissionGrant (plugin, permission type, resource path, granted_at)
    • BackupMetadata (version, timestamp, checksum, chunk_count)
    • BillingTier enum (free, pro, power, team)
    • AuthTokens (access_token, refresh_token, expires_at)
    • UserProfile (id, email, tier)
  • Create src/shared/batch-types.ts with all types for the batch builder and storage layer:
    • StorageTarget'local' | 'cloud' | 'sync' | 'none'
    • ConnectorType'imap' | 'filesystem' | 'calendar' | 'api' | 'gmail' | 'gdrive' | 'outlook'
    • BatchActionType'create_record' | 'update_record' | 'delete_record' | 'index_document' | 'send_notification' | 'call_agent'
    • BatchSource{ connector: ConnectorType, config: Record<string, unknown> }
    • BatchTrigger{ type: 'cron' | 'event', schedule?: string, timezone?: string }
    • BatchAnalysis{ prompt: string, model_override?: string, output_schema?: object }
    • BatchAction{ type: BatchActionType, table?: string, mapping?: Record<string, string> }
    • BatchStorage{ records: StorageTarget, vectors: StorageTarget, raw_data: StorageTarget }
    • BatchConfig — full config object: id, name, description, enabled, source, trigger, analysis, actions, storage, permissions
    • BatchStatus'idle' | 'running' | 'error' | 'disabled'
    • BatchRunResult{ batchId, runAt, status, itemsProcessed, errors }
    • PluginListing{ id, name, description, author, version, rating, installs, category, permissions, price }
    • InstalledPlugin{ listing: PluginListing, installedAt, enabled, storageConfig: BatchStorage }
    • DataSourceInfo{ type: ConnectorType, label, recordCount, sizeBytes, storageTarget: StorageTarget }
    • StorageStats{ localUsedBytes, cloudUsedBytes, cloudLimitBytes, sources: DataSourceInfo[] }
  • Update tsconfig.json paths if needed to include src/shared/
  • Files: src/shared/api-types.ts, src/shared/batch-types.ts, tsconfig.json
  • Outcome: Type-safe contracts for all backend communication and the batch/storage subsystem. Backend repo mirrors these as Pydantic schemas.

Phase 1 — LiteLLM Multi-Provider Client

Step 1.1 — Create unified LLM client wrapper

  • Create src/main/llm/litellm-client.ts:
    • LiteLLMClient class with unified interface:
      • complete(messages: Message[], options?: CompletionOptions): Promise<CompletionResponse>
      • stream(messages: Message[], options?: CompletionOptions): AsyncGenerator<string>
      • embed(text: string): Promise<number[]>
    • CompletionOptions: model override, temperature, max_tokens, tools
    • Provider-agnostic: internally maps to the correct provider SDK
    • Fallback chain: tries primary provider, on failure tries secondary, logs each attempt
    • Timeout handling: per-provider configurable timeouts
  • Create src/main/llm/providers.ts:
    • ProviderConfig interface: name, apiKey, model, endpoint (for Ollama), timeout, isLocal
    • ProviderRegistry: manages configured providers, persists to electron-store
    • getActiveProvider(), setActiveProvider(name), addProvider(config), removeProvider(name)
    • getFallbackChain(): ProviderConfig[]
    • Supported providers: OpenAI, Anthropic, Google (Gemini), Mistral, Groq, Ollama (local)
  • Create src/main/llm/embeddings.ts (refactored):
    • Support multiple embedding providers (OpenAI text-embedding-3-small, local ONNX with all-MiniLM-L6-v2)
    • Auto-select: use local ONNX if available, fall back to API
    • Same embedText(text): Promise<number[]> interface
  • Files: src/main/llm/litellm-client.ts, src/main/llm/providers.ts, src/main/llm/embeddings.ts
  • Outcome: Single LLM interface that all local components use. Supports 6+ providers with fallback.

Step 1.2 — Migrate existing AI code to use new LLM client

  • Update src/main/ai/orchestrator.ts:
    • Replace direct getLLM() calls with LiteLLMClient.complete() / LiteLLMClient.stream()
    • Keep local orchestration working with the new client (backend delegation comes in Phase 3)
  • Update src/main/ai/llm.ts:
    • Deprecate. Redirect getLLM() to instantiate via LiteLLMClient as a thin compatibility shim
  • Update src/main/ai/embeddings.ts to delegate to src/main/llm/embeddings.ts
  • Update src/main/ai/token.ts:
    • Add listStoredProviders(): Promise<string[]> to enumerate which providers have tokens
  • Ensure all existing AI features (chat, daily brief, tool calling) continue to work
  • Files: src/main/ai/orchestrator.ts, src/main/ai/llm.ts, src/main/ai/embeddings.ts, src/main/ai/token.ts
  • Outcome: Existing AI features work identically but go through the new unified LLM client.

Phase 2 — Local Plugin System & Batch Agents

Step 2.1 — Create plugin manifest system and permission manager

  • Create src/main/permissions/manifest-validator.ts:
    • PluginManifest interface: name, description, version, permissions: PermissionRequest[], schedule?: string (cron), entryPoint: string
    • PermissionRequest: type (read_folder, read_email, read_calendar, read_browser_history), resource?: string (path, account), reason: string
    • validateManifest(manifest): ValidationResult — validates structure, checks for dangerous permissions
  • Create src/main/permissions/permission-manager.ts:
    • PermissionManager class (singleton):
      • grantPermission(pluginName, permission): void — persists to SQLite
      • revokePermission(pluginName, permission): void
      • checkPermission(pluginName, permission): boolean
      • getPluginPermissions(pluginName): PermissionGrant[]
      • getAllGrants(): PermissionGrant[]
      • logAccess(pluginName, permission, resource, timestamp): void — activity log
      • getActivityLog(pluginName?, limit?): ActivityLogEntry[]
    • Permission grants stored in a new plugin_permissions SQLite table
    • Activity log stored in a new plugin_activity_log SQLite table
  • Add plugin_permissions and plugin_activity_log tables to src/main/db/schema.ts
  • Generate and apply migration
  • Files: src/main/permissions/manifest-validator.ts, src/main/permissions/permission-manager.ts, src/main/db/schema.ts, src/main/db/migrations/
  • Outcome: Granular, opt-in permission system for plugins. Every access is logged.

Step 2.2 — Create worker pool and batch runner

  • Create src/main/workers/worker-pool.ts:
    • WorkerPool class:
      • Manages a pool of Node.js worker_threads
      • runPlugin(manifest, context): Promise<PluginResult> — spawns or reuses a worker, sends manifest + context, receives result
      • Worker lifecycle: create, send message, receive result, terminate on timeout
      • Max concurrent workers: configurable (default 4)
      • Error isolation: worker crash doesn't affect main process
  • Create src/main/workers/batch-runner.ts:
    • BatchRunner class:
      • registerPlugin(manifest): void — validates manifest, stores in registry
      • startScheduler(): void — cron-based scheduler using node-cron or simple setInterval
      • runPlugin(name, triggerContext?): Promise<PluginResult> — manual trigger
      • stopAll(): void — graceful shutdown of all scheduled plugins
      • Scheduler checks permissions before each run; skips if revoked
      • Results logged to activity log
  • Create src/main/workers/plugin-worker.ts:
    • Worker thread entry point
    • Receives plugin config + context via parentPort.on('message')
    • Dynamically imports the plugin entry point
    • Executes run(context) with sandboxed access (only permitted resources)
    • Posts result back via parentPort.postMessage()
  • Files: src/main/workers/worker-pool.ts, src/main/workers/batch-runner.ts, src/main/workers/plugin-worker.ts
  • Outcome: Isolated plugin execution environment with scheduling, permissions enforcement, and error isolation.

Step 2.3 — Implement batch agent plugins

  • Create src/plugins/email-scanner.ts:
    • Manifest: requires read_email permission
    • Connects to IMAP via imapflow (account configured in settings)
    • Scans for new emails since last run
    • Uses LiteLLMClient to classify each email (has actionable task? extract title, priority, description)
    • Returns extracted task metadata (never raw email content) for execution via backend or local playbook
  • Create src/plugins/file-watcher.ts:
    • Manifest: requires read_folder permission for each watched path
    • Uses chokidar to watch approved directories
    • On new/modified file: reads content, generates embedding, upserts into vector store
    • Supports: .txt, .md, .pdf (text extraction), .docx (basic extraction)
  • Create src/plugins/calendar-sync.ts:
    • Manifest: requires read_calendar permission
    • Parses ICS files or connects to CalDAV endpoint
    • Detects scheduling conflicts
    • Suggests reorganizations via LLM analysis
    • Returns calendar events + conflict reports
  • Create src/plugins/browser-agent.ts:
    • Manifest: requires read_browser_history permission (explicit opt-in)
    • Reads browser bookmarks and history from known browser paths (Chrome, Firefox, Edge)
    • Indexes relevant entries into vector store
    • Privacy-first: only indexes URLs and titles, not page content
  • Files: src/plugins/email-scanner.ts, src/plugins/file-watcher.ts, src/plugins/calendar-sync.ts, src/plugins/browser-agent.ts
  • Outcome: Four local batch agents running as isolated worker threads, using LiteLLM for analysis.

Phase 3 — Backend Integration

Step 3.1 — Create backend HTTP/WebSocket client

  • Create src/main/api/backend-client.ts:
    • BackendClient class:
      • baseUrl configurable (default: production cloud URL, overridable for dev)
      • setAuthToken(jwt: string): void
      • chat(request: ChatRequest): Promise<ChatResponse> — POST /api/v1/chat
      • chatStream(request: ChatRequest): AsyncGenerator<string> — WebSocket /api/v1/chat/stream
      • getPlaybooks(): Promise<ExecutionPlan[]> — GET /api/v1/plans/playbook
      • uploadBackup(blob: Buffer, metadata: BackupMetadata): Promise<void> — PUT /api/v1/backup
      • downloadBackup(): Promise<{ blob: Buffer, metadata: BackupMetadata }> — GET /api/v1/backup
      • Automatic retry with exponential backoff (max 3 attempts)
      • Offline detection: returns cached playbook responses when offline
    • isOnline(): boolean — connectivity check
  • Create src/main/api/plan-runner.ts:
    • PlanRunner class:
      • execute(plan: ExecutionPlan): Promise<PlanResult> — executes plan steps locally
      • Step handlers: create_record (inserts into SQLite), update_record, delete_record, index_document (upserts into vector store), send_notification (Electron notification API)
      • Each step logs to activity log
      • Supports data_from_step references (pipeline execution)
      • Validates plan structure before execution
  • Files: src/main/api/backend-client.ts, src/main/api/plan-runner.ts
  • Outcome: Electron can communicate with the cloud backend and execute returned plans locally.

Step 3.2 — Refactor orchestrator to delegate to backend

  • Update src/main/ai/orchestrator.ts:
    • When online: forward chat requests to backend via BackendClient.chatStream()
    • Build ChatRequest from local context: query SQLite for user profile, relevant documents (from vector store), recent tasks, conversation history
    • Stream backend response tokens to renderer via existing ai:stream IPC channel
    • Execute any returned actions via PlanRunner
    • When offline: fall back to local orchestration (existing LangGraph pipeline) with degraded capabilities
    • Remove direct agent logic (project agent, knowledge agent, general agent tool definitions) — these now live on the backend
    • Keep buildProjectContext() and buildGlobalContext() as context builders for the request payload
  • Update src/main/router/index.ts ai sub-router:
    • chat mutation: call refactored orchestrator (which now delegates to backend)
    • Add getPlaybooks query: fetches cached playbooks
    • Keep dailyBrief mutation: sends daily brief request to backend
  • Add IPC handler for plan execution results
  • Files: src/main/ai/orchestrator.ts, src/main/router/index.ts, src/main/ipc.ts
  • Outcome: Chat intelligence lives on the backend; Electron is the execution layer.

Step 3.3 — Implement Shared Memory (three-tier local memory)

  • Create src/main/database/shared-memory.ts:
    • Short-term memory: In-memory conversation buffer
      • ConversationBuffer class: stores last N messages per session
      • addMessage(sessionId, role, content), getHistory(sessionId, limit?) -> Message[]
      • Cleared on session end
    • Long-term KV store: SQLite-backed key-value store
      • New agent_memory table: id, namespace (agent name), key, value (JSON text), updated_at
      • AgentMemoryStore class: get(namespace, key), set(namespace, key, value), delete(namespace, key), listKeys(namespace)
      • Used by agents to persist learned facts, user preferences
    • Vector store: Already exists (LanceDB). Enhance with:
      • Multi-collection support: separate tables for notes, emails, files, calendar
      • searchByCollection(collection, query, limit) -> SearchResult[]
  • Add agent_memory table to src/main/db/schema.ts
  • Generate migration
  • Files: src/main/database/shared-memory.ts, src/main/db/schema.ts, src/main/db/migrations/
  • Outcome: Three-tier memory system supporting short-term conversation, long-term agent facts, and semantic search.

Phase 4 — Security: E2E Backup & Offline Mode

Step 4.1 — Implement E2E encrypted backup

  • Create src/main/backup/e2e-crypto.ts:
    • generatePassphrase(): string — BIP39-compatible 12-word recovery phrase
    • deriveKey(passphrase: string, salt: Buffer): Promise<Buffer> — Argon2id key derivation (time cost 3, memory 64MB, parallelism 1)
    • encrypt(data: Buffer, key: Buffer): { ciphertext: Buffer, iv: Buffer, authTag: Buffer } — AES-256-GCM
    • decrypt(ciphertext: Buffer, key: Buffer, iv: Buffer, authTag: Buffer): Buffer
    • Uses node:crypto for AES and argon2 npm package for key derivation
  • Create src/main/backup/backup-manager.ts:
    • BackupManager class:
      • createBackup(passphrase: string): Promise<BackupBlob> — Exports SQLite DB, encrypts, returns blob + metadata
      • restoreBackup(blob: Buffer, passphrase: string): Promise<void> — Decrypts blob, replaces local DB, re-initializes
      • uploadBackup(passphrase: string): Promise<void> — Creates backup, uploads via BackendClient
      • downloadAndRestore(passphrase: string): Promise<void> — Downloads from backend, decrypts, restores
      • Incremental backup: chunks DB into segments, encrypts each separately, tracks content hashes to skip unchanged chunks
      • Metadata header: version, timestamp, checksum (SHA-256 of plaintext), chunk count
  • Files: src/main/backup/e2e-crypto.ts, src/main/backup/backup-manager.ts
  • Outcome: User data never leaves the device unencrypted. Backend stores only opaque blobs.

Step 4.2 — Implement offline sync queue

  • Create src/main/backup/sync-queue.ts:
    • SyncQueue class:
      • enqueue(action: QueuedAction): void — Adds action to persistent queue (SQLite table sync_queue)
      • processQueue(): Promise<void> — Processes queued actions in FIFO order when online
      • getQueueSize(): number
      • clearQueue(): void
      • Conflict resolution: last-write-wins with timestamps
    • New sync_queue table: id, action_type, payload (JSON), created_at, status (pending/processing/failed), retry_count, last_error
    • Auto-drain: watches connectivity, starts processing when online
    • Failed actions: retry up to 3 times with exponential backoff, then mark as failed for user review
  • Add sync_queue table to schema
  • Integrate with BackendClient: when offline, chat/backup calls enqueue instead of failing
  • Files: src/main/backup/sync-queue.ts, src/main/db/schema.ts, src/main/api/backend-client.ts
  • Outcome: App works offline; queued actions sync automatically when connectivity returns.

Phase 5 — Auth Integration & Database Encryption

Step 5.1 — Integrate auth into Electron app

  • Create src/main/auth/auth-manager.ts:
    • AuthManager class:
      • login(email, password): Promise<void> — Calls backend POST /api/v1/auth/login, stores JWT in secure storage (via token.ts)
      • register(email, password): Promise<void> — Calls POST /api/v1/auth/register
      • logout(): void — Clears stored JWT
      • getToken(): string | null — Returns current JWT
      • refreshToken(): Promise<void> — Auto-refresh before expiry
      • isAuthenticated(): boolean
      • getCurrentTier(): BillingTier
    • Auto-refresh: checks token expiry every 5 minutes, refreshes if < 10 minutes remaining
  • Add tRPC procedures: auth.login, auth.register, auth.logout, auth.status, auth.tier
  • Wire BackendClient to use AuthManager.getToken() for all requests
  • Files: src/main/auth/auth-manager.ts, src/main/router/index.ts, src/main/api/backend-client.ts
  • Outcome: Electron app has full auth flow; backend requests are authenticated.

Step 5.2 — Migrate from better-sqlite3 to SQLCipher

  • Add @journeyapps/sqlcipher to dependencies (replaces better-sqlite3)
  • Update src/main/db/index.ts:
    • Replace better-sqlite3 import with @journeyapps/sqlcipher
    • On first launch: derive DB key from OS keychain or prompt user
    • initDb(password): opens DB with PRAGMA key = 'password'
    • Migration path for existing unencrypted DBs: detect → export → create encrypted → import → delete old
    • WAL mode still enabled after keying
  • Update src/main/index.ts: pass password to initDb()
  • Test that all existing Drizzle operations work with SQLCipher
  • Files: package.json, src/main/db/index.ts, src/main/index.ts
  • Outcome: All local data encrypted at rest with SQLCipher.

Phase 6 — Renderer UI Updates

Navigation model: The app has a sidebar with top-level routes matching the pages below. Each page is a full-screen view. Shared hooks live in src/renderer/hooks/. All data access goes through tRPC procedures — no direct IPC calls from components.

Step 6.1 — Restructure app shell and routing

  • Update src/renderer/App.tsx:
    • Define top-level routes: /chat, /batch-builder, /plugins, /data-manager, /settings, /activity
    • Add sidebar navigation with icons and labels for each route
    • Persist last active route in electron-store
  • Create src/renderer/hooks/useProvider.ts:
    • useProvider() — returns active provider config, setProvider(), testProvider(), list of configured providers
    • Backed by tRPC provider.* procedures (to be added in Phase 1)
  • Create src/renderer/hooks/useStorage.ts:
    • useStorage() — returns StorageStats, setStorageTarget(source, target), migrateData(source, from, to)
    • Backed by tRPC storage.* procedures (to be added in Phase 2)
  • Files: src/renderer/App.tsx, src/renderer/hooks/useProvider.ts, src/renderer/hooks/useStorage.ts
  • Outcome: App shell with all top-level routes and shared data hooks.

Step 6.2 — ChatPage with context panel

  • Create src/renderer/pages/ChatPage.tsx:
    • Two-column layout: chat area (left/main) + collapsible ContextPanel (right)
    • Wraps ChatWindow and ContextPanel components
    • Online/offline status bar at top
  • Create src/renderer/components/chat/ChatWindow.tsx:
    • Message list rendering MessageBubble for each entry
    • Input bar with send button and attachment support
    • Handles streaming tokens from useChat hook
    • Plan approval UI inline: expandable plan steps with approve/reject per-step
    • Error states: offline, auth expired, rate limited, server error (distinct UI for each)
  • Create src/renderer/components/chat/MessageBubble.tsx:
    • Renders user / assistant / system messages
    • Supports markdown rendering for assistant messages
    • Shows tool-call indicators when the agent uses a tool
    • Timestamp and copy-to-clipboard action
  • Create src/renderer/components/chat/ContextPanel.tsx:
    • Shows what context the agent used for the last response: matched documents, recent tasks, memory entries
    • Each context item links to its source (note, file, batch result)
    • Collapsible, persists open/closed state
  • Create src/renderer/hooks/useChat.ts:
    • useChat(sessionId) — message list, sendMessage(), streaming state, connection mode ('backend' | 'local')
    • Automatically falls back to local orchestrator when offline
    • Exposes approveStep(stepId) / rejectStep(stepId) for plan execution
  • Files: src/renderer/pages/ChatPage.tsx, src/renderer/components/chat/ChatWindow.tsx, src/renderer/components/chat/MessageBubble.tsx, src/renderer/components/chat/ContextPanel.tsx, src/renderer/hooks/useChat.ts
  • Outcome: Full chat UI with context transparency, plan approval, and seamless online/offline fallback.

Step 6.3 — BatchBuilderPage

  • Create src/renderer/pages/BatchBuilderPage.tsx:
    • Two views: Active Batches list (default) and Create New Batch wizard
    • Active list renders BatchCard for each active batch config
    • "Create" button opens the wizard
  • Create src/renderer/components/batch-builder/NaturalLanguageInput.tsx:
    • Textarea where the user describes the batch in plain language
    • "Generate" button calls useBatchBuilder().generate(description)
    • Loading skeleton while the LLM generates the config
  • Create src/renderer/components/batch-builder/ConfigPreview.tsx:
    • Shows the generated BatchConfig as an editable form (not raw JSON)
    • Sections: Source, Trigger, Analysis, Actions, Storage — each collapsible
    • Inline editing for every field (prompt textarea, cron expression with human-readable label, mapping table)
    • "Edit raw JSON" toggle for power users
  • Create src/renderer/components/batch-builder/ConnectorPicker.tsx:
    • Dropdown of available connector types (IMAP, Filesystem, Gmail, GDrive, Outlook, Calendar, Generic API)
    • When selected, shows connector-specific config fields (e.g. IMAP: host, folder, filter_from; Filesystem: path picker)
    • OAuth connectors show "Connect account" button that opens the OAuth flow
  • Create src/renderer/components/batch-builder/StoragePicker.tsx:
    • Three-way toggle per storage dimension: Local / Cloud / Sync / None
    • Dimensions: Records, Vectors, Raw data
    • Shows storage impact estimate per option
    • Disabled options grayed out with tier tooltip if current tier doesn't support cloud
  • Create src/renderer/components/batch-builder/SchedulePicker.tsx:
    • Mode toggle: Cron (with human-readable label, e.g. "Every day at 08:00") / Event (on new data from connector)
    • Timezone selector (defaults to system timezone)
    • Visual cron builder for non-technical users (with raw cron input fallback)
  • Create src/renderer/components/batch-builder/BatchCard.tsx:
    • Shows batch name, connector icon, last run time, next run time, status badge (idle / running / error / disabled)
    • Actions: Run now, Edit, Disable/Enable, Delete
    • Expandable to show last run summary (items processed, errors)
  • Create src/renderer/components/batch-builder/BatchTestRunner.tsx:
    • "Dry Run" panel: picks one real item from the source, runs the full analysis pipeline, shows output without saving
    • Shows LLM output, action mapping preview, what would be stored and where
    • Pass/Fail indicator with detailed error on failure
  • Create src/renderer/hooks/useBatchBuilder.ts:
    • useBatchBuilder()generate(description): Promise<BatchConfig>, validate(config), save(config), activate(id), deactivate(id), runNow(id), dryRun(id), delete(id), list of saved configs with live status
    • Backed by tRPC batch.* procedures
  • Files: src/renderer/pages/BatchBuilderPage.tsx, src/renderer/components/batch-builder/{NaturalLanguageInput,ConfigPreview,ConnectorPicker,StoragePicker,SchedulePicker,BatchCard,BatchTestRunner}.tsx, src/renderer/hooks/useBatchBuilder.ts
  • Outcome: Full Batch Builder UI — users can describe a batch in natural language, review/edit the generated config, dry-run it, and activate it with a single flow.

Step 6.4 — PluginStorePage

  • Create src/renderer/pages/PluginStorePage.tsx:
    • Two tabs: Marketplace (browse available plugins) and Installed (manage installed plugins)
    • Marketplace: search bar, category filter chips, grid of plugin cards sorted by rating/installs
    • Installed: list of InstalledPlugin entries with enable/disable toggles and settings links
  • Create plugin card component (inline or shared common/):
    • Shows name, author, description, rating (stars), install count, category badge, price/free badge
    • "Install" button → triggers permission request dialog → installs plugin
    • "Settings" button (installed) → opens plugin-specific config drawer
  • Plugin install flow:
    • On install click: fetch plugin manifest from backend
    • Show PermissionDialog with the permissions the plugin requires
    • On approve: call tRPC plugins.install(id), download and register the plugin worker
    • Show StoragePicker for the plugin's data (what goes local/cloud/sync)
  • Files: src/renderer/pages/PluginStorePage.tsx
  • Outcome: Users can discover and install pre-built plugins from the marketplace with full permission visibility.

Step 6.5 — DataManagerPage

  • Create src/renderer/pages/DataManagerPage.tsx:
    • Top section: StorageOverview dashboard
    • Below: list of DataSourceCard for each active data source (one card per connector/plugin)
    • "Migrate" button opens MigrationWizard
  • Create src/renderer/components/data-manager/StorageOverview.tsx:
    • Visual breakdown: local disk used vs. cloud used vs. cloud limit
    • Per-category breakdown (emails, files, notes, calendar, vectors)
    • Tier upgrade CTA if approaching cloud limit
  • Create src/renderer/components/data-manager/DataSourceCard.tsx:
    • Card per data source (e.g. "Gmail Scanner", "Documenti/Fatture watcher")
    • Shows record count, size, last sync time
    • Inline StoragePicker toggle for that source (where its data lives)
    • "Clear local cache" / "Delete all data" actions with confirmation
  • Create src/renderer/components/data-manager/MigrationWizard.tsx:
    • Step wizard: select source → select direction (local → cloud or cloud → local) → confirm
    • Shows estimated data size and time
    • Progress indicator during migration
    • Rolls back on error
  • Files: src/renderer/pages/DataManagerPage.tsx, src/renderer/components/data-manager/{StorageOverview,DataSourceCard,MigrationWizard}.tsx
  • Outcome: Users have full visibility and control over where every piece of their data lives.

Step 6.6 — ActivityLogPage

  • Create src/renderer/pages/ActivityLogPage.tsx:
    • Full-page filterable table of all batch/plugin activity entries
    • Columns: timestamp, source (batch name / plugin name), action type, data accessed, storage destination, status
    • Filters: source, date range, action type, status (success/error)
    • Row expand: shows full detail — which records were created/updated, which files were read, LLM calls made
    • Export as CSV button
  • Files: src/renderer/pages/ActivityLogPage.tsx
  • Outcome: Complete transparency log so users can audit exactly what each agent did and when.

Step 6.7 — SettingsPage (multi-provider, auth, backup, embeddings)

  • Create src/renderer/pages/SettingsPage.tsx with tabbed sections:
    • AI Providers tab:
      • List of configured providers with status badge (active / inactive / error)
      • Add provider form: name dropdown (OpenAI, Anthropic, Google, Mistral, Groq, Ollama), API key input, model selection, endpoint (for Ollama)
      • Set primary provider and fallback chain
      • Test connection button per provider
      • Separate "Embeddings provider" section: provider + model for embeddings (OpenAI, Cohere, Voyage, Mistral Embed)
      • Info callout: "Text sent to the embeddings provider to generate vectors — make sure you trust this provider with your data"
    • Account & Billing tab:
      • Login/register form (when not authenticated)
      • Current tier display with feature list and upgrade CTA
      • Usage indicators (batch count, cloud storage used)
      • Logout button
    • Backup & Sync tab:
      • Recovery passphrase: generate new / view existing (masked, reveal on click)
      • Manual backup trigger with last backup timestamp
      • Auto-backup schedule toggle + interval picker
      • Backup history table (timestamp, size, restore button)
    • Permissions tab:
      • Table of all active permission grants (plugin/batch, permission type, resource, granted date)
      • Revoke button per grant
      • Links to ActivityLogPage for per-source audit
  • Create src/renderer/components/common/ProviderSelector.tsx:
    • Reusable dropdown that lists configured LLM providers
    • Used in BatchBuilder (model_override field) and Settings
  • Create src/renderer/components/common/PermissionDialog.tsx:
    • Modal triggered when a plugin/batch requests new permissions
    • Lists each requested permission with its reason and resource path
    • Per-permission approve/deny toggles (deny is default)
    • Shows plugin/batch manifest info (name, description, version)
    • "Approve selected" confirms; "Deny all" closes without granting
  • Files: src/renderer/pages/SettingsPage.tsx, src/renderer/components/common/PermissionDialog.tsx, src/renderer/components/common/ProviderSelector.tsx
  • Outcome: Centralised settings covering providers, embeddings, auth, backup, and permissions.

Phase 7 — Cleanup & Hardening

Step 7.1 — Remove deprecated AI code

  • Delete src/main/ai/copilot.ts (Copilot SDK replaced by LiteLLM)
  • Delete src/main/ai/chat-copilot.ts (LangChain adapter no longer needed)
  • Delete or archive src/main/ai/llm.ts (replaced by src/main/llm/litellm-client.ts)
  • Remove @github/copilot-sdk, @langchain/langgraph from dependencies (if unused)
  • Clean up src/main/ai/provider.ts: simplify to delegate to src/main/llm/providers.ts
  • Remove currentSender module-level mutable state from orchestrator (proper context passing)
  • Update src/main/index.ts startup: remove import './ai/copilot', add BatchRunner.startScheduler(), add AuthManager init
  • Files: Multiple files under src/main/ai/, package.json, src/main/index.ts
  • Outcome: No dead code; clean, maintainable codebase.

Step 7.2 — Add error handling and logging

  • Implement structured logging in main process:
    • Log levels: debug, info, warn, error
    • Log destinations: console (dev), file (production, rotated)
    • Correlation IDs for request tracing across IPC → backend → response
  • Add error boundaries in renderer:
    • Per-route error boundaries
    • AI chat error boundary (graceful degradation)
    • Plugin error boundary (shows which plugin failed)
  • Files: src/main/utils/logger.ts (new), src/renderer/components/ErrorBoundary.tsx (new)
  • Outcome: Production-ready error handling and observability.

Step 7.3 — Electron integration tests

  • Test BackendClient with mocked HTTP responses
  • Test PlanRunner with sample execution plans
  • Test SyncQueue offline → online transition
  • Test BackupManager encrypt → decrypt round-trip
  • Test PermissionManager grant → check → revoke cycle
  • Files: src/main/__tests__/ (new test directory)
  • Outcome: Confidence that all Electron-side components work correctly.

New Dependencies (package.json)

Package Purpose
@journeyapps/sqlcipher Encrypted SQLite (replaces better-sqlite3)
argon2 Key derivation for E2E backup
node-cron Batch agent scheduling
chokidar File watching (FileWatcher plugin)
imapflow IMAP client (IMAP connector)
googleapis Gmail + GDrive OAuth connectors
lancedb Local vector store
onnxruntime-node Local embeddings (optional, future)

Execution Notes

  • Each step is independently committable and produces working code.
  • Phases 1-2 (LLM client + plugins) are independent of the backend — can start immediately.
  • Phase 3 (backend integration) requires the backend repo to have the /api/v1/chat endpoint ready.
  • Phase 5.2 (SQLCipher) is intentionally late to avoid encryption overhead during active schema changes.
  • The existing app continues to work throughout the migration. Local orchestration is preserved until backend is ready (Step 3.2).
  • One step at a time. Implement one numbered step per session. When the step is fully done, mark all its checkboxes as [x] in this file and commit with message step N complete: <outcome line>.