Commit Graph
5 Commits
Author SHA1 Message Date
yangyaofei 02ab4136de fix(sync): invalidate bootstrap context when a newer same-directory run starts
## Problem
Review on #3151: with isCurrent reduced to store liveness, a completed
run's context stays current while a forced same-directory rerun starts,
so a late deferred response could commit over the newer run's state.

## Fix
Track a per-directory run sequence. isCurrent captures the sequence at
run start and stays true across settle (deferred recovery pulls still
commit — the original bug) but flips false the moment a newer run for
the same directory begins. Entries are cleared on directory disposal.

## Validation
- New test: after a forced rerun the first context is retired and the
  second is current. 20 pass in child-store.test.ts.
- bun test src/sync/: 540 pass / 15 fail — same 15 pre-existing on main.
- type-check and oxlint clean on authored lines.
2026-08-27 00:30:39 +08:00
yangyaofei cab2f9adf3 fix(sync): keep bootstrap context live for deferred recovery pulls
## Problem
Pending permission requests (and questions, MCP/LSP/VCS status) vanish
permanently after a page reload. The deferred recovery phase in
bootstrapDirectory — the code that re-pulls permission.list after load —
never executed, so the UI had no way to re-render a card for a request
the opencode server still holds. Fixes #3150.

## Root cause
The deferred phase is scheduled via setTimeout(0) guarded by isStale(),
which maps to the pump's isCurrent(). isCurrent required the run token
to still be present in runningBootstraps, but the pump deletes that
token in .finally() as soon as onBootstrap settles — always before the
setTimeout macrotask fires. The guard was therefore deterministically
stale and the phase was dead code (introduced by 85400459).

## Fix
Make isCurrent a store-liveness check — disposed, generation, and store
identity — instead of run-token ownership. The pump never replaces a
running entry for the same directory (queueBootstrap defers via
rerunRequested), so during the run itself this is equivalent. This
mirrors the existing isCurrent contract in session-message-loader.ts.

## Validation
- New regression test in child-store.test.ts fails on main and passes
  with the fix (isCurrent stays true across the post-settle macrotask,
  flips false after teardown).
- bun test src/sync/: 539 pass / 15 fail — the same 15 fail on pristine
  main; the only delta is the new passing test.
- bun run type-check (packages/ui): pass.
- bunx oxlint on both changed files: no findings on authored lines.
2026-08-27 00:21:43 +08:00
yangyaofeiandBohdan Triapitsyn ba95e13b36 feat(tts): add TTS buttons to PlanView and FilesView markdown preview with configurable input mode (#1443)
Add TTS controls to markdown preview surfaces so users can listen to plans
and markdown files directly from the preview toolbar.

Changes:
- Add read-aloud / stop buttons to PlanView markdown preview.
- Add read-aloud / stop buttons to FilesView markdown preview for markdown files.
- Respect the existing showMessageTTSButtons preference in both preview views.
- Add a persisted ttsInputMode setting with sanitized/raw modes.
- Keep sanitized mode as the default for backward compatibility.
- Allow raw markdown only for server TTS providers that can handle markdown.
- Always use sanitized text for browser and macOS say fallback paths.
- Add Voice Settings controls for TTS input mode.
- Add i18n keys for the new preview buttons and setting labels.
- Merge latest main and preserve newer FilesView toolbar/editor changes.

Validation:
- bun test packages/ui/src/stores/useConfigStore.test.ts packages/ui/src/components/chat/message/parts/ToolPart.test.ts packages/web/server/lib/tts/routes.test.js
- bun run type-check
- bun run lint
- git diff --check

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-06-08 19:31:21 +03:00
yangyaofeiandBohdan Triapitsyn a6bf73b245 fix(tts): allow remote custom provider URLs on desktop runtime (#1439)
* feat(tts/stt): add API key support for OpenAI-compatible custom providers

## Problem
Custom (OpenAI-compatible) TTS/STT provider in Voice Settings has no way to
pass an API key or bearer token. Many self-hosted or third-party compatible
servers require authentication, making them unreachable from OpenChamber.

The server-side TTS route already accepts an `apiKey` parameter, but the
frontend never sends it. The STT route hardcodes `'not-required'`.

## Implementation
- Add `openaiCompatibleApiKey` to Zustand config store, persisted to localStorage
- Add API Key input field in VoiceSettings.tsx under the custom provider section
- Wire `openaiCompatibleApiKey` through useServerTTS to the TTS backend
- Add `apiKey` field to AudioStreamConfig for STT, forwarded as X-API-Key header
- Update server STT route to accept and forward X-API-Key to transcribeAudio
- Update stt.js to use client-provided apiKey before falling back to env var

## Files changed
- packages/ui/src/stores/useConfigStore.ts
- packages/ui/src/components/sections/openchamber/VoiceSettings.tsx
- packages/ui/src/hooks/useServerTTS.ts
- packages/ui/src/hooks/useBrowserVoice.ts
- packages/ui/src/lib/voice/audioStreamService.ts
- packages/web/server/lib/tts/routes.js
- packages/web/server/lib/tts/stt.js

* feat(tts/stt): add separate API key support for custom TTS and STT providers

## Problem
Custom (OpenAI-compatible) TTS and STT providers in Voice Settings have no way
to pass API keys. Many self-hosted or third-party compatible servers require
authentication, making them unreachable from OpenChamber Desktop (Electron).

## Implementation
- Add `openaiCompatibleApiKey` for TTS (persisted to localStorage, passed in JSON body)
- Add `sttApiKey` for STT (persisted to localStorage, passed via Authorization: Bearer header)
- Two independent keys: TTS and STT are configured separately
- STT authentication follows OpenAI standard (Authorization: Bearer <token>)
- TTS authentication follows existing pattern (apiKey in JSON body)
- Backend STT route extracts bearer token from Authorization header
- Backend STT service prefers client-provided key over OPENAI_API_KEY env var

## Fixes
- Fixed P1: ConfigStore interface now declares setOpenaiCompatibleApiKey setter
- STT API key is only forwarded when sttProvider === 'server' (not leaked to other providers)

## Files changed (7)
- packages/ui/src/stores/useConfigStore.ts
- packages/ui/src/components/sections/openchamber/VoiceSettings.tsx
- packages/ui/src/hooks/useServerTTS.ts
- packages/ui/src/hooks/useBrowserVoice.ts
- packages/ui/src/lib/voice/audioStreamService.ts
- packages/web/server/lib/tts/routes.js
- packages/web/server/lib/tts/stt.js

* fix: refresh server STT callback when API key changes

* fix(tts): allow remote custom provider URLs on desktop runtime

## Problem
Custom OpenAI-compatible TTS/STT provider URLs are restricted to
localhost addresses only. Remote URLs are silently rejected at the
server boundary, making custom cloud-based TTS/STT providers unusable
in the desktop app.

## Root Cause
`base-url.js` rejects non-localhost URLs unless
`OPENCHAMBER_ALLOW_REMOTE_OPENAI_COMPAT_URLS=true` is set, but this
env var is never set by desktop shells and is not exposed in settings.

## Solution
Use the existing `OPENCHAMBER_RUNTIME` env var (already set to
`'desktop'` by both Electron and Tauri shells) to auto-allow remote
custom URLs on desktop. Web deployments retain SSRF protection by
default. The explicit env var override still works for either direction.

Closes openchamber/openchamber#1438

* fix(tts): respect explicit env var override on desktop runtime

## Problem
The previous implementation used `|| isDesktop` which unconditionally
allowed remote URLs on desktop, making `OPENCHAMBER_ALLOW_REMOTE_OPENAI_COMPAT_URLS=false`
a no-op. The PR description documented this as supported but it didn't work.

## Fix
Changed precedence logic: explicit env var (set or unset) takes absolute
priority in both directions. When no explicit flag exists, desktop runtime
defaults to allowing remote URLs. This lets operators tighten desktop
deployments with env var =false.

## Test
Added test case: desktop + env var =false → remote URLs denied (8 pass, 0 fail).

Addresses PR review: P1 (env var precedence) and P2 (missing deny test).

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-06-08 19:15:54 +03:00
yangyaofeiandBohdan Triapitsyn 06526767a2 feat(tts/stt): add API key support for OpenAI-compatible custom providers (#1361)
* feat(tts/stt): add API key support for OpenAI-compatible custom providers

## Problem
Custom (OpenAI-compatible) TTS/STT provider in Voice Settings has no way to
pass an API key or bearer token. Many self-hosted or third-party compatible
servers require authentication, making them unreachable from OpenChamber.

The server-side TTS route already accepts an `apiKey` parameter, but the
frontend never sends it. The STT route hardcodes `'not-required'`.

## Implementation
- Add `openaiCompatibleApiKey` to Zustand config store, persisted to localStorage
- Add API Key input field in VoiceSettings.tsx under the custom provider section
- Wire `openaiCompatibleApiKey` through useServerTTS to the TTS backend
- Add `apiKey` field to AudioStreamConfig for STT, forwarded as X-API-Key header
- Update server STT route to accept and forward X-API-Key to transcribeAudio
- Update stt.js to use client-provided apiKey before falling back to env var

## Files changed
- packages/ui/src/stores/useConfigStore.ts
- packages/ui/src/components/sections/openchamber/VoiceSettings.tsx
- packages/ui/src/hooks/useServerTTS.ts
- packages/ui/src/hooks/useBrowserVoice.ts
- packages/ui/src/lib/voice/audioStreamService.ts
- packages/web/server/lib/tts/routes.js
- packages/web/server/lib/tts/stt.js

* feat(tts/stt): add separate API key support for custom TTS and STT providers

## Problem
Custom (OpenAI-compatible) TTS and STT providers in Voice Settings have no way
to pass API keys. Many self-hosted or third-party compatible servers require
authentication, making them unreachable from OpenChamber Desktop (Electron).

## Implementation
- Add `openaiCompatibleApiKey` for TTS (persisted to localStorage, passed in JSON body)
- Add `sttApiKey` for STT (persisted to localStorage, passed via Authorization: Bearer header)
- Two independent keys: TTS and STT are configured separately
- STT authentication follows OpenAI standard (Authorization: Bearer <token>)
- TTS authentication follows existing pattern (apiKey in JSON body)
- Backend STT route extracts bearer token from Authorization header
- Backend STT service prefers client-provided key over OPENAI_API_KEY env var

## Fixes
- Fixed P1: ConfigStore interface now declares setOpenaiCompatibleApiKey setter
- STT API key is only forwarded when sttProvider === 'server' (not leaked to other providers)

## Files changed (7)
- packages/ui/src/stores/useConfigStore.ts
- packages/ui/src/components/sections/openchamber/VoiceSettings.tsx
- packages/ui/src/hooks/useServerTTS.ts
- packages/ui/src/hooks/useBrowserVoice.ts
- packages/ui/src/lib/voice/audioStreamService.ts
- packages/web/server/lib/tts/routes.js
- packages/web/server/lib/tts/stt.js

* fix: refresh server STT callback when API key changes

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-05-24 00:58:22 +03:00