@@ -28,6 +28,8 @@ import {
2828 SymphonyDirNotConfiguredError
2929} from "../server/operations/symphony-utils.js" ;
3030import { seedReposConfig } from "./seed-repos-config.js" ;
31+ import { SUPPORTED_OPERATION_IDS , resolveOperationId } from "./approval-operations.js" ;
32+ import { shouldAutoApprove } from "./approval-policy.js" ;
3133import { ActivityLogStore } from "./activity-log-store.js" ;
3234import { ApprovalStore } from "./approval-store.js" ;
3335import { JobStore , isTerminalJobStatus } from "./job-store.js" ;
@@ -131,7 +133,7 @@ export class DesktopApplication {
131133 getMaxInFlightCommands : ( ) => MAX_IN_FLIGHT_COMMANDS ,
132134 machineName : os . hostname ( ) ,
133135 pluginVersion : DESKTOP_GATEWAY_VERSION ,
134- supportedOperations : SUPPORTED_OPERATION_IDS ,
136+ supportedOperations : [ ... SUPPORTED_OPERATION_IDS ] ,
135137 onStatusChange : ( status ) => this . onCloudSocketStatus ( status ) ,
136138 onHelloAck : ( event ) => {
137139 if ( event . resumeFromSequence ) {
@@ -525,7 +527,11 @@ export class DesktopApplication {
525527
526528 const operationId = resolveOperationId ( request . path ) ;
527529 if ( ! operationId ) {
528- return { allow : true } ;
530+ return {
531+ allow : false ,
532+ statusCode : 403 ,
533+ payload : { error : `Unmapped operation: ${ request . path } ` }
534+ } ;
529535 }
530536
531537 const settings = this . settingsStore . getAll ( ) ;
@@ -547,11 +553,13 @@ export class DesktopApplication {
547553
548554 const configuredTier = ( settings . autoApprovalRules [ operationId ] ??
549555 settings . defaultApprovalTier ) as RiskTier ;
550- const tier : RiskTier = request . forceApproval ? "high" : configuredTier ;
551- if ( tier === "auto" && ! request . forceApproval ) {
556+ if ( configuredTier === "auto" && ! request . forceApproval ) {
557+ return { allow : true } ;
558+ }
559+ const manualTier : Exclude < RiskTier , "auto" > = configuredTier === "auto" ? "high" : configuredTier ;
560+ if ( shouldAutoApprove ( operationId , manualTier , request . forceApproval ?? false ) ) {
552561 return { allow : true } ;
553562 }
554- const manualTier : Exclude < RiskTier , "auto" > = tier === "auto" ? "high" : tier ;
555563
556564 const reason =
557565 request . approvalReason ?. trim ( ) ||
@@ -1019,140 +1027,6 @@ export class DesktopApplication {
10191027 }
10201028}
10211029
1022- const SUPPORTED_OPERATION_IDS = [
1023- "symphony_launch" ,
1024- "symphony_loop" ,
1025- "symphony_loop_kill" ,
1026- "symphony_plan_loop" ,
1027- "symphony_status" ,
1028- "symphony_kill" ,
1029- "symphony_chat" ,
1030- "symphony_comment_chat" ,
1031- "symphony_commit_message" ,
1032- "symphony_sessions" ,
1033- "symphony_plan" ,
1034- "symphony_judges" ,
1035- "symphony_logs" ,
1036- "symphony_chat_history" ,
1037- "terminal_chat" ,
1038- "ticket_chat" ,
1039- "run_viewer_chat" ,
1040- "codex_review" ,
1041- "codex_argue" ,
1042- "git_action" ,
1043- "git_pr" ,
1044- "health_check" ,
1045- "repos_config" ,
1046- "deploy" ,
1047- "learnings" ,
1048- "filesystem"
1049- ] ;
1050-
1051- function resolveOperationId ( pathname : string ) : string | null {
1052- if ( ! pathname . startsWith ( "/api/engineer/" ) ) {
1053- return null ;
1054- }
1055-
1056- if ( pathname === "/api/engineer/symphony/launch" ) {
1057- return "symphony_launch" ;
1058- }
1059- if ( pathname === "/api/engineer/symphony/loop" ) {
1060- return "symphony_loop" ;
1061- }
1062- if ( pathname === "/api/engineer/symphony/loop/kill" ) {
1063- return "symphony_loop_kill" ;
1064- }
1065- if ( pathname . startsWith ( "/api/engineer/symphony/plan-loop/" ) ) {
1066- return "symphony_plan_loop" ;
1067- }
1068- if ( pathname . startsWith ( "/api/engineer/symphony/status/" ) ) {
1069- return "symphony_status" ;
1070- }
1071- if ( pathname === "/api/engineer/symphony/kill" ) {
1072- return "symphony_kill" ;
1073- }
1074- if ( pathname . startsWith ( "/api/engineer/symphony/chat/" ) ) {
1075- return "symphony_chat" ;
1076- }
1077- if ( pathname . startsWith ( "/api/engineer/symphony/comment-chat/" ) ) {
1078- return "symphony_comment_chat" ;
1079- }
1080- if ( pathname . startsWith ( "/api/engineer/symphony/commit-message/" ) ) {
1081- return "symphony_commit_message" ;
1082- }
1083- if ( pathname === "/api/engineer/symphony/sessions" ) {
1084- return "symphony_sessions" ;
1085- }
1086- if ( pathname . startsWith ( "/api/engineer/symphony/plan/" ) ) {
1087- return "symphony_plan" ;
1088- }
1089- if ( pathname . startsWith ( "/api/engineer/symphony/judges/" ) ) {
1090- return "symphony_judges" ;
1091- }
1092- if ( pathname . startsWith ( "/api/engineer/symphony/logs/" ) ) {
1093- return "symphony_logs" ;
1094- }
1095- if ( pathname . startsWith ( "/api/engineer/symphony/chat-history/" ) ) {
1096- return "symphony_chat_history" ;
1097- }
1098- if ( pathname . startsWith ( "/api/engineer/symphony/pending-learnings" ) ) {
1099- return "learnings" ;
1100- }
1101- if ( pathname . startsWith ( "/api/engineer/symphony/process-learnings" ) ) {
1102- return "learnings" ;
1103- }
1104- if ( pathname . startsWith ( "/api/engineer/symphony/process-all-learnings" ) ) {
1105- return "learnings" ;
1106- }
1107- if ( pathname === "/api/engineer/terminal-chat" ) {
1108- return "terminal_chat" ;
1109- }
1110- if ( pathname === "/api/engineer/ticket-chat" ) {
1111- return "ticket_chat" ;
1112- }
1113- if ( pathname === "/api/engineer/run-viewer-chat" ) {
1114- return "run_viewer_chat" ;
1115- }
1116- if ( pathname . startsWith ( "/api/engineer/codex/argue/" ) ) {
1117- return "codex_argue" ;
1118- }
1119- if ( pathname . startsWith ( "/api/engineer/codex/" ) ) {
1120- return "codex_review" ;
1121- }
1122- if ( pathname . startsWith ( "/api/engineer/git/pr" ) || pathname === "/api/engineer/git/user" ) {
1123- return "git_pr" ;
1124- }
1125- if ( pathname . startsWith ( "/api/engineer/git" ) ) {
1126- return "git_action" ;
1127- }
1128- if ( pathname === "/api/engineer/health-check" ) {
1129- return "health_check" ;
1130- }
1131- if ( pathname === "/api/engineer/repos" ) {
1132- return "repos_config" ;
1133- }
1134- if ( pathname . startsWith ( "/api/engineer/deploy" ) ) {
1135- return "deploy" ;
1136- }
1137- if ( pathname === "/api/engineer/learnings" ) {
1138- return "learnings" ;
1139- }
1140- if ( pathname . startsWith ( "/api/engineer/work-directory/" ) ) {
1141- return "filesystem" ;
1142- }
1143- if ( pathname . startsWith ( "/api/engineer/symphony/sessions/" ) ) {
1144- return "symphony_sessions" ;
1145- }
1146- if (
1147- pathname === "/api/engineer/directories" ||
1148- pathname === "/api/engineer/files/search" ||
1149- pathname . startsWith ( "/api/engineer/run-viewer-extract" )
1150- ) {
1151- return "filesystem" ;
1152- }
1153-
1154- return null ;
1155- }
11561030
11571031const APPROVAL_TIMEOUT_MS = 120_000 ;
11581032const MAX_IN_FLIGHT_COMMANDS = 2 ;
0 commit comments