Skip to content

Comments

LiveKit WebRTC migration: replace custom SFU with LiveKit#273

Merged
joelteply merged 14 commits intomainfrom
feature/live-audio-session
Feb 21, 2026
Merged

LiveKit WebRTC migration: replace custom SFU with LiveKit#273
joelteply merged 14 commits intomainfrom
feature/live-audio-session

Conversation

@joelteply
Copy link
Contributor

Summary

  • Replaced custom WebSocket SFU (call_server.rs + mixer.rs + binary frame protocol) with LiveKit WebRTC (encrypted UDP, Opus codec, DTLS-SRTP)
  • LiveKitAgent (Rust): server-side participant that bridges TTS/STT/VAD into LiveKit rooms via NativeAudioSource/NativeVideoSource
  • AudioStreamClient (TypeScript): rewritten to use livekit-client SDK — track.attach() for audio/video, native transcription API
  • Token service: JWT access tokens with metadata-based participant roles (replaces identity string prefixes)
  • Subtitle sync: captions display instantly via data channel, duration matched to actual audio length
  • Procedural video avatars: RGBA→I420 conversion pipeline, 10fps colored circle per persona (proof-of-concept for Bevy upgrade)
  • Hold music + ambient audio: separate LiveKit audio tracks per source

What changed

Before After
Raw PCM over TCP WebSocket Opus over encrypted UDP (WebRTC)
Custom binary frame protocol RTP/RTCP (automatic)
Server-side audio mixing (mixer.rs) LiveKit SFU selective forwarding
Canvas putImageData for video <video srcObject> via track.attach()
No encryption DTLS-SRTP
~3000 lines custom transport ~800 lines LiveKit integration

Key files

  • src/workers/continuum-core/src/voice/livekit_agent.rs — LiveKitAgent (server-side participant)
  • src/widgets/live/AudioStreamClient.ts — LiveKit JS SDK client
  • src/widgets/live/LiveWidget.ts — updated connection flow, video rendering, subtitle sync
  • src/shared/LiveKitTypes.ts — participant metadata types (shared Rust↔TS)
  • src/scripts/install-livekit.sh — LiveKit server binary installer

Test plan

  • Audio E2E: human speaks → VAD → STT → transcription displayed
  • TTS E2E: AI generates speech → NativeAudioSource → browser playback
  • Video: procedural avatar renders in participant grid
  • Subtitles: sync with audio start, duration matches speech length
  • Hold music: plays when participant is alone
  • Multiple personas: each gets own LiveKitAgent with separate tracks

- Fix Kokoro vocab loading: download tokenizer.json (HuggingFace format)
  instead of non-existent vocab.json, parse nested model.vocab section
- Fix TTSRegistry HashMap random iteration: add Vec priority ordering
  so kokoro > edge > piper > orpheus > silence is deterministic
- Fix Silero VAD model path: add workers/models/vad/ as first candidate
- Add max_sentence_frames (312 = ~10s) to prevent 38s+ Whisper chunks
- Change VAD init failure from debug! to warn! for visibility
- Auto-download Kokoro + Silero models in npm start build script
- Clean up stale corrupt vocab.json files on redeploy
- Add Pocket-TTS adapter (Kyutai 117M, Candle) with voice cloning from
  reference audio and 8 preset voices (Les Mis characters)
- Rewrite AIAudioBridge.speak() to use Rust voice/speak-in-call directly:
  one IPC call does synthesis + mixer injection, audio never leaves Rust.
  Eliminates broken TS→Rust→TS→Rust round-trip that caused 300s timeouts.
- Fix IPC base: add 60s request timeout + reject all pending on socket
  close/error (was hanging forever on unresponsive Rust worker)
- Add shared audio_utils module for TTS adapters (normalize, resample)
- Add TTS_MODELS/STT_MODELS centralized model registry in Constants.ts
- Add ModelCapabilities with voice cloning, emotion tags, LoRA trainable flags
- Automated Pocket-TTS voice embeddings + Silero VAD download in deploy script
- Default TTS adapter: pocket (was kokoro)
…ecords)

- PostgresAdapter implementing StorageAdapter trait (outlier B proving the interface)
  - Async-native via deadpool-postgres connection pool (no worker thread)
  - Type-aware parameter coercion: NULL typing, SQLite boolean (0/1) → PG BOOLEAN,
    JSON string → JSONB, numeric coercion via information_schema column introspection
  - Schema-qualified table names for multi-tenant isolation
  - Auto-schema creation from data shape with ALTER TABLE for new columns
  - ON CONFLICT (id) DO NOTHING for idempotent inserts

- MigrationEngine: streaming adapter-to-adapter data transfer
  - Cursor-based pagination (no full dataset in memory)
  - Per-collection atomic progress tracking (lock-free status reads)
  - Pause/resume support via atomic flags
  - Background tokio::spawn with MigrationHandle for non-blocking operation
  - Verify command compares record counts between source and target

- DataModule adapter routing: Arc<dyn StorageAdapter> with connection string parsing
  - postgres:// → PostgresAdapter, file paths → SqliteAdapter
  - Migration commands: start, status, pause, resume, verify, cutover, rollback

- 7 TypeScript migration CLI commands (generator-created)
- 12 Rust tests (7 PG adapter + 5 migration), 947 total lib tests pass

Successfully migrated 822,402/823,480 records (99.87%) from 3.1GB SQLite → 1.6GB Postgres.
1,108 failures = all duplicate key violations (live system writes during migration).
…ngterm.db

CognitionLogger, SentinelEscalationService, and PersonaTaskExecutor were all
writing memories and cognition logs to the shared database.sqlite (129K leaked
records, 811MB). Added static dbHandle registry on CognitionLogger, registered
from Hippocampus on init. All 10 DataCreate, 2 DataList, 2 DataUpdate calls
now route through persona-specific longterm.db handles. Verified: shared DB
counts unchanged, 14 persona DBs now receiving cognition tables.
…llback

ServerConfig.getDatabasePath() now reads DATABASE_URL from config.env first.
If it's a postgres:// URL, returns that (routes to PostgresAdapter in Rust).
Otherwise falls back to SQLite path. DatabaseConfig.ts updated with POSTGRES
constant and routing documentation. config.env gets DATABASE_URL setting.

Verified: Postgres serving all reads/writes, SQLite frozen at migration counts.
Dedicated Postgres cluster on port 5433 with data directory at
~/.continuum/data/postgres/ — all continuum data under one tree.
Migrated from system Postgres (port 5432) via pg_dump/restore.
DatabaseConfig.ts updated with correct connection string.
- Revert DatabaseConfig port 5433→5432 (dedicated cluster was a mistake)
- Add skipCount param to DataList — skips the separate COUNT(*) IPC
  round-trip when callers only need items (not pagination totals)
- Corpus loading (loadCorpusFromORM) now uses skipCount: true,
  eliminating 12 unnecessary COUNT queries at startup (6 personas × 2)
- Created 21 Postgres indexes on commonly queried columns (persona_id,
  room_id, created_at, etc.) — startup count queries 100-3000x faster
- Startup SLOW IPC burst reduced from ~5s to ~1.5s
- Zero SLOW IPC entries after startup (steady state is clean)
…rowser canvas rendering

Binary Frame Protocol (FrameKind discriminator byte):
- 0x01=Audio (PCM16), 0x02=Video (header+pixels), 0x03=AvatarState (JSON)
- Backward compatible: legacy messages without prefix treated as raw audio
- VideoFrameHeader: 16-byte LE header (width, height, pixelFormat, timestamp, sequence)
- VideoPixelFormat enum: RGBA8, NV12, VP8, H264, JPEG (format-agnostic pipe)

Rust call_server:
- CallJoinResult struct replaces unwieldy tuple returns
- video_tx/message_tx broadcast channels for video frames and JSON events
- push_video() with mix-minus (see everyone's video but your own)
- TestPatternGenerator: SMPTE color bars with moving scan line (160x120 @10FPS)
- Auto-starts test video source on call creation (proves plumbing)
- Ambient audio sources (TV/music/background) with never-excluded mix-minus

Browser AudioStreamClient:
- Parses FrameKind byte on incoming binary, routes audio/video/avatar
- Prepends 0x01 on outgoing mic audio
- onVideoFrame callback with decoded header + pixel data
- onAvatarUpdate callback for avatar state JSON

Browser LiveWidget:
- Canvas element renders RGBA8 video frames via putImageData()
- Video display appears above participant grid when stream active
- Pixelated rendering for sharp low-res test pattern

VoiceOrchestrator: gutted turn-taking gating (broadcast-only, no blocking)
Mixer: ambient participant type, is_audio_native filtering
GPT-4o Realtime adapter added (OpenAI Realtime API WebSocket)

961 Rust lib tests + 15 integration tests pass. TypeScript compiles clean.
Subtitle timing:
- AI transcription via data channel (native LiveKit transcription has
  browser resolution issues where participant is null)
- Caption displays immediately on transcription arrival (before IPC relay)
- voice:ai:speech event only extends fade timer, no longer overwrites
  the correctly-timed caption with a late duplicate
- Display name resolved from participants list, not payload UUID
- Default caption timeout 15s (was 5s), adjusted by audio duration

Video avatar pipeline (E2E proof-of-concept):
- RGBA→I420 conversion using BT.601 color space
- Procedural avatar: colored circle on dark background, color from
  identity hash via HSL with fixed saturation/lightness
- NativeVideoSource lazily created on first frame (avoids garbled init)
- Background loop publishes at 10fps per agent
- Browser receives via TrackSubscribed → track.attach() → <video>

Rust cleanup: removed dead publish_native_transcription, prefixed
reserved fields (_audio_track_sid, _event_tx), added display_name
to LiveKitAgent for transcription attribution.
Copilot AI review requested due to automatic review settings February 21, 2026 16:21
@joelteply joelteply merged commit 80918c7 into main Feb 21, 2026
2 of 5 checks passed
@joelteply joelteply deleted the feature/live-audio-session branch February 21, 2026 16:21
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Migrates live voice/video transport from a custom WebSocket SFU to LiveKit WebRTC, adds Rust-side orchestration + updated TS bindings, and introduces initial storage migration command scaffolding plus per-persona DB logging improvements.

Changes:

  • Replace call server/mixer plumbing with LiveKit agent manager and updated IPC/bindings for voice injection, ambient tracks, and STT utilities.
  • Add LiveKit token flow + participant metadata typing shared across TS/Rust, and update live widget styling/rendering expectations.
  • Introduce migration command surface area (start/status/verify/cutover/rollback/pause/resume) and per-persona DB handle routing for cognition/memory writes.

Reviewed changes

Copilot reviewed 141 out of 172 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
src/workers/continuum-core/src/voice/call_server_orchestrator_test.rs Updates test participants to include is_audio_native field
src/workers/continuum-core/src/secrets.rs Broadens env-var patterns that get loaded as secrets
src/workers/continuum-core/src/orm/mod.rs Exposes migration + Postgres adapter modules from ORM
src/workers/continuum-core/src/main.rs Switches server startup from WS call server to LiveKit agent manager
src/workers/continuum-core/src/lib.rs Removes CallManager re-export (call server removed)
src/workers/continuum-core/src/ipc/mod.rs Replaces CallManager plumbing with LiveKitAgentManager in IPC server
src/workers/continuum-core/src/ffi/mod.rs Removes obsolete FFI voice_should_route_to_tts entry point
src/workers/continuum-core/src/audio_constants.rs Adds LiveKit port + dev credential constants
src/workers/continuum-core/build.rs Adds macOS -ObjC link arg for LiveKit WebRTC static libs
src/workers/continuum-core/bindings/test-voice-loop.ts Updates voice loop test to broadcast semantics (no TTS routing check)
src/workers/continuum-core/bindings/modules/voice.ts Extends Voice IPC client API (inject/ambient/STT/transcribe/poll)
src/workers/continuum-core/bindings/modules/base.ts Adds IPC request timeouts + proper pending request rejection
src/workers/continuum-core/bindings/benchmark-voice.ts Removes benchmark for deleted TTS routing check
src/workers/continuum-core/bindings/RustCore.ts Removes FFI binding + wrapper for shouldRouteToTts
src/workers/continuum-core/Cargo.toml Adds LiveKit + Pocket-TTS + Postgres deps
src/workers/Cargo.toml Enables tokio-postgres serde/chrono features
src/widgets/live/public/live-widget.scss Adds video canvas styling + spotlight/participant video visibility rules
src/widgets/live/audio-worklet-processor.js Removes microphone capture worklet (LiveKit client handles capture)
src/widgets/live/audio-playback-worklet.js Removes custom playback worklet (LiveKit handles playback)
src/tests/integration/voice-transcription-relay.test.ts Updates import path to voice orchestrator index entry
src/tests/integration/voice-orchestrator.test.ts Reworks integration tests for broadcast model behavior
src/tests/integration/tts-stt-roundtrip.test.ts Uses central socket-path resolver instead of hardcoded path
src/system/voice/shared/VoiceConfig.ts Derives TTS/STT adapter IDs from centralized model constants + adds helpers
src/system/voice/server/index.ts Ensures TS orchestrator instantiated for event listeners even with Rust routing
src/system/voice/server/VoiceWebSocketHandler.ts Removes setTTSCallback wiring (handled via Rust/LiveKit path)
src/system/voice/server/VoiceOrchestratorRustBridge.ts Reworks Rust bridge: DB user-type lookup + AIAudioBridge registration + broadcast
src/system/user/server/modules/consciousness/PersonaTimeline.ts Suppresses DB events for per-persona timeline writes/updates
src/system/user/server/modules/cognitive/memory/Hippocampus.ts Registers persona DB handle with CognitionLogger; suppresses events on memory writes
src/system/user/server/modules/cognition/CognitionLogger.ts Adds per-persona dbHandle routing + suppressEvents on updates
src/system/user/server/modules/PersonaTaskExecutor.ts Writes memories via data/create to persona DB handle (vs ORM default shared DB)
src/system/user/server/modules/PersonaMessageEvaluator.ts Passes personaId into CognitionLogger plan completion logging
src/system/user/server/PersonaUser.ts Removes TS TTS injection subscription; optimizes corpus load with skipCount
src/system/shared/ModelCapabilities.ts Adds TTS/audio capability types + query helpers
src/system/shared/Constants.ts Centralizes TTS/STT model metadata in TTS_MODELS + STT_MODELS
src/system/sentinel/SentinelEscalationService.ts Writes sentinel memories into persona DB handle via CognitionLogger
src/system/secrets/SecretManager.ts Broadens env-var secret patterns to include secrets/tokens/URLs
src/system/data/config/DatabaseConfig.ts Adds default Postgres connection constant + updated routing docs
src/system/config/ServerConfig.ts Returns Postgres URL if DATABASE_URL is postgres*, else sqlite fallback
src/stdout Adds a committed export artifact file
src/shared/version.ts Bumps version constant
src/shared/generated-command-constants.ts Adds migration command constants
src/shared/audio-constants.json Adds LiveKit constants + dev credentials to generated audio constants
src/shared/LiveKitTypes.ts Adds shared participant role metadata types + helpers
src/shared/AudioConstants.ts Adds LiveKit URL + dev key/secret constants
src/server/generated.ts Regenerates server registry to include migration commands
src/scripts/install-livekit.sh Adds installer script for LiveKit server binary
src/scripts/ensure-config.ts Adds LiveKit config.env template entries
src/package.json Adds LiveKit deps + livekit install script; bumps version
src/markdown Adds a committed report artifact file
src/generator/specs/migration-verify.json Adds generator spec for migration/verify command
src/generator/specs/migration-status.json Adds generator spec for migration/status command
src/generator/specs/migration-start.json Adds generator spec for migration/start command
src/generator/specs/migration-rollback.json Adds generator spec for migration/rollback command
src/generator/specs/migration-resume.json Adds generator spec for migration/resume command
src/generator/specs/migration-pause.json Adds generator spec for migration/pause command
src/generator/specs/migration-cutover.json Adds generator spec for migration/cutover command
src/generator/generate-rust-bindings.ts Runs export tests in release; increases timeout; treats specific SIGSEGV as non-fatal
src/generator/generate-audio-constants.ts Generates LiveKit constants into TS/Rust/audio-constants.json
src/generated-command-schemas.json Regenerates command schemas for migration + skipCount
src/commands/voice/synthesize/server/VoiceSynthesizeServerCommand.ts Derives valid adapters from constants; changes default adapter + voice defaults
src/commands/migration/verify/test/integration/MigrationVerifyIntegration.test.ts Adds generated integration test scaffold for migration/verify
src/commands/migration/verify/shared/MigrationVerifyTypes.ts Adds shared types + executor for migration/verify
src/commands/migration/verify/server/MigrationVerifyServerCommand.ts Forwards migration/verify to Rust IPC
src/commands/migration/verify/package.json Adds command package metadata
src/commands/migration/verify/browser/MigrationVerifyBrowserCommand.ts Adds browser command forwarding to server
src/commands/migration/verify/README.md Adds generated README for migration/verify
src/commands/migration/verify/.npmignore Adds npmignore for command package
src/commands/migration/status/test/integration/MigrationStatusIntegration.test.ts Adds generated integration test scaffold for migration/status
src/commands/migration/status/shared/MigrationStatusTypes.ts Adds shared types + executor for migration/status
src/commands/migration/status/server/MigrationStatusServerCommand.ts Forwards migration/status to Rust IPC
src/commands/migration/status/package.json Adds command package metadata
src/commands/migration/status/browser/MigrationStatusBrowserCommand.ts Adds browser command forwarding to server
src/commands/migration/status/README.md Adds generated README for migration/status
src/commands/migration/status/.npmignore Adds npmignore for command package
src/commands/migration/start/test/integration/MigrationStartIntegration.test.ts Adds generated integration test scaffold for migration/start
src/commands/migration/start/shared/MigrationStartTypes.ts Adds shared types + executor for migration/start
src/commands/migration/start/server/MigrationStartServerCommand.ts Validates params then forwards migration/start to Rust IPC
src/commands/migration/start/package.json Adds command package metadata
src/commands/migration/start/browser/MigrationStartBrowserCommand.ts Adds browser command forwarding to server
src/commands/migration/start/README.md Adds generated README for migration/start
src/commands/migration/start/.npmignore Adds npmignore for command package
src/commands/migration/rollback/test/integration/MigrationRollbackIntegration.test.ts Adds generated integration test scaffold for migration/rollback
src/commands/migration/rollback/shared/MigrationRollbackTypes.ts Adds shared types + executor for migration/rollback
src/commands/migration/rollback/server/MigrationRollbackServerCommand.ts Validates params then forwards migration/rollback to Rust IPC
src/commands/migration/rollback/package.json Adds command package metadata
src/commands/migration/rollback/browser/MigrationRollbackBrowserCommand.ts Adds browser command forwarding to server
src/commands/migration/rollback/README.md Adds generated README for migration/rollback
src/commands/migration/rollback/.npmignore Adds npmignore for command package
src/commands/migration/resume/test/integration/MigrationResumeIntegration.test.ts Adds generated integration test scaffold for migration/resume
src/commands/migration/resume/shared/MigrationResumeTypes.ts Adds shared types + executor for migration/resume
src/commands/migration/resume/server/MigrationResumeServerCommand.ts Forwards migration/resume to Rust IPC
src/commands/migration/resume/package.json Adds command package metadata
src/commands/migration/resume/browser/MigrationResumeBrowserCommand.ts Adds browser command forwarding to server
src/commands/migration/resume/README.md Adds generated README for migration/resume
src/commands/migration/resume/.npmignore Adds npmignore for command package
src/commands/migration/pause/test/integration/MigrationPauseIntegration.test.ts Adds generated integration test scaffold for migration/pause
src/commands/migration/pause/shared/MigrationPauseTypes.ts Adds shared types + executor for migration/pause
src/commands/migration/pause/server/MigrationPauseServerCommand.ts Forwards migration/pause to Rust IPC
src/commands/migration/pause/package.json Adds command package metadata
src/commands/migration/pause/browser/MigrationPauseBrowserCommand.ts Adds browser command forwarding to server
src/commands/migration/pause/README.md Adds generated README for migration/pause
src/commands/migration/pause/.npmignore Adds npmignore for command package
src/commands/migration/cutover/test/integration/MigrationCutoverIntegration.test.ts Adds generated integration test scaffold for migration/cutover
src/commands/migration/cutover/shared/MigrationCutoverTypes.ts Adds shared types + executor for migration/cutover
src/commands/migration/cutover/server/MigrationCutoverServerCommand.ts Validates params then forwards migration/cutover to Rust IPC
src/commands/migration/cutover/package.json Adds command package metadata
src/commands/migration/cutover/browser/MigrationCutoverBrowserCommand.ts Adds browser command forwarding to server
src/commands/migration/cutover/README.md Adds generated README for migration/cutover
src/commands/migration/cutover/.npmignore Adds npmignore for command package
src/commands/data/list/shared/DataListTypes.ts Adds skipCount param to avoid separate COUNT query
src/commands/data/list/server/DataListServerCommand.ts Implements skipCount fast path
src/commands/collaboration/live/transcription/server/CollaborationLiveTranscriptionServerCommand.ts Routes transcriptions through Rust orchestrator + emits directed events
src/commands/collaboration/live/leave/server/LiveLeaveServerCommand.ts Updates orchestrator import to index entry
src/commands/collaboration/live/join/shared/LiveJoinTypes.ts Adds LiveKit token + URL fields to join result
src/commands/collaboration/live/join/server/LiveJoinServerCommand.ts Generates LiveKit token/URL and registers session via orchestrator
src/browser/generated.ts Regenerates browser registry to include migration commands
Files not reviewed (1)
  • src/package-lock.json: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +75 to +77
* LiveKit API secret (dev mode)
*/
export const LIVEKIT_API_SECRET = 'secret';
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bundling a LiveKit API secret into shared TS constants makes it very easy to accidentally ship credentials to the browser or leak them via logs. Even if these are “dev” credentials, the safer pattern is: never define API secrets in any file that can be imported by frontend code; read secrets only on the server (SecretManager/process.env) and ensure the browser only receives a short-lived JWT token.

Suggested change
* LiveKit API secret (dev mode)
*/
export const LIVEKIT_API_SECRET = 'secret';
* LiveKit API secret
*
* NOTE: Intentionally not exported from shared constants.
* Server-side code should read the secret from a secure source
* such as environment variables or a secret manager, and expose
* only short-lived tokens (for example, JWT) to the browser.
*/

Copilot uses AI. Check for mistakes.

// DEBUG: Log what we're returning to browser
console.error(`🎙️ LiveJoin RESULT: callId=${call.id.slice(0, 8)}, existed=${existed}, participants=${call.getActiveParticipants().length}, myParticipant=${myParticipant.displayName}`);
console.error(`🎙️ LiveJoin RESULT: callId=${call.id.slice(0, 8)}, existed=${existed}, participants=${call.getActiveParticipants().length}, myParticipant=${myParticipant.displayName}, livekitToken=${livekitToken.slice(0, 20)}...`);
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logging any portion of the LiveKit JWT access token is risky (it’s a bearer credential until expiry, and logs often end up in external systems). Remove token contents from logs (or log only the fact that a token was generated, plus maybe its TTL/length).

Copilot uses AI. Check for mistakes.
// Piper: sequential queue (blocks Rust event loop ~42s)
response = await this.enqueuePiper(handle, () =>
this.voiceClient.voiceSynthesize(params.text, params.voice || 'af', adapter)
this.voiceClient.voiceSynthesize(params.text, params.voice || 'default', adapter)
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Passing 'default' as the voice fallback is likely invalid for several adapters (e.g., Edge generally expects a concrete voice like en-US-AriaNeural, Kokoro often expects af, etc.). A safer approach is to omit the voice field when not provided (let Rust pick adapter defaults), or choose adapter-specific defaults from TTS_MODELS[...].defaultVoice based on the selected adapter.

Copilot uses AI. Check for mistakes.
// All other adapters: direct Rust IPC (pocket ~3-6s, kokoro ~97ms, edge <200ms, orpheus ~2-5s, silence instant)
response = await this.voiceClient.voiceSynthesize(
params.text, params.voice || 'af', adapter
params.text, params.voice || 'default', adapter
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Passing 'default' as the voice fallback is likely invalid for several adapters (e.g., Edge generally expects a concrete voice like en-US-AriaNeural, Kokoro often expects af, etc.). A safer approach is to omit the voice field when not provided (let Rust pick adapter defaults), or choose adapter-specific defaults from TTS_MODELS[...].defaultVoice based on the selected adapter.

Copilot uses AI. Check for mistakes.
Comment on lines 155 to 158
DataCreate.execute({
dbHandle: this.dbHandles.get(personaId),
collection: COLLECTIONS.COGNITION_STATE_SNAPSHOTS,
data: entityData,
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Several logging calls pass dbHandle: this.dbHandles.get(personaId) which can be undefined. That can cause writes to fail or (worse) fall back to a shared/default database, breaking persona isolation. Consider a guard like: if no dbHandle is registered, skip the write (or queue until registered), and optionally emit a single warning per persona to avoid log spam.

Copilot uses AI. Check for mistakes.
Comment on lines +32 to +41
// Route through Rust VoiceOrchestrator for AI participant notification
const responderIds = await getRustVoiceOrchestrator().onUtterance({
sessionId: params.callSessionId,
speakerId: params.speakerId,
speakerName: params.speakerName,
speakerType: 'human',
transcript: params.transcript,
confidence: params.confidence,
timestamp: params.timestamp,
});
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code path unconditionally routes via getRustVoiceOrchestrator(), which bypasses the USE_RUST_VOICE feature flag exposed by getVoiceOrchestrator(). If the intent is to respect that flag, route through getVoiceOrchestrator().onUtterance(...) (or conditionally call Rust vs TS).

Copilot uses AI. Check for mistakes.
Comment on lines +90 to +94
// during global destructor phase. Treat as success if the signal is SIGSEGV
// and no compilation errors occurred.
if (error.signal === 'SIGSEGV' && !stderr.includes('could not compile') && !stderr.includes('error[')) {
console.log(` ⚠️ WebRTC native cleanup crash (SIGSEGV) — bindings generated OK`);
return true;
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Treating SIGSEGV as success can mask real crashes and lead to silently corrupted/partial outputs. If this is necessary as a workaround, consider additionally verifying the expected bindings artifacts were actually produced/updated (e.g., by checking output files exist and are non-empty), and scope the workaround as narrowly as possible (specific package + known stderr signature).

Suggested change
// during global destructor phase. Treat as success if the signal is SIGSEGV
// and no compilation errors occurred.
if (error.signal === 'SIGSEGV' && !stderr.includes('could not compile') && !stderr.includes('error[')) {
console.log(` ⚠️ WebRTC native cleanup crash (SIGSEGV) — bindings generated OK`);
return true;
// during global destructor phase. Treat as success only for the known
// affected package if the signal is SIGSEGV, no compilation errors
// occurred, and bindings output appears to have been generated.
if (
pkg === 'continuum-core' &&
error.signal === 'SIGSEGV' &&
!stderr.includes('could not compile') &&
!stderr.includes('error[')
) {
const generatedOk =
fs.existsSync(GENERATED_DIR) &&
fs.readdirSync(GENERATED_DIR).length > 0;
if (generatedOk) {
console.log(
` ⚠️ WebRTC native cleanup crash (SIGSEGV) — bindings appear generated; treating as success`,
);
return true;
}
console.error(
` ❌ SIGSEGV occurred and no generated bindings were found in ${GENERATED_DIR}`,
);
return false;

Copilot uses AI. Check for mistakes.
Comment on lines +3 to +6
Exported: 2026-02-21T02:21:24.604Z
Messages: 0

---
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a generated/export artifact rather than source code. If it’s not required for runtime/docs, it should be removed from the repo and added to .gitignore (or moved under a dedicated artifacts/ directory that’s excluded from releases) to avoid noisy diffs and accidental commits.

Suggested change
Exported: 2026-02-21T02:21:24.604Z
Messages: 0
---
This file is a **documentation/placeholder** describing the format of a chat
export used by this project. It is not a live export artifact and should not
contain real exported data, timestamps, or message counts.
## Example format (illustrative only)
Exported: <ISO-8601 timestamp>
Messages: <number>
---
<message data goes here>

Copilot uses AI. Check for mistakes.
Comment on lines +47 to +50
// TODO: Replace with your actual command parameters
const result = await client.commands['Migration Start']({
// Add your required parameters here
// Example: name: 'test-value'
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

migration/start has required parameters (source, target), so this integration test will fail if it’s ever executed. Either provide real parameters (possibly from env vars), or mark/skip the test until it’s implemented so CI doesn’t pick up a guaranteed-failing scaffold.

Suggested change
// TODO: Replace with your actual command parameters
const result = await client.commands['Migration Start']({
// Add your required parameters here
// Example: name: 'test-value'
// Read required parameters from environment variables
const source = process.env.MIGRATION_SOURCE;
const target = process.env.MIGRATION_TARGET;
if (!source || !target) {
console.log(
' ⚠️ Skipping Migration Start command execution: MIGRATION_SOURCE and/or MIGRATION_TARGET env vars are not set',
);
return;
}
const result = await client.commands['Migration Start']({
// Required parameters for migration/start
source,
target,

Copilot uses AI. Check for mistakes.
Comment on lines 67 to 81
/**
* Get main SQLite database file path
* Get main database connection string.
*
* Checks DATABASE_URL first — if set to a postgres:// URL, returns that
* (routes to PostgresAdapter in Rust). Otherwise falls back to the
* SQLite file path at $HOME/.continuum/data/database.sqlite.
*/
getDatabasePath(): string {
const databaseUrl = process.env.DATABASE_URL;
if (databaseUrl && (databaseUrl.startsWith('postgres://') || databaseUrl.startsWith('postgresql://'))) {
return databaseUrl;
}
const dir = this.getDatabaseDir();
return `${dir}/database.sqlite`;
}
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method name getDatabasePath() no longer matches its behavior now that it can return a Postgres URL/connection string. Renaming to something like getDatabaseConnectionString() (or getDatabaseUrlOrPath()) would make call sites clearer and reduce ambiguity.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant