7 months ago
663a8bc* fix: default workspace jsonrpc cwd
Co-authored-by: Max Weinbach <mweinbach@users.noreply.github.com>
* chore: add desktop remote access deps
Co-authored-by: Max Weinbach <mweinbach@users.noreply.github.com>
* feat: add desktop remote access bridge
Co-authored-by: Max Weinbach <mweinbach@users.noreply.github.com>
* feat: scaffold mobile expo app
Co-authored-by: Max Weinbach <mweinbach@users.noreply.github.com>
* docs: add mobile remote access runbook
Co-authored-by: Max Weinbach <mweinbach@users.noreply.github.com>
* feat: wire mobile relay transport client
Co-authored-by: Max Weinbach <mweinbach@users.noreply.github.com>
* feat: simulate mobile jsonrpc fallback
Co-authored-by: Max Weinbach <mweinbach@users.noreply.github.com>
* test: cover mobile transport integration
Co-authored-by: Max Weinbach <mweinbach@users.noreply.github.com>
* fix: tighten mobile jsonrpc client handling
Co-authored-by: Max Weinbach <mweinbach@users.noreply.github.com>
* feat: add mobile approval request flow
Co-authored-by: Max Weinbach <mweinbach@users.noreply.github.com>
* test: cover mobile input request flow
Co-authored-by: Max Weinbach <mweinbach@users.noreply.github.com>
* docs: update mobile remote access status
Co-authored-by: Max Weinbach <mweinbach@users.noreply.github.com>
* feat: send mobile turns through runtime client
Co-authored-by: Max Weinbach <mweinbach@users.noreply.github.com>
* feat: add mobile jsonrpc request timeouts
Co-authored-by: Max Weinbach <mweinbach@users.noreply.github.com>
* test: add mobile trusted reconnect coverage
Co-authored-by: Max Weinbach <mweinbach@users.noreply.github.com>
* fix: repair expo app config
Co-authored-by: Max Weinbach <mweinbach@users.noreply.github.com>
* chore: sync expo project defaults
Co-authored-by: Max Weinbach <mweinbach@users.noreply.github.com>
* refactor: move mobile app into apps folder
Co-authored-by: Max Weinbach <mweinbach@users.noreply.github.com>
* expo
* fix: realign mobile Expo SDK 54 dependencies
* feat: refresh mobile pairing onboarding
Reframe the mobile pairing landing screen around connecting a computer first, with clearer status messaging, onboarding steps, and faster reconnect entry points. Update the pairing stack titles and scan copy to match the new flow.
* fix: preserve desktop state before mobile relay init
* chore: add mobile xcode workspace helper scripts
* fixes
* feat: add workspace routing foundation for mobile feature parity
- Add generic call() method to CoworkJsonRpcClient for workspace control RPCs
- Increase default JSON-RPC timeout from 5s to 15s for workspace operations
- Add bridge-level workspace/list and workspace/switch interception in MobileRelayBridge
- Create workspaceStore (Zustand) for mobile workspace state management
- Add Zod schemas for workspace, skills, memory, provider, MCP, and backup types
- Wire workspace context hydration into MobileAppProvider on connect/disconnect
- Add setWorkspaceListProvider to MobileRelayBridge for IPC workspace list injection
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: restructure mobile tabs and add workspace switcher
- Restructure from 2 tabs to 4: Threads | Workspace | Settings | Connection
- Move existing connection/pairing UI to dedicated Connection tab
- Repurpose Settings tab as configuration hub (providers, models, MCP, usage)
- Add Workspace tab hub with navigation to skills, memory, backups
- Build workspace-aware thread list header showing active workspace + model
- Add workspace switcher modal for multi-workspace browsing
- Add clearAll() to threadStore for workspace switch rehydration
- Create placeholder screens for Phase 3/4 sub-routes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add rich thread feed rendering components for mobile
- Add MarkdownText component with code block rendering, inline code, and bold
- Add ToolCallCard with collapsible args/result, state badges, approval info
- Add ReasoningCard with collapsible expand/collapse and accent styling
- Add TodoCard with checkbox-style status indicators
- Add SubagentBar horizontal strip for active agent visibility
- Refactor ThreadFeedItem to delegate to rich components per item kind
- Enhance PendingRequestCard with danger styling and monospace command display
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add skills, memory, and backup management for mobile
- Create skillsStore with full CRUD (fetch, install, enable/disable, delete)
- Create memoryStore with fetch, upsert, delete, and scope filtering
- Create backupStore with fetch, create checkpoint, restore, delete
- Flesh out skills screen with toggle switches and delete actions
- Flesh out memory screen with scope filter tabs, add form, and long-press delete
- Flesh out backups screen with checkpoint list, restore/delete actions
- Wire store cleanup into MobileAppProvider on disconnect
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add provider, model, MCP, and usage settings for mobile
- Create providerStore with catalog fetch, API key set, authorize, logout, callback
- Create mcpStore with server fetch, validate, and delete
- Flesh out providers screen with expandable cards, API key input, disconnect
- Flesh out models screen with model picker from connected providers
- Flesh out MCP servers screen with validation, delete, tool count
- Flesh out usage screen with aggregated token/cost stats from thread snapshots
- Wire provider and MCP store cleanup into MobileAppProvider on disconnect
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(desktop): back remote access with remodex state
* fix(mobile): expose fallback relay mode explicitly
* fix(mobile): use live relay transport outside demo mode
* fix(bash): prefer absolute POSIX shell paths
* test: stabilize subprocess integration coverage
* fix(mobile): use iphone relay role header
* refactor: align mobile settings with shared control contracts
* test: cover shared mobile control contracts
* fix(mobile): resolve shared workspace modules in metro
* fix: fall back to managed remote relay state
* fix desktop titlebar drag handling on Windows
* feat(mobile): match desktop theme tokens and implement single stack navigation
- Restructures mobile app navigation from a tab-based layout to a flat stack layout.
- Updates the Home Screen to act as the primary Threads List with a native search bar.
- Replaces the feed ScrollView with an inverted FlatList for Chat Screens, keeping messages anchored at the bottom.
- Introduces an inline `PendingRequestCard` injected natively into the inverted list data to keep context clear during active tooling without modals.
- Implements a transparent `FileExplorerDrawer` as a right-edge slide-in using Reanimated.
- Aligns all mobile colors and design tokens with the desktop app's `AppTheme` configuration across `light` and `dark` modes.
- Re-routes all Settings, Integrations, and Skills views to push natively from the newly restructured root layout.
* fix: default workspace jsonrpc cwd (#61)
* feat: add file upload and multimodal image support
Add the ability for users to attach files with their prompts. All uploaded
files are saved to a "User Uploads/" directory in the workspace, and the
model receives a hidden system note with the file path.
For models that support multimodal input (supportsImageInput), images are
also sent inline as base64 content. For Google/Gemini models, audio, video,
and PDF files are additionally sent as inline multimodal content.
Changes:
- Expand turn/start and turn/steer JSON-RPC input schema to accept
{type: "file", filename, contentBase64, mimeType} parts
- Add extractJsonRpcInput() to parse text + file attachments from input
- Wire attachments through route handlers -> session -> TurnExecutionManager
- TurnExecutionManager saves files to "User Uploads/" and builds multimodal
content arrays based on provider capabilities
- Fix PI message bridge to pass image content parts through for user messages
(Google Interactions runtime already supported this)
- Add file attachment UI to desktop app with paperclip button, file picker,
and attachment chips
- Update WebSocket protocol docs and regenerate codegen artifacts
https://claude.ai/code/session_01MfYmqHYGHoHuDwUje9xn9w
* fix: use native multimodal input format for OpenAI responses runtime
Update the OpenAI Responses runtime to handle multimodal user messages
natively instead of relying on the PI bridge to flatten them to text.
When user messages contain image content parts, the OpenAI conversion now
builds proper input_image parts with data URLs, matching the Responses API
format. The PiMessageLike user type is widened to accept array content so
the PI bridge's multimodal output flows through correctly.
The PI bridge remains the normalization layer (ModelMessage -> PiMessage)
for all runtimes, but each runtime now handles its native format:
- PI runtime: passes image content parts through to the PI SDK
- OpenAI runtime: converts to input_image with data URLs
- Google runtime: already handled via convertRichContentParts()
https://claude.ai/code/session_01MfYmqHYGHoHuDwUje9xn9w
* fix: wire file attachments through turn/steer pipeline
The turn/steer schema accepted file parts but the handler silently
dropped them. Now attachments flow through sendSteerMessage ->
pendingSteers -> commitPendingSteers -> buildUserMessageContent,
so steered messages get the same file-save + inline-multimodal
treatment as turn/start messages.
https://claude.ai/code/session_01MfYmqHYGHoHuDwUje9xn9w
* fix: address review feedback for file attachment support
- Fix PI message bridge to only treat image parts as multimodal when they
have both data and mimeType fields, fixing the
"preserves non-text user content with a placeholder" test
- Wire attachments through the desktop steer path
(dispatchJsonRpcTurnSteer -> steerJsonRpcTurn now forward file parts)
- Allow attachment-only messages (no text) by relaxing the empty-text
guards in sendUserMessageToThread, sendMessage, and the turn/start
route handler
- Generate fallback display text from filenames for attachment-only submits
https://claude.ai/code/session_01MfYmqHYGHoHuDwUje9xn9w
* fix: deduplicate upload filenames, allow file-only steers, preserve attachments in queue/reconnect
- Deduplicate filenames in User Uploads: check both same-message
collisions and existing files on disk, appending _1, _2 etc.
- Allow file-only turn/steer requests (relaxed text-required validation
on both server route handlers and TurnExecutionManager steer validation)
- Preserve attachments through the desktop message queue: new
pendingThreadAttachments map stores attachments alongside queued text,
flushOneQueuedThreadMessage retrieves them
- Pass attachments through reconnectThread and newThread paths so
files aren't silently dropped on inactive/transcriptOnly threads
- Handle empty text in content parts (skip empty text part when
attachments provide the content)
https://claude.ai/code/session_01MfYmqHYGHoHuDwUje9xn9w
* fix: keyboard send path, per-message queue attachments, and edge case fixes
- Route Enter-key sends through the attachment-aware path so keyboard
submits include pending files (was bypassing onSubmit)
- Store queued attachments per-message (FIFO array) instead of
per-thread (single slot), preventing overwrites when multiple messages
are queued with different attachments
- Fix flush retry: re-prepend attachments alongside text when
sendUserMessageToThread rejects, so retries don't lose files
- Fix attachment-only queue: use `=== undefined` check instead of falsy
check, so empty-string text entries (from attachment-only sends) are
not silently dropped
- Fix reconnectThread: queue attachments even when firstMessage is empty
- Fix newThread: grab queued attachments from pending map when
dispatching the first message via ensureThreadSocket's non-queued path
https://claude.ai/code/session_01MfYmqHYGHoHuDwUje9xn9w
* fix: keep message and attachment queues in sync on flush retry
Use direct array manipulation instead of prependPendingThreadMessage
when re-queuing a failed flush, since prependPendingThreadMessage
silently drops empty strings. This prevents the two parallel FIFOs
(pendingThreadMessages and pendingThreadAttachments) from desyncing
when an attachment-only message retry occurs.
https://claude.ai/code/session_01MfYmqHYGHoHuDwUje9xn9w
* fix: only clear pending attachments when send is accepted
sendMessage now returns a boolean indicating whether the message was
dispatched. ChatView clears pendingAttachments only when sendMessage
returns true, so rejected sends (e.g. steer temporarily not accepted)
preserve the user's file selections for retry.
https://claude.ai/code/session_01MfYmqHYGHoHuDwUje9xn9w
* fix: guard OpenAI input_image by image/* MIME type, clean attachments on workspace removal
- Only convert user content parts to OpenAI input_image when mimeType
starts with "image/", preventing audio/video/PDF blobs (tagged as
type: "image" for Gemini) from being sent as input_image to OpenAI
- Clear pendingThreadAttachments in workspace teardown to prevent
memory leaks from orphaned base64 payloads
https://claude.ai/code/session_01MfYmqHYGHoHuDwUje9xn9w
* fix(desktop): align pending queue drain and file-only JSON-RPC input
- Clear pendingThreadAttachments when draining pending messages to keep queues in sync.
- Omit empty text parts in turn/start and turn/steer payloads when only files are sent.
Made-with: Cursor
* fix(ci): install apps/mobile deps in postinstall
Root postinstall only ran bun install for apps/desktop, so CI never
installed zustand for apps/mobile and tests importing mobile stores
failed with unresolved packages.
Also defer relay "connected" status until after secureReady trust
persistence completes so snapshots match persisted userData/remodex state.
Made-with: Cursor
* fix(ci): serialize tests; clear attachments on thread switch; propagate send failures
- Run bun test with max-concurrency 1 so JSDOM-based desktop tests do not
clobber shared globalThis.document/window under parallel async tests.
- Reset ChatView pendingAttachments when selectedThreadId changes.
- newThread/reconnectThread return boolean; sendMessage returns false when
transcript/reconnect paths fail (e.g. missing server URL) so UI keeps
composer and attachments.
- Allow reconnectThread for draft threads when only attachments are queued.
Made-with: Cursor
* test(desktop): stabilize header and relay CI assertions
- update the skills header test to override store state at runtime without
polluting later store-mocked test files
- wait for secureReady persistence in mobile relay tests instead of assuming
a single timer tick is enough on CI
Made-with: Cursor
* test: stabilize header assertions and extend large journal replay timeout
- wait for the skills header text before asserting to avoid sampling an
intermediate render on slower CI workers
- give the 1000+ event JSON-RPC replay test a larger per-test timeout so it
can complete on slower GitHub runners without timing out at Bun's 5s default
Made-with: Cursor
* test: fix leaked skills header mock and slow journal replay timeout
- restore module mocks in skills-catalog-page so HeaderAndFilters tests do not
inherit the stubbed "header" component on CI
- wait for the real header text before asserting in header-and-filters
- extend the per-test timeout for the 1000+ event JSON-RPC replay case
Made-with: Cursor
* test: extend JSON-RPC replay timeouts for CI runners
Give the two remaining journal replay tests explicit 20s budgets so they do
not trip Bun's default 5s timeout on slower GitHub runners. The local suite
still passes unchanged under CI's max-concurrency=1 setting.
Made-with: Cursor
* test: remove leaked skills page component mocks
Stop mocking HeaderAndFilters and InstallationCardGrid in the skills catalog
page test. The HeaderAndFilters mock could remain cached in Bun's module graph
and cause the later header-and-filters test to render literal "header" on CI.
Made-with: Cursor
* test: widen jsonrpc replay CI timeouts
Increase the timeout budget only for the replay-heavy JSON-RPC flow tests that have been timing out on GitHub Actions ubuntu-latest.\n\nNormalize the mixed timeout declaration in the journal-projection test, keep the broader waits scoped to replay-specific assertions, and record the CI investigation plus verification evidence in tasks/todo.md.
* fix: resolve attachment review feedback
Honor configured uploads directories for turn attachments, enforce attachment size limits before decode, and stop injecting fake placeholder text for attachment-only sends and steers. Also switch the desktop picker to chunked base64 encoding, make pending-steer duplicate detection attachment-aware, regenerate the JSON-RPC schema artifacts, and add focused regression coverage.
Verification:
- ~/.bun/bin/bun test test/session.test.ts test/server.jsonrpc.flow.test.ts apps/desktop/test/jsonrpc-single-connection.test.ts apps/desktop/test/chat-reasoning-ui.test.ts
- ~/.bun/bin/bun run typecheck
- ~/.bun/bin/bun test test/jsonrpc.codegen.test.ts
- ~/.bun/bin/bun test --max-concurrency 1
* fix: preserve queued attachments during defaults replay
Replay queued attachment payloads when workspace defaults flush pending thread messages so deferred first sends keep their file inputs.
Mirror the reducer's atomic requeue behavior, export the shared attachment-shift helper for the workspace-default action, and add a desktop regression for attachment-only queued sends.
Verification:
- ~/.bun/bin/bun test apps/desktop/test/workspace-settings-sync.test.ts
- ~/.bun/bin/bun run typecheck
- ~/.bun/bin/bun test --max-concurrency 1
* fix: finish attachment turn validation
Start live sessions for attachment-only transcript sends by treating queued attachments as first-turn input and replaying them through the existing pending-message path.
Add shared per-turn attachment count and combined-size limits, enforce them in the JSON-RPC turn schema and runtime validation, and cover the new transcript/request-limit cases with focused desktop and server regressions.
Verification:
- ~/.bun/bin/bun test apps/desktop/test/jsonrpc-single-connection.test.ts test/server.jsonrpc.flow.test.ts test/session.test.ts
- ~/.bun/bin/bun run docs:generate-jsonrpc
- ~/.bun/bin/bun run typecheck
- ~/.bun/bin/bun test --max-concurrency 1
* fix: cap attachment picker and steer queues
Reject oversized desktop attachment selections before renderer-side buffering by sharing attachment size math between the picker and the harness helpers.
Bound pending steer attachment payload growth during long-running turns and deduplicate attachment-aware requeue behavior so attachment-only retries stay in sync across desktop queue flush paths.
Add focused picker/runtime/session regressions and record the verification trail in tasks/todo.md.
* fix: track final deduplicated upload filenames
* Baseline: added autoresearch harness using two CI-like unit-suite reruns in autoresearch.sh and docs/typecheck/test:stable gating in autoresearch.checks.sh. Current baseline is green with ci_failure_score=0.
Result: {"status":"keep","ci_failure_score":0}
* Retargeted autoresearch to the real PR #61 CI failure: repeated CI-like reruns of the JSON-RPC thread/resume replay timeout. Baseline reproduced 0 failures across 6 focused reruns, and docs/typecheck/full CI-style unit suite/test:stable all passed.
Result: {"status":"keep","resume_replay_failures":0}
* Widened the reproducer from the single PR-failing test to the entire test/server.jsonrpc.flow.test.ts file under CI=true. Baseline stayed green across 8 reruns, with full docs/typecheck/CI-style suite/test:stable checks still passing.
Result: {"status":"keep","jsonrpc_flow_failures":0}
* Strengthened merge-ref reproduction to 4 repeated CI=true full-suite runs in the actual PR #61 merge worktree. All 4 benchmark runs passed and the merge-ref docs/typecheck/CI-suite/test:stable checks also passed, so the flake is real but rarer than the earlier failed check suggested.
Result: {"status":"keep","merge_ci_failures":0}
* Established a sensitive reproducer for the real flake by hammering the exact PR #61 merge-ref thread/resume JSON-RPC test 200 times under CI=true. Baseline reproduced 17 failures, confirming the race lives in the targeted test path itself rather than needing the full suite.
Result: {"status":"keep","merge_resume_failures":17}
* Pivoted the flake benchmark onto the current repo so candidate fixes can be measured directly. Baseline reproduced 1 timeout in 100 CI-like reruns of the exact thread/resume JSON-RPC test.
Result: {"status":"keep","resume_failures":1}
* Forced active connection shutdown in the flaky test cleanup (`await server.stop(true)`) so Bun does not hang on lingering websocket state. This improved the 100-run thread/resume stress metric from 1 failure to 0, and docs/typecheck/full CI-style suite/test:stable all passed.
Result: {"status":"keep","resume_failures":0}
* Hardened the main CI workflow with explicit typecheck and stable per-file unit test steps, and added test/ci.workflow.test.ts to lock those guardrails in. The workflow benchmark is now green and full docs/typecheck/CI-style suite/test:stable checks all passed.
Result: {"status":"keep","ci_guardrail_failures":0}
* Codified forced-close teardown guardrails for the websocket-heavy JSON-RPC flow spec by adding stopTestServer() in test/helpers/wsHarness.ts and replacing plain await server.stop() callsites. The unsafe teardown count dropped from 20 to 0, and docs/typecheck/full CI-style suite/test:stable all passed.
Result: {"status":"keep","unsafe_jsonrpc_teardowns":0}
* Retargeted the benchmark to the remaining websocket-heavy sibling file, test/server.toolstream.test.ts. Baseline found 2 plain await server.stop() teardown callsites still using the known-risk pattern.
Result: {"status":"keep","unsafe_toolstream_teardowns":2}
* Applied the shared forced-close helper to test/server.toolstream.test.ts, replacing its 2 remaining plain await server.stop() teardown callsites. The unsafe toolstream teardown count dropped from 2 to 0, and docs/typecheck/full CI-style suite/test:stable all passed.
Result: {"status":"keep","unsafe_toolstream_teardowns":0}
* Retargeted the benchmark to the remaining server/websocket specs still using plain stop teardowns. Baseline found 48 plain server.stop() callsites across desktop.controlSocket.threadList, harness.ws.e2e, server.jsonrpc.control, server.jsonrpc, and server.test.
Result: {"status":"keep","unsafe_server_teardowns":48}
* Applied stopTestServer() across the remaining server/websocket specs: desktop.controlSocket.threadList, harness.ws.e2e, server.jsonrpc.control, server.jsonrpc, and server.test. The unsafe server teardown count dropped from 48 to 0, and docs/typecheck/full CI-style suite/test:stable all passed.
Result: {"status":"keep","unsafe_server_teardowns":0}
* Broadened the benchmark to all remaining plain server.stop() callsites across test/**/*.test.ts. Baseline found 2 leftovers in the live/persisted handoff test inside test/server.jsonrpc.flow.test.ts.
Result: {"status":"keep","unsafe_all_server_teardowns":2}
* Removed the final 2 plain server.stop() callsites in test/server.jsonrpc.flow.test.ts by routing the live/persisted handoff teardown through stopTestServer(). The all-tests unsafe teardown count dropped from 2 to 0, and docs/typecheck/full CI-style suite/test:stable all passed.
Result: {"status":"keep","unsafe_all_server_teardowns":0}
* Retargeted to the real overall signal: 5 repeated CI=true full-suite runs after the teardown and workflow hardening. All 5 benchmark runs passed, and docs/typecheck/CI-style suite/test:stable checks also passed.
Result: {"status":"keep","full_ci_failures":0}
* Retargeted to the stricter per-file stable runner signal. Three repeated `bun run test:stable -- --max-concurrency 1` passes succeeded, and docs/typecheck/full CI-style suite/test:stable checks also passed.
Result: {"status":"keep","stable_failures":0}
* Retargeted from flake detection to CI signal quality. Baseline on one full CI-style suite pass shows 41 noisy green-run lines: 29 repeated Google GenAI experimental warnings, 11 repeated Langfuse degraded-config warnings, and 1 expected desktop init error line.
Result: {"status":"keep","ci_noise_lines":41}
* Reduced green-run CI noise with low-risk test-only changes: disabled observability in the shared websocket test helper and silenced the expected desktop init error log in bootstrap-cache.test.ts. Noise dropped from 41 to 32 lines, removing the expected error line and cutting Langfuse warnings from 11 to 3.
Result: {"status":"keep","ci_noise_lines":32}
* Cached the Google Interactions client per API key in src/runtime/googleNativeInteractions.ts and added a focused runtime test proving the SDK experimental warning only fires once per key. Full-suite green-run noise dropped from 32 to 4 lines, with Google warnings reduced from 29 to 1 and checks still all green.
Result: {"status":"keep","ci_noise_lines":4}
* Reduced the remaining test-only CI noise by making server test helper env overrides preserve AGENT_OBSERVABILITY_ENABLED=false, silencing expected observability-runtime warnings inside the observability runtime tests, and defaulting agent.test configs to observabilityDisabled unless a test explicitly opts in. Real full-suite noise dropped from 4 to 1 line (only the one-time Google SDK experimental warning remains), and docs/typecheck/CI-style suite/test:stable all passed.
Result: {"status":"keep","ci_noise_lines":1}
* fix: address unresolved PR review feedback
* test: isolate desktop chat view regression state
* design tweaks
* fix: restore desktop composer steer hint
* fix: address security issues from PR review feedback
- Add MAX_PENDING_STEER_COUNT (32) limit to prevent unbounded queue growth
- Fix path traversal checks by adding path.sep suffix to startsWith validation
- Add 100MB size limit before base64 decoding in file uploads
* docs: add TODO comments for P2 validation timing issues
Add TODO notes documenting two P2 UX issues where uploaded file path
validation happens AFTER the client receives success acknowledgment:
1. turn/start: session_busy emitted before buildUserMessageContent validates
2. turn/steer: steer_accepted emitted before commitPendingSteers validates
Both are low-priority UX issues - errors are still caught and emitted,
but clients receive async errors after already clearing composer state.
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat(mobile): redesign mobile app to match desktop theme with native iOS patterns
- Update theme tokens to use warm sepia palette matching desktop app
- Replace hardcoded colors with theme-aware tokens across all screens
- Remove decorative polka dots and background circles from Screen and pairing hero
- Fix ScrollView flex layout so content scrolls properly under transparent headers
- Add liquid glass header buttons via expo-glass-effect with fallback
- Replace ActionSheetIOS with native Link.Menu context menu for quick access
- Reorganize tab bar: workspace first, then threads, skills, settings
- Consolidate pairing/connection hero into workspace tab as primary hub
- Add inline workspace dropdown selector with chevron indicator
- Set headerBlurEffect to none for clean transparent collapsing headers
- Clean up threads tab to focus on thread list only
- Add expo-glass-effect and expo-image dependencies
* feat(shared): add mobile relay security module with X25519 encryption and secure envelope encoding
Introduces shared cryptographic utilities for desktop-mobile secure relay:
- X25519 key pair generation and validation
- ECDH shared key derivation
- AES-256-GCM secure envelope encoding/decoding with sequence counters
- Pairing QR version constant (v2)
- Relay control message parsing helpers
- Exponential backoff reconnect delay calculation
This is the foundational security layer for the Remodex secure transport,
enabling end-to-end encryption between desktop and mobile via the relay server.
* feat(models): add Fireworks Kimi K2.5 Turbo router and fix GLM-5 display name
- Add new Kimi K2.5 Turbo model via Fireworks router endpoint
- accounts/fireworks/routers/kimi-k2p5-turbo
- Supports text + image input, reasoning enabled
- 262K context window, 65K max tokens
- Pricing: bash.6/M input, /M output tokens
- Fix GLM-5 display name from "GLM-5" to "GLM 5" for consistency
- Register in child agent model routing and connection catalog
* feat(desktop): integrate secure relay encryption into mobile bridge
Refactors electron mobile relay services to use shared security module:
- MobileRelayBridge: X25519 key exchange, AES-256-GCM envelope encryption
- Secure channel establishment with phone via clientHello/secureReady handshake
- Encrypted JSON-RPC tunneling over relay with sequence counters for replay protection
- Exponential backoff reconnect delays with attempt tracking
- Queue management for messages during handshake/reconnect
- Reject mismatched phone pairing attempts (anti-downgrade)
- MobileRelayStore: Ed25519 key validation, atomic writes with 0o600 permissions
- MobileRelayTypes: wire format updated (nonce replaces tag field)
- RemodexState: key validation with fingerprint helpers
* fix(desktop): reposition composer hint to show during streaming
Moves the composer hint (keyboard shortcuts) from PromptInputFooter to
PromptInputBody so it remains visible during streaming/steering modes.
Previously hidden by footer layout changes during busy states.
* test(desktop): add secure transport test coverage for mobile relay
- Mobile relay bridge tests now use real X25519 key pairs
- Added secure envelope encoding/decoding in test fixtures
- New test: rejects secure-ready before client hello
- New test: rejects different phone when already paired
- New test: handles encrypted bridge-level requests
- New test: reconnects with exponential backoff
- Schema tests updated for RELAY_PAIRING_QR_VERSION constant
- Remodex state tests use generated key pairs
* feat(mobile): implement secure relay transport with X25519 encryption
- RemodexSecureTransport: full E2EE implementation with:
- Per-device X25519 key pairs persisted with private keys
- ECDH shared key derivation on mac registration
- AES-256-GCM secure envelopes with sequence counters
- Message queueing during handshake/reconnect
- Protocol state machine (idle -> connecting -> secureReady -> connected)
- Key validation on load and pairing (isValidRelayKeyPair, isValidRelayPublicKey)
- Key fingerprinting for trusted device display
- QR payload version constant from shared security module
- Secure transport client uses buildRelayKeyFingerprint helper
- Reject unexpected client hello from mac to phone
- Exponential backoff for reconnects (computeRelayReconnectDelayMs)
* test: update mobile tests for secure transport constants
- Use RELAY_PAIRING_QR_VERSION constant in QR pairing tests
- Update transport integration test to use version constant
- Connection catalog test updated for GLM 5 display name
- New test: rejects unsupported pairing payload versions
* docs(lessons): add guidance on preserving related changes during cleanup
Adds lesson: do not revert adjacent user-wanted changes during local
diff cleanup; confirm intent before discarding model/catalog updates.
* fix: validate mobile relay turns before ack
Prevent uploaded-file turn and steer requests from being acknowledged before validation completes, and preserve trusted mobile reconnects across relay restarts and session rotation.
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
* fix: address mobile remote access review feedback
* test(desktop): stabilize electron mocks in full suite
* fix: harden mobile relay recovery flows
* fix: address mobile remote access review findings
* fix: add missing return statement in handleRelayControlMessage switch
Adds explicit return false after the switch block to handle unhandled
control message kinds, preventing them from leaking to the application
message handler. Resolves Cursor BugBot review comment about missing
return causing control message leaks.
* fix: allow trusted phone reconnects after pairing session expiry
Reorder clientHello validation to check trusted phone status before
enforcing pairing session expiry. Previously, the expiry check ran first
and blocked ALL clientHello messages after 5 minutes, including
reconnects from already-paired devices. Now:
1. Trusted reconnects skip expiry check entirely
2. Different phones are rejected if already paired
3. First-time pairing attempts are subject to 5-minute expiry
This fixes transient relay/socket drops breaking remote access after
the initial 5-minute pairing window expires.
* fix: revoke active relay when forgetting trusted phone
* fix: harden uploaded attachment handling
* fix: tighten relay trust revocation and workspace switching
* fix: harden mobile relay handshake and bootstrap retry
* fix: align native remodex forget state contract
* fix: harden google uploaded multimodal attachments
* fix: restore turn input compatibility and validation
* chore(mobile): align app icon with desktop branding
* fix: repair remodex key mismatches on load
* fix: initialize mobile crypto before router boot
* refactor(desktop): own mobile relay state in cowork home
* fix(mobile): align session bootstrap transport typing
* Changes from Codex
* fix(mobile): harden pairing and workspace switch flows
* fix(relay): close mobile remote access review gaps
* fix: require encrypted relay pairing proof before trust
* fix: invalidate mobile relay workspace cache on saves
* fix: harden remote access bootstrap and attachment limits
Prevent stale mobile bootstrap runs from reviving reset sessions, defer relay state disk I/O until first use, and reject oversized desktop attachments before base64 encoding. This keeps remote access state consistent and avoids renderer memory spikes during uploads.
Made-with: Cursor
* fix: address PR review comments for mobile remote access
- Remove stale empty array caching on workspace cache error
- Refactor module-level mutable state for relay IPC registrations
- Treat error snapshots as pairing failures in QR connect
- Do not fail session rotation on legacy redirect dial
Made-with: Cursor
* fix: block uploaded attachment symlink escapes
Made-with: Cursor
* fix(relay): clear stale state after start failure
Made-with: Cursor
* fix(relay): scope secure channel keys to sessions
Made-with: Cursor
* fix(relay): harden finalize and forget recovery
Made-with: Cursor
* fix(mobile): handle relay WebSocket ctor failures in openSocket
Wrap createSocket in try/catch so synchronous constructor errors transition
to error status with secureError instead of leaving connecting state.
Add .catch on scheduled reconnect openSocket to surface persistence or
other async failures without unhandled rejections.
Test: WebSocket that throws maps to error state and secureError event.
Made-with: Cursor
* fix(desktop): scope window drag state per registration
Move active window drag tracking into registerWindowIpc so stale drag
state cannot leak across IPC re-registrations.
Add a regression test that reproduces a second-registration move event
without a new drag start.
Made-with: Cursor
* fix(desktop): update workspace roots before relay cache invalidation
Set approved workspace roots immediately after saveState persists workspaces,
then invalidate the mobile relay workspace cache to avoid a stale validation window.
Add IPC tests for saveState ordering and align electron test mocks to avoid
cross-file export mismatches during multi-file test runs.
Made-with: Cursor
* fix(desktop): recover relay after forget during sidecar reconnect
Queue relay session restart when forgetting a trusted phone while the sidecar is offline,
then trigger restart when the sidecar reconnects so remote access recovers automatically.
Also make workspace IPC dialog import resilient for mixed test mocks and add bridge
coverage for the sidecar-offline forget flow.
Made-with: Cursor
* fix(mobile): persist replay counters across trusted reconnects
Persist per-session replay counters for trusted desktops and restore them on
connectTrusted so cold-start reconnects no longer reuse low counters.
Also surface workspace-cache refresh failures as bridge errors instead of
returning misleading empty workspace/list results, with coverage for both
IPC cache behavior and encrypted bridge-level error responses.
Made-with: Cursor
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Max Weinbach <mweinbach@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>Parent6ee6c0c