// // AgentTask.swift // Zyquo Agent // // Author: Simon-Pierre Boucher // Mail: contact@spboucher.ai // // The persisted task record: one human↔agent conversation bound to one // workspace. The user's prompts and each run's steps + final answer are // embedded (AgentStep is Codable and its tool outputs are already bounded by // the MemoryManager's offloading), so reopening a past task re-renders its // full step history even after multiple runs — the workspace transcript // (`.zyquo/transcript.json`, via Transcript.load) additionally holds the // latest run for trajectory inspection. // import Foundation /// Sidebar/status-pill state of a task. enum AgentTaskStatus: String, Codable, Sendable { /// No run yet, or the last run's outcome was cleared. case idle /// A run is preparing (workspace, system prompt) or drafting its plan. case planning /// A run is actively streaming or executing tools. case running /// A tool call is held at the policy gate, waiting for the user. case awaitingApproval /// A LoopGuard trip paused the run, waiting for continue/stop. case awaitingInput case done case failed var displayName: String { switch self { case .idle: return "Idle" case .planning: return "Planning" case .running: return "Running" case .awaitingApproval: return "Awaiting approval" case .awaitingInput: return "Awaiting input" case .done: return "Done" case .failed: return "Failed" } } /// True while a live run owns this task. var isActive: Bool { switch self { case .planning, .running, .awaitingApproval, .awaitingInput: return true case .idle, .done, .failed: return false } } } /// One entry of the task's human↔agent history: either a user prompt or one /// completed agent run (its steps, plan, and outcome). struct TaskMessage: Codable, Identifiable, Sendable { enum Kind: String, Codable, Sendable { case user case agentRun } var id: UUID = UUID() var kind: Kind /// User prompt text, or the run's final answer (empty when it failed). var text: String /// The run's steps, embedded so past runs re-render verbatim. var steps: [AgentStep]? /// The plan as it stood when the run ended. var plan: TaskPlan? /// How the run ended (agentRun entries only). var outcome: AgentRunOutcome? var createdAt: Date = Date() } /// One agent task (a sidebar row): conversation + model + safety mode + /// workspace binding. struct AgentTask: Codable, Identifiable, Sendable { var id: UUID = UUID() var title: String var createdAt: Date = Date() var updatedAt: Date = Date() var pinned: Bool = false var status: AgentTaskStatus = .idle var modelID: String var providerID: ProviderID var safetyMode: SafetyMode = .guarded /// Active persona (Phase 6): its system-prompt addition is appended to /// the agent system prompt on every run of this task; nil = none. var personaID: UUID? /// Path of the task's workspace directory; nil until the first run /// creates it. var workspacePath: String? /// The user↔agent history (prompts + completed runs). var messages: [TaskMessage] = [] /// Default title derived from a prompt's leading words. static func title(fromPrompt prompt: String) -> String { let firstLine = prompt .split(separator: "\n", omittingEmptySubsequences: true) .first.map(String.init) ?? prompt let trimmed = firstLine.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { return "New Task" } if trimmed.count <= 48 { return trimmed } let cut = trimmed.prefix(48) // Break on the last word boundary inside the prefix. if let lastSpace = cut.lastIndex(of: " ") { return String(cut[.. TranscriptDocument? { guard let url = workspaceURL else { return nil } return Transcript.load(from: url) } }