Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
75f78f5
read mic permission from the speech service mirror in the voice butto…
RaajeevChandran Sep 3, 2026
12a2eb8
memoize the swap-emulation flag file read off the main-thread sample …
RaajeevChandran Sep 3, 2026
6ba89dc
coalesce status item refreshes to one pass per runloop turn
RaajeevChandran Sep 3, 2026
af9a25d
store chat model candidates on the picker cache instead of filtering …
RaajeevChandran Sep 3, 2026
a82552e
warm the chat sessions first read off main before the launch chat pre…
RaajeevChandran Sep 3, 2026
2339dd9
resolve keychain secret references off the main actor during config a…
RaajeevChandran Sep 3, 2026
0dac679
cap live diff preview scans so streaming deltas stay bounded
RaajeevChandran Sep 3, 2026
4855c82
move download completion and resume disk probes off the main actor
RaajeevChandran Sep 3, 2026
f172fc6
mark lock-guarded emulation flag statics nonisolated unsafe
RaajeevChandran Sep 3, 2026
2a2341a
drain quit path write flushes concurrently with one bounded wait
RaajeevChandran Sep 3, 2026
35b7a96
warm highlightr off main and cap mid-stream re-highlight size
RaajeevChandran Sep 3, 2026
bc24dad
build relay websocket session off the main actor
RaajeevChandran Sep 3, 2026
47990bb
estimate tokens by utf8 length to keep per-render budget math O(1)
RaajeevChandran Sep 3, 2026
b717fa7
make agent secret name listing non-blocking on the compose path
RaajeevChandran Sep 3, 2026
018bc45
keep the secret name memo warm across mutations and fit quit flushes …
RaajeevChandran Sep 3, 2026
c158c3e
gate the sessions prewarm on storage readiness and drain the queue wi…
RaajeevChandran Sep 3, 2026
b13e779
resolve secret refs before the provider read-mutate-write so config a…
RaajeevChandran Sep 3, 2026
7662215
re-check relay connect by task identity and retire the stale socket s…
RaajeevChandran Sep 3, 2026
76f6c15
rebuild plain once when the streaming highlight cap trips
RaajeevChandran Sep 3, 2026
25f56d1
bound the preview scan by bytes and stop overloading the diff truncat…
RaajeevChandran Sep 3, 2026
558687c
reset the seed in-flight flag through a sync helper
RaajeevChandran Sep 3, 2026
e624112
budget the stateless trim note before choosing kept units
RaajeevChandran Sep 4, 2026
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
115 changes: 83 additions & 32 deletions Packages/OsaurusCore/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -743,12 +743,27 @@
// Skip speculative warming in that state and let the first
// real open pay its own (unavoidable) cost instead.
guard !Self.isUnderResourcePressure else { return }
// Highlightr's first touch evaluates highlight.js in a
// JSContext; warm it on a background thread now so the
// first code block a chat cell renders doesn't pay the
// engine boot on main.
prewarmHighlightrOffMain()
// Same for the agent-secret account memo: the chat-preview
// compose reads it synchronously, and headless composers
// (HTTP, subagents, channels) never run the ChatView
// prewarm, so seed it here for the whole process.
AgentSecretsKeychain.prewarmAccounts()
self?.prewarmManagementWindow()
// Warm ChatView's (deep, slow-to-realize) generic metadata too,
// spaced out so the two heavy SwiftUI realizations don't stack
// into a single main-thread stall during the launch settle.
try? await Task.sleep(for: .seconds(1.0))
guard !Self.isUnderResourcePressure else { return }
// Warm the sessions manager's first read through the
// database queue off the main actor before the ChatView
// prewarm makes it the first toucher of
// `ChatSessionsManager.shared` on main.
await ChatSessionsManager.prewarmShared()
ChatWindowManager.shared.prewarmChatView()
// And the menu-bar popover content, so the first click on
// the status item doesn't pay the panel's first realization.
Expand Down Expand Up @@ -926,12 +941,12 @@
event.eventID == kAEOpenApplication,
event.paramDescriptor(forKeyword: keyAEPropData)?.enumCodeValue
== keyAELaunchedAsLogInItem
{

Check warning on line 944 in Packages/OsaurusCore/AppDelegate.swift

View workflow job for this annotation

GitHub Actions / swiftlint

Opening braces should be preceded by a single space and on the same line as the declaration (opening_brace)
return true
}
if let parent = NSRunningApplication(processIdentifier: getppid()),
parent.bundleIdentifier == "com.apple.loginwindow"
{

Check warning on line 949 in Packages/OsaurusCore/AppDelegate.swift

View workflow job for this annotation

GitHub Actions / swiftlint

Opening braces should be preceded by a single space and on the same line as the declaration (opening_brace)
return true
}
return false
Expand Down Expand Up @@ -1489,33 +1504,50 @@
BrowserSessionManager.shared.shutdownAll()
SharedConfigurationService.shared.remove()
SharedConfigurationService.shared.flushPendingWork()
// Tool enable/policy changes persist via a background serial writer to
// keep the UI snappy; drain it here so a toggle made right before quit
// isn't lost when `_exit` skips the pending write.
ToolConfigurationStore.flushPendingWrites()

// Same for the Computer Use autonomy policy (its own coalescing writer).
ComputerUsePolicyStore.flushPendingWrites()

// Same for the sandbox and agent-delegation stores.
SandboxConfigurationStore.flushPendingWrites()
SubagentConfigurationStore.flushPendingWrites()

// Provider/tool configuration files (remote.json, mcp.json, …) persist
// through ConfigDiskWriter's background queue, and credentials persist
// through the Keychain serial write queue. Drain both, bounded, so a
// provider added or edited right before quit survives relaunch —
// otherwise `_exit` below drops the pending write and the provider
// comes back disabled or credential-less.
ConfigDiskWriter.flushPendingWrites()
Keychain.flushPendingWrites()

// Aptabase batches analytics in an in-memory queue and normally drains
// it from its own `willTerminate` observer — but that flush is async and
// the `_exit(0)` below skips it. Kick a final bounded, best-effort send
// so the last session's events have a chance to leave first. No-op unless
// telemetry is live and consented, so most quits pay nothing here.
TelemetryService.shared.flushForQuit()
// Drain the background writers so edits made right before quit aren't
// lost when `_exit` skips their pending writes: the coalescing config
// stores (tools / Computer Use policy / sandbox / delegation), the
// provider/tool files behind ConfigDiskWriter, and the Keychain serial
// write queue. Each drain is individually bounded, but they used to
// run back to back on the main thread — up to ~13s of serial waits on
// a slow disk, well past the app-hang watchdog. Fan them out and wait
// once: every flush blocks on its own queue's semaphore, so they
// drain concurrently and the quit pays only the slowest one, capped
// by the group deadline below.
//
// Aptabase rides along: it batches analytics in memory and its own
// `willTerminate` flush is async, which `_exit(0)` would skip.
// `prepareQuitFlush` reads the consent gates on main and hands back the
// blocking send-and-wait for a worker thread while the group waits.
//
// Budgets: the two 3.0s-default drains (Keychain, ConfigDiskWriter)
// get an explicit sub-cap so they can finish inside the group deadline
// — a slow securityd write that lands at 2.4s must still be honored,
// not cut off by `_exit`. The deadline itself stays under the 3.0s
// app-hang watchdog so a timed-out flush isn't filed as a hang. No-op unless telemetry is consented.
let flushGroup = DispatchGroup()
let flushWorkers = DispatchQueue.global(qos: .userInitiated)
var flushes: [@Sendable () -> Void] = [
{ ToolConfigurationStore.flushPendingWrites() },
{ ComputerUsePolicyStore.flushPendingWrites() },
{ SandboxConfigurationStore.flushPendingWrites() },
{ SubagentConfigurationStore.flushPendingWrites() },
{ ConfigDiskWriter.flushPendingWrites(timeout: 2.5) },
{ Keychain.flushPendingWrites(timeout: 2.5) },
]
if let telemetryFlush = TelemetryService.shared.prepareQuitFlush() {
flushes.append(telemetryFlush)
}
for flush in flushes {
flushGroup.enter()
flushWorkers.async {
flush()
flushGroup.leave()
}
}
if flushGroup.wait(timeout: .now() + 2.8) == .timedOut {
NSLog("Osaurus quit flush timed out; exiting with writes possibly pending")
}

// Hard-exit without running `atexit`/C++ static destructors.
// AppKit's `terminate:` would otherwise call `exit()`, which runs
Expand Down Expand Up @@ -1614,13 +1646,13 @@
serverController.$serverHealth
.receive(on: RunLoop.main)
.sink { [weak self] _ in
self?.updateStatusItemAndMenu()
self?.scheduleStatusItemUpdate()
}
.store(in: &cancellables)
serverController.$isRunning
.receive(on: RunLoop.main)
.sink { [weak self] isRunning in
self?.updateStatusItemAndMenu()
self?.scheduleStatusItemUpdate()
if isRunning {
self?.completeFirstSuccessfulServerStart()
}
Expand All @@ -1629,22 +1661,22 @@
serverController.$configuration
.receive(on: RunLoop.main)
.sink { [weak self] _ in
self?.updateStatusItemAndMenu()
self?.scheduleStatusItemUpdate()
}
.store(in: &cancellables)

serverController.$activeRequestCount
.receive(on: RunLoop.main)
.sink { [weak self] _ in
self?.updateStatusItemAndMenu()
self?.scheduleStatusItemUpdate()
}
.store(in: &cancellables)

// Observe VAD service state for menu bar indicator
VADService.shared.$state
.receive(on: RunLoop.main)
.sink { [weak self] _ in
self?.updateStatusItemAndMenu()
self?.scheduleStatusItemUpdate()
}
.store(in: &cancellables)

Expand Down Expand Up @@ -1689,6 +1721,25 @@
}
}

/// Coalesces the status-item refresh to one pass per runloop turn. Five
/// publishers funnel into it, and `$activeRequestCount` alone can fire
/// several times in a single turn under request churn — each pass detaches
/// the menu, re-sets the button image, and rebuilds the tooltip, which is
/// enough WindowServer traffic to stall main when the machine is already
/// slow. The flag resets before the update runs, so a publish that lands
/// during the update still schedules a fresh pass and no state is missed.
private var statusItemUpdateScheduled = false

private func scheduleStatusItemUpdate() {
guard !statusItemUpdateScheduled else { return }
statusItemUpdateScheduled = true
DispatchQueue.main.async { [weak self] in
guard let self else { return }
self.statusItemUpdateScheduled = false
self.updateStatusItemAndMenu()
}
}

private func updateStatusItemAndMenu() {
guard let statusItem else { return }
// Ensure no NSMenu is attached so button action is triggered
Expand Down Expand Up @@ -1885,7 +1936,7 @@
self?.popover?.performClose(nil)
}

if #available(macOS 26.0, *) {

Check warning on line 1939 in Packages/OsaurusCore/AppDelegate.swift

View workflow job for this annotation

GitHub Actions / swiftlint

Use #unavailable instead of #available with an empty body (unavailable_condition)
// Tahoe: keying the popover window above is enough, and activating
// here would close the popover over full-screen apps (see comment
// above).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ public enum ComputerUsePolicyStore {
}

/// Synchronously drain any pending write (call from `applicationWillTerminate`).
public static func flushPendingWrites(timeout: TimeInterval = 1.5) {
/// `nonisolated` so the quit path can drain all stores concurrently off
/// main; the coordinator is internally locked.
nonisolated public static func flushPendingWrites(timeout: TimeInterval = 1.5) {
writeCoordinator.flushSync(timeout: timeout)
}

Expand All @@ -60,7 +62,7 @@ public enum ComputerUsePolicyStore {
return OsaurusPaths.computerUseConfigFile()
}

private static let writeCoordinator = WriteCoordinator()
nonisolated private static let writeCoordinator = WriteCoordinator()

private final class WriteCoordinator: @unchecked Sendable {
private let queue = DispatchQueue(label: "com.osaurus.computeruse.write", qos: .utility)
Expand Down
71 changes: 52 additions & 19 deletions Packages/OsaurusCore/Configuration/Declarative/ConfigApplier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -692,7 +692,7 @@ enum ConfigApplier {
// in results — only the ref display names do.
var botToken: (value: String, display: String)?
if let raw = section.botTokenRef {
let (secret, display) = resolveSecretRef(raw)
let (secret, display) = await resolveSecretRef(raw)
guard let secret else {
results.append(
ConfigApplyResult(
Expand All @@ -705,7 +705,7 @@ enum ConfigApplier {
}
var appToken: (value: String, display: String)?
if let raw = section.appTokenRef {
let (secret, display) = resolveSecretRef(raw)
let (secret, display) = await resolveSecretRef(raw)
guard let secret else {
results.append(
ConfigApplyResult(
Expand Down Expand Up @@ -800,12 +800,31 @@ enum ConfigApplier {
@MainActor
private static func applyMCPServers(
_ entries: [MCPServerEntry], prune: Bool
) -> [ConfigApplyResult] {
) async -> [ConfigApplyResult] {
var results: [ConfigApplyResult] = []
let manager = MCPProviderManager.shared
var matched = Set<UUID>()

for entry in entries {
// Resolve every secret reference up front. `resolveSecretRef`
// suspends (a detached Keychain read that can take seconds), and
// the existing-provider branch below copies the live provider,
// mutates the copy, and writes the whole struct back — a
// suspension inside that window would silently revert any edit
// Settings saved meanwhile. With the awaits hoisted here, the
// read-mutate-write below is synchronous on the main actor again.
var resolvedToken: (secret: String?, display: String)?
if let raw = entry.tokenRef {
resolvedToken = await resolveSecretRef(raw)
}
var resolvedEnvRefs: [(key: String, secret: String?, display: String)] = []
if let refs = entry.secretEnvRefs {
for (envKey, raw) in refs.sorted(by: { $0.key < $1.key }) {
let (secret, display) = await resolveSecretRef(raw)
resolvedEnvRefs.append((envKey, secret, display))
}
}

let existing = ConfigExporter.manageableMCPProviders().first {
$0.name.lowercased() == entry.name.lowercased()
}
Expand All @@ -823,8 +842,7 @@ enum ConfigApplier {
}
// A token reference stores the bearer token directly —
// no Settings visit needed when it resolves and lands.
if let raw = entry.tokenRef {
let (secret, display) = resolveSecretRef(raw)
if case let (secret, display)? = resolvedToken {
if let secret {
provider.authType = .bearerToken
if MCPProviderKeychain.saveToken(secret, for: provider.id) {
Expand Down Expand Up @@ -858,9 +876,8 @@ enum ConfigApplier {
{
provider.executionHost = host
}
if let refs = entry.secretEnvRefs {
for (envKey, raw) in refs.sorted(by: { $0.key < $1.key }) {
let (secret, display) = resolveSecretRef(raw)
if !resolvedEnvRefs.isEmpty {
for (envKey, secret, display) in resolvedEnvRefs {
guard let secret else {
secretFailure = true
secretMessages.append(
Expand Down Expand Up @@ -921,8 +938,7 @@ enum ConfigApplier {
// a bad reference never leaves a half-configured server.
var resolvedSecretEnv: [(key: String, value: String, display: String)] = []
var unresolved: [String] = []
for (envKey, raw) in (entry.secretEnvRefs ?? [:]).sorted(by: { $0.key < $1.key }) {
let (secret, display) = resolveSecretRef(raw)
for (envKey, secret, display) in resolvedEnvRefs {
if let secret {
resolvedSecretEnv.append((envKey, secret, display))
} else {
Expand Down Expand Up @@ -987,8 +1003,7 @@ enum ConfigApplier {
var auth = entry.auth.flatMap(ConfigMCPAuth.auth(forKey:)) ?? MCPProviderAuthType.none
var token: String? = nil
var tokenDisplay: String? = nil
if let raw = entry.tokenRef {
let (secret, display) = resolveSecretRef(raw)
if case let (secret, display)? = resolvedToken {
guard let secret else {
results.append(
ConfigApplyResult(
Expand Down Expand Up @@ -1194,7 +1209,7 @@ enum ConfigApplier {
// sheet. Mirrors the interactive path: entering a key flips
// the provider to API-key auth.
if let raw = entry.apiKeyRef {
let (secret, display) = resolveSecretRef(raw)
let (secret, display) = await resolveSecretRef(raw)
guard let secret else {
results.append(
ConfigApplyResult(
Expand All @@ -1203,12 +1218,19 @@ enum ConfigApplier {
+ "nothing was stored."))
continue
}
var updated = frozen
updated.authType = .apiKey
let toSave = updated
// Re-read the provider after the suspension: `frozen` is a
// pre-await copy, and writing it back would revert any
// Settings edit that landed while the Keychain read ran.
// Only the auth flip is ours to write.
let providerId = frozen.id
await MainActor.run {
guard
var current = RemoteProviderManager.shared.configuration.providers
.first(where: { $0.id == providerId })
else { return }
current.authType = .apiKey
RemoteProviderManager.shared.updateProvider(
toSave, apiKey: secret, oauthTokens: nil)
current, apiKey: secret, oauthTokens: nil)
}
results.append(
ConfigApplyResult(
Expand Down Expand Up @@ -1273,11 +1295,22 @@ enum ConfigApplier {
/// Resolve a `*_ref` document value into (secret, safe display name).
/// `nil` secret means malformed / missing / empty — callers report by
/// display only; the value itself never reaches a result or a log.
static func resolveSecretRef(_ raw: String) -> (secret: String?, display: String) {
static func resolveSecretRef(_ raw: String) async -> (secret: String?, display: String) {
switch ConfigSecretRef.parse(raw) {
case .failure:
return (nil, raw)
case .success(let ref):
// Keychain refs reach securityd through a synchronous
// SecItemCopyMatching; resolved on the main actor, that IPC
// round-trip has stalled the app for seconds when securityd was
// slow. Hop off the cooperative executor for the read. Env refs
// are a dictionary lookup and stay inline.
if case .keychain = ref.source {
let secret = await Task.detached(priority: .userInitiated) {
ref.resolve()
}.value
return (secret, ref.display)
}
return (ref.resolve(), ref.display)
}
}
Expand Down Expand Up @@ -1378,7 +1411,7 @@ enum ConfigApplier {
// is read from env/keychain and stored exactly like an entered one.
// Validation refuses api_key_ref for the OAuth/pairing resolutions.
if let raw = entry.apiKeyRef, case .preset = resolution {
let (secret, display) = resolveSecretRef(raw)
let (secret, display) = await resolveSecretRef(raw)
guard let secret else {
return ProviderAddOutcome(
result: ConfigApplyResult(
Expand Down
Loading
Loading