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
BACKEND_PLAN.mdfor 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.tswith 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)BillingTierenum (free,pro,power,team)AuthTokens(access_token, refresh_token, expires_at)UserProfile(id, email, tier)
- Create
src/shared/batch-types.tswith 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,permissionsBatchStatus—'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.jsonpaths if needed to includesrc/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:LiteLLMClientclass 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:ProviderConfiginterface: name, apiKey, model, endpoint (for Ollama), timeout, isLocalProviderRegistry: manages configured providers, persists to electron-storegetActiveProvider(),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 withLiteLLMClient.complete()/LiteLLMClient.stream() - Keep local orchestration working with the new client (backend delegation comes in Phase 3)
- Replace direct
- Update
src/main/ai/llm.ts:- Deprecate. Redirect
getLLM()to instantiate viaLiteLLMClientas a thin compatibility shim
- Deprecate. Redirect
- Update
src/main/ai/embeddings.tsto delegate tosrc/main/llm/embeddings.ts - Update
src/main/ai/token.ts:- Add
listStoredProviders(): Promise<string[]>to enumerate which providers have tokens
- Add
- 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:PluginManifestinterface:name,description,version,permissions: PermissionRequest[],schedule?: string(cron),entryPoint: stringPermissionRequest:type(read_folder, read_email, read_calendar, read_browser_history),resource?: string(path, account),reason: stringvalidateManifest(manifest): ValidationResult— validates structure, checks for dangerous permissions
- Create
src/main/permissions/permission-manager.ts:PermissionManagerclass (singleton):grantPermission(pluginName, permission): void— persists to SQLiterevokePermission(pluginName, permission): voidcheckPermission(pluginName, permission): booleangetPluginPermissions(pluginName): PermissionGrant[]getAllGrants(): PermissionGrant[]logAccess(pluginName, permission, resource, timestamp): void— activity loggetActivityLog(pluginName?, limit?): ActivityLogEntry[]
- Permission grants stored in a new
plugin_permissionsSQLite table - Activity log stored in a new
plugin_activity_logSQLite table
- Add
plugin_permissionsandplugin_activity_logtables tosrc/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:WorkerPoolclass:- 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
- Manages a pool of Node.js
- Create
src/main/workers/batch-runner.ts:BatchRunnerclass:registerPlugin(manifest): void— validates manifest, stores in registrystartScheduler(): void— cron-based scheduler usingnode-cronor simple setIntervalrunPlugin(name, triggerContext?): Promise<PluginResult>— manual triggerstopAll(): 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_emailpermission - Connects to IMAP via
imapflow(account configured in settings) - Scans for new emails since last run
- Uses
LiteLLMClientto 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
- Manifest: requires
- Create
src/plugins/file-watcher.ts:- Manifest: requires
read_folderpermission for each watched path - Uses
chokidarto watch approved directories - On new/modified file: reads content, generates embedding, upserts into vector store
- Supports: .txt, .md, .pdf (text extraction), .docx (basic extraction)
- Manifest: requires
- Create
src/plugins/calendar-sync.ts:- Manifest: requires
read_calendarpermission - Parses ICS files or connects to CalDAV endpoint
- Detects scheduling conflicts
- Suggests reorganizations via LLM analysis
- Returns calendar events + conflict reports
- Manifest: requires
- Create
src/plugins/browser-agent.ts:- Manifest: requires
read_browser_historypermission (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
- Manifest: requires
- 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:BackendClientclass:baseUrlconfigurable (default: production cloud URL, overridable for dev)setAuthToken(jwt: string): voidchat(request: ChatRequest): Promise<ChatResponse>— POST /api/v1/chatchatStream(request: ChatRequest): AsyncGenerator<string>— WebSocket /api/v1/chat/streamgetPlaybooks(): Promise<ExecutionPlan[]>— GET /api/v1/plans/playbookuploadBackup(blob: Buffer, metadata: BackupMetadata): Promise<void>— PUT /api/v1/backupdownloadBackup(): 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:PlanRunnerclass: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_stepreferences (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
ChatRequestfrom 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:streamIPC 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()andbuildGlobalContext()as context builders for the request payload
- When online: forward chat requests to backend via
- Update
src/main/router/index.tsaisub-router:chatmutation: call refactored orchestrator (which now delegates to backend)- Add
getPlaybooksquery: fetches cached playbooks - Keep
dailyBriefmutation: 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
ConversationBufferclass: stores last N messages per sessionaddMessage(sessionId, role, content),getHistory(sessionId, limit?) -> Message[]- Cleared on session end
- Long-term KV store: SQLite-backed key-value store
- New
agent_memorytable:id,namespace(agent name),key,value(JSON text),updated_at AgentMemoryStoreclass:get(namespace, key),set(namespace, key, value),delete(namespace, key),listKeys(namespace)- Used by agents to persist learned facts, user preferences
- New
- Vector store: Already exists (LanceDB). Enhance with:
- Multi-collection support: separate tables for notes, emails, files, calendar
searchByCollection(collection, query, limit) -> SearchResult[]
- Short-term memory: In-memory conversation buffer
- Add
agent_memorytable tosrc/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 phrasederiveKey(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-GCMdecrypt(ciphertext: Buffer, key: Buffer, iv: Buffer, authTag: Buffer): Buffer- Uses
node:cryptofor AES andargon2npm package for key derivation
- Create
src/main/backup/backup-manager.ts:BackupManagerclass:createBackup(passphrase: string): Promise<BackupBlob>— Exports SQLite DB, encrypts, returns blob + metadatarestoreBackup(blob: Buffer, passphrase: string): Promise<void>— Decrypts blob, replaces local DB, re-initializesuploadBackup(passphrase: string): Promise<void>— Creates backup, uploads viaBackendClientdownloadAndRestore(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:SyncQueueclass:enqueue(action: QueuedAction): void— Adds action to persistent queue (SQLite tablesync_queue)processQueue(): Promise<void>— Processes queued actions in FIFO order when onlinegetQueueSize(): numberclearQueue(): void- Conflict resolution: last-write-wins with timestamps
- New
sync_queuetable: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
failedfor user review
- Add
sync_queuetable 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:AuthManagerclass: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/registerlogout(): void— Clears stored JWTgetToken(): string | null— Returns current JWTrefreshToken(): Promise<void>— Auto-refresh before expiryisAuthenticated(): booleangetCurrentTier(): 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
BackendClientto useAuthManager.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/sqlcipherto dependencies (replacesbetter-sqlite3) - Update
src/main/db/index.ts:- Replace
better-sqlite3import with@journeyapps/sqlcipher - On first launch: derive DB key from OS keychain or prompt user
initDb(password): opens DB withPRAGMA key = 'password'- Migration path for existing unencrypted DBs: detect → export → create encrypted → import → delete old
- WAL mode still enabled after keying
- Replace
- Update
src/main/index.ts: pass password toinitDb() - 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
- Define top-level routes:
- 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()— returnsStorageStats,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
ChatWindowandContextPanelcomponents - Online/offline status bar at top
- Two-column layout: chat area (left/main) + collapsible
- Create
src/renderer/components/chat/ChatWindow.tsx:- Message list rendering
MessageBubblefor each entry - Input bar with send button and attachment support
- Handles streaming tokens from
useChathook - 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)
- Message list rendering
- 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
BatchCardfor 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
BatchConfigas 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
- Shows the generated
- 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)
- Shows batch name, connector icon, last run time, next run time, status badge (
- 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
InstalledPluginentries 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
PermissionDialogwith the permissions the plugin requires - On approve: call tRPC
plugins.install(id), download and register the plugin worker - Show
StoragePickerfor 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:
StorageOverviewdashboard - Below: list of
DataSourceCardfor each active data source (one card per connector/plugin) - "Migrate" button opens
MigrationWizard
- Top section:
- 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
StoragePickertoggle 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.tsxwith 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
- AI Providers tab:
- 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 bysrc/main/llm/litellm-client.ts) - Remove
@github/copilot-sdk,@langchain/langgraphfrom dependencies (if unused) - Clean up
src/main/ai/provider.ts: simplify to delegate tosrc/main/llm/providers.ts - Remove
currentSendermodule-level mutable state from orchestrator (proper context passing) - Update
src/main/index.tsstartup: removeimport './ai/copilot', addBatchRunner.startScheduler(), addAuthManagerinit - 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/chatendpoint 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).