update skill config
This commit is contained in:
@@ -1,22 +1,22 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
Guide Claude Code when work in repo.
|
||||
|
||||
## Keeping This File Up to Date
|
||||
|
||||
Update this file whenever a lesson is learned during development. Specifically, update CLAUDE.md when:
|
||||
Update when lesson learned. Update when:
|
||||
|
||||
- A non-obvious architectural decision is made or discovered
|
||||
- A gotcha, footgun, or surprising behavior is encountered (and the fix/workaround)
|
||||
- A new command, workflow, or tool is added to the project
|
||||
- A convention is established that isn't obvious from reading the code
|
||||
- An integration detail is clarified (e.g., how the IPC protocol actually behaves, edge cases in the agent tool call cycle)
|
||||
- Non-obvious arch decision made or found
|
||||
- Gotcha, footgun, surprising behavior hit (+ fix/workaround)
|
||||
- New command, workflow, tool added
|
||||
- Convention set that not obvious from code
|
||||
- Integration detail clarified (IPC protocol behavior, agent tool call edge cases)
|
||||
|
||||
Do **not** add things already derivable from reading the code, generic best practices, or ephemeral task notes — only durable, reusable knowledge.
|
||||
Do **not** add derivable-from-code things, generic best practices, or ephemeral task notes — durable knowledge only.
|
||||
|
||||
## Repository Layout
|
||||
|
||||
This is a **monorepo with git submodules**. Each submodule is an independent repo with its own `.claude/CLAUDE.md` for detailed guidance.
|
||||
**Monorepo with git submodules.** Each submodule independent repo with own `.claude/CLAUDE.md`.
|
||||
|
||||
| Directory | What | Submodule |
|
||||
|-----------|------|-----------|
|
||||
@@ -25,13 +25,13 @@ This is a **monorepo with git submodules**. Each submodule is an independent rep
|
||||
| **`website/`** | Landing page (single `index.html`) | `git.muticolturano.com/adiuvAI/website` |
|
||||
| **`docs/`** | Planning docs & working memory (not a submodule) | -- |
|
||||
|
||||
After cloning, run `git submodule update --init --recursive` to populate submodule contents.
|
||||
After clone, run `git submodule update --init --recursive` to populate submodules.
|
||||
|
||||
---
|
||||
|
||||
## adiuvAI (Electron App)
|
||||
|
||||
> **Detailed docs**: `adiuvAI/.claude/CLAUDE.md` covers commands, architecture, AI subsystem, design context, and conventions in depth.
|
||||
> **Detailed docs**: `adiuvAI/.claude/CLAUDE.md` — commands, architecture, AI subsystem, design context, conventions.
|
||||
|
||||
### Commands
|
||||
|
||||
@@ -62,12 +62,12 @@ Main Process (Node.js)
|
||||
└── LangGraph orchestrator (3 specialist agents, pluggable LLM providers)
|
||||
```
|
||||
|
||||
**This is a local-first app.** All user data (tasks, notes, projects) lives in local SQLite. The AI system (LangGraph + LangChain) runs entirely in the Electron main process with pluggable providers (OpenAI, Anthropic, GitHub Copilot).
|
||||
**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).
|
||||
|
||||
**IPC channels**:
|
||||
- `'trpc'` — bidirectional tRPC request/response (all CRUD)
|
||||
- `'ai:stream'` — one-way token streaming from main → renderer
|
||||
- `'ai:action'` — AI side-effects (e.g., task auto-created by agent)
|
||||
- `'ai:stream'` — one-way token streaming main → renderer
|
||||
- `'ai:action'` — AI side-effects (e.g. agent auto-creates task)
|
||||
|
||||
**Key source paths**:
|
||||
- `src/main/ipc.ts` — Custom tRPC↔IPC bridge
|
||||
@@ -80,46 +80,54 @@ Main Process (Node.js)
|
||||
- `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 in query results using user's FormatPrefs
|
||||
- `src/main/api/format-row.ts` — Formats timestamp columns using user's FormatPrefs
|
||||
|
||||
**Non-obvious details**:
|
||||
- `electron-trpc` is NOT used — custom IPC bridge in `ipc.ts` + `ipcLink.ts` because electron-trpc bundles tRPC v10 internals
|
||||
- `electron-trpc` NOT used — custom IPC bridge in `ipc.ts` + `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 and LanceDB)
|
||||
- DB has no foreign key constraints — cascade deletes are implemented in tRPC procedures
|
||||
- Timestamps are milliseconds (JavaScript `Date.getTime()`), not ISO strings
|
||||
- DB has no foreign key constraints — cascade deletes in tRPC procedures
|
||||
- Timestamps are milliseconds (`Date.getTime()`), not ISO strings
|
||||
- Notes auto-embed to LanceDB on create/update (fire-and-forget, errors swallowed)
|
||||
|
||||
**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`.
|
||||
- **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.
|
||||
|
||||
**Onboarding Wizard**:
|
||||
- First-run wizard collects 5 fields: `job_role`, `industry`, `primary_use_case`, `tone_preference`, `language`. Plus `user_name` derived from profile `name`+`surname`.
|
||||
- First-run wizard collects 5 fields: `job_role`, `industry`, `primary_use_case`, `tone_preference`, `language`. Plus `user_name` from `name`+`surname`.
|
||||
- All fields stored as encrypted core memory (backend `MemoryMiddleware`), not local electron-store.
|
||||
- `onboarding_completed_at` on the `users` table (nullable TIMESTAMPTZ) gates the flow — `null` = show wizard, non-null = skip.
|
||||
- `AppShell.tsx` gates: if `profile.onboardingCompletedAt == null` → render `<OnboardingFlow>` instead of the app.
|
||||
- `onboarding_completed_at` on `users` table (nullable TIMESTAMPTZ) gates flow — `null` = show wizard, non-null = skip.
|
||||
- `AppShell.tsx` gates: if `profile.onboardingCompletedAt == null` → render `<OnboardingFlow>` 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) are stored in electron-store (`FormatPrefs`), not core memory — they're device-specific.
|
||||
- `drizzle-executor.ts` wraps all query results through `formatRow()`/`formatRows()` using the user's FormatPrefs.
|
||||
- Settings > Profile section allows post-onboarding editing of all fields + format prefs.
|
||||
- **Gotcha — shadcn Button `outline` variant in dark mode**: The variant defines `dark:bg-input/30 dark:border-input dark:hover:bg-input/50` which overrides any custom `className` background. Fix: switch between `variant="default"` and `variant="outline"` instead of adding 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". This must be done in both the main process (`locale-defaults.ts`) and renderer (`OnboardingFlow.tsx`).
|
||||
- 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.
|
||||
- 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 — 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).
|
||||
- Config in `src/renderer/i18n.ts`. 5 languages: EN, IT, ES, FR, DE. `SUPPORTED_LANGUAGES` array exported for UI selectors.
|
||||
- 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`). Before adding a new key, check if `common.*` already has it.
|
||||
- **`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`).
|
||||
- `LanguageSync` component in `src/renderer/index.tsx` reads persisted `uiLanguage` from electron-store via tRPC on startup and syncs to i18next.
|
||||
- Language selector lives in `GeneralSection.tsx` (Settings > General). On change it: (1) calls `i18n.changeLanguage()`, (2) persists to electron-store via `setUiLanguage` mutation, (3) writes to backend core memory so AI responds in the same language.
|
||||
- `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 that need translation use `labelKey` pattern (not `label`): store a translation key, call `t(labelKey)` at render time. Used in `NAV_ITEMS`, `COLUMNS`, `SECTIONS`, `SUGGESTION_CHIPS`.
|
||||
- When adding new translated text: add the key to **all 5** JSON files. Keep `common.*` keys consistent across all languages.
|
||||
- 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`.
|
||||
- When adding new translated text: add key to **all 5** JSON files. Keep `common.*` consistent across all languages.
|
||||
|
||||
**Google OAuth (adiuvAI side)**:
|
||||
- `adiuvai://` is NOT accepted by Google as a redirect URI — Google only accepts `http://localhost` or `https://`. The API backend exposes `GET /auth/oauth/google/web-callback` which receives the Google redirect and immediately bounces to `adiuvai://oauth/callback?...`. The redirect URI registered in Google Cloud Console points to the backend, not the Electron app.
|
||||
- `app.requestSingleInstanceLock()` is required for the `second-instance` event to fire on Windows/Linux. If it returns `false`, call `app.quit()` immediately (another instance is already running).
|
||||
- In dev (`process.defaultApp === true`), `setAsDefaultProtocolClient('adiuvai')` must include `[path.resolve(process.argv[1])]` as the third argument so the OS protocol registration includes the entry script.
|
||||
- `loginWithOAuth` uses `fetch()` directly (not `this.get()`) because the authorize endpoint is public — `get()` throws when not authenticated.
|
||||
- The backup key in `backup-key.ts` is stored in `encryptedTokens` under the key `backup_key`, reusing `getToken/setToken` from `token.ts`. It is device-bound and never password-derived, so social-login users can use backup features without issue.
|
||||
- `adiuvai://` NOT accepted by Google as redirect URI — Google only accepts `http://localhost` or `https://`. API backend exposes `GET /auth/oauth/google/web-callback` which receives Google redirect and bounces to `adiuvai://oauth/callback?...`. Redirect URI in Google Cloud Console points to backend, not Electron app.
|
||||
- `app.requestSingleInstanceLock()` required for `second-instance` event on Windows/Linux. If returns `false`, call `app.quit()` immediately.
|
||||
- In dev (`process.defaultApp === true`), `setAsDefaultProtocolClient('adiuvai')` must include `[path.resolve(process.argv[1])]` as third arg so OS protocol registration includes entry script.
|
||||
- `loginWithOAuth` uses `fetch()` directly (not `this.get()`) — authorize endpoint is public, `get()` throws when not authenticated.
|
||||
- Backup key in `backup-key.ts` stored in `encryptedTokens` under key `backup_key`, reusing `getToken/setToken` from `token.ts`. Device-bound, never password-derived — social-login users can use backup features.
|
||||
|
||||
---
|
||||
|
||||
@@ -185,11 +193,11 @@ FastAPI app (app/main.py)
|
||||
└── Marketplace (app/marketplace/) — plugin catalog, review, revenue sharing
|
||||
```
|
||||
|
||||
**LLM routing**: `gpt-4o-mini` classifies intent → routes to domain agent → agent uses `gpt-4o` with tools → tool calls describe client-side operations (JSON) → Electron executes locally and returns results.
|
||||
**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.
|
||||
|
||||
**Zero-trust data model**: The backend never stores or decrypts user content. PostgreSQL holds only auth, billing, plugin metadata, and storage record pointers. All user data is E2E-encrypted before leaving the Electron client.
|
||||
**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.
|
||||
|
||||
**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 aren't configured.
|
||||
**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.
|
||||
|
||||
**Database**: PostgreSQL with async SQLAlchemy 2.0 + asyncpg. 9 ORM models in `app/models.py`. Alembic migrations in `alembic/versions/`.
|
||||
|
||||
@@ -197,36 +205,36 @@ FastAPI app (app/main.py)
|
||||
|
||||
### 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 without re-login
|
||||
- **Refresh tokens hashed**: Plaintext returned to client, stored as SHA-256 in DB — server can never retrieve the plaintext (intentional)
|
||||
- **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)
|
||||
- **WebSocket auth via query param**: `?token=<jwt>` 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 — the Electron client executes against local SQLite
|
||||
- **Alembic async/sync split**: App uses `postgresql+asyncpg`, Alembic CLI needs `postgresql+psycopg2` — `env.py` handles the URL conversion
|
||||
- **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
|
||||
- **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 — a known trade-off
|
||||
- **Vector search on encrypted data is not semantic**: Backend derives deterministic 32-dim floats from blob SHA-256 for storage/search — known trade-off
|
||||
|
||||
**Onboarding (API side)**:
|
||||
- `PUT /auth/me/memory` — updates core memory k/v pairs and optionally marks onboarding complete (`mark_onboarded: true` sets `users.onboarding_completed_at`).
|
||||
- `POST /auth/me/onboarding/reset` — nullifies `onboarding_completed_at` so the wizard re-runs.
|
||||
- `PUT /auth/me/memory` — updates core memory k/v pairs, optionally marks onboarding complete (`mark_onboarded: true` sets `users.onboarding_completed_at`).
|
||||
- `POST /auth/me/onboarding/reset` — nullifies `onboarding_completed_at` so wizard re-runs.
|
||||
- `POST /auth/onboarding/normalize` — LLM-normalizes free-text onboarding inputs via `gpt-4o-mini`; returns inputs unchanged on error.
|
||||
- `get_current_user()` in `auth.py` middleware now decrypts core memory blocks and includes them in `UserProfile.memory` dict.
|
||||
- `users.onboarding_completed_at` is a nullable TIMESTAMPTZ column — returned as epoch ms (int) in UserProfile schema.
|
||||
- `get_current_user()` in `auth.py` middleware decrypts core memory blocks, includes in `UserProfile.memory` dict.
|
||||
- `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 the user's `language` from `MemoryCore` and appends a system prompt directive ("Always respond in {language}") to all 4 `run_*` functions.
|
||||
- The Electron client writes the user's chosen language to backend core memory on language change, so the API picks it up on the next agent call.
|
||||
- `_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.
|
||||
- Electron client writes chosen language to backend core memory on change — API picks up on next agent call.
|
||||
|
||||
**Google OAuth (api side)**:
|
||||
- OAuth routes live in `app/api/routes/auth.py`: `GET /auth/oauth/{provider}/authorize`, `POST /auth/oauth/{provider}/callback`, `GET /auth/oauth/{provider}/web-callback` (bounces to deep link, excluded from OpenAPI schema).
|
||||
- Provider abstraction in `app/auth/oauth_providers.py` — `GoogleOAuthProvider` uses `httpx` directly (no `authlib`). PKCE S256 is implemented manually via `generate_pkce_pair()`.
|
||||
- `_pending_states` dict in `routes/auth.py` is **in-memory** — works for single-process dev but does not survive restarts and does not scale to multiple workers. Replace with Redis in production.
|
||||
- `users.password_hash` is **nullable** — social-only users have `password_hash=None`. `await db.flush()` is required before creating a linked `OAuthAccount` to populate `new_user.id` before commit.
|
||||
- `OAUTH_REDIRECT_URI` must point to the **API backend** (e.g. `https://api.adiuvai.com/...`), not the website domain. `adiuvai.com` is a static site with no server-side routing.
|
||||
- **Unverified email + existing account = 409**: if `email_verified=False` and the email is already registered, the callback returns 409. Without this guard, branch 3 would attempt to INSERT a duplicate email and crash with a DB constraint violation (500).
|
||||
- **Testing OAuth routes**: mock `GoogleOAuthProvider.exchange_code` and `get_userinfo` with `patch.object(..., new=AsyncMock(...))` — works because FastAPI instantiates a new provider per request. Use `monkeypatch.setattr(settings, "GOOGLE_AUTH_CLIENT_ID", ...)` to simulate configured credentials without restarting the app.
|
||||
- OAuth routes in `app/api/routes/auth.py`: `GET /auth/oauth/{provider}/authorize`, `POST /auth/oauth/{provider}/callback`, `GET /auth/oauth/{provider}/web-callback` (bounces to deep link, excluded from OpenAPI schema).
|
||||
- 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.
|
||||
- **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
|
||||
|
||||
@@ -243,19 +251,19 @@ Enforced in `app/api/middleware/rate_limit.py` (sliding window) and `app/billing
|
||||
|
||||
## Cross-Project Integration
|
||||
|
||||
The Electron app and FastAPI backend communicate via **WebSocket** (`/chat/stream`):
|
||||
Electron app and FastAPI backend communicate via **WebSocket** (`/chat/stream`):
|
||||
|
||||
1. Electron connects with `?token=<jwt>` query param
|
||||
2. Client sends `ChatRequest` JSON frame
|
||||
3. Server streams text chunks, then a final frame: `{"done": true, "response": "...", "actions": []}`
|
||||
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 connection alive
|
||||
5. Server pings every 30 seconds to keep alive
|
||||
|
||||
The Electron app also has a **fully local AI path** (LangGraph orchestrator in main process) that doesn't require the backend — this is the primary path for desktop use.
|
||||
Electron also has **fully local AI path** (LangGraph orchestrator in main process) that doesn't require backend — primary path for desktop use.
|
||||
|
||||
---
|
||||
|
||||
## MCP Servers
|
||||
|
||||
- **Langfuse Docs** (`https://langfuse.com/api/mcp`) — configured at workspace level for prompt management documentation
|
||||
- **shadcn** (`npx shadcn@latest mcp`) — configured in `adiuvAI/` for UI component generation
|
||||
- **Langfuse Docs** (`https://langfuse.com/api/mcp`) — workspace-level, prompt management docs
|
||||
- **shadcn** (`npx shadcn@latest mcp`) — configured in `adiuvAI/` for UI component generation
|
||||
Reference in New Issue
Block a user