Skip to content

Desktop migration: Rust backend → Python backend (#5302)#5374

Open
beastoin wants to merge 83 commits intomainfrom
collab/5302-integration
Open

Desktop migration: Rust backend → Python backend (#5302)#5374
beastoin wants to merge 83 commits intomainfrom
collab/5302-integration

Conversation

@beastoin
Copy link
Collaborator

@beastoin beastoin commented Mar 5, 2026

Closes #5302. Migrates desktop from Rust backend to Python backend. Desktop becomes thin client; Python backend = source of truth.

What changed

Backend (kai) — 10 new REST endpoints: OAuth auth (Google/Apple + callback), chat sessions/messages + generate-title, conversations count, focus sessions CRUD, staged tasks CRUD, screen activity sync, advice CRUD, assistant settings. All use existing Firebase auth middleware.

Agent VM endpoints (kai) — Fixed 2 gaps found via Codex audit:

  1. vm-ensure now creates new GCE VMs for first-time users (ported from Rust agent.rs): generates omi-agent-{uid[:12]} name, writes Firestore, spawns background GCE creation (e2-small, pd-ssd 50GB, startup script from GCS bucket).
  2. vm-status now returns all fields desktop needs (vm_name, ip, auth_token, zone, created_at, last_query_at) and triggers restart for stopped/terminated VMs (Rust parity).

Desktop Swift (ren) — APIClient path fixes + decoder hardening, AuthService pointed to Python OAuth, TasksStore migration cleanup, TranscriptionRetryService path updates.

Tests — 147 PR-specific tests (134 original + 13 new agent VM tests covering vm-ensure creation, idempotency, restart, vm-status full fields, restart-on-check, GCE failure graceful degradation, UID truncation).

Architecture

Before:  Desktop → Rust backend (local binary) → Firestore/Redis
After:   Desktop → Python backend (Cloud Run) → Firestore/Redis/LLM

Verification

Verifier Result Tests Notes
kelvin PASS 1026 passed, 13 pre-existing fails Combined with #5395 + #5413
noa PASS 134 PR-specific ALL PASS 0 CRITICAL, 2 WARNING (non-blocking)
noa (rebased) PASS 761 passed, 0 regressions SHA 78d15d27
kai (driver) PASS 381 passed locally 5 pre-existing failures only

Agent VM tests: 13/13 PASS — vm-ensure creates new VMs, idempotent provisioning, restart stopped VMs, error recovery; vm-status returns full fields, triggers restart (Rust parity), graceful GCE failure; UID truncation to 12 chars.

Driver verdict: PASS. All endpoints tested live against dev Firestore on Mac Mini. No regressions.

Security

  • auth.py uses verify_id_token() (cryptographic JWT verification), not base64 decode
  • sanitize_pii() on all email logging
  • Jinja2 templates use |tojson filter for JS context
  • GCE VM auth tokens use uuid4 (cryptographic randomness)

Infra Prerequisites

  • OAuth redirect URIs: Already registered in Google Cloud Console and Apple Developer Console (https://api.omi.me/v1/auth/callback/google and /apple) — same paths as existing Rust backend
  • Env vars on Cloud Run (already set from Rust backend): GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, APPLE_CLIENT_ID, APPLE_TEAM_ID, APPLE_KEY_ID, APPLE_PRIVATE_KEY, BASE_API_URL, FIREBASE_API_KEY
  • Agent VM env vars (optional, defaults to based-hardware): GCE_PROJECT_ID, GCE_SOURCE_IMAGE, AGENT_GCS_BUCKET
  • No new secrets, no new GCP services, no console registration needed

Deployment Steps

  1. Merge to main (no squash)
  2. Backend (hand to @mon):
    • gh workflow run gcp_backend.yml -f environment=prod -f branch=main (Cloud Run image)
    • gh workflow run gke_backend_listen.yml -f environment=prod -f branch=main (Helm rollout)
    • gh workflow run gke_pusher.yml -f environment=prod -f branch=main (pusher image)
  3. Desktop: auto-deploys via desktop_auto_release.yml → Codemagic
  4. Verify: new endpoints respond (/v4/desktop/chat, /v1/conversations/count, /v1/advice, /v1/auth/authorize, /v1/agent/vm-ensure, /v1/agent/vm-status), no new 5xx, desktop connects to Python backend
  5. Rollback: redeploy previous image tag; desktop ./scripts/rollback_release.sh <tag>

Merge order

This PR merges first → then #5395 → then #5413.


by AI for @beastoin

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 5, 2026

Greptile Summary

This PR migrates the Omi desktop backend from Rust to Python by adding new FastAPI endpoints and database helpers that parallel the existing Rust backend's capabilities. It introduces desktop-specific chat-session CRUD (/v2/chat-sessions), message persistence without triggering the LLM pipeline (/v2/messages/save), message rating, a from-segments conversation creation endpoint (/v1/conversations/from-segments), screen-activity sync with background Pinecone vector upsert (/v1/screen-activity/sync), and assistant-settings / AI-profile endpoints (/v1/users/assistant-settings, /v1/users/ai-profile). The Swift client is updated in lockstep to drop deprecated fields and point to the new endpoint paths. Test coverage is thorough across all four new feature areas.

Key observation:

  • ChatSessionResponse.app_id backward-compat gap — sessions created before this migration only have plugin_id in Firestore; the new response model exposes app_id but has no fallback validator, so legacy sessions will always return app_id: null. A simple @model_validator can fix this by falling back to plugin_id when app_id is missing.

Confidence Score: 4/5

  • PR is generally safe; the app_id backward-compatibility gap in ChatSessionResponse requires remediation to ensure legacy sessions expose correct app association.
  • Good test coverage and clean architecture throughout. The primary finding is a verified logic issue: ChatSessionResponse.app_id will silently return null for any pre-existing chat sessions that only have plugin_id in Firestore. This is not speculative — it's a data integrity issue that will affect any session created before this migration. The fix (a @model_validator fallback) is straightforward. No other blockers identified.
  • backend/routers/chat.py (ChatSessionResponse app_id mapping needs validator fallback)

Sequence Diagram

sequenceDiagram
    participant Desktop as Desktop App (Swift)
    participant API as FastAPI Backend (Python)
    participant FS as Firestore
    participant PC as Pinecone (background)

    Note over Desktop,PC: Chat Session Flow
    Desktop->>API: POST /v2/chat-sessions
    API->>FS: add_chat_session(uid, session_data)
    API-->>Desktop: ChatSessionResponse

    Desktop->>API: POST /v2/messages/save
    API->>FS: save_message(uid, message_data)
    API->>FS: add_message_to_chat_session (preview, count, updated_at)
    API-->>Desktop: SaveMessageResponse {id, created_at}

    Desktop->>API: PATCH /v2/messages/{id}/rating
    API->>FS: update_message_rating(uid, message_id, rating)
    API-->>Desktop: StatusResponse {status: ok}

    Desktop->>API: DELETE /v2/chat-sessions/{id}
    API->>FS: delete_chat_session_messages (batch delete)
    API->>FS: delete_chat_session
    API-->>Desktop: StatusResponse {status: ok}

    Note over Desktop,PC: Conversation from Segments Flow
    Desktop->>API: POST /v1/conversations/from-segments
    API->>API: validate segments, compute finished_at
    API->>API: process_conversation(uid, language, obj)
    API->>FS: store conversation
    API-->>Desktop: FromSegmentsResponse {id, status, discarded}

    Note over Desktop,PC: Screen Activity Sync Flow
    Desktop->>API: POST /v1/screen-activity/sync
    API->>FS: upsert_screen_activity (sync, truncates ocrText@1000)
    API-->>Desktop: ScreenActivitySyncResponse {synced, last_id}
    API-)PC: upsert_screen_activity_vectors (async daemon thread)
Loading

Last reviewed commit: 534a65f

Comment on lines +520 to +528
class ChatSessionResponse(BaseModel):
id: str
title: str
preview: Optional[str] = None
created_at: datetime
updated_at: datetime
app_id: Optional[str] = None
message_count: int = 0
starred: bool = False
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ChatSessionResponse maps app_id directly, but existing Firestore chat-session documents only store plugin_id (the legacy field name). When get_chat_session_by_id or get_chat_sessions returns a document that has plugin_id but no app_id, Pydantic silently sets app_id=None in the response — even though the session does belong to an app.

New sessions written by create_chat_session (lines 578–579) correctly store both app_id and plugin_id, so they're fine. But any session created before this migration (or by the mobile app) will always report app_id=null to the desktop client.

A @model_validator can fix this:

Suggested change
class ChatSessionResponse(BaseModel):
id: str
title: str
preview: Optional[str] = None
created_at: datetime
updated_at: datetime
app_id: Optional[str] = None
message_count: int = 0
starred: bool = False
from pydantic import BaseModel, model_validator
class ChatSessionResponse(BaseModel):
id: str
title: str
preview: Optional[str] = None
created_at: datetime
updated_at: datetime
app_id: Optional[str] = None
message_count: int = 0
starred: bool = False
@model_validator(mode='before')
@classmethod
def _coerce_plugin_id(cls, data):
if isinstance(data, dict) and data.get('app_id') is None:
data['app_id'] = data.get('plugin_id')
return data

This ensures legacy sessions correctly surface their app association to the desktop client.

@beastoin
Copy link
Collaborator Author

beastoin commented Mar 5, 2026

E2E Live Firestore Test — Day 2 Endpoints (21/21 PASS)

Local backend on port 8789 running trunk collab/5302-integration, authenticated via Firebase ID token against dev Firestore.

Staged Tasks (8/8)

✓ POST /v1/staged-tasks (create)
✓ POST /v1/staged-tasks (create 2nd)  
✓ POST /v1/staged-tasks (dedup returns existing, same id)
✓ GET /v1/staged-tasks (list)
✓ PATCH /v1/staged-tasks/batch-scores
✓ POST /v1/staged-tasks/promote → promoted=true, created action item
✓ DELETE /v1/staged-tasks/{id}
✓ DELETE /v1/staged-tasks/nonexistent (idempotent, 200)

Daily Scores (3/3)

✓ GET /v1/daily-score
✓ GET /v1/daily-score?date=2026-03-05
✓ GET /v1/scores (daily+weekly+overall)

Focus Sessions (5/5)

✓ POST /v1/focus-sessions (create focused)
✓ POST /v1/focus-sessions (create distracted, with duration)
✓ GET /v1/focus-sessions (list)
✓ GET /v1/focus-stats?date=2026-03-05
✓ DELETE /v1/focus-sessions/{id}

Advice (5/5)

✓ POST /v1/advice (create, category=health, confidence=0.85)
✓ GET /v1/advice (list)
✓ PATCH /v1/advice/{id} (mark read)
✓ POST /v1/advice/mark-all-read
✓ DELETE /v1/advice/{id}

All 21 endpoints hit real Firestore and return expected responses.

by AI for @beastoin

@beastoin
Copy link
Collaborator Author

beastoin commented Mar 5, 2026

E2E Live Test — Full Trunk (Day 1 + Day 2 + Auth)

Local backend (23/23 PASS)

Backend on port 8789 running trunk collab/5302-integration with all merged PRs (#5370 Day 1, #5375/#5376 Day 2, #5360 auth). Authenticated via Firebase ID token against dev Firestore.

✓ GET /v1/auth/authorize?provider=google (307 redirect — correct OAuth)
✓ POST /v1/staged-tasks (create)
✓ POST /v1/staged-tasks (create 2nd)
✓ POST /v1/staged-tasks (dedup returns same id)
✓ GET /v1/staged-tasks (list — 2 items)
✓ PATCH /v1/staged-tasks/batch-scores
✓ POST /v1/staged-tasks/promote (promoted=True)
✓ DELETE /v1/staged-tasks (exists)
✓ DELETE /v1/staged-tasks (idempotent, nonexistent)
✓ GET /v1/daily-score
✓ GET /v1/daily-score?date=2026-03-05
✓ GET /v1/scores (daily+weekly+overall)
✓ POST /v1/focus-sessions (focused)
✓ POST /v1/focus-sessions (distracted, with duration)
✓ GET /v1/focus-sessions (list)
✓ GET /v1/focus-stats (sessions=2, focused=1, distracted=1)
✓ DELETE /v1/focus-sessions
✓ POST /v1/advice (health, confidence=0.9)
✓ GET /v1/advice (list)
✓ GET /v1/advice?category=health
✓ PATCH /v1/advice (mark read)
✓ POST /v1/advice/mark-all-read
✓ DELETE /v1/advice

Mac Mini E2E (13/13 PASS)

Python backend tunneled to Mac Mini (beastoin-agents-f1-mac-mini) via SSH reverse tunnel.

✓ POST /v1/staged-tasks (200)
✓ GET /v1/staged-tasks (200)
✓ POST /v1/staged-tasks/promote (200)
✓ DELETE /v1/staged-tasks (idempotent) (200)
✓ GET /v1/daily-score (200)
✓ GET /v1/scores (200)
✓ POST /v1/focus-sessions (200)
✓ GET /v1/focus-sessions (200)
✓ GET /v1/focus-stats (200)
✓ POST /v1/advice (200)
✓ GET /v1/advice (200)
✓ POST /v1/advice/mark-all-read (200)
✓ GET /v1/auth/authorize (307)

All endpoints hit real Firestore. Integration PR ready for merge.

by AI for @beastoin

@beastoin
Copy link
Collaborator Author

beastoin commented Mar 5, 2026

Day 3 Integration Complete

Both sub-PRs merged into trunk:

Integration trunk status

  • 122 desktop-related tests passing
  • All sub-PRs reviewed and test-approved
  • PR body updated with complete Day 1-3 summary

Remaining before merge

  • Mac Mini live test (CP9) — OAuth sign-in, chat session flow, conversations count against Python backend
  • Manager merge approval

by AI for @beastoin

@beastoin
Copy link
Collaborator Author

beastoin commented Mar 5, 2026

E2E Live Test Evidence (CP9) — kai

Tested against local Python backend (localhost:8789) on collab/5302-integration branch with Firebase auth token.

A. New Desktop Endpoints (4/4 pass)

Endpoint Status Response
POST /v2/chat/generate-title 200 {"title":"Deploying to Production with CI/CD"}
GET /v1/conversations/count 200 {"count":0.0}
GET /v1/conversations/count?statuses=completed 200 {"count":0.0}
Validation: 11 statuses 400 "Too many status values (max 10)"
Validation: empty messages 400 "messages list cannot be empty"

B. Adapted/Path-Changed Endpoints (4/4 pass)

Old Path New Path Status
/v2/chat/initial-message POST /v2/initial-message 200
/v1/personas/check-username GET /v1/apps/check-username 200
/v2/agent/status GET /v1/agent/vm-status 200
/v2/agent/provision POST /v1/agent/vm-ensure 200

C. Desktop Chat Session Flow (7/7 pass)

  • Create session → 200 (got ID)
  • List sessions → 200
  • Get session → 200
  • Update title → 200 (title updated)
  • Save message (human) → 200
  • Save message (AI) → 200
  • Generate title → 200
  • Delete session → 200
  • Verify deleted → 404 (correct)

D. Mobile Regression Check (8/8 pass — no breakage)

Endpoint Status
GET /v1/conversations 200
POST /v1/conversations/from-segments 200
POST /v2/messages (streaming) 200 (SSE stream)
GET /v2/messages 200
GET /v1/apps 200
GET /v2/apps (public, no auth) 200
GET /v1/users/assistant-settings 200
GET /v1/users/ai-profile 200

Ren's Mac Mini Evidence (7/7 pass)

OAuth sign-in OK, 7 endpoints verified via curl, desktop app launched successfully.

All checkpoints passed (CP0-CP9). PR ready for merge.

by AI for @beastoin

@beastoin
Copy link
Collaborator Author

beastoin commented Mar 5, 2026

Mac Mini Live Test — Python Backend Only (CP9)

CONFIRMED: Desktop app runs fully on Python backend with zero Rust dependency.

Evidence

1. Authenticated app loading real data from Python backend:

mac-authenticated

App shows beastoin's conversations loaded from dev Firestore via Python backend. Sidebar navigation, tasks, conversations all functional.

2. Backend logs — authenticated API calls from Mac Mini (100.126.187.125):

GET /v3/memories?limit=500           → 200
GET /v1/agent/vm-status              → 200
GET /v1/action-items                 → 200
GET /v1/conversations?limit=10       → 200
POST /v1/staged-tasks/promote        → 200
POST /v1/agent/vm-ensure             → 200
GET /v2/messages?limit=50            → 200
GET /v2/desktop/appcast.xml          → 200

All for UID R2IxlZVs8sRU20j9jLNTBiiFAoO2 (beastoin), all 200 OK.

3. OAuth flow verified through Python backend:

GET /v1/auth/authorize → 307 Redirect (to Google)
GET /v1/auth/callback/google → 200 OK (token exchange successful)

4. No Rust backend running:

  • Mac Mini has NO Rust desktop backend process
  • App connects to Python backend at VPS port 8789 via Tailscale
  • All endpoints served by Python backend only

5. Setup:

  • Built from collab/5302-integration branch on Mac Mini
  • .env: OMI_API_URL=http://<vps-tailscale-ip>:8789
  • Auth via Firebase custom token (dev project) injected into UserDefaults
  • ATS exception added for HTTP dev testing

Summary

All Day 1-3 endpoints work. Desktop app is fully operational on Python backend. Rust backend can be safely decommissioned.

by AI for @beastoin

@beastoin
Copy link
Collaborator Author

beastoin commented Mar 9, 2026

Independent Verification — PR #5374

Verifier: kelvin
Branch: verify/combined-5374-5395-5413
Combined with: PRs #5395, #5413

Test Results

  • Auth tests: 15/15 PASS
  • Desktop chat: PASS
  • Focus sessions: PASS
  • Advice: PASS
  • Staged tasks: PASS
  • Screen activity sync: 2 FAIL (pre-existing on main)
  • From-segments: PASS
  • Chat generate title: PASS
  • Conversations count: PASS
  • Combined suite: 1026 passed, 13 failed (ALL 13 pre-existing on main)

Codex Audit

  • C1 (CRITICAL → FIXED): Auth fallback decoded JWT without signature verification. Fixed in 94c9130 — now uses firebase_admin.auth.verify_id_token().
  • C2 (CRITICAL → FIXED): Email PII logged without sanitization. Fixed in 94c9130 — now uses sanitize_pii().
  • 12 WARNINGs: Non-blocking. Notable: no size limit on image_b64 in screen_frame handler (W1), staged tasks dedup full collection scan (W2), focused_minutes field name misleading (W6).

Cross-PR Interaction

Remote Sync

  • All 3 sub-PRs verified as ancestors of combined branch ✓

Verdict: PASS (after CRITICAL fixes applied)

@beastoin
Copy link
Collaborator Author

beastoin commented Mar 9, 2026

Independent Verification — PR #5374

Verifier: noa (independent, did not author this code)
Branch: verify/noa-combined-5374-5395-5413
Combined with: PRs #5395, #5413
Verified SHA: 94c9130ff2638a3eb0cf9fbf1434cec2a7d1cb27

Test Results

  • Combined suite: 1026 pass, 13 fail, 42 errors
  • All failures are pre-existing on main (5F test_process_conversation_usage_context, 6F test_prompt_cache_integration, 1E test_llm_usage_endpoints) or environment-only (no GCP credentials for Firestore-dependent tests)
  • New tests from this PR: test_auth_routes (15P), test_from_segments (1E env), test_desktop_chat (1E env), test_screen_activity_sync (2F+8E env), test_assistant_settings_ai_profile (31E env), test_focus_sessions (20P), test_advice (26P), test_staged_tasks (50P), test_chat_generate_title (12P), test_conversations_count (11P)
  • No regressions vs baseline (main: 785P, 11F, 1E)

Codex Audit

  • 0 CRITICAL, 10 WARNING (all non-blocking)
  • Auth bypass fix (commit 94c9130): verified soundfirebase_admin.auth.verify_id_token() replaces unsafe base64 JWT decode
  • All 17 new endpoints: properly auth-gated with Depends(auth.get_current_user_uid)
  • Import layering: no violations
  • No database migrations required

Warnings (non-blocking)

  1. Dead code: TranscriptionService.swift still references DEEPGRAM_API_KEY (unreachable)
  2. GEMINI_API_KEY partially removed (EmbeddingService/GoalsAIService retain optional fallback)
  3. staged_tasks full-collection scan on create (O(N) reads per create)
  4. screen_activity fire-and-forget vector upsert (no retry on failure)
  5. Firestore composite indexes may need creation for advice/staged_tasks queries

Commands Run

git fetch origin main collab/5302-integration fix/desktop-stt-backend-5393 collab/5396-integration
git checkout -b verify/noa-combined-5374-5395-5413 origin/main
git merge --no-ff origin/collab/5302-integration
git merge --no-ff origin/fix/desktop-stt-backend-5393
git merge --no-ff origin/collab/5396-integration  # conflict in test.sh resolved
python3 -m pytest tests/unit/<each file> -v --tb=line  # 53 test files
git push origin verify/noa-combined-5374-5395-5413
git merge-base --is-ancestor origin/collab/5302-integration origin/verify/noa-combined-5374-5395-5413  # PASS

Remote Sync

  • Branch pushed and ancestry verified for all 3 sub-PRs ✓

Verdict: PASS

@beastoin
Copy link
Collaborator Author

beastoin commented Mar 9, 2026

Combined UAT Summary — Desktop Migration PRs

Verifier: noa | Branch: verify/noa-combined-5374-5395-5413 | Merge order: #5374#5395#5413

PR Scope Tests Architecture Codex Severity Verdict
#5374 Rust→Python backend migration (33 files) 134P, env-only errors Clean: auth-gated, layering ok 0 CRITICAL, 5 WARNING PASS
#5395 STT through /v4/listen (8 files) No new test files; combined 1026P Clean: WebSocket lifecycle robust 0 CRITICAL, 2 WARNING PASS
#5413 Proactive AI through /v4/listen (30 files) 107P (7 new test files) Clean: handler pattern safe 0 CRITICAL, 3 WARNING PASS

Combined: 1026 pass, 13 fail (pre-existing), 42 errors (env-only) | Cross-PR interference: none | Remote sync: verified

Overall Verdict: PASS — ready for merge in order #5374#5395#5413

beastoin added 14 commits March 10, 2026 03:15
Replaces the hardcoded omi-desktop-auth Cloud Run URL with the
OMI_API_URL environment variable, matching APIClient.baseURL resolution.
Python backend already has identical /v1/auth/* endpoints.

Closes #5359
Pass redirect_uri from the auth session to the callback HTML template
instead of hardcoding omi://auth/callback. This enables desktop apps
(which use omi-computer://auth/callback) to receive OAuth callbacks
correctly when authenticating through the Python backend.
Both Google and Apple callback endpoints now pass the session's
redirect_uri to the auth_callback.html template, enabling dynamic
custom URL scheme redirects per client (mobile vs desktop).
Add server-side validation at /v1/auth/authorize to reject redirect_uri
values that don't match allowed app schemes (omi://, omi-computer://,
omi-computer-dev://). Also fix empty string fallback with 'or' operator.
Use |tojson filter for safe template variable serialization. Add
defense-in-depth scheme validation in JavaScript before redirect.
Block redirect and manual link for disallowed schemes.
…ering

15 tests covering:
- Redirect_uri allowlist validation (rejects https, javascript, data, ftp, empty)
- Allowed schemes pass (omi://, omi-computer://, omi-computer-dev://)
- Google/Apple callback uses session redirect_uri in template
- Fallback to default omi://auth/callback when missing
- XSS safety: JSON-escaped redirect_uri prevents script injection
When FIREBASE_API_KEY has app restrictions (e.g. Android-only),
the signInWithIdp REST API returns 403. Fall back to decoding
the Google id_token JWT, looking up the user via Admin SDK
(get_user_by_email), and creating a custom token directly.
This makes auth work regardless of API key restrictions.
Desktop app uploads transcriptions via this endpoint but Python backend
only had it in the developer API (API key auth). This adds a user-auth
version to conversations router, reusing the same process_conversation
pipeline. Defaults source to 'desktop', accepts timezone and
input_device_name fields sent by Swift client.
New router for desktop app's session-based chat:
- GET/POST/GET/:id/PATCH/:id/DELETE /v2/chat-sessions
- POST /v2/desktop/messages (simple save, not streaming)
- PATCH /v2/messages/:id/rating
beastoin and others added 12 commits March 10, 2026 03:15
…ions

Path updates (5 endpoints):
- v2/chat/initial-message → v2/initial-message
- v2/agent/provision → v1/agent/vm-ensure
- v2/agent/status → v1/agent/vm-status
- v1/personas/check-username → v1/apps/check-username
- v1/personas/generate-prompt → v1/app/generate-prompts (POST→GET)

Decoder hardening:
- ServerConversation.createdAt: use decodeIfPresent with Date() fallback
- ActionItemsListResponse: try "action_items" then "items" key (Python vs staged-tasks)
- AgentProvisionResponse/AgentStatusResponse: make fields optional, add hasVm
- UsernameAvailableResponse: support both is_taken (Python) and available (Rust)

Graceful no-ops:
- recordLlmUsage(): no-op with log (endpoint removed)
- fetchTotalOmiAICost(): return nil immediately (endpoint removed)
- getChatMessageCount(): return 0 immediately (endpoint removed)

Remove staged-tasks migration:
- Remove migrateStagedTasks() and migrateConversationItemsToStaged() from APIClient
- Remove migration callers and functions from TasksStore

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
C1: Replace unsafe base64 JWT decode with firebase_admin.auth.verify_id_token()
which verifies signature against Google public keys before trusting claims.
C2: Wrap email in sanitize_pii() per CLAUDE.md logging rules.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@beastoin beastoin force-pushed the collab/5302-integration branch from 94c9130 to 78d15d2 Compare March 10, 2026 02:15
@beastoin
Copy link
Collaborator Author

Independent Verification — PR #5374 (rebased)

Verifier: noa | Branch: verify/noa-combined-5374-5395-5413-v2 | SHA: 78d15d27

Test Results

  • 134 PR-specific tests: ✅ ALL PASS (auth_routes, from_segments, desktop_chat, chat_generate_title, conversations_count, focus_sessions, advice)
  • Full combined suite: 761 passed, 105 failed (all pre-existing on main), 39 errors (GCP creds — pre-existing)
  • Baseline (main): 591 passed, 139 failed — zero regressions

Architecture Review

  • Import hygiene: ✅ All new modules use top-level imports, proper hierarchy
  • Auth security: ✅ JWT verification via firebase_admin.auth.verify_id_token(), redirect_uri allowlist (omi://, omi-computer://, omi-computer-dev://), auth codes single-use
  • Logging security: ✅ sanitize() wraps response.text, sanitize_pii() wraps emails, no raw tokens in logs
  • State management: ✅ No new @published on AppState, clean service replacement

Mac Mini E2E (agent-swift + cliclick)

  • Connected via agent-swift connect --pid <PID> — full accessibility tree captured
  • Sidebar navigation verified: Dashboard → Chat → Memories → Tasks → Settings
  • Screenshots captured via screencapture -l <WID> for each page
  • Chat page: message input + Claude model selector present
  • Settings page: Screen Capture, Audio Recording, Notifications, Ask omi, Font Size all rendered

Warnings (non-blocking)

  • W1: Dead code _verify_apple_id_token defined but never called (auth.py:496-534)
  • W2: Inconsistent base URL resolution between BackendTranscriptionService (APIClient) vs BackendProactiveService (getenv)

Verdict: ✅ PASS

0 CRITICAL, 2 WARNING (non-blocking). Merge order: #5374#5395#5413.

@beastoin
Copy link
Collaborator Author

Deployment Steps Checklist

Deploy surfaces: Backend (Cloud Run + GKE backend-listen + GKE pusher) + Desktop (auto-deploy)

Pre-merge

  • Manager merge approval
  • Merge to main (no squash)

Backend deploy (hand to @mon)

  1. gh workflow run gcp_backend.yml -f environment=prod -f branch=main — Deploy Backend to Cloud Run (image build)
  2. gh workflow run gke_backend_listen.yml -f environment=prod -f branch=main — Upgrade Backend Listen Helm Chart (rollout)
  3. gh workflow run gke_pusher.yml -f environment=prod -f branch=main — Deploy Backend Pusher to GKE
  4. Verify all 3 workflows complete green

Desktop deploy (automatic)

  1. desktop_auto_release.yml triggers on merge (auto-increments version, pushes tag)
  2. Codemagic omi-desktop-swift-release builds, signs, notarizes, publishes

Post-deploy verification

  1. Cloud Logging: no new 5xx errors on backend-listen
  2. Verify new endpoints respond: /v4/desktop/chat, /v4/desktop/conversations/count, /v4/desktop/advice, /v1/auth/authorize
  3. Desktop app updates via Sparkle and connects to Python backend
  4. Monitor T+1h, T+4h, T+24h

Rollback plan

  • Backend: redeploy previous image tag via same workflows
  • Desktop: ./scripts/rollback_release.sh <tag>

by AI for @beastoin

@beastoin
Copy link
Collaborator Author

Independent Verification — PR #5374 (collab/5302-integration)

Verifier: noa (independent)
Branch: verify/noa-combined-5374-5395-5413-v2 (combined with #5395, #5413)
SHA: 94c9130
Backend: api.omi.me (prod Python backend)
Platform: Mac Mini (macOS 26, ad-hoc signed)

Results

Test Result
Swift build (combined branch) PASS — 1146 compilation units
Real Google OAuth sign-in PASS — full OAuth flow via Safari → backend callback → app
Onboarding (5 steps) PASS — Chat → Notifications → Floating Bar → Voice Input → Tasks → Dashboard
Dashboard load PASS
Sidebar navigation (Chat, Memories, Tasks, Apps) PASS
Auth state persistence PASS — isSignedIn=true, hasCompletedOnboarding=true

Notes

  • JWT signature verification (new auth security fix commit) working correctly
  • No regressions in sign-in or onboarding flow

Verdict: PASS

@beastoin
Copy link
Collaborator Author

Independent Verification — PR #5374

Verifier: noa (independent)
Branch: verify/noa-combined-5374-5395-5413-5537 (e3cab73)
SHA verified: 78d15d2 (current HEAD, matches remote)

Scope

Desktop auth + base integration: Google OAuth flow, AuthService, AppState, sidebar navigation, backend endpoints (auth, chat, advice, tasks, focus sessions, screen activity).

Results

Check Result
Backend tests 905 pass, 127 fail, 39 err — identical to main baseline (0 regressions)
Swift build PASS (30.58s)
Auth E2E PASS — Google OAuth → custom token → Firebase ID token → signed in
Dashboard E2E PASS — loads in 411ms, tasks/goals/conversations sections render
Sidebar navigation PASS — Conversations page loads (19 items)
Codex audit 0 CRITICAL, 6 WARNING (see combined summary)
Remote sync PASS — ancestry verified

Codex Warnings (non-blocking)

  • W-2: AuthService.apiBaseURL calls fatalError on missing OMI_API_URL — crashes app vs graceful fallback
  • W-4: AdviceResponse uses object type for timestamps — bypasses Pydantic validation
  • W-5: staged_tasks dedup does full collection scan — O(n) Firestore reads per creation

Known Errors (pre-existing, not regressions)

  • HTTP 404s for settings sync, scores, conversations count — desktop API endpoints not deployed to prod
  • ACP bridge "Internal error" — AI service unavailable (infra, not code)

Verdict: PASS

Zero regressions. Auth flow verified end-to-end with real Google OAuth. Warnings are non-blocking with clear follow-up paths.

@beastoin
Copy link
Collaborator Author

Agent VM gaps fixed and verified:

  1. vm-ensure: creates new GCE VMs for first-time users (ported from Rust agent.rs) — generates omi-agent-{uid[:12]} name, writes Firestore, spawns background GCE creation
  2. vm-status: returns all desktop-needed fields (vm_name, ip, auth_token, zone, created_at, last_query_at), triggers restart for stopped/terminated VMs (Rust parity)
  3. Fail-fast: GCE timeout and missing IP now raise instead of writing bad state

Test results:

  • pytest tests/unit/test_agent_vm.py -v — 21/21 pass
  • Reviewer: PR_APPROVED_LGTM (iteration 4)
  • Tester: TESTS_APPROVED (iteration 2)

All checkpoints passed (CP0-CP8). PR ready for merge pending manager approval.


by AI for @beastoin

@beastoin
Copy link
Collaborator Author

Re-verification — PR #5374 (VM endpoint fix)

Verifier: noa (independent)
SHA verified: 40ae983 (updated HEAD)
Branch: verify/noa-combined-5374-5395-5413-5537 (0a7b431)

New Commits (8)

  • 4ec7217c7 Add GCE VM creation logic for new users (port from Rust agent.rs)
  • 4587490b8 Add unit tests for vm-ensure and vm-status (13 tests)
  • 23d88ee7c Fix reviewer issues: imports to top-level, fail-fast on GCE timeout/IP
  • 4b8704e95 Fix test in-function imports per CLAUDE.md
  • c9640aa6f Move time import to module level
  • f1a912daf Fix test in-function imports
  • 03ddcd8cd Add boundary and edge-case tests (8 new, 21 total)
  • 40ae983af Fix test isolation: mock GCE status in incomplete-payload tests

Code Review

  • _vm_response() returns all fields desktop needs (vm_name, ip, auth_token, zone, created_at, last_query_at) — matches AgentVMService expectations
  • vm-ensure provisions new VMs: generates vm_name from UID prefix, creates auth_token, claims Firestore slot, spawns background GCE creation
  • vm-status now verifies GCE status and restarts stopped VMs (was passive before)
  • Imports at module level per CLAUDE.md
  • GCE config via env vars (GCE_PROJECT_ID, GCE_SOURCE_IMAGE, AGENT_GCS_BUCKET)

Results

Check Result
New VM tests 21/21 pass
Full suite 1110 pass, 0 new failures
test.sh merge Resolved — kept all entries
Ancestry 40ae983

Verdict: PASS

VM endpoints now complete — creation, status with restart, full response fields. All tests pass.

@beastoin
Copy link
Collaborator Author

Independent E2E Verification — Local Backend

Verifier: noa (independent)
Combined branch: 0841bd3 (PRs #5374 + #5395 + #5413 merged in order)
Tested SHA: 94c9130 (includes auth security fix — JWT signature verification)

Local Backend E2E Test

Set up local Python backend from combined branch on Mac Mini, wired desktop app to http://localhost:8000.

Pipeline verified end-to-end:

Desktop app → ws://localhost:8000/v4/listen → Deepgram STT → transcript segments → app

Results:

  • ✅ WebSocket /v4/listen accepted, Deepgram connection established
  • ✅ 35 transcript segments captured in session 31
  • ✅ Full transcript text accurately transcribed from test audio
  • ✅ Auth token verification working (prod Firebase tokens accepted)
  • ✅ Session persistence across kill/relaunch (auth_tokenExpiry preserved)

Evidence from app logs:

BackendTranscriptionService: Connecting to ws://localhost:8000/v4/listen?language=multi&sample_rate=16000&codec=pcm16&channels=1&source=desktop
BackendTranscriptionService: Connected
Transcript [NEW] Speaker 1: Testing the local back end transcription pipeline. This audio is being captured by the Omi desktop application. And sent to the local Python backend running on port 8,000.

Declarative E2E Flows (4/4 PASS)

Flow Test Result
1 Auth & Session Bootstrap ✅ PASS — session restored after kill/relaunch
2 Live Audio Transcription ✅ PASS — 35 segments via local backend, 172 widgets responsive
3 Screen Analysis Settings ✅ PASS — General, Rewind, Privacy pages rendered
4 Navigation ✅ PASS — Dashboard, Chat, Memories, Tasks, Apps, Settings

Verdict: PASS — Desktop migration from Rust to Python backend verified end-to-end with local backend running combined PR code.

Note: Current PR HEAD is 40ae983 — unit tests verified at that SHA in previous verification round. This E2E test was on 94c9130 (auth fix commit).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate desktop macOS app from Rust backend to Python backend

1 participant