native macOS codings agent orchestrator prowl.onev.cat
Something went wrong. Try again.
Swift
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397import Foundationimport Testing
@testable import Prowl
struct AgentRuntimeAdapterTests { private struct CatalogExpectation { let runtime: AgentProfileRuntime let agent: DetectedAgent let executable: String let arguments: [String] }
private struct IntentExpectation { let runtime: AgentProfileRuntime let promptedArguments: [String] let headlessArguments: [String] }
@Test func profileRuntimeCatalogCoversEveryLaunchableCLI() throws { let expected = [ CatalogExpectation(runtime: .claude, agent: .claude, executable: "claude", arguments: []), CatalogExpectation(runtime: .codex, agent: .codex, executable: "codex", arguments: []), CatalogExpectation(runtime: .gemini, agent: .gemini, executable: "gemini", arguments: []), CatalogExpectation(runtime: .cursor, agent: .cursor, executable: "cursor-agent", arguments: []), CatalogExpectation( runtime: .cline, agent: .cline, executable: "cline", arguments: ["--auto-approve", "false", "--tui"]), CatalogExpectation(runtime: .opencode, agent: .opencode, executable: "opencode", arguments: []), CatalogExpectation(runtime: .copilot, agent: .copilot, executable: "copilot", arguments: []), CatalogExpectation(runtime: .kimi, agent: .kimi, executable: "kimi", arguments: []), CatalogExpectation( runtime: .devin, agent: .devin, executable: "devin", arguments: ["--permission-mode", "auto"]), CatalogExpectation(runtime: .droid, agent: .droid, executable: "droid", arguments: []), CatalogExpectation(runtime: .amp, agent: .amp, executable: "amp", arguments: []), CatalogExpectation(runtime: .qoder, agent: .qoder, executable: "qodercli", arguments: []), CatalogExpectation(runtime: .qwen, agent: .qwen, executable: "qwen", arguments: []), CatalogExpectation( runtime: .grok, agent: .grok, executable: "grok", arguments: ["--permission-mode", "default"]), CatalogExpectation(runtime: .pi, agent: .pi, executable: "pi", arguments: []), CatalogExpectation( runtime: .omp, agent: .omp, executable: "omp", arguments: ["--approval-mode", "always-ask"]), CatalogExpectation(runtime: .antigravity, agent: .antigravity, executable: "agy", arguments: []), ]
#expect(AgentProfileRuntime.allCases.count == expected.count) for expectation in expected { let invocation = try AgentRuntimeAdapterRegistry.makeStartInvocation( AgentStartRequest(runtime: expectation.runtime, intent: .interactive) ) #expect(expectation.runtime.agent == expectation.agent) #expect(invocation.executable == expectation.executable) #expect(invocation.arguments == expectation.arguments) } #expect(AgentRuntimeAdapterRegistry.launchableAgents == DetectedAgent.allCases) }
@Test func promptedAndHeadlessStartsUseRuntimeSpecificCLISemantics() throws { let expected = [ IntentExpectation( runtime: .claude, promptedArguments: ["Review this."], headlessArguments: ["-p", "Review this."]), IntentExpectation( runtime: .codex, promptedArguments: ["Review this."], headlessArguments: ["exec", "Review this."]), IntentExpectation( runtime: .gemini, promptedArguments: ["--prompt-interactive", "Review this."], headlessArguments: ["--prompt", "Review this."]), IntentExpectation( runtime: .cursor, promptedArguments: ["Review this."], headlessArguments: ["--print", "Review this."]), IntentExpectation( runtime: .cline, promptedArguments: ["--auto-approve", "false", "--tui", "Review this."], headlessArguments: ["--auto-approve", "false", "Review this."]), IntentExpectation( runtime: .opencode, promptedArguments: ["--prompt", "Review this."], headlessArguments: ["run", "Review this."]), IntentExpectation( runtime: .copilot, promptedArguments: ["--interactive", "Review this."], headlessArguments: ["--prompt", "Review this."]), IntentExpectation( runtime: .kimi, promptedArguments: ["--prompt", "Review this."], headlessArguments: ["--print", "--prompt", "Review this."]), IntentExpectation( runtime: .devin, promptedArguments: ["--permission-mode", "auto", "--", "Review this."], headlessArguments: ["--permission-mode", "auto", "--print", "--", "Review this."]), IntentExpectation( runtime: .droid, promptedArguments: ["Review this."], headlessArguments: ["exec", "Review this."]), IntentExpectation( runtime: .qoder, promptedArguments: ["--prompt-interactive", "Review this."], headlessArguments: ["--print", "Review this."]), IntentExpectation( runtime: .qwen, promptedArguments: ["--prompt-interactive", "Review this."], headlessArguments: ["--prompt", "Review this."]), IntentExpectation( runtime: .grok, promptedArguments: ["--permission-mode", "default", "Review this."], headlessArguments: ["--permission-mode", "default", "--single", "Review this."]), IntentExpectation( runtime: .pi, promptedArguments: ["Review this."], headlessArguments: ["--print", "Review this."]), IntentExpectation( runtime: .omp, promptedArguments: ["--approval-mode", "always-ask", "Review this."], headlessArguments: ["--approval-mode", "always-ask", "--print", "Review this."]), IntentExpectation( runtime: .antigravity, promptedArguments: ["--prompt-interactive", "Review this."], headlessArguments: ["--print", "Review this."]), ]
for expectation in expected { let prompted = try AgentRuntimeAdapterRegistry.makeStartInvocation( AgentStartRequest(runtime: expectation.runtime, intent: .prompt("Review this.")) ) let headless = try AgentRuntimeAdapterRegistry.makeStartInvocation( AgentStartRequest(runtime: expectation.runtime, intent: .headless("Review this.")) ) #expect(prompted.arguments == expectation.promptedArguments) #expect(headless.arguments == expectation.headlessArguments) }
#expect( throws: AgentRuntimeError.unsupportedStartIntent(.amp, .prompt("Review this.")) ) { try AgentRuntimeAdapterRegistry.makeStartInvocation( AgentStartRequest(runtime: .amp, intent: .prompt("Review this.")) ) } let ampHeadless = try AgentRuntimeAdapterRegistry.makeStartInvocation( AgentStartRequest(runtime: .amp, intent: .headless("Review this.")) ) #expect(ampHeadless.arguments == ["--execute", "Review this."]) }
@Test func profileOptionsAreCapabilityGated() throws { let noModel: Set<AgentProfileRuntime> = [.droid, .amp] let noReasoning: Set<AgentProfileRuntime> = [.gemini, .cursor, .kimi, .droid, .devin] let executionModeSelection: Set<AgentProfileRuntime> = [ .claude, .codex, .gemini, .cursor, .cline, .opencode, .copilot, .kimi, .qoder, .qwen, .grok, .omp, .devin, .antigravity, ] let isolated: Set<AgentProfileRuntime> = [ .claude, .codex, .gemini, .cline, .copilot, .qoder, .qwen, .pi, .omp, ]
for runtime in AgentProfileRuntime.allCases { let adapter = try #require(AgentRuntimeAdapterRegistry.profileAdapter(for: runtime)) #expect(adapter.supportsModelSelection == !noModel.contains(runtime)) #expect(adapter.supportsReasoningEffort == !noReasoning.contains(runtime)) #expect( adapter.executionModeOptions == (executionModeSelection.contains(runtime) ? [.standard, .unrestricted] : []) ) #expect(adapter.supportsAccountIsolation == isolated.contains(runtime)) } }
@Test func expandedRuntimeOptionsRenderOnlyVerifiedFields() throws { let configuration = AgentLaunchConfiguration( model: "model-x", executionMode: .unrestricted, reasoningEffort: "high" ) let expected: [(AgentProfileRuntime, [String])] = [ (.gemini, ["--model", "model-x", "--approval-mode", "yolo", "--sandbox=false"]), (.cursor, ["--model", "model-x", "--yolo", "--sandbox", "disabled"]), (.cline, ["--model", "model-x", "--thinking", "high", "--auto-approve", "true", "--tui"]), (.opencode, ["--model", "model-x", "--variant", "high", "--auto"]), (.copilot, ["--model", "model-x", "--reasoning-effort", "high", "--allow-all"]), (.kimi, ["--model", "model-x", "--yolo"]), (.droid, []), (.amp, ["--effort", "high"]), (.qoder, ["--model", "model-x", "--reasoning-effort", "high", "--dangerously-skip-permissions"]), (.qwen, ["--model", "model-x", "--reasoning-effort", "high", "--approval-mode", "yolo", "--sandbox=false"]), ( .grok, [ "--model", "model-x", "--reasoning-effort", "high", "--permission-mode", "bypassPermissions", "--sandbox", "off", ] ), (.pi, ["--model", "model-x", "--thinking", "high"]), (.omp, ["--model", "model-x", "--thinking", "high", "--approval-mode", "yolo"]), (.antigravity, ["--model", "model-x", "--effort", "high", "--dangerously-skip-permissions"]), ]
for (runtime, arguments) in expected { let invocation = try AgentRuntimeAdapterRegistry.makeStartInvocation( AgentStartRequest(runtime: runtime, intent: .interactive, configuration: configuration) ) #expect(invocation.arguments == arguments) } }
@Test func guardedModesOverrideUnsafeRuntimeDefaults() throws { let expected: [(AgentProfileRuntime, [String])] = [ (.cline, ["--auto-approve", "false", "--tui"]), (.grok, ["--permission-mode", "default"]), (.omp, ["--approval-mode", "always-ask"]), ]
for (runtime, arguments) in expected { let invocation = try AgentRuntimeAdapterRegistry.makeStartInvocation( AgentStartRequest(runtime: runtime, intent: .interactive) ) #expect(invocation.arguments == arguments) } }
@Test func launchObservationUsesTheLastOccurrenceOfAnOption() { let cline = AgentRuntimeAdapterRegistry.observe( runtime: .cline, arguments: ["cline", "--auto-approve", "false", "--auto-approve", "true"] ) #expect(cline.executionMode == .unrestricted)
let omp = AgentRuntimeAdapterRegistry.observe( runtime: .omp, arguments: ["omp", "--approval-mode", "always-ask", "--approval-mode=yolo"] ) #expect(omp.executionMode == .unrestricted) }
@Test func codexStartBuildsUnrestrictedInvocation() throws { let invocation = try AgentRuntimeAdapterRegistry.makeStartInvocation( AgentStartRequest( agent: .codex, intent: .prompt("Continue the handoff."), configuration: AgentLaunchConfiguration(model: "gpt-5.4", executionMode: .unrestricted) ) )
#expect(invocation.executable == "codex") #expect( invocation.arguments == ["--model", "gpt-5.4", "--dangerously-bypass-approvals-and-sandbox", "Continue the handoff."] ) }
@Test func interactiveStartRendersNoPromptArgument() throws { let codex = try AgentRuntimeAdapterRegistry.makeStartInvocation( AgentStartRequest(agent: .codex, intent: .interactive) ) #expect(codex.executable == "codex") #expect(codex.arguments.isEmpty)
let claude = try AgentRuntimeAdapterRegistry.makeStartInvocation( AgentStartRequest( agent: .claude, intent: .interactive, configuration: AgentLaunchConfiguration(model: "claude-opus-5") ) ) #expect(claude.executable == "claude") #expect(claude.arguments == ["--model", "claude-opus-5"]) }
@Test func headlessStartRendersOneShotExecutionMode() throws { let codex = try AgentRuntimeAdapterRegistry.makeStartInvocation( AgentStartRequest(agent: .codex, intent: .headless("Summarize the repo.")) ) #expect(codex.arguments == ["exec", "Summarize the repo."])
let claude = try AgentRuntimeAdapterRegistry.makeStartInvocation( AgentStartRequest(agent: .claude, intent: .headless("Summarize the repo.")) ) #expect(claude.arguments == ["-p", "Summarize the repo."]) }
@Test func reasoningEffortMapsToRuntimeSpecificOptions() throws { let claude = try AgentRuntimeAdapterRegistry.makeStartInvocation( AgentStartRequest( agent: .claude, intent: .interactive, configuration: AgentLaunchConfiguration(reasoningEffort: "high") ) ) #expect(claude.arguments == ["--effort", "high"])
let codex = try AgentRuntimeAdapterRegistry.makeStartInvocation( AgentStartRequest( agent: .codex, intent: .interactive, configuration: AgentLaunchConfiguration(reasoningEffort: "xhigh") ) ) #expect(codex.arguments == ["-c", "model_reasoning_effort=xhigh"]) }
@Test func extraArgumentsAppendAfterAdapterOptionsBeforePrompt() throws { let invocation = try AgentRuntimeAdapterRegistry.makeStartInvocation( AgentStartRequest( agent: .codex, intent: .prompt("Go."), configuration: AgentLaunchConfiguration( model: "gpt-5.4", extraArguments: ["--search", "--cd", "/tmp/with space"] ) ) ) #expect( invocation.arguments == ["--model", "gpt-5.4", "--search", "--cd", "/tmp/with space", "Go."] ) }
@Test func accountIsolationCapabilityIsDeclaredPerAdapter() { let codex = AgentRuntimeAdapterRegistry.adapter(for: .codex) #expect(codex?.supportsAccountIsolation == true) #expect(codex?.accountHomeEnvironmentVariable == "CODEX_HOME")
let claude = AgentRuntimeAdapterRegistry.adapter(for: .claude) #expect(claude?.supportsAccountIsolation == true) #expect(claude?.accountHomeEnvironmentVariable == "CLAUDE_CONFIG_DIR") #expect(claude?.reasoningEffortSuggestions.contains("high") == true) }
@Test func runtimeSuggestionsMatchCurrentCapabilities() { let codex = AgentRuntimeAdapterRegistry.adapter(for: .codex) #expect( codex?.modelSuggestions == [ "gpt-6.1-sol", "gpt-6-astra", "gpt-6-sol", "gpt-6-luna", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", ] ) #expect(codex?.reasoningEffortSuggestions == ["low", "medium", "high", "xhigh", "max"])
let claude = AgentRuntimeAdapterRegistry.adapter(for: .claude) #expect( claude?.modelSuggestions == [ "claude-fable-5-1", "claude-opus-5-5", "claude-sonnet-5", "claude-haiku-4-5-20251001", "claude-fable-5", "claude-opus-5", ] ) #expect(claude?.reasoningEffortSuggestions == ["low", "medium", "high", "xhigh", "max"]) }
@Test func launchConfigurationDecodesLegacyPayloadWithoutNewFields() throws { let legacy = Data(#"{"model":"gpt-5.4","executionMode":"standard"}"#.utf8) let configuration = try JSONDecoder().decode(AgentLaunchConfiguration.self, from: legacy) #expect(configuration.model == "gpt-5.4") #expect(configuration.reasoningEffort == nil) #expect(configuration.extraArguments.isEmpty) }
@Test func observedLaunchOnlyClaimsExplicitUnrestrictedMode() { let codex = AgentRuntimeAdapterRegistry.observe( agent: .codex, arguments: ["codex", "--model", "gpt-5.4", "--dangerously-bypass-approvals-and-sandbox"] ) #expect(codex.model == "gpt-5.4") #expect(codex.executionMode == .unrestricted)
let yolo = AgentRuntimeAdapterRegistry.observe(agent: .codex, arguments: ["codex", "--yolo"]) #expect(yolo.executionMode == .unrestricted)
let claude = AgentRuntimeAdapterRegistry.observe( agent: .claude, arguments: ["claude", "--allow-dangerously-skip-permissions"] ) #expect(claude.executionMode == nil) }
@Test func inheritedConfigurationCarriesOnlyPortableSourceIntent() { let observation = AgentLaunchObservation(model: "gpt-5.4", executionMode: .unrestricted)
let claude = AgentRuntimeAdapterRegistry.inheritedConfiguration( from: .codex, observation: observation, to: .claude ) #expect(claude.model == nil) #expect(claude.executionMode == .unrestricted)
let codex = AgentRuntimeAdapterRegistry.inheritedConfiguration( from: .codex, observation: observation, to: .codex ) #expect(codex.model == "gpt-5.4") #expect(codex.executionMode == .unrestricted) }
@Test func terminalInputQuotesEveryArgument() { let invocation = AgentInvocation( executable: "codex", arguments: ["exec", "resume", "session id", "Write 'current.md'\nwithout shell injection."] )
#expect( invocation.terminalInput == "'codex' 'exec' 'resume' 'session id' 'Write '\"'\"'current.md'\"'\"'\nwithout shell injection.'" ) }
}