chainlesschain 0.162.154 → 0.162.156

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/src/assets/web-panel/.build-hash +1 -1
  4. package/src/assets/web-panel/assets/{AIOps-CIpoXyVI.js → AIOps-DjBXlypQ.js} +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-Ch_QXIPk.js → ActionButton-Cv9er9bp.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-CvaBYShv.js → Analytics-CLKDjSNs.js} +3 -3
  7. package/src/assets/web-panel/assets/AppLayout-CfyZNzmJ.js +9 -0
  8. package/src/assets/web-panel/assets/{Audit-Bng8eHCW.js → Audit-CXafD178.js} +1 -1
  9. package/src/assets/web-panel/assets/BackgroundAgents-CP5c-GSd.js +1 -0
  10. package/src/assets/web-panel/assets/BackgroundAgents-Dq2340Kl.css +1 -0
  11. package/src/assets/web-panel/assets/{Backup-DvBs8-lm.js → Backup-BbYLQfyt.js} +1 -1
  12. package/src/assets/web-panel/assets/{BaseInput-DKwajm0g.js → BaseInput-Bqe0MwZL.js} +1 -1
  13. package/src/assets/web-panel/assets/{Chat-BoGpZ3uO.js → Chat-jNA_yUPH.js} +5 -5
  14. package/src/assets/web-panel/assets/{ChatBubbleRenderer-mDQl_PXO.js → ChatBubbleRenderer-BOFLF01s.js} +1 -1
  15. package/src/assets/web-panel/assets/{Checkbox-BMJEmxFQ.js → Checkbox-Dlzz-zD1.js} +1 -1
  16. package/src/assets/web-panel/assets/{Codegen-Bsoag5CV.js → Codegen-BAsDQjGf.js} +1 -1
  17. package/src/assets/web-panel/assets/{Col-CDjH9-sp.js → Col-Ju0HrAuS.js} +1 -1
  18. package/src/assets/web-panel/assets/{Community-Cg2QHPQ6.js → Community-DynJd43Z.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compact-BU2BaqP-.js → Compact-CabbZVww.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compliance-D6ZYMCqZ.js → Compliance-DW4BmAoh.js} +1 -1
  21. package/src/assets/web-panel/assets/{Cowork-DaZ8VW-I.js → Cowork-Bk1S_7X-.js} +2 -2
  22. package/src/assets/web-panel/assets/{Cron-DQY1RH9v.js → Cron-By4sh_NM.js} +2 -2
  23. package/src/assets/web-panel/assets/{Crosschain-BVR28vk9.js → Crosschain-DdZdbS1C.js} +1 -1
  24. package/src/assets/web-panel/assets/{DID-N44KvTP-.js → DID-CkL-sQyS.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dashboard-Bjhlx6wS.js → Dashboard-g_mOpumg.js} +2 -2
  26. package/src/assets/web-panel/assets/{Dropdown-CpLYxpy1.js → Dropdown-DtHyE7Ao.js} +1 -1
  27. package/src/assets/web-panel/assets/{EmailListRenderer-srEITSPI.js → EmailListRenderer-Kpvov5Wr.js} +1 -1
  28. package/src/assets/web-panel/assets/{FamilyGuardDashboard-juOC_fX2.js → FamilyGuardDashboard-C4kZoePR.js} +1 -1
  29. package/src/assets/web-panel/assets/{Federation-nRaQtaXW.js → Federation-BAwWoK2N.js} +1 -1
  30. package/src/assets/web-panel/assets/{FormItemContext-DflhTj09.js → FormItemContext-DeI0i5f8.js} +1 -1
  31. package/src/assets/web-panel/assets/{GenericCardRenderer-DulI_3NI.js → GenericCardRenderer-CkcKRtos.js} +1 -1
  32. package/src/assets/web-panel/assets/{Git-BxOPefXn.js → Git-kX6jjI9y.js} +2 -2
  33. package/src/assets/web-panel/assets/{Governance-C6G_Wnwi.js → Governance-BXJCBm6x.js} +1 -1
  34. package/src/assets/web-panel/assets/{Inference-BnFYa2BF.js → Inference-ujD2JSKo.js} +1 -1
  35. package/src/assets/web-panel/assets/{KnowledgeGraph-TqQLEabm.js → KnowledgeGraph-3Dnuf1Wh.js} +1 -1
  36. package/src/assets/web-panel/assets/{Logs-BH4AVh7s.js → Logs-lVAZCtUB.js} +2 -2
  37. package/src/assets/web-panel/assets/{Marketplace-DoWLTaEE.js → Marketplace-qnzNPm-A.js} +1 -1
  38. package/src/assets/web-panel/assets/{McpTools-D6yC7OG_.js → McpTools-CqjzefRU.js} +5 -5
  39. package/src/assets/web-panel/assets/{Memory-DLCeHj21.js → Memory-BFiPAxk6.js} +2 -2
  40. package/src/assets/web-panel/assets/{MobileBridge-BZBE0GRl.js → MobileBridge-D0O43VwF.js} +1 -1
  41. package/src/assets/web-panel/assets/{MobileProjects-DJA15G1J.js → MobileProjects-B3now48t.js} +1 -1
  42. package/src/assets/web-panel/assets/{Mtc-CyTKJDHn.js → Mtc-DybhtaCS.js} +4 -4
  43. package/src/assets/web-panel/assets/{MtcAudit-DZKjgEVc.js → MtcAudit-D7MmY4F0.js} +5 -5
  44. package/src/assets/web-panel/assets/{Multisig-2rMPNhSO.js → Multisig-wZvGJnB8.js} +3 -3
  45. package/src/assets/web-panel/assets/{NLProgramming-VeMk4Fx-.js → NLProgramming-BNPpgfvn.js} +1 -1
  46. package/src/assets/web-panel/assets/{Notes-jr2GHMhh.js → Notes-B6FS3KBj.js} +3 -3
  47. package/src/assets/web-panel/assets/{NotificationSettings-WN9VZyFb.js → NotificationSettings-CK0yla9U.js} +1 -1
  48. package/src/assets/web-panel/assets/OrderTableRenderer-CXfPHIpy.js +1 -0
  49. package/src/assets/web-panel/assets/{Organization-DdjVHuzT.js → Organization-geqgwN1T.js} +4 -4
  50. package/src/assets/web-panel/assets/{Overflow-l8vBsGGq.js → Overflow-BWJYZFbC.js} +1 -1
  51. package/src/assets/web-panel/assets/{OverrideContext-BlgL9440.js → OverrideContext-C0w0P4A3.js} +1 -1
  52. package/src/assets/web-panel/assets/{P2P-CVQDHHwq.js → P2P-DnU1505F.js} +2 -2
  53. package/src/assets/web-panel/assets/{PdhVaultBrowser-CTsNh8qE.js → PdhVaultBrowser-BGY74KnC.js} +3 -3
  54. package/src/assets/web-panel/assets/{Permissions-DLW87uyK.js → Permissions-CgArrWtQ.js} +3 -3
  55. package/src/assets/web-panel/assets/{PersonalDataHub-N8OvASTx.js → PersonalDataHub-C2vYE61a.js} +2 -2
  56. package/src/assets/web-panel/assets/{Pipeline-BPZmdlz2.js → Pipeline-CBYLI4yW.js} +1 -1
  57. package/src/assets/web-panel/assets/{Privacy-MFZw_GNh.js → Privacy-B-PdW9aN.js} +1 -1
  58. package/src/assets/web-panel/assets/{ProjectInit-zW0pEsfU.js → ProjectInit-CuWQegbV.js} +2 -2
  59. package/src/assets/web-panel/assets/{ProjectSettings-CppVHr_o.js → ProjectSettings-4d32m_cS.js} +2 -2
  60. package/src/assets/web-panel/assets/{Projects-BTglkJO2.js → Projects-VLA7ttwd.js} +1 -1
  61. package/src/assets/web-panel/assets/{Providers-CPHZ6JKT.js → Providers-B_Izb_oa.js} +1 -1
  62. package/src/assets/web-panel/assets/{QrScannerModal-DO4klNZ2.js → QrScannerModal-C6B_ndXV.js} +1 -1
  63. package/src/assets/web-panel/assets/{QuickAsk-B37TTqUa.js → QuickAsk-C6tavL-y.js} +1 -1
  64. package/src/assets/web-panel/assets/{Recommend-n7KIfK7y.js → Recommend-CF-LaZ6x.js} +1 -1
  65. package/src/assets/web-panel/assets/{RemoteSession-SjVoS9mm.js → RemoteSession-DBqTTQ9_.js} +2 -2
  66. package/src/assets/web-panel/assets/{Reputation-Dt6RTeHi.js → Reputation-B01FALUi.js} +1 -1
  67. package/src/assets/web-panel/assets/{Row-Dty6jz5V.js → Row-DO0fd48A.js} +1 -1
  68. package/src/assets/web-panel/assets/{RssFeed-DDLhqw5j.js → RssFeed-BV5LDzFR.js} +3 -3
  69. package/src/assets/web-panel/assets/{Search-DrYbXi-W.js → Search-CTti4_MP.js} +1 -1
  70. package/src/assets/web-panel/assets/{Security-Fc9Z6fbB.js → Security-DwSrWRCw.js} +4 -4
  71. package/src/assets/web-panel/assets/{Services-BAhPgjSM.js → Services-BEf1QyM4.js} +2 -2
  72. package/src/assets/web-panel/assets/{Skeleton-8JF7EPfE.js → Skeleton-LZXTpovy.js} +1 -1
  73. package/src/assets/web-panel/assets/{Skills-DL39dY0t.js → Skills-SKgc0Zug.js} +1 -1
  74. package/src/assets/web-panel/assets/{Sla-CSvjcATc.js → Sla-C_QVOtkN.js} +1 -1
  75. package/src/assets/web-panel/assets/{SpeechSettings-Demx7Gnx.js → SpeechSettings-v5TW2V-X.js} +1 -1
  76. package/src/assets/web-panel/assets/{SyncSettings-CKLnRWJr.js → SyncSettings-CQPjHdqU.js} +2 -2
  77. package/src/assets/web-panel/assets/Tasks-DCd-uYcI.js +1 -0
  78. package/src/assets/web-panel/assets/{Templates-AjQI5rxD.js → Templates-62VDIjtj.js} +1 -1
  79. package/src/assets/web-panel/assets/{Tenant-dbtEh1yR.js → Tenant-BnFQ0pNy.js} +1 -1
  80. package/src/assets/web-panel/assets/{Terminal-DX-9jjfk.js → Terminal-D3v0MGSj.js} +2 -2
  81. package/src/assets/web-panel/assets/{TimelineRenderer-C11EDZRt.js → TimelineRenderer-BI1Ka-cu.js} +1 -1
  82. package/src/assets/web-panel/assets/{Tokens-GiA9fiZn.js → Tokens-BwX1jXYP.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trigger-BPcnclJn.js → Trigger-CSA8TqNx.js} +1 -1
  84. package/src/assets/web-panel/assets/{Trust-C-DVY8nq.js → Trust-Bn3IjCx7.js} +1 -1
  85. package/src/assets/web-panel/assets/{UkeySign-DxodSwME.js → UkeySign-pUbOAqNi.js} +1 -1
  86. package/src/assets/web-panel/assets/{VideoEditing-D_WV0O8O.js → VideoEditing-Db2RercF.js} +1 -1
  87. package/src/assets/web-panel/assets/{Wallet-DSeNlVSU.js → Wallet-CpR1BVAl.js} +4 -4
  88. package/src/assets/web-panel/assets/{WebAuthn-Bwuuvpz1.js → WebAuthn-Yxtkn7Zp.js} +5 -5
  89. package/src/assets/web-panel/assets/{WorkflowEditor-8M96EU1N.js → WorkflowEditor-CYLe9a2E.js} +1 -1
  90. package/src/assets/web-panel/assets/{chat-D5S6SmLm.js → chat-gsOGdak0.js} +1 -1
  91. package/src/assets/web-panel/assets/{collapseMotion-DVfhbsdP.js → collapseMotion-CqM1QOwM.js} +1 -1
  92. package/src/assets/web-panel/assets/{colors-5fbnGkAP.js → colors-D9fN299a.js} +1 -1
  93. package/src/assets/web-panel/assets/{compact-item-DaSuuMlW.js → compact-item-vkd7s-ay.js} +1 -1
  94. package/src/assets/web-panel/assets/{createContext-BNOHcXAF.js → createContext-C7YkbMyy.js} +1 -1
  95. package/src/assets/web-panel/assets/devWarning-DEcR2h6k.js +1 -0
  96. package/src/assets/web-panel/assets/{echarts-CL0F2SnX.js → echarts-C61Vn3mP.js} +1 -1
  97. package/src/assets/web-panel/assets/{hasIn-DONwZUra.js → hasIn-ig7reRSU.js} +1 -1
  98. package/src/assets/web-panel/assets/{icons-BsDmGpcT.js → icons-DEmcnxBf.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-BJp4kRjw.js → index-9TxdMYju.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-MOgO5Yvx.js → index-B7QRpKif.js} +1 -1
  101. package/src/assets/web-panel/assets/index-BEibanjg.js +1 -0
  102. package/src/assets/web-panel/assets/{index-CAYmc5Nh.js → index-BHHN_KTS.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-BcWMZqb4.js → index-BMw48d19.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-B_yDD9Lm.js → index-BYe9HRSt.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-xo2f4DZR.js → index-BfpVcXKa.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-DbdQBn4n.js → index-BhftELHI.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-Dd7xwGcD.js → index-BkP17Z95.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-RkInlwV0.js → index-BpGlLbRv.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-DM3Ug61e.js → index-Bt-9ZhIu.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-CrkCFohi.js → index-BvDEukXe.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-_6S41ikV.js → index-C7AY39sg.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-uR5KTNZA.js → index-C8HZMygt.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-5_fj5gVd.js → index-CAkz1f1r.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-BLzMuz38.js → index-CDWTycrg.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-BEO5f7Vf.js → index-CGzGe5tv.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-CEzd5m5a.js → index-Co2NYyew.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BcqOK7Lv.js → index-CspGymmg.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-DP2-3A_j.js → index-D2yhtk6a.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-LRIt-AFl.js → index-D6s_kbjt.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-B4L3gsNA.js → index-DFM1oNe1.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-KwV9g_cf.js → index-DFfxF7Vt.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-BK2N5yVW.js → index-DPguq3X7.js} +4 -4
  123. package/src/assets/web-panel/assets/{index-Cf2OCa8q.js → index-DQ8U7kzC.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-D7gs_3Ck.js → index-DZAxCnjS.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-PsMErAB1.js → index-DhdXbsXX.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-CTZeOnzO.js → index-DuwbKHoS.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-Dm5A-MN5.js → index-DvCJXmrx.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-vYtprlNs.js → index-Dy_TKZ9h.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-DHx2lhHW.js → index-FzGr6CVk.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-Cn5LtyIH.js → index-PkH39qsp.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-CmUOjyL6.js → index-SDrcQXO1.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-CztESwB4.js → index-W52sHRdr.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-CSfYJqXt.js → index-bVbgYBhv.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-BrAJSu-v.js → index-gj8kxcAi.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-CLXf-8ek.js → index-gpurplyq.js} +1 -1
  136. package/src/assets/web-panel/assets/{index-ClGqO-2X.js → index-nX8YE6cj.js} +1 -1
  137. package/src/assets/web-panel/assets/index-z_25tU3p.js +1 -0
  138. package/src/assets/web-panel/assets/{initDefaultProps-CxwLynp9.js → initDefaultProps-BrgfVRg9.js} +1 -1
  139. package/src/assets/web-panel/assets/{motion-XbPvGJbW.js → motion-D_3xmwac.js} +1 -1
  140. package/src/assets/web-panel/assets/{move-D1u5pcgS.js → move-DDDJdlxw.js} +1 -1
  141. package/src/assets/web-panel/assets/{mtc-parser-BDxhTBMH.js → mtc-parser-BK2uEWwn.js} +1 -1
  142. package/src/assets/web-panel/assets/{omit-EtIFGg5k.js → omit-BPHKbDcf.js} +1 -1
  143. package/src/assets/web-panel/assets/{pickAttrs-D33sWb0l.js → pickAttrs-LNnTscuz.js} +1 -1
  144. package/src/assets/web-panel/assets/{placementArrow-BwFRzaGo.js → placementArrow-CvmTZll5.js} +1 -1
  145. package/src/assets/web-panel/assets/{responsiveObserve-CBk2WqvQ.js → responsiveObserve-eBj7qi2J.js} +1 -1
  146. package/src/assets/web-panel/assets/{slide-B52HtrKn.js → slide-D68hHkrM.js} +1 -1
  147. package/src/assets/web-panel/assets/{statusUtils-D4qxqWnH.js → statusUtils-daTlaeo0.js} +1 -1
  148. package/src/assets/web-panel/assets/{styleChecker-BxaCFlOL.js → styleChecker-DtyKMtHB.js} +1 -1
  149. package/src/assets/web-panel/assets/useFlexGapSupport-ekFwFs9h.js +1 -0
  150. package/src/assets/web-panel/assets/{useFs-C6VAU1_6.js → useFs-jRaSEoF4.js} +1 -1
  151. package/src/assets/web-panel/assets/{useMergedState-Dc9sO2d7.js → useMergedState-DXbJa_zE.js} +1 -1
  152. package/src/assets/web-panel/assets/{usePersonalDataHub-rEG6oXG8.js → usePersonalDataHub-DD5nGzjA.js} +1 -1
  153. package/src/assets/web-panel/assets/{useRefs-CAeFRzPf.js → useRefs-BrCfMLMc.js} +1 -1
  154. package/src/assets/web-panel/assets/{useState-LwExUYDO.js → useState-CnxtbLoC.js} +1 -1
  155. package/src/assets/web-panel/assets/{vendor-BMxUs6UX.js → vendor-Bn1JzzAn.js} +1 -1
  156. package/src/assets/web-panel/assets/{vnode-DOTafkEi.js → vnode-KcgLUiom.js} +1 -1
  157. package/src/assets/web-panel/assets/{zoom-BuwwN5K2.js → zoom-Cyk2PQ-s.js} +1 -1
  158. package/src/assets/web-panel/index.html +3 -3
  159. package/src/command-manifest.json +50 -1
  160. package/src/commands/agenda.js +228 -0
  161. package/src/commands/agent.js +23 -2
  162. package/src/commands/agents.js +62 -1
  163. package/src/commands/auto-mode.js +87 -0
  164. package/src/commands/background-session.js +499 -0
  165. package/src/commands/batch.js +345 -0
  166. package/src/commands/command.js +4 -1
  167. package/src/commands/permissions.js +49 -0
  168. package/src/commands/remote-control.js +407 -0
  169. package/src/data/changelog.json +12 -2
  170. package/src/gateways/ws/background-agent-protocol.js +337 -0
  171. package/src/gateways/ws/message-dispatcher.js +10 -0
  172. package/src/gateways/ws/remote-session-protocol.js +102 -1
  173. package/src/gateways/ws/ws-server.js +61 -0
  174. package/src/harness/mcp-client.js +8 -0
  175. package/src/index.js +22 -0
  176. package/src/lib/agent-batch.js +235 -0
  177. package/src/lib/agent-notify.js +66 -0
  178. package/src/lib/agent-schedule-store.js +330 -0
  179. package/src/lib/auto-mode-config.js +396 -0
  180. package/src/lib/background-agent-supervisor.js +202 -17
  181. package/src/lib/background-session-transport.js +315 -0
  182. package/src/lib/permission-denial-store.js +159 -0
  183. package/src/lib/remote-approval-bridge.js +360 -0
  184. package/src/lib/remote-control.js +270 -0
  185. package/src/lib/repl-denials.js +26 -0
  186. package/src/lib/shell-approval.js +4 -0
  187. package/src/lib/ws-rpc-client.js +150 -0
  188. package/src/repl/agent-repl.js +208 -16
  189. package/src/repl/permission-tier.js +31 -0
  190. package/src/runtime/agent-core.js +161 -0
  191. package/src/runtime/coding-agent-contract-shared.cjs +104 -0
  192. package/src/runtime/coding-agent-policy.cjs +28 -0
  193. package/src/runtime/headless-runner.js +126 -8
  194. package/src/runtime/headless-stream.js +87 -1
  195. package/src/runtime/mcp-config.js +11 -0
  196. package/src/runtime/mcp-tool-search.js +0 -0
  197. package/src/runtime/policies/agent-policy.js +17 -0
  198. package/src/workers/background-agent-worker.js +218 -37
  199. package/src/assets/web-panel/assets/AppLayout-B6Q6olcr.js +0 -9
  200. package/src/assets/web-panel/assets/OrderTableRenderer-CjNO-e9T.js +0 -1
  201. package/src/assets/web-panel/assets/Tasks-mhITRxjv.js +0 -1
  202. package/src/assets/web-panel/assets/devWarning-K_woVElh.js +0 -1
  203. package/src/assets/web-panel/assets/index-3ILvaBpo.js +0 -1
  204. package/src/assets/web-panel/assets/index-JpB-FoZq.js +0 -1
  205. package/src/assets/web-panel/assets/useFlexGapSupport-B1lWlqXN.js +0 -1
@@ -64,6 +64,7 @@ import {
64
64
  toOllamaMessages,
65
65
  imageUrlBlockToAnthropic,
66
66
  } from "../lib/image-input.js";
67
+ import { executeToolSearch, gateDeferredMcpCall } from "./mcp-tool-search.js";
67
68
 
68
69
  /**
69
70
  * Names of MCP servers currently mounted by an in-flight run_skill call.
@@ -1439,6 +1440,7 @@ export async function executeTool(name, args, context = {}) {
1439
1440
  additionalDirectories: context.additionalDirectories || null,
1440
1441
  sandbox: context.sandbox || null,
1441
1442
  ruleAllowed,
1443
+ settingsVerdict,
1442
1444
  subAgentDepth: context.subAgentDepth || 0,
1443
1445
  subAgentBudget: context.subAgentBudget || null,
1444
1446
  interactiveApproval: context.interactiveApproval || false,
@@ -2057,6 +2059,7 @@ async function executeToolInner(
2057
2059
  additionalDirectories,
2058
2060
  sandbox,
2059
2061
  ruleAllowed = false,
2062
+ settingsVerdict = null,
2060
2063
  subAgentDepth = 0,
2061
2064
  subAgentBudget = null,
2062
2065
  interactiveApproval = false,
@@ -2338,6 +2341,40 @@ async function executeToolInner(
2338
2341
  : {}),
2339
2342
  ...(classifyAllShell ? { classifyAllShell: true } : {}),
2340
2343
  };
2344
+ // Layer-by-layer explanation chain for a blocked command: which layers
2345
+ // were consulted and what each said (settings rules → shell policy →
2346
+ // approval gate). Attached to denial results so `/permissions denials`
2347
+ // and `cc permissions recent` can explain WHY, not just THAT.
2348
+ const buildPermissionChain = (gated) => {
2349
+ const chain = [
2350
+ {
2351
+ layer: "settings-rules",
2352
+ outcome:
2353
+ settingsVerdict?.decision || (ruleAllowed ? "allow" : "no-match"),
2354
+ rule: settingsVerdict?.rule || null,
2355
+ },
2356
+ {
2357
+ layer: "shell-policy",
2358
+ outcome: shellPolicy?.decision || null,
2359
+ rule: shellPolicy?.ruleId || null,
2360
+ reason: shellPolicy?.reason || null,
2361
+ },
2362
+ ];
2363
+ // A hard shell-policy deny returns before the gate is consulted
2364
+ // (via === "shell-policy") — no approval-gate layer to explain then.
2365
+ if (gated && gated.via !== "shell-policy") {
2366
+ chain.push({
2367
+ layer: "approval-gate",
2368
+ outcome: gated.decision,
2369
+ via: gated.via || null,
2370
+ policy: gated.policy || null,
2371
+ riskLevel: gated.riskLevel || null,
2372
+ rule: gated.gateRule || null,
2373
+ reason: gated.gateReason || null,
2374
+ });
2375
+ }
2376
+ return chain;
2377
+ };
2341
2378
  const override = getRuntimeToolDescriptorByCommand(args.command);
2342
2379
  let shellPolicy;
2343
2380
  let approvalOutcome = null;
@@ -2375,6 +2412,7 @@ async function executeToolInner(
2375
2412
  : `[ApprovalGate] command denied by the ${tierLabel}approval policy (via ${gated.via}). Retrying the same command will not help — it needs user approval. Tell the user (they can run it themselves, approve it, or relax the policy) and continue with other work.`,
2376
2413
  shellCommandPolicy: shellPolicy,
2377
2414
  approval: approvalOutcome,
2415
+ permissionChain: buildPermissionChain(gated),
2378
2416
  },
2379
2417
  override || runtimeDescriptor,
2380
2418
  );
@@ -2386,6 +2424,7 @@ async function executeToolInner(
2386
2424
  {
2387
2425
  error: `[Shell Policy] ${shellPolicy.reason} This command is blocked by policy and will not run — do not retry it; find another approach.`,
2388
2426
  shellCommandPolicy: shellPolicy,
2427
+ permissionChain: buildPermissionChain(null),
2389
2428
  },
2390
2429
  override || runtimeDescriptor,
2391
2430
  );
@@ -2971,6 +3010,100 @@ async function executeToolInner(
2971
3010
  }
2972
3011
  }
2973
3012
 
3013
+ case "notify": {
3014
+ try {
3015
+ const { sendAgentNotification } =
3016
+ await import("../lib/agent-notify.js");
3017
+ const outcome = await sendAgentNotification({
3018
+ title: args.title,
3019
+ body: args.body,
3020
+ level: args.level,
3021
+ });
3022
+ return attachDescriptor(outcome);
3023
+ } catch (err) {
3024
+ return attachDescriptor({ error: `notify failed: ${err.message}` });
3025
+ }
3026
+ }
3027
+
3028
+ case "schedule": {
3029
+ try {
3030
+ const { AgentScheduleStore } =
3031
+ await import("../lib/agent-schedule-store.js");
3032
+ const { parseDuration } = await import("../lib/loop.js");
3033
+ const store = new AgentScheduleStore();
3034
+ const action = String(args.action || "").toLowerCase();
3035
+ if (action === "wakeup") {
3036
+ if (!args.prompt) {
3037
+ return attachDescriptor({
3038
+ error: "schedule wakeup requires a prompt",
3039
+ });
3040
+ }
3041
+ const delayMs = args.delay ? parseDuration(args.delay) : 0;
3042
+ const entry = store.scheduleWakeup({
3043
+ prompt: args.prompt,
3044
+ delayMs,
3045
+ label: args.label || null,
3046
+ });
3047
+ return attachDescriptor({
3048
+ scheduled: entry,
3049
+ hint: "Run `cc agenda run` (e.g. via cron or `cc loop`) to fire due entries.",
3050
+ });
3051
+ }
3052
+ if (action === "cron") {
3053
+ if (!args.prompt || !args.cron) {
3054
+ return attachDescriptor({
3055
+ error: "schedule cron requires a prompt and a cron expression",
3056
+ });
3057
+ }
3058
+ const entry = store.createCron({
3059
+ prompt: args.prompt,
3060
+ cron: args.cron,
3061
+ label: args.label || null,
3062
+ });
3063
+ return attachDescriptor({ scheduled: entry });
3064
+ }
3065
+ if (action === "monitor") {
3066
+ if (!args.command) {
3067
+ return attachDescriptor({
3068
+ error: "schedule monitor requires a command",
3069
+ });
3070
+ }
3071
+ const intervalMs = args.interval
3072
+ ? parseDuration(args.interval)
3073
+ : 60000;
3074
+ const entry = store.createMonitor({
3075
+ command: args.command,
3076
+ intervalMs,
3077
+ stopWhen: args.stop_when || null,
3078
+ notify: args.notify_title ? { title: args.notify_title } : null,
3079
+ maxChecks: args.max_checks ?? null,
3080
+ label: args.label || null,
3081
+ });
3082
+ return attachDescriptor({ scheduled: entry });
3083
+ }
3084
+ if (action === "list") {
3085
+ return attachDescriptor({ entries: store.list() });
3086
+ }
3087
+ if (action === "cancel") {
3088
+ if (!args.id) {
3089
+ return attachDescriptor({
3090
+ error: "schedule cancel requires an id",
3091
+ });
3092
+ }
3093
+ const removed = store.cancel(args.id);
3094
+ return attachDescriptor({
3095
+ cancelled: removed ? removed.id : null,
3096
+ found: Boolean(removed),
3097
+ });
3098
+ }
3099
+ return attachDescriptor({
3100
+ error: `unknown schedule action "${args.action}". Valid: wakeup, cron, monitor, list, cancel.`,
3101
+ });
3102
+ } catch (err) {
3103
+ return attachDescriptor({ error: `schedule failed: ${err.message}` });
3104
+ }
3105
+ }
3106
+
2974
3107
  case "search_files": {
2975
3108
  // An explicit directory scopes the search to one root; otherwise span
2976
3109
  // cwd plus any --add-dir roots so cross-package searches find matches.
@@ -3347,6 +3480,15 @@ async function executeToolInner(
3347
3480
  }
3348
3481
  }
3349
3482
 
3483
+ // Internal tool-search tool (MCP context scaling): resolves deferred
3484
+ // MCP tool schemas from the registry attached at setup time. Read-only,
3485
+ // local — same risk class as list_skills, so no approval gate.
3486
+ if (localToolExecutor?.kind === "tool-search") {
3487
+ return attachDescriptor(
3488
+ executeToolSearch(localToolExecutor.registry, args || {}),
3489
+ );
3490
+ }
3491
+
3350
3492
  if (localToolExecutor?.kind === "mcp") {
3351
3493
  if (!mcpClient || typeof mcpClient.callTool !== "function") {
3352
3494
  return attachDescriptor({
@@ -3354,6 +3496,15 @@ async function executeToolInner(
3354
3496
  });
3355
3497
  }
3356
3498
 
3499
+ // Deferred-schema gate: a direct call to a tool whose schema was never
3500
+ // loaded returns a self-healing error embedding the schema (and marks
3501
+ // it loaded), instead of forwarding likely-malformed arguments to the
3502
+ // server. No-op unless tool search deferred this tool.
3503
+ const deferredGate = gateDeferredMcpCall(name, localToolExecutor);
3504
+ if (deferredGate) {
3505
+ return attachDescriptor(deferredGate);
3506
+ }
3507
+
3357
3508
  try {
3358
3509
  const result = await mcpClient.callTool(
3359
3510
  localToolExecutor.serverName,
@@ -6285,6 +6436,16 @@ export function formatToolArgs(name, args) {
6285
6436
  return `[${args.role}] ${(args.task || "").substring(0, 60)}`;
6286
6437
  case "search_sessions":
6287
6438
  return `"${(args.query || "").substring(0, 60)}"`;
6439
+ case "notify":
6440
+ return `${args.level || "info"}: ${(args.title || "").substring(0, 50)}`;
6441
+ case "schedule":
6442
+ return args.action === "cron"
6443
+ ? `cron ${args.cron || ""}`.trim()
6444
+ : args.action === "monitor"
6445
+ ? `monitor ${(args.command || "").substring(0, 40)}`.trim()
6446
+ : args.action === "wakeup"
6447
+ ? `wakeup +${args.delay || "0s"}`
6448
+ : String(args.action || "");
6288
6449
  default:
6289
6450
  return JSON.stringify(args).substring(0, 60);
6290
6451
  }
@@ -849,6 +849,110 @@ const CODING_AGENT_TOOL_CONTRACTS = Object.freeze([
849
849
  tags: ["tool:search_sessions", "contract:coding-agent", "tier:extension"],
850
850
  },
851
851
  },
852
+ {
853
+ name: "notify",
854
+ title: "Send Notification",
855
+ kind: "notify",
856
+ tier: "extension",
857
+ description:
858
+ "Push a notification to the user's configured channels (Telegram / WeCom / DingTalk / Feishu) and/or a paired mobile device. Use for long-running tasks: report completion, flag that you need a human decision, or surface an error. Best-effort: reports which channels delivered.",
859
+ inputSchema: {
860
+ type: "object",
861
+ properties: {
862
+ title: { type: "string", description: "Short notification title" },
863
+ body: { type: "string", description: "Notification body text" },
864
+ level: {
865
+ type: "string",
866
+ enum: ["info", "success", "failure"],
867
+ description: "Severity (default: info)",
868
+ },
869
+ },
870
+ required: ["title"],
871
+ },
872
+ ...TOOL_POLICY_METADATA.notify,
873
+ permissions: {
874
+ level: "standard",
875
+ scopes: ["network:notify"],
876
+ },
877
+ telemetry: {
878
+ category: "notify",
879
+ tags: ["tool:notify", "contract:coding-agent", "tier:extension"],
880
+ },
881
+ },
882
+ {
883
+ name: "schedule",
884
+ title: "Schedule / Monitor",
885
+ kind: "schedule",
886
+ tier: "extension",
887
+ description:
888
+ "Set up work that outlives this turn (persisted for `cc agenda run` to execute). Actions: `wakeup` — run a prompt once after a delay; `cron` — run a prompt on a 5-field cron schedule; `monitor` — run a shell command every interval and notify when its output matches a regex; `list` — show scheduled items; `cancel` — remove one by id.",
889
+ inputSchema: {
890
+ type: "object",
891
+ properties: {
892
+ action: {
893
+ type: "string",
894
+ enum: ["wakeup", "cron", "monitor", "list", "cancel"],
895
+ description: "What to do",
896
+ },
897
+ prompt: {
898
+ type: "string",
899
+ description:
900
+ "For wakeup/cron: the agent prompt to run (via `cc agent -p`) when it fires",
901
+ },
902
+ delay: {
903
+ type: "string",
904
+ description:
905
+ "For wakeup: how long from now before it fires (e.g. 30s, 5m, 1h)",
906
+ },
907
+ cron: {
908
+ type: "string",
909
+ description:
910
+ 'For cron: a 5-field cron expression, e.g. "0 9 * * 1" (09:00 every Monday)',
911
+ },
912
+ command: {
913
+ type: "string",
914
+ description: "For monitor: the shell command to run each interval",
915
+ },
916
+ interval: {
917
+ type: "string",
918
+ description:
919
+ "For monitor: how often to run the command (e.g. 30s, 5m)",
920
+ },
921
+ stop_when: {
922
+ type: "string",
923
+ description:
924
+ "For monitor: a regex; when the command output matches, the monitor fires its notification and stops",
925
+ },
926
+ notify_title: {
927
+ type: "string",
928
+ description: "For monitor: notification title sent when it matches",
929
+ },
930
+ max_checks: {
931
+ type: "number",
932
+ description:
933
+ "For monitor: give up after this many checks (optional safety cap)",
934
+ },
935
+ label: {
936
+ type: "string",
937
+ description: "Optional human label for list/cancel",
938
+ },
939
+ id: {
940
+ type: "string",
941
+ description: "For cancel: the schedule entry id to remove",
942
+ },
943
+ },
944
+ required: ["action"],
945
+ },
946
+ ...TOOL_POLICY_METADATA.schedule,
947
+ permissions: {
948
+ level: "standard",
949
+ scopes: ["schedule:write"],
950
+ },
951
+ telemetry: {
952
+ category: "schedule",
953
+ tags: ["tool:schedule", "contract:coding-agent", "tier:extension"],
954
+ },
955
+ },
852
956
  ]);
853
957
 
854
958
  const CODING_AGENT_MVP_TOOL_NAMES = Object.freeze(
@@ -261,6 +261,34 @@ const TOOL_POLICY_METADATA = Object.freeze({
261
261
  approvalFlow: "auto",
262
262
  isReadOnly: true,
263
263
  },
264
+ // Sends an outbound notification to the user's own channels/device. No
265
+ // workspace mutation and no un-gated shell; blocked in plan mode (an
266
+ // external side effect) but otherwise auto — it targets the user, not a
267
+ // third party.
268
+ notify: {
269
+ riskLevel: RISK_LEVELS.LOW,
270
+ category: TOOL_CATEGORIES.WRITE,
271
+ availableInPlanMode: false,
272
+ planModeBehavior: "blocked",
273
+ requiresPlanApproval: false,
274
+ requiresConfirmation: false,
275
+ approvalFlow: "auto",
276
+ isReadOnly: false,
277
+ },
278
+ // Persists a wakeup/cron/monitor entry for `cc agenda run` to execute later.
279
+ // Writing the schedule is itself harmless (a JSONL row under the user's
280
+ // config dir); the eventual `cc agent`/shell run is gated when the agenda
281
+ // consumer executes it. Blocked in plan mode (schedules future side effects).
282
+ schedule: {
283
+ riskLevel: RISK_LEVELS.LOW,
284
+ category: TOOL_CATEGORIES.WRITE,
285
+ availableInPlanMode: false,
286
+ planModeBehavior: "blocked",
287
+ requiresPlanApproval: false,
288
+ requiresConfirmation: false,
289
+ approvalFlow: "auto",
290
+ isReadOnly: false,
291
+ },
264
292
  });
265
293
 
266
294
  function normalizeGitCommand(command) {
@@ -15,9 +15,11 @@
15
15
  * Permission model: headless cannot show an interactive approval prompt, so the
16
16
  * default is fail-closed (deny MEDIUM/HIGH-risk shell). --permission-mode opts
17
17
  * into a looser tier:
18
- * - (default) / plan → STRICT + deny-confirmer (plan also restricts tools)
19
- * - acceptEdits → TRUSTED + deny-confirmer (HIGH-risk shell still denied)
20
- * - bypassPermissions → AUTOPILOT (everything allowed, no confirm)
18
+ * - (default) / manual / dontAsk / plan → STRICT + deny-confirmer
19
+ * (plan also restricts tools)
20
+ * - auto / acceptEdits → TRUSTED + deny-confirmer
21
+ * (HIGH-risk shell still denied)
22
+ * - bypassPermissions → AUTOPILOT (everything allowed)
21
23
  */
22
24
 
23
25
  import { bootstrap } from "./bootstrap.js";
@@ -33,6 +35,7 @@ import {
33
35
  resolvePermissionPromptTool,
34
36
  makePermissionPromptConfirmer,
35
37
  } from "./mcp-config.js";
38
+ import { maybeApplyToolSearch } from "./mcp-tool-search.js";
36
39
  import { IterationBudget } from "../lib/iteration-budget.js";
37
40
  import {
38
41
  startSession as jsonlStartSession,
@@ -68,6 +71,9 @@ export const READ_ONLY_TOOLS = Object.freeze([
68
71
 
69
72
  const VALID_PERMISSION_MODES = Object.freeze([
70
73
  "default",
74
+ "manual",
75
+ "auto",
76
+ "dontAsk",
71
77
  "plan",
72
78
  "acceptEdits",
73
79
  "bypassPermissions",
@@ -81,19 +87,30 @@ export { installPipeSafety } from "./pipe-safety.js";
81
87
  import { installPipeSafety } from "./pipe-safety.js";
82
88
 
83
89
  /**
84
- * Resolve a --permission-mode string into the session-policy tier + a
85
- * non-interactive confirmer + whether to clamp tools to the read-only set.
90
+ * Normalize a public --permission-mode spelling to the canonical internal mode.
86
91
  *
87
- * @param {string} mode
88
- * @returns {{ sessionPolicy: string, confirmer: (ctx:any)=>Promise<boolean>, readOnly: boolean }}
92
+ * @param {string} [mode]
93
+ * @returns {string}
89
94
  */
90
- export function resolvePermissionMode(mode = "default") {
95
+ export function normalizePermissionMode(mode = "default") {
91
96
  const m = mode || "default";
92
97
  if (!VALID_PERMISSION_MODES.includes(m)) {
93
98
  throw new Error(
94
99
  `Invalid --permission-mode "${m}". Expected one of: ${VALID_PERMISSION_MODES.join(", ")}`,
95
100
  );
96
101
  }
102
+ return m;
103
+ }
104
+
105
+ /**
106
+ * Resolve a --permission-mode string into the session-policy tier + a
107
+ * non-interactive confirmer + whether to clamp tools to the read-only set.
108
+ *
109
+ * @param {string} mode
110
+ * @returns {{ sessionPolicy: string, confirmer: (ctx:any)=>Promise<boolean>, readOnly: boolean, allowInteractiveApprovals: boolean }}
111
+ */
112
+ export function resolvePermissionMode(mode = "default") {
113
+ const m = normalizePermissionMode(mode);
97
114
  // Headless can't ask a human — deny when a confirm would be required.
98
115
  const denyConfirmer = async () => false;
99
116
  const allowConfirmer = async () => true;
@@ -103,25 +120,38 @@ export function resolvePermissionMode(mode = "default") {
103
120
  sessionPolicy: "autopilot",
104
121
  confirmer: allowConfirmer,
105
122
  readOnly: false,
123
+ allowInteractiveApprovals: false,
106
124
  };
125
+ case "auto":
107
126
  case "acceptEdits":
108
127
  return {
109
128
  sessionPolicy: "trusted",
110
129
  confirmer: denyConfirmer,
111
130
  readOnly: false,
131
+ allowInteractiveApprovals: true,
112
132
  };
113
133
  case "plan":
114
134
  return {
115
135
  sessionPolicy: "strict",
116
136
  confirmer: denyConfirmer,
117
137
  readOnly: true,
138
+ allowInteractiveApprovals: true,
139
+ };
140
+ case "dontAsk":
141
+ return {
142
+ sessionPolicy: "strict",
143
+ confirmer: denyConfirmer,
144
+ readOnly: false,
145
+ allowInteractiveApprovals: false,
118
146
  };
147
+ case "manual":
119
148
  case "default":
120
149
  default:
121
150
  return {
122
151
  sessionPolicy: "strict",
123
152
  confirmer: denyConfirmer,
124
153
  readOnly: false,
154
+ allowInteractiveApprovals: true,
125
155
  };
126
156
  }
127
157
  }
@@ -610,6 +640,28 @@ export async function runAgentHeadless(options = {}, deps = {}) {
610
640
  let approvalGate = null;
611
641
  try {
612
642
  approvalGate = await getApprovalGate();
643
+ if (approvalGate && (options.permissionMode || "default") === "auto") {
644
+ // autoMode.decisions: user-configured riskLevel → allow/ask/deny
645
+ // classifier. Only wrap when settings actually customize the map so the
646
+ // unconfigured auto path keeps the byte-identical trusted-tier mapping.
647
+ try {
648
+ const {
649
+ loadAutoModeConfig,
650
+ resolveAutoModeDecisions,
651
+ createAutoModeApprovalGate,
652
+ } = await import("../lib/auto-mode-config.js");
653
+ const autoConfig = loadAutoModeConfig({
654
+ cwd,
655
+ settingsFile: options.settingsFile,
656
+ });
657
+ const resolved = resolveAutoModeDecisions(autoConfig.effective);
658
+ if (resolved.customized) {
659
+ approvalGate = createAutoModeApprovalGate(approvalGate, resolved);
660
+ }
661
+ } catch {
662
+ // fail to the default trusted mapping — never block the run
663
+ }
664
+ }
613
665
  if (approvalGate) {
614
666
  if (typeof approvalGate.setSessionPolicy === "function") {
615
667
  approvalGate.setSessionPolicy(sessionId, perm.sessionPolicy);
@@ -800,6 +852,22 @@ export async function runAgentHeadless(options = {}, deps = {}) {
800
852
  writeErr(` mcp: ${c.server} (${c.tools} tools)\n`);
801
853
  }
802
854
  }
855
+ // MCP tool search (context scaling): when the tool schemas would eat a
856
+ // significant share of the context window, defer them behind the
857
+ // internal tool_search tool. Below-threshold / disabled → no-op, the
858
+ // wiring object is untouched.
859
+ if (mcp) {
860
+ try {
861
+ (deps.maybeApplyToolSearch || maybeApplyToolSearch)(mcp, {
862
+ model,
863
+ provider,
864
+ cwd: options.cwd || process.cwd(),
865
+ writeErr: isText ? writeErr : () => {},
866
+ });
867
+ } catch {
868
+ // best-effort — full schemas still work without deferral
869
+ }
870
+ }
803
871
  } catch (err) {
804
872
  writeErr(`Error: ${err.message}\n`);
805
873
  emitHeadlessError(err.message);
@@ -861,6 +929,35 @@ export async function runAgentHeadless(options = {}, deps = {}) {
861
929
  }
862
930
  }
863
931
 
932
+ // --remote-control: route CONFIRM-tier approvals to paired mobile/web
933
+ // devices (第四阶段 #2). Self-hosts a lightweight WS server + approval
934
+ // bridge for THIS run's session, prints the pairing URI/QR to stderr, and
935
+ // installs the remote confirmer on the gate (same override point as
936
+ // --permission-prompt-tool; that flag wins when both are given since it is
937
+ // installed above and this block skips). Fail-closed on timeout.
938
+ let _remoteApproval = null;
939
+ if (options.remoteControl && !options.permissionPromptTool) {
940
+ try {
941
+ const { startHeadlessRemoteApproval } =
942
+ await import("../lib/remote-approval-bridge.js");
943
+ _remoteApproval = await (
944
+ deps.startHeadlessRemoteApproval || startHeadlessRemoteApproval
945
+ )({
946
+ agentSessionId: sessionId,
947
+ writeErr,
948
+ isText,
949
+ });
950
+ if (approvalGate && typeof approvalGate.setConfirmer === "function") {
951
+ approvalGate.setConfirmer(_remoteApproval.confirmer);
952
+ }
953
+ } catch (err) {
954
+ // Remote approval could not come up → keep headless fail-closed rather
955
+ // than running un-gated; say why so the user can fix pairing.
956
+ writeErr(` remote-control: unavailable (${err.message})\n`);
957
+ _remoteApproval = null;
958
+ }
959
+ }
960
+
864
961
  // --otlp <file>: attach an OpenTelemetry recorder so the real agent loop's
865
962
  // model/tool/retry spans are captured and exported as OTLP/JSON on exit.
866
963
  // Off by default (zero cost) — only built when a path is requested.
@@ -1350,6 +1447,15 @@ export async function runAgentHeadless(options = {}, deps = {}) {
1350
1447
  } catch {
1351
1448
  // best-effort — never mask the run's own outcome
1352
1449
  }
1450
+ // Tear down the --remote-control approval bridge + its self-hosted WS
1451
+ // server so the run's port/socket never outlives the invocation.
1452
+ if (_remoteApproval) {
1453
+ try {
1454
+ await _remoteApproval.close();
1455
+ } catch {
1456
+ // best-effort — never mask the run's own outcome
1457
+ }
1458
+ }
1353
1459
  // settings.json Stop + SessionEnd hooks when the run finishes. Stop is the
1354
1460
  // canonical async-hook trigger ("run the test suite at turn end"); fire its
1355
1461
  // async hooks fire-and-forget, then settle so a fast background check can
@@ -1502,6 +1608,18 @@ export async function runAgentHeadless(options = {}, deps = {}) {
1502
1608
  } else if (isStream) {
1503
1609
  emitStream({ type: "denials_summary", count: denials.length, denials });
1504
1610
  }
1611
+ try {
1612
+ const { appendRecentDenials } =
1613
+ await import("../lib/permission-denial-store.js");
1614
+ appendRecentDenials(denials, {
1615
+ sessionId,
1616
+ permissionMode: options.permissionMode || "default",
1617
+ cwd,
1618
+ source: "headless",
1619
+ });
1620
+ } catch {
1621
+ // Persisting the review surface is best-effort; never affect the run.
1622
+ }
1505
1623
  }
1506
1624
 
1507
1625
  if (isStream) {