@@ -155,7 +155,7 @@ func buildTools(server *Server) map[string]Tool {
155155 return "/api/v2/projects/" + projectID + "/reports/run-plans/" + runID , nil , nil
156156 }),
157157 "codencer.get_gateway_run_events" : server .gatewayRunEventsTool (),
158- "codencer.cancel_project_run" : server .unsupportedProjectLifecycleTool ("codencer.cancel_project_run" , "Return an explicit capability blocker for project- run cancellation when the selected route cannot cancel safely ." , "cancel_project_run" ),
158+ "codencer.cancel_project_run" : server .projectForwardTool ("codencer.cancel_project_run" , "Cancel a shared project run through the selected Gateway relay ." , [] string { "project_id" , "run_id" }, cancelProjectRunRoute ),
159159 "codencer.resume_project_run" : server .unsupportedProjectLifecycleTool ("codencer.resume_project_run" , "Return an explicit capability blocker for project-run resume when the selected route cannot resume safely." , "resume_project_run" ),
160160 "codencer.get_blocker" : {
161161 Name : "codencer.get_blocker" ,
@@ -262,6 +262,10 @@ func (s *Server) projectForwardTool(name, description string, required []string,
262262 return ToolResult {}, apiErr
263263 }
264264 path = appendSelector (path , args )
265+ if name == "codencer.cancel_project_run" {
266+ auditMetadata = projectLifecycleAuditMetadata (match , args )
267+ s .recordGatewayAuditWithMetadata (ctx , principal , "cancel_project_run_requested" , "Requested cancel_project_run for run " + requiredStringValue (args , "run_id" )+ " in project " + projectID , auditMetadata )
268+ }
265269 method := http .MethodGet
266270 if body != nil {
267271 method = http .MethodPost
@@ -275,6 +279,8 @@ func (s *Server) projectForwardTool(name, description string, required []string,
275279 eventType := "run_failed"
276280 if name == "codencer.get_run_report" {
277281 eventType = "report_read"
282+ } else if name == "codencer.cancel_project_run" {
283+ auditMetadata = projectLifecycleAuditMetadata (match , args )
278284 } else {
279285 runRecord .Status = "failed"
280286 runRecord .ResultSummary = "Gateway relay call failed: " + apiErr .Code
@@ -288,6 +294,10 @@ func (s *Server) projectForwardTool(name, description string, required []string,
288294 _ = record
289295 s .recordTerminalRunAuditOnce (ctx , principal , projectID , payload , metadata )
290296 s .recordGatewayAuditWithMetadata (ctx , principal , "report_read" , "Read run report " + requiredStringValue (args , "run_id" )+ " for project " + projectID , metadata )
297+ } else if name == "codencer.cancel_project_run" {
298+ record , metadata := s .refreshRunRecordFromLifecyclePayload (ctx , principal , match , args , payload , reportStatusForPayload (payload , "available" ))
299+ _ = record
300+ s .recordGatewayAuditWithMetadata (ctx , principal , terminalAuditType (payload ), terminalAuditSummary (projectID , payload ), metadata )
291301 } else if recordRun {
292302 runRecord , auditMetadata = s .finishRunRecord (ctx , runRecord , payload , reportStatusForPayload (payload , "available" ))
293303 if obj , ok := payload .(map [string ]any ); ok && runRecord .ID != "" {
@@ -460,6 +470,28 @@ func selectedProjectLocation(project relayProject, args map[string]any) projectL
460470 return projectLocation {}
461471}
462472
473+ func projectLifecycleAuditMetadata (match relayProjectMatch , args map [string ]any ) map [string ]any {
474+ metadata := map [string ]any {
475+ "project_id" : match .Project .ProjectID ,
476+ "run_id" : requiredStringValue (args , "run_id" ),
477+ "relay_profile_id" : match .Profile .ID ,
478+ }
479+ if runHistoryID := strings .TrimSpace (stringArg (args , "run_history_id" )); runHistoryID != "" {
480+ metadata ["run_history_id" ] = runHistoryID
481+ }
482+ location := selectedProjectLocation (match .Project , args )
483+ for key , value := range map [string ]string {
484+ "connector_id" : location .ConnectorID ,
485+ "machine_id" : location .MachineID ,
486+ "host_label" : location .HostLabel ,
487+ } {
488+ if strings .TrimSpace (value ) != "" {
489+ metadata [key ] = strings .TrimSpace (value )
490+ }
491+ }
492+ return metadata
493+ }
494+
463495func resolvedExecutorLabel (project relayProject , args map [string ]any ) string {
464496 return firstNonEmpty (
465497 strings .TrimSpace (stringArg (args , "profile" )),
@@ -491,6 +523,10 @@ func terminalAuditType(payload any) string {
491523 switch status {
492524 case "submitted" , "started" , "starting" , "queued" , "pending" , "running" , "in_progress" , "validating" :
493525 return "run_started"
526+ case "cancel_requested" , "cancelling" , "canceling" :
527+ return "run_cancel_requested"
528+ case "cancelled" , "canceled" :
529+ return "run_cancelled"
494530 case "failed" , "failed_adapter" , "failed_bridge" , "failed_validation" , "timeout" , "adapter_error" , "bridge_error" :
495531 return "run_failed"
496532 case "blocked" , "question" , "manual_approval_required" , "needs_approval" , "needs_manual_attention" , "permission_request_required" , "unsafe_action" , "validation_failed" :
@@ -510,6 +546,10 @@ func terminalAuditSummary(projectID string, payload any) string {
510546 outcome = "failed"
511547 case "blocker" :
512548 outcome = "reached blocker"
549+ case "run_cancel_requested" :
550+ outcome = "cancel requested"
551+ case "run_cancelled" :
552+ outcome = "cancelled"
513553 }
514554 if runID == "" {
515555 return "Run " + outcome + " for project " + projectID
@@ -686,6 +726,24 @@ func getProjectRunRoute(args map[string]any) (string, []byte, *apiError) {
686726 return "/api/v2/projects/" + projectID + "/runs/" + runID , nil , nil
687727}
688728
729+ func cancelProjectRunRoute (args map [string ]any ) (string , []byte , * apiError ) {
730+ projectID , apiErr := requiredString (args , "project_id" )
731+ if apiErr != nil {
732+ return "" , nil , apiErr
733+ }
734+ runID , apiErr := requiredString (args , "run_id" )
735+ if apiErr != nil {
736+ return "" , nil , apiErr
737+ }
738+ payload := map [string ]any {}
739+ copyOptional (payload , args , "reason" )
740+ body , apiErr := jsonBody (payload )
741+ if apiErr != nil {
742+ return "" , nil , apiErr
743+ }
744+ return "/api/v2/projects/" + projectID + "/runs/" + runID + "/cancel" , body , nil
745+ }
746+
689747func submitProjectTaskRoute (wait bool ) func (map [string ]any ) (string , []byte , * apiError ) {
690748 return func (args map [string ]any ) (string , []byte , * apiError ) {
691749 projectID , apiErr := requiredString (args , "project_id" )
0 commit comments