* ci(release): bake Sentry DSN into shipped tauri bundle
Released builds weren't reporting anything to Sentry. Root cause: the
tauri.conf.json `beforeBuildCommand` re-runs `vite build` inside
`cargo tauri build`. The prior `yarn build` step set `VITE_SENTRY_DSN`
for its own run, but the tauri step did not — so the rebuild produced
a DSN-less `dist/` that overwrote the good one, and the shipped web UI
initialized Sentry with an empty DSN (`initSentry` returns early when
`!SENTRY_DSN`).
Fix:
- `release.yml` / `build-desktop` — declare `VITE_SENTRY_DSN` and
`VITE_DEBUG` on the tauri-build step so the `beforeBuildCommand`
rebuild bakes them into the final bundle.
- `release-packages.yml` / `build-cli-linux-arm64` — guard against a
missing `vars.OPENHUMAN_SENTRY_DSN` so the Linux arm64 CLI tarball
cannot ship without error reporting baked in via `option_env!`.
The core sidecar's `option_env!("OPENHUMAN_SENTRY_DSN")` already gets
the value from the dedicated "Build sidecar core binary" step; the
tauri shell doesn't rebuild it (separate crate, not a workspace dep),
so the baked DSN survives into the bundled installer.
* feat(observability): Sentry release tracking + source maps (#405)
Tags every Sentry event with a canonical release identifier shared by
the frontend and Rust core, uploads source maps so stack traces are
symbolicated in the dashboard, and adds a CLI probe for repeatable
verification of any future release.
Release identifier
openhuman@<semver>[+<short_git_sha>]
- Frontend (`app/src/utils/config.ts::SENTRY_RELEASE`) builds the tag
from `VITE_BUILD_SHA`.
- Core sidecar (`src/main.rs::build_release_tag`) builds the same tag
from `option_env!("OPENHUMAN_BUILD_SHA")`, so events from both
surfaces group under one release. Cargo's fingerprint already tracks
`option_env!` changes.
Environment separation
- Frontend: new `APP_ENVIRONMENT` export (`development` | `staging` |
`production`) derived from `VITE_OPENHUMAN_APP_ENV`, passed to
`Sentry.init`.
- Core: `resolve_environment` honors `OPENHUMAN_APP_ENV` at runtime,
falling back to `debug_assertions` detection.
Source-map upload
- `@sentry/vite-plugin` added as an app devDependency.
- `vite.config.ts` emits source maps unconditionally and registers the
plugin only when `SENTRY_AUTH_TOKEN` is present, so local dev skips
silently. The plugin uploads `dist/**/*.js{,.map}` under the
canonical release name and then deletes the on-disk `.map` files so
they never ship to end users.
CI wiring (`release.yml` + `release-packages.yml`)
- `Build frontend` and `Build and package Tauri app` both receive
`VITE_BUILD_SHA`, `SENTRY_RELEASE`, `SENTRY_AUTH_TOKEN`, `SENTRY_ORG`,
`SENTRY_PROJECT_FRONTEND`. The tauri step needs the same env because
its `beforeBuildCommand` re-runs `vite build`.
- `Build sidecar core binary` receives `OPENHUMAN_BUILD_SHA` so
`option_env!` bakes the short SHA into the release tag.
- `build-cli-linux-arm64` mirrors `OPENHUMAN_BUILD_SHA` and
`OPENHUMAN_APP_ENV` for the arm64 CLI tarball.
Verification support
- New `openhuman sentry-test` CLI subcommand captures an `Error`-level
event against the currently-initialized client, flushes, and prints
the event UUID. Optional `--panic` flag exercises the panic
integration. Requires a DSN resolvable at runtime or baked in at
compile time; exits non-zero otherwise so misconfiguration is loud.
- `src/main.rs` now loads `.env` before `sentry::init`, so a DSN
defined only in the repo-local dotenv file (common dev case) is
honored by the startup-time Sentry client.
Docs
- `docs/sentry.md` covers the release identifier, environment table,
source-map pipeline, required CI variables, and a verification
runbook with troubleshooting tips.
- `.env.example` + `app/.env.example` document the new build-time vars.
The `actions/github-script` block for the backlog-issue step used a JS
template literal whose markdown body lines sat at column 1, outside the
`script: |` literal block. YAML treated those lines as new mapping keys
and rejected the whole file — every run of release-packages.yml failed
at load time with zero jobs executed, so the brew tap, apt repo, npm
publish, linux-arm64 CLI, and smoke tests never ran for recent releases.
- Rewrite the issue body as a string array joined with `\n`, so every
line lives inside the block scalar at the correct indentation.
- Fix stray backslash-escapes around the final `core.info(...)` template
literal that were JS-invalid even before YAML choked on the body.
Validated locally with PyYAML; file now parses cleanly.
* feat(voice): add dedicated voice assistance module for STT/TTS
Extracts speech-to-text (whisper.cpp) and text-to-speech (piper) into a
dedicated `src/openhuman/voice/` domain module with its own RPC namespace
(`openhuman.voice_*`). Adds proactive availability checking via
`voice_status` so the UI can show clear errors when binaries/models are
missing instead of failing silently at transcription time.
- New module: voice/types.rs, voice/ops.rs, voice/schemas.rs, voice/mod.rs
- 4 RPC endpoints: voice_status, voice_transcribe, voice_transcribe_bytes, voice_tts
- 21 unit tests + 1 integration test (json_rpc_e2e)
- Frontend updated to use voice_* endpoints with status check on mode switch
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* style: fix cargo fmt in voice/ops.rs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(e2e): add voice mode integration spec
Tests switching to voice input mode, verifying status check fires,
recording button renders, and switching back to text mode restores
text input. Also checks reply mode toggle visibility.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove unused waitForText import in voice-mode e2e spec
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(voice): in-process whisper engine and LLM post-processing
- Add whisper-rs (0.16) for in-process whisper.cpp inference, eliminating
cold-start latency from subprocess-per-call (~1-3s) to warm inference
(~50ms). Model is loaded once during bootstrap and reused across calls.
Falls back to whisper-cli subprocess if in-process loading fails.
- Add LLM post-processing layer that passes raw transcription through
Ollama to fix grammar, punctuation, and filler words. Accepts optional
conversation context to disambiguate names and technical terms.
Gracefully degrades to raw whisper output if Ollama is unavailable.
- Update voice RPC endpoints with new optional params (context,
skip_cleanup) and return both cleaned text and raw_text.
- Update frontend to pass conversation history as context for voice
transcription cleanup, and update TypeScript interfaces to match.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* style: apply cargo fmt formatting fixes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(build): make whisper-rs optional behind `whisper` feature flag
The whisper-rs crate requires cmake to compile whisper.cpp from source,
which is not available in the CI environment. Move it behind an optional
cargo feature so CI builds succeed without cmake.
The whisper_engine module now compiles as a no-op stub when the feature
is disabled, returning "whisper feature not compiled in" errors. Desktop
builds can opt in with `--features whisper`.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* style: cargo fmt whisper_engine.rs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(ci): make whisper-rs mandatory and install cmake in CI
Revert whisper-rs from optional to mandatory dependency. Add cmake
installation to all CI workflows (build, typecheck, test, release) and
the CI Docker image so whisper-rs can compile whisper.cpp from source.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* style: cargo fmt whisper_engine.rs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address code review findings across voice module
- whisper_engine: validate WAV sample rate (must be 16kHz) and channel
count (1 or 2) before feeding audio to whisper
- speech: offload load_engine and transcribe_in_process to
tokio::task::spawn_blocking to avoid blocking the Tokio runtime
- ops: use RAII guard for WHISPER_BIN env var in test to prevent races
and ensure restore on panic; log temp file cleanup failures instead
of silently ignoring; sanitize paths in debug logs to basenames only
- postprocess: add test for disabled cleanup config returning raw text
- voice-mode.spec: assert failure when neither voice CTA nor
unavailable message appears; make reply mode test runnable in
isolation with auth/nav setup
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* style: apply cargo fmt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Split monolithic inline bash from release.yml and release-packages.yml
into standalone scripts under scripts/release/ for easier debugging
and manual execution.
New scripts:
- bump-version.js: version bumping across package.json/tauri/Cargo
- stage-sidecar.sh: stage + verify sidecar binary for Tauri bundler
- sign-and-notarize-macos.sh: macOS code signing and notarization
- repackage-dmg.sh: re-create and notarize DMG post-signing
- upload-macos-artifacts.sh: re-upload notarized artifacts to release
- package-cli-tarball.sh: package CLI binary into release tarball
- build-linux-arm64.sh: build Linux arm64 CLI tarball
- update-homebrew.sh: render and commit Homebrew formula to tap
- build-apt-packages.sh: build .deb packages and apt repository
- publish-npm.sh: stamp version and publish npm package
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>