Guard BackupService and vault unlock for client mode
Details
- BackupService: skip scheduled backups in client mode (server handles
its own backups). Previously, SettingsViewModel's DI resolution
triggered BackupService constructor which immediately started the
backup timer — even in client mode.
- PluginRegistry.EnsurePluginVaultsUnlocked: skip in client mode since
vault state is managed by the server. The NativeLib vault calls would
fail (or succeed silently via catch) since the native runtime isn't
initialized in client mode.
Register EmbeddingService and IEmbeddingService in Desktop DI
Details
EmbeddingService (ONNX embedding model) and its IEmbeddingService
interface were never registered in Desktop's DI container. The Server
project registered a no-op HeadlessEmbeddingService, but Desktop was
missing the mapping entirely. This caused RagSearchService resolution
to fail when CommandPaletteViewModel built AI commands during
MainWindowViewModel construction.
Pre-existing bug surfaced by client mode startup where the full
DI resolution chain is exercised earlier than in standalone mode.
Fix client mode startup: license check and error handling
Details
In client mode, ShowMainWindow crashed at LicenseExpirationService because
PrivStackService.GetLicenseStatus() requires the native runtime to be
initialized — which it isn't in client mode. The exception was silently
swallowed by the fire-and-forget task, leaving the app with no visible
window.
- Add license_status field to /api/v1/status endpoint response
- Parse and store server license status during TryEnterClientMode()
- Add CheckLicenseStatusFromServer() to LicenseExpirationService for
applying license status from a remote server string
- Use server-provided license status in client mode ShowMainWindow
- Add ContinueWith error handler on fire-and-forget EnterClientModeAsync
to log failures instead of silently swallowing them
Route entity type registration through SDK transport layer
Details
RegisterEntityType was called directly via NativeLib P/Invoke in both
PluginRegistry (Desktop) and HeadlessPluginRegistry (Server), bypassing
the ISdkTransport abstraction. In client mode, the native runtime isn't
initialized, causing all entity schema registrations to fail with error
code -4.
- Add RegisterEntityType to ISdkTransport interface
- Implement in FfiSdkTransport (delegates to NativeLib)
- Implement in HttpSdkTransport (POSTs to /api/v1/sdk/register-entity-type)
- Add public RegisterEntityType method on SdkHost
- Add /sdk/register-entity-type endpoint to LocalApiServer
- Update PluginRegistry to route through SdkHost instead of NativeLib
- Update HeadlessPluginRegistry with same fix, remove unused NativeLib import
Add SDK transport abstraction for Desktop client mode
Details
Extract FFI calls from SdkHost into ISdkTransport interface with two
implementations:
- FfiSdkTransport: wraps NativeLibrary P/Invoke calls with pointer
marshalling (standalone mode, existing behavior)
- HttpSdkTransport: proxies SDK calls over HTTP to a running headless
server (client mode, new)
When Desktop detects a running headless server at startup (probes
GET /api/v1/status), it switches to client mode: swaps the transport
from FFI to HTTP, skips DuckDB initialization and the unlock screen,
and routes all plugin data operations through the server. This solves
the DuckDB single-process limitation during development — server for
API testing, Desktop for UI verification, both against the same data.
Server-side: LocalApiServer gains SDK passthrough endpoints under
/api/v1/sdk/* (execute, search, vault, blob, db maintenance) so the
HttpSdkTransport has something to call. All endpoints sit behind the
existing API key authentication.
In client mode, server-managed background services (backup, file sync,
snapshot sync, reminders, RAG indexing, API server) are skipped since
the headless server handles them. UI services, plugin discovery, and
IPC still run locally.
SdkHost.SetTransport() follows the same post-build wiring pattern as
SetSyncOutbound() and SetVaultUnlockPrompt(). Pure refactor in
standalone mode — zero behavior change when no server is detected.
Get notified about new releases