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
|
@@ -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
|
|
19
|
-
*
|
|
20
|
-
* -
|
|
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
|
-
*
|
|
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 {
|
|
92
|
+
* @param {string} [mode]
|
|
93
|
+
* @returns {string}
|
|
89
94
|
*/
|
|
90
|
-
export function
|
|
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) {
|
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
resolvePermissionPromptTool,
|
|
43
43
|
makePermissionPromptConfirmer,
|
|
44
44
|
} from "./mcp-config.js";
|
|
45
|
+
import { maybeApplyToolSearch } from "./mcp-tool-search.js";
|
|
45
46
|
import { IterationBudget } from "../lib/iteration-budget.js";
|
|
46
47
|
import { CostBudget } from "../lib/cost-budget.js";
|
|
47
48
|
import {
|
|
@@ -723,10 +724,34 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
|
|
|
723
724
|
let approvalGate = null;
|
|
724
725
|
try {
|
|
725
726
|
approvalGate = await getApprovalGate();
|
|
727
|
+
if (approvalGate && (options.permissionMode || "default") === "auto") {
|
|
728
|
+
// autoMode.decisions: user-configured riskLevel → allow/ask/deny
|
|
729
|
+
// classifier (same wiring as headless-runner). Only wrap when settings
|
|
730
|
+
// customize the map so the unconfigured path stays byte-identical.
|
|
731
|
+
try {
|
|
732
|
+
const {
|
|
733
|
+
loadAutoModeConfig,
|
|
734
|
+
resolveAutoModeDecisions,
|
|
735
|
+
createAutoModeApprovalGate,
|
|
736
|
+
} = await import("../lib/auto-mode-config.js");
|
|
737
|
+
const autoConfig = loadAutoModeConfig({
|
|
738
|
+
cwd,
|
|
739
|
+
settingsFile: options.settingsFile,
|
|
740
|
+
});
|
|
741
|
+
const resolved = resolveAutoModeDecisions(autoConfig.effective);
|
|
742
|
+
if (resolved.customized) {
|
|
743
|
+
approvalGate = createAutoModeApprovalGate(approvalGate, resolved);
|
|
744
|
+
}
|
|
745
|
+
} catch {
|
|
746
|
+
// fail to the default trusted mapping — never block the run
|
|
747
|
+
}
|
|
748
|
+
}
|
|
726
749
|
if (approvalGate) {
|
|
727
750
|
approvalGate.setSessionPolicy?.(sessionId, perm.sessionPolicy);
|
|
728
751
|
approvalGate.setConfirmer?.(
|
|
729
|
-
interactive
|
|
752
|
+
interactive && perm.allowInteractiveApprovals
|
|
753
|
+
? interactiveConfirm
|
|
754
|
+
: perm.confirmer,
|
|
730
755
|
);
|
|
731
756
|
}
|
|
732
757
|
} catch {
|
|
@@ -949,6 +974,20 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
|
|
|
949
974
|
loadJetbrainsMcp: deps.loadJetbrainsMcp,
|
|
950
975
|
},
|
|
951
976
|
);
|
|
977
|
+
// MCP tool search (context scaling): defer big tool schemas behind the
|
|
978
|
+
// internal tool_search tool. Below-threshold / disabled → no-op.
|
|
979
|
+
if (mcp) {
|
|
980
|
+
try {
|
|
981
|
+
(deps.maybeApplyToolSearch || maybeApplyToolSearch)(mcp, {
|
|
982
|
+
model,
|
|
983
|
+
provider,
|
|
984
|
+
cwd: options.cwd || process.cwd(),
|
|
985
|
+
writeErr,
|
|
986
|
+
});
|
|
987
|
+
} catch {
|
|
988
|
+
// best-effort — full schemas still work without deferral
|
|
989
|
+
}
|
|
990
|
+
}
|
|
952
991
|
} catch (err) {
|
|
953
992
|
emit({
|
|
954
993
|
type: "result",
|
|
@@ -987,6 +1026,43 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
|
|
|
987
1026
|
}
|
|
988
1027
|
}
|
|
989
1028
|
|
|
1029
|
+
// --remote-control: route CONFIRM-tier approvals to paired mobile/web
|
|
1030
|
+
// devices (第四阶段 #2). Pairing info is emitted as a stream event so panel
|
|
1031
|
+
// hosts can render the QR. --permission-prompt-tool and --interactive-
|
|
1032
|
+
// approvals win when given (this block skips) — explicit routing beats the
|
|
1033
|
+
// ambient device bridge.
|
|
1034
|
+
let _remoteApproval = null;
|
|
1035
|
+
if (
|
|
1036
|
+
options.remoteControl &&
|
|
1037
|
+
!options.permissionPromptTool &&
|
|
1038
|
+
options.interactiveApprovals !== true
|
|
1039
|
+
) {
|
|
1040
|
+
try {
|
|
1041
|
+
const { startHeadlessRemoteApproval } =
|
|
1042
|
+
await import("../lib/remote-approval-bridge.js");
|
|
1043
|
+
_remoteApproval = await startHeadlessRemoteApproval({
|
|
1044
|
+
agentSessionId: sessionId,
|
|
1045
|
+
});
|
|
1046
|
+
emit({
|
|
1047
|
+
type: "remote_control",
|
|
1048
|
+
subtype: "pairing",
|
|
1049
|
+
pairing_uri: _remoteApproval.pairing.uri,
|
|
1050
|
+
remote_session_id: _remoteApproval.pairing.remoteSessionId,
|
|
1051
|
+
expires_at: _remoteApproval.pairing.expiresAt,
|
|
1052
|
+
});
|
|
1053
|
+
if (approvalGate && typeof approvalGate.setConfirmer === "function") {
|
|
1054
|
+
approvalGate.setConfirmer(_remoteApproval.confirmer);
|
|
1055
|
+
}
|
|
1056
|
+
} catch (err) {
|
|
1057
|
+
emit({
|
|
1058
|
+
type: "remote_control",
|
|
1059
|
+
subtype: "unavailable",
|
|
1060
|
+
error: err.message,
|
|
1061
|
+
});
|
|
1062
|
+
_remoteApproval = null;
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
|
|
990
1066
|
const loopOptionsBase = {
|
|
991
1067
|
model,
|
|
992
1068
|
provider,
|
|
@@ -1723,6 +1799,16 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
|
|
|
1723
1799
|
}
|
|
1724
1800
|
}
|
|
1725
1801
|
|
|
1802
|
+
// Tear down the --remote-control approval bridge + its self-hosted WS
|
|
1803
|
+
// server so the stream session's port never outlives the invocation.
|
|
1804
|
+
if (_remoteApproval) {
|
|
1805
|
+
try {
|
|
1806
|
+
await _remoteApproval.close();
|
|
1807
|
+
} catch {
|
|
1808
|
+
// best-effort
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1726
1812
|
// settings.json SessionEnd hooks (observe-only) when stdin closes.
|
|
1727
1813
|
if (settingsHooks) {
|
|
1728
1814
|
try {
|
|
@@ -169,6 +169,14 @@ export async function setupMcpFromConfig(servers, deps = {}) {
|
|
|
169
169
|
// (created before this field existed) is passed in.
|
|
170
170
|
if (!Array.isArray(result.resources)) result.resources = [];
|
|
171
171
|
if (!Array.isArray(result.prompts)) result.prompts = [];
|
|
172
|
+
// Per-server usage instructions from the MCP initialize response (surfaced
|
|
173
|
+
// by tool search; absent for servers that send none).
|
|
174
|
+
if (
|
|
175
|
+
!result.instructionsByServer ||
|
|
176
|
+
typeof result.instructionsByServer !== "object"
|
|
177
|
+
) {
|
|
178
|
+
result.instructionsByServer = {};
|
|
179
|
+
}
|
|
172
180
|
const {
|
|
173
181
|
mcpClient,
|
|
174
182
|
extraToolDefinitions,
|
|
@@ -261,6 +269,9 @@ export async function setupMcpFromConfig(servers, deps = {}) {
|
|
|
261
269
|
resources: resources.length,
|
|
262
270
|
prompts: prompts.length,
|
|
263
271
|
});
|
|
272
|
+
if (typeof res?.instructions === "string" && res.instructions.trim()) {
|
|
273
|
+
result.instructionsByServer[name] = res.instructions.trim();
|
|
274
|
+
}
|
|
264
275
|
for (const r of resources) {
|
|
265
276
|
if (!r || !r.uri) continue;
|
|
266
277
|
result.resources.push({ ...r, server: name });
|
|
Binary file
|
|
@@ -44,6 +44,17 @@ export function resolveAgentPolicy({
|
|
|
44
44
|
// IDEA built-in MCP tri-state (undefined=auto when the JetBrains plugin
|
|
45
45
|
// injected the endpoint / true=--jetbrains / false=--no-jetbrains).
|
|
46
46
|
jetbrains: overrides.jetbrains,
|
|
47
|
+
// Consumed by the interactive REPL (startAgentRepl) — plain passthrough
|
|
48
|
+
// (undefined = unset). These were silently dropped by this allowlist
|
|
49
|
+
// before 2026-07-09, breaking --vim/--think/--thinking-budget/
|
|
50
|
+
// --fallback-model/--pdh/--permission-mode for interactive sessions.
|
|
51
|
+
permissionMode: overrides.permissionMode,
|
|
52
|
+
vimMode: overrides.vimMode,
|
|
53
|
+
thinking: overrides.thinking,
|
|
54
|
+
thinkingBudget: overrides.thinkingBudget,
|
|
55
|
+
fallbackModels: overrides.fallbackModels,
|
|
56
|
+
pdh: overrides.pdh,
|
|
57
|
+
outputStyle: overrides.outputStyle,
|
|
47
58
|
};
|
|
48
59
|
}
|
|
49
60
|
|
|
@@ -58,6 +69,12 @@ export function resolveServerPolicy(overrides = {}) {
|
|
|
58
69
|
project: overrides.project || process.cwd(),
|
|
59
70
|
httpPort: overrides.httpPort || null,
|
|
60
71
|
bundlePath: overrides.bundlePath || null,
|
|
72
|
+
// startServer() reads these for the Remote Session E2EE relay. They were
|
|
73
|
+
// silently dropped by this allowlist before 2026-07-09 — `cc serve
|
|
74
|
+
// --remote-session-relay-url` never actually enabled the relay (same
|
|
75
|
+
// allowlist-drop class as the batch-9 resolveAgentPolicy fix).
|
|
76
|
+
remoteSessionRelayUrl: overrides.remoteSessionRelayUrl || null,
|
|
77
|
+
remoteSessionPeerId: overrides.remoteSessionPeerId || null,
|
|
61
78
|
};
|
|
62
79
|
}
|
|
63
80
|
|
|
@@ -1,60 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* background-agent-worker — detached supervisor process for one background
|
|
3
|
+
* session. Spawns the agent CLI child with output to the session log, keeps a
|
|
4
|
+
* heartbeat in the state file, and (since batch 10) hosts a local session
|
|
5
|
+
* transport so `cc attach <id>` can send follow-up prompts interactively.
|
|
6
|
+
*
|
|
7
|
+
* Turn loop: the initial task runs as turn 1. While a client is attached the
|
|
8
|
+
* worker stays alive between turns (phase "idle"); a received prompt starts
|
|
9
|
+
* the next turn via `job.followUpArgv + ["-p", text]` on the same session id
|
|
10
|
+
* (headless `--session` resumes the conversation). The worker finalizes and
|
|
11
|
+
* exits only when the current turn has ended, the prompt queue is empty, and
|
|
12
|
+
* no client is attached.
|
|
13
|
+
*/
|
|
14
|
+
|
|
1
15
|
import { spawn } from "node:child_process";
|
|
2
16
|
import { readFileSync } from "node:fs";
|
|
17
|
+
import { randomBytes } from "node:crypto";
|
|
3
18
|
import {
|
|
19
|
+
DEFAULT_HEARTBEAT_INTERVAL_MS,
|
|
20
|
+
backgroundAgentsDir,
|
|
4
21
|
openBackgroundLogFile,
|
|
5
22
|
readBackgroundAgentState,
|
|
6
23
|
removeJobFile,
|
|
7
24
|
writeBackgroundAgentState,
|
|
8
25
|
} from "../lib/background-agent-supervisor.js";
|
|
26
|
+
import { startBackgroundSessionServer } from "../lib/background-session-transport.js";
|
|
9
27
|
|
|
10
28
|
const jobFile = process.argv[2];
|
|
11
29
|
let job;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
30
|
+
let log;
|
|
31
|
+
let server = null;
|
|
32
|
+
let child = null;
|
|
33
|
+
let heartbeat = null;
|
|
34
|
+
let finalized = false;
|
|
35
|
+
let phase = "turn";
|
|
36
|
+
let turnCount = 0;
|
|
37
|
+
let lastExit = { code: 0, signal: null };
|
|
38
|
+
let transportState = null;
|
|
39
|
+
const promptQueue = [];
|
|
40
|
+
|
|
41
|
+
function mergeState(patch) {
|
|
42
|
+
const current = readBackgroundAgentState(job.id) || { id: job.id };
|
|
43
|
+
const next = { ...current, id: job.id, ...patch };
|
|
44
|
+
writeBackgroundAgentState(next);
|
|
45
|
+
return next;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function writeHeartbeat() {
|
|
49
|
+
const current = readBackgroundAgentState(job.id) || { id: job.id };
|
|
50
|
+
if (current.status && current.status !== "running") return false;
|
|
51
|
+
writeBackgroundAgentState({
|
|
52
|
+
...current,
|
|
53
|
+
id: job.id,
|
|
54
|
+
pid: process.pid,
|
|
55
|
+
workerPid: process.pid,
|
|
56
|
+
agentPid: child?.pid ?? current.agentPid ?? null,
|
|
57
|
+
status: "running",
|
|
58
|
+
heartbeatAt: Date.now(),
|
|
59
|
+
// Re-assert the transport endpoint so a launcher write racing the
|
|
60
|
+
// worker's initial merge self-heals within one heartbeat.
|
|
61
|
+
...(transportState ? { transport: transportState } : {}),
|
|
62
|
+
});
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function finalize(code, signal, errorMessage) {
|
|
67
|
+
if (finalized) return;
|
|
68
|
+
finalized = true;
|
|
69
|
+
clearInterval(heartbeat);
|
|
70
|
+
const current = readBackgroundAgentState(job.id) || { id: job.id };
|
|
71
|
+
// An external `cc daemon stop` already recorded its own terminal status —
|
|
72
|
+
// don't overwrite it; just clear the dead transport endpoint.
|
|
73
|
+
if (!current.status || current.status === "running") {
|
|
74
|
+
writeBackgroundAgentState({
|
|
75
|
+
...current,
|
|
76
|
+
id: job.id,
|
|
77
|
+
status: errorMessage ? "failed" : code === 0 ? "completed" : "failed",
|
|
78
|
+
endedAt: Date.now(),
|
|
79
|
+
exitCode: errorMessage ? (code ?? 1) : code,
|
|
80
|
+
signal: signal || null,
|
|
81
|
+
...(errorMessage ? { error: errorMessage } : {}),
|
|
82
|
+
phase: null,
|
|
83
|
+
transport: null,
|
|
84
|
+
});
|
|
85
|
+
} else {
|
|
86
|
+
writeBackgroundAgentState({ ...current, phase: null, transport: null });
|
|
87
|
+
}
|
|
88
|
+
const done = () => {
|
|
89
|
+
try {
|
|
90
|
+
log?.close();
|
|
91
|
+
} catch {
|
|
92
|
+
/* log fd already closed */
|
|
93
|
+
}
|
|
94
|
+
process.exit(errorMessage ? 1 : (code ?? 1));
|
|
95
|
+
};
|
|
96
|
+
if (server) {
|
|
97
|
+
server.broadcast({ type: "closing" });
|
|
98
|
+
server.close().then(done, done);
|
|
99
|
+
} else {
|
|
100
|
+
done();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function startTurn(argv, promptText) {
|
|
105
|
+
phase = "turn";
|
|
106
|
+
turnCount++;
|
|
107
|
+
child = spawn(process.execPath, [job.cliEntry, ...argv], {
|
|
17
108
|
cwd: job.cwd,
|
|
18
109
|
env: { ...process.env, CC_BACKGROUND_AGENT_ID: job.id },
|
|
19
110
|
stdio: ["ignore", log.fd, log.fd],
|
|
20
111
|
windowsHide: true,
|
|
21
112
|
});
|
|
113
|
+
mergeState({
|
|
114
|
+
status: "running",
|
|
115
|
+
phase,
|
|
116
|
+
turnCount,
|
|
117
|
+
agentPid: child.pid,
|
|
118
|
+
heartbeatAt: Date.now(),
|
|
119
|
+
});
|
|
120
|
+
server?.broadcast({
|
|
121
|
+
type: "turn-started",
|
|
122
|
+
turn: turnCount,
|
|
123
|
+
prompt: promptText || null,
|
|
124
|
+
});
|
|
22
125
|
child.on("exit", (code, signal) => {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
status: code === 0 ? "completed" : "failed",
|
|
28
|
-
endedAt: Date.now(),
|
|
29
|
-
exitCode: code,
|
|
30
|
-
signal: signal || null,
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
log.close();
|
|
34
|
-
process.exit(code ?? 1);
|
|
126
|
+
lastExit = { code, signal };
|
|
127
|
+
child = null;
|
|
128
|
+
server?.broadcast({ type: "turn-ended", turn: turnCount, exitCode: code });
|
|
129
|
+
maybeContinue();
|
|
35
130
|
});
|
|
36
131
|
child.on("error", (error) => {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
...current,
|
|
40
|
-
status: "failed",
|
|
41
|
-
endedAt: Date.now(),
|
|
42
|
-
exitCode: 1,
|
|
43
|
-
error: error.message,
|
|
44
|
-
});
|
|
45
|
-
log.close();
|
|
46
|
-
process.exit(1);
|
|
132
|
+
child = null;
|
|
133
|
+
finalize(1, null, error.message);
|
|
47
134
|
});
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function maybeContinue() {
|
|
138
|
+
if (finalized) return;
|
|
139
|
+
// External stop marked the session terminal while a turn was in flight —
|
|
140
|
+
// don't start another turn against a stopped session.
|
|
141
|
+
const current = readBackgroundAgentState(job.id);
|
|
142
|
+
if (current?.status && current.status !== "running") {
|
|
143
|
+
finalize(lastExit.code, lastExit.signal);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (promptQueue.length && Array.isArray(job.followUpArgv)) {
|
|
147
|
+
const text = promptQueue.shift();
|
|
148
|
+
startTurn([...job.followUpArgv, "-p", text], text);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (server && server.clientCount() > 0) {
|
|
152
|
+
phase = "idle";
|
|
153
|
+
mergeState({ phase, agentPid: null, heartbeatAt: Date.now() });
|
|
154
|
+
server.broadcast({ type: "idle", turn: turnCount });
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
finalize(lastExit.code, lastExit.signal);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function getStatus() {
|
|
161
|
+
return {
|
|
162
|
+
id: job.id,
|
|
163
|
+
sessionId: job.sessionId || null,
|
|
164
|
+
title: job.title || null,
|
|
165
|
+
phase,
|
|
166
|
+
turn: turnCount,
|
|
167
|
+
interactive: Array.isArray(job.followUpArgv),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function main() {
|
|
172
|
+
job = JSON.parse(readFileSync(jobFile, "utf8"));
|
|
173
|
+
removeJobFile(jobFile);
|
|
174
|
+
log = openBackgroundLogFile(job.id);
|
|
175
|
+
|
|
176
|
+
// Session transport (best-effort): interactive attach needs it, but a
|
|
177
|
+
// transport failure must never take down the background task itself.
|
|
178
|
+
try {
|
|
179
|
+
const token = randomBytes(16).toString("hex");
|
|
180
|
+
server = await startBackgroundSessionServer({
|
|
181
|
+
id: job.id,
|
|
182
|
+
dir: backgroundAgentsDir(),
|
|
183
|
+
token,
|
|
184
|
+
getStatus,
|
|
185
|
+
onPrompt: (text) => {
|
|
186
|
+
if (!Array.isArray(job.followUpArgv)) {
|
|
187
|
+
throw new Error(
|
|
188
|
+
"this session was launched without follow-up support — start a new one with cc agent --bg",
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
promptQueue.push(text);
|
|
192
|
+
const queued = promptQueue.length;
|
|
193
|
+
if (!child) maybeContinue();
|
|
194
|
+
return { queued };
|
|
195
|
+
},
|
|
196
|
+
onStop: () => {
|
|
197
|
+
child?.kill("SIGTERM");
|
|
198
|
+
},
|
|
199
|
+
onClientChange: (count) => {
|
|
200
|
+
// Last client detached while the session is idle → nothing left to
|
|
201
|
+
// wait for; finalize with the last turn's exit code.
|
|
202
|
+
if (count === 0 && !child && promptQueue.length === 0) {
|
|
203
|
+
finalize(lastExit.code, lastExit.signal);
|
|
204
|
+
}
|
|
205
|
+
},
|
|
57
206
|
});
|
|
207
|
+
transportState = { pipe: server.pipePath, token };
|
|
208
|
+
mergeState({ transport: transportState });
|
|
209
|
+
} catch {
|
|
210
|
+
server = null;
|
|
58
211
|
}
|
|
59
|
-
|
|
212
|
+
|
|
213
|
+
writeHeartbeat();
|
|
214
|
+
heartbeat = setInterval(() => {
|
|
215
|
+
try {
|
|
216
|
+
if (!writeHeartbeat()) {
|
|
217
|
+
clearInterval(heartbeat);
|
|
218
|
+
// External stop: the child tree is being killed by the stopper; make
|
|
219
|
+
// sure the worker itself never lingers holding the pipe.
|
|
220
|
+
if (!child) finalize(lastExit.code, lastExit.signal);
|
|
221
|
+
}
|
|
222
|
+
} catch {
|
|
223
|
+
/* do not let heartbeat persistence kill the worker */
|
|
224
|
+
}
|
|
225
|
+
}, DEFAULT_HEARTBEAT_INTERVAL_MS);
|
|
226
|
+
heartbeat.unref?.();
|
|
227
|
+
|
|
228
|
+
startTurn(job.argv, null);
|
|
60
229
|
}
|
|
230
|
+
|
|
231
|
+
main().catch((error) => {
|
|
232
|
+
if (job?.id) {
|
|
233
|
+
try {
|
|
234
|
+
finalize(1, null, error.message);
|
|
235
|
+
return;
|
|
236
|
+
} catch {
|
|
237
|
+
/* fall through to hard exit */
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
process.exit(1);
|
|
241
|
+
});
|