diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index e2d4421..0a590b3 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -32,7 +32,6 @@ Rules: |-----------|------|-----------| | **`adiuvAI/`** | Electron desktop app (TypeScript/React) | `git.muticolturano.com/adiuvAI/adiuvAI` | | **`api/`** | FastAPI backend (Python) | `git.muticolturano.com/adiuvAI/api` | -| **`website/`** | Landing page (single `index.html`) | `git.muticolturano.com/adiuvAI/website` | | **`docs/`** | Planning docs & working memory (not a submodule) | -- | After clone, run `git submodule update --init --recursive` to populate submodules. @@ -47,13 +46,16 @@ After clone, run `git submodule update --init --recursive` to populate submodule ```bash cd adiuvAI -npm run start # Start dev server (Electron + Vite) -npm run lint # ESLint -npm run knip # Dead code analysis -npm run make # Build installers (Windows/Linux/macOS) -npm run package # Package without creating installers -npx drizzle-kit generate # Generate migration from schema changes -npx drizzle-kit push # Push schema directly (dev only) +npm run start # Dev (Electron + Vite) +npm run lint # ESLint +npm run knip # Dead code analysis +npm run make # Build installers (Win/Linux/macOS) +npm run package # Package without installers +npm run dev:web # Standalone web SPA dev +npm run build:web # Build standalone SPA → dist-web/ +npm run preview:web # Preview built web SPA +npx drizzle-kit generate # Generate migration from schema +npx drizzle-kit push # Push schema directly (dev only) ``` No test suite currently. @@ -66,45 +68,54 @@ Renderer (React 19 + TanStack Router) Preload (contextBridge: window.electronTRPC + window.electronAI) ↓ IPC channels Main Process (Node.js) - ├── tRPC router (all CRUD + AI procedures) + ├── tRPC router (CRUD + AI proxy procedures) ├── SQLite (better-sqlite3 + Drizzle ORM, WAL mode) - └── LangGraph orchestrator (3 specialist agents, pluggable LLM providers) + └── Backend delegation layer (orchestrator.ts forwards to FastAPI WS) ``` -**Local-first app.** All user data (tasks, notes, projects) in local SQLite. AI system (LangGraph + LangChain) runs in Electron main process, pluggable providers (OpenAI, Anthropic, GitHub Copilot). +**Local-first storage, cloud AI.** All user data (clients, projects, tasks, notes, timelines) in local SQLite. AI lives entirely on the FastAPI backend — Electron orchestrator is a thin delegation shell that forwards to `/api/v1/device` WS and dispatches v3 typed stream frames + tool-call ↔ DrizzleExecutor round-trips back to renderer. **IPC channels**: -- `'trpc'` — bidirectional tRPC request/response (all CRUD) -- `'ai:stream'` — one-way token streaming main → renderer +- `'trpc'` — bidirectional tRPC request/response (all CRUD + auth + agent + memory proxy) +- `'ai:stream'` — one-way v3 stream frames main → renderer - `'ai:action'` — AI side-effects (e.g. agent auto-creates task) -**Key source paths**: -- `src/main/ipc.ts` — Custom tRPC↔IPC bridge -- `src/main/router/index.ts` — All tRPC routers (~600 LOC) -- `src/main/ai/orchestrator.ts` — LangGraph intent routing + 3 agents (~991 LOC) -- `src/main/db/schema.ts` — 7 tables (clients, projects, tasks, checkpoints, notes, noteEdits, taskComments) -- `src/renderer/routes/` — File-based routing (TanStack Router auto-generates `routeTree.gen.ts`) -- `src/renderer/components/ui/` — shadcn/ui primitives (new-york theme, neutral colors) -- `src/main/auth/auth-manager.ts` — Login, register, logout, OAuth flow (singleton) -- `src/main/auth/backup-key.ts` — Device-specific AES-256 backup key (safeStorage, not password-derived) -- `src/main/ai/token.ts` — Two-tier token storage: safeStorage + electron-store fallback -- `src/main/auth/locale-defaults.ts` — Detects timezone, date/time format, language from OS locale -- `src/main/api/format-row.ts` — Formats timestamp columns using user's FormatPrefs +**Main process layout (`src/main/`)**: +- `index.ts` — Window creation, app lifecycle, protocol handler +- `ipc.ts` — Custom tRPC↔IPC bridge +- `store.ts` — electron-store for `FormatPrefs` + `uiLanguage`; exports `getUiLanguage()` +- `router/index.ts` — All tRPC sub-routers (~1627 LOC) +- `db/schema.ts` — 10 tables: clients, projects, tasks, timelineEvents, timelineEventDependencies, notes, noteEdits, taskComments, agentRuns, agentRunActions +- `db/index.ts` — Drizzle + better-sqlite3 (WAL), singleton `getDb()`, `initDb()` migrations +- `db/notes-backfill.ts` — Startup backfill: generates `aiSummary` for notes with null summary +- `ai/orchestrator.ts` — Thin backend-delegation layer (~304 LOC). Connectivity/auth guard → `BackendClient.sendHomeRequest()` / `sendFloatingRequest()` → forwards v3 stream frames to renderer. Also schedules daily-brief regeneration. +- `ai/token.ts` — Two-tier token storage (safeStorage + electron-store fallback) +- `agents/agent-scheduler.ts` — Local agent scheduling (filesystem agents) +- `api/backend-client.ts` — WS client to FastAPI: handles tool-call round-trips, v3 stream frame dispatch, journey + agent proxies +- `api/drizzle-executor.ts` — Executes backend-issued tool calls against local SQLite. Wraps results through `formatRow()`/`formatRows()` using user FormatPrefs +- `auth/auth-manager.ts` — Login, register, logout, OAuth flow (singleton) +- `auth/backup-key.ts` — Device-specific AES-256 backup key (safeStorage, not password-derived) +- `auth/locale-defaults.ts` — Detects timezone, date/time format, language from OS locale + +**tRPC routers** (in `appRouter`): `health`, `settings`, `clients`, `projects`, `tasks`, `timelineEvents`, `timelineEventDependencies`, `notes`, `noteEdits`, `taskComments`, `ai`, `auth`, `agent` (with `local` / `cloud` / `journey` sub-routers), `memory`. + +**Renderer** (`src/renderer/`): file-based routing via TanStack Router (`routeTree.gen.ts` auto-generated). shadcn/ui new-york theme, neutral colors. Path alias `@/*` → `src/renderer/*`. Notes editor: Milkdown (`@milkdown/crepe`). **Non-obvious details**: -- `electron-trpc` NOT used — custom IPC bridge in `ipc.ts` + `ipcLink.ts` because electron-trpc bundles tRPC v10 internals +- `electron-trpc` NOT used — custom IPC bridge (`ipc.ts` + `lib/ipcLink.ts`) because electron-trpc bundles tRPC v10 internals - Vite configs use `.mts` extension to avoid ESM/CJS conflicts with electron-forge -- `forge.config.ts` has complex cross-compilation hooks (downloads platform-specific native binaries for better-sqlite3) +- `forge.config.ts` has cross-compilation hooks (downloads platform-specific native binaries for better-sqlite3) - DB has no foreign key constraints — cascade deletes in tRPC procedures - Timestamps are milliseconds (`Date.getTime()`), not ISO strings -- Notes use `aiSummary` index (≤250 char, backend gpt-4o-mini) for AI navigation — LanceDB fully removed -- AI note edits go through `noteEdits` HITL table; AI calls `propose_note_edit`, user approves/rejects in UI +- Notes use `aiSummary` (≤250 char, backend `gpt-4o-mini` via `POST /api/v1/agents/notes/summarize`) for AI navigation — LanceDB fully removed +- AI note edits go through `noteEdits` HITL table (`type: append|insert|replace`, `status: pending|approved|rejected`); backend tool `propose_note_edit` → drizzle-executor inserts row; user approves/rejects in UI; auto-reject on missing anchor +- `checkpoints` table replaced by `timelineEvents` + `timelineEventDependencies` (events are typed `milestone|checkpoint|activity`, with optional dep edges) +- `agentRuns` + `agentRunActions` populated by backend-client on tool_call/run_complete frames; UI reads via `agent.runs` / `agent.runActions` **Settings Page (shared Electron + Web)**: -- Settings page runs in **both** Electron and standalone web SPA (future landing-page portal). Same React components — no duplication. -- **Platform Adapter pattern**: `PlatformProvider` context (`src/renderer/lib/platform.tsx`) exposes `isElectron`/`isWeb`/`hasLocalAgents`/`hasFileDialog` flags. Components use `usePlatform()` to gate Electron-only features or disable on web. -- **6 sections**: Profile, AI Preferences, Account, Billing, Appearance, Agents. Sidebar nav with icons in `types.ts` (`SECTIONS` array). -- **Web build**: `vite.web.config.mts` builds standalone SPA to `dist-web/`. Entry: `web.html` → `src/renderer/web-main.tsx` (uses `httpBatchLink` via `src/renderer/lib/httpLink.ts` instead of `ipcLink`). Scripts: `npm run dev:web`, `npm run build:web`, `npm run preview:web`. +- Settings page runs in **both** Electron and standalone web SPA. Same React components — no duplication. +- **Platform Adapter**: `PlatformProvider` context (`src/renderer/lib/platform.tsx`) exposes `isElectron`/`isWeb`/`hasLocalAgents`/`hasFileDialog`. Components use `usePlatform()` to gate Electron-only features. +- **Web build**: `vite.web.config.mts` → `dist-web/`. Entry: `web.html` → `src/renderer/web-main.tsx` (uses `httpBatchLink` via `lib/httpLink.ts` instead of `ipcLink`). - **Electron-only gating**: Device ID card and local agent filesystem gated behind `platform.isElectron`. On web: visible but disabled, not hidden. - **Gotcha**: Do NOT add Electron-specific settings (server URL, native file pickers) without wrapping in `platform.isElectron`. Same component tree renders on web. @@ -115,21 +126,21 @@ Main Process (Node.js) - `AppShell.tsx` gates: if `profile.onboardingCompletedAt == null` → render `` instead of app. - `auth.status` tRPC procedure auto-seeds `language` and `user_name` into MemoryCore if missing (fire-and-forget `.catch(() => {})`). - Format prefs (timezone, dateFormat, timeFormat) stored in electron-store (`FormatPrefs`), not core memory — device-specific. -- `drizzle-executor.ts` wraps all query results through `formatRow()`/`formatRows()` using user's FormatPrefs. +- `drizzle-executor.ts` wraps all query results through `formatRow()`/`formatRows()` using user FormatPrefs. - Settings > Profile allows post-onboarding edit of all fields + format prefs. -- **Gotcha — shadcn Button `outline` variant in dark mode**: Variant defines `dark:bg-input/30 dark:border-input dark:hover:bg-input/50` — overrides custom `className` background. Fix: switch between `variant="default"` and `variant="outline"` instead of className overrides. +- **Gotcha — shadcn Button `outline` variant in dark mode**: variant defines `dark:bg-input/30 dark:border-input dark:hover:bg-input/50` — overrides custom `className` background. Fix: switch between `variant="default"` and `variant="outline"` instead of className overrides. - **Gotcha — locale codes vs human names**: `app.getLocale()` and `navigator.language` return codes like `en-US`. Use `Intl.DisplayNames(undefined, { type: 'language' })` to convert to "English". Must do in both main process (`locale-defaults.ts`) and renderer (`OnboardingFlow.tsx`). **i18n (Internationalization)**: -- Uses `i18next` + `react-i18next` with bundled JSON translations (no lazy loading). +- `i18next` + `react-i18next` with bundled JSON translations (no lazy loading). - Config in `src/renderer/i18n.ts`. 5 languages: EN, IT, ES, FR, DE. `SUPPORTED_LANGUAGES` exported for UI selectors. - Translation files: `src/renderer/locales/{en,it,es,fr,de}/translation.json`. Namespaces: `nav`, `auth`, `tasks`, `settings`, `common`, `errors`, `home`, `timeline`, `projects`, `agents`. - **`common.*` namespace** holds shared labels (`save`, `cancel`, `delete`, `edit`, `add`, `rename`, `saving`, `deleting`, `creating`, `renameDescription`, `deleteTitle`). Check `common.*` before adding new key. -- Pluralization uses i18next `_one`/`_other` suffixes (e.g. `tasksDueToday_one`, `tasksDueToday_other`). +- Pluralization uses i18next `_one`/`_other` suffixes. - `LanguageSync` component in `src/renderer/index.tsx` reads persisted `uiLanguage` from electron-store via tRPC on startup, syncs to i18next. - Language selector in `GeneralSection.tsx` (Settings > General). On change: (1) calls `i18n.changeLanguage()`, (2) persists to electron-store via `setUiLanguage` mutation, (3) writes to backend core memory so AI responds in same language. -- `getUiLanguage()` exported from `src/main/store.ts` — used by `orchestrator.ts` to append language hint to daily brief prompt. -- Static data arrays needing translation use `labelKey` pattern (not `label`): store translation key, call `t(labelKey)` at render. Used in `NAV_ITEMS`, `COLUMNS`, `SECTIONS`, `SUGGESTION_CHIPS`. +- `getUiLanguage()` exported from `src/main/store.ts`. +- Static data arrays needing translation use `labelKey` pattern: store translation key, call `t(labelKey)` at render. Used in `NAV_ITEMS`, `COLUMNS`, `SECTIONS`, `SUGGESTION_CHIPS`. - When adding new translated text: add key to **all 5** JSON files. Keep `common.*` consistent across all languages. **Google OAuth (adiuvAI side)**: @@ -158,16 +169,16 @@ gunicorn app.main:app -k uvicorn.workers.UvicornWorker -w 4 --timeout 120 alembic upgrade head # Testing -pytest # all tests -pytest -v # verbose -pytest tests/test_agents.py # single file -pytest tests/test_agents.py -k test_name # single test +pytest # all tests +pytest -v # verbose +pytest tests/test_deep_agent.py # single file +pytest tests/test_deep_agent.py -k test_name # single test # Linting/formatting ruff check . ruff format . -# Docker (full stack: app + postgres + minio + qdrant) +# Docker (full stack) docker compose up --build ``` @@ -175,56 +186,72 @@ docker compose up --build ``` FastAPI app (app/main.py) -├── Middleware: TierRateLimiter → Sanitizer → CORS -├── HTTP Routes (app/api/routes/) -│ ├── auth.py — register, login, token refresh (bcrypt + HS256 JWT) -│ ├── chat.py — POST /chat, WS /chat/stream -│ ├── plans.py — execution plan playbooks -│ ├── storage.py — E2E-encrypted cloud storage (S3) -│ ├── backup.py — encrypted backup upload/download -│ ├── vectors.py — encrypted vector upsert/search (Pinecone/Qdrant) -│ ├── plugins.py — plugin marketplace (Power+ tier) -│ └── billing.py — Stripe subscriptions +├── Lifespan: APScheduler crons (memory hourly + audit weekly) when SCHEDULER_ENABLED +├── Middleware: TierRateLimit → Sanitizer → CORS +├── HTTP Routes (app/api/routes/) — all under /api/v1 +│ ├── auth.py — register, login, refresh, profile, OAuth, onboarding, password +│ ├── chat.py — POST /chat, /chat/brief, /chat/embed +│ ├── agents.py — catalog, can-create, trigger, notes/summarize +│ ├── agent_setup.py — guided agent setup (journey) +│ ├── billing.py — Stripe checkout, webhook, subscription, invoices +│ ├── device_ws.py — WS /device (unified streaming endpoint: home, floating, brief, journey) +│ └── memory.py — core / relational / forget-all ├── Agent System (app/agents/) -│ ├── task_agent.py — 8 tools -│ ├── project_agent.py — 6 tools -│ ├── checkpoint_agent.py — 4 tools -│ └── note_agent.py — 5 tools -├── Orchestration (app/core/) -│ ├── orchestrator.py — intent classification + agent routing -│ ├── agent_registry.py — decorator-based agent registry -│ ├── execution_plan.py — server-side prompt templates + plan builder -│ ├── llm.py — LiteLLM factory (100+ providers) -│ └── memory_middleware.py -├── Billing (app/billing/) -│ ├── tier_manager.py — feature matrix (Free/Pro/Power/Team) -│ └── stripe_service.py — Stripe checkout + webhooks -├── Storage (app/storage/) — S3 blob store, vector store, encryption -└── Marketplace (app/marketplace/) — plugin catalog, review, revenue sharing +│ ├── task_agent.py +│ ├── project_agent.py +│ ├── note_agent.py +│ ├── timeline_agent.py +│ └── filesystem_agent.py +├── Core (app/core/) +│ ├── deep_agent.py — main agent runner (run_home / run_floating / run_brief / run_journey) +│ ├── brief_agent.py — daily brief generation +│ ├── agent_runner.py — local + cloud agent run executor +│ ├── agent_session_buffer.py — per-session conversation buffer +│ ├── agent_registry.py — decorator-based agent registry +│ ├── llm.py — LiteLLM factory (multi-provider) +│ ├── memory_middleware.py — encrypted core memory read/write +│ ├── memory_extraction.py — LLM extraction from conversation tail +│ ├── memory_maintenance.py — drain queue, contradiction audit, proactive mining +│ ├── note_summarizer.py — gpt-4o-mini summary for notes +│ ├── output_formatter.py — render agent output to user-facing markdown +│ ├── embeddings.py +│ ├── device_manager.py — device registration / WS session tracking +│ ├── ws_context.py — per-WS user context plumbing +│ ├── langfuse_client.py — Langfuse prompt + tracing client +│ └── preprocessors/ — input preprocessors (e.g. email_html.py) +├── Auth (app/auth/oauth_providers.py) — GoogleOAuthProvider (httpx + manual PKCE) +├── Billing (app/billing/) — tier_manager + stripe_service +├── Integrations (app/integrations/) — gmail.py, ms_graph.py +└── Models (app/models.py) — SQLAlchemy 2.0 ORM ``` -**LLM routing**: `gpt-4o-mini` classifies intent → routes to domain agent → agent uses `gpt-4o` with tools → tool calls describe client-side ops (JSON) → Electron executes locally, returns results. +**HTTP route prefix**: every router included with `prefix="/api/v1"`. So `/api/v1/auth/...`, `/api/v1/chat`, `/api/v1/agents/...`, `/api/v1/memory/...`, `/api/v1/device` (WS). -**Zero-trust data model**: Backend never stores or decrypts user content. PostgreSQL holds only auth, billing, plugin metadata, storage record pointers. All user data E2E-encrypted before leaving Electron client. +**ORM models** (`app/models.py`): `User`, `RefreshToken`, `OAuthAccount`, `Subscription`, `LocalAgentConfig`, `CloudAgentConfig`, `AgentRunLog`, `MemoryCore`, `MemoryAssociative`, `MemoryEpisodic`, `MemoryProactive`, `ExtractionQueue`, `MemoryRelation`, `Plugin`. PostgreSQL (asyncpg + SQLAlchemy 2.0 async). Alembic migrations in `alembic/versions/`. -**Key config**: `app/config/settings.py` — all env vars via Pydantic Settings. Copy `.env.example` to `.env` for local dev. Stripe and S3 gracefully stub when keys absent. +**Lifespan crons** (only if `settings.SCHEDULER_ENABLED`): +- `_memory_cron_tick` — hourly: drains Free-tier extraction queue + mines proactive patterns for Power+ users +- `_memory_audit_cron_tick` — weekly: contradiction scan + label canonicalization for all users (Phase 7) -**Database**: PostgreSQL with async SQLAlchemy 2.0 + asyncpg. 9 ORM models in `app/models.py`. Alembic migrations in `alembic/versions/`. +**LLM routing**: backend agents own all intelligence. Tool calls describe client-side ops (JSON) → Electron `drizzle-executor` runs them against local SQLite → result returned to backend over WS. Tool loop cap inside agent runner prevents runaway iteration. -**Testing**: pytest + pytest-asyncio. Fixtures in `tests/conftest.py` create in-memory SQLite + moto-mocked S3. Test users seeded per tier (free/pro/power/team). +**Zero-trust data model**: backend never stores raw user content. PostgreSQL holds auth, billing, plugin metadata, encrypted memory (Core/Associative/Episodic/Proactive/Relational), agent configs, run logs. + +**Config**: `app/config/settings.py` — all env vars via Pydantic Settings. Copy `.env.example` to `.env` for local dev. + +**Testing**: pytest + pytest-asyncio. Fixtures in `tests/conftest.py`. Active suites: agent runner, auth, brief/deep agents, device WS, integrations, journey, memory (audit/extraction/middleware/models/proactive/relations), middleware, output formatter, preprocessors, schemas, ws_unified. ### Non-obvious details -- **Tier from DB, not JWT**: `get_current_user` decodes JWT but fetches authoritative tier from `subscriptions` table — tier changes take effect immediately, no re-login needed -- **Refresh tokens hashed**: Plaintext returned to client, stored as SHA-256 in DB — server can never retrieve plaintext (intentional) +- **Tier from DB, not JWT**: `get_current_user` decodes JWT but fetches authoritative tier from `subscriptions` — tier changes take effect immediately, no re-login needed +- **Refresh tokens hashed**: plaintext returned to client, stored as SHA-256 in DB — server can never retrieve plaintext (intentional) - **WebSocket auth via query param**: `?token=` instead of Bearer header (WebSocket handshake limitation) -- **Prompt IP protection**: `PromptTemplateRegistry` keeps prompts server-side; clients receive opaque `template_id`. `SanitizerMiddleware` strips leaked fragments from responses -- **Agents don't execute operations**: Tools return JSON describing client-side ops — Electron client executes against local SQLite -- **Alembic async/sync split**: App uses `postgresql+asyncpg`, Alembic CLI needs `postgresql+psycopg2` — `env.py` handles URL conversion -- **Tool loop cap**: Agent `_tool_loop` stops after 5 iterations to prevent infinite loops -- **Route order matters**: `/backup/history` must be declared before `/backup/{backup_id}` to avoid path param shadowing +- **Unified device WS**: `/api/v1/device` is the single bidirectional channel. Handles home requests, floating requests, daily briefs, journeys, heartbeats. Tool calls round-trip through the same socket +- **Prompt IP protection**: prompts kept server-side via Langfuse (`langfuse_client`). `SanitizerMiddleware` strips leaked fragments from responses +- **Agents don't execute operations**: tools return JSON describing client-side ops — Electron client executes against local SQLite +- **Alembic async/sync split**: app uses `postgresql+asyncpg`, Alembic CLI needs `postgresql+psycopg2` — `env.py` handles URL conversion - **CORS includes `app://`**: Electron uses custom `app://` protocol, not http/https -- **Vector search on encrypted data is not semantic**: Backend derives deterministic 32-dim floats from blob SHA-256 for storage/search — known trade-off +- **Run-disconnect tracking**: `_mark_runs_disconnected` flips active runs when WS drops so client can resume cleanly **Onboarding (API side)**: - `PUT /auth/me/memory` — updates core memory k/v pairs, optionally marks onboarding complete (`mark_onboarded: true` sets `users.onboarding_completed_at`). @@ -234,7 +261,7 @@ FastAPI app (app/main.py) - `users.onboarding_completed_at` — nullable TIMESTAMPTZ, returned as epoch ms (int) in UserProfile schema. **i18n (API side)**: -- `_language_instruction()` in `app/core/deep_agent.py` reads user's `language` from `MemoryCore`, appends system prompt directive ("Always respond in {language}") to all 4 `run_*` functions. +- `_language_instruction()` in `app/core/deep_agent.py` reads user's `language` from `MemoryCore`, appends system prompt directive ("Always respond in {language}") to all `run_*` functions. - Electron client writes chosen language to backend core memory on change — API picks up on next agent call. **Google OAuth (api side)**: @@ -242,38 +269,40 @@ FastAPI app (app/main.py) - Provider abstraction in `app/auth/oauth_providers.py` — `GoogleOAuthProvider` uses `httpx` directly (no `authlib`). PKCE S256 implemented manually via `generate_pkce_pair()`. - `_pending_states` dict in `routes/auth.py` is **in-memory** — works for single-process dev, doesn't survive restarts, doesn't scale to multiple workers. Replace with Redis in production. - `users.password_hash` is **nullable** — social-only users have `password_hash=None`. `await db.flush()` required before creating linked `OAuthAccount` to populate `new_user.id` before commit. -- `OAUTH_REDIRECT_URI` must point to **API backend** (e.g. `https://api.adiuvai.com/...`), not website domain. `adiuvai.com` is static site with no server-side routing. +- `OAUTH_REDIRECT_URI` must point to **API backend** (e.g. `https://api.adiuvai.com/...`). - **Unverified email + existing account = 409**: if `email_verified=False` and email already registered, callback returns 409. Without this guard, branch 3 would INSERT duplicate email and crash with DB constraint violation (500). - **Testing OAuth routes**: mock `GoogleOAuthProvider.exchange_code` and `get_userinfo` with `patch.object(..., new=AsyncMock(...))` — works because FastAPI instantiates new provider per request. Use `monkeypatch.setattr(settings, "GOOGLE_AUTH_CLIENT_ID", ...)` to simulate configured credentials without restart. ### Tier System -| Feature | Free | Pro | Power | Team | -|---------|------|-----|-------|------| -| Rate limit | 20/min | 60/min | 120/min | 200/min | -| Agents | 3 | unlimited | unlimited | unlimited | -| Cloud storage | 0 GB | 5 GB | 25 GB | unlimited | -| Plugin marketplace | no | no | yes | yes | +Source of truth: `app/billing/tier_manager.py` (`FEATURES` + `RATE_LIMITS` dicts). -Enforced in `app/api/middleware/rate_limit.py` (sliding window) and `app/billing/tier_manager.py` (feature checks + quota enforcement). +| Feature | Free | Pro | Power | Team | +|---------------------|--------|-----------|-----------|-----------| +| Rate limit | 20/min | 60/min | 120/min | 200/min | +| Providers | 1 | unlimited | unlimited | unlimited | +| Relational memory | no | yes | yes | yes | +| Proactive mining | no | no | yes | yes | + +`tier_manager.get_tier()` falls back to `'power'` in dev (`settings.ENV == 'dev'`) when no subscription found, else `'free'`. Enforced in `app/api/middleware/rate_limit.py` (sliding window) and `tier_manager.check_feature()` calls scattered through agent + memory paths. --- ## Cross-Project Integration -Electron app and FastAPI backend communicate via **WebSocket** (`/chat/stream`): +Electron app and FastAPI backend communicate via **WebSocket** (`/api/v1/device`): 1. Electron connects with `?token=` query param -2. Client sends `ChatRequest` JSON frame -3. Server streams text chunks, then final frame: `{"done": true, "response": "...", "actions": []}` -4. Server sends `tool_call` frames → Electron executes against local SQLite → returns `tool_result` -5. Server pings every 30 seconds to keep alive +2. Client sends typed request frames (home / floating / brief / journey_start / journey_message) +3. Server streams v3 typed frames (text deltas, tool_call, run_complete, error) +4. Tool call frames → Electron `drizzle-executor` runs against local SQLite → returns `tool_result` over same socket +5. Heartbeat loop keeps connection alive; backend marks runs disconnected on drop -Electron also has **fully local AI path** (LangGraph orchestrator in main process) that doesn't require backend — primary path for desktop use. +There is no fully-local AI fallback — the Electron orchestrator is a thin delegation shell that requires connectivity + auth. If offline or logged out, `checkConnectivity()` short-circuits with a user-facing error. --- ## MCP Servers - **Langfuse Docs** (`https://langfuse.com/api/mcp`) — workspace-level, prompt management docs -- **shadcn** (`npx shadcn@latest mcp`) — configured in `adiuvAI/` for UI component generation \ No newline at end of file +- **shadcn** (`npx shadcn@latest mcp`) — configured in `adiuvAI/` for UI component generation diff --git a/adiuvAI b/adiuvAI index df8cbb5..a5a6e25 160000 --- a/adiuvAI +++ b/adiuvAI @@ -1 +1 @@ -Subproject commit df8cbb5c35464a02ed9d84f7c3b888002ca13843 +Subproject commit a5a6e25a8980c94fab16a3931b5a679fd9b32d9d