// // UISmoke.swift // Zyquo Agent // // Author: Simon-Pierre Boucher // Mail: contact@spboucher.ai // // Hidden `--run-ui-smoke` mode: drives the RunController (the UI's view // model) with the scripted MockProviderClient — no network, no keys, no // window — and asserts the published state transitions the command-center // UI renders from: step entries appear and stream, the plan lands, tokens // accumulate, the run completes, and the task record gains the persisted // agent-run message with its embedded steps. Everything runs against // temp-rooted persistence so the user's real tasks and policy rules are // never touched. // import Foundation enum UISmoke { /// Returns the process exit status (0 = all checks passed). @MainActor static func run() async -> Int32 { var failures: [String] = [] func check(_ condition: Bool, _ label: String) { if condition { print(" ✔ \(label)") } else { failures.append(label) print(" ✘ \(label)") } } print("Zyquo Agent — UI smoke (RunController + MockProviderClient)") // Temp-rooted persistence: no real tasks/rules touched. let temporaryRoot = FileManager.default.temporaryDirectory .appendingPathComponent("ZyquoAgent-uismoke-\(UUID().uuidString.prefix(8))") let persistence = PersistenceService(rootDirectory: temporaryRoot) defer { try? FileManager.default.removeItem(at: temporaryRoot) } let store = TaskStore(persistence: persistence) let task = store.newTask(model: MockProviderClient.model) let controller = RunController(taskID: task.id, store: store, policyPersistence: persistence) var sawEntries = false var sawPlan = false var sawRunningStatus = false var sawApproval = false controller.start( prompt: "Create a demo folder in the workspace with a shell command and verify it exists.", model: MockProviderClient.model, client: MockProviderClient(), apiKey: "mock" ) check(controller.isRunning, "run starts (isRunning)") // Poll the published state until the run finishes (mock finishes in // well under a second; 15s is a generous CI ceiling). let deadline = Date().addingTimeInterval(15) while controller.outcome == nil && Date() < deadline { sawEntries = sawEntries || !controller.entries.isEmpty sawPlan = sawPlan || controller.plan != nil sawRunningStatus = sawRunningStatus || (store.task(id: task.id)?.status.isActive ?? false) // Guarded mode holds the mock's `mkdir` at the gate — resolve the // approval card the way the UI's Approve button does. if controller.pendingApproval != nil { sawApproval = true controller.resolveApproval(.approve) } try? await Task.sleep(nanoseconds: 20_000_000) } check(sawEntries, "live step entries were published during the run") check(sawPlan, "plan was published (update_plan intercepted)") check(sawRunningStatus, "task status went active while running") check(sawApproval, "approval card surfaced and resolved (guarded mode)") guard let outcome = controller.outcome else { print(" ✘ run did not finish within 15s") return 1 } if case .completed(let answer) = outcome { check(!answer.isEmpty, "run completed with a final answer") } else { check(false, "run completed (got \(outcome))") } check(!controller.isRunning, "isRunning cleared after the run") check(controller.entries.isEmpty, "live timeline folded into history after the run") check(controller.tokensUsed > 0, "token usage accumulated") check(controller.pendingApproval == nil, "no dangling approval") let finished = store.task(id: task.id) check(finished?.status == .done, "task status is Done") check(finished?.messages.count == 2, "history has prompt + run (got \(finished?.messages.count ?? 0))") let runMessage = finished?.messages.last check(runMessage?.kind == .agentRun, "last history entry is the agent run") check((runMessage?.steps?.count ?? 0) >= 3, "run message embeds the steps (got \(runMessage?.steps?.count ?? 0))") check(runMessage?.plan?.doneCount == 2, "run message carries the finished plan") check(finished?.workspacePath != nil, "task bound to a workspace") check(!controller.terminalLines.isEmpty, "terminal feed captured output") // Reopen path: a fresh controller re-attaches the workspace. if let workspacePath = finished?.workspacePath { let reopened = RunController(taskID: task.id, store: store, policyPersistence: persistence) check(reopened.workspaceRoot?.path == workspacePath, "reopened controller reattaches the workspace") check(reopened.plan != nil, "reopened controller restores the last plan") try? FileManager.default.removeItem(at: URL(fileURLWithPath: workspacePath)) } if failures.isEmpty { print("UI smoke: all checks passed.") return 0 } print("UI smoke: \(failures.count) check(s) FAILED.") return 1 } }