@@ -11,14 +11,13 @@ use std::collections::HashMap;
1111use std:: path:: Path ;
1212
1313use crate :: ndjson:: { self , SAFE_OUTPUT_FILENAME } ;
14- use crate :: sanitize:: SanitizeContent ;
1514use crate :: safeoutputs:: {
1615 AddBuildTagResult , AddPrCommentResult , CreateBranchResult , CreateGitTagResult ,
1716 CreatePrResult , CreateWikiPageResult , CreateWorkItemResult , CommentOnWorkItemResult ,
18- ExecutionContext , ExecutionResult , Executor , LinkWorkItemsResult , QueueBuildResult ,
19- ReplyToPrCommentResult , ReportIncompleteResult , ResolvePrThreadResult , SubmitPrReviewResult ,
20- ToolResult , UpdatePrResult , UpdateWikiPageResult , UpdateWorkItemResult ,
21- UploadAttachmentResult ,
17+ ExecutionContext , ExecutionResult , Executor , LinkWorkItemsResult , MissingDataResult ,
18+ MissingToolResult , NoopResult , QueueBuildResult , ReplyToPrCommentResult ,
19+ ReportIncompleteResult , ResolvePrThreadResult , SubmitPrReviewResult , ToolResult ,
20+ UpdatePrResult , UpdateWikiPageResult , UpdateWorkItemResult , UploadAttachmentResult ,
2221} ;
2322
2423// Re-export memory types for use by main.rs
@@ -256,10 +255,13 @@ pub async fn execute_safe_output(
256255
257256 debug ! ( "Dispatching tool: {}" , tool_name) ;
258257
259- // Dispatch based on tool name. All standard tools go through `dispatch_tool` which
260- // handles deserialization and sanitized execution uniformly. Special cases (informational
261- // outputs and report-incomplete) are handled inline.
258+ // Dispatch based on tool name. All registered tools go through `dispatch_tool`,
259+ // which handles deserialization and sanitized execution uniformly.
262260 let result = if let Some ( dispatched_result) = dispatch_executor_tools ! ( tool_name, entry, ctx, {
261+ "noop" => NoopResult ,
262+ "missing-tool" => MissingToolResult ,
263+ "missing-data" => MissingDataResult ,
264+ "report-incomplete" => ReportIncompleteResult ,
263265 "create-work-item" => CreateWorkItemResult ,
264266 "comment-on-work-item" => CommentOnWorkItemResult ,
265267 "update-work-item" => UpdateWorkItemResult ,
@@ -280,25 +282,8 @@ pub async fn execute_safe_output(
280282 } ) ? {
281283 dispatched_result
282284 } else {
283- match tool_name {
284- // Informational outputs — no side effects, always succeed
285- "noop" | "missing-tool" | "missing-data" => {
286- debug ! ( "Skipping informational entry: {}" , tool_name) ;
287- ExecutionResult :: success ( format ! ( "Skipped informational output: {}" , tool_name) )
288- }
289- // report-incomplete does not implement Executor; Stage 3 surfaces its reason as a failure
290- "report-incomplete" => {
291- let mut output: ReportIncompleteResult = serde_json:: from_value ( entry. clone ( ) )
292- . map_err ( |e| anyhow:: anyhow!( "Failed to parse report-incomplete: {}" , e) ) ?;
293- output. sanitize_content_fields ( ) ;
294- debug ! ( "report-incomplete: {}" , output. reason) ;
295- ExecutionResult :: failure ( format ! ( "Agent reported task incomplete: {}" , output. reason) )
296- }
297- other => {
298- error ! ( "Unknown tool type: {}" , other) ;
299- bail ! ( "Unknown tool type: {}. No executor registered." , other)
300- }
301- }
285+ error ! ( "Unknown tool type: {}" , tool_name) ;
286+ bail ! ( "Unknown tool type: {}. No executor registered." , tool_name)
302287 } ;
303288
304289 Ok ( ( tool_name. to_string ( ) , result) )
@@ -470,7 +455,7 @@ mod tests {
470455 let ( tool_name, result) = result. unwrap ( ) ;
471456 assert_eq ! ( tool_name, "noop" ) ;
472457 assert ! ( result. success) ;
473- assert ! ( result. message. contains( "Skipped " ) ) ;
458+ assert ! ( result. message. contains( "No operation " ) ) ;
474459 }
475460
476461 #[ tokio:: test]
@@ -1050,9 +1035,9 @@ mod tests {
10501035 assert_eq ! ( results. len( ) , 2 ) ;
10511036 // create-work-item goes through Executor trait → dry-run intercepted
10521037 assert ! ( results[ 0 ] . message. contains( "[DRY-RUN]" ) ) ;
1053- // noop is handled inline, not through Executor → runs normally
1038+ // noop now also goes through Executor trait → dry-run intercepted
10541039 assert ! ( results[ 1 ] . success) ;
1055- assert ! ( results[ 1 ] . message. contains( "noop " ) ) ;
1040+ assert ! ( results[ 1 ] . message. contains( "[DRY-RUN] " ) ) ;
10561041 }
10571042
10581043 #[ tokio:: test]
@@ -1124,8 +1109,8 @@ mod tests {
11241109
11251110 #[ tokio:: test]
11261111 async fn test_dry_run_report_incomplete_still_fails ( ) {
1127- // report-incomplete is dispatched inline (not through Executor trait),
1128- // so it still returns ExecutionResult::failure even in dry-run mode.
1112+ // report-incomplete uses an Executor override that still returns
1113+ // ExecutionResult::failure even in dry-run mode.
11291114 // This is correct: the agent declared it couldn't complete the task.
11301115 let entry = serde_json:: json!( {
11311116 "name" : "report-incomplete" ,
0 commit comments