Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions assistant/src/prompts/templates/BOOTSTRAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ Mark declined fields so you don't re-ask later (e.g., `Work role: declined_by_us

**Call `file_edit` immediately whenever you learn something, in the same turn.** Don't batch saves. The moment the user gives you a name, save it. The moment you infer their style, save it.

**After tool calls, do not repeat yourself.** Your text before tool calls is already visible to the user. When tool results return and you continue, pick up where you left off — don't re-confirm, re-greet, or re-ask the same question. If you already asked something and are waiting for the user's answer, just stop.

**The contents of IDENTITY.md, SOUL.md, and USER.md are already in your system prompt.** Use the exact text you see there for `old_string` in `file_edit`. Do not guess or invent content.

Update `IDENTITY.md` (name, nature, personality, style) and `USER.md` (their name, pronouns, goals, locale, work role, hobbies, daily tools). Save behavioral guidelines to `SOUL.md`.
Expand Down
2 changes: 1 addition & 1 deletion assistant/src/prompts/templates/SOUL.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ You work for your user. You do not work for a compliance department. Your user i

**Be genuinely helpful, not performatively helpful.** Skip the "Great question!" and "I'd be happy to help!" filler. Just help. Actions over words.

**Talk before you work.** Always say something to the user before using tools. Even a short message beats minutes of silence while tools run in the background. The user should never wonder if you're still there.
**Talk before you work.** Always say something to the user before using tools. Even a short message beats minutes of silence while tools run in the background. The user should never wonder if you're still there. But after tools complete, do not repeat or rephrase what you already said — your earlier text is already visible. Pick up where you left off or stop if you're waiting for the user.

**Be resourceful before asking.** Try to figure it out. Read the file. Check the context. Search for it. Check what tools and skills you have. If a connection is broken, try to fix it. If a service needs setup, offer to do it. Escalate only after you've tried.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ struct ImproveExperienceStepView: View {
.fill(tosAccepted ? VColor.primaryBase : Color.clear)

RoundedRectangle(cornerRadius: VRadius.sm)
.strokeBorder(tosAccepted ? Color.clear : VColor.borderBase, lineWidth: 1.5)
.strokeBorder(tosAccepted ? Color.clear : VColor.borderElement, lineWidth: 1.5)

if tosAccepted {
VIconView(.check, size: 12)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ final class OnboardingState {

var subtitle: String {
switch self {
case .vellumCloud: return "Ready out of the box. Runs entirely on Vellum's secure infrastructure."
case .vellumCloud: return "Always on, 24/7, even when your Mac is asleep. Runs on Vellum's secure infrastructure."
case .local: return "Your machine, your data. Nothing leaves your Mac."
case .docker: return "Same privacy as local, but sandboxed using Docker."
case .oldLocal: return "Legacy local mode without Docker."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ public struct VSelectableTextView: NSViewRepresentable {
textView.autoresizingMask = [.width]
textView.textContainerInset = .zero

textView.delegate = context.coordinator

textView.linkTextAttributes = [
.foregroundColor: tintColor,
.underlineStyle: NSUnderlineStyle.single.rawValue,
Expand Down Expand Up @@ -193,7 +195,7 @@ public struct VSelectableTextView: NSViewRepresentable {

public func makeCoordinator() -> Coordinator { Coordinator() }

public final class Coordinator {
public final class Coordinator: NSObject, NSTextViewDelegate {
var lastAttributedString: NSAttributedString?
var lastLineSpacing: CGFloat = 0
private var pendingAttributedString: NSAttributedString?
Expand Down Expand Up @@ -240,6 +242,22 @@ public struct VSelectableTextView: NSViewRepresentable {
}
}

// MARK: - NSTextViewDelegate

/// Opens clicked links in the default browser.
/// Reference: https://developer.apple.com/documentation/appkit/nstextviewdelegate/textview(_:clickedonlink:at:)
public func textView(_ textView: NSTextView, clickedOnLink link: Any, at charIndex: Int) -> Bool {
if let url = link as? URL {
NSWorkspace.shared.open(url)
return true
}
if let string = link as? String, let url = URL(string: string) {
NSWorkspace.shared.open(url)
return true
}
return false
}

func applyAttributedString(
_ attributedString: NSAttributedString,
lineSpacing: CGFloat,
Expand Down
18 changes: 13 additions & 5 deletions clients/shared/Network/DictationClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,28 +28,36 @@ public struct DictationClient: DictationClientProtocol {
"navigate",
]

/// Timeout for the dictation HTTP request. Kept short so the client falls
/// back to raw transcription quickly when the assistant is unreachable rather
/// than leaving the user staring at a "Processing…" spinner.
static let requestTimeout: TimeInterval = 5

public func process(_ request: DictationRequest) async -> DictationResponseMessage {
let start = CFAbsoluteTimeGetCurrent()
do {
let encodedRequest = try JSONEncoder().encode(request)
let response = try await GatewayHTTPClient.post(
path: "assistants/{assistantId}/dictation",
body: encodedRequest,
timeout: 10
timeout: Self.requestTimeout
)
let elapsed = CFAbsoluteTimeGetCurrent() - start
guard response.isSuccess else {
log.error("process dictation failed (HTTP \(response.statusCode))")
log.warning("Dictation request failed (HTTP \(response.statusCode)) after \(String(format: "%.1f", elapsed))s")
return fallbackResponse(for: request, errorMessage: "HTTP \(response.statusCode)")
}

let patched = injectType("dictation_response", into: response.data)
do {
return try JSONDecoder().decode(DictationResponseMessage.self, from: patched)
} catch {
log.error("process dictation decode error: \(error.localizedDescription)")
log.warning("Dictation response decode failed after \(String(format: "%.1f", elapsed))s: \(error.localizedDescription)")
return fallbackResponse(for: request, errorMessage: "Failed to decode dictation response")
}
} catch {
log.error("process dictation error: \(error.localizedDescription)")
let elapsed = CFAbsoluteTimeGetCurrent() - start
log.warning("Dictation request error after \(String(format: "%.1f", elapsed))s: \(error.localizedDescription)")
return fallbackResponse(for: request, errorMessage: error.localizedDescription)
}
}
Expand All @@ -58,7 +66,7 @@ public struct DictationClient: DictationClientProtocol {

/// Internal for test coverage.
func fallbackResponse(for request: DictationRequest, errorMessage: String) -> DictationResponseMessage {
log.warning("Falling back to raw dictation response after HTTP failure: \(errorMessage, privacy: .public)")
log.warning("Using local transcription fallback (\(errorMessage, privacy: .public)). Transcription length=\(request.transcription.count)")
let mode = fallbackMode(for: request)
let text: String
switch mode {
Expand Down
Loading