Skip to content

Cherry-pick fixes for v0.6.1#23820

Merged
noanflaherty merged 5 commits into
release/v0.6.1from
cherry-pick/release/v0.6.1
Apr 6, 2026
Merged

Cherry-pick fixes for v0.6.1#23820
noanflaherty merged 5 commits into
release/v0.6.1from
cherry-pick/release/v0.6.1

Conversation

@noanflaherty
Copy link
Copy Markdown
Contributor

@noanflaherty noanflaherty commented Apr 6, 2026

Summary


Open with Devin

noanflaherty and others added 3 commits April 6, 2026 16:10
)

* fix: use VButton for inline surface action buttons

Replace raw Button with manual color functions in InlineSurfaceRouter
with the design system VButton component. The manual buttonForeground
used VColor.auxWhite (always #FFFFFF) against VColor.primaryBase which
resolves to #FDFDFC in dark mode, producing invisible white-on-white
text.

Closes LUM-730

Co-Authored-By: ashlee@vellum.ai <ashlee@vellum.ai>

* fix: replace auxWhite-on-primaryBase with VButton in additional locations

FileUploadSurfaceView: Upload/Cancel buttons used raw Button with
VColor.auxWhite on VColor.primaryBase — white-on-white in dark mode.
Replaced with VButton(.primary) and VButton(.outlined).

JITPermissionView: Permission buttons used the same auxWhite pattern.
Replaced with VButton(.primary/.outlined, isFullWidth: true).

ImproveExperienceStepView: ToS checkbox checkmark used auxWhite on
primaryBase fill. Changed to VColor.contentInset which adapts per
color scheme.

ChatGallerySection: Gallery demo of surface action pills mirrored
the old buggy pattern. Updated to use VButton so the gallery
accurately represents production rendering.

Co-Authored-By: ashlee@vellum.ai <ashlee@vellum.ai>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: ashlee@vellum.ai <ashlee@vellum.ai>
…23811)

* Make dictation engine start non-blocking and improve audio resilience

- Add installTapAndStartAsync to AudioEngineController for non-blocking
  engine start using Swift concurrency (withCheckedContinuation)
- Extract installTapAndStartImpl to share logic between sync/async paths
- Listen for AVAudioEngineConfigurationChange to re-prewarm inputNode
  after Bluetooth device connect/disconnect and AirPods mode switches
- Restructure VoiceInputManager.beginRecording() to show recording UI
  and play activation chime immediately, then start engine async via Task
- Move DictationContextCapture off the critical path: engine starts
  concurrently on its audio queue while context capture runs on main
- Add SFSpeechRecognizer transient unavailability retry (recreate if
  isAvailable returns false after sleep/wake or heavy use)
- Handle edge case where PTT is released before async engine start
  completes (stopRecordingForDictation cleans up directly)

Co-Authored-By: tkheyfets <timur@vellum.ai>

* Tear down engine when async startup outlives recording session

When PTT is released before installTapAndStartAsync completes, the
isRecording guard now stops and removes the tap if the engine started
successfully, preventing the mic path from staying alive with no
active recording session.

Co-Authored-By: tkheyfets <timur@vellum.ai>

* Add recording generation token and gate context capture on start success

Co-Authored-By: tkheyfets <timur@vellum.ai>

* Guard stale teardown against active sessions and gate rewarm on mic auth

Co-Authored-By: tkheyfets <timur@vellum.ai>

* Move context capture to Task.detached to avoid blocking main actor

Co-Authored-By: tkheyfets <timur@vellum.ai>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: tkheyfets <timur@vellum.ai>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fcd93c40a2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +568 to 572
beginRecording()
guard isRecording else { return }
if currentMode == .dictation {
let isVellumFrontmost = NSWorkspace.shared.frontmostApplication?.bundleIdentifier == Bundle.main.bundleIdentifier
if !isVellumFrontmost {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Capture dictation context before starting recording

Starting recording before context capture introduces a race where handleFinalTranscription can run while currentDictationContext is still unset, causing dictation sessions to fall back to the conversation path instead of inserting at the cursor. This is most visible when AX/clipboard context capture is slow (it can block up to seconds) or the utterance is short; users will see recording succeed but the result routed to chat rather than the target app.

Useful? React with 👍 / 👎.

devin-ai-integration Bot and others added 2 commits April 6, 2026 16:28
…stallTap (#23766)

After audio-route changes (Bluetooth, USB mic, AirPods mode switch), the
format cached inside AVAudioInputNode diverges from the engine's actual
hardware format. Both outputFormat(forBus:) and a nil format argument to
installTap resolve to this stale value, causing:

  'Failed to create tap due to format mismatch,
   <AVAudioFormat: 2 ch, 44100 Hz, Float32, deinterleaved>'

Fix: call audioEngine.reset() before re-querying the format, then pass it
explicitly to installTap. This forces the engine to discard its cached graph
state and re-read the hardware, so the tap, node, and engine all agree.

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: tkheyfets <timur@vellum.ai>
…ode conversations (#23824)

* fix: pass transport metadata through POST /v1/messages to enable host environment hints

The HTTP message handler auto-creates conversations without transport
metadata, so applyTransportMetadata() returns early and host environment
hints (hostHomeDir, hostUsername) are never injected into the LLM context.
This causes the assistant to hallucinate the user's home directory path
from their display name instead of using the actual macOS username.

Thread transport metadata from the message request body through
SendMessageDeps.getOrCreateConversation() to the daemon, and send
hostHomeDir/hostUsername from the macOS client in every message request.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: replace dynamic imports with static type imports

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@noanflaherty noanflaherty merged commit 6201e2a into release/v0.6.1 Apr 6, 2026
13 of 14 checks passed
@noanflaherty noanflaherty deleted the cherry-pick/release/v0.6.1 branch April 6, 2026 21:13
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 5 new potential issues.

View 8 additional findings in Devin Review.

Open in Devin Review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔴 stopContinuousRecording missing recognitionTask guard after async engine start refactor

stopContinuousRecording() has the same "stop engine → call endAudio → wait for recognition callback" pattern as stopRecordingForDictation(), but was not updated with the guard recognitionTask != nil else { ... } guard added at VoiceInputManager.swift:911-923.

If stopContinuousRecording() is called before the async engine start completes (i.e. recognitionTask is still nil): (1) hasInstalledTap is false so stopAndRemoveTap() is skipped, (2) recognitionRequest?.endAudio() is called but no recognition task is listening, (3) isRecording stays true since the method intentionally doesn't set it — that's the callback's job. When the async engine task later completes, it sees isRecording == true and recordingGeneration matches (no new session started), so it proceeds to install the tap and create a recognition task. But the request already had endAudio() called — appending audio after endAudio() violates SFSpeechAudioBufferRecognitionRequest's contract, corrupting the audio pipeline. The manager is stuck with isRecording = true and no way to recover.

(Refers to lines 260-277)

Prompt for agents
stopContinuousRecording() in VoiceInputManager.swift:260-277 needs the same guard for recognitionTask == nil that was added to stopRecordingForDictation() at lines 908-923. When beginRecording() now starts the engine asynchronously, there is a window where stopContinuousRecording can be called before the recognition task has been created. In that case, endAudio() is called on a request with no recognition task, isRecording stays true forever, and the async engine completion later creates a recognition task on a request that already had endAudio called.

The fix should mirror the pattern in stopRecordingForDictation: after setting hasInstalledTap = false, add a guard checking recognitionTask != nil. If nil, perform direct cleanup: set recognitionRequest = nil, isRecording = false, reset amplitude state, and return early. Without this, the method relies on a callback that will never arrive.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +744 to +752
guard success else {
log.error("Audio engine failed to start — invalid format or engine error")
self.isRecording = false
self.onRecordingStateChanged?(false)
self.currentDictationContext = nil
self.recognitionRequest = nil
self.overlayWindow.dismiss()
self.resetAudioEngine()
return
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Activation chime plays before engine start, no deactivation chime on engine failure

The activation chime was moved from after successful engine start (old line 738) to before the async engine start (new line 681). If installTapAndStartAsync fails (lines 744-752), the error handling resets isRecording and dismisses the overlay, but never plays a deactivation chime. The user hears the activation sound with no corresponding deactivation, giving no audio feedback that recording failed. In the old synchronous flow, the chime only played after the engine started successfully, so this asymmetry never occurred.

Suggested change
guard success else {
log.error("Audio engine failed to start — invalid format or engine error")
self.isRecording = false
self.onRecordingStateChanged?(false)
self.currentDictationContext = nil
self.recognitionRequest = nil
self.overlayWindow.dismiss()
self.resetAudioEngine()
return
guard success else {
log.error("Audio engine failed to start — invalid format or engine error")
self.isRecording = false
self.onRecordingStateChanged?(false)
self.currentDictationContext = nil
self.recognitionRequest = nil
self.overlayWindow.dismiss()
self.resetAudioEngine()
VoiceFeedback.playDeactivationChime()
return
}
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +168 to 198
private func installTapAndStartImpl(
bufferSize: AVAudioFrameCount,
block: @escaping AVAudioNodeTapBlock
) -> Bool {
let inputNode = audioEngine.inputNode

// Stop, remove any existing tap, and reset the engine so that
// outputFormat(forBus:) returns a value consistent with the
// current hardware — not a stale cache from a previous route.
audioEngine.stop()
inputNode.removeTap(onBus: 0)
audioEngine.reset()

let format = inputNode.outputFormat(forBus: 0)
guard format.channelCount > 0, format.sampleRate > 0 else {
log.error("Invalid audio format — channels: \(format.channelCount), sampleRate: \(format.sampleRate)")
return false
}

inputNode.installTap(onBus: 0, bufferSize: bufferSize, format: format, block: block)

audioEngine.prepare()
do {
try audioEngine.start()
return true
} catch {
log.error("Failed to start audio engine: \(error.localizedDescription)")
inputNode.removeTap(onBus: 0)
inputNode.installTap(onBus: 0, bufferSize: bufferSize, format: nil, block: block)

audioEngine.prepare()
do {
try audioEngine.start()
return true
} catch {
log.error("Failed to start audio engine: \(error.localizedDescription)")
inputNode.removeTap(onBus: 0)
return false
}
return false
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🚩 installTapAndStartImpl now passes explicit format instead of nil — behavioral change affects OpenAIVoiceService

The refactored installTapAndStartImpl (AudioEngineController.swift:187) now passes an explicit format to installTap(onBus:bufferSize:format:block:) instead of the previous nil. This is a semantic change that affects ALL callers, including OpenAIVoiceService.swift:227 which still uses the synchronous installTapAndStart and whose comment at line 224-226 says "Passes nil for format so AVAudioEngine uses its internal hardware format". The new implementation calls audioEngine.reset() before querying the format, which should produce a fresh hardware-consistent format, making the explicit pass equivalent to nil in practice. However, the comment in OpenAIVoiceService is now stale, and any edge case where reset() + outputFormat(forBus:) diverges from what nil would resolve to internally could cause a format mismatch crash.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines 567 to 584
private func captureContextAndBeginRecording() {
beginRecording()
guard isRecording else { return }
if currentMode == .dictation {
let isVellumFrontmost = NSWorkspace.shared.frontmostApplication?.bundleIdentifier == Bundle.main.bundleIdentifier
if !isVellumFrontmost {
currentDictationContext = DictationContextCapture.capture()
let generation = recordingGeneration
Task.detached { [weak self] in
let context = DictationContextCapture.capture()
await MainActor.run { [weak self] in
guard let self else { return }
guard self.isRecording, self.recordingGeneration == generation else { return }
self.currentDictationContext = context
}
}
}
}
beginRecording()
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🚩 Async context capture introduces a race with fast transcription results

The PR moves DictationContextCapture.capture() from synchronous (before recording starts) to a Task.detached that runs concurrently with the async engine start. In captureContextAndBeginRecording() at line 574 and requestPermissionsAndRecord() at line 824, context capture now races with engine initialization and speech recognition.

If the engine starts quickly (pre-warmed) and the user speaks a very short phrase, the recognition could deliver a final result before the context capture task completes and sets self.currentDictationContext. In handleFinalTranscription at VoiceInputManager.swift:843, a nil currentDictationContext causes fallback to conversation mode instead of dictation mode.

In practice this is unlikely: DictationContextCapture.capture() typically completes in milliseconds (AX attribute reads), while speech recognition needs hundreds of milliseconds minimum. However, if the target app is hung (5-second AX timeout at DictationContextCapture.swift:52) or the clipboard fallback fires (up to 500ms at DictationContextCapture.swift:155), the context capture could be significantly delayed. This represents a behavioral change from the old synchronous-context-first flow.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +574 to +581
Task.detached { [weak self] in
let context = DictationContextCapture.capture()
await MainActor.run { [weak self] in
guard let self else { return }
guard self.isRecording, self.recordingGeneration == generation else { return }
self.currentDictationContext = context
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🚩 DictationContextCapture clipboard fallback running off main thread via Task.detached

DictationContextCapture.capture() is now called from Task.detached (lines 574-581), meaning it runs on an arbitrary background thread. The clipboardSelectedText() method at DictationContextCapture.swift:129 uses NSPasteboard.general (not documented as thread-safe), posts CGEvent keystrokes, and polls RunLoop.current. While CGEvent posting is thread-safe and RunLoop.current works per-thread, NSPasteboard operations from a background thread are technically outside Apple's documented thread-safety guarantees. In practice this is widely done without issues, but worth noting as a potential source of subtle bugs on future macOS versions.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

dvargasfuertes pushed a commit that referenced this pull request Apr 6, 2026
* revert: disable Teleport feature flag by default (#23744) (#23815)

* fix: replace auxWhite-on-primaryBase with VButton across the app (#23802)

* fix: use VButton for inline surface action buttons

Replace raw Button with manual color functions in InlineSurfaceRouter
with the design system VButton component. The manual buttonForeground
used VColor.auxWhite (always #FFFFFF) against VColor.primaryBase which
resolves to #FDFDFC in dark mode, producing invisible white-on-white
text.

Closes LUM-730

Co-Authored-By: ashlee@vellum.ai <ashlee@vellum.ai>

* fix: replace auxWhite-on-primaryBase with VButton in additional locations

FileUploadSurfaceView: Upload/Cancel buttons used raw Button with
VColor.auxWhite on VColor.primaryBase — white-on-white in dark mode.
Replaced with VButton(.primary) and VButton(.outlined).

JITPermissionView: Permission buttons used the same auxWhite pattern.
Replaced with VButton(.primary/.outlined, isFullWidth: true).

ImproveExperienceStepView: ToS checkbox checkmark used auxWhite on
primaryBase fill. Changed to VColor.contentInset which adapts per
color scheme.

ChatGallerySection: Gallery demo of surface action pills mirrored
the old buggy pattern. Updated to use VButton so the gallery
accurately represents production rendering.

Co-Authored-By: ashlee@vellum.ai <ashlee@vellum.ai>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: ashlee@vellum.ai <ashlee@vellum.ai>

* Make dictation engine start non-blocking with audio route resilience (#23811)

* Make dictation engine start non-blocking and improve audio resilience

- Add installTapAndStartAsync to AudioEngineController for non-blocking
  engine start using Swift concurrency (withCheckedContinuation)
- Extract installTapAndStartImpl to share logic between sync/async paths
- Listen for AVAudioEngineConfigurationChange to re-prewarm inputNode
  after Bluetooth device connect/disconnect and AirPods mode switches
- Restructure VoiceInputManager.beginRecording() to show recording UI
  and play activation chime immediately, then start engine async via Task
- Move DictationContextCapture off the critical path: engine starts
  concurrently on its audio queue while context capture runs on main
- Add SFSpeechRecognizer transient unavailability retry (recreate if
  isAvailable returns false after sleep/wake or heavy use)
- Handle edge case where PTT is released before async engine start
  completes (stopRecordingForDictation cleans up directly)

Co-Authored-By: tkheyfets <timur@vellum.ai>

* Tear down engine when async startup outlives recording session

When PTT is released before installTapAndStartAsync completes, the
isRecording guard now stops and removes the tap if the engine started
successfully, preventing the mic path from staying alive with no
active recording session.

Co-Authored-By: tkheyfets <timur@vellum.ai>

* Add recording generation token and gate context capture on start success

Co-Authored-By: tkheyfets <timur@vellum.ai>

* Guard stale teardown against active sessions and gate rewarm on mic auth

Co-Authored-By: tkheyfets <timur@vellum.ai>

* Move context capture to Task.detached to avoid blocking main actor

Co-Authored-By: tkheyfets <timur@vellum.ai>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: tkheyfets <timur@vellum.ai>

* [LUM-681] Fix audio tap format mismatch by resetting engine before installTap (#23766)

After audio-route changes (Bluetooth, USB mic, AirPods mode switch), the
format cached inside AVAudioInputNode diverges from the engine's actual
hardware format. Both outputFormat(forBus:) and a nil format argument to
installTap resolve to this stale value, causing:

  'Failed to create tap due to format mismatch,
   <AVAudioFormat: 2 ch, 44100 Hz, Float32, deinterleaved>'

Fix: call audioEngine.reset() before re-querying the format, then pass it
explicitly to installTap. This forces the engine to discard its cached graph
state and re-read the hardware, so the tap, node, and engine all agree.

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: tkheyfets <timur@vellum.ai>

* fix: pass transport hints through HTTP message endpoint for managed-mode conversations (#23824)

* fix: pass transport metadata through POST /v1/messages to enable host environment hints

The HTTP message handler auto-creates conversations without transport
metadata, so applyTransportMetadata() returns early and host environment
hints (hostHomeDir, hostUsername) are never injected into the LLM context.
This causes the assistant to hallucinate the user's home directory path
from their display name instead of using the actual macOS username.

Thread transport metadata from the message request body through
SendMessageDeps.getOrCreateConversation() to the daemon, and send
hostHomeDir/hostUsername from the macOS client in every message request.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: replace dynamic imports with static type imports

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: ashlee@vellum.ai <ashlee@vellum.ai>
Co-authored-by: tkheyfets <timur@vellum.ai>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
dvargasfuertes pushed a commit that referenced this pull request Apr 6, 2026
* Release v0.6.1

* Cherry-pick fixes for v0.6.1 (#23785)

* Increase teleport import timeout from 2 to 5 minutes (#23749)

* increase teleport import timeout from 2 to 5 minutes

* fix: update platform import timeout error message to say 5 minutes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Update billing tab copy: referral subtitle, remove earning cap note, move credit info to card subtitle (#23751)

* fix(macos): always collapse thinking blocks by default (#23750)

Thinking blocks were auto-expanding during streaming, showing a wall of
text. Remove the auto-expand logic so blocks always start collapsed.
Users can still manually expand them. The header already shows
"Thinking..." vs "Thought process" as a streaming indicator.

Closes LUM-729

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: ashlee@vellum.ai <ashlee@vellum.ai>

* [LUM-684/LUM-726] Fix dictation crash: pass nil format to installTap (#23754)

* Fix dictation crash: pass nil format to installTap, consolidate audio engine calls

Pass nil for the format parameter in AVAudioNode.installTap(onBus:bufferSize:format:block:)
so AVAudioEngine uses its own internal hardware format, which is always self-consistent.
This prevents NSInternalInconsistencyException crashes caused by format.sampleRate != hwFormat.sampleRate
when the cached format from outputFormat(forBus:) diverges from the engine's internal hardware
format after audio route changes (Bluetooth, USB mic, AirPods mode switch).

AudioEngineController.swift:
- installTapAndStart() now passes nil instead of explicit format to installTap
- Removed 6 now-unused methods: inputNodeFormat(), installTap(bufferSize:format:block:),
  removeTap(), prepare(), start(), prepareAndStart()

OpenAIVoiceService.swift:
- startRecording(): replaced separate inputNodeFormat/installTap/prepare/start chain
  with single installTapAndStart() call
- startBargeInMonitor(): same migration to installTapAndStart()
- Removed error-path removeTap() call (handled internally by installTapAndStart)

Resolves: LUM-684, LUM-726
Co-Authored-By: tkheyfets <timur@vellum.ai>

* fix: use explicit block: parameter in guard statements for installTapAndStart

Swift doesn't support trailing closure syntax with guard statements,
causing compilation errors. Use explicit block: parameter label instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: tkheyfets <timur@vellum.ai>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: replace contrast buttons with primary style (#23753)

Remove all production usages of .contrast button style in favor of .primary.
Fixes white-on-white button visibility issues in chat composer.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Inject host environment via transport hints (#23779)

* refactor: discriminated union for transport metadata, remove iOS proxy setup (#23776)

* feat: inject interface ID and macOS host environment into transport hints (#23777)

* feat: send hostHomeDir and hostUsername from macOS client (#23778)

* fix: remove iOS from proxy restoration in conversation-process.ts (#23782)

---------

Co-authored-by: Carson Shaar <carson.s.shaar@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: ashlee@vellum.ai <ashlee@vellum.ai>
Co-authored-by: tkheyfets <timur@vellum.ai>
Co-authored-by: Tirman Sidhu <tirmansidhu@gmail.com>

* [skip ci] Cherry-pick fixes for v0.6.1 (#23820)

* revert: disable Teleport feature flag by default (#23744) (#23815)

* fix: replace auxWhite-on-primaryBase with VButton across the app (#23802)

* fix: use VButton for inline surface action buttons

Replace raw Button with manual color functions in InlineSurfaceRouter
with the design system VButton component. The manual buttonForeground
used VColor.auxWhite (always #FFFFFF) against VColor.primaryBase which
resolves to #FDFDFC in dark mode, producing invisible white-on-white
text.

Closes LUM-730

Co-Authored-By: ashlee@vellum.ai <ashlee@vellum.ai>

* fix: replace auxWhite-on-primaryBase with VButton in additional locations

FileUploadSurfaceView: Upload/Cancel buttons used raw Button with
VColor.auxWhite on VColor.primaryBase — white-on-white in dark mode.
Replaced with VButton(.primary) and VButton(.outlined).

JITPermissionView: Permission buttons used the same auxWhite pattern.
Replaced with VButton(.primary/.outlined, isFullWidth: true).

ImproveExperienceStepView: ToS checkbox checkmark used auxWhite on
primaryBase fill. Changed to VColor.contentInset which adapts per
color scheme.

ChatGallerySection: Gallery demo of surface action pills mirrored
the old buggy pattern. Updated to use VButton so the gallery
accurately represents production rendering.

Co-Authored-By: ashlee@vellum.ai <ashlee@vellum.ai>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: ashlee@vellum.ai <ashlee@vellum.ai>

* Make dictation engine start non-blocking with audio route resilience (#23811)

* Make dictation engine start non-blocking and improve audio resilience

- Add installTapAndStartAsync to AudioEngineController for non-blocking
  engine start using Swift concurrency (withCheckedContinuation)
- Extract installTapAndStartImpl to share logic between sync/async paths
- Listen for AVAudioEngineConfigurationChange to re-prewarm inputNode
  after Bluetooth device connect/disconnect and AirPods mode switches
- Restructure VoiceInputManager.beginRecording() to show recording UI
  and play activation chime immediately, then start engine async via Task
- Move DictationContextCapture off the critical path: engine starts
  concurrently on its audio queue while context capture runs on main
- Add SFSpeechRecognizer transient unavailability retry (recreate if
  isAvailable returns false after sleep/wake or heavy use)
- Handle edge case where PTT is released before async engine start
  completes (stopRecordingForDictation cleans up directly)

Co-Authored-By: tkheyfets <timur@vellum.ai>

* Tear down engine when async startup outlives recording session

When PTT is released before installTapAndStartAsync completes, the
isRecording guard now stops and removes the tap if the engine started
successfully, preventing the mic path from staying alive with no
active recording session.

Co-Authored-By: tkheyfets <timur@vellum.ai>

* Add recording generation token and gate context capture on start success

Co-Authored-By: tkheyfets <timur@vellum.ai>

* Guard stale teardown against active sessions and gate rewarm on mic auth

Co-Authored-By: tkheyfets <timur@vellum.ai>

* Move context capture to Task.detached to avoid blocking main actor

Co-Authored-By: tkheyfets <timur@vellum.ai>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: tkheyfets <timur@vellum.ai>

* [LUM-681] Fix audio tap format mismatch by resetting engine before installTap (#23766)

After audio-route changes (Bluetooth, USB mic, AirPods mode switch), the
format cached inside AVAudioInputNode diverges from the engine's actual
hardware format. Both outputFormat(forBus:) and a nil format argument to
installTap resolve to this stale value, causing:

  'Failed to create tap due to format mismatch,
   <AVAudioFormat: 2 ch, 44100 Hz, Float32, deinterleaved>'

Fix: call audioEngine.reset() before re-querying the format, then pass it
explicitly to installTap. This forces the engine to discard its cached graph
state and re-read the hardware, so the tap, node, and engine all agree.

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: tkheyfets <timur@vellum.ai>

* fix: pass transport hints through HTTP message endpoint for managed-mode conversations (#23824)

* fix: pass transport metadata through POST /v1/messages to enable host environment hints

The HTTP message handler auto-creates conversations without transport
metadata, so applyTransportMetadata() returns early and host environment
hints (hostHomeDir, hostUsername) are never injected into the LLM context.
This causes the assistant to hallucinate the user's home directory path
from their display name instead of using the actual macOS username.

Thread transport metadata from the message request body through
SendMessageDeps.getOrCreateConversation() to the daemon, and send
hostHomeDir/hostUsername from the macOS client in every message request.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: replace dynamic imports with static type imports

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: ashlee@vellum.ai <ashlee@vellum.ai>
Co-authored-by: tkheyfets <timur@vellum.ai>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: reset non-version-bump files to match main

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Noa Flaherty <noa@vellum.ai>
Co-authored-by: Carson Shaar <carson.s.shaar@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: ashlee@vellum.ai <ashlee@vellum.ai>
Co-authored-by: tkheyfets <timur@vellum.ai>
Co-authored-by: Tirman Sidhu <tirmansidhu@gmail.com>
Co-authored-by: David Vargas Fuertes <vargasvellum@Davids-MacBook-Pro.local>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant