Files
james.maes 219532f468 feat(04-04): AgentOrchestrator actor + submit/cancelAndSubmit (AGENT-06)
- AgentOrchestrator actor: turn-lifecycle state machine; LLMProvider
  protocol-driven; injection via providerFactory closure for testability
- submit(_:) returns .ran or .rejected(.turnInFlight)
- cancelAndSubmit(_:) returns .superseded(priorId, .bargedIn); critical
  AGENT-06 actor-reentrancy guard: priorTask.cancel() THEN
  `_ = await priorTask.value` BEFORE assigning new currentTurn (prevents
  late .messageStop from cancelled stream from clobbering currentTurn)
- ReplayLog wired: turnNonce persisted via startTurn(turnNonce:); full
  blob via .toolResultFull (AGENT-08); SEC-06 invariant (nonce never
  appears in OrchestratorEvent emitted to bus)
- ToolResultPacker integration: 8 KB cap on model-facing string (AGENT-08);
  full bytes preserved for replay via separate channel
- Tool-use loop: append assistant tool_use msg, dispatch via
  ToolDispatcher, append wrapped tool_result msg, re-invoke provider
- Architectural deviation (Rule 3 — blocking dep cycle): Replay already
  depends on AgentCore for TurnID; the plan's literal layout would have
  introduced AgentCore→Replay cycle. Resolved by carving a new target
  `AgentOrchestrator` in the AgentCore package, depending on both
  AgentCore and Replay. Plan files_modified paths shifted from
  `Sources/AgentCore/` to `Sources/AgentOrchestrator/` accordingly
- 11 new tests (OrchestratorSubmitTests + OrchestratorCancelTests) plus
  MockLLMProvider scriptable test double; 105 total AgentCore tests green
2026-04-24 18:16:05 -05:00

23 lines
891 B
Swift

import Foundation
/// Internal state machine states surfaced to DevOverlay (Plan 04-05).
///
/// The orchestrator emits `.stateChange(...)` on transitions; the bus
/// forwards these to the HUD which animates the particle ring per state.
public enum TurnState: Sendable, Equatable {
case idle
case booting
case thinking
case speaking // reserved for P6 voice loop; never emitted in P4
case listening // reserved for P6 voice loop; never emitted in P4
case awaitingConfirmation(ConfirmationID)
case reconfiguring
}
/// Stable identifier for a confirmation prompt (`mcp-applescript` etc.).
/// P5 wires real semantics; P4 stubs the type so `TurnState` is complete.
public struct ConfirmationID: Sendable, Hashable, Equatable, RawRepresentable {
public let rawValue: String
public init(rawValue: String) { self.rawValue = rawValue }
}