# 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 }` - `BatchTrigger` — `{ type: 'cron' | 'event', schedule?: string, timezone?: string }` - `BatchAnalysis` — `{ prompt: string, model_override?: string, output_schema?: object }` - `BatchAction` — `{ type: BatchActionType, table?: string, mapping?: Record }` - `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` - `stream(messages: Message[], options?: CompletionOptions): AsyncGenerator` - `embed(text: string): Promise` - `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` 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` 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` — 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` — 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` — POST /api/v1/chat - `chatStream(request: ChatRequest): AsyncGenerator` — WebSocket /api/v1/chat/stream - `getPlaybooks(): Promise` — GET /api/v1/plans/playbook - `uploadBackup(blob: Buffer, metadata: BackupMetadata): Promise` — 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` — 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` — 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` — Exports SQLite DB, encrypts, returns blob + metadata - `restoreBackup(blob: Buffer, passphrase: string): Promise` — Decrypts blob, replaces local DB, re-initializes - `uploadBackup(passphrase: string): Promise` — Creates backup, uploads via `BackendClient` - `downloadAndRestore(passphrase: string): Promise` — 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` — 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` — Calls backend POST /api/v1/auth/login, stores JWT in secure storage (via token.ts) - `register(email, password): Promise` — Calls POST /api/v1/auth/register - `logout(): void` — Clears stored JWT - `getToken(): string | null` — Returns current JWT - `refreshToken(): Promise` — 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`, `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: `.