文件最后提交记录最后更新时间
fix(gateway): replay only pending interactive requests Co-authored-by: Cursor <cursoragent@cursor.com> 14 天前
feat(gateway): wire interactive Web UI permission flow end-to-end The PolitDeck gateway used to expose a GatewayPermissionBus with getPermissionBus() / consume() / rejectSession(), but nobody ever called bus.register(...) — agent runtime PermissionRequest events were never bridged to the Web UI's banner, and permissionDecide() always returned { delivered: false }. The UI's "Allow + remember" button only wrote allowedTools to localStorage; that value only took effect on the *next* turn (via syncPermissionRules at runChatViaGateway entry), so within the current turn the agent saw stale rules and the prompt looped forever. On top of that, PermissionRuntime.decide consulted tool.checkPermissions() *before* user-configured allow rules, so tools that hardcode ask (web_fetch, web_search) overrode an explicit grant. This patch closes the loop: * src/gateway/permission/createGatewayPermissionHook.ts (new) Callback hook handler that emits a permission_request GatewayEvent into the active submitTurn stream, registers a pending entry with the bus, and awaits the user's decision. On allow + remember it mutates the live session permissionRules.allow array (shared by reference with the agent's PermissionContext) so subsequent calls to the same tool inside the same turn bypass the ask path. If the session has no active turn sink the hook denies immediately — better a clean denial than a silent hang. * src/gateway/client/InProcessGateway.ts Deduplicate the two competing emitForSession() definitions and keep the boolean-returning version. The hook now uses the return value to detect "nobody listening" and deny. * src/extension/hooks/execution/HookRuntime.ts Expose getCallbackExecutor() so external code can register callback hooks (specifically the permission gateway hook) before the runtime dispatches events. * src/cli/createLocalGateway.ts In createSession, inject a per-session callback hook on the PermissionRequest event pointing at the gateway permission callback, and register the handler with that session's CallbackHookExecutor — wiring the gateway-side bus, the live permissionRules array, and the emit sink together so each session gets its own properly-scoped permission round-trip. * src/permission/decision/PermissionRuntime.ts Reorder decide() so user-configured allow rules are consulted *before* tool.checkPermissions(). This lets an explicit "Allow + remember" grant defeat tools that hardcode ask. Deny rules still beat allow rules (checked first), so a user can't be tricked into granting a tool that's explicitly denied at config / session scope. * ui/src/components/chat/hooks/useChatComposerState.ts * ui/src/components/chat/utils/chatPermissions.ts Drop the provider !== 'claude' gate from handleGrantToolPermission and getPilotDeckPermissionSuggestion. After the PolitDeck-only migration every provider routes through the same gateway PermissionContext, so the "Permission added" UI is useful for DeepSeek / OpenAI / Anthropic / etc., not just Claude. End-to-end result: agent triggers ask → Web UI shows banner → user clicks Allow + remember → hook resolves with allow → live permissionRules.allow is mutated → next decide() walks the allow branch immediately, with no per-turn ceremony. Same-tool follow-up calls in the same turn no longer re-prompt; new turns keep working because syncPermissionRules continues to mirror localStorage on entry. Co-authored-by: Cursor <cursoragent@cursor.com> 21 天前