Memory tools degraded silently and misleadingly when the mandatory
`openjarvis_rust` extension was absent from the *serving* venv:
- `POST /v1/memory/store` returned HTTP 200 `{"status":"stored","note":
"no backend available"}` and stored nothing (silent data loss).
- `POST /v1/memory/index` returned a generic "No memory backend available",
and the desktop frontend discarded the server `detail` and threw a blanket
"Failed to index path", blaming the path instead of the real cause.
- `GET /v1/memory/config` reported `backend_type: sqlite` even though no
backend could be constructed.
Root cause: `SQLiteMemory.__init__` calls `get_rust_module()` (which raises
ImportError by design — the Rust ext is mandatory, no Python fallback), and
`_get_memory_backend` swallowed that ImportError and returned `None`,
conflating "native extension missing" (a hard install error) with "memory
intentionally disabled" (benign). A chunking floor also silently dropped whole
short documents, and the installer never verified the extension imported from
the serving venv before writing its success marker.
Fix (no fake Python fallback — the Rust ext stays mandatory by design):
- Add `MemoryBackendUnavailable` + `RUST_MISSING_HINT` in tools/storage/_stubs.
`SQLiteMemory.__init__` translates the bridge ImportError into this clear,
actionable error ("run `uv run maturin develop ...`").
- `_get_memory_backend` distinguishes the two cases: a missing native ext
raises HTTP 503 with the actionable hint; a benign unconfigured backend
still returns `None` (graceful path preserved for search/stats).
- `/store` now returns 503 instead of a 200 silent no-op.
- `/config` reports `available: false` + `detail` instead of falsely claiming
a healthy `backend_type`.
- `/index` adds a `note` when `chunks_indexed == 0` so "indexed" never
silently means "stored nothing".
- chunk_text no longer drops an entire short document below `min_chunk_size`
(the floor only discards tiny *trailing* fragments now).
- Frontend `storeMemory`/`indexMemoryPath` surface the server `detail` instead
of blanket strings; `MemoryConfig` gains optional `available`/`detail`.
(Left the pre-existing `backend` vs `backend_type` mismatch untouched.)
- build-extension.sh verifies `import openjarvis_rust` succeeds in the serving
venv before writing the `extension-built` marker.
Regression tests: tests/server/test_api_routes.py::TestMemoryRustMissing mocks
`get_rust_module` to raise ImportError and asserts /store (503, not 200 no-op),
/index (actionable detail, not "Failed to index path"), and /config
(available:false) all surface the clear error; tests/memory/test_chunking.py
asserts short-only docs are kept while tiny trailing fragments are still
filtered.
Fixes#502
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When `jarvis serve` runs with an API key configured, AuthMiddleware 401s
every /v1 and /api request that lacks a Bearer token. The frontend never
sent one, so telemetry, managed-agents, savings, etc. all failed (#266).
- Add getApiKey() (reads settings.apiKey, with optional
VITE_OPENJARVIS_API_KEY build-time override) and authHeaders() to
api.ts, plus an apiFetch() wrapper that prepends getBase() and injects
the Bearer header on every local-server call. Route all /v1 + /api
fetches through it so none can omit auth.
- Add `apiKey` to the Settings model (store.ts) and a password field in
Settings → Connection so users can enter it.
Keyless local servers are unaffected: with no key, no Authorization
header is sent (byte-for-byte unchanged). The Supabase savings path keeps
its own anon key — not conflated with the local key.
Bootstraps vitest (no prior frontend test runner) + a `test` script, and
adds api.auth.test.ts covering getApiKey/authHeaders. Verified: tsc
--noEmit clean, vitest 6/6, vite build succeeds.
Deferred (not in scope): WebSocket auth (browsers can't set WS headers)
and Tauri auto-injecting a generated key.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor: merge desktop/ into frontend/, eliminate duplicate Tauri scaffolding
The project had two overlapping directories: desktop/ (Tauri Rust backend +
stale React components) and frontend/ (real React app + dead Tauri stub).
This consolidates everything under frontend/:
- Move desktop/src-tauri/ → frontend/src-tauri/ (the real 1,720-line Rust
backend with Ollama sidecar, backend lifecycle, cloud keys, overlay, etc.)
- Preserve 9 old desktop React components in frontend/src/components/Desktop/
(excluded from TS build — APIs have drifted, kept for future integration)
- Delete the old frontend/src-tauri/ stub (246 lines, never compiled)
- Delete desktop/ entirely
- Fix tauri.conf.json frontendDist path (../../frontend/dist → ../dist)
- Update CI workflow, bump script, .gitignore, and docs paths
- Rename setup/ → Setup/ for consistent PascalCase component directories
* feat: add memory UI, settings, and fix memory API routes
- Add Memory tab to Data Sources page with stats, search, index path,
and manual store functionality
- Add Memory section to Settings page with backend picker, context
injection toggle, and parameter sliders (top_k, min_score, max_tokens)
- Add memory API functions to frontend (getMemoryStats, searchMemory,
storeMemory, indexMemoryPath, getMemoryConfig)
- Fix backend /v1/memory/* routes to use app-level memory backend
instead of creating fresh SQLiteMemory instances per request
- Add GET /v1/memory/config and POST /v1/memory/index endpoints
- Gracefully handle missing Rust backend (return defaults instead of 500)
- Fix nested scroll in Agents Interact tab (use viewport-relative height)
* fix: memory API routes, dialog plugin, and UI polish
- Fix memory API: use backend.retrieve() not .search(), .count() not
.stats() to match actual SQLiteMemory interface
- Handle None backend gracefully in index endpoint (503 instead of crash)
- Expand ~ in index path (expanduser + resolve)
- Install @tauri-apps/plugin-dialog for native folder picker in Tauri
- Browse button only shows in Tauri (browser can't get absolute paths)
- Redesign Memory tab: proper cards, color-coded search scores, two-column
layout for index/store, loading spinners, accent gradient on stats card
---------
Co-authored-by: Jon Saad-Falcon <41205309+jonsaadfalcon@users.noreply.github.com>
* feat: add Apple Contacts connector (macOS AddressBook)
Reads directly from ~/Library/Application Support/AddressBook/AddressBook-v22.abcddb
in read-only mode. Extracts names, phone numbers, emails, postal addresses, URLs,
social profiles, and notes for each contact. Requires Full Disk Access like
iMessage and Apple Notes connectors.
- New connector: src/openjarvis/connectors/apple_contacts.py
- Registered in connectors/__init__.py
- Added frontend catalog entry (PIM category) and icon mapping
- MCP tools: contacts_search, contacts_get_contact
* test: add comprehensive tests for Apple Contacts connector
15 tests covering: is_connected (exists/missing), sync yields all real
contacts (skips system rows), extracts all field types (phone, email,
address, URL, social, notes), cleans Apple label markup, org-only
contacts, minimal contacts, since filter, sync_status tracking,
structured metadata, disconnect, mcp_tools, registry, empty DB,
missing DB. Uses fake SQLite database — no real Contacts DB needed.
* fix: scan iCloud/Exchange source databases for all contacts
The main AddressBook database only contains locally-created contacts.
Synced contacts (iCloud, Exchange, etc.) live under
Sources/<UUID>/AddressBook-v22.abcddb. Now scans all source databases
and deduplicates by ZUNIQUEID across sources.
Adds tests for multi-source scanning and cross-source deduplication.
The project had two overlapping directories: desktop/ (Tauri Rust backend +
stale React components) and frontend/ (real React app + dead Tauri stub).
This consolidates everything under frontend/:
- Move desktop/src-tauri/ → frontend/src-tauri/ (the real 1,720-line Rust
backend with Ollama sidecar, backend lifecycle, cloud keys, overlay, etc.)
- Preserve 9 old desktop React components in frontend/src/components/Desktop/
(excluded from TS build — APIs have drifted, kept for future integration)
- Delete the old frontend/src-tauri/ stub (246 lines, never compiled)
- Delete desktop/ entirely
- Fix tauri.conf.json frontendDist path (../../frontend/dist → ../dist)
- Update CI workflow, bump script, .gitignore, and docs paths
- Rename setup/ → Setup/ for consistent PascalCase component directories
Server returns template IDs with underscores (code_reviewer, research_monitor)
but TEMPLATE_INSTRUCTIONS used hyphens. Added both formats + prompts for
personal_deep_research and inbox_triager templates.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds a new Upload / Paste data source that lets users paste text or upload
documents (.txt, .md, .pdf, .docx, .csv) directly into the knowledge base.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Clarify that Gmail uses App Passwords (not OAuth login popup), emphasize
2-Step Verification prerequisite, and add a collapsible troubleshooting
section for common issues. Also improve the error message when Gmail
auth fails to specifically mention App Password requirements.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move the Interact tab to the first position in agent detail view and
default to it when opening an agent. Add "(optional)" hint to Advanced
Settings and add (?) tooltips to Memory Extraction, Observation
Compression, Retrieval Strategy, and Task Decomposition labels.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add TEMPLATE_INSTRUCTIONS map with sensible defaults for daily-briefing,
research-monitor, code-reviewer, and meeting-prep templates so users get
a starting prompt when selecting a template. Show a warning hint when the
instruction contains [bracketed placeholders] that need to be replaced.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the manual/interval/cron schedule picker with friendlier presets
(Daily, Weekly, Every N hours, Custom cron) that generate the correct
cron expressions or interval values behind the scenes. Update
formatSchedule() to render human-readable labels like "Daily at 9:00 AM"
and "Weekly on Mon, Wed, Fri at 9:00 AM".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Frontend: add progress stages (Connecting → Authenticating → Connected →
Syncing) with spinner and progress bar to the data source connect flow.
Previously sources would silently stay "Not connected" after setup.
Show error message on failure instead of swallowing exceptions.
- Obsidian connector: use timezone-aware datetime (tz=timezone.utc) in
fromtimestamp() to fix "can't compare offset-naive and offset-aware
datetimes" crash during incremental sync.
- Google Calendar connector: catch HTTPStatusError when listing events
for individual calendars (e.g. US Holidays returning 404) so one
inaccessible calendar doesn't crash the entire sync.
- Agent SSE timeout: increase progress queue timeout from 120s to 600s
so complex multi-hop deep research queries aren't killed mid-execution
on slower local models.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Skip SetupWizard on launch, go straight to Chat page
- Add Data Sources page with sidebar nav (separate tabs for data sources + messaging channels)
- Add "Connect your data" banner + quick-action buttons on Chat empty state
- Add hint on deep research agent pointing to Data Sources + Messaging tabs
- Consistent naming: "Data Sources" and "Messaging Channels" everywhere
- Fix Apple Notes / iMessage setup (remove broken system prefs link)
- Fix Slack data source: auto-join public channels, rate limit retry, is_member filter
- Add channels:join scope to all Slack manifests + docs
- Fix Gmail: use gmail_imap connector (IMAP + app password), increase limit to 5000
- Fix sync endpoint: run in background thread, return immediately
- Show sync progress with progress bar, error messages, Sync Now / Re-sync / Retry buttons
- Add triggerSync() API + SyncStatusDisplay component
- Rewrite all connector setup instructions with precise click-by-click steps
- Notion: share all pages at once via top-level page sharing
- Obsidian: show how to find vault path via Obsidian UI or Finder
- SendBlue: add link to API Credentials page, add ngrok webhook step
- Slack messaging: add Copy button for JSON manifest
- Add OpenJarvis Slack icon asset
- Shorten deep research template description
- Fix desktop app port (8222 → 8000 to match server default)
- Disable auto-updater for local dev builds
- Register Slack, Outlook, GCalendar connectors in __init__.py
- Auto-create default agent when setting up messaging channels
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Removed WhatsApp (Baileys blocked by WhatsApp servers) and Twilio SMS
(SendBlue handles SMS as fallback automatically). Updated Slack setup
instructions with the App Manifest JSON for one-step configuration.
Three channels: iMessage + SMS (SendBlue), Slack (Socket Mode)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Register sendblue in channels/__init__.py so ChannelRegistry works
- Auto-restore SendBlue bindings on server startup from database,
re-creating ChannelBridge + DeepResearchAgent so webhooks survive
server restarts
- Add sendblue to CLI channel_cmd.py (_get_channel, help text) and
SystemBuilder._resolve_channel() for config.toml support
- Health check endpoint GET /v1/channels/sendblue/health returns
channel_connected, bridge_wired, ready status
- Frontend: SendBlueWizard checks health on mount, shows
"Disconnected" badge with "Reconnect" button when bridge is dead
- Docs: CLI usage, server restart behavior, ngrok re-registration,
troubleshooting table, health check endpoint
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
App Token (xapp-) is required for Socket Mode DMs, not optional.
Removed xoxe- from CLI prompt since session tokens don't work.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When /api/lines returns no numbers (free/shared line tier), the wizard
now shows a manual input with instructions to copy the phone number
from the "Send from" field in the SendBlue dashboard, instead of
showing an error.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backend:
- POST /v1/channels/sendblue/verify — validates API keys, auto-fetches
assigned phone numbers from SendBlue GET /api/lines
- POST /v1/channels/sendblue/register-webhook — auto-registers the
/webhooks/sendblue callback URL with SendBlue
- POST /v1/channels/sendblue/test — sends a test iMessage to verify setup
Frontend:
- SendBlueWizard component with multi-step guided flow:
Step 1: "Open SendBlue signup" button (opens in new tab)
Step 2: Paste API Key + Secret → "Verify & Find Number" auto-fetches
Step 3: Shows discovered phone number → "Activate Phone Number"
Step 4: Success state with "Send Test" button
- Active state shows agent's number + test message sender
- Generic channels (Slack, WhatsApp, SMS, local iMessage) listed below
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When user connects Slack in the Messaging tab, the backend now creates
a SlackChannel with Socket Mode, registers a handler that routes incoming
DMs to DeepResearchAgent, and starts listening. Unbinding disconnects
the Socket Mode client.
Requires slack-sdk installed and both bot_token (xoxb-) + app_token (xapp-).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The 2-second polling interval was replacing local messages (with _usage,
_telemetry) with server messages (without those fields). Now stores
metadata in a ref and merges it back when polling refreshes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The progress indicator now only shows when waitingForResponse or sending
is true, preventing it from persisting after the response arrives due to
stale isAgentWorking/hasPending checks. Agent response bubbles now display
elapsed time, tool call count, and a copy button in an XRay-style footer.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace "Run Now" button with "Chat ready" hint when on the Interact
tab so users know they can just type a message without pressing Run.
- Add learning log entries in generate_deep_research() for query start,
tool calls, tool results, query completion, and errors so interactive
queries are visible in the Logs tab.
- Rewrite LogsTab to poll every 5 seconds, merge execution traces with
learning log entries into a unified timeline sorted by timestamp.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Refactor sendAgentMessage to accept onProgress/onContentDelta/onDone
callbacks so the InteractTab can show content as it arrives word by
word, display tool-call progress labels (e.g. "Querying data with
SQL"), and render an elapsed-time footer. The backend now streams
tool_progress SSE events from DeepResearchAgent before the final
content, matching the Chat tab's polished streaming UX.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Added waitingForResponse state that stays true during the entire stream
consumption. Shows a pulsing indicator with descriptive text so the user
knows the agent is working (searching, analyzing, writing report).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. InteractTab now displays the streamed agent response as a bubble
immediately after it finishes (was being discarded)
2. Connecting a source auto-triggers background ingest into KnowledgeStore
so Google Drive data appears with chunk counts immediately
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Google OAuth flow runs in a background thread. The frontend now polls
every 3s after connecting to detect when the token arrives, and auto-refreshes
every 10s to catch background completions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>