Complete rebuild of voice input on a server-authoritative streaming architecture, replacing the legacy Web Speech / whole-blob / WASM engines and the dead voice-agent layer (~4k lines removed). Speech-to-text (dictation): - Client streams 16 kHz mono PCM16 chunks over /api/dictation/ws with seq/ack ordering; buffered audio is retained and replayed on reconnect - Server transcribes and streams live partial transcripts back; segments auto-commit every ~15s with silence suppression and adaptive finalization timeouts - Local provider (default, zero config): sherpa-onnx models in a forked worker process — auto-download with progress, staged extraction with verification, corrupt-model auto-recovery, idle shutdown after 5 min - Model catalog with settings picker (accuracy/speed ratings, sizes, download/delete): Parakeet TDT v2 (English) and v3 (25 European languages, auto-detected), Whisper base and tiny (multilingual, light) - OpenAI-compatible provider for any Whisper endpoint - Composer overlay with live transcript, volume meter, timer, and cancel / insert / insert-and-send actions; failed transcriptions keep their audio for retry or accepting the partial text as-is - Configurable keyboard shortcut (default mod+alt+v) toggles dictation; Enter confirms and Escape cancels while recording - Overlay is pixel-aligned with the composer (measured footer height, matching paddings/typography/gaps) — no layout shift when toggling Text-to-speech: - Local Kokoro provider (English, 11 voices) synthesized in the same worker via /api/dictation/tts/speak, managed by the shared model pipeline; sentence-pipelined playback keeps time-to-first-audio at ~1 sentence regardless of message length, and stop cancels in-flight synthesis - Sanitizer keeps inline-code content (strips backticks only), reads interword slashes aloud, and removes only absolute file paths Settings: - Voice page unified: a single read-aloud toggle owns all playback options (the confusing "Enable Voice Mode" is gone); a new "Enable voice input" toggle (default on, persisted to settings.json) hides the composer mic entirely when disabled Mobile and transport: - iOS/Android microphone permissions added (dictation was previously impossible on mobile) - Fixed Android WebSocket upgrades: the Capacitor WebView origin (https://localhost) was missing from the packaged-client allowlist, 403-ing every WS connection — root cause of the old mobile SSE lock, which is now removed for all transports Security and conventions: - All HTTP routes sit behind the global /api auth gate; the WS upgrade explicitly validates the UI session and origin, with oc_url_token narrowly allowlisted and covered by tests; the dictation socket mints a fresh URL token before connecting - Routes register before the generic OpenCode proxy; the client goes through runtimeFetch/getRuntimeUrlResolver, and runtime switches reset the dictation socket - VS Code deliberately reports dictation as unavailable (no server process in that runtime) CI: workflow Node bumped 20 -> 22 to match the repo engines and fix better-sqlite3 installs broken by node-gyp@latest on Node 20. New dependency: sherpa-onnx-node (prebuilt N-API; macOS/Linux x64+arm64, Windows x64 — Windows-on-ARM falls back to the OpenAI-compatible provider)
OpenChamber Mobile
Capacitor shell for the dedicated OpenChamber mobile web surface.
The mobile package reuses the web build, then rewrites mobile.html to index.html in packages/mobile/dist so native iOS/Android always launch MobileApp instead of the hosted surface selector.
Runtime Model
- The native app bundles the mobile UI only; it does not embed the OpenChamber web server or OpenCode server.
- On first launch in Capacitor, the app shows a connection screen for an existing OpenChamber server.
- Connections are saved locally in the app and can be managed from the mobile overflow menu under
Instances. - The connection screen and
Instancesmenu item are Capacitor-only. Hostedmobile.htmlin a normal browser keeps the regular web behavior. - Password-protected OpenChamber servers can be unlocked from the mobile app. The app stores the issued client token with the saved connection.
Commands
Run these from packages/mobile, or use the root mobile:* aliases.
bun run build: buildspackages/weband prepares mobile web assets.bun run sync: prepares assets and runscap sync.bun run add:ios: creates the native iOS project.bun run add:android: creates the native Android project.bun run build:android:debug: builds a debug Android APK without launching an emulator.bun run build:ios:simulator: builds an iOS Simulator app without launching Xcode or Simulator.bun run sim:run: boots a simulator if needed, installs the built iOS app, and launches it.bun run sim:serve: startsserve-simin detached JSON mode and prints the browser preview URL.bun run sim:list: lists runningserve-simstreams.bun run sim:kill: stops runningserve-simstreams.bun run open:ios: opens the iOS project.bun run open:android: opens the Android project.
Headless Quickstart
bun run build
bun run sync
bun run build:ios:simulator
bun run build:android:debug
These commands build and sync the native projects without launching Xcode, Android Studio, Simulator, or an emulator.
Local Tooling
The default scripts assume the local Homebrew/Xcode paths prepared for this workspace:
- Xcode:
/Applications/Xcode.app/Contents/Developer - JDK 21:
/opt/homebrew/opt/openjdk@21 - Android SDK:
/opt/homebrew/share/android-commandlinetools
Override DEVELOPER_DIR, JAVA_HOME, ANDROID_HOME, or ANDROID_SDK_ROOT when using a different local setup.
Required local tools:
- Xcode with iOS Simulator support.
- CocoaPods for iOS dependency installation.
- JDK 21 for Android Gradle builds.
- Android SDK command-line tools with platform/build-tools 35.
Troubleshooting
- If
xcodebuildreports that the active developer directory is Command Line Tools, keep using the provided scripts or setDEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer. - If Android builds fail with
Unable to locate a Java Runtimeorsource release: 21, install/use JDK 21 and setJAVA_HOMEaccordingly. - If Android SDK packages are missing, install
platform-tools,platforms;android-35, andbuild-tools;35.0.0, then accept SDK licenses. - If CocoaPods cannot find Capacitor pods after reinstalling dependencies, run
bun installfrom the workspace root, then rerunbun run sync. - If connecting to a remote OpenChamber server fails from the app while
/healthworks in curl, check that the server build includes the packaged-client CORS allowlist forcapacitor://localhostand local dev origins. - If
serve-simpreview says the stream is not producing frames, check the raw MJPEG stream before assuming the simulator stopped. In prior testing the raw stream worked while the browser preview UI stayed stale.
Generated Assets
The native projects currently use Capacitor-generated launcher and splash assets. Replace them before release branding work.