@@ -130,6 +130,7 @@ interface SessionConfig {
130130 sdkSessionId ?: string ;
131131 model ?: string ;
132132 framework ?: "claude" | "codex" ;
133+ executionMode ?: "plan" ;
133134}
134135
135136interface ManagedSession {
@@ -152,16 +153,84 @@ function getClaudeCliPath(): string {
152153 : join ( appPath , ".vite/build/claude-cli/cli.js" ) ;
153154}
154155
156+ interface PendingPermission {
157+ resolve : ( response : RequestPermissionResponse ) => void ;
158+ reject : ( error : Error ) => void ;
159+ sessionId : string ;
160+ toolCallId : string ;
161+ }
162+
155163@injectable ( )
156164export class AgentService extends TypedEventEmitter < AgentServiceEvents > {
157165 private sessions = new Map < string , ManagedSession > ( ) ;
158166 private currentToken : string | null = null ;
167+ private pendingPermissions = new Map < string , PendingPermission > ( ) ;
159168
160169 public updateToken ( newToken : string ) : void {
161170 this . currentToken = newToken ;
162171 log . info ( "Session token updated" ) ;
163172 }
164173
174+ /**
175+ * Respond to a pending permission request from the UI.
176+ * This resolves the promise that the agent is waiting on.
177+ */
178+ public respondToPermission (
179+ sessionId : string ,
180+ toolCallId : string ,
181+ optionId : string ,
182+ ) : void {
183+ const key = `${ sessionId } :${ toolCallId } ` ;
184+ const pending = this . pendingPermissions . get ( key ) ;
185+
186+ if ( ! pending ) {
187+ log . warn ( "No pending permission found" , { sessionId, toolCallId } ) ;
188+ return ;
189+ }
190+
191+ log . info ( "Permission response received" , {
192+ sessionId,
193+ toolCallId,
194+ optionId,
195+ } ) ;
196+
197+ pending . resolve ( {
198+ outcome : {
199+ outcome : "selected" ,
200+ optionId,
201+ } ,
202+ } ) ;
203+
204+ this . pendingPermissions . delete ( key ) ;
205+ }
206+
207+ /**
208+ * Cancel a pending permission request.
209+ * This resolves the promise with a "cancelled" outcome per ACP spec.
210+ */
211+ public cancelPermission ( sessionId : string , toolCallId : string ) : void {
212+ const key = `${ sessionId } :${ toolCallId } ` ;
213+ const pending = this . pendingPermissions . get ( key ) ;
214+
215+ if ( ! pending ) {
216+ log . warn ( "No pending permission found to cancel" , {
217+ sessionId,
218+ toolCallId,
219+ } ) ;
220+ return ;
221+ }
222+
223+ log . info ( "Permission cancelled" , { sessionId, toolCallId } ) ;
224+
225+ pending . resolve ( {
226+ outcome : {
227+ outcome : "cancelled" ,
228+ } ,
229+ } ) ;
230+
231+ this . pendingPermissions . delete ( key ) ;
232+ }
233+
165234 private getToken ( fallback : string ) : string {
166235 return this . currentToken || fallback ;
167236 }
@@ -232,6 +301,7 @@ export class AgentService extends TypedEventEmitter<AgentServiceEvents> {
232301 sdkSessionId,
233302 model,
234303 framework,
304+ executionMode,
235305 } = config ;
236306
237307 if ( ! isRetry ) {
@@ -289,7 +359,11 @@ export class AgentService extends TypedEventEmitter<AgentServiceEvents> {
289359 await connection . newSession ( {
290360 cwd : repoPath ,
291361 mcpServers,
292- _meta : { sessionId : taskRunId , model } ,
362+ _meta : {
363+ sessionId : taskRunId ,
364+ model,
365+ ...( executionMode && { initialModeId : executionMode } ) ,
366+ } ,
293367 } ) ;
294368 }
295369
@@ -515,6 +589,9 @@ export class AgentService extends TypedEventEmitter<AgentServiceEvents> {
515589 _channel : string ,
516590 clientStreams : { readable : ReadableStream ; writable : WritableStream } ,
517591 ) : ClientSideConnection {
592+ // Capture service reference for use in client callbacks
593+ const service = this ;
594+
518595 const emitToRenderer = ( payload : unknown ) => {
519596 // Emit event via TypedEventEmitter for tRPC subscription
520597 this . emit ( AgentServiceEvent . SessionEvent , {
@@ -546,6 +623,63 @@ export class AgentService extends TypedEventEmitter<AgentServiceEvents> {
546623 async requestPermission (
547624 params : RequestPermissionRequest ,
548625 ) : Promise < RequestPermissionResponse > {
626+ const toolName =
627+ ( params . toolCall ?. rawInput as { toolName ?: string } | undefined )
628+ ?. toolName || "" ;
629+ const toolCallId = params . toolCall ?. toolCallId || "" ;
630+
631+ log . info ( "requestPermission called" , {
632+ sessionId : taskRunId ,
633+ toolCallId,
634+ toolName,
635+ title : params . toolCall ?. title ,
636+ optionCount : params . options . length ,
637+ } ) ;
638+
639+ // If we have a toolCallId, always prompt the user for permission.
640+ // The claude.ts adapter only calls requestPermission when user input is needed.
641+ // (It handles auto-approve internally for acceptEdits/bypassPermissions modes)
642+ if ( toolCallId ) {
643+ log . info ( "Permission request requires user input" , {
644+ sessionId : taskRunId ,
645+ toolCallId,
646+ toolName,
647+ title : params . toolCall ?. title ,
648+ } ) ;
649+
650+ return new Promise ( ( resolve , reject ) => {
651+ const key = `${ taskRunId } :${ toolCallId } ` ;
652+ service . pendingPermissions . set ( key , {
653+ resolve,
654+ reject,
655+ sessionId : taskRunId ,
656+ toolCallId,
657+ } ) ;
658+
659+ log . info ( "Emitting permission request to renderer" , {
660+ sessionId : taskRunId ,
661+ toolCallId,
662+ } ) ;
663+ service . emit ( AgentServiceEvent . PermissionRequest , {
664+ sessionId : taskRunId ,
665+ toolCallId,
666+ title : params . toolCall ?. title || "Permission Required" ,
667+ options : params . options . map ( ( o ) => ( {
668+ kind : o . kind ,
669+ name : o . name ,
670+ optionId : o . optionId ,
671+ description : ( o as { description ?: string } ) . description ,
672+ } ) ) ,
673+ rawInput : params . toolCall ?. rawInput ,
674+ } ) ;
675+ } ) ;
676+ }
677+
678+ // Fallback: no toolCallId means we can't track the response, auto-approve
679+ log . warn ( "No toolCallId in permission request, auto-approving" , {
680+ sessionId : taskRunId ,
681+ toolName,
682+ } ) ;
549683 const allowOption = params . options . find (
550684 ( o ) => o . kind === "allow_once" || o . kind === "allow_always" ,
551685 ) ;
@@ -611,6 +745,8 @@ export class AgentService extends TypedEventEmitter<AgentServiceEvents> {
611745 sdkSessionId : "sdkSessionId" in params ? params . sdkSessionId : undefined ,
612746 model : "model" in params ? params . model : undefined ,
613747 framework : "framework" in params ? params . framework : "claude" ,
748+ executionMode :
749+ "executionMode" in params ? params . executionMode : undefined ,
614750 } ;
615751 }
616752
0 commit comments