chainlesschain 0.162.154 → 0.162.155
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.
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-CIpoXyVI.js → AIOps-Cth_zZeA.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-Ch_QXIPk.js → ActionButton-zfwA3Oxn.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-CvaBYShv.js → Analytics-FeKc4aU2.js} +2 -2
- package/src/assets/web-panel/assets/AppLayout-C6WIrDZk.js +9 -0
- package/src/assets/web-panel/assets/{Audit-Bng8eHCW.js → Audit-C2gRq5Qi.js} +1 -1
- package/src/assets/web-panel/assets/BackgroundAgents-Dq2340Kl.css +1 -0
- package/src/assets/web-panel/assets/BackgroundAgents-TeTIFiW-.js +1 -0
- package/src/assets/web-panel/assets/{Backup-DvBs8-lm.js → Backup-CLJk3k6i.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-DKwajm0g.js → BaseInput-5ms537NY.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-BoGpZ3uO.js → Chat-4Nr5U9PJ.js} +5 -5
- package/src/assets/web-panel/assets/ChatBubbleRenderer-BFEnA8LN.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-BMJEmxFQ.js → Checkbox-B2Bca5ev.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-Bsoag5CV.js → Codegen-D6Uysdbv.js} +1 -1
- package/src/assets/web-panel/assets/{Col-CDjH9-sp.js → Col-B3XQ8xc7.js} +1 -1
- package/src/assets/web-panel/assets/{Community-Cg2QHPQ6.js → Community-CqwKtoPz.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BU2BaqP-.js → Compact-BZZINTus.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-D6ZYMCqZ.js → Compliance-Dr3BXkWA.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-DaZ8VW-I.js → Cowork-rx_x9Wqc.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-DQY1RH9v.js → Cron-BpZr7rHc.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BVR28vk9.js → Crosschain-CeV5EYVg.js} +1 -1
- package/src/assets/web-panel/assets/{DID-N44KvTP-.js → DID-fe2Qaowz.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-Bjhlx6wS.js → Dashboard-Fh_EdWVa.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-CpLYxpy1.js → Dropdown-N1PIwWYf.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-srEITSPI.js → EmailListRenderer-B6vK93Ed.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-juOC_fX2.js → FamilyGuardDashboard-e3iUZhAg.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-nRaQtaXW.js → Federation-uInc9YcN.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-DflhTj09.js → FormItemContext-DmQZSX0z.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-DulI_3NI.js → GenericCardRenderer-CV_Z8OAo.js} +1 -1
- package/src/assets/web-panel/assets/{Git-BxOPefXn.js → Git-jXAhNN8d.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-C6G_Wnwi.js → Governance-Blw1esBB.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-BnFYa2BF.js → Inference-CoK8Ia0Z.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-TqQLEabm.js → KnowledgeGraph-5xrglMTK.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BH4AVh7s.js → Logs-BGIG9798.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-DoWLTaEE.js → Marketplace-DFqhJMuY.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-D6yC7OG_.js → McpTools-7U8tEhwK.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-DLCeHj21.js → Memory-BrvboPLE.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-BZBE0GRl.js → MobileBridge-nzcd7d1H.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-DJA15G1J.js → MobileProjects-DETVNKa7.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-CyTKJDHn.js → Mtc-SZcCYec3.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-DZKjgEVc.js → MtcAudit-Cx_d3N17.js} +5 -5
- package/src/assets/web-panel/assets/{Multisig-2rMPNhSO.js → Multisig-DtcmSUo-.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-VeMk4Fx-.js → NLProgramming-Dgx2QHl-.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-jr2GHMhh.js → Notes-oHTGEHoJ.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-WN9VZyFb.js → NotificationSettings-qKD-IlOl.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-CjNO-e9T.js → OrderTableRenderer-CiU4Sy1J.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-DdjVHuzT.js → Organization-BBNXnltP.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-l8vBsGGq.js → Overflow-Df2fDbvT.js} +1 -1
- package/src/assets/web-panel/assets/{OverrideContext-BlgL9440.js → OverrideContext-C0w0P4A3.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-CVQDHHwq.js → P2P-DUHj6zjO.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-CTsNh8qE.js → PdhVaultBrowser-B2nWgdoV.js} +4 -4
- package/src/assets/web-panel/assets/{Permissions-DLW87uyK.js → Permissions-CiW0afny.js} +3 -3
- package/src/assets/web-panel/assets/{PersonalDataHub-N8OvASTx.js → PersonalDataHub-cc1mHU_D.js} +2 -2
- package/src/assets/web-panel/assets/{Pipeline-BPZmdlz2.js → Pipeline-B93zTapU.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-MFZw_GNh.js → Privacy-Db1jXWfd.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-zW0pEsfU.js → ProjectInit-DFoZ1h9U.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-CppVHr_o.js → ProjectSettings-BkkUnmYs.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-BTglkJO2.js → Projects-Xa779XLC.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-CPHZ6JKT.js → Providers-DOF01IEN.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-DO4klNZ2.js → QrScannerModal-TohRsKd0.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-B37TTqUa.js → QuickAsk-DjLcr1ZU.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-n7KIfK7y.js → Recommend--Ism6bMx.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-SjVoS9mm.js → RemoteSession-B8CL3YHM.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-Dt6RTeHi.js → Reputation-CaRxB3Ep.js} +1 -1
- package/src/assets/web-panel/assets/{Row-Dty6jz5V.js → Row-BioxZBBH.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-DDLhqw5j.js → RssFeed-B2LipwH3.js} +3 -3
- package/src/assets/web-panel/assets/{Search-DrYbXi-W.js → Search-BpjLBV8u.js} +1 -1
- package/src/assets/web-panel/assets/{Security-Fc9Z6fbB.js → Security-TibHuJWP.js} +3 -3
- package/src/assets/web-panel/assets/{Services-BAhPgjSM.js → Services-DtkuWseW.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-8JF7EPfE.js → Skeleton-BIcPQae6.js} +1 -1
- package/src/assets/web-panel/assets/Skills-DoTWIQOX.js +1 -0
- package/src/assets/web-panel/assets/{Sla-CSvjcATc.js → Sla-uUSU185m.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-Demx7Gnx.js → SpeechSettings-B7Bgc9mk.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-CKLnRWJr.js → SyncSettings-K-XrJ2s9.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-mhITRxjv.js → Tasks-B3hEXNcI.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-AjQI5rxD.js → Templates-CaFnyGDK.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-dbtEh1yR.js → Tenant-C2X0PtEC.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-DX-9jjfk.js → Terminal-CKU5yVsT.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-C11EDZRt.js → TimelineRenderer-8trZeYDC.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-GiA9fiZn.js → Tokens-BUEEe9US.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-BPcnclJn.js → Trigger-COU97I7Y.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-C-DVY8nq.js → Trust-DNWQChaP.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-DxodSwME.js → UkeySign-DWX_VtCp.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-D_WV0O8O.js → VideoEditing-D9eIDhsp.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-DSeNlVSU.js → Wallet-BOnCCgIL.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-Bwuuvpz1.js → WebAuthn-DN4dD-W8.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-8M96EU1N.js → WorkflowEditor-ClvDb_jX.js} +1 -1
- package/src/assets/web-panel/assets/{chat-D5S6SmLm.js → chat-DIxQ_Eeb.js} +1 -1
- package/src/assets/web-panel/assets/{collapseMotion-DVfhbsdP.js → collapseMotion-CqM1QOwM.js} +1 -1
- package/src/assets/web-panel/assets/{colors-5fbnGkAP.js → colors-BOX3WKcP.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-DaSuuMlW.js → compact-item-Bny82VDi.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BNOHcXAF.js → createContext-Cnoy3enS.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-Dr9dzlQ5.js +1 -0
- package/src/assets/web-panel/assets/{echarts-CL0F2SnX.js → echarts-C61Vn3mP.js} +1 -1
- package/src/assets/web-panel/assets/{hasIn-DONwZUra.js → hasIn-BJ7fkx3F.js} +1 -1
- package/src/assets/web-panel/assets/{icons-BsDmGpcT.js → icons-DEmcnxBf.js} +1 -1
- package/src/assets/web-panel/assets/{index-B_yDD9Lm.js → index--CcGiwAI.js} +1 -1
- package/src/assets/web-panel/assets/{index-5_fj5gVd.js → index--Xb2lq6E.js} +1 -1
- package/src/assets/web-panel/assets/{index-uR5KTNZA.js → index-2N9Y1cLx.js} +1 -1
- package/src/assets/web-panel/assets/{index-MOgO5Yvx.js → index-7GLXvG8p.js} +1 -1
- package/src/assets/web-panel/assets/{index-CLXf-8ek.js → index-B1-h2QrC.js} +1 -1
- package/src/assets/web-panel/assets/{index-D7gs_3Ck.js → index-B6WoW8DJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CztESwB4.js → index-B7Fah_BX.js} +1 -1
- package/src/assets/web-panel/assets/index-B92M3t6B.js +1 -0
- package/src/assets/web-panel/assets/{index-BEO5f7Vf.js → index-BDkN6YOI.js} +1 -1
- package/src/assets/web-panel/assets/{index-_6S41ikV.js → index-BJ8-oLHI.js} +1 -1
- package/src/assets/web-panel/assets/{index-CrkCFohi.js → index-BM0mQhVv.js} +1 -1
- package/src/assets/web-panel/assets/{index-BrAJSu-v.js → index-BP7HFsAK.js} +1 -1
- package/src/assets/web-panel/assets/{index-BcqOK7Lv.js → index-BWPey0Uf.js} +1 -1
- package/src/assets/web-panel/assets/{index-BLzMuz38.js → index-BbZynivw.js} +1 -1
- package/src/assets/web-panel/assets/{index-DbdQBn4n.js → index-BcmXbIwv.js} +1 -1
- package/src/assets/web-panel/assets/{index-KwV9g_cf.js → index-Bk5yST66.js} +1 -1
- package/src/assets/web-panel/assets/{index-ClGqO-2X.js → index-Bvheiep2.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dd7xwGcD.js → index-C--PPqwi.js} +1 -1
- package/src/assets/web-panel/assets/{index-DP2-3A_j.js → index-C1nHEOFE.js} +1 -1
- package/src/assets/web-panel/assets/{index-CAYmc5Nh.js → index-CEPTiBWy.js} +1 -1
- package/src/assets/web-panel/assets/{index-LRIt-AFl.js → index-CEq5Vh0i.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dm5A-MN5.js → index-CN6emwbq.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cf2OCa8q.js → index-CQ-xLCVb.js} +1 -1
- package/src/assets/web-panel/assets/{index-DHx2lhHW.js → index-CW6n-y1L.js} +1 -1
- package/src/assets/web-panel/assets/{index-DM3Ug61e.js → index-CZ6CHiCQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BJp4kRjw.js → index-CZCTan8Q.js} +1 -1
- package/src/assets/web-panel/assets/{index-PsMErAB1.js → index-CnjtAKdw.js} +1 -1
- package/src/assets/web-panel/assets/{index-CEzd5m5a.js → index-D2qNdrNW.js} +1 -1
- package/src/assets/web-panel/assets/{index-B4L3gsNA.js → index-D9QEc7Tq.js} +1 -1
- package/src/assets/web-panel/assets/{index-BcWMZqb4.js → index-DBhkJqVF.js} +1 -1
- package/src/assets/web-panel/assets/{index-CSfYJqXt.js → index-DSRIjAWO.js} +1 -1
- package/src/assets/web-panel/assets/{index-CmUOjyL6.js → index-DxX8hhmL.js} +1 -1
- package/src/assets/web-panel/assets/{index-RkInlwV0.js → index-Kio1ekYg.js} +1 -1
- package/src/assets/web-panel/assets/{index-CTZeOnzO.js → index-OaNFXV8N.js} +1 -1
- package/src/assets/web-panel/assets/{index-BK2N5yVW.js → index-Ty4w18mR.js} +4 -4
- package/src/assets/web-panel/assets/{index-Cn5LtyIH.js → index-ceH9DsAi.js} +1 -1
- package/src/assets/web-panel/assets/index-d2UcGpK-.js +1 -0
- package/src/assets/web-panel/assets/{index-xo2f4DZR.js → index-qOBKrw-g.js} +1 -1
- package/src/assets/web-panel/assets/{index-vYtprlNs.js → index-rn6xd12s.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-CxwLynp9.js → initDefaultProps-BmxMBuUZ.js} +1 -1
- package/src/assets/web-panel/assets/{motion-XbPvGJbW.js → motion-DnXNwo2k.js} +1 -1
- package/src/assets/web-panel/assets/{move-D1u5pcgS.js → move-WnIJwmZN.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-BDxhTBMH.js → mtc-parser-DAto-c2w.js} +1 -1
- package/src/assets/web-panel/assets/{omit-EtIFGg5k.js → omit-BtvW9W0H.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-D33sWb0l.js → pickAttrs-DFqI9-_C.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BwFRzaGo.js → placementArrow-Dax6wOK3.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-CBk2WqvQ.js → responsiveObserve-BUFWWC83.js} +1 -1
- package/src/assets/web-panel/assets/{slide-B52HtrKn.js → slide-DyARyuSz.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-D4qxqWnH.js → statusUtils-BZSDugpi.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-BxaCFlOL.js → styleChecker-B7o_K-4R.js} +1 -1
- package/src/assets/web-panel/assets/useFlexGapSupport-Dg7Bg8VG.js +1 -0
- package/src/assets/web-panel/assets/{useFs-C6VAU1_6.js → useFs-DQYXo5Rk.js} +1 -1
- package/src/assets/web-panel/assets/{useMergedState-Dc9sO2d7.js → useMergedState-DXbJa_zE.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-rEG6oXG8.js → usePersonalDataHub-E8NBAnkq.js} +1 -1
- package/src/assets/web-panel/assets/{useRefs-CAeFRzPf.js → useRefs-BrCfMLMc.js} +1 -1
- package/src/assets/web-panel/assets/{useState-LwExUYDO.js → useState-CnxtbLoC.js} +1 -1
- package/src/assets/web-panel/assets/{vendor-BMxUs6UX.js → vendor-Bn1JzzAn.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-DOTafkEi.js → vnode-DD71_w1b.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-BuwwN5K2.js → zoom-zc9EMd3q.js} +1 -1
- package/src/assets/web-panel/index.html +3 -3
- package/src/command-manifest.json +36 -1
- package/src/commands/agent.js +23 -2
- package/src/commands/agents.js +62 -1
- package/src/commands/auto-mode.js +87 -0
- package/src/commands/background-session.js +499 -0
- package/src/commands/command.js +4 -1
- package/src/commands/permissions.js +49 -0
- package/src/commands/remote-control.js +407 -0
- package/src/data/changelog.json +7 -2
- package/src/gateways/ws/background-agent-protocol.js +337 -0
- package/src/gateways/ws/message-dispatcher.js +10 -0
- package/src/gateways/ws/remote-session-protocol.js +102 -1
- package/src/gateways/ws/ws-server.js +61 -0
- package/src/harness/mcp-client.js +8 -0
- package/src/index.js +10 -0
- package/src/lib/auto-mode-config.js +396 -0
- package/src/lib/background-agent-supervisor.js +202 -17
- package/src/lib/background-session-transport.js +315 -0
- package/src/lib/permission-denial-store.js +159 -0
- package/src/lib/remote-approval-bridge.js +360 -0
- package/src/lib/remote-control.js +270 -0
- package/src/lib/repl-denials.js +26 -0
- package/src/lib/shell-approval.js +4 -0
- package/src/lib/ws-rpc-client.js +150 -0
- package/src/repl/agent-repl.js +208 -16
- package/src/repl/permission-tier.js +31 -0
- package/src/runtime/agent-core.js +57 -0
- package/src/runtime/headless-runner.js +126 -8
- package/src/runtime/headless-stream.js +87 -1
- package/src/runtime/mcp-config.js +11 -0
- package/src/runtime/mcp-tool-search.js +0 -0
- package/src/runtime/policies/agent-policy.js +17 -0
- package/src/workers/background-agent-worker.js +218 -37
- package/src/assets/web-panel/assets/AppLayout-B6Q6olcr.js +0 -9
- package/src/assets/web-panel/assets/ChatBubbleRenderer-mDQl_PXO.js +0 -1
- package/src/assets/web-panel/assets/Skills-DL39dY0t.js +0 -1
- package/src/assets/web-panel/assets/devWarning-K_woVElh.js +0 -1
- package/src/assets/web-panel/assets/index-3ILvaBpo.js +0 -1
- package/src/assets/web-panel/assets/index-JpB-FoZq.js +0 -1
- package/src/assets/web-panel/assets/useFlexGapSupport-B1lWlqXN.js +0 -1
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal promise-based WebSocket RPC client for the cc WS server protocol
|
|
3
|
+
* (auth + id-correlated request/response + fire-hose events). Used by
|
|
4
|
+
* `cc remote-control` for its in-process loopback HOST connection — the
|
|
5
|
+
* remote-session registry closes a session when its host disconnects, so the
|
|
6
|
+
* unified entry keeps this client open for the server's lifetime.
|
|
7
|
+
*
|
|
8
|
+
* Protocol notes:
|
|
9
|
+
* - every request carries a unique `id`. Flat responses (auth, remote-session-*)
|
|
10
|
+
* echo it back as `id`; envelope responses (session-create & friends,
|
|
11
|
+
* createCodingAgentEvent) repurpose `id` as a random eventId and carry the
|
|
12
|
+
* correlation key in `requestId` — so match `requestId` FIRST, then `id`.
|
|
13
|
+
* - the FIRST frame matching a pending key settles that request (fine for
|
|
14
|
+
* command/one-shot topics; streaming topics need onEvent, not request()).
|
|
15
|
+
* - messages matching no pending key are events → onEvent callbacks.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import WebSocket from "ws";
|
|
19
|
+
|
|
20
|
+
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
21
|
+
|
|
22
|
+
export class WsRpcClient {
|
|
23
|
+
constructor({ url, WebSocketImpl = WebSocket, timeoutMs } = {}) {
|
|
24
|
+
if (!url) throw new Error("url is required");
|
|
25
|
+
this.url = url;
|
|
26
|
+
this.WebSocketImpl = WebSocketImpl;
|
|
27
|
+
this.timeoutMs = timeoutMs || DEFAULT_TIMEOUT_MS;
|
|
28
|
+
this.ws = null;
|
|
29
|
+
this._pending = new Map();
|
|
30
|
+
this._counter = 0;
|
|
31
|
+
this._eventHandlers = new Set();
|
|
32
|
+
this._closed = false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
onEvent(handler) {
|
|
36
|
+
this._eventHandlers.add(handler);
|
|
37
|
+
return () => this._eventHandlers.delete(handler);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
connect() {
|
|
41
|
+
return new Promise((resolve, reject) => {
|
|
42
|
+
const ws = new this.WebSocketImpl(this.url);
|
|
43
|
+
this.ws = ws;
|
|
44
|
+
const onOpen = () => {
|
|
45
|
+
ws.off?.("error", onError);
|
|
46
|
+
resolve();
|
|
47
|
+
};
|
|
48
|
+
const onError = (err) => {
|
|
49
|
+
reject(new Error(`WebSocket connect failed: ${err.message || err}`));
|
|
50
|
+
};
|
|
51
|
+
ws.once("open", onOpen);
|
|
52
|
+
ws.once("error", onError);
|
|
53
|
+
ws.on("message", (data) => this._onMessage(data));
|
|
54
|
+
ws.on("close", () => this._onClose());
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
_onMessage(data) {
|
|
59
|
+
let message;
|
|
60
|
+
try {
|
|
61
|
+
message = JSON.parse(data.toString("utf8"));
|
|
62
|
+
} catch {
|
|
63
|
+
return; // non-JSON frames are not part of this protocol
|
|
64
|
+
}
|
|
65
|
+
const key =
|
|
66
|
+
message.requestId && this._pending.has(message.requestId)
|
|
67
|
+
? message.requestId
|
|
68
|
+
: message.id && this._pending.has(message.id)
|
|
69
|
+
? message.id
|
|
70
|
+
: null;
|
|
71
|
+
if (key) {
|
|
72
|
+
const pending = this._pending.get(key);
|
|
73
|
+
this._pending.delete(key);
|
|
74
|
+
clearTimeout(pending.timer);
|
|
75
|
+
if (message.type === "error") {
|
|
76
|
+
pending.reject(
|
|
77
|
+
new Error(
|
|
78
|
+
message.message ||
|
|
79
|
+
message.payload?.message ||
|
|
80
|
+
message.code ||
|
|
81
|
+
message.payload?.code ||
|
|
82
|
+
"server error",
|
|
83
|
+
),
|
|
84
|
+
);
|
|
85
|
+
} else {
|
|
86
|
+
pending.resolve(message);
|
|
87
|
+
}
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
for (const handler of this._eventHandlers) {
|
|
91
|
+
try {
|
|
92
|
+
handler(message);
|
|
93
|
+
} catch {
|
|
94
|
+
// one bad handler must not break the read loop
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
_onClose() {
|
|
100
|
+
this._closed = true;
|
|
101
|
+
for (const [, pending] of this._pending) {
|
|
102
|
+
clearTimeout(pending.timer);
|
|
103
|
+
pending.reject(new Error("connection closed"));
|
|
104
|
+
}
|
|
105
|
+
this._pending.clear();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Send `{id, type, ...payload}` and await the matching response frame. */
|
|
109
|
+
request(type, payload = {}, { timeoutMs } = {}) {
|
|
110
|
+
if (this._closed || !this.ws || this.ws.readyState !== this.ws.OPEN) {
|
|
111
|
+
return Promise.reject(new Error("not connected"));
|
|
112
|
+
}
|
|
113
|
+
const id = `rpc-${process.pid}-${++this._counter}`;
|
|
114
|
+
const frame = { id, type, ...payload };
|
|
115
|
+
return new Promise((resolve, reject) => {
|
|
116
|
+
const timer = setTimeout(() => {
|
|
117
|
+
this._pending.delete(id);
|
|
118
|
+
reject(new Error(`request "${type}" timed out`));
|
|
119
|
+
}, timeoutMs || this.timeoutMs);
|
|
120
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
121
|
+
this._pending.set(id, { resolve, reject, timer });
|
|
122
|
+
this.ws.send(JSON.stringify(frame), (err) => {
|
|
123
|
+
if (err) {
|
|
124
|
+
this._pending.delete(id);
|
|
125
|
+
clearTimeout(timer);
|
|
126
|
+
reject(err);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Authenticate against a token-protected server. No-op when token is null. */
|
|
133
|
+
async auth(token) {
|
|
134
|
+
if (!token) return { success: true, skipped: true };
|
|
135
|
+
const result = await this.request("auth", { token });
|
|
136
|
+
if (!result.success) {
|
|
137
|
+
throw new Error(result.message || "authentication failed");
|
|
138
|
+
}
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
close() {
|
|
143
|
+
this._closed = true;
|
|
144
|
+
try {
|
|
145
|
+
this.ws?.close();
|
|
146
|
+
} catch {
|
|
147
|
+
// already closing
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
package/src/repl/agent-repl.js
CHANGED
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
recordDenial,
|
|
35
35
|
formatDenials,
|
|
36
36
|
} from "../lib/repl-denials.js";
|
|
37
|
+
import { appendRecentDenials } from "../lib/permission-denial-store.js";
|
|
37
38
|
import { bootstrap, shutdown } from "../runtime/bootstrap.js";
|
|
38
39
|
import {
|
|
39
40
|
createSession,
|
|
@@ -112,6 +113,7 @@ import {
|
|
|
112
113
|
} from "./permission-prompt.js";
|
|
113
114
|
import {
|
|
114
115
|
parsePermissionTier,
|
|
116
|
+
parsePermissionModeArg,
|
|
115
117
|
describeTier,
|
|
116
118
|
nextTier,
|
|
117
119
|
} from "./permission-tier.js";
|
|
@@ -154,9 +156,23 @@ let _classifyAllShell = false;
|
|
|
154
156
|
let _sandbox = null;
|
|
155
157
|
// Bounded log of tool calls the agent was BLOCKED from running this session
|
|
156
158
|
// (shell-policy / ApprovalGate / settings rule / hook). Surfaced by
|
|
157
|
-
// `/permissions denials`
|
|
159
|
+
// `/permissions denials` and mirrored to `cc permissions recent`.
|
|
158
160
|
const _recentDenials = [];
|
|
159
161
|
|
|
162
|
+
function persistRecentDenial(record, options = {}) {
|
|
163
|
+
try {
|
|
164
|
+
appendRecentDenials(record, {
|
|
165
|
+
sessionId: options.sessionId,
|
|
166
|
+
permissionMode: options.permissionMode,
|
|
167
|
+
cwd: options.cwd || process.cwd(),
|
|
168
|
+
source: "repl",
|
|
169
|
+
});
|
|
170
|
+
return true;
|
|
171
|
+
} catch {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
160
176
|
/**
|
|
161
177
|
* Fire settings.json Notification hooks (observe-only) — the agent needs the
|
|
162
178
|
* user's attention (e.g. waiting on a permission/risk confirmation). A hook can
|
|
@@ -318,7 +334,15 @@ export async function agentLoop(messages, options) {
|
|
|
318
334
|
: "",
|
|
319
335
|
});
|
|
320
336
|
if (denial) {
|
|
321
|
-
|
|
337
|
+
const record = { ...denial, at: Date.now() };
|
|
338
|
+
recordDenial(options.denialLog, record);
|
|
339
|
+
if (options.persistRecentDenials === true) {
|
|
340
|
+
persistRecentDenial(record, {
|
|
341
|
+
sessionId: options.sessionId,
|
|
342
|
+
permissionMode: options.permissionMode,
|
|
343
|
+
cwd: options.cwd,
|
|
344
|
+
});
|
|
345
|
+
}
|
|
322
346
|
}
|
|
323
347
|
}
|
|
324
348
|
// Parity with Desktop AIChatPage's `Switch to Trusted` button:
|
|
@@ -409,10 +433,21 @@ export async function startAgentRepl(options = {}) {
|
|
|
409
433
|
// the injection after the turn (or as a backstop at the next submit on throw).
|
|
410
434
|
let pendingBtw = [];
|
|
411
435
|
let _btwRestore = null;
|
|
412
|
-
// Current
|
|
413
|
-
//
|
|
414
|
-
// with _approvalGate.setSessionPolicy below.
|
|
436
|
+
// Current permission mode (strict|trusted|autopilot|auto), mirrored here so
|
|
437
|
+
// Shift+Tab can cycle it and `/permissions <tier>` can set it. Kept in sync
|
|
438
|
+
// with _approvalGate.setSessionPolicy below. "auto" rides the trusted gate
|
|
439
|
+
// tier but additionally activates the autoMode.decisions classifier wrapper
|
|
440
|
+
// (when settings customize it) — gateTierFor() maps mode → real gate tier.
|
|
415
441
|
let _sessionTier = "strict";
|
|
442
|
+
const gateTierFor = (mode) =>
|
|
443
|
+
mode === "auto" ? "trusted" : mode === "dontAsk" ? "strict" : mode;
|
|
444
|
+
// dontAsk (headless parity, interactive form): anything that would prompt
|
|
445
|
+
// is denied instead — both confirmers below consult this before prompting.
|
|
446
|
+
const _dontAskActive = () => _sessionTier === "dontAsk";
|
|
447
|
+
// Resolved autoMode.decisions map (loaded once at startup); null until the
|
|
448
|
+
// gate is wired. The wrapper is installed only when settings customize the
|
|
449
|
+
// map, and only bites while _sessionTier === "auto" (isActive predicate).
|
|
450
|
+
let _autoModeResolved = null;
|
|
416
451
|
const baseUrl = options.baseUrl || "http://localhost:11434";
|
|
417
452
|
const apiKey = options.apiKey || null;
|
|
418
453
|
// Configured vision model (config.llm.visionModel) — used when a turn carries
|
|
@@ -558,8 +593,39 @@ export async function startAgentRepl(options = {}) {
|
|
|
558
593
|
const { getApprovalGate } =
|
|
559
594
|
await import("../lib/session-core-singletons.js");
|
|
560
595
|
_approvalGate = await getApprovalGate();
|
|
596
|
+
// autoMode.decisions: wrap the gate with the configurable classifier
|
|
597
|
+
// BEFORE setConfirmer so the wrapper captures the interactive confirmer.
|
|
598
|
+
// Inactive (delegating) unless the session mode is "auto"; not installed
|
|
599
|
+
// at all when settings don't customize the map (byte-identical path).
|
|
600
|
+
try {
|
|
601
|
+
const {
|
|
602
|
+
loadAutoModeConfig,
|
|
603
|
+
resolveAutoModeDecisions,
|
|
604
|
+
createAutoModeApprovalGate,
|
|
605
|
+
} = await import("../lib/auto-mode-config.js");
|
|
606
|
+
_autoModeResolved = resolveAutoModeDecisions(
|
|
607
|
+
loadAutoModeConfig({ cwd: process.cwd() }).effective,
|
|
608
|
+
);
|
|
609
|
+
if (_autoModeResolved.customized) {
|
|
610
|
+
_approvalGate = createAutoModeApprovalGate(
|
|
611
|
+
_approvalGate,
|
|
612
|
+
_autoModeResolved,
|
|
613
|
+
{ isActive: () => _sessionTier === "auto" },
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
} catch (_err) {
|
|
617
|
+
_autoModeResolved = null; // fail to the plain gate tiers
|
|
618
|
+
}
|
|
561
619
|
if (typeof _approvalGate.setConfirmer === "function") {
|
|
562
620
|
_approvalGate.setConfirmer(async ({ tool, args, riskLevel }) => {
|
|
621
|
+
if (_dontAskActive()) {
|
|
622
|
+
process.stdout.write(
|
|
623
|
+
chalk.yellow(
|
|
624
|
+
`\n ✕ denied without asking (dontAsk mode): ${riskLevel || "medium"}-risk ${tool || "run_shell"}${args?.command ? " — " + args.command : ""}\n`,
|
|
625
|
+
),
|
|
626
|
+
);
|
|
627
|
+
return false;
|
|
628
|
+
}
|
|
563
629
|
await _fireNotification(
|
|
564
630
|
`Permission needed: ${riskLevel || "medium"}-risk ${tool || "run_shell"}${args?.command ? " — " + args.command : ""}`,
|
|
565
631
|
);
|
|
@@ -630,6 +696,14 @@ export async function startAgentRepl(options = {}) {
|
|
|
630
696
|
// Confirmer is shared by permission `ask` rules AND hook `ask` decisions,
|
|
631
697
|
// so define it unconditionally (a `hook:` rule label flows through too).
|
|
632
698
|
_permissionConfirm = async ({ tool, args, rule, reason }) => {
|
|
699
|
+
if (_dontAskActive()) {
|
|
700
|
+
process.stdout.write(
|
|
701
|
+
chalk.yellow(
|
|
702
|
+
`\n ✕ denied without asking (dontAsk mode): ${tool}${rule ? ` (${rule})` : reason ? ` — ${reason}` : ""}\n`,
|
|
703
|
+
),
|
|
704
|
+
);
|
|
705
|
+
return false;
|
|
706
|
+
}
|
|
633
707
|
await _fireNotification(
|
|
634
708
|
`Permission needed: ${tool}${rule ? " (" + rule + ")" : ""}`,
|
|
635
709
|
);
|
|
@@ -1021,6 +1095,27 @@ export async function startAgentRepl(options = {}) {
|
|
|
1021
1095
|
`(mcp__<server>__<tool>)`,
|
|
1022
1096
|
),
|
|
1023
1097
|
);
|
|
1098
|
+
// MCP tool search (context scaling): defer big tool schemas behind the
|
|
1099
|
+
// internal tool_search tool. Below-threshold / disabled → no-op.
|
|
1100
|
+
try {
|
|
1101
|
+
const { maybeApplyToolSearch } =
|
|
1102
|
+
await import("../runtime/mcp-tool-search.js");
|
|
1103
|
+
const ts = maybeApplyToolSearch(_adhocMcp, {
|
|
1104
|
+
model,
|
|
1105
|
+
provider,
|
|
1106
|
+
cwd: process.cwd(),
|
|
1107
|
+
});
|
|
1108
|
+
if (ts) {
|
|
1109
|
+
logger.log(
|
|
1110
|
+
chalk.gray(
|
|
1111
|
+
`MCP: tool search active — ${ts.deferredCount} schema(s) ` +
|
|
1112
|
+
`deferred (~${ts.savedTokens} tok saved)`,
|
|
1113
|
+
),
|
|
1114
|
+
);
|
|
1115
|
+
}
|
|
1116
|
+
} catch (_err) {
|
|
1117
|
+
// best-effort — full schemas still work without deferral
|
|
1118
|
+
}
|
|
1024
1119
|
}
|
|
1025
1120
|
} catch (mcpErr) {
|
|
1026
1121
|
logger.log(chalk.yellow(`MCP: --mcp-config failed — ${mcpErr.message}`));
|
|
@@ -1045,6 +1140,27 @@ export async function startAgentRepl(options = {}) {
|
|
|
1045
1140
|
}
|
|
1046
1141
|
}
|
|
1047
1142
|
|
|
1143
|
+
// --permission-mode for the interactive session (headless parity): manual →
|
|
1144
|
+
// strict, acceptEdits → trusted, bypassPermissions → autopilot, and auto →
|
|
1145
|
+
// trusted tier + the autoMode.decisions classifier. An explicit flag wins
|
|
1146
|
+
// over the bundle's approvalPolicy default. Unknown values are ignored here
|
|
1147
|
+
// (headless validates them; interactively we just keep the default tier).
|
|
1148
|
+
if (options.permissionMode && _approvalGate && sessionId) {
|
|
1149
|
+
const parsed = parsePermissionModeArg(options.permissionMode);
|
|
1150
|
+
if (parsed && typeof _approvalGate.setSessionPolicy === "function") {
|
|
1151
|
+
try {
|
|
1152
|
+
_approvalGate.setSessionPolicy(sessionId, parsed.tier);
|
|
1153
|
+
_sessionTier = parsed.auto
|
|
1154
|
+
? "auto"
|
|
1155
|
+
: parsed.dontAsk
|
|
1156
|
+
? "dontAsk"
|
|
1157
|
+
: parsed.tier;
|
|
1158
|
+
} catch (_err) {
|
|
1159
|
+
// Non-critical — keep the default tier
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1048
1164
|
// Phase G #5 — inject top-K memory recall into system prompt for new sessions
|
|
1049
1165
|
// Skip on resume (existing context already reflects prior work) and when
|
|
1050
1166
|
// --no-recall-memory is passed.
|
|
@@ -1394,7 +1510,9 @@ export async function startAgentRepl(options = {}) {
|
|
|
1394
1510
|
sessionId &&
|
|
1395
1511
|
typeof _approvalGate.setSessionPolicy === "function"
|
|
1396
1512
|
) {
|
|
1397
|
-
|
|
1513
|
+
// "auto" rides the trusted tier, so it cycles as trusted → autopilot
|
|
1514
|
+
// (cycling is also how you exit auto mode without /permissions).
|
|
1515
|
+
const next = nextTier(gateTierFor(_sessionTier));
|
|
1398
1516
|
try {
|
|
1399
1517
|
_approvalGate.setSessionPolicy(sessionId, next);
|
|
1400
1518
|
_sessionTier = next;
|
|
@@ -1741,7 +1859,7 @@ export async function startAgentRepl(options = {}) {
|
|
|
1741
1859
|
` ${chalk.cyan("/init")} Inventory this folder into a cc.md project-memory file (/init [--force])`,
|
|
1742
1860
|
);
|
|
1743
1861
|
logger.log(
|
|
1744
|
-
` ${chalk.cyan("/context")} Live context-window usage by role`,
|
|
1862
|
+
` ${chalk.cyan("/context")} Live context-window usage by role + MCP tool schemas`,
|
|
1745
1863
|
);
|
|
1746
1864
|
logger.log(
|
|
1747
1865
|
` ${chalk.cyan("/copy")} Copy last response to clipboard (/copy code → last code block)`,
|
|
@@ -2580,6 +2698,48 @@ export async function startAgentRepl(options = {}) {
|
|
|
2580
2698
|
: ""
|
|
2581
2699
|
}`,
|
|
2582
2700
|
);
|
|
2701
|
+
// MCP tool-schema share of the window (gap-analysis 第二阶段 item 3):
|
|
2702
|
+
// the tools parameter rides along on EVERY request but isn't part of
|
|
2703
|
+
// `messages`, so it's invisible to the role buckets above. Show the
|
|
2704
|
+
// per-server schema cost + tool-search state + optimization advice.
|
|
2705
|
+
if (_adhocMcp?.extraToolDefinitions?.length) {
|
|
2706
|
+
const { describeMcpToolContext } =
|
|
2707
|
+
await import("../runtime/mcp-tool-search.js");
|
|
2708
|
+
const info = describeMcpToolContext(_adhocMcp, {
|
|
2709
|
+
estimate: estimateTokens,
|
|
2710
|
+
model,
|
|
2711
|
+
provider,
|
|
2712
|
+
});
|
|
2713
|
+
if (info) {
|
|
2714
|
+
const ts = info.toolSearch;
|
|
2715
|
+
logger.log(chalk.bold("\nMCP tool schemas (sent every request):"));
|
|
2716
|
+
for (const row of info.servers) {
|
|
2717
|
+
const share = window
|
|
2718
|
+
? Math.round((row.sentTokens / window) * 100)
|
|
2719
|
+
: 0;
|
|
2720
|
+
logger.log(
|
|
2721
|
+
` ${row.server.padEnd(16)}${String(row.sentTokens).padStart(7)} tok ${String(share).padStart(3)}%` +
|
|
2722
|
+
chalk.gray(
|
|
2723
|
+
` (${row.tools} tools${
|
|
2724
|
+
row.deferred ? `, ${row.deferred} deferred` : ""
|
|
2725
|
+
})`,
|
|
2726
|
+
),
|
|
2727
|
+
);
|
|
2728
|
+
}
|
|
2729
|
+
logger.log(
|
|
2730
|
+
` ${"total".padEnd(16)}${String(info.sentTokens).padStart(7)} tok` +
|
|
2731
|
+
(ts.active
|
|
2732
|
+
? chalk.gray(
|
|
2733
|
+
` (tool search: ${ts.deferredCount} deferred, ` +
|
|
2734
|
+
`${ts.loadedCount} loaded, ~${info.savedTokens} tok saved)`,
|
|
2735
|
+
)
|
|
2736
|
+
: ""),
|
|
2737
|
+
);
|
|
2738
|
+
for (const line of info.advice) {
|
|
2739
|
+
logger.log(chalk.yellow(` ⚠ ${line}`));
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
}
|
|
2583
2743
|
} catch (err) {
|
|
2584
2744
|
logger.error(`/context failed: ${err.message}`);
|
|
2585
2745
|
}
|
|
@@ -3740,17 +3900,22 @@ export async function startAgentRepl(options = {}) {
|
|
|
3740
3900
|
// Review what the agent was BLOCKED from running this session
|
|
3741
3901
|
// (Claude-Code 2.1.193 "recent denials").
|
|
3742
3902
|
logger.log(formatDenials(_recentDenials));
|
|
3903
|
+
logger.log(
|
|
3904
|
+
chalk.gray(
|
|
3905
|
+
" Cross-session history (headless + REPL): cc permissions recent",
|
|
3906
|
+
),
|
|
3907
|
+
);
|
|
3743
3908
|
prompt();
|
|
3744
3909
|
return;
|
|
3745
3910
|
}
|
|
3746
3911
|
if (arg) {
|
|
3747
|
-
// Set this session's
|
|
3912
|
+
// Set this session's permission mode mid-session (Claude-Code
|
|
3748
3913
|
// permission-mode / Shift+Tab parity; mirrors `cc session policy --set`).
|
|
3749
|
-
const
|
|
3750
|
-
if (!
|
|
3914
|
+
const parsed = parsePermissionModeArg(arg);
|
|
3915
|
+
if (!parsed) {
|
|
3751
3916
|
logger.info(
|
|
3752
|
-
"Usage: /permissions [strict|trusted|autopilot] " +
|
|
3753
|
-
"(aliases: default · accept-edits · bypass). No arg = show rules.",
|
|
3917
|
+
"Usage: /permissions [strict|trusted|autopilot|auto|dontask] " +
|
|
3918
|
+
"(aliases: default · manual · accept-edits · bypass). No arg = show rules.",
|
|
3754
3919
|
);
|
|
3755
3920
|
} else if (
|
|
3756
3921
|
!_approvalGate ||
|
|
@@ -3762,11 +3927,31 @@ export async function startAgentRepl(options = {}) {
|
|
|
3762
3927
|
);
|
|
3763
3928
|
} else {
|
|
3764
3929
|
try {
|
|
3765
|
-
_approvalGate.setSessionPolicy(sessionId, tier);
|
|
3766
|
-
_sessionTier =
|
|
3930
|
+
_approvalGate.setSessionPolicy(sessionId, parsed.tier);
|
|
3931
|
+
_sessionTier = parsed.auto
|
|
3932
|
+
? "auto"
|
|
3933
|
+
: parsed.dontAsk
|
|
3934
|
+
? "dontAsk"
|
|
3935
|
+
: parsed.tier;
|
|
3767
3936
|
logger.info(
|
|
3768
|
-
`Approval policy → ${chalk.cyan(
|
|
3937
|
+
`Approval policy → ${chalk.cyan(_sessionTier)} ${chalk.gray(`(${describeTier(_sessionTier)})`)}`,
|
|
3769
3938
|
);
|
|
3939
|
+
if (parsed.auto) {
|
|
3940
|
+
if (_autoModeResolved?.customized) {
|
|
3941
|
+
const m = _autoModeResolved.map;
|
|
3942
|
+
logger.info(
|
|
3943
|
+
chalk.gray(
|
|
3944
|
+
` autoMode.decisions: low→${m.low.decision} · medium→${m.medium.decision} · high→${m.high.decision} (cc auto-mode config for sources)`,
|
|
3945
|
+
),
|
|
3946
|
+
);
|
|
3947
|
+
} else {
|
|
3948
|
+
logger.info(
|
|
3949
|
+
chalk.gray(
|
|
3950
|
+
" no customized autoMode.decisions in settings — auto behaves like trusted",
|
|
3951
|
+
),
|
|
3952
|
+
);
|
|
3953
|
+
}
|
|
3954
|
+
}
|
|
3770
3955
|
} catch (_err) {
|
|
3771
3956
|
logger.info("Could not set the approval policy.");
|
|
3772
3957
|
}
|
|
@@ -3785,7 +3970,12 @@ export async function startAgentRepl(options = {}) {
|
|
|
3785
3970
|
logger.log(renderPermissions(_permissionRules, { files }));
|
|
3786
3971
|
logger.log(
|
|
3787
3972
|
chalk.gray(
|
|
3788
|
-
|
|
3973
|
+
` Current mode: ${_sessionTier} (${describeTier(_sessionTier)})`,
|
|
3974
|
+
),
|
|
3975
|
+
);
|
|
3976
|
+
logger.log(
|
|
3977
|
+
chalk.gray(
|
|
3978
|
+
" Set tier mid-session: /permissions <strict|trusted|autopilot|auto|dontask>",
|
|
3789
3979
|
),
|
|
3790
3980
|
);
|
|
3791
3981
|
if (_recentDenials.length) {
|
|
@@ -4371,6 +4561,8 @@ export async function startAgentRepl(options = {}) {
|
|
|
4371
4561
|
checkpointSession: sessionId,
|
|
4372
4562
|
checkpointMarks: _checkpointMarks,
|
|
4373
4563
|
denialLog: _recentDenials,
|
|
4564
|
+
persistRecentDenials: true,
|
|
4565
|
+
permissionMode: _sessionTier,
|
|
4374
4566
|
prepareCall,
|
|
4375
4567
|
approvalGate: _approvalGate,
|
|
4376
4568
|
permissionRules: _permissionRules,
|
|
@@ -10,6 +10,7 @@ const TIER_ALIASES = {
|
|
|
10
10
|
// strict — every risky action asks (the default)
|
|
11
11
|
strict: "strict",
|
|
12
12
|
default: "strict",
|
|
13
|
+
manual: "strict",
|
|
13
14
|
normal: "strict",
|
|
14
15
|
off: "strict",
|
|
15
16
|
// trusted — low/medium-risk auto-approved, high-risk still asks (acceptEdits)
|
|
@@ -45,6 +46,32 @@ export function parsePermissionTier(arg) {
|
|
|
45
46
|
: null;
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
const AUTO_ALIASES = new Set(["auto", "auto-mode", "automode"]);
|
|
50
|
+
const DONT_ASK_ALIASES = new Set(["dontask", "dont-ask", "noask", "no-ask"]);
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Parse a permission-mode argument that may also be `auto` (the configurable
|
|
54
|
+
* autoMode.decisions classifier) or `dontAsk` (anything that would prompt is
|
|
55
|
+
* denied instead — headless semantics brought to the interactive session).
|
|
56
|
+
* Auto rides the trusted gate tier; dontAsk rides strict; the REPL activates
|
|
57
|
+
* the matching behavior while the mode is engaged.
|
|
58
|
+
*
|
|
59
|
+
* @param {string} arg
|
|
60
|
+
* @returns {{ tier: "strict"|"trusted"|"autopilot", auto: boolean, dontAsk: boolean } | null}
|
|
61
|
+
*/
|
|
62
|
+
export function parsePermissionModeArg(arg) {
|
|
63
|
+
const a = String(arg == null ? "" : arg)
|
|
64
|
+
.trim()
|
|
65
|
+
.toLowerCase();
|
|
66
|
+
if (AUTO_ALIASES.has(a))
|
|
67
|
+
return { tier: "trusted", auto: true, dontAsk: false };
|
|
68
|
+
if (DONT_ASK_ALIASES.has(a)) {
|
|
69
|
+
return { tier: "strict", auto: false, dontAsk: true };
|
|
70
|
+
}
|
|
71
|
+
const tier = parsePermissionTier(a);
|
|
72
|
+
return tier ? { tier, auto: false, dontAsk: false } : null;
|
|
73
|
+
}
|
|
74
|
+
|
|
48
75
|
/** One-line description of what a tier auto-approves. */
|
|
49
76
|
export function describeTier(tier) {
|
|
50
77
|
switch (tier) {
|
|
@@ -54,6 +81,10 @@ export function describeTier(tier) {
|
|
|
54
81
|
return "low/medium-risk auto-approved; high-risk still asks";
|
|
55
82
|
case "strict":
|
|
56
83
|
return "every risky action asks";
|
|
84
|
+
case "auto":
|
|
85
|
+
return "autoMode.decisions classifier (riskLevel → allow/ask/deny); unconfigured = trusted";
|
|
86
|
+
case "dontAsk":
|
|
87
|
+
return "anything that would ask is denied instead (no prompts)";
|
|
57
88
|
default:
|
|
58
89
|
return "";
|
|
59
90
|
}
|
|
@@ -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
|
);
|
|
@@ -3347,6 +3386,15 @@ async function executeToolInner(
|
|
|
3347
3386
|
}
|
|
3348
3387
|
}
|
|
3349
3388
|
|
|
3389
|
+
// Internal tool-search tool (MCP context scaling): resolves deferred
|
|
3390
|
+
// MCP tool schemas from the registry attached at setup time. Read-only,
|
|
3391
|
+
// local — same risk class as list_skills, so no approval gate.
|
|
3392
|
+
if (localToolExecutor?.kind === "tool-search") {
|
|
3393
|
+
return attachDescriptor(
|
|
3394
|
+
executeToolSearch(localToolExecutor.registry, args || {}),
|
|
3395
|
+
);
|
|
3396
|
+
}
|
|
3397
|
+
|
|
3350
3398
|
if (localToolExecutor?.kind === "mcp") {
|
|
3351
3399
|
if (!mcpClient || typeof mcpClient.callTool !== "function") {
|
|
3352
3400
|
return attachDescriptor({
|
|
@@ -3354,6 +3402,15 @@ async function executeToolInner(
|
|
|
3354
3402
|
});
|
|
3355
3403
|
}
|
|
3356
3404
|
|
|
3405
|
+
// Deferred-schema gate: a direct call to a tool whose schema was never
|
|
3406
|
+
// loaded returns a self-healing error embedding the schema (and marks
|
|
3407
|
+
// it loaded), instead of forwarding likely-malformed arguments to the
|
|
3408
|
+
// server. No-op unless tool search deferred this tool.
|
|
3409
|
+
const deferredGate = gateDeferredMcpCall(name, localToolExecutor);
|
|
3410
|
+
if (deferredGate) {
|
|
3411
|
+
return attachDescriptor(deferredGate);
|
|
3412
|
+
}
|
|
3413
|
+
|
|
3357
3414
|
try {
|
|
3358
3415
|
const result = await mcpClient.callTool(
|
|
3359
3416
|
localToolExecutor.serverName,
|