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,396 @@
|
|
|
1
|
+
import settingsLoader from "./settings-loader.cjs";
|
|
2
|
+
|
|
3
|
+
const { loadManagedSettings, readSettingsFile, settingsPaths } = settingsLoader;
|
|
4
|
+
|
|
5
|
+
export const AUTO_MODE_SCHEMA = "chainlesschain.auto-mode/v1";
|
|
6
|
+
|
|
7
|
+
export const AUTO_MODE_DEFAULTS = Object.freeze({
|
|
8
|
+
schema: AUTO_MODE_SCHEMA,
|
|
9
|
+
mode: "auto",
|
|
10
|
+
sessionPolicy: "trusted",
|
|
11
|
+
nonInteractiveConfirm: "deny",
|
|
12
|
+
interactiveConfirm: "ask",
|
|
13
|
+
precedence: Object.freeze([
|
|
14
|
+
"managed-settings",
|
|
15
|
+
"permission-rules.deny",
|
|
16
|
+
"permission-rules.ask",
|
|
17
|
+
"permission-rules.allow",
|
|
18
|
+
"shell-policy",
|
|
19
|
+
"approval-gate",
|
|
20
|
+
"hooks",
|
|
21
|
+
]),
|
|
22
|
+
decisions: Object.freeze([
|
|
23
|
+
Object.freeze({
|
|
24
|
+
match: Object.freeze({ riskLevel: "low" }),
|
|
25
|
+
decision: "allow",
|
|
26
|
+
reason: "read-only and low-risk tools do not require approval",
|
|
27
|
+
}),
|
|
28
|
+
Object.freeze({
|
|
29
|
+
match: Object.freeze({ riskLevel: "medium" }),
|
|
30
|
+
decision: "allow",
|
|
31
|
+
reason: "auto maps to the trusted ApprovalGate policy",
|
|
32
|
+
}),
|
|
33
|
+
Object.freeze({
|
|
34
|
+
match: Object.freeze({ riskLevel: "high" }),
|
|
35
|
+
decision: "ask",
|
|
36
|
+
nonInteractiveDecision: "deny",
|
|
37
|
+
reason: "dangerous execution still needs approval",
|
|
38
|
+
}),
|
|
39
|
+
]),
|
|
40
|
+
settings: Object.freeze({
|
|
41
|
+
classifyAllShell: false,
|
|
42
|
+
}),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
function clone(value) {
|
|
46
|
+
return JSON.parse(JSON.stringify(value));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function isPlainObject(value) {
|
|
50
|
+
return value && typeof value === "object" && !Array.isArray(value);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function autoModeFromSettings(data) {
|
|
54
|
+
if (!isPlainObject(data?.autoMode)) return null;
|
|
55
|
+
return clone(data.autoMode);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Layered merge for `autoMode` settings. NOT mergeSandboxSettings: that
|
|
60
|
+
* helper's array branch stringifies elements (`map(String)`) for sandbox
|
|
61
|
+
* allowlists, which destroys array-form `decisions` rule objects into
|
|
62
|
+
* "[object Object]". Here `decisions` arrays are an ORDERED RULE LIST — a
|
|
63
|
+
* closer layer replaces the whole list; everything else keeps the familiar
|
|
64
|
+
* closer-scalar-wins / deep-object-merge semantics.
|
|
65
|
+
*/
|
|
66
|
+
function mergeAutoModeSettings(base, overlay) {
|
|
67
|
+
const out = isPlainObject(base) ? clone(base) : {};
|
|
68
|
+
for (const [key, value] of Object.entries(overlay || {})) {
|
|
69
|
+
if (Array.isArray(value)) {
|
|
70
|
+
out[key] = clone(value);
|
|
71
|
+
} else if (isPlainObject(value)) {
|
|
72
|
+
out[key] = mergeAutoModeSettings(out[key], value);
|
|
73
|
+
} else if (["string", "boolean", "number"].includes(typeof value)) {
|
|
74
|
+
out[key] = value;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function loadAutoModeConfig(opts = {}) {
|
|
81
|
+
const cwd = opts.cwd || process.cwd();
|
|
82
|
+
let effective = clone(AUTO_MODE_DEFAULTS.settings);
|
|
83
|
+
const files = [];
|
|
84
|
+
|
|
85
|
+
for (const file of settingsPaths(cwd, opts.settingsFile)) {
|
|
86
|
+
const data = readSettingsFile(file, { onWarn: opts.onWarn });
|
|
87
|
+
const autoMode = autoModeFromSettings(data);
|
|
88
|
+
if (!autoMode) continue;
|
|
89
|
+
effective = mergeAutoModeSettings(effective, autoMode);
|
|
90
|
+
files.push(file);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const managed = loadManagedSettings(opts);
|
|
94
|
+
let managedFile = null;
|
|
95
|
+
if (managed.settings) {
|
|
96
|
+
const autoMode = autoModeFromSettings(managed.settings);
|
|
97
|
+
if (autoMode) {
|
|
98
|
+
effective = mergeAutoModeSettings(effective, autoMode);
|
|
99
|
+
managedFile = managed.file;
|
|
100
|
+
files.push(managed.file);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
schema: AUTO_MODE_SCHEMA,
|
|
106
|
+
defaults: clone(AUTO_MODE_DEFAULTS.settings),
|
|
107
|
+
effective,
|
|
108
|
+
files,
|
|
109
|
+
managedFile,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function autoModeDefaultsDocument() {
|
|
114
|
+
return clone(AUTO_MODE_DEFAULTS);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const RISK_LEVELS = Object.freeze(["low", "medium", "high"]);
|
|
118
|
+
const DECISION_VALUES = Object.freeze(["allow", "ask", "deny"]);
|
|
119
|
+
|
|
120
|
+
function normalizeDecisionValue(value) {
|
|
121
|
+
const v = typeof value === "string" ? value.toLowerCase() : null;
|
|
122
|
+
if (v === "confirm") return "ask";
|
|
123
|
+
return DECISION_VALUES.includes(v) ? v : null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function defaultDecisionMap() {
|
|
127
|
+
const map = {};
|
|
128
|
+
for (const rule of AUTO_MODE_DEFAULTS.decisions) {
|
|
129
|
+
map[rule.match.riskLevel] = {
|
|
130
|
+
decision: rule.decision,
|
|
131
|
+
reason: rule.reason,
|
|
132
|
+
source: "default",
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
return map;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Compile a shell-style glob (`*` wildcard only) into an anchored RegExp.
|
|
140
|
+
* Case-sensitive on purpose: match patterns are user-vetted capabilities and
|
|
141
|
+
* a looser match could over-authorize.
|
|
142
|
+
*/
|
|
143
|
+
function compileGlob(pattern) {
|
|
144
|
+
const escaped = String(pattern)
|
|
145
|
+
.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
146
|
+
.replace(/\\\*/g, ".*");
|
|
147
|
+
return new RegExp(`^${escaped}$`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Resolve the effective decision rules for `--permission-mode auto`.
|
|
152
|
+
*
|
|
153
|
+
* Accepts `autoMode.decisions` in two shapes:
|
|
154
|
+
* - object: `{ "medium": "ask", "high": { "decision": "deny", "reason": "…" } }`
|
|
155
|
+
* (riskLevel-level overrides only)
|
|
156
|
+
* - array (same shape as the defaults document):
|
|
157
|
+
* `[{ "match": { "riskLevel": "medium" }, "decision": "ask", "reason": "…" }]`
|
|
158
|
+
* Array rules may additionally match on `tool` (exact name) and/or
|
|
159
|
+
* `commandPattern` (a `*` glob against the shell command). Rules with a
|
|
160
|
+
* tool/commandPattern are FINE-GRAINED: they are tried in declaration
|
|
161
|
+
* order before the riskLevel map, first full match wins.
|
|
162
|
+
*
|
|
163
|
+
* Invalid risk levels / decision values / non-string patterns are ignored
|
|
164
|
+
* (fail to defaults) so a typo in settings.json can never loosen NOR silently
|
|
165
|
+
* break the gate.
|
|
166
|
+
*
|
|
167
|
+
* @param {object} [effectiveSettings] merged `autoMode` settings
|
|
168
|
+
* @returns {{
|
|
169
|
+
* map: Record<string,{decision:string,reason:string,source:string}>,
|
|
170
|
+
* rules: Array<{match:object,decision:string,reason:string,_test:(ctx:object)=>boolean}>,
|
|
171
|
+
* customized: boolean,
|
|
172
|
+
* }}
|
|
173
|
+
*/
|
|
174
|
+
export function resolveAutoModeDecisions(effectiveSettings = {}) {
|
|
175
|
+
const map = defaultDecisionMap();
|
|
176
|
+
const rules = [];
|
|
177
|
+
let customized = false;
|
|
178
|
+
|
|
179
|
+
const raw = effectiveSettings?.decisions;
|
|
180
|
+
const entries = [];
|
|
181
|
+
if (Array.isArray(raw)) {
|
|
182
|
+
for (const rule of raw) {
|
|
183
|
+
if (!isPlainObject(rule)) continue;
|
|
184
|
+
const match = isPlainObject(rule.match) ? rule.match : {};
|
|
185
|
+
const decision = normalizeDecisionValue(rule.decision);
|
|
186
|
+
if (!decision) continue;
|
|
187
|
+
const hasTool = typeof match.tool === "string" && match.tool.trim();
|
|
188
|
+
const hasPattern =
|
|
189
|
+
typeof match.commandPattern === "string" && match.commandPattern.trim();
|
|
190
|
+
if (hasTool || hasPattern) {
|
|
191
|
+
// Fine-grained rule: tried in order before the riskLevel map.
|
|
192
|
+
const riskLevel =
|
|
193
|
+
typeof match.riskLevel === "string" &&
|
|
194
|
+
RISK_LEVELS.includes(match.riskLevel.toLowerCase())
|
|
195
|
+
? match.riskLevel.toLowerCase()
|
|
196
|
+
: null;
|
|
197
|
+
const tool = hasTool ? match.tool.trim() : null;
|
|
198
|
+
const pattern = hasPattern ? match.commandPattern.trim() : null;
|
|
199
|
+
const regex = pattern ? compileGlob(pattern) : null;
|
|
200
|
+
const reason =
|
|
201
|
+
typeof rule.reason === "string" && rule.reason.trim()
|
|
202
|
+
? rule.reason.trim()
|
|
203
|
+
: `autoMode.decisions rule matches ${[
|
|
204
|
+
tool ? `tool=${tool}` : null,
|
|
205
|
+
pattern ? `command=${pattern}` : null,
|
|
206
|
+
riskLevel ? `risk=${riskLevel}` : null,
|
|
207
|
+
]
|
|
208
|
+
.filter(Boolean)
|
|
209
|
+
.join(" ")} → ${decision}`;
|
|
210
|
+
rules.push({
|
|
211
|
+
match: {
|
|
212
|
+
...(riskLevel ? { riskLevel } : {}),
|
|
213
|
+
...(tool ? { tool } : {}),
|
|
214
|
+
...(pattern ? { commandPattern: pattern } : {}),
|
|
215
|
+
},
|
|
216
|
+
decision,
|
|
217
|
+
reason,
|
|
218
|
+
_test: (ctx) => {
|
|
219
|
+
if (riskLevel) {
|
|
220
|
+
const r = RISK_LEVELS.includes(ctx.riskLevel)
|
|
221
|
+
? ctx.riskLevel
|
|
222
|
+
: "low";
|
|
223
|
+
if (r !== riskLevel) return false;
|
|
224
|
+
}
|
|
225
|
+
if (tool && ctx.tool !== tool) return false;
|
|
226
|
+
if (regex && !regex.test(String(ctx.args?.command ?? ""))) {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
return true;
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
customized = true;
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
entries.push([match.riskLevel, rule.decision, rule.reason]);
|
|
236
|
+
}
|
|
237
|
+
} else if (isPlainObject(raw)) {
|
|
238
|
+
for (const [riskLevel, value] of Object.entries(raw)) {
|
|
239
|
+
if (isPlainObject(value)) {
|
|
240
|
+
entries.push([riskLevel, value.decision, value.reason]);
|
|
241
|
+
} else {
|
|
242
|
+
entries.push([riskLevel, value, undefined]);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
for (const [riskLevelRaw, decisionRaw, reasonRaw] of entries) {
|
|
248
|
+
const riskLevel =
|
|
249
|
+
typeof riskLevelRaw === "string" ? riskLevelRaw.toLowerCase() : null;
|
|
250
|
+
if (!RISK_LEVELS.includes(riskLevel)) continue;
|
|
251
|
+
const decision = normalizeDecisionValue(decisionRaw);
|
|
252
|
+
if (!decision) continue;
|
|
253
|
+
const reason =
|
|
254
|
+
typeof reasonRaw === "string" && reasonRaw.trim()
|
|
255
|
+
? reasonRaw.trim()
|
|
256
|
+
: `autoMode.decisions maps ${riskLevel} risk to ${decision}`;
|
|
257
|
+
if (map[riskLevel].decision !== decision) customized = true;
|
|
258
|
+
map[riskLevel] = { decision, reason, source: "settings" };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return { map, rules, customized };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Wrap a session-core ApprovalGate so `--permission-mode auto` consults the
|
|
266
|
+
* user-configured riskLevel → decision map instead of the fixed trusted-policy
|
|
267
|
+
* tier. Only wired when `resolveAutoModeDecisions` reports `customized` — the
|
|
268
|
+
* unconfigured path keeps the byte-identical trusted mapping.
|
|
269
|
+
*
|
|
270
|
+
* The wrapper cannot up-authorize hard shell-policy denies: those return from
|
|
271
|
+
* `evaluateShellCommandWithApproval` before the gate is ever consulted.
|
|
272
|
+
*
|
|
273
|
+
* @param {object} inner session-core ApprovalGate (or compatible)
|
|
274
|
+
* @param {ReturnType<typeof resolveAutoModeDecisions>} resolved
|
|
275
|
+
* @param {{ isActive?: () => boolean }} [opts] `isActive` lets a host that can
|
|
276
|
+
* switch permission modes mid-session (the REPL) leave the wrapper
|
|
277
|
+
* installed permanently and toggle it: when it returns false, decide()
|
|
278
|
+
* delegates untouched to the inner gate. Omitted → always active
|
|
279
|
+
* (headless runs pick the mode once per process).
|
|
280
|
+
*/
|
|
281
|
+
export function createAutoModeApprovalGate(inner, resolved, opts = {}) {
|
|
282
|
+
const map = resolved?.map || defaultDecisionMap();
|
|
283
|
+
const rules = Array.isArray(resolved?.rules) ? resolved.rules : [];
|
|
284
|
+
const isActive =
|
|
285
|
+
typeof opts.isActive === "function" ? opts.isActive : () => true;
|
|
286
|
+
let confirm = null;
|
|
287
|
+
|
|
288
|
+
return {
|
|
289
|
+
isAutoModeGate: true,
|
|
290
|
+
setSessionPolicy(sessionId, policy) {
|
|
291
|
+
return inner?.setSessionPolicy?.(sessionId, policy);
|
|
292
|
+
},
|
|
293
|
+
getSessionPolicy(sessionId) {
|
|
294
|
+
return inner?.getSessionPolicy?.(sessionId);
|
|
295
|
+
},
|
|
296
|
+
clearSessionPolicy(sessionId) {
|
|
297
|
+
return inner?.clearSessionPolicy?.(sessionId);
|
|
298
|
+
},
|
|
299
|
+
setConfirmer(fn) {
|
|
300
|
+
confirm = typeof fn === "function" ? fn : null;
|
|
301
|
+
inner?.setConfirmer?.(fn);
|
|
302
|
+
},
|
|
303
|
+
hasConfirmer() {
|
|
304
|
+
return typeof confirm === "function";
|
|
305
|
+
},
|
|
306
|
+
async decide(ctx = {}) {
|
|
307
|
+
if (!isActive()) return inner.decide(ctx);
|
|
308
|
+
const riskLevel = RISK_LEVELS.includes(ctx.riskLevel)
|
|
309
|
+
? ctx.riskLevel
|
|
310
|
+
: "low";
|
|
311
|
+
// Fine-grained rules (tool / commandPattern) run in declaration order
|
|
312
|
+
// before the riskLevel map — first full match wins.
|
|
313
|
+
let rule = null;
|
|
314
|
+
for (const candidate of rules) {
|
|
315
|
+
let matched = false;
|
|
316
|
+
try {
|
|
317
|
+
matched = candidate._test({ ...ctx, riskLevel });
|
|
318
|
+
} catch {
|
|
319
|
+
matched = false; // a broken matcher never decides anything
|
|
320
|
+
}
|
|
321
|
+
if (matched) {
|
|
322
|
+
rule = {
|
|
323
|
+
decision: candidate.decision,
|
|
324
|
+
reason: candidate.reason,
|
|
325
|
+
source: "settings",
|
|
326
|
+
match: candidate.match,
|
|
327
|
+
};
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (!rule) {
|
|
332
|
+
const fromMap = map[riskLevel];
|
|
333
|
+
rule = {
|
|
334
|
+
decision: fromMap.decision,
|
|
335
|
+
reason: fromMap.reason,
|
|
336
|
+
source: fromMap.source,
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
const common = {
|
|
340
|
+
policy: "auto-mode",
|
|
341
|
+
riskLevel,
|
|
342
|
+
reason: rule.reason,
|
|
343
|
+
rule: {
|
|
344
|
+
riskLevel,
|
|
345
|
+
decision: rule.decision,
|
|
346
|
+
source: rule.source,
|
|
347
|
+
...(rule.match ? { match: rule.match } : {}),
|
|
348
|
+
},
|
|
349
|
+
};
|
|
350
|
+
if (rule.decision === "allow") {
|
|
351
|
+
return {
|
|
352
|
+
decision: "allow",
|
|
353
|
+
via: "auto-mode-config",
|
|
354
|
+
base: "allow",
|
|
355
|
+
...common,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
if (rule.decision === "deny") {
|
|
359
|
+
return {
|
|
360
|
+
decision: "deny",
|
|
361
|
+
via: "auto-mode-config",
|
|
362
|
+
base: "deny",
|
|
363
|
+
...common,
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
// ask — same confirm semantics as the session-core gate: no confirmer
|
|
367
|
+
// means fail-closed deny (headless installs a deny-confirmer anyway).
|
|
368
|
+
if (typeof confirm !== "function") {
|
|
369
|
+
return {
|
|
370
|
+
decision: "deny",
|
|
371
|
+
via: "no-confirmer",
|
|
372
|
+
base: "confirm",
|
|
373
|
+
...common,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
let ok = false;
|
|
377
|
+
try {
|
|
378
|
+
ok = await confirm(ctx);
|
|
379
|
+
} catch (error) {
|
|
380
|
+
return {
|
|
381
|
+
decision: "deny",
|
|
382
|
+
via: "confirm-error",
|
|
383
|
+
base: "confirm",
|
|
384
|
+
error,
|
|
385
|
+
...common,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
return {
|
|
389
|
+
decision: ok ? "allow" : "deny",
|
|
390
|
+
via: ok ? "user-confirm" : "user-deny",
|
|
391
|
+
base: "confirm",
|
|
392
|
+
...common,
|
|
393
|
+
};
|
|
394
|
+
},
|
|
395
|
+
};
|
|
396
|
+
}
|
|
@@ -17,6 +17,23 @@ import { getHomeDir } from "./paths.js";
|
|
|
17
17
|
|
|
18
18
|
export const _deps = { spawn, spawnSync };
|
|
19
19
|
|
|
20
|
+
export const DEFAULT_HEARTBEAT_INTERVAL_MS = 5000;
|
|
21
|
+
export const DEFAULT_HEARTBEAT_STALE_MS = 120000;
|
|
22
|
+
|
|
23
|
+
function nowMs(options = {}) {
|
|
24
|
+
return typeof options.now === "number" ? options.now : Date.now();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function heartbeatStaleMs(options = {}) {
|
|
28
|
+
const configured =
|
|
29
|
+
options.heartbeatStaleMs ||
|
|
30
|
+
process.env.CC_BACKGROUND_AGENT_HEARTBEAT_STALE_MS;
|
|
31
|
+
const n = Number(configured);
|
|
32
|
+
return Number.isFinite(n) && n > 0
|
|
33
|
+
? Math.floor(n)
|
|
34
|
+
: DEFAULT_HEARTBEAT_STALE_MS;
|
|
35
|
+
}
|
|
36
|
+
|
|
20
37
|
export function backgroundAgentsDir() {
|
|
21
38
|
const dir =
|
|
22
39
|
process.env.CC_BACKGROUND_AGENTS_DIR ||
|
|
@@ -73,12 +90,43 @@ export function isProcessAlive(pid) {
|
|
|
73
90
|
}
|
|
74
91
|
}
|
|
75
92
|
|
|
76
|
-
export function
|
|
93
|
+
export function normalizeBackgroundAgentTitle(title) {
|
|
94
|
+
const value = String(title || "").trim();
|
|
95
|
+
if (!value) throw new Error("Background agent title cannot be empty");
|
|
96
|
+
return value.slice(0, 160);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function effectiveBackgroundAgentState(state, options = {}) {
|
|
77
100
|
if (!state) return null;
|
|
78
|
-
if (state.status
|
|
79
|
-
|
|
101
|
+
if (state.status !== "running") return state;
|
|
102
|
+
const t = nowMs(options);
|
|
103
|
+
let next = state;
|
|
104
|
+
if (
|
|
105
|
+
Number.isFinite(Number(state.heartbeatAt)) &&
|
|
106
|
+
t - Number(state.heartbeatAt) > heartbeatStaleMs(options)
|
|
107
|
+
) {
|
|
108
|
+
next = {
|
|
109
|
+
...state,
|
|
110
|
+
status: "lost",
|
|
111
|
+
endedAt: state.endedAt || t,
|
|
112
|
+
lostReason: "heartbeat-stale",
|
|
113
|
+
};
|
|
114
|
+
} else if (!isProcessAlive(state.pid)) {
|
|
115
|
+
next = {
|
|
116
|
+
...state,
|
|
117
|
+
status: "lost",
|
|
118
|
+
endedAt: state.endedAt || t,
|
|
119
|
+
lostReason: "process-exited",
|
|
120
|
+
};
|
|
80
121
|
}
|
|
81
|
-
|
|
122
|
+
if (next !== state && options.persist !== false) {
|
|
123
|
+
try {
|
|
124
|
+
writeBackgroundAgentState(next);
|
|
125
|
+
} catch {
|
|
126
|
+
/* best-effort; callers still get the corrected state */
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return next;
|
|
82
130
|
}
|
|
83
131
|
|
|
84
132
|
export function listBackgroundAgents(options = {}) {
|
|
@@ -86,17 +134,137 @@ export function listBackgroundAgents(options = {}) {
|
|
|
86
134
|
.filter((name) => name.endsWith(".json") && !name.includes(".job."))
|
|
87
135
|
.map((name) => readBackgroundAgentState(name.slice(0, -5)))
|
|
88
136
|
.filter(Boolean)
|
|
89
|
-
.map(effectiveBackgroundAgentState)
|
|
137
|
+
.map((state) => effectiveBackgroundAgentState(state, options))
|
|
90
138
|
.filter((state) => options.all || state.status === "running")
|
|
91
139
|
.sort((a, b) => (b.startedAt || 0) - (a.startedAt || 0));
|
|
92
140
|
}
|
|
93
141
|
|
|
142
|
+
function sleepSyncMs(ms) {
|
|
143
|
+
try {
|
|
144
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
145
|
+
} catch {
|
|
146
|
+
const until = Date.now() + ms;
|
|
147
|
+
while (Date.now() < until) {
|
|
148
|
+
/* fallback busy-wait — only a few ms */
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function renameBackgroundAgent(id, title, options = {}) {
|
|
154
|
+
const state = effectiveBackgroundAgentState(readBackgroundAgentState(id), {
|
|
155
|
+
now: options.now,
|
|
156
|
+
heartbeatStaleMs: options.heartbeatStaleMs,
|
|
157
|
+
});
|
|
158
|
+
if (!state) throw new Error(`Background agent not found: ${id}`);
|
|
159
|
+
const normalized = normalizeBackgroundAgentTitle(title);
|
|
160
|
+
const renamedAt = typeof options.now === "number" ? options.now : Date.now();
|
|
161
|
+
// The worker persists state via read-modify-write (heartbeat / transport /
|
|
162
|
+
// turn merges). A rename that lands inside one of those read→write windows
|
|
163
|
+
// gets clobbered by the worker's stale snapshot — so verify after a short
|
|
164
|
+
// beat and re-apply onto the freshest state (bounded retries; the window is
|
|
165
|
+
// milliseconds wide, worst during worker startup's write burst).
|
|
166
|
+
let next = { ...state, title: normalized, renamedAt };
|
|
167
|
+
for (let attempt = 0; attempt < 4; attempt++) {
|
|
168
|
+
writeBackgroundAgentState(next);
|
|
169
|
+
sleepSyncMs(15);
|
|
170
|
+
const current = readBackgroundAgentState(id) || next;
|
|
171
|
+
if (current.title === normalized) {
|
|
172
|
+
return { ...current, title: normalized, renamedAt };
|
|
173
|
+
}
|
|
174
|
+
next = { ...current, title: normalized, renamedAt };
|
|
175
|
+
}
|
|
176
|
+
writeBackgroundAgentState(next);
|
|
177
|
+
return next;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Build the follow-up argv template for interactive attach turns: the launch
|
|
182
|
+
* argv minus every token that carried the FIRST turn's prompt (positional
|
|
183
|
+
* task words, `-p/--print` and its inline value). The worker appends
|
|
184
|
+
* `["-p", "<follow-up text>"]` per turn, so all remaining flags (model,
|
|
185
|
+
* permission-mode, session id, …) keep applying to later turns.
|
|
186
|
+
*
|
|
187
|
+
* @param {string[]} argv the background child argv (before any piped-prompt
|
|
188
|
+
* token is appended)
|
|
189
|
+
* @param {object} [opts]
|
|
190
|
+
* @param {string[]} [opts.positionalTokens] raw positional prompt tokens
|
|
191
|
+
* @param {string|null} [opts.printValue] inline `-p <value>` text, when the
|
|
192
|
+
* prompt came from --print
|
|
193
|
+
*/
|
|
194
|
+
export function buildFollowUpArgv(argv, opts = {}) {
|
|
195
|
+
const positionalLeft = Array.isArray(opts.positionalTokens)
|
|
196
|
+
? [...opts.positionalTokens]
|
|
197
|
+
: [];
|
|
198
|
+
const printValue =
|
|
199
|
+
typeof opts.printValue === "string" && opts.printValue.trim()
|
|
200
|
+
? opts.printValue
|
|
201
|
+
: null;
|
|
202
|
+
const out = [];
|
|
203
|
+
for (let i = 0; i < argv.length; i++) {
|
|
204
|
+
const arg = argv[i];
|
|
205
|
+
if (arg === "-p" || arg === "--print") {
|
|
206
|
+
if (printValue !== null && argv[i + 1] === printValue) i++;
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
// equals-form: --print=<value> carries the prompt in the same token
|
|
210
|
+
if (
|
|
211
|
+
printValue !== null &&
|
|
212
|
+
arg.startsWith("--print=") &&
|
|
213
|
+
arg.slice("--print=".length) === printValue
|
|
214
|
+
) {
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
if (positionalLeft.length && arg === positionalLeft[0]) {
|
|
218
|
+
positionalLeft.shift();
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
out.push(arg);
|
|
222
|
+
}
|
|
223
|
+
return out;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Continue a finished/crashed background session as a NEW background session
|
|
228
|
+
* on the same conversation (`cc daemon resume <id> <prompt>`). The original
|
|
229
|
+
* launch argv is intentionally NOT persisted (it may carry secrets), so the
|
|
230
|
+
* resume runs a minimal `agent --session <sid> -p <prompt>` — model/provider
|
|
231
|
+
* come from config defaults, and the headless runner replays the JSONL
|
|
232
|
+
* transcript for the session id.
|
|
233
|
+
*/
|
|
234
|
+
export function resumeBackgroundAgent(id, prompt, options = {}) {
|
|
235
|
+
const state = effectiveBackgroundAgentState(readBackgroundAgentState(id), {
|
|
236
|
+
now: options.now,
|
|
237
|
+
heartbeatStaleMs: options.heartbeatStaleMs,
|
|
238
|
+
});
|
|
239
|
+
if (!state) throw new Error(`Background agent not found: ${id}`);
|
|
240
|
+
if (state.status === "running") {
|
|
241
|
+
throw new Error(
|
|
242
|
+
`Background agent ${id} is still running — use cc attach ${id} to send follow-up prompts instead`,
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
if (!state.sessionId) {
|
|
246
|
+
throw new Error(`Background agent ${id} has no session id to resume from`);
|
|
247
|
+
}
|
|
248
|
+
const text = String(prompt || "").trim();
|
|
249
|
+
if (!text) throw new Error("resume requires a prompt");
|
|
250
|
+
const argv = ["agent", "--session", state.sessionId, "-p", text];
|
|
251
|
+
return launchBackgroundAgent({
|
|
252
|
+
argv,
|
|
253
|
+
cwd: options.cwd || state.cwd || process.cwd(),
|
|
254
|
+
sessionId: state.sessionId,
|
|
255
|
+
title: options.title || state.title || text.slice(0, 100),
|
|
256
|
+
cliEntry: options.cliEntry,
|
|
257
|
+
followUpArgv: ["agent", "--session", state.sessionId],
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
94
261
|
export function launchBackgroundAgent({
|
|
95
262
|
argv,
|
|
96
263
|
cwd,
|
|
97
264
|
sessionId,
|
|
98
265
|
title,
|
|
99
266
|
cliEntry,
|
|
267
|
+
followUpArgv,
|
|
100
268
|
}) {
|
|
101
269
|
const id = createBackgroundAgentId();
|
|
102
270
|
const dir = backgroundAgentsDir();
|
|
@@ -112,8 +280,30 @@ export function launchBackgroundAgent({
|
|
|
112
280
|
title: title || "Background agent",
|
|
113
281
|
cliEntry: cliEntry || process.argv[1],
|
|
114
282
|
logFile: logPath(id),
|
|
283
|
+
// Present = interactive attach can start follow-up turns; absent = the
|
|
284
|
+
// transport rejects prompts (log-only session).
|
|
285
|
+
...(Array.isArray(followUpArgv) ? { followUpArgv } : {}),
|
|
115
286
|
};
|
|
116
287
|
writeFileSync(jobFile, JSON.stringify(job), { mode: 0o600 });
|
|
288
|
+
// Write the initial state BEFORE spawning so the worker's own merges (the
|
|
289
|
+
// transport endpoint lands within its first ~100ms) can never be clobbered
|
|
290
|
+
// by a late launcher write racing the worker.
|
|
291
|
+
const state = {
|
|
292
|
+
id,
|
|
293
|
+
sessionId,
|
|
294
|
+
title: job.title,
|
|
295
|
+
cwd,
|
|
296
|
+
pid: null,
|
|
297
|
+
workerPid: null,
|
|
298
|
+
agentPid: null,
|
|
299
|
+
status: "running",
|
|
300
|
+
startedAt: Date.now(),
|
|
301
|
+
heartbeatAt: Date.now(),
|
|
302
|
+
endedAt: null,
|
|
303
|
+
exitCode: null,
|
|
304
|
+
logFile: job.logFile,
|
|
305
|
+
};
|
|
306
|
+
writeBackgroundAgentState(state);
|
|
117
307
|
let child;
|
|
118
308
|
try {
|
|
119
309
|
child = _deps.spawn(process.execPath, [worker, jobFile], {
|
|
@@ -124,23 +314,18 @@ export function launchBackgroundAgent({
|
|
|
124
314
|
});
|
|
125
315
|
} catch (error) {
|
|
126
316
|
rmSync(jobFile, { force: true });
|
|
317
|
+
rmSync(statePath(id), { force: true });
|
|
127
318
|
throw error;
|
|
128
319
|
}
|
|
129
320
|
child.unref();
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
title: job.title,
|
|
134
|
-
cwd,
|
|
321
|
+
const current = readBackgroundAgentState(id) || state;
|
|
322
|
+
const withPid = {
|
|
323
|
+
...current,
|
|
135
324
|
pid: child.pid,
|
|
136
|
-
|
|
137
|
-
startedAt: Date.now(),
|
|
138
|
-
endedAt: null,
|
|
139
|
-
exitCode: null,
|
|
140
|
-
logFile: job.logFile,
|
|
325
|
+
workerPid: child.pid,
|
|
141
326
|
};
|
|
142
|
-
writeBackgroundAgentState(
|
|
143
|
-
return
|
|
327
|
+
writeBackgroundAgentState(withPid);
|
|
328
|
+
return withPid;
|
|
144
329
|
}
|
|
145
330
|
|
|
146
331
|
export function readBackgroundAgentLog(id, options = {}) {
|