@@ -13,6 +13,12 @@ import type {
1313 CommandExecutionStatus ,
1414 DynamicToolCallStatus ,
1515 FileUpdateChange ,
16+ GuardianApprovalReview ,
17+ GuardianApprovalReviewAction ,
18+ GuardianApprovalReviewStatus ,
19+ GuardianCommandSource ,
20+ ItemGuardianApprovalReviewCompletedNotification ,
21+ ItemGuardianApprovalReviewStartedNotification ,
1622 McpToolCallError ,
1723 McpToolCallResult ,
1824 McpToolCallStatus ,
@@ -24,6 +30,9 @@ import {logger} from "./Logger";
2430
2531type CodexItemStatus = CommandExecutionStatus | PatchApplyStatus | McpToolCallStatus | DynamicToolCallStatus ;
2632type AcpToolCallStatus = "pending" | "in_progress" | "completed" | "failed" ;
33+ type GuardianApprovalReviewNotification =
34+ | ItemGuardianApprovalReviewStartedNotification
35+ | ItemGuardianApprovalReviewCompletedNotification ;
2736
2837function toAcpStatus ( status : CodexItemStatus ) : AcpToolCallStatus {
2938 switch ( status ) {
@@ -143,6 +152,36 @@ export function createMcpRawOutput(
143152 } ;
144153}
145154
155+ export function guardianApprovalReviewToolCallId ( reviewId : string ) : string {
156+ return `guardian_assessment:${ reviewId } ` ;
157+ }
158+
159+ export function createGuardianApprovalReviewToolCall (
160+ event : GuardianApprovalReviewNotification ,
161+ ) : UpdateSessionEvent {
162+ return {
163+ sessionUpdate : "tool_call" ,
164+ toolCallId : guardianApprovalReviewToolCallId ( event . reviewId ) ,
165+ kind : "think" ,
166+ title : "Guardian Review" ,
167+ status : toAcpGuardianApprovalReviewStatus ( event . review . status ) ,
168+ content : createGuardianApprovalReviewContent ( event . review , event . action ) ,
169+ rawInput : event as unknown as Record < string , JsonValue > ,
170+ } ;
171+ }
172+
173+ export function createGuardianApprovalReviewToolCallUpdate (
174+ event : GuardianApprovalReviewNotification ,
175+ ) : UpdateSessionEvent {
176+ return {
177+ sessionUpdate : "tool_call_update" ,
178+ toolCallId : guardianApprovalReviewToolCallId ( event . reviewId ) ,
179+ status : toAcpGuardianApprovalReviewStatus ( event . review . status ) ,
180+ content : createGuardianApprovalReviewContent ( event . review , event . action ) ,
181+ rawOutput : event as unknown as Record < string , JsonValue > ,
182+ } ;
183+ }
184+
146185export function fuzzyFileSearchToolCallId ( sessionId : string ) : string {
147186 return `fuzzyFileSearch.${ sessionId } ` ;
148187}
@@ -257,6 +296,108 @@ function createSearchTitle(query: string | null, path: string | null): string {
257296 return "Search" ;
258297}
259298
299+ function toAcpGuardianApprovalReviewStatus ( status : GuardianApprovalReviewStatus ) : AcpToolCallStatus {
300+ switch ( status ) {
301+ case "inProgress" :
302+ return "in_progress" ;
303+ case "approved" :
304+ return "completed" ;
305+ case "denied" :
306+ case "aborted" :
307+ case "timedOut" :
308+ return "failed" ;
309+ }
310+ }
311+
312+ function createGuardianApprovalReviewContent (
313+ review : GuardianApprovalReview ,
314+ action : GuardianApprovalReviewAction ,
315+ ) : ToolCallContent [ ] {
316+ const lines = [ `Status: ${ formatGuardianApprovalReviewStatus ( review . status ) } ` ] ;
317+ const actionSummary = createGuardianApprovalReviewActionSummary ( action ) ;
318+ if ( actionSummary ) {
319+ lines . push ( `Action: ${ actionSummary } ` ) ;
320+ }
321+ if ( review . riskLevel ) {
322+ lines . push ( `Risk: ${ review . riskLevel } ` ) ;
323+ }
324+ if ( review . rationale ?. trim ( ) ) {
325+ lines . push ( `Rationale: ${ review . rationale } ` ) ;
326+ }
327+
328+ return [ {
329+ type : "content" ,
330+ content : {
331+ type : "text" ,
332+ text : lines . join ( "\n" ) ,
333+ } ,
334+ } ] ;
335+ }
336+
337+ function formatGuardianApprovalReviewStatus ( status : GuardianApprovalReviewStatus ) : string {
338+ switch ( status ) {
339+ case "inProgress" :
340+ return "In progress" ;
341+ case "approved" :
342+ return "Approved" ;
343+ case "denied" :
344+ return "Denied" ;
345+ case "aborted" :
346+ return "Aborted" ;
347+ case "timedOut" :
348+ return "Timed out" ;
349+ }
350+ }
351+
352+ function createGuardianApprovalReviewActionSummary ( action : GuardianApprovalReviewAction ) : string | null {
353+ switch ( action . type ) {
354+ case "command" :
355+ return `${ guardianCommandSourceLabel ( action . source ) } ${ action . command } ` ;
356+ case "execve" : {
357+ const command = action . argv . length > 0 ? action . argv : [ action . program ] ;
358+ return `${ guardianCommandSourceLabel ( action . source ) } ${ shellJoin ( command ) } ` ;
359+ }
360+ case "applyPatch" :
361+ if ( action . files . length === 1 ) {
362+ return `apply_patch touching ${ action . files [ 0 ] } ` ;
363+ }
364+ return `apply_patch touching ${ action . files . length } files` ;
365+ case "networkAccess" : {
366+ const label = action . target . length > 0 ? action . target : action . host ;
367+ return `network access to ${ label } ` ;
368+ }
369+ case "mcpToolCall" : {
370+ const label = action . connectorName ?? action . server ;
371+ return `MCP ${ action . toolName } on ${ label } ` ;
372+ }
373+ case "requestPermissions" :
374+ return action . reason ?? "request additional permissions" ;
375+ }
376+ }
377+
378+ function guardianCommandSourceLabel ( source : GuardianCommandSource ) : string {
379+ switch ( source ) {
380+ case "shell" :
381+ return "shell" ;
382+ case "unifiedExec" :
383+ return "exec" ;
384+ }
385+ }
386+
387+ function shellJoin ( args : string [ ] ) : string {
388+ return args . map ( shellQuote ) . join ( " " ) ;
389+ }
390+
391+ function shellQuote ( arg : string ) : string {
392+ if ( arg . length === 0 ) {
393+ return "''" ;
394+ }
395+ if ( / ^ [ A - Z a - z 0 - 9 _ / : = + . , @ % - ] + $ / . test ( arg ) ) {
396+ return arg ;
397+ }
398+ return `'${ arg . replace ( / ' / g, `'\\''` ) } '` ;
399+ }
400+
260401async function createPatchContent ( change : FileUpdateChange ) : Promise < ToolCallContent | null > {
261402 try {
262403 switch ( change . kind . type ) {
0 commit comments