native macOS codings agent orchestrator prowl.onev.cat
Something went wrong. Try again.
Swift
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208// supacode/Domain/Workflow/WorkflowLineRenderer.swift// The text a workflow run puts in front of an agent (docs-ai 063, dsl-spec §4/§10): the typed// line formats, the single place that spells `prowl workflow deliver`, the launch protocol block,// and the rendered-text boundary every typed line crosses.
import Foundationimport ProwlCLIShared
/// A rendered line would not survive as one terminal line (`RENDERED_TEXT_INVALID`).nonisolated struct WorkflowRenderedTextError: Error, Equatable, Sendable { let line: String var code: String { CLIErrorCode.renderedTextInvalid } var message: String { "The rendered text contains a line terminator or control character and cannot be typed as one line." }}
/// The rendered-text boundary: every string that reaches `insertCommittedText` + submit is/// validated after template substitution, so a template value can never smuggle a newline/// into a TUI. Violations stop the step; nothing is ever typed partially.nonisolated enum WorkflowRenderedText { static func isSingleLine(_ text: String) -> Bool { WorkflowValidator.isSingleLine(text) }
static func validateLine(_ line: String) throws(WorkflowRenderedTextError) { guard isSingleLine(line) else { throw WorkflowRenderedTextError(line: line) } }}
/// The completion command of one activation. This is the only place that spells/// `prowl workflow deliver`: the typed hint, the scoped-read completion trailer, the launch/// protocol block, the watchdog nudge, every re-delivery, and the `WORKFLOW_DELIVERY_REQUIRED`/// message all read from it, so no path can show a token-less or verdict-less command.nonisolated struct WorkflowCompletionCommand: Equatable, Sendable { static let protocolVersion = 1 static let executable = "prowl workflow deliver" static let commandSeparator = " or "
let token: String /// Declared verdict values; nil when the step has no verdict. let verdicts: [String]?
init(token: String, verdicts: [String]?) { self.token = token self.verdicts = verdicts }
/// `PROWL_WORKFLOW_TOKEN=<token> prowl workflow deliver [--verdict v] -`, one per allowed verdict. var messageCommands: [String] { launchCommands.map { "\(WorkflowSchema.tokenEnvironmentKey)=\(token) \($0)" } }
/// `prowl workflow deliver [--verdict v] -`; the token travels in the launched child's environment. var launchCommands: [String] { guard let verdicts, !verdicts.isEmpty else { return ["\(Self.executable) -"] } return verdicts.map { "\(Self.executable) --verdict \($0) -" } }
/// Appended to a typed prompt or scoped-read line. var typedSuffix: String { " — finish with: " + messageCommands.joined(separator: Self.commandSeparator) }
/// Added to the scoped-read response, not to the saved prompt body. func completionTrailer() -> String { var lines = ["", "---", "When this step is fully complete, finish with"] if messageCommands.count > 1 { lines[lines.count - 1] += " exactly one of:" } else { lines[lines.count - 1] += ":" } lines += messageCommands.map { " \($0)" } lines.append("") return lines.joined(separator: "\n") }
/// The workflow protocol block appended to a `launch` step's kickoff prompt in place of S2's /// dispatch protocol. The token is not spelled: it is set in the child environment exactly /// like `PROWL_DISPATCH_ID`. func protocolBlock( runID: String, workflowName: String, role: String, stepTitle: String?, expect: WorkflowExpectation ) -> String { let (sections, format) = (expect.sections, expect.format) var lines = [ "Prowl workflow completion protocol v\(Self.protocolVersion):", "You are the \"\(role)\" role of the Prowl workflow run \(runID) (\(workflowName)).", ] var delivery = "Deliver the output of this step" if let stepTitle, !stepTitle.isEmpty { delivery += " (\(stepTitle))" } delivery += " as \(Self.formatDescription(format)) on stdin" if !sections.isEmpty { delivery += " with the sections: \(sections.joined(separator: ", "))" } delivery += "." lines.append(delivery) if launchCommands.count > 1 { lines.append( "When your work for this step is fully complete, make exactly one of these commands your final tool action:") } else { lines.append("When your work for this step is fully complete, make this command your final tool action:") } for (index, command) in launchCommands.enumerated() { if index > 0 { lines.append("or:") } lines.append(command) } if launchCommands.count > 1 { lines.append("Choose exactly one verdict.") } lines.append( "Do not use prowl agents dispatch-complete for this step; Prowl supplies the workflow context automatically.") return lines.joined(separator: "\n") + "\n" }
/// The `WORKFLOW_DELIVERY_REQUIRED` message for a `dispatch-complete` issued against this activation. func deliveryRequiredMessage(runID: String, stepID: String) -> String { "This pane's pending dispatch is a workflow activation (run \(runID), step \(stepID)); " + "deliver the step's output instead with: " + messageCommands.joined(separator: Self.commandSeparator) }
private static func formatDescription(_ format: WorkflowDeliveryFormat) -> String { switch format { case .markdown: "a markdown document" case .text: "plain text" case .json: "a JSON document" } }}
/// Typed line formats. Every line Prowl types into a pane starts with `[Prowl] ` so its origin/// is visible; every line is validated as one terminal line before it is returned.nonisolated enum WorkflowTypedLine { static let prefix = AgentDispatchPrompt.injectedPrefix static let nudgeText = "When your work for this step is fully complete, finish with: "
/// `text` → `[Prowl] <text>[ — finish with: …]`. static func text(_ text: String, completion: WorkflowCompletionCommand?) throws(WorkflowRenderedTextError) -> String { try validated(prefix + text + (completion?.typedSuffix ?? "")) }
/// Transport is chosen after template rendering, never by an authored DSL field. static func prompt(_ grant: WorkflowTaskContent, completion: WorkflowCompletionCommand?) throws(WorkflowRenderedTextError) -> String { let inline = grant.text + (grant.resources.isEmpty ? "" : " " + grant.guidance) let line = prefix + inline + (completion?.typedSuffix ?? "") if line.utf8.count <= 4096 && WorkflowRenderedText.isSingleLine(line) { return line } return try text(grant.guidance, completion: completion) }
/// The panel's "Ask again" after a provisional delivery: what was missing, and the command. static func askAgain( issues: [WorkflowDeliveryIssue], completion: WorkflowCompletionCommand ) throws(WorkflowRenderedTextError) -> String { let problems = issues.map(\.message).joined(separator: "; ") return try validated( prefix + "Your delivery for this step had \(problems). Deliver it again, complete, with: " + completion.messageCommands.joined(separator: WorkflowCompletionCommand.commandSeparator)) }
/// The watchdog's one automatic nudge (and the panel's "Nudge again"). static func nudge(completion: WorkflowCompletionCommand) throws(WorkflowRenderedTextError) -> String { try validated( prefix + nudgeText + completion.messageCommands.joined(separator: WorkflowCompletionCommand.commandSeparator)) }
private static func validated(_ line: String) throws(WorkflowRenderedTextError) -> String { try WorkflowRenderedText.validateLine(line) return line }}
nonisolated enum WorkflowLaunchPromptError: Error, Equatable, Sendable { case containsNUL /// `PROMPT_TOO_LARGE`. case tooLarge(bytes: Int)
var code: String { switch self { case .containsNUL: CLIErrorCode.invalidArgument case .tooLarge: CLIErrorCode.promptTooLarge } }}
/// The kickoff prompt of a `launch` step: passed whole through A2's prompt carrier, so it may/// span lines, but NUL is rejected and the rendered prompt is capped (dsl-spec §4).nonisolated enum WorkflowLaunchPrompt { static let maximumBytes = WorkflowSizeLimits.launchPrompt
static func validate(_ prompt: String) throws(WorkflowLaunchPromptError) { guard !prompt.contains("\0") else { throw .containsNUL } let bytes = prompt.utf8.count guard bytes <= maximumBytes else { throw .tooLarge(bytes: bytes) } }
static func render(userPrompt: String, protocolBlock: String?) -> String { guard let protocolBlock else { return userPrompt } return "\(userPrompt)\n\n---\n\(protocolBlock)" }}