SPB Git

spb/zyquo-agent Public MIT

The autonomous agent that actually operates your Mac — plans, runs real commands, verifies its own work.

Swift 94.7% Shell 4.1% Python 0.7% Makefile 0.5%
4.3 KB · 124 lines swift
Raw Blame History
1//2//  AgentTask.swift3//  Zyquo Agent4//5//  Author: Simon-Pierre Boucher6//  Mail: contact@spboucher.ai7//8//  The persisted task record: one human↔agent conversation bound to one9//  workspace. The user's prompts and each run's steps + final answer are10//  embedded (AgentStep is Codable and its tool outputs are already bounded by11//  the MemoryManager's offloading), so reopening a past task re-renders its12//  full step history even after multiple runs — the workspace transcript13//  (`.zyquo/transcript.json`, via Transcript.load) additionally holds the14//  latest run for trajectory inspection.15//1617import Foundation1819/// Sidebar/status-pill state of a task.20enum AgentTaskStatus: String, Codable, Sendable {21    /// No run yet, or the last run's outcome was cleared.22    case idle23    /// A run is preparing (workspace, system prompt) or drafting its plan.24    case planning25    /// A run is actively streaming or executing tools.26    case running27    /// A tool call is held at the policy gate, waiting for the user.28    case awaitingApproval29    /// A LoopGuard trip paused the run, waiting for continue/stop.30    case awaitingInput31    case done32    case failed3334    var displayName: String {35        switch self {36        case .idle: return "Idle"37        case .planning: return "Planning"38        case .running: return "Running"39        case .awaitingApproval: return "Awaiting approval"40        case .awaitingInput: return "Awaiting input"41        case .done: return "Done"42        case .failed: return "Failed"43        }44    }4546    /// True while a live run owns this task.47    var isActive: Bool {48        switch self {49        case .planning, .running, .awaitingApproval, .awaitingInput: return true50        case .idle, .done, .failed: return false51        }52    }53}5455/// One entry of the task's human↔agent history: either a user prompt or one56/// completed agent run (its steps, plan, and outcome).57struct TaskMessage: Codable, Identifiable, Sendable {58    enum Kind: String, Codable, Sendable {59        case user60        case agentRun61    }6263    var id: UUID = UUID()64    var kind: Kind65    /// User prompt text, or the run's final answer (empty when it failed).66    var text: String67    /// The run's steps, embedded so past runs re-render verbatim.68    var steps: [AgentStep]?69    /// The plan as it stood when the run ended.70    var plan: TaskPlan?71    /// How the run ended (agentRun entries only).72    var outcome: AgentRunOutcome?73    var createdAt: Date = Date()74}7576/// One agent task (a sidebar row): conversation + model + safety mode +77/// workspace binding.78struct AgentTask: Codable, Identifiable, Sendable {79    var id: UUID = UUID()80    var title: String81    var createdAt: Date = Date()82    var updatedAt: Date = Date()83    var pinned: Bool = false84    var status: AgentTaskStatus = .idle85    var modelID: String86    var providerID: ProviderID87    var safetyMode: SafetyMode = .guarded88    /// Active persona (Phase 6): its system-prompt addition is appended to89    /// the agent system prompt on every run of this task; nil = none.90    var personaID: UUID?91    /// Path of the task's workspace directory; nil until the first run92    /// creates it.93    var workspacePath: String?94    /// The user↔agent history (prompts + completed runs).95    var messages: [TaskMessage] = []9697    /// Default title derived from a prompt's leading words.98    static func title(fromPrompt prompt: String) -> String {99        let firstLine = prompt100            .split(separator: "\n", omittingEmptySubsequences: true)101            .first.map(String.init) ?? prompt102        let trimmed = firstLine.trimmingCharacters(in: .whitespacesAndNewlines)103        guard !trimmed.isEmpty else { return "New Task" }104        if trimmed.count <= 48 { return trimmed }105        let cut = trimmed.prefix(48)106        // Break on the last word boundary inside the prefix.107        if let lastSpace = cut.lastIndex(of: " ") {108            return String(cut[..<lastSpace]) + "…"109        }110        return String(cut) + "…"111    }112113    /// Workspace directory URL, when one has been created.114    var workspaceURL: URL? {115        workspacePath.map { URL(fileURLWithPath: $0) }116    }117118    /// Latest persisted run transcript from the workspace, when present.119    func loadTranscript() -> TranscriptDocument? {120        guard let url = workspaceURL else { return nil }121        return Transcript.load(from: url)122    }123}124