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
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"files": {
"desktop/macos/Desktop/Sources/Providers/ChatProvider.swift": 6215,
"desktop/macos/Desktop/Sources/Providers/ChatProvider.swift": 6186,
"desktop/macos/Desktop/Sources/Providers/ChatToolExecutor.swift": 2850
},
"raise_justifications": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,72 @@
import Foundation

extension ChatProvider {
/// Harness-only chat reset that awaits backend deletion before returning.
/// Returns an error message when backend deletion fails so E2E flows don't
/// proceed against stale persisted messages.
func automationResetChatForHarness() async -> String? {
guard AppBuild.isNonProduction else { return nil }
return await resetChatForAuthorizedHarness()
}

/// Completes a harness reset after its non-production automation entrypoint
/// has established eligibility. The main-chat transaction sets
/// `defaultJournalAlreadyCleared` only after its authoritative owner-scoped
/// control clear has succeeded.
func resetChatForAuthorizedHarness(defaultJournalAlreadyCleared: Bool = false) async -> String? {
isClearing = true
defer { isClearing = false }

if isInDefaultChat {
let runtimeChatId = mainChatRuntimeChatId(sessionId: nil)
let surface = AgentSurfaceReference.mainChat(chatId: runtimeChatId)
AgentRuntimeStatusStore.shared.clear(surface: surface)
if !defaultJournalAlreadyCleared {
guard await kernelTurnProjection.clear(surface: surface) else {
return "failed to clear default kernel journal"
}
}
} else {
let sessionToDelete = currentSession
if let session = sessionToDelete {
let surface = AgentSurfaceReference.mainChat(chatId: session.id)
AgentRuntimeStatusStore.shared.clear(surface: surface)
guard await kernelTurnProjection.clear(surface: surface) else {
return "failed to clear session kernel journal"
}
}
if let session = sessionToDelete {
sessions.removeAll { $0.id == session.id }
}
currentSession = nil
messages = []
resetMessagesPagination()
_ = await createNewSession()
}
return nil
}

/// Reset isolation must clear the same kernel-owned surface the flow will
/// exercise. Fault bundles intentionally have no authenticated owner, so
/// establish a temporary non-production owner for this transaction rather
/// than bypassing the owner boundary or carrying a synthetic session forward.
func automationResetMainChatForHarness() async -> String? {
guard AppBuild.isNonProduction else { return nil }
return await performMainChatHarnessResetTransaction()
}

/// Performs the owner-scoped reset transaction after the automation entrypoint
/// has established that the bundle is non-production.
func performMainChatHarnessResetTransaction() async -> String? {
let bundleScope = (Bundle.main.bundleIdentifier ?? "desktop")
.replacingOccurrences(of: ".", with: "-")
let resetOwnerID = "desktop-harness-reset-\(bundleScope)"
return await RuntimeOwnerIdentity.withAutomationOwnerIfMissing(resetOwnerID) { [self] in
let clear = await automationClearOwnerSurfaceState(chatId: "default")
let clear = await clearOwnerSurfaceStateForAuthorizedHarness(chatId: "default")
if let error = clear["error"] {
return error
}
return await automationResetChatForHarness()
return await resetChatForAuthorizedHarness(defaultJournalAlreadyCleared: true)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Clear the active app-scoped surface before skipping

When a test/fault flow runs while an app is selected, mainChatRuntimeChatId(sessionId: nil) resolves the visible default chat to default|<appId>, but the preceding owner clear only targets chatId: "default". Passing defaultJournalAlreadyCleared: true here then skips kernelTurnProjection.clear for the actual app-scoped surface, and resetJournalProjection ignores the default-surface clear because it does not match mainChatSurfaceReference(), so reset_main_chat can report success while leaving the selected app chat/journal stale.

Useful? React with 👍 / 👎.

}
}
}
45 changes: 8 additions & 37 deletions desktop/macos/Desktop/Sources/Providers/ChatProvider.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Combine

Check warning on line 1 in desktop/macos/Desktop/Sources/Providers/ChatProvider.swift

View workflow job for this annotation

GitHub Actions / Desktop Swift Build & Tests

Large changed file

desktop/macos/Desktop/Sources/Providers/ChatProvider.swift is 6186 lines; consider splitting files over 800 lines.

Check warning on line 1 in desktop/macos/Desktop/Sources/Providers/ChatProvider.swift

View workflow job for this annotation

GitHub Actions / PR Metadata Preflight

Large changed file

desktop/macos/Desktop/Sources/Providers/ChatProvider.swift is 6186 lines; consider splitting files over 800 lines.
import CoreGraphics
@preconcurrency import GRDB
import OmiSupport
Expand Down Expand Up @@ -1276,7 +1276,7 @@
/// Reset history-pagination state. Must accompany every clear/replace of
/// `messages` outside the two loaders (`selectSession`,
/// `loadDefaultChatMessages`), which set both fields from a fresh fetch.
private func resetMessagesPagination() {
func resetMessagesPagination() {
messagesPaginationOffset = 0
hasMoreMessages = false
}
Expand Down Expand Up @@ -1704,7 +1704,7 @@
RuntimeOwnerIdentity.currentOwnerId()
}

private func mainChatRuntimeChatId(sessionId: String?) -> String {
func mainChatRuntimeChatId(sessionId: String?) -> String {
guard let sessionId, !sessionId.isEmpty else {
if let appId = selectedAppId, !appId.isEmpty {
return "default|\(appId)"
Expand Down Expand Up @@ -5883,41 +5883,6 @@
AnalyticsManager.shared.chatCleared()
}

/// Harness-only chat reset that awaits backend deletion before returning.
/// Returns an error message when backend deletion fails so E2E flows don't
/// proceed against stale persisted messages.
func automationResetChatForHarness() async -> String? {
guard AppBuild.isNonProduction else { return nil }
isClearing = true
defer { isClearing = false }

if isInDefaultChat {
let runtimeChatId = mainChatRuntimeChatId(sessionId: nil)
let surface = AgentSurfaceReference.mainChat(chatId: runtimeChatId)
AgentRuntimeStatusStore.shared.clear(surface: surface)
guard await kernelTurnProjection.clear(surface: surface) else {
return "failed to clear default kernel journal"
}
} else {
let sessionToDelete = currentSession
if let session = sessionToDelete {
let surface = AgentSurfaceReference.mainChat(chatId: session.id)
AgentRuntimeStatusStore.shared.clear(surface: surface)
guard await kernelTurnProjection.clear(surface: surface) else {
return "failed to clear session kernel journal"
}
}
if let session = sessionToDelete {
sessions.removeAll { $0.id == session.id }
}
currentSession = nil
messages = []
resetMessagesPagination()
_ = await createNewSession()
}
return nil
}

// MARK: - App Selection

/// Select a chat app and load its sessions
Expand Down Expand Up @@ -6171,6 +6136,12 @@
guard AppBuild.isNonProduction else {
return ["error": "clear_owner_surface_state is disabled on production bundles"]
}
return await clearOwnerSurfaceStateForAuthorizedHarness(chatId: chatId)
}

/// Performs the owner-scoped control clear after a non-production automation
/// entrypoint has established eligibility.
func clearOwnerSurfaceStateForAuthorizedHarness(chatId: String = "default") async -> [String: String] {
kernelTurnProjection.attachControlClient(resolvedAgentClient())
guard await kernelTurnProjection.clearOwnerSurfaceState(chatId: chatId) else {
return ["error": "kernel owner surface clear failed", "chat_id": chatId]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import XCTest

Check warning on line 1 in desktop/macos/Desktop/Tests/KernelTurnRecordedProjectionTests.swift

View workflow job for this annotation

GitHub Actions / Desktop Swift Build & Tests

Large changed file

desktop/macos/Desktop/Tests/KernelTurnRecordedProjectionTests.swift is 1040 lines; consider splitting files over 800 lines.

Check warning on line 1 in desktop/macos/Desktop/Tests/KernelTurnRecordedProjectionTests.swift

View workflow job for this annotation

GitHub Actions / PR Metadata Preflight

Large changed file

desktop/macos/Desktop/Tests/KernelTurnRecordedProjectionTests.swift is 1040 lines; consider splitting files over 800 lines.

@testable import Omi_Computer

Expand Down Expand Up @@ -490,6 +490,57 @@
XCTAssertTrue(provider.messages.isEmpty)
}

func testFaultHarnessResetUsesCredentialFreeControlClearOnceAndCompletesProjectionReset() async throws {
let provider = ChatProvider()
let surface = provider.mainChatSurfaceReference()
let statusStore = AgentRuntimeStatusStore.shared
statusStore.reset()
defer { statusStore.reset() }
statusStore.beginRequest(surface: surface)
provider.projectJournalTurn(
try turn(
surface: surface,
turnId: "visible-before-fault-reset",
turnSeq: 1,
content: "This must disappear after the authoritative control clear"
))
var modelReadinessRequests = 0
var clearCalls: [(ownerID: String, generation: Int)] = []
provider.kernelTurnProjection = KernelTurnProjection(
host: provider,
client: AgentClient.Session(harnessMode: "piMono"),
ownerIDProvider: {
RuntimeOwnerIdentity.currentOwnerId(allowAutomationOverride: true)
},
journalListOperation: { _, _, ownerID, afterTurnSeq, limit in
XCTAssertFalse(ownerID.isEmpty)
XCTAssertEqual(afterTurnSeq, 0)
XCTAssertEqual(limit, 1)
return self.journalPage(
conversationId: "fault-harness-conversation",
turns: [],
generation: 9
)
},
journalClearOperation: { _, _, ownerID, expectedGeneration in
clearCalls.append((ownerID, expectedGeneration))
return 1
},
kernelReadyOperation: {
modelReadinessRequests += 1
return false
}
)

let error = await provider.performMainChatHarnessResetTransaction()

XCTAssertNil(error)
XCTAssertEqual(modelReadinessRequests, 0)
XCTAssertEqual(clearCalls.map(\.generation), [9])
XCTAssertTrue(provider.messages.isEmpty)
XCTAssertNil(statusStore.projection(for: surface))
}

func testClearFailsClosedWhenGenerationBootstrapFails() async throws {
struct BootstrapFailure: Error {}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"change": "Improved beta chat recovery when authentication fault checks run"
}
Loading