@@ -40,21 +40,23 @@ pub mod handoff;
4040pub mod mode;
4141pub mod nightwatch;
4242pub mod persona;
43+ pub mod output_poster;
4344pub mod scheduler;
4445pub mod scope;
4546
4647pub use compound:: { CompoundReviewResult , CompoundReviewWorkflow , ReviewGroupDef , SwarmConfig } ;
4748pub use concurrency:: { ConcurrencyController , FairnessPolicy , ModeQuotas } ;
4849pub use config:: {
49- AgentDefinition , AgentLayer , CompoundReviewConfig , ConcurrencyConfig , NightwatchConfig ,
50- OrchestratorConfig , TrackerConfig , TrackerStates , WorkflowConfig ,
50+ AgentDefinition , AgentLayer , CompoundReviewConfig , ConcurrencyConfig , GiteaOutputConfig ,
51+ NightwatchConfig , OrchestratorConfig , TrackerConfig , TrackerStates , WorkflowConfig ,
5152} ;
5253pub use cost_tracker:: { BudgetVerdict , CostSnapshot , CostTracker } ;
5354pub use dispatcher:: { DispatchTask , Dispatcher , DispatcherStats } ;
5455pub use dual_mode:: DualModeOrchestrator ;
5556pub use error:: OrchestratorError ;
5657pub use handoff:: { HandoffBuffer , HandoffContext , HandoffLedger } ;
5758pub use mode:: { IssueMode , TimeMode } ;
59+ pub use output_poster:: OutputPoster ;
5860pub use nightwatch:: {
5961 dual_panel_evaluate, validate_certificate, Claim , CorrectionAction , CorrectionLevel ,
6062 DriftAlert , DriftMetrics , DriftScore , DualPanelResult , NightwatchMonitor , RateLimitTracker ,
@@ -129,6 +131,8 @@ pub struct AgentOrchestrator {
129131 persona_registry : PersonaRegistry ,
130132 /// Renderer for persona metaprompts.
131133 metaprompt_renderer : MetapromptRenderer ,
134+ /// Output poster for posting agent output to Gitea issues.
135+ output_poster : Option < OutputPoster > ,
132136 /// Circuit breakers for each provider to prevent cascading failures.
133137 #[ allow( dead_code) ]
134138 circuit_breakers : Arc < Mutex < HashMap < String , CircuitBreaker > > > ,
@@ -203,6 +207,9 @@ impl AgentOrchestrator {
203207 None => MetapromptRenderer :: new ( ) . expect ( "default template should always compile" ) ,
204208 } ;
205209
210+ // Initialize output poster if Gitea config is provided
211+ let output_poster = config. gitea . as_ref ( ) . map ( OutputPoster :: new) ;
212+
206213 Ok ( Self {
207214 config,
208215 spawner,
@@ -221,6 +228,7 @@ impl AgentOrchestrator {
221228 cost_tracker,
222229 persona_registry,
223230 metaprompt_renderer,
231+ output_poster,
224232 circuit_breakers : Arc :: new ( Mutex :: new ( HashMap :: new ( ) ) ) ,
225233 active_flows : HashMap :: new ( ) ,
226234 } )
@@ -736,7 +744,35 @@ impl AgentOrchestrator {
736744 }
737745 }
738746
739- // Process exits
747+ // Drain output from exiting agents BEFORE removing them
748+ for ( name, def, status) in & exited {
749+ // Drain remaining output events
750+ let mut output_lines: Vec < String > = Vec :: new ( ) ;
751+ if let Some ( managed) = self . active_agents . get_mut ( name) {
752+ while let Ok ( event) = managed. output_rx . try_recv ( ) {
753+ self . nightwatch . observe ( name, & event) ;
754+ match & event {
755+ crate :: OutputEvent :: Stdout { line, .. } => {
756+ output_lines. push ( line. clone ( ) ) ;
757+ }
758+ crate :: OutputEvent :: Stderr { line, .. } => {
759+ output_lines. push ( format ! ( "[stderr] {}" , line) ) ;
760+ }
761+ _ => { }
762+ }
763+ }
764+ }
765+
766+ // Post output to Gitea if configured
767+ if let ( Some ( poster) , Some ( issue) ) = ( & self . output_poster , def. gitea_issue ) {
768+ let exit_code = status. code ( ) ;
769+ if let Err ( e) = poster. post_agent_output ( name, issue, & output_lines, exit_code) . await {
770+ warn ! ( agent = %name, issue = issue, error = %e, "failed to post output to Gitea" ) ;
771+ }
772+ }
773+ }
774+
775+ // NOW remove from active_agents and handle exits
740776 for ( name, def, status) in exited {
741777 self . active_agents . remove ( & name) ;
742778 self . handle_agent_exit ( & name, & def, status) ;
@@ -1100,6 +1136,7 @@ mod tests {
11001136 fallback_model: None ,
11011137 grace_period_secs: None ,
11021138 max_cpu_seconds: None ,
1139+ gitea_issue: None ,
11031140 } ,
11041141 AgentDefinition {
11051142 name: "sync" . to_string( ) ,
@@ -1120,6 +1157,7 @@ mod tests {
11201157 fallback_model: None ,
11211158 grace_period_secs: None ,
11221159 max_cpu_seconds: None ,
1160+ gitea_issue: None ,
11231161 } ,
11241162 ] ,
11251163 restart_cooldown_secs : 60 ,
@@ -1129,6 +1167,7 @@ mod tests {
11291167 persona_data_dir : None ,
11301168 flows : vec ! [ ] ,
11311169 flow_state_dir : None ,
1170+ gitea : None ,
11321171 }
11331172 }
11341173
@@ -1293,6 +1332,7 @@ task = "test"
12931332 fallback_model: None ,
12941333 grace_period_secs: None ,
12951334 max_cpu_seconds: None ,
1335+ gitea_issue: None ,
12961336 } ] ,
12971337 restart_cooldown_secs : 0 , // instant restart for testing
12981338 max_restart_count : 3 ,
@@ -1301,6 +1341,7 @@ task = "test"
13011341 persona_data_dir : None ,
13021342 flows : vec ! [ ] ,
13031343 flow_state_dir : None ,
1344+ gitea : None ,
13041345 }
13051346 }
13061347
@@ -1376,6 +1417,7 @@ task = "test"
13761417 fallback_model: None ,
13771418 grace_period_secs: None ,
13781419 max_cpu_seconds: None ,
1420+ gitea_issue: None ,
13791421 } ] ;
13801422 let mut orch = AgentOrchestrator :: new ( config) . unwrap ( ) ;
13811423
@@ -1510,6 +1552,7 @@ task = "test"
15101552 fallback_model: None ,
15111553 grace_period_secs: None ,
15121554 max_cpu_seconds: None ,
1555+ gitea_issue: None ,
15131556 } ] ;
15141557
15151558 // Set up persona data dir with a test persona
@@ -1592,6 +1635,7 @@ sfia_skills = [{ code = "TEST", name = "Testing", level = 4, description = "Desi
15921635 fallback_model: None ,
15931636 grace_period_secs: None ,
15941637 max_cpu_seconds: None ,
1638+ gitea_issue: None ,
15951639 } ] ;
15961640
15971641 // No persona_data_dir, so registry will be empty
0 commit comments