@@ -380,7 +380,7 @@ pub(crate) fn translate_sse_event(
380380 "session.updated" => translate_session_created_or_updated ( properties, state, true ) ,
381381 "session.status" => translate_session_status ( properties, state) ,
382382 "session.idle" => translate_session_idle ( properties, state) ,
383- "permission.updated " => translate_sse_permission ( properties, state) ,
383+ "permission.asked " => translate_sse_permission ( properties, state) ,
384384 "question.asked" => translate_question_asked ( properties, state) ,
385385 "question.replied" => translate_question_completed ( properties) ,
386386 "question.rejected" => translate_question_completed ( properties) ,
@@ -1468,24 +1468,28 @@ fn translate_sse_permission(properties: &Value, state: &mut SessionTranslationSt
14681468 . get ( "sessionID" )
14691469 . and_then ( |v| v. as_str ( ) )
14701470 . unwrap_or ( & state. session_id ) ;
1471+ // OpenCode sends "permission" field, not "type"
14711472 let perm_type = properties
1472- . get ( "type " )
1473+ . get ( "permission " )
14731474 . and_then ( |v| v. as_str ( ) )
14741475 . unwrap_or ( "command" ) ;
1475- let pattern = properties. get ( "pattern" ) ;
1476+ // OpenCode sends "patterns" array, not "pattern" string
1477+ let patterns = properties. get ( "patterns" ) ;
14761478
14771479 // Encode sessionId:permissionId into the event `id` so the frontend
14781480 // can round-trip it back to `respond_to_server_request`.
14791481 let composite_id = format ! ( "{session_id}:{permission_id}" ) ;
14801482
1481- let command_label = if let Some ( pat) = pattern {
1482- if let Some ( arr) = pat. as_array ( ) {
1483+ // Defensive handling: patterns can be array, string, or missing
1484+ let command_label = if let Some ( pats) = patterns {
1485+ if let Some ( arr) = pats. as_array ( ) {
14831486 arr. iter ( )
14841487 . filter_map ( |v| v. as_str ( ) )
14851488 . collect :: < Vec < _ > > ( )
14861489 . join ( ", " )
14871490 } else {
1488- pat. as_str ( ) . unwrap_or ( perm_type) . to_string ( )
1491+ // Fallback if patterns is unexpectedly a string
1492+ pats. as_str ( ) . unwrap_or ( perm_type) . to_string ( )
14891493 }
14901494 } else {
14911495 perm_type. to_string ( )
@@ -1495,21 +1499,25 @@ fn translate_sse_permission(properties: &Value, state: &mut SessionTranslationSt
14951499 "id" : composite_id,
14961500 "method" : "codex/requestApproval" ,
14971501 "params" : {
1498- "threadId" : session_id,
1499- "type" : perm_type,
1500- "command" : [ command_label] ,
1501- "rawInput" : { }
1502+ "permission" : perm_type,
1503+ "command" : [ command_label]
15021504 }
15031505 } ) ]
15041506}
15051507
15061508/// Build the REST body for a permission decision.
15071509///
1508- /// `accept` = true → `{ response: "allow" }`
1509- /// `accept` = false → `{ response: "deny" }`
1510- pub ( crate ) fn build_permission_response ( accept : bool ) -> Value {
1511- let response = if accept { "allow" } else { "deny" } ;
1512- json ! ( { "response" : response } )
1510+ /// Maps frontend decisions to OpenCode reply format:
1511+ /// - `"accept"` → `{ reply: "once" }`
1512+ /// - `"always"` → `{ reply: "always" }`
1513+ /// - `"decline"` (or other) → `{ reply: "reject" }`
1514+ pub ( crate ) fn build_permission_response ( decision : & str ) -> Value {
1515+ let reply = match decision {
1516+ "accept" => "once" ,
1517+ "always" => "always" ,
1518+ _ => "reject" ,
1519+ } ;
1520+ json ! ( { "reply" : reply } )
15131521}
15141522
15151523// ---------------------------------------------------------------------------
@@ -2406,22 +2414,23 @@ mod tests {
24062414 }
24072415
24082416 #[ test]
2409- fn permission_updated_produces_approval_request ( ) {
2417+ fn permission_asked_produces_approval_request ( ) {
24102418 let mut state = make_state ( ) ;
24112419 let event = json ! ( {
2412- "type" : "permission.updated " ,
2420+ "type" : "permission.asked " ,
24132421 "properties" : {
24142422 "id" : "perm_42" ,
2415- "type " : "bash" ,
2423+ "permission " : "bash" ,
24162424 "sessionID" : "ses_test123" ,
2417- "pattern " : "rm -rf /tmp/test"
2425+ "patterns " : [ "rm -rf /tmp/test" ]
24182426 }
24192427 } ) ;
24202428 let events = translate_sse_event ( & event, & mut state) ;
24212429 assert_eq ! ( events. len( ) , 1 ) ;
24222430 assert_eq ! ( events[ 0 ] [ "method" ] , "codex/requestApproval" ) ;
24232431 assert_eq ! ( events[ 0 ] [ "id" ] , "ses_test123:perm_42" ) ;
2424- assert_eq ! ( events[ 0 ] [ "params" ] [ "type" ] , "bash" ) ;
2432+ assert_eq ! ( events[ 0 ] [ "params" ] [ "permission" ] , "bash" ) ;
2433+ assert_eq ! ( events[ 0 ] [ "params" ] [ "command" ] [ 0 ] , "rm -rf /tmp/test" ) ;
24252434 }
24262435
24272436 #[ test]
@@ -2438,11 +2447,14 @@ mod tests {
24382447
24392448 #[ test]
24402449 fn permission_response_shapes ( ) {
2441- let accept = build_permission_response ( true ) ;
2442- assert_eq ! ( accept[ "response" ] , "allow" ) ;
2450+ let accept = build_permission_response ( "accept" ) ;
2451+ assert_eq ! ( accept[ "reply" ] , "once" ) ;
2452+
2453+ let always = build_permission_response ( "always" ) ;
2454+ assert_eq ! ( always[ "reply" ] , "always" ) ;
24432455
2444- let deny = build_permission_response ( false ) ;
2445- assert_eq ! ( deny[ "response " ] , "deny " ) ;
2456+ let deny = build_permission_response ( "decline" ) ;
2457+ assert_eq ! ( deny[ "reply " ] , "reject " ) ;
24462458 }
24472459
24482460 #[ test]
0 commit comments