@@ -201,73 +201,86 @@ func (s *permissionService) Request(ctx context.Context, opts CreatePermissionRe
201 return true, nil
201 return true, nil
204+ // Set up the request under requestMu, then release it *before*
205+ // blocking on the human response. Holding requestMu across the wait
206+ // would serialize every permission prompt process-wide, so parallel
207+ // sub-agents each needing approval would queue behind one another.
208+ // Per-request resolution is scoped by the pendingRequests map (keyed
209+ // by request ID), so the wait needs no global lock.
210+ respCh, requestID, resolved, granted := func() (chan bool, string, bool, bool) {
204− s.requestMu.Lock()
211+ s.requestMu.Lock()
205− defer s.requestMu.Unlock()
212+ defer s.requestMu.Unlock()
207− // tell the UI that a permission was requested
214+ // tell the UI that a permission was requested
208− s.notificationBroker.Publish(pubsub.CreatedEvent, PermissionNotification{
209− ToolCallID: opts.ToolCallID,
212− s.autoApproveSessionsMu.RLock()
213− autoApprove := s.autoApproveSessions[opts.SessionID]
214− s.autoApproveSessionsMu.RUnlock()
217 s.notificationBroker.Publish(pubsub.CreatedEvent, PermissionNotification{
215 s.notificationBroker.Publish(pubsub.CreatedEvent, PermissionNotification{
218 ToolCallID: opts.ToolCallID,
216 ToolCallID: opts.ToolCallID,
224− fileInfo, err := os.Stat(opts.Path)
219+ s.autoApproveSessionsMu.RLock()
225− dir := opts.Path
220+ autoApprove := s.autoApproveSessions[opts.SessionID]
226− if err == nil {
221+ s.autoApproveSessionsMu.RUnlock()
227− if fileInfo.IsDir() {
222+
228− dir = opts.Path
223+ if autoApprove {
229− } else {
224+ s.notificationBroker.Publish(pubsub.CreatedEvent, PermissionNotification{
230− dir = filepath.Dir(opts.Path)
225+ ToolCallID: opts.ToolCallID,
228+ return nil, "", true, true
234− if dir == "." {
231+ fileInfo, err := os.Stat(opts.Path)
235− dir = s.workingDir
232+ dir := opts.Path
236− }
233+ if err == nil {
237− permission := PermissionRequest{
234+ if fileInfo.IsDir() {
238− ID: uuid.New().String(),
235+ dir = opts.Path
239− Path: dir,
236+ } else {
240− SessionID: opts.SessionID,
237+ dir = filepath.Dir(opts.Path)
241− ToolCallID: opts.ToolCallID,
238+ }
242− ToolName: opts.ToolName,
243− Description: opts.Description,
244− Action: opts.Action,
245− Params: opts.Params,
248− if _, ok := s.sessionPermissions.Get(PermissionKey{
241+ if dir == "." {
244+ permission := PermissionRequest{
245+ ID: uuid.New().String(),
249− SessionID: permission.SessionID,
247+ SessionID: opts.SessionID,
250− ToolName: permission.ToolName,
248+ ToolCallID: opts.ToolCallID,
251− Action: permission.Action,
249+ ToolName: opts.ToolName,
252− Path: permission.Path,
250+ Description: opts.Description,
253− }); ok {
251+ Action: opts.Action,
254− s.notificationBroker.Publish(pubsub.CreatedEvent, PermissionNotification{
252+ Params: opts.Params,
255− ToolCallID: opts.ToolCallID,
261− s.activeRequestMu.Lock()
255+ if _, ok := s.sessionPermissions.Get(PermissionKey{
262− s.activeRequest = &permission
256+ SessionID: permission.SessionID,
263− s.activeRequestMu.Unlock()
257+ ToolName: permission.ToolName,
258+ Action: permission.Action,
259+ Path: permission.Path,
261+ s.notificationBroker.Publish(pubsub.CreatedEvent, PermissionNotification{
262+ ToolCallID: opts.ToolCallID,
265+ return nil, "", true, true
265− respCh := make(chan bool, 1)
268+ s.activeRequestMu.Lock()
266− s.pendingRequests.Set(permission.ID, respCh)
269+ s.activeRequest = &permission
267− defer s.pendingRequests.Del(permission.ID)
270+ s.activeRequestMu.Unlock()
272+ respCh := make(chan bool, 1)
273+ s.pendingRequests.Set(permission.ID, respCh)
269− // Publish the request
275+ // Publish the request
270− s.Publish(pubsub.CreatedEvent, permission)
276+ s.Publish(pubsub.CreatedEvent, permission)
277+ return respCh, permission.ID, false, false
283+ defer s.pendingRequests.Del(requestID)
273 case <-ctx.Done():
286 case <-ctx.Done():