Files
openhuman/.github/workflows/release-packages.yml
T
Workflow config file is invalid. Please check your config file: model.ReadWorkflow: yaml: line 298: could not find expected ':'
cf344facf9 feat(voice): dedicated voice assistance module for STT/TTS (#178)
* 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>
2026-04-01 15:52:52 -07:00

329 lines
14 KiB
YAML

name: Release Packages
# Triggered after the main release.yml publishes a release.
# By the time this fires, macOS (aarch64 + x86_64) and Linux x86_64
# CLI tarballs are already attached to the release by release.yml.
#
# This workflow adds:
# 1. Linux arm64 CLI tarball
# 2. Homebrew tap formula update
# 3. Debian apt repository (gh-pages)
# 4. npm package publish
# 5. Smoke tests for each channel
# 6. One-time backlog issue for future package managers
on:
release:
types: [published]
permissions:
contents: write
pages: write
id-token: write
issues: write
concurrency:
group: release-packages-${{ github.event.release.tag_name }}
cancel-in-progress: false
jobs:
# ────────────────────────────────────────────────────────────────────────────
# 1. Build Linux arm64 CLI tarball (native runner)
# Requires: ubuntu-24.04-arm GitHub-hosted runner (free for public repos).
# If this runner type is unavailable on your plan, replace runs-on with
# ubuntu-22.04 and add: uses: taiki-e/install-action@cross + use
# `cross build --target aarch64-unknown-linux-gnu` instead of plain cargo.
# ────────────────────────────────────────────────────────────────────────────
build-cli-linux-arm64:
name: Build Linux arm64 CLI tarball
runs-on: ubuntu-24.04-arm
steps:
- name: Checkout tag
uses: actions/checkout@v4
with:
ref: ${{ github.event.release.tag_name }}
fetch-depth: 1
submodules: true
- name: Install Rust
uses: dtolnay/rust-toolchain@1.93.0
- name: Cache Cargo
uses: Swatinem/rust-cache@v2
with:
key: linux-arm64-release
- name: Install system dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends \
pkg-config libssl-dev build-essential cmake
- name: Build CLI binary and package tarball
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
OPENHUMAN_SENTRY_DSN: ${{ vars.OPENHUMAN_SENTRY_DSN }}
run: |
cargo build --release --bin openhuman-core
VERSION="${{ github.event.release.tag_name }}"
bash scripts/release/package-cli-tarball.sh \
target/release/openhuman-core \
"${VERSION#v}" \
aarch64-unknown-linux-gnu
# ────────────────────────────────────────────────────────────────────────────
# 2. Update Homebrew tap
# Requires secret: HOMEBREW_TAP_TOKEN (PAT or App token with contents:write
# on tinyhumansai/homebrew-openhuman)
# ────────────────────────────────────────────────────────────────────────────
update-homebrew:
name: Update Homebrew tap formula
runs-on: ubuntu-latest
needs: [build-cli-linux-arm64]
steps:
- name: Checkout main repo (for formula template)
uses: actions/checkout@v4
with:
ref: ${{ github.event.release.tag_name }}
path: src
- name: Checkout Homebrew tap
uses: actions/checkout@v4
with:
repository: tinyhumansai/homebrew-openhuman
token: ${{ secrets.HOMEBREW_TAP_TOKEN }}
path: tap
- name: Update Homebrew formula
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
bash src/scripts/release/update-homebrew.sh \
"${{ github.event.release.tag_name }}" \
src/packages/homebrew/openhuman.rb \
tap
# ────────────────────────────────────────────────────────────────────────────
# 3. Build Debian apt repository and deploy to GitHub Pages
# Requires: APT_SIGNING_KEY (ASCII-armor GPG private key secret)
# APT_SIGNING_KEY_ID (key fingerprint / ID)
# GitHub Pages must be enabled (Settings → Pages → Source: gh-pages branch)
# ────────────────────────────────────────────────────────────────────────────
build-apt-repo:
name: Build apt repository
runs-on: ubuntu-22.04
needs: [build-cli-linux-arm64]
steps:
- name: Checkout tag
uses: actions/checkout@v4
with:
ref: ${{ github.event.release.tag_name }}
fetch-depth: 1
- name: Install apt-repo build tools
run: |
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends \
dpkg-dev apt-utils gnupg2
- name: Import GPG signing key
env:
APT_SIGNING_KEY: ${{ secrets.APT_SIGNING_KEY }}
run: |
echo "$APT_SIGNING_KEY" | gpg --batch --import
gpg --list-secret-keys
- name: Checkout gh-pages branch
uses: actions/checkout@v4
with:
ref: gh-pages
path: gh-pages
fetch-depth: 0
- name: Build .deb packages, apt repo, and deploy to gh-pages
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
APT_SIGNING_KEY_ID: ${{ secrets.APT_SIGNING_KEY_ID }}
run: |
bash scripts/release/build-apt-packages.sh \
"${{ github.event.release.tag_name }}" \
--deploy-gh-pages gh-pages
# ────────────────────────────────────────────────────────────────────────────
# 4. Publish npm package
# Requires secret: NPM_TOKEN (automation token from npmjs.com)
# ────────────────────────────────────────────────────────────────────────────
publish-npm:
name: Publish npm package
runs-on: ubuntu-latest
steps:
- name: Checkout tag
uses: actions/checkout@v4
with:
ref: ${{ github.event.release.tag_name }}
fetch-depth: 1
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 24.x
registry-url: 'https://registry.npmjs.org'
- name: Set version and publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: bash scripts/release/publish-npm.sh "${{ github.event.release.tag_name }}"
# ────────────────────────────────────────────────────────────────────────────
# 5. Smoke test: Homebrew
# ────────────────────────────────────────────────────────────────────────────
smoke-homebrew:
name: Smoke — Homebrew (${{ matrix.os }})
runs-on: ${{ matrix.os }}
needs: [update-homebrew]
continue-on-error: true
strategy:
fail-fast: false
matrix:
os: [macos-latest, ubuntu-22.04]
steps:
- name: Install Homebrew (Linux)
if: runner.os == 'Linux'
run: |
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
echo "/home/linuxbrew/.linuxbrew/bin" >> "$GITHUB_PATH"
- name: Tap and install
run: |
brew tap tinyhumansai/openhuman
brew install openhuman
- name: Smoke test
run: openhuman --version
# ────────────────────────────────────────────────────────────────────────────
# 6. Smoke test: apt
# ────────────────────────────────────────────────────────────────────────────
smoke-apt:
name: Smoke — apt (ubuntu-22.04)
runs-on: ubuntu-22.04
needs: [build-apt-repo]
continue-on-error: true
steps:
- name: Add apt repository
run: |
sudo apt-get install -y --no-install-recommends gnupg2 curl ca-certificates
curl -fsSL https://tinyhumansai.github.io/openhuman/apt/KEY.gpg \
| sudo gpg --dearmor -o /etc/apt/keyrings/openhuman.gpg
echo "deb [signed-by=/etc/apt/keyrings/openhuman.gpg arch=amd64] \
https://tinyhumansai.github.io/openhuman/apt stable main" \
| sudo tee /etc/apt/sources.list.d/openhuman.list
- name: Install and smoke test
run: |
sudo apt-get update
sudo apt-get install -y openhuman
openhuman --version
# ────────────────────────────────────────────────────────────────────────────
# 7. Smoke test: npm
# ────────────────────────────────────────────────────────────────────────────
smoke-npm:
name: Smoke — npm (${{ matrix.os }})
runs-on: ${{ matrix.os }}
needs: [publish-npm]
continue-on-error: true
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
steps:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 24.x
- name: Wait for npm propagation, then install
run: |
VERSION="${{ github.event.release.tag_name }}"
VERSION="${VERSION#v}"
# npm can take up to ~2 min to propagate a new publish
for i in 1 2 3 4 5; do
npm install -g "openhuman@${VERSION}" && break || sleep 30
done
- name: Smoke test
run: openhuman --version
# ────────────────────────────────────────────────────────────────────────────
# 8. File the "future package managers" backlog issue (once ever)
# ────────────────────────────────────────────────────────────────────────────
create-backlog-issue:
name: Create backlog issue (once)
runs-on: ubuntu-latest
steps:
- name: Create issue if it doesn't exist
uses: actions/github-script@v7
with:
script: |
const { owner, repo } = context.repo;
const label = 'distribution-backlog';
const title = '[Backlog] Package manager distribution — next tiers';
// Check for existing open or closed issue with this exact title
const { data: existing } = await github.rest.issues.listForRepo({
owner, repo,
state: 'all',
labels: label,
per_page: 10,
});
if (existing.some(i => i.title === title)) {
core.info('Backlog issue already exists — skipping.');
return;
}
// Ensure the label exists
try {
await github.rest.issues.createLabel({
owner, repo,
name: label,
color: '0075ca',
description: 'Package distribution backlog',
});
} catch (_) { /* label may already exist */ }
const body = `## Summary
Track remaining package manager channels. Each tier reflects expected maintenance commitment from the core team.
## Tier 1 — Official (core team maintains)
- [ ] **npx / pnpm dlx** — zero-install via the npm package already published; document the one-liner: \`npx openhuman@latest\`
- [ ] **Scoop (Windows)** — needs a Windows binary (un-comment the Windows matrix in \`release.yml\` first); add a \`tinyhumansai/scoop-openhuman\` bucket
## Tier 2 — Community-supported (PRs welcome, core team reviews)
- [ ] **AUR (Arch Linux)** — add \`PKGBUILD\` pointing at the GitHub release tarball; list in \`packages/\`
- [ ] **Nix / nixpkgs** — upstream a \`pkgs/tools/openhuman/default.nix\` derivation; document local flake overlay as interim
## Tier 3 — Planned (no timeline)
- [ ] **Snap / Snapcraft** — \`snapcraft.yaml\`, publish to Snap Store
- [ ] **Flatpak** — \`org.tinyhumans.Openhuman.yaml\`, publish to Flathub
- [ ] **WinGet** — manifest in \`microsoft/winget-pkgs\` once Windows binary is stable
## Acceptance criteria
- [ ] Each official channel has a CI smoke test (install + \`openhuman --version\`)
- [ ] Install commands appear in \`docs/install.md\`
- [ ] Checksums shipped for all artifacts
`;
const issue = await github.rest.issues.create({
owner, repo,
title,
body,
labels: [label],
});
core.info(\`Created backlog issue: \${issue.data.html_url}\`);