* feat: inline approval gate for tool execution
Replace SDK-level needsApproval with Promise-based approval gate inside
tool execute functions. The SDK stream stays alive while the UI shows
inline approve/reject buttons on ToolCall blocks.
Changes:
- Add approvalGate.ts: Promise-based approval system with event listeners
- tools.ts: requestApproval() inside execute for confirm mode
- tool-call.tsx: inline approval buttons and keyboard shortcuts
- ChatMessageList.tsx: subscribe to approval events, render approval UI
- useAIChatStreaming.ts: remove old useToolApproval hook integration
- AIChatSidePanel.tsx: remove old approval hook, clean up unused destructuring
- systemPrompt.ts: update confirm mode to not ask for text confirmation
- preload.cjs: filter pager env var prefixes from terminal display
- mcpServerBridge.cjs: add approval gate for ACP/MCP write operations
- aiBridge.cjs: wire IPC for MCP approval response and main window getter
- preload.cjs: add onMcpApprovalRequest/respondMcpApproval APIs
* fix: scope approval gate by chatSessionId and replay for late subscribers
Address Codex PR review comments:
- Add chatSessionId to ApprovalRequest for session isolation
- Scope clearAllPendingApprovals(chatSessionId?) to only clear
approvals belonging to the target session
- Add replayPendingApprovals() so late-mounting ChatMessageList
picks up approvals that fired while unmounted
- Scope MCP clearPendingApprovals in aiBridge cancel handler to
effectiveChatSessionId instead of clearing all
- Pass chatSessionId through MCP approval IPC flow
* chore: remove old approval flow code
- Delete useToolApproval.ts (unused hook)
- Delete InlineApprovalCard.tsx (replaced by ToolCall inline buttons)
- Remove stale comments referencing old hook in AIChatSidePanel
- Remove unused ai.chat.toolApprovalTitle i18n key from en/zh-CN
* fix: session-scoped approval gate and MCP replay survival
- handleStop passes activeSessionId to clearAllPendingApprovals
- setupMcpApprovalBridge stores MCP approvals in pendingApprovals map
so they survive ChatMessageList unmount/remount cycles
- ChatMessageList accepts activeSessionId prop and filters standalone
MCP approval blocks to the current session only
- AIChatSidePanel passes activeSessionId to ChatMessageList
* fix: filter PTY exec marker echoes and exit code lines from terminal
Extend filterMcpMarkers in preload.cjs to strip all shell-visible
artifacts from AI command execution:
- Echoed printf start marker: printf '%s\n' '__NCMCP_..._S'
- Echoed exit code restoration: (exit $__nc)
- PowerShell: Write-Output, $global:LASTEXITCODE, $__nc assignment
- Fish: set __nc $status
- Cmd: echo __NCMCP_...
- Widen guard to also trigger on __nc and PAGER=cat strings
* fix: scope SDK approvals, deny MCP on no renderer, fix memo comparator
- createCattyTools accepts chatSessionId and passes it to
requestApproval so SDK approvals can be matched by
clearAllPendingApprovals(activeSessionId) on stop
- useAIChatStreaming passes sessionId to createCattyTools
- mcpServerBridge: deny (resolve false) when no renderer window is
available instead of auto-approving, preserving confirm mode safety
- ChatMessageList: add activeSessionId to React.memo comparator so
switching sessions triggers re-render for correct MCP approval filter
* fix: MCP listener lifecycle, approval timeout, and UI sync on stop
- Move setupMcpApprovalBridge from ChatMessageList to AIChatSidePanel
so the IPC listener survives tab/panel switches
- Add 5-minute auto-deny timeout to requestApproval to prevent
indefinite isStreaming hangs when user walks away
- Add onApprovalCleared listener system: clearAllPendingApprovals now
notifies UI subscribers so ChatMessageList removes stale cards
- ChatMessageList subscribes to onApprovalCleared to sync local state
* fix: main-process approval timeout and full tool args in payload
- Add 5-minute auto-deny timeout to requestApprovalFromRenderer
matching the renderer-side requestApproval behavior
- Forward all tool params (excluding chatSessionId) to approval UI
instead of cherry-picking command/input/path, so sftpRename
oldPath/newPath and other tool-specific args are visible
* fix: move MCP bridge to TerminalLayer, narrow terminal filter guard
- Move setupMcpApprovalBridge from AIChatSidePanel to TerminalLayer
so the IPC listener stays alive regardless of side panel tab.
AIChatSidePanel only mounts when activeSidePanelTab==='ai'.
- Narrow preload.cjs filter guard back to __NCMCP_ only, preventing
false-positive stripping of user scripts containing __nc or PAGER=cat
* fix: eliminate PTY wrapper echo leakage and duplicate prompts
- Posix wrapper now emits 2 lines instead of 4: start marker + command
on line 1 (joined with ;), end marker + exit on line 2. This
eliminates the duplicate prompt echo from the separate start marker.
- Rename __nc to __NCMCP_rc in all shell variants (posix/fish/powershell)
so every wrapper variable contains the __NCMCP_ prefix. The preload
guard `data.includes("__NCMCP_")` now reliably catches ALL wrapper
artifacts regardless of chunk boundaries.
- Update all filterMcpMarkers regex patterns to match the restructured
wrapper format and renamed variable.
* fix: sync main-process approval timeout with renderer UI cleanup
- When requestApprovalFromRenderer times out, send IPC event
netcatty:ai:mcp:approval-cleared to renderer so stale approval
cards are removed
- Add onMcpApprovalCleared preload bridge for the new IPC channel
- setupMcpApprovalBridge now subscribes to cleared events, removes
timed-out entries from pendingApprovals and notifies clearedListeners
so ChatMessageList drops the stale card
* fix: surface denied inline approvals as errors in UI
- Detect error or denial payloads ("error" string or "ok: false")
returned by tools when the user denies an execution
- Set isError: true on the tool-result message so the ToolCall UI
renders it as a failure (red/rejected) instead of a success (green)