Voice Daemon Session-Loop Unification#8308
Conversation
👀 Where to focus your review
Risk level: High — This replaces the entire voice LLM execution path. While all existing call UX behaviors are preserved and the approach was extensively reviewed per-milestone, end-to-end voice call testing is essential before shipping. |
|
Addressed in #8352 |
* feat: add voice -> run/session bridge with streaming event sink and cancellation Co-Authored-By: Claude <noreply@anthropic.com> * fix: scope run abort handles to originating run to prevent stale cancellation (#8238) Co-authored-by: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
) * feat: replace CallOrchestrator with session-backed CallController for voice turns Co-Authored-By: Claude <noreply@anthropic.com> * Fix: Resolve voice turn promise on abort/exception (#8280) * fix: resolve voice turn promise on abort and agent-loop exceptions Co-Authored-By: Claude <noreply@anthropic.com> * fix: increment llmRunVersion in destroy() to prevent post-turn side effects on destroyed controller (#8290) Co-authored-by: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
* feat: enforce guardian context and strict side-effect policy parity for voice turns Co-Authored-By: Claude <noreply@anthropic.com> * fix: auto-deny tool confirmations for non-guardian voice turns to prevent 300s timeout (#8302) Co-authored-by: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
* chore: clean up legacy call orchestrator artifacts and update architecture docs Co-Authored-By: Claude <noreply@anthropic.com> * fix: fix stale Orch participant and duplicate Session declaration in ARCHITECTURE.md Mermaid diagrams (#8307) Co-authored-by: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
…cts (#8398) * fix: reset activatedViaWakeWord flag on voice mode activation failure (#8180) Co-authored-by: Claude <noreply@anthropic.com> * fix: use effective base URL for polling and cancel task on dismiss (#8185) Co-authored-by: Claude <noreply@anthropic.com> * fix: derive segment count from manifest in media diagnostics (#8187) Co-authored-by: Claude <noreply@anthropic.com> * fix: enforce payload size limit on pairing proxy endpoints (#8188) Co-authored-by: Claude <noreply@anthropic.com> * fix: restore approvalConversationGenerator in RuntimeHttpServer (#8189) Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: approved devices race condition and optimistic clear (#8190) Co-authored-by: Claude <noreply@anthropic.com> * fix: move zero-frame check before atomic rename in preprocess (#8191) Co-authored-by: Claude <noreply@anthropic.com> * fix: include context field in map cache config hash (#8192) Co-authored-by: Claude <noreply@anthropic.com> * fix: align lastPairedAt type to Int matching generated IPC types (#8193) Co-authored-by: Claude <noreply@anthropic.com> * improve Gmail OAuth setup UX with auto-detection and clearer messaging (#8194) Rewrite the Google OAuth setup skill prompts to feel like 3 user actions instead of a confusing 10-step process. Auto-detect sign-in completion by polling browser snapshots instead of asking users to confirm manually. Add progress callouts and clearer credential entry instructions. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: reset activatedViaWakeWord flag outside wakeWordEnabled guard (#8195) Co-authored-by: Claude <noreply@anthropic.com> * fix: send deny on PairingApprovalWindow close and supersede (#8196) When the pairing approval window is closed via the X button or superseded by a new request, sends a deny response to the daemon. Prevents iOS devices from hanging in pending state indefinitely. - Track currentPairingRequestId and responseSent flag - Add WindowCloseDelegate (NSWindowDelegate) for X-button close - denyIfNeeded() sends deny for unanswered requests in close() - Set responseSent = true in onDecision before calling close() Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: clear stale pairing overrides during v4 migration (#8197) Co-authored-by: Claude <noreply@anthropic.com> * fix: re-register QR pairing before TTL and handle missing daemon (#8199) Adds a timer to re-register the pairing request before the 5-minute TTL expires, keeping the QR code valid while the sheet is open. Also shows an error when daemon client is unavailable instead of rendering an empty sheet. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: honor task cancellation in QRPairingSheet pairing flow (#8210) Co-authored-by: Claude <noreply@anthropic.com> * fix: enforce Content-Length pre-check in pairing proxy (#8212) Co-authored-by: Claude <noreply@anthropic.com> * fix: roll back optimistic removal in removeApprovedDevice on IPC failure (#8215) Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat: reduce maxTokens default from 64000 to 16000 (#8217) Co-authored-by: Claude <noreply@anthropic.com> * feat: add streamThinking config flag and filter thinking deltas (#8219) Co-authored-by: Claude <noreply@anthropic.com> * fix: skip deny on same-ID retry in PairingApprovalWindow (#8223) Co-authored-by: Claude <noreply@anthropic.com> * fix: update maxTokens schema default and tests to 16000 (#8224) Co-authored-by: Claude <noreply@anthropic.com> * fix: separate iOS override migration and guard macOS cleanup (#8225) iOS: moves override cleanup into its own migratePairingOverridesIfNeeded() with a separate migration key, so existing v4-migrated users still get cleanup. macOS: only runs cleanup when legacy iosPairingUseOverride key is actually present, preserving intentional post-M9 overrides. Addresses feedback from PR #8197. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: avoid QR flicker during refresh and register on daemon connect (#8227) Keeps old QR visible during re-registration by only swapping credentials atomically on HTTP 200 success. Adds .onChange(of: daemonClient) to trigger registration when daemon becomes available after sheet opens. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: handle floating promise and add timeout in title generation (#8228) Co-authored-by: Claude <noreply@anthropic.com> * refactor: deduplicate isPlainObject into shared utility (#8229) Co-authored-by: Claude <noreply@anthropic.com> * perf: add database indexes on memorySegments table (#8230) Co-authored-by: Claude <noreply@anthropic.com> * feat: add backpressure and metrics to session message queue (#8231) Co-authored-by: Claude <noreply@anthropic.com> * perf: cache shell-parsing and risk classification results (#8232) Co-authored-by: Claude <noreply@anthropic.com> * security: add pino log serializer to scrub sensitive data (#8233) Co-authored-by: Claude <noreply@anthropic.com> * refactor: replace unsafe type assertions with proper type guards (#8234) Co-authored-by: Claude <noreply@anthropic.com> * feat: add circuit breaker to gateway runtime client (#8236) Co-authored-by: Claude <noreply@anthropic.com> * refactor: create centralized environment variable registry (#8235) Co-authored-by: Claude <noreply@anthropic.com> * feat: add status indication to macOS menu bar icon (#8237) Co-authored-by: Claude <noreply@anthropic.com> * refactor: replace any types with proper interfaces in test files (#8241) Co-authored-by: Claude <noreply@anthropic.com> * fix: remove iosPairingUseOverride deletion from v4 migration (#8239) Co-authored-by: Claude <noreply@anthropic.com> * fix: remove Porcupine iOS-only SPM dep that breaks Xcode 26.2 build (#8240) Porcupine's SPM package is iOS-only and pulls in ios-voice-processor which uses AVAudioSession (unavailable on macOS). Xcode 26.2 is now strict about this. The wake word engine is currently a stub so removing the dep is safe. Also fixes missing VellumAssistantShared imports and a stale protocol conformance in the wake word files. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * perf: add database indexes on memoryItems table (#8244) Co-authored-by: Claude <noreply@anthropic.com> * fix: add per-surface serialization to prevent race conditions (#8245) Co-authored-by: Claude <noreply@anthropic.com> * feat: source filter for Available Skills + platform catalog API (#8097) * feat: add source filter to Available Skills and fetch catalog from platform API - Add All/Vellum/Community source filter pills to Available Skills tab - Capitalize "Vellum" in empty state, hide community disclaimer for Vellum filter - Show Vellum catalog skills even if installed (with "Installed" label) - Add refresh button to Installed tab empty state - Switch catalog fetch from GitHub raw URL to platform API (/v1/skills/) - Pass platform session token (X-Session-Token) through IPC for authenticated fetch - Regenerate IPC contract with optional sessionToken on SkillsSearchRequest Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: resolve platform token locally and fetch skill content via tar API - Remove sessionToken from IPC contract and Swift chain; the daemon now reads the platform API token from ~/.vellum/platform-token instead of receiving it via IPC messages - SessionTokenManager writes platform token to disk when set/deleted so the daemon can read it for authenticated platform API calls - Implement fetchSkillContent via platform tar API (GET /v1/skills/{id}/) which returns a tar.gz archive; extracts SKILL.md from the tarball with bundled fallback on failure - Add readPlatformToken()/getPlatformTokenPath() to platform.ts Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: add AbortSignal support to tool implementations (#8246) Co-authored-by: Claude <noreply@anthropic.com> * refactor: split http-server.ts into focused middleware and route modules (#8243) Co-authored-by: Claude <noreply@anthropic.com> * fix: handle QR refresh failure and remove dead onChange (#8248) Shows error state after 2 consecutive refresh registration failures instead of silently leaving a stale QR. Removes dead .onChange(of: daemonClient != nil) since daemonClient is always non-nil in production. Addresses feedback from PR #8227. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat: add response style section, trim verbose system prompt sections, strengthen SOUL.md conciseness (#8249) * security: enforce 0o600 permissions on log files (#8252) Co-authored-by: Claude <noreply@anthropic.com> * fix: propagate AbortSignal through permission checking (#8253) Co-authored-by: Claude <noreply@anthropic.com> * feat: complete ingress config schema with validation (#8254) Co-authored-by: Claude <noreply@anthropic.com> * perf: add database indexes on remaining tables (#8255) Co-authored-by: Claude <noreply@anthropic.com> * feat(macos): add secret dev mode toggle (#8226) * feat(macos): add secret dev mode toggle via Assistant ID tap gesture Tap the Assistant ID 7 times in Settings > Advanced to toggle dev mode, similar to Android's developer options. Dev mode gates the Feature Flags editor, Developer (env vars) section, and Platform URL in Connect tab. State persists in UserDefaults. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: default dev mode to enabled in DEBUG builds Uses `UserDefaults.object(forKey:) as? Bool ?? true` so debug builds start with dev mode on, while still respecting an explicit toggle-off. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: reset refresh timer and failure counter on QR retry (#8257) Resets consecutiveRefreshFailures to 0 and restarts the refresh timer when the user clicks Retry after a QR refresh failure. Without this, the QR would silently expire after 5 minutes and the next single failure would immediately re-trigger the error state. Addresses feedback from PR #8248. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * refactor: split lifecycle.ts into focused modules (#8259) Co-authored-by: Claude <noreply@anthropic.com> * feat: make global hotkey configurable in macOS client (#8258) Add keyboard shortcut settings UI, UserDefaults persistence, and dynamic re-registration. Defaults to Cmd+Shift+G. * feat: add structured error serialization to pino logger (#8260) Co-authored-by: Claude <noreply@anthropic.com> * fix: add numeric bounds validation for memory item confidence/importance (#8261) Co-authored-by: Claude <noreply@anthropic.com> * feat: make hardcoded daemon timeout values configurable (#8265) Co-authored-by: Claude <noreply@anthropic.com> * feat: add Zod schema for message metadata validation (#8268) Co-authored-by: Claude <noreply@anthropic.com> * refactor: separate migration code from platform utilities (#8272) Co-authored-by: Claude <noreply@anthropic.com> * refactor: use consistent pino logging throughout migration and startup code (#8273) Co-authored-by: Claude <noreply@anthropic.com> * fix: resolve all CI type-check and lint errors (#8276) - Add missing streamThinking to ThinkingConfigSchema default and test assertions - Fix boolean | undefined return type in contradiction-checker transaction - Wrap media-processing stage handlers to return Promise<void> - Cast Cron through unknown for Record<string, unknown> conversion - Update AgentLoopRun type to accept CheckpointInfo parameter - Add withSurface to ToolSetupContext test stubs - Add sessionId to ToolContext test stubs - Cast input_schema to typed interface for property access in tests - Fix fetch mock casts through unknown for Bun's preconnect property - Remove unused imports/vars and fix lint violations across 14 files Co-authored-by: Claude <noreply@anthropic.com> * feat(macos): add health check indicator to Platform URL row (#8271) * feat(macos): add health check indicator to Platform URL row Show a dynamic status icon on the Platform URL row in Settings > Connect that indicates whether the Vellum platform is reachable. Hits /healthz on appear and displays green checkmark (reachable), red xmark (unreachable), or spinner (checking). Error details shown on hover via tooltip. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: gate platform health check on dev mode Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: move response style directives to SOUL.md, remove buildResponseStyleSection (#8277) * Guardian Cross-Channel Approval UX Polish (#8208) * M1: Backend generative guardian copy + emoji title (#8117) * feat: replace static guardian thread copy with model-generated copy Co-Authored-By: Claude <noreply@anthropic.com> * fix: avoid blocking external channel dispatch on LLM copy generation (#8122) Co-authored-by: Claude <noreply@anthropic.com> * fix: create macOS delivery row before awaiting LLM copy generation (#8127) Co-authored-by: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> * M2: macOS native notification + deep link to thread (#8147) * feat: add macOS native notification + deep link for guardian requests Co-Authored-By: Claude <noreply@anthropic.com> * fix: add questionText to guardian IPC message for notification body (#8162) Co-authored-by: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> * M3: QA hardening + documentation (#8179) * test: add guardian copy generation tests and update architecture docs Co-Authored-By: Claude <noreply@anthropic.com> * test: add dedicated test for real buildFallbackCopy implementation (#8198) Co-authored-by: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> * Fix: reject non-emoji guardian titles from model output (#8247) * fix: validate emoji prefix in generated guardian title Co-Authored-By: Claude <noreply@anthropic.com> * fix: only reject old Guardian question prefix, don't enforce emoji (#8251) Co-authored-by: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> * refactor: remove assistant inbox feature flag gating from desktop UI (#8281) Co-authored-by: Harrison Ngo <harrison@vellum.ai> Co-authored-by: Claude <noreply@anthropic.com> * docs: add provider abstraction and approval resilience rules to AGENTS.md (#8279) Co-authored-by: Claude <noreply@anthropic.com> * refactor: remove assistantInbox config schema and defaults (#8282) Co-authored-by: Harrison Ngo <harrison@vellum.ai> Co-authored-by: Claude <noreply@anthropic.com> * refactor: make ingress ACL enforcement always-on (remove feature flag gating) (#8283) Co-authored-by: Harrison Ngo <harrison@vellum.ai> Co-authored-by: Claude <noreply@anthropic.com> * docs: add rule for agents to keep AGENTS.md up to date (#8284) Co-authored-by: Claude <noreply@anthropic.com> * refactor: remove assistantInbox feature-flag check from ingress ACL enforcement (#8287) Co-authored-by: Harrison Ngo <harrison@vellum.ai> Co-authored-by: Claude <noreply@anthropic.com> * docs: update inbox docs to reflect always-on behavior (#8293) Co-authored-by: Harrison Ngo <harrison@vellum.ai> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: change private to internal for properties accessed from AppDelegate extension (#8294) connectionStatusCancellable, pulseTimer, and pulsePhase are accessed from AppDelegate+MenuBar.swift, which is a separate file. Private restricts access to the declaring file, so these need internal access. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat: increase maxInputTokens from 180k to 200k (#8295) Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: resolve CI type-check and lint errors in ingress config and imports (#8296) Co-authored-by: Claude <noreply@anthropic.com> * fix: use Chrome debugger API for CSP-bypass eval, fix Swift Result<Void,String> build error, add influencer CLI command (#8297) Co-authored-by: marinatrajk <marina@odyseek.com> Co-authored-by: Claude <noreply@anthropic.com> * refactor: remove QuickChat feature (#8300) The QuickChat floating panel had an unfixable input focus bug — the NSPanel couldn't reliably receive keyboard input. Remove the entire feature: panel, view, hotkey monitors, notification category, settings shortcut, and background session helpers. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: remove stale relayPort from storage when port field is cleared (#8301) Co-authored-by: marinatrajk <marina@odyseek.com> Co-authored-by: Claude <noreply@anthropic.com> * fix(macos): use consistent paragraph style for placeholder height measurement (#8303) Co-authored-by: Claude <noreply@anthropic.com> * fix(macos): resolve CI build errors in settings views (#8306) - Remove #if DEBUG guard from SettingsPanelEnvVarsSheet so it compiles in release builds (already gated by dev mode at runtime) - Use explicit .some(true)/.some(false)/.none patterns in SettingsConnectTab switch statements to satisfy exhaustiveness checker Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: make error message bubbles span full chat width (#8312) Error bubbles were constrained to the same 520pt maxWidth as regular messages. Use .infinity for error messages so they fill the available chat area. Co-authored-by: Claude <noreply@anthropic.com> * feat: add Quick Input bar (Cmd+/) (#8309) * feat: add Quick Input bar (Cmd+/) for fast new thread creation Adds a floating Spotlight-style input bar that appears system-wide via Cmd+/ (Carbon RegisterEventHotKey). Type a message, hit Return, and it opens the main window with a new thread containing that message. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address PR review — prevent handler leak, fix multi-monitor, no-flash on submit - Track and remove Carbon event handler on teardown to prevent accumulation - Place Quick Input panel on the screen with the mouse cursor - Skip restoring previous app on submit so main window doesn't flash Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: don't restore previous app on resign-key dismissal Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix(lint): resolve lint errors in influencer client (#8316) - Replace `as any` with proper `Omit<ExtensionCommand, 'id'>` type cast - Change InfluencerProfile nullable fields from `| null` to `| undefined` - Update parseFollowerCount return type from `number | null` to `number | undefined` - Replace all `!== null` checks with `!== undefined` per project convention Co-authored-by: Claude <noreply@anthropic.com> * feat: request 16kHz mono format in AlwaysOnAudioMonitor audio tap (#8318) Co-authored-by: Claude <noreply@anthropic.com> * feat: add PorcupineBinding.swift — dlopen wrapper for Porcupine C API (#8320) Co-authored-by: Claude <noreply@anthropic.com> * feat: bundle Porcupine dylib, model, and keywords in build.sh (#8321) Co-authored-by: Claude <noreply@anthropic.com> * fix: always start daemon HTTP server for iOS pairing (#8322) The daemon's HTTP server (port 7821) is required for iOS pairing — the gateway proxies all iOS traffic through it. Previously this was gated behind the `localHttpEnabled` feature flag, so the daemon never started its HTTP server unless the flag was manually enabled via env var, causing HTTP 502 errors on iOS after QR pairing. Three fixes: - AppDelegate: always set RUNTIME_HTTP_PORT=7821 (remove flag gate) - AssistantCli: always forward RUNTIME_HTTP_PORT to CLI (remove flag gate) - CLI local.ts: add RUNTIME_HTTP_PORT to the daemon env forwarding list (the CLI was building a second minimal env and stripping it out) Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat: replace PorcupineWakeWordEngine stub with real Porcupine C SDK implementation (#8323) Co-authored-by: Claude <noreply@anthropic.com> * feat: add wake word keyword selection and fix APIKeyManager usage (#8324) Co-authored-by: Claude <noreply@anthropic.com> * fix(swift): fix IPC Unix socket protocol and reconnect on cancel (#8325) * fix(swift): widen access control for AppDelegate+MenuBar extension and fix Result<Void, String> error Properties accessed by AppDelegate+MenuBar.swift (in a separate file) need internal access — private restricts to the declaring file. Also wrap registration error strings in a proper Error-conforming type since Result requires its Failure type to conform to Error. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(macos): resolve QR pairing issues with httpPort fallback and LAN gateway - Add resolvedHttpPort computed property that falls back to RUNTIME_HTTP_PORT env var, then default 7821, when daemonClient.httpPort is nil (happens when daemon HTTP server starts after socket connection during Qdrant timeout) - Add effectiveGatewayUrl that falls back to localLanUrl when no cloud gateway is configured, enabling LAN-only pairing - Update canGenerateQR, registration body, and QR payload to use effectiveGatewayUrl instead of gatewayUrl - Update hasGateway check to include LAN availability Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: always start daemon HTTP server for iOS pairing The daemon HTTP server was gated behind RUNTIME_HTTP_PORT env var, which relied on a fragile setenv → getenv → subprocess forwarding chain from the macOS app. ProcessInfo.processInfo.environment is a frozen snapshot from launch, so the env var often wasn't forwarded, causing the HTTP server to never start and iOS pairing requests to fail silently. Three fixes: - getRuntimeHttpPort() now defaults to 7821, so the HTTP server always starts regardless of env var forwarding - Wire setPairingBroadcast so pairing_approval_request IPC messages actually reach the macOS app - Remove conditional guard around HTTP server startup in lifecycle.ts Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(macos): add loading state during token regeneration Show a spinner and disable the QR button while the daemon restarts after bearer token regeneration. Polls the healthz endpoint for up to 30s to avoid showing a dead-end "daemon unreachable" error. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(swift): fix IPC Unix socket protocol and reconnect on cancel Two issues caused the macOS app to disconnect from the daemon after ~1 second and never reconnect: 1. DaemonConnection configured NWProtocolTCP.Options() on a Unix domain socket endpoint. Unix sockets don't use TCP — the invalid protocol stack caused the NWConnection to become unstable and disconnect shortly after connecting, before authentication could complete. 2. The .cancelled state handler didn't call scheduleReconnect() when the connection had already been established (resumed==true). This meant spontaneous disconnects were permanent — unlike .failed which properly triggers auto-reconnect. Without a stable IPC connection, all daemon broadcasts (pairing approval requests, reminders, schedule events, watcher alerts) were silently dropped because there were 0 authenticated sockets. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: use AVAudioConverter for 16kHz resampling instead of custom tap format (#8326) Co-authored-by: Claude <noreply@anthropic.com> * fix: move FRAMEWORKS_DIR definition before Porcupine staleness check in build.sh (#8328) Co-authored-by: Claude <noreply@anthropic.com> * fix: add input validation and handle cleanup in PorcupineBinding (#8327) Co-authored-by: Claude <noreply@anthropic.com> * fix: prevent double dismiss of Quick Input panel on submit (#8315) Add isDismissing guard to prevent re-entrant dismiss calls when showMainWindow causes the panel to resign key while the onSubmit closure also calls dismiss explicitly. Co-authored-by: Claude <noreply@anthropic.com> * Release v0.3.6 (#8329) Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * fix: move wake word callback outside lock and use dynamic frame length (#8331) Co-authored-by: Claude <noreply@anthropic.com> * fix: prevent duplicate audio in AVAudioConverter and use ceil for buffer capacity (#8332) Co-authored-by: Claude <noreply@anthropic.com> * perf: disable LLM reranking for memory recall (#8067) LLM reranking was calling Claude Haiku API on every message to re-score memory candidates, adding ~2.2s latency. The RRF merge already produces a well-ordered list from lexical, semantic, recency, and entity scores. Disabling reranking reduces memory recall from ~2.6s to ~700ms. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix(swift): restore NWProtocolTCP on Unix socket, keep cancelled reconnect (#8340) The previous commit removed NWProtocolTCP.Options() from Unix socket connections, but this protocol has been in use since the original IPC implementation (PR #499) and has been working in production for years. Removing it risks breaking IPC in the v0.3.7 release. Restores the TCP protocol stack while keeping the .cancelled state reconnect improvement (scheduleReconnect on post-connection cancel). Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: add explicit self. for property captures in PorcupineWakeWordEngine (#8337) Swift strict concurrency requires explicit `self.` when referencing properties inside closures. os.Logger string interpolation creates implicit closures, so `keyword` and `sensitivity` need `self.` prefix. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: add explicit self for keyword reference in os.Logger closure (#8342) The release build (universal binary) requires explicit `self.` for property references inside os.Logger string interpolation closures. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: resolve CI failures blocking v0.3.7 release (#8344) - TypeScript: fix setPairingBroadcast type to use ServerMessage instead of loose { type: string; [key: string]: unknown } - Gateway lint: prefix unused vars with _ in whatsapp-deliver test - Swift: add explicit self. for keyword property in closure Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: broadcast pairing approval to HTTP/SSE clients (#8348) When the macOS app uses HTTP transport (localHttpEnabled flag), it connects via SSE instead of the Unix domain socket. Pairing approval requests were only broadcast to IPC socket clients, so HTTP-connected clients never received them. Two changes: 1. RuntimeHttpServer now also publishes pairing events to the AssistantEventHub with assistantId 'self' so SSE subscribers receive them. 2. AssistantEventHub allows events without sessionId (system events) to pass through to all subscribers regardless of their sessionId filter. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * M1: Add Voice -> Run/Session Bridge (#8222) * feat: add voice -> run/session bridge with streaming event sink and cancellation Co-Authored-By: Claude <noreply@anthropic.com> * fix: scope run abort handles to originating run to prevent stale cancellation (#8238) Co-authored-by: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> * M2: Replace CallOrchestrator with Session-Backed Voice Controller (#8275) * feat: replace CallOrchestrator with session-backed CallController for voice turns Co-Authored-By: Claude <noreply@anthropic.com> * Fix: Resolve voice turn promise on abort/exception (#8280) * fix: resolve voice turn promise on abort and agent-loop exceptions Co-Authored-By: Claude <noreply@anthropic.com> * fix: increment llmRunVersion in destroy() to prevent post-turn side effects on destroyed controller (#8290) Co-authored-by: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> * M3: Guardian Context and Side-Effect Policy Parity (#8299) * feat: enforce guardian context and strict side-effect policy parity for voice turns Co-Authored-By: Claude <noreply@anthropic.com> * fix: auto-deny tool confirmations for non-guardian voice turns to prevent 300s timeout (#8302) Co-authored-by: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> * M4: Clean Up Legacy Orchestrator Artifacts + Docs (#8304) * chore: clean up legacy call orchestrator artifacts and update architecture docs Co-Authored-By: Claude <noreply@anthropic.com> * fix: fix stale Orch participant and duplicate Session declaration in ARCHITECTURE.md Mermaid diagrams (#8307) Co-authored-by: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> * fix: address final PR review feedback — move bridge injection, fix opener marker persistence, remove duplicate user message persist Co-Authored-By: Claude <noreply@anthropic.com> * feat(macos): add Restart menu item to status bar menu (#8347) * feat(macos): add Restart menu item to status bar menu Add a Restart option to the menu bar dropdown that stops the daemon, launches a fresh app instance, and terminates the current one. Useful for recovering from stuck states without manually quitting and relaunching. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: gate app termination on successful relaunch in performRestart Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Vellum Assistant <assistant@vellum.ai> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat: add memory degradation banner to component gallery with #F5F3EB background (#8356) Co-authored-by: Claude <noreply@anthropic.com> * Add rename option to thread right-click context menu (#8351) * M1: Add session_rename IPC message to daemon (#8338) * feat: add session_rename IPC message for client-initiated title changes Co-Authored-By: Claude <noreply@anthropic.com> * fix: validate conversation exists before renaming in handleSessionRename (#8341) Co-authored-by: Nicolas Zeeb <all@Nicolass-MacBook-Pro.local> Co-authored-by: Claude <noreply@anthropic.com> --------- Co-authored-by: Nicolas Zeeb <all@Nicolass-MacBook-Pro.local> Co-authored-by: Claude <noreply@anthropic.com> * M2: Add rename option to macOS thread context menu UI (#8346) * feat: add rename option to macOS thread context menu Co-Authored-By: Claude <noreply@anthropic.com> * fix: only show rename option for threads with a daemon session (#8350) Co-authored-by: Nicolas Zeeb <all@Nicolass-MacBook-Pro.local> Co-authored-by: Claude <noreply@anthropic.com> --------- Co-authored-by: Nicolas Zeeb <all@Nicolass-MacBook-Pro.local> Co-authored-by: Claude <noreply@anthropic.com> --------- Co-authored-by: Nicolas Zeeb <all@Nicolass-MacBook-Pro.local> Co-authored-by: Claude <noreply@anthropic.com> * fix: remove redundant conversation_id index on memory_segments (#8357) Co-authored-by: Claude <noreply@anthropic.com> * fix: remove unscoped queue-full error and guard expireStale against exceptions (#8358) Co-authored-by: Claude <noreply@anthropic.com> * fix: preserve statusCode fallback when status is undefined in getErrorStatusCode (#8360) Co-authored-by: Claude <noreply@anthropic.com> * fix: preserve Error name/message/stack in log redaction serializer (#8359) Co-authored-by: Claude <noreply@anthropic.com> * fix: include workingDir and manifestOverride in risk cache key (#8361) Co-authored-by: Claude <noreply@anthropic.com> * fix: remove redundant fingerprint index from memoryItems migration (#8362) Co-authored-by: Claude <noreply@anthropic.com> * fix: add missing legitimate env vars to KNOWN_VELLUM_VARS (#8363) Co-authored-by: Claude <noreply@anthropic.com> * fix: clean up settled surface mutex entries to prevent memory leak (#8364) Co-authored-by: Claude <noreply@anthropic.com> * fix: return immediately on aborted CAPTCHA wait instead of breaking (#8365) Co-authored-by: Claude <noreply@anthropic.com> * fix: rebind menu bar connection observer after client replacement and fix pulse animation (#8366) Co-authored-by: Claude <noreply@anthropic.com> * fix: limit half-open probes to single attempt and propagate CircuitBreakerOpenError (#8367) Co-authored-by: Claude <noreply@anthropic.com> * fix: read daemon timeouts without triggering loadConfig/migration side effects (#8368) Co-authored-by: Claude <noreply@anthropic.com> * fix: enforce log file permissions on existing files at startup (#8371) Co-authored-by: Claude <noreply@anthropic.com> * fix: auto-start SSE on HTTP transport connect for system events (#8370) When using HTTP transport (VELLUM_FLAG_LOCAL_HTTP_ENABLED=1), SSE was only started when MainWindowView.onAppear fired. This meant system events like pairing approval requests were lost if they arrived before the main window appeared. Auto-start the SSE stream immediately after the first health check passes in connect(). MainWindowView.onAppear still calls startSSE() but it's a no-op when the stream is already running. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: throw typed cancellation error from permission checks (#8372) Co-authored-by: Claude <noreply@anthropic.com> * feat: add pre-commit warning when modifying system-prompt.ts (#8354) * feat: add pre-commit warning when modifying system-prompt.ts Adds a non-blocking warning to the pre-commit hook that fires when system-prompt.ts is staged. Reminds contributors to consider whether their change belongs in a skill, IDENTITY.md, SOUL.md, USER.md, LOOKS.md, or skills/ instead of directly in the system prompt. Includes a 3-second pause to ensure the warning is actually read. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: add BOOTSTRAP.md to system-prompt warning file list Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * Release v0.3.7 (#8373) Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * fix: initialize pairing handlers so approval responses are processed (#8374) initPairingHandlers() was defined but never called, leaving pairingStoreRef as null. When macOS sent a pairing_approval_response over IPC, the handler silently returned on the null check (line 29), so approvals never reached the PairingStore — iOS stayed stuck on "Waiting for approval" and the device was never added to the allowlist. Wire up initPairingHandlers() in lifecycle.ts after the HTTP server starts, passing the PairingStore instance and bearer token. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: inject call-control prompt + await barge-in teardown Co-Authored-By: Claude <noreply@anthropic.com> * feat: improve media analysis skill defaults and add best practices (#8385) Update defaults based on real-world usage feedback: keyframe interval 3s→1s, segment duration 20s→15s, skip_dead_time on→off. Add best practices section with broad-vs-targeted map prompt guidance, sample prompt/schema, clip delivery notes, and vision analysis limitations. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: unconditional teardown await + bump run version before barge-in Co-Authored-By: Claude <noreply@anthropic.com> * feat: add holistic Codex review phase to safe-blitz command (#8387) Co-authored-by: Claude <noreply@anthropic.com> * fix: watch http-token file so gateway picks up daemon token changes (#8389) When the daemon restarts and writes a new bearer token to ~/.vellum/http-token, a gateway process that started earlier still holds the stale token. This causes 401 errors for iOS clients that received the new token during pairing. Add a file watcher for http-token (following the existing CredentialWatcher pattern) that refreshes runtimeBearerToken, runtimeProxyBearerToken, and runtimeGatewayOriginSecret in the live config when the file changes. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: drop legacy conversation_id index and remove from Drizzle schema (#8393) Co-authored-by: Claude <noreply@anthropic.com> * fix: unreserve dedup cache entries on CircuitBreakerOpenError before returning 503 (#8394) Co-authored-by: Claude <noreply@anthropic.com> * fix: validate daemon timeout bounds in readDaemonTimeouts (#8395) Co-authored-by: Claude <noreply@anthropic.com> * fix: propagate queue-full status through subagent message path (#8397) Co-authored-by: Claude <noreply@anthropic.com> --------- Co-authored-by: Alex Nork <48630278+alex-nork@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Ashlee Radka <ashleeradka@gmail.com> Co-authored-by: asharma53 <64060709+asharma53@users.noreply.github.com> Co-authored-by: siddseethepalli <siddseethepalli@gmail.com> Co-authored-by: David Vargas Fuertes <vargas@vellum.ai> Co-authored-by: NgoHarrison <harrison.ngo719@gmail.com> Co-authored-by: Harrison Ngo <harrison@vellum.ai> Co-authored-by: Aaron Levin <awlevin@users.noreply.github.com> Co-authored-by: V <vincent@vellum.ai> Co-authored-by: Marina Trajkovska <trajk.marina@gmail.com> Co-authored-by: marinatrajk <marina@odyseek.com> Co-authored-by: vellum-automation[bot] <192048195+vellum-automation[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Jason Zhou <jason@vellum.ai> Co-authored-by: Vellum Assistant <assistant@vellum.ai> Co-authored-by: Tirman Sidhu <tirmansidhu@gmail.com> Co-authored-by: Nick <127171085+ZeebBoyBlue@users.noreply.github.com> Co-authored-by: Nicolas Zeeb <all@Nicolass-MacBook-Pro.local>
* fix: reset activatedViaWakeWord flag on voice mode activation failure (#8180) Co-authored-by: Claude <noreply@anthropic.com> * fix: use effective base URL for polling and cancel task on dismiss (#8185) Co-authored-by: Claude <noreply@anthropic.com> * fix: derive segment count from manifest in media diagnostics (#8187) Co-authored-by: Claude <noreply@anthropic.com> * fix: enforce payload size limit on pairing proxy endpoints (#8188) Co-authored-by: Claude <noreply@anthropic.com> * fix: restore approvalConversationGenerator in RuntimeHttpServer (#8189) Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: approved devices race condition and optimistic clear (#8190) Co-authored-by: Claude <noreply@anthropic.com> * fix: move zero-frame check before atomic rename in preprocess (#8191) Co-authored-by: Claude <noreply@anthropic.com> * fix: include context field in map cache config hash (#8192) Co-authored-by: Claude <noreply@anthropic.com> * fix: align lastPairedAt type to Int matching generated IPC types (#8193) Co-authored-by: Claude <noreply@anthropic.com> * improve Gmail OAuth setup UX with auto-detection and clearer messaging (#8194) Rewrite the Google OAuth setup skill prompts to feel like 3 user actions instead of a confusing 10-step process. Auto-detect sign-in completion by polling browser snapshots instead of asking users to confirm manually. Add progress callouts and clearer credential entry instructions. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: reset activatedViaWakeWord flag outside wakeWordEnabled guard (#8195) Co-authored-by: Claude <noreply@anthropic.com> * fix: send deny on PairingApprovalWindow close and supersede (#8196) When the pairing approval window is closed via the X button or superseded by a new request, sends a deny response to the daemon. Prevents iOS devices from hanging in pending state indefinitely. - Track currentPairingRequestId and responseSent flag - Add WindowCloseDelegate (NSWindowDelegate) for X-button close - denyIfNeeded() sends deny for unanswered requests in close() - Set responseSent = true in onDecision before calling close() Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: clear stale pairing overrides during v4 migration (#8197) Co-authored-by: Claude <noreply@anthropic.com> * fix: re-register QR pairing before TTL and handle missing daemon (#8199) Adds a timer to re-register the pairing request before the 5-minute TTL expires, keeping the QR code valid while the sheet is open. Also shows an error when daemon client is unavailable instead of rendering an empty sheet. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: honor task cancellation in QRPairingSheet pairing flow (#8210) Co-authored-by: Claude <noreply@anthropic.com> * fix: enforce Content-Length pre-check in pairing proxy (#8212) Co-authored-by: Claude <noreply@anthropic.com> * fix: roll back optimistic removal in removeApprovedDevice on IPC failure (#8215) Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat: reduce maxTokens default from 64000 to 16000 (#8217) Co-authored-by: Claude <noreply@anthropic.com> * feat: add streamThinking config flag and filter thinking deltas (#8219) Co-authored-by: Claude <noreply@anthropic.com> * fix: skip deny on same-ID retry in PairingApprovalWindow (#8223) Co-authored-by: Claude <noreply@anthropic.com> * fix: update maxTokens schema default and tests to 16000 (#8224) Co-authored-by: Claude <noreply@anthropic.com> * fix: separate iOS override migration and guard macOS cleanup (#8225) iOS: moves override cleanup into its own migratePairingOverridesIfNeeded() with a separate migration key, so existing v4-migrated users still get cleanup. macOS: only runs cleanup when legacy iosPairingUseOverride key is actually present, preserving intentional post-M9 overrides. Addresses feedback from PR #8197. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: avoid QR flicker during refresh and register on daemon connect (#8227) Keeps old QR visible during re-registration by only swapping credentials atomically on HTTP 200 success. Adds .onChange(of: daemonClient) to trigger registration when daemon becomes available after sheet opens. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: handle floating promise and add timeout in title generation (#8228) Co-authored-by: Claude <noreply@anthropic.com> * refactor: deduplicate isPlainObject into shared utility (#8229) Co-authored-by: Claude <noreply@anthropic.com> * perf: add database indexes on memorySegments table (#8230) Co-authored-by: Claude <noreply@anthropic.com> * feat: add backpressure and metrics to session message queue (#8231) Co-authored-by: Claude <noreply@anthropic.com> * perf: cache shell-parsing and risk classification results (#8232) Co-authored-by: Claude <noreply@anthropic.com> * security: add pino log serializer to scrub sensitive data (#8233) Co-authored-by: Claude <noreply@anthropic.com> * refactor: replace unsafe type assertions with proper type guards (#8234) Co-authored-by: Claude <noreply@anthropic.com> * feat: add circuit breaker to gateway runtime client (#8236) Co-authored-by: Claude <noreply@anthropic.com> * refactor: create centralized environment variable registry (#8235) Co-authored-by: Claude <noreply@anthropic.com> * feat: add status indication to macOS menu bar icon (#8237) Co-authored-by: Claude <noreply@anthropic.com> * refactor: replace any types with proper interfaces in test files (#8241) Co-authored-by: Claude <noreply@anthropic.com> * fix: remove iosPairingUseOverride deletion from v4 migration (#8239) Co-authored-by: Claude <noreply@anthropic.com> * fix: remove Porcupine iOS-only SPM dep that breaks Xcode 26.2 build (#8240) Porcupine's SPM package is iOS-only and pulls in ios-voice-processor which uses AVAudioSession (unavailable on macOS). Xcode 26.2 is now strict about this. The wake word engine is currently a stub so removing the dep is safe. Also fixes missing VellumAssistantShared imports and a stale protocol conformance in the wake word files. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * perf: add database indexes on memoryItems table (#8244) Co-authored-by: Claude <noreply@anthropic.com> * fix: add per-surface serialization to prevent race conditions (#8245) Co-authored-by: Claude <noreply@anthropic.com> * feat: source filter for Available Skills + platform catalog API (#8097) * feat: add source filter to Available Skills and fetch catalog from platform API - Add All/Vellum/Community source filter pills to Available Skills tab - Capitalize "Vellum" in empty state, hide community disclaimer for Vellum filter - Show Vellum catalog skills even if installed (with "Installed" label) - Add refresh button to Installed tab empty state - Switch catalog fetch from GitHub raw URL to platform API (/v1/skills/) - Pass platform session token (X-Session-Token) through IPC for authenticated fetch - Regenerate IPC contract with optional sessionToken on SkillsSearchRequest Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: resolve platform token locally and fetch skill content via tar API - Remove sessionToken from IPC contract and Swift chain; the daemon now reads the platform API token from ~/.vellum/platform-token instead of receiving it via IPC messages - SessionTokenManager writes platform token to disk when set/deleted so the daemon can read it for authenticated platform API calls - Implement fetchSkillContent via platform tar API (GET /v1/skills/{id}/) which returns a tar.gz archive; extracts SKILL.md from the tarball with bundled fallback on failure - Add readPlatformToken()/getPlatformTokenPath() to platform.ts Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: add AbortSignal support to tool implementations (#8246) Co-authored-by: Claude <noreply@anthropic.com> * refactor: split http-server.ts into focused middleware and route modules (#8243) Co-authored-by: Claude <noreply@anthropic.com> * fix: handle QR refresh failure and remove dead onChange (#8248) Shows error state after 2 consecutive refresh registration failures instead of silently leaving a stale QR. Removes dead .onChange(of: daemonClient != nil) since daemonClient is always non-nil in production. Addresses feedback from PR #8227. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat: add response style section, trim verbose system prompt sections, strengthen SOUL.md conciseness (#8249) * security: enforce 0o600 permissions on log files (#8252) Co-authored-by: Claude <noreply@anthropic.com> * fix: propagate AbortSignal through permission checking (#8253) Co-authored-by: Claude <noreply@anthropic.com> * feat: complete ingress config schema with validation (#8254) Co-authored-by: Claude <noreply@anthropic.com> * perf: add database indexes on remaining tables (#8255) Co-authored-by: Claude <noreply@anthropic.com> * feat(macos): add secret dev mode toggle (#8226) * feat(macos): add secret dev mode toggle via Assistant ID tap gesture Tap the Assistant ID 7 times in Settings > Advanced to toggle dev mode, similar to Android's developer options. Dev mode gates the Feature Flags editor, Developer (env vars) section, and Platform URL in Connect tab. State persists in UserDefaults. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: default dev mode to enabled in DEBUG builds Uses `UserDefaults.object(forKey:) as? Bool ?? true` so debug builds start with dev mode on, while still respecting an explicit toggle-off. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: reset refresh timer and failure counter on QR retry (#8257) Resets consecutiveRefreshFailures to 0 and restarts the refresh timer when the user clicks Retry after a QR refresh failure. Without this, the QR would silently expire after 5 minutes and the next single failure would immediately re-trigger the error state. Addresses feedback from PR #8248. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * refactor: split lifecycle.ts into focused modules (#8259) Co-authored-by: Claude <noreply@anthropic.com> * feat: make global hotkey configurable in macOS client (#8258) Add keyboard shortcut settings UI, UserDefaults persistence, and dynamic re-registration. Defaults to Cmd+Shift+G. * feat: add structured error serialization to pino logger (#8260) Co-authored-by: Claude <noreply@anthropic.com> * fix: add numeric bounds validation for memory item confidence/importance (#8261) Co-authored-by: Claude <noreply@anthropic.com> * feat: make hardcoded daemon timeout values configurable (#8265) Co-authored-by: Claude <noreply@anthropic.com> * feat: add Zod schema for message metadata validation (#8268) Co-authored-by: Claude <noreply@anthropic.com> * refactor: separate migration code from platform utilities (#8272) Co-authored-by: Claude <noreply@anthropic.com> * refactor: use consistent pino logging throughout migration and startup code (#8273) Co-authored-by: Claude <noreply@anthropic.com> * fix: resolve all CI type-check and lint errors (#8276) - Add missing streamThinking to ThinkingConfigSchema default and test assertions - Fix boolean | undefined return type in contradiction-checker transaction - Wrap media-processing stage handlers to return Promise<void> - Cast Cron through unknown for Record<string, unknown> conversion - Update AgentLoopRun type to accept CheckpointInfo parameter - Add withSurface to ToolSetupContext test stubs - Add sessionId to ToolContext test stubs - Cast input_schema to typed interface for property access in tests - Fix fetch mock casts through unknown for Bun's preconnect property - Remove unused imports/vars and fix lint violations across 14 files Co-authored-by: Claude <noreply@anthropic.com> * feat(macos): add health check indicator to Platform URL row (#8271) * feat(macos): add health check indicator to Platform URL row Show a dynamic status icon on the Platform URL row in Settings > Connect that indicates whether the Vellum platform is reachable. Hits /healthz on appear and displays green checkmark (reachable), red xmark (unreachable), or spinner (checking). Error details shown on hover via tooltip. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: gate platform health check on dev mode Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: move response style directives to SOUL.md, remove buildResponseStyleSection (#8277) * Guardian Cross-Channel Approval UX Polish (#8208) * M1: Backend generative guardian copy + emoji title (#8117) * feat: replace static guardian thread copy with model-generated copy Co-Authored-By: Claude <noreply@anthropic.com> * fix: avoid blocking external channel dispatch on LLM copy generation (#8122) Co-authored-by: Claude <noreply@anthropic.com> * fix: create macOS delivery row before awaiting LLM copy generation (#8127) Co-authored-by: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> * M2: macOS native notification + deep link to thread (#8147) * feat: add macOS native notification + deep link for guardian requests Co-Authored-By: Claude <noreply@anthropic.com> * fix: add questionText to guardian IPC message for notification body (#8162) Co-authored-by: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> * M3: QA hardening + documentation (#8179) * test: add guardian copy generation tests and update architecture docs Co-Authored-By: Claude <noreply@anthropic.com> * test: add dedicated test for real buildFallbackCopy implementation (#8198) Co-authored-by: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> * Fix: reject non-emoji guardian titles from model output (#8247) * fix: validate emoji prefix in generated guardian title Co-Authored-By: Claude <noreply@anthropic.com> * fix: only reject old Guardian question prefix, don't enforce emoji (#8251) Co-authored-by: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> * refactor: remove assistant inbox feature flag gating from desktop UI (#8281) Co-authored-by: Harrison Ngo <harrison@vellum.ai> Co-authored-by: Claude <noreply@anthropic.com> * docs: add provider abstraction and approval resilience rules to AGENTS.md (#8279) Co-authored-by: Claude <noreply@anthropic.com> * refactor: remove assistantInbox config schema and defaults (#8282) Co-authored-by: Harrison Ngo <harrison@vellum.ai> Co-authored-by: Claude <noreply@anthropic.com> * refactor: make ingress ACL enforcement always-on (remove feature flag gating) (#8283) Co-authored-by: Harrison Ngo <harrison@vellum.ai> Co-authored-by: Claude <noreply@anthropic.com> * docs: add rule for agents to keep AGENTS.md up to date (#8284) Co-authored-by: Claude <noreply@anthropic.com> * refactor: remove assistantInbox feature-flag check from ingress ACL enforcement (#8287) Co-authored-by: Harrison Ngo <harrison@vellum.ai> Co-authored-by: Claude <noreply@anthropic.com> * docs: update inbox docs to reflect always-on behavior (#8293) Co-authored-by: Harrison Ngo <harrison@vellum.ai> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: change private to internal for properties accessed from AppDelegate extension (#8294) connectionStatusCancellable, pulseTimer, and pulsePhase are accessed from AppDelegate+MenuBar.swift, which is a separate file. Private restricts access to the declaring file, so these need internal access. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat: increase maxInputTokens from 180k to 200k (#8295) Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: resolve CI type-check and lint errors in ingress config and imports (#8296) Co-authored-by: Claude <noreply@anthropic.com> * fix: use Chrome debugger API for CSP-bypass eval, fix Swift Result<Void,String> build error, add influencer CLI command (#8297) Co-authored-by: marinatrajk <marina@odyseek.com> Co-authored-by: Claude <noreply@anthropic.com> * refactor: remove QuickChat feature (#8300) The QuickChat floating panel had an unfixable input focus bug — the NSPanel couldn't reliably receive keyboard input. Remove the entire feature: panel, view, hotkey monitors, notification category, settings shortcut, and background session helpers. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: remove stale relayPort from storage when port field is cleared (#8301) Co-authored-by: marinatrajk <marina@odyseek.com> Co-authored-by: Claude <noreply@anthropic.com> * fix(macos): use consistent paragraph style for placeholder height measurement (#8303) Co-authored-by: Claude <noreply@anthropic.com> * fix(macos): resolve CI build errors in settings views (#8306) - Remove #if DEBUG guard from SettingsPanelEnvVarsSheet so it compiles in release builds (already gated by dev mode at runtime) - Use explicit .some(true)/.some(false)/.none patterns in SettingsConnectTab switch statements to satisfy exhaustiveness checker Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: make error message bubbles span full chat width (#8312) Error bubbles were constrained to the same 520pt maxWidth as regular messages. Use .infinity for error messages so they fill the available chat area. Co-authored-by: Claude <noreply@anthropic.com> * feat: add Quick Input bar (Cmd+/) (#8309) * feat: add Quick Input bar (Cmd+/) for fast new thread creation Adds a floating Spotlight-style input bar that appears system-wide via Cmd+/ (Carbon RegisterEventHotKey). Type a message, hit Return, and it opens the main window with a new thread containing that message. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address PR review — prevent handler leak, fix multi-monitor, no-flash on submit - Track and remove Carbon event handler on teardown to prevent accumulation - Place Quick Input panel on the screen with the mouse cursor - Skip restoring previous app on submit so main window doesn't flash Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: don't restore previous app on resign-key dismissal Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix(lint): resolve lint errors in influencer client (#8316) - Replace `as any` with proper `Omit<ExtensionCommand, 'id'>` type cast - Change InfluencerProfile nullable fields from `| null` to `| undefined` - Update parseFollowerCount return type from `number | null` to `number | undefined` - Replace all `!== null` checks with `!== undefined` per project convention Co-authored-by: Claude <noreply@anthropic.com> * feat: request 16kHz mono format in AlwaysOnAudioMonitor audio tap (#8318) Co-authored-by: Claude <noreply@anthropic.com> * feat: add PorcupineBinding.swift — dlopen wrapper for Porcupine C API (#8320) Co-authored-by: Claude <noreply@anthropic.com> * feat: bundle Porcupine dylib, model, and keywords in build.sh (#8321) Co-authored-by: Claude <noreply@anthropic.com> * fix: always start daemon HTTP server for iOS pairing (#8322) The daemon's HTTP server (port 7821) is required for iOS pairing — the gateway proxies all iOS traffic through it. Previously this was gated behind the `localHttpEnabled` feature flag, so the daemon never started its HTTP server unless the flag was manually enabled via env var, causing HTTP 502 errors on iOS after QR pairing. Three fixes: - AppDelegate: always set RUNTIME_HTTP_PORT=7821 (remove flag gate) - AssistantCli: always forward RUNTIME_HTTP_PORT to CLI (remove flag gate) - CLI local.ts: add RUNTIME_HTTP_PORT to the daemon env forwarding list (the CLI was building a second minimal env and stripping it out) Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat: replace PorcupineWakeWordEngine stub with real Porcupine C SDK implementation (#8323) Co-authored-by: Claude <noreply@anthropic.com> * feat: add wake word keyword selection and fix APIKeyManager usage (#8324) Co-authored-by: Claude <noreply@anthropic.com> * fix(swift): fix IPC Unix socket protocol and reconnect on cancel (#8325) * fix(swift): widen access control for AppDelegate+MenuBar extension and fix Result<Void, String> error Properties accessed by AppDelegate+MenuBar.swift (in a separate file) need internal access — private restricts to the declaring file. Also wrap registration error strings in a proper Error-conforming type since Result requires its Failure type to conform to Error. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(macos): resolve QR pairing issues with httpPort fallback and LAN gateway - Add resolvedHttpPort computed property that falls back to RUNTIME_HTTP_PORT env var, then default 7821, when daemonClient.httpPort is nil (happens when daemon HTTP server starts after socket connection during Qdrant timeout) - Add effectiveGatewayUrl that falls back to localLanUrl when no cloud gateway is configured, enabling LAN-only pairing - Update canGenerateQR, registration body, and QR payload to use effectiveGatewayUrl instead of gatewayUrl - Update hasGateway check to include LAN availability Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: always start daemon HTTP server for iOS pairing The daemon HTTP server was gated behind RUNTIME_HTTP_PORT env var, which relied on a fragile setenv → getenv → subprocess forwarding chain from the macOS app. ProcessInfo.processInfo.environment is a frozen snapshot from launch, so the env var often wasn't forwarded, causing the HTTP server to never start and iOS pairing requests to fail silently. Three fixes: - getRuntimeHttpPort() now defaults to 7821, so the HTTP server always starts regardless of env var forwarding - Wire setPairingBroadcast so pairing_approval_request IPC messages actually reach the macOS app - Remove conditional guard around HTTP server startup in lifecycle.ts Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(macos): add loading state during token regeneration Show a spinner and disable the QR button while the daemon restarts after bearer token regeneration. Polls the healthz endpoint for up to 30s to avoid showing a dead-end "daemon unreachable" error. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(swift): fix IPC Unix socket protocol and reconnect on cancel Two issues caused the macOS app to disconnect from the daemon after ~1 second and never reconnect: 1. DaemonConnection configured NWProtocolTCP.Options() on a Unix domain socket endpoint. Unix sockets don't use TCP — the invalid protocol stack caused the NWConnection to become unstable and disconnect shortly after connecting, before authentication could complete. 2. The .cancelled state handler didn't call scheduleReconnect() when the connection had already been established (resumed==true). This meant spontaneous disconnects were permanent — unlike .failed which properly triggers auto-reconnect. Without a stable IPC connection, all daemon broadcasts (pairing approval requests, reminders, schedule events, watcher alerts) were silently dropped because there were 0 authenticated sockets. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: use AVAudioConverter for 16kHz resampling instead of custom tap format (#8326) Co-authored-by: Claude <noreply@anthropic.com> * fix: move FRAMEWORKS_DIR definition before Porcupine staleness check in build.sh (#8328) Co-authored-by: Claude <noreply@anthropic.com> * fix: add input validation and handle cleanup in PorcupineBinding (#8327) Co-authored-by: Claude <noreply@anthropic.com> * fix: prevent double dismiss of Quick Input panel on submit (#8315) Add isDismissing guard to prevent re-entrant dismiss calls when showMainWindow causes the panel to resign key while the onSubmit closure also calls dismiss explicitly. Co-authored-by: Claude <noreply@anthropic.com> * Release v0.3.6 (#8329) Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * fix: move wake word callback outside lock and use dynamic frame length (#8331) Co-authored-by: Claude <noreply@anthropic.com> * fix: prevent duplicate audio in AVAudioConverter and use ceil for buffer capacity (#8332) Co-authored-by: Claude <noreply@anthropic.com> * perf: disable LLM reranking for memory recall (#8067) LLM reranking was calling Claude Haiku API on every message to re-score memory candidates, adding ~2.2s latency. The RRF merge already produces a well-ordered list from lexical, semantic, recency, and entity scores. Disabling reranking reduces memory recall from ~2.6s to ~700ms. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix(swift): restore NWProtocolTCP on Unix socket, keep cancelled reconnect (#8340) The previous commit removed NWProtocolTCP.Options() from Unix socket connections, but this protocol has been in use since the original IPC implementation (PR #499) and has been working in production for years. Removing it risks breaking IPC in the v0.3.7 release. Restores the TCP protocol stack while keeping the .cancelled state reconnect improvement (scheduleReconnect on post-connection cancel). Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: add explicit self. for property captures in PorcupineWakeWordEngine (#8337) Swift strict concurrency requires explicit `self.` when referencing properties inside closures. os.Logger string interpolation creates implicit closures, so `keyword` and `sensitivity` need `self.` prefix. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: add explicit self for keyword reference in os.Logger closure (#8342) The release build (universal binary) requires explicit `self.` for property references inside os.Logger string interpolation closures. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: resolve CI failures blocking v0.3.7 release (#8344) - TypeScript: fix setPairingBroadcast type to use ServerMessage instead of loose { type: string; [key: string]: unknown } - Gateway lint: prefix unused vars with _ in whatsapp-deliver test - Swift: add explicit self. for keyword property in closure Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: broadcast pairing approval to HTTP/SSE clients (#8348) When the macOS app uses HTTP transport (localHttpEnabled flag), it connects via SSE instead of the Unix domain socket. Pairing approval requests were only broadcast to IPC socket clients, so HTTP-connected clients never received them. Two changes: 1. RuntimeHttpServer now also publishes pairing events to the AssistantEventHub with assistantId 'self' so SSE subscribers receive them. 2. AssistantEventHub allows events without sessionId (system events) to pass through to all subscribers regardless of their sessionId filter. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat(macos): add Restart menu item to status bar menu (#8347) * feat(macos): add Restart menu item to status bar menu Add a Restart option to the menu bar dropdown that stops the daemon, launches a fresh app instance, and terminates the current one. Useful for recovering from stuck states without manually quitting and relaunching. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: gate app termination on successful relaunch in performRestart Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Vellum Assistant <assistant@vellum.ai> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat: add memory degradation banner to component gallery with #F5F3EB background (#8356) Co-authored-by: Claude <noreply@anthropic.com> * Add rename option to thread right-click context menu (#8351) * M1: Add session_rename IPC message to daemon (#8338) * feat: add session_rename IPC message for client-initiated title changes Co-Authored-By: Claude <noreply@anthropic.com> * fix: validate conversation exists before renaming in handleSessionRename (#8341) Co-authored-by: Nicolas Zeeb <all@Nicolass-MacBook-Pro.local> Co-authored-by: Claude <noreply@anthropic.com> --------- Co-authored-by: Nicolas Zeeb <all@Nicolass-MacBook-Pro.local> Co-authored-by: Claude <noreply@anthropic.com> * M2: Add rename option to macOS thread context menu UI (#8346) * feat: add rename option to macOS thread context menu Co-Authored-By: Claude <noreply@anthropic.com> * fix: only show rename option for threads with a daemon session (#8350) Co-authored-by: Nicolas Zeeb <all@Nicolass-MacBook-Pro.local> Co-authored-by: Claude <noreply@anthropic.com> --------- Co-authored-by: Nicolas Zeeb <all@Nicolass-MacBook-Pro.local> Co-authored-by: Claude <noreply@anthropic.com> --------- Co-authored-by: Nicolas Zeeb <all@Nicolass-MacBook-Pro.local> Co-authored-by: Claude <noreply@anthropic.com> * fix: remove redundant conversation_id index on memory_segments (#8357) Co-authored-by: Claude <noreply@anthropic.com> * fix: remove unscoped queue-full error and guard expireStale against exceptions (#8358) Co-authored-by: Claude <noreply@anthropic.com> * fix: preserve statusCode fallback when status is undefined in getErrorStatusCode (#8360) Co-authored-by: Claude <noreply@anthropic.com> * fix: preserve Error name/message/stack in log redaction serializer (#8359) Co-authored-by: Claude <noreply@anthropic.com> * fix: include workingDir and manifestOverride in risk cache key (#8361) Co-authored-by: Claude <noreply@anthropic.com> * fix: remove redundant fingerprint index from memoryItems migration (#8362) Co-authored-by: Claude <noreply@anthropic.com> * fix: add missing legitimate env vars to KNOWN_VELLUM_VARS (#8363) Co-authored-by: Claude <noreply@anthropic.com> * fix: clean up settled surface mutex entries to prevent memory leak (#8364) Co-authored-by: Claude <noreply@anthropic.com> * fix: return immediately on aborted CAPTCHA wait instead of breaking (#8365) Co-authored-by: Claude <noreply@anthropic.com> * fix: rebind menu bar connection observer after client replacement and fix pulse animation (#8366) Co-authored-by: Claude <noreply@anthropic.com> * fix: limit half-open probes to single attempt and propagate CircuitBreakerOpenError (#8367) Co-authored-by: Claude <noreply@anthropic.com> * fix: read daemon timeouts without triggering loadConfig/migration side effects (#8368) Co-authored-by: Claude <noreply@anthropic.com> * fix: enforce log file permissions on existing files at startup (#8371) Co-authored-by: Claude <noreply@anthropic.com> * fix: auto-start SSE on HTTP transport connect for system events (#8370) When using HTTP transport (VELLUM_FLAG_LOCAL_HTTP_ENABLED=1), SSE was only started when MainWindowView.onAppear fired. This meant system events like pairing approval requests were lost if they arrived before the main window appeared. Auto-start the SSE stream immediately after the first health check passes in connect(). MainWindowView.onAppear still calls startSSE() but it's a no-op when the stream is already running. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: throw typed cancellation error from permission checks (#8372) Co-authored-by: Claude <noreply@anthropic.com> * feat: add pre-commit warning when modifying system-prompt.ts (#8354) * feat: add pre-commit warning when modifying system-prompt.ts Adds a non-blocking warning to the pre-commit hook that fires when system-prompt.ts is staged. Reminds contributors to consider whether their change belongs in a skill, IDENTITY.md, SOUL.md, USER.md, LOOKS.md, or skills/ instead of directly in the system prompt. Includes a 3-second pause to ensure the warning is actually read. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: add BOOTSTRAP.md to system-prompt warning file list Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * Release v0.3.7 (#8373) Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * fix: initialize pairing handlers so approval responses are processed (#8374) initPairingHandlers() was defined but never called, leaving pairingStoreRef as null. When macOS sent a pairing_approval_response over IPC, the handler silently returned on the null check (line 29), so approvals never reached the PairingStore — iOS stayed stuck on "Waiting for approval" and the device was never added to the allowlist. Wire up initPairingHandlers() in lifecycle.ts after the HTTP server starts, passing the PairingStore instance and bearer token. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat: improve media analysis skill defaults and add best practices (#8385) Update defaults based on real-world usage feedback: keyframe interval 3s→1s, segment duration 20s→15s, skip_dead_time on→off. Add best practices section with broad-vs-targeted map prompt guidance, sample prompt/schema, clip delivery notes, and vision analysis limitations. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add holistic Codex review phase to safe-blitz command (#8387) Co-authored-by: Claude <noreply@anthropic.com> * fix: watch http-token file so gateway picks up daemon token changes (#8389) When the daemon restarts and writes a new bearer token to ~/.vellum/http-token, a gateway process that started earlier still holds the stale token. This causes 401 errors for iOS clients that received the new token during pairing. Add a file watcher for http-token (following the existing CredentialWatcher pattern) that refreshes runtimeBearerToken, runtimeProxyBearerToken, and runtimeGatewayOriginSecret in the live config when the file changes. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: drop legacy conversation_id index and remove from Drizzle schema (#8393) Co-authored-by: Claude <noreply@anthropic.com> * fix: unreserve dedup cache entries on CircuitBreakerOpenError before returning 503 (#8394) Co-authored-by: Claude <noreply@anthropic.com> * fix: validate daemon timeout bounds in readDaemonTimeouts (#8395) Co-authored-by: Claude <noreply@anthropic.com> * fix: propagate queue-full status through subagent message path (#8397) Co-authored-by: Claude <noreply@anthropic.com> * fix: respect env var precedence in http-token watcher (#8399) Address review feedback on #8389: 1. Skip file-based token refresh when RUNTIME_BEARER_TOKEN env var is set, preserving the same precedence as loadConfig() where env vars override the file. Prevents cloud deployments with pinned tokens from being silently overwritten by daemon file writes. 2. Wrap mkdirSync in try-catch so a non-writable parent directory doesn't crash the gateway at startup — the watcher is gracefully skipped instead. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat: add queue-if-busy behavior and hub publishing to POST /v1/messages (#8391) (#8400) Co-authored-by: Claude <noreply@anthropic.com> --------- Co-authored-by: Alex Nork <48630278+alex-nork@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Ashlee Radka <ashleeradka@gmail.com> Co-authored-by: asharma53 <64060709+asharma53@users.noreply.github.com> Co-authored-by: siddseethepalli <siddseethepalli@gmail.com> Co-authored-by: David Vargas Fuertes <vargas@vellum.ai> Co-authored-by: NgoHarrison <harrison.ngo719@gmail.com> Co-authored-by: Harrison Ngo <harrison@vellum.ai> Co-authored-by: Aaron Levin <awlevin@users.noreply.github.com> Co-authored-by: V <vincent@vellum.ai> Co-authored-by: Marina Trajkovska <trajk.marina@gmail.com> Co-authored-by: marinatrajk <marina@odyseek.com> Co-authored-by: vellum-automation[bot] <192048195+vellum-automation[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Jason Zhou <jason@vellum.ai> Co-authored-by: Vellum Assistant <assistant@vellum.ai> Co-authored-by: Tirman Sidhu <tirmansidhu@gmail.com> Co-authored-by: Nick <127171085+ZeebBoyBlue@users.noreply.github.com> Co-authored-by: Nicolas Zeeb <all@Nicolass-MacBook-Pro.local>
b25a7e7 to
80fd4d2
Compare
| const persistedContent = opts.content === CALL_OPENING_MARKER | ||
| ? '(call connected — deliver opening greeting)' |
There was a problem hiding this comment.
🟡 [CALL_OPENING] marker replaced before reaching LLM, but voiceCallControlPrompt still references it as a trigger
In voice-session-bridge.ts, the [CALL_OPENING] marker is replaced with benign text '(call connected — deliver opening greeting)' before being sent to orchestrator.startRun(). This means the LLM never sees [CALL_OPENING] in the user message. However, the voiceCallControlPrompt injected alongside the message still instructs the model to match on [CALL_OPENING].
Root Cause and Impact
The replacement at voice-session-bridge.ts:198-199:
const persistedContent = opts.content === CALL_OPENING_MARKER
? '(call connected — deliver opening greeting)'
: opts.content;Means the persisted (and runtime) user message is '(call connected — deliver opening greeting)', not [CALL_OPENING].
But buildVoiceCallControlPrompt at voice-session-bridge.ts:137-144 generates instructions referencing the original marker:
10. If the latest user turn is [CALL_OPENING], greet the caller warmly and ask how you can help.
(or for outbound: "...briefly introduce yourself once as an assistant, state why you are calling using the Task context, and ask a short permission/check-in question.")
Since the model sees (call connected — deliver opening greeting) — not [CALL_OPENING] — rule 10 won't match as intended. The model will likely still generate a greeting from the descriptive text, but will miss the specific protocol guidance (e.g., "state why you are calling using the Task context", "ask a short permission/check-in question", "vary the wording").
Impact: Opening greeting quality may be degraded for both inbound and outbound calls. The model falls back to general behavior instead of following the specific greeting protocol in rule 10/11.
Prompt for agents
The [CALL_OPENING] marker is replaced with benign text before being sent to the LLM, but the voiceCallControlPrompt rules still reference [CALL_OPENING] as the expected user turn content. Fix this mismatch by either:
1. In voice-session-bridge.ts (line 198-199): Keep [CALL_OPENING] as the content sent to startRun, and instead strip it during session history persistence (similar to how other runtime injections are stripped).
2. OR in voice-session-bridge.ts buildVoiceCallControlPrompt (lines 137-144): Update rules 10 and 11 to reference the replacement text '(call connected — deliver opening greeting)' instead of [CALL_OPENING].
Option 1 is preferred because it preserves the exact marker-matching protocol that the old CallOrchestrator used, while the stripping ensures the marker doesn't leak into persisted history.
Was this helpful? React with 👍 or 👎 to provide feedback.
Summary
Move voice call turn execution off the bespoke
CallOrchestratorLLM loop and onto the same daemon/session pipeline used by other channels. Voice calls now have memory, tools, skills, runtime injections, and guardian context parity with chat channels.Changes
RunOrchestratorto support streaming event sink (VoiceRunEventSink) for real-time TTS token streaming and run cancellation for barge-in. Createdvoice-session-bridge.tsmodule with dependency injection via daemon lifecycle. Run abort handles are scoped to their originating run to prevent stale cancellation.CallOrchestrator(directprovider.sendMessage()loop) with session-backedCallControllerthat routes voice turns through the bridge. Preserved all call UX behavior: control markers, barge-in, state machine, guardian verification.generation_cancelledevents are forwarded to the event sink andturnCompletepromise always settles on abort.sourceChannel: 'voice',turnChannelContext,guardianContext, andforceStrictSideEffectsmatching channel ingress behavior. Non-guardian/unverified voice actors get tool confirmations auto-denied immediately (not 300s timeout).Milestone PRs (merged into feature branch)
Feedback PRs (merged into milestone branches)
Project issue
Closes #8181
Test plan
rg -n "provider.sendMessage\(.*\[\]" assistant/src/callsreturns no active voice turn path call sites[ASK_GUARDIAN]still creates pending question + cross-channel guardian dispatch[END_CALL]still terminates relay/call session correctlyanswerCallandinstructionAPIs still function for active call controllerGenerated with Claude Code