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,315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* background-session-transport — local control channel for interactive attach.
|
|
3
|
+
*
|
|
4
|
+
* The background-agent worker hosts a per-session NDJSON server on a local
|
|
5
|
+
* pipe (Windows named pipe / POSIX domain socket). `cc attach <id>` connects
|
|
6
|
+
* to it to send follow-up prompts into the running session and receive
|
|
7
|
+
* lifecycle events (turn-started / turn-ended / idle), turning the log-only
|
|
8
|
+
* attach into a real interactive takeover.
|
|
9
|
+
*
|
|
10
|
+
* Auth model: the worker generates a random token, stored in the 0600 state
|
|
11
|
+
* file next to the pipe path. A client must open with
|
|
12
|
+
* `{type:"hello", token}` — anything else (or a wrong token, or silence for
|
|
13
|
+
* 5s) destroys the connection. Named pipes are reachable by other local
|
|
14
|
+
* users on Windows, so possession of the state file IS the capability.
|
|
15
|
+
*
|
|
16
|
+
* Protocol (one JSON object per line, both directions):
|
|
17
|
+
* client → worker: {type:"hello", token} · {type:"prompt", text}
|
|
18
|
+
* · {type:"status"} · {type:"stop"} · {type:"detach"}
|
|
19
|
+
* worker → client: {type:"hello", ...status} · {type:"accepted", queued}
|
|
20
|
+
* · {type:"status", ...status} · {type:"error", message}
|
|
21
|
+
* · {type:"turn-started"|"turn-ended"|"idle", ...}
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import net from "node:net";
|
|
25
|
+
import { unlinkSync } from "node:fs";
|
|
26
|
+
import { join } from "node:path";
|
|
27
|
+
|
|
28
|
+
const HELLO_TIMEOUT_MS = 5000;
|
|
29
|
+
const MAX_LINE_BYTES = 1024 * 1024;
|
|
30
|
+
|
|
31
|
+
/** Pipe endpoint for a session id (Windows named pipe / POSIX socket file). */
|
|
32
|
+
export function transportPipePath(id, dir) {
|
|
33
|
+
if (process.platform === "win32") return `\\\\.\\pipe\\cc-bg-${id}`;
|
|
34
|
+
return join(dir, `${id}.sock`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* NDJSON line framing with a carry buffer — a chunk boundary can split a
|
|
39
|
+
* line, so the remainder must carry into the next chunk instead of being
|
|
40
|
+
* parsed (and dropped) as its own line.
|
|
41
|
+
*/
|
|
42
|
+
export function createNdjsonReader(onMessage, onError = () => {}) {
|
|
43
|
+
let carry = "";
|
|
44
|
+
return (chunk) => {
|
|
45
|
+
carry += chunk.toString("utf8");
|
|
46
|
+
if (carry.length > MAX_LINE_BYTES) {
|
|
47
|
+
carry = "";
|
|
48
|
+
onError(new Error("line too long"));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
let index;
|
|
52
|
+
while ((index = carry.indexOf("\n")) !== -1) {
|
|
53
|
+
const line = carry.slice(0, index).replace(/\r$/, "");
|
|
54
|
+
carry = carry.slice(index + 1);
|
|
55
|
+
if (!line.trim()) continue;
|
|
56
|
+
let message;
|
|
57
|
+
try {
|
|
58
|
+
message = JSON.parse(line);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
onError(error);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
onMessage(message);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function writeMessage(socket, message) {
|
|
69
|
+
if (!socket || socket.destroyed) return false;
|
|
70
|
+
try {
|
|
71
|
+
socket.write(`${JSON.stringify(message)}\n`);
|
|
72
|
+
return true;
|
|
73
|
+
} catch {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Start the worker-side control server.
|
|
80
|
+
*
|
|
81
|
+
* @param {object} opts
|
|
82
|
+
* @param {string} opts.id background agent id
|
|
83
|
+
* @param {string} opts.dir backgroundAgentsDir() (POSIX socket location)
|
|
84
|
+
* @param {string} opts.token shared secret from the session state file
|
|
85
|
+
* @param {(text:string)=>{queued:number}} opts.onPrompt queue a follow-up
|
|
86
|
+
* prompt; throw to reject (message is relayed to the client)
|
|
87
|
+
* @param {()=>void} [opts.onStop] kill the current agent turn
|
|
88
|
+
* @param {(count:number)=>void} [opts.onClientChange]
|
|
89
|
+
* @param {()=>object} [opts.getStatus] session status payload for hello/status
|
|
90
|
+
* @returns {Promise<{pipePath:string, clientCount:()=>number,
|
|
91
|
+
* broadcast:(msg:object)=>void, close:()=>Promise<void>}>}
|
|
92
|
+
*/
|
|
93
|
+
export function startBackgroundSessionServer(opts) {
|
|
94
|
+
const { id, dir, token, onPrompt, onStop, onClientChange, getStatus } = opts;
|
|
95
|
+
const pipePath = opts.pipePath || transportPipePath(id, dir);
|
|
96
|
+
if (process.platform !== "win32") {
|
|
97
|
+
try {
|
|
98
|
+
unlinkSync(pipePath); // stale socket from a crashed prior worker
|
|
99
|
+
} catch {
|
|
100
|
+
/* fine — usually ENOENT */
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const clients = new Set();
|
|
104
|
+
const notifyClients = () => {
|
|
105
|
+
try {
|
|
106
|
+
onClientChange?.(clients.size);
|
|
107
|
+
} catch {
|
|
108
|
+
/* observer errors never take the server down */
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const server = net.createServer((socket) => {
|
|
113
|
+
let authed = false;
|
|
114
|
+
const helloTimer = setTimeout(() => {
|
|
115
|
+
if (!authed) socket.destroy();
|
|
116
|
+
}, HELLO_TIMEOUT_MS);
|
|
117
|
+
helloTimer.unref?.();
|
|
118
|
+
|
|
119
|
+
socket.on(
|
|
120
|
+
"data",
|
|
121
|
+
createNdjsonReader(
|
|
122
|
+
(message) => {
|
|
123
|
+
if (!authed) {
|
|
124
|
+
if (message.type === "hello" && message.token === token) {
|
|
125
|
+
authed = true;
|
|
126
|
+
clearTimeout(helloTimer);
|
|
127
|
+
clients.add(socket);
|
|
128
|
+
writeMessage(socket, {
|
|
129
|
+
type: "hello",
|
|
130
|
+
...(getStatus ? getStatus() : { id }),
|
|
131
|
+
});
|
|
132
|
+
notifyClients();
|
|
133
|
+
} else {
|
|
134
|
+
socket.destroy();
|
|
135
|
+
}
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
switch (message.type) {
|
|
139
|
+
case "prompt": {
|
|
140
|
+
const text = String(message.text || "").trim();
|
|
141
|
+
if (!text) {
|
|
142
|
+
writeMessage(socket, {
|
|
143
|
+
type: "error",
|
|
144
|
+
message: "prompt text is empty",
|
|
145
|
+
});
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
const result = onPrompt(text);
|
|
150
|
+
writeMessage(socket, {
|
|
151
|
+
type: "accepted",
|
|
152
|
+
queued: result?.queued ?? 1,
|
|
153
|
+
});
|
|
154
|
+
} catch (error) {
|
|
155
|
+
writeMessage(socket, {
|
|
156
|
+
type: "error",
|
|
157
|
+
message: error?.message || String(error),
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
case "status": {
|
|
163
|
+
writeMessage(socket, {
|
|
164
|
+
type: "status",
|
|
165
|
+
...(getStatus ? getStatus() : { id }),
|
|
166
|
+
});
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
case "stop": {
|
|
170
|
+
try {
|
|
171
|
+
onStop?.();
|
|
172
|
+
writeMessage(socket, { type: "stopping" });
|
|
173
|
+
} catch (error) {
|
|
174
|
+
writeMessage(socket, {
|
|
175
|
+
type: "error",
|
|
176
|
+
message: error?.message || String(error),
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
case "detach": {
|
|
182
|
+
socket.end();
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
default:
|
|
186
|
+
writeMessage(socket, {
|
|
187
|
+
type: "error",
|
|
188
|
+
message: `unknown message type: ${message.type}`,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
() => {
|
|
193
|
+
// Unparseable input on an authed connection is a broken client —
|
|
194
|
+
// and on an unauthed one, a probe. Either way, drop it.
|
|
195
|
+
socket.destroy();
|
|
196
|
+
},
|
|
197
|
+
),
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
const drop = () => {
|
|
201
|
+
clearTimeout(helloTimer);
|
|
202
|
+
if (clients.delete(socket)) notifyClients();
|
|
203
|
+
};
|
|
204
|
+
socket.on("close", drop);
|
|
205
|
+
socket.on("error", drop);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
return new Promise((resolve, reject) => {
|
|
209
|
+
server.once("error", reject);
|
|
210
|
+
server.listen(pipePath, () => {
|
|
211
|
+
server.removeListener("error", reject);
|
|
212
|
+
server.on("error", () => {
|
|
213
|
+
/* post-listen errors must not crash the worker */
|
|
214
|
+
});
|
|
215
|
+
resolve({
|
|
216
|
+
pipePath,
|
|
217
|
+
clientCount: () => clients.size,
|
|
218
|
+
broadcast: (message) => {
|
|
219
|
+
for (const socket of clients) writeMessage(socket, message);
|
|
220
|
+
},
|
|
221
|
+
close: () =>
|
|
222
|
+
new Promise((done) => {
|
|
223
|
+
for (const socket of clients) socket.destroy();
|
|
224
|
+
clients.clear();
|
|
225
|
+
server.close(() => {
|
|
226
|
+
if (process.platform !== "win32") {
|
|
227
|
+
try {
|
|
228
|
+
unlinkSync(pipePath);
|
|
229
|
+
} catch {
|
|
230
|
+
/* already gone */
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
done();
|
|
234
|
+
});
|
|
235
|
+
}),
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Client side: connect, authenticate, and expose send/close. Resolves only
|
|
243
|
+
* after the server's hello ack — so a resolved connection is usable.
|
|
244
|
+
*
|
|
245
|
+
* @param {object} opts
|
|
246
|
+
* @param {string} opts.pipePath from the session state file `transport.pipe`
|
|
247
|
+
* @param {string} opts.token from the session state file `transport.token`
|
|
248
|
+
* @param {(msg:object)=>void} [opts.onEvent] server-pushed messages
|
|
249
|
+
* @param {()=>void} [opts.onClose]
|
|
250
|
+
* @returns {Promise<{send:(msg:object)=>boolean, close:()=>void, hello:object}>}
|
|
251
|
+
*/
|
|
252
|
+
export function connectBackgroundSession(opts) {
|
|
253
|
+
const { pipePath, token, onEvent, onClose, timeoutMs = 5000 } = opts;
|
|
254
|
+
return new Promise((resolve, reject) => {
|
|
255
|
+
let settled = false;
|
|
256
|
+
const socket = net.connect(pipePath);
|
|
257
|
+
const timer = setTimeout(() => {
|
|
258
|
+
if (!settled) {
|
|
259
|
+
settled = true;
|
|
260
|
+
socket.destroy();
|
|
261
|
+
reject(new Error("session transport handshake timed out"));
|
|
262
|
+
}
|
|
263
|
+
}, timeoutMs);
|
|
264
|
+
timer.unref?.();
|
|
265
|
+
|
|
266
|
+
socket.on(
|
|
267
|
+
"data",
|
|
268
|
+
createNdjsonReader((message) => {
|
|
269
|
+
if (!settled) {
|
|
270
|
+
if (message.type === "hello") {
|
|
271
|
+
settled = true;
|
|
272
|
+
clearTimeout(timer);
|
|
273
|
+
resolve({
|
|
274
|
+
hello: message,
|
|
275
|
+
send: (msg) => writeMessage(socket, msg),
|
|
276
|
+
close: () => {
|
|
277
|
+
writeMessage(socket, { type: "detach" });
|
|
278
|
+
socket.end();
|
|
279
|
+
},
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
try {
|
|
285
|
+
onEvent?.(message);
|
|
286
|
+
} catch {
|
|
287
|
+
/* rendering errors never kill the connection */
|
|
288
|
+
}
|
|
289
|
+
}),
|
|
290
|
+
);
|
|
291
|
+
socket.on("connect", () => {
|
|
292
|
+
writeMessage(socket, { type: "hello", token });
|
|
293
|
+
});
|
|
294
|
+
socket.on("error", (error) => {
|
|
295
|
+
if (!settled) {
|
|
296
|
+
settled = true;
|
|
297
|
+
clearTimeout(timer);
|
|
298
|
+
reject(error);
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
socket.on("close", () => {
|
|
302
|
+
if (!settled) {
|
|
303
|
+
settled = true;
|
|
304
|
+
clearTimeout(timer);
|
|
305
|
+
reject(new Error("session transport closed during handshake"));
|
|
306
|
+
} else {
|
|
307
|
+
try {
|
|
308
|
+
onClose?.();
|
|
309
|
+
} catch {
|
|
310
|
+
/* observer errors are not connection errors */
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
mkdirSync,
|
|
4
|
+
readFileSync,
|
|
5
|
+
renameSync,
|
|
6
|
+
writeFileSync,
|
|
7
|
+
} from "node:fs";
|
|
8
|
+
import { dirname, join } from "node:path";
|
|
9
|
+
import { getHomeDir } from "./paths.js";
|
|
10
|
+
import { MAX_RECENT_DENIALS, formatDenialChain } from "./repl-denials.js";
|
|
11
|
+
|
|
12
|
+
export const RECENT_DENIALS_VERSION = 1;
|
|
13
|
+
export const DEFAULT_RECENT_DENIAL_LIMIT = 100;
|
|
14
|
+
|
|
15
|
+
export const _deps = {
|
|
16
|
+
existsSync,
|
|
17
|
+
mkdirSync,
|
|
18
|
+
readFileSync,
|
|
19
|
+
renameSync,
|
|
20
|
+
writeFileSync,
|
|
21
|
+
getHomeDir,
|
|
22
|
+
now: () => Date.now(),
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export function recentDenialsPath() {
|
|
26
|
+
return join(_deps.getHomeDir(), "recent-denials.json");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function emptyStore() {
|
|
30
|
+
return { version: RECENT_DENIALS_VERSION, denials: [] };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function readStore(file = recentDenialsPath()) {
|
|
34
|
+
if (!_deps.existsSync(file)) return emptyStore();
|
|
35
|
+
try {
|
|
36
|
+
const parsed = JSON.parse(_deps.readFileSync(file, "utf8"));
|
|
37
|
+
return {
|
|
38
|
+
version: RECENT_DENIALS_VERSION,
|
|
39
|
+
denials: Array.isArray(parsed?.denials) ? parsed.denials : [],
|
|
40
|
+
};
|
|
41
|
+
} catch {
|
|
42
|
+
return emptyStore();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function writeStore(store, file = recentDenialsPath()) {
|
|
47
|
+
_deps.mkdirSync(dirname(file), { recursive: true });
|
|
48
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
49
|
+
_deps.writeFileSync(tmp, JSON.stringify(store, null, 2) + "\n", "utf8");
|
|
50
|
+
_deps.renameSync(tmp, file);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function normalizeRecord(record, metadata = {}) {
|
|
54
|
+
if (!record || typeof record !== "object") return null;
|
|
55
|
+
return {
|
|
56
|
+
at: typeof record.at === "number" ? record.at : _deps.now(),
|
|
57
|
+
tool: String(record.tool || "?"),
|
|
58
|
+
summary: String(record.summary || ""),
|
|
59
|
+
reason: String(record.reason || ""),
|
|
60
|
+
via: String(record.via || "policy"),
|
|
61
|
+
rule: record.rule == null ? null : String(record.rule),
|
|
62
|
+
count: Number.isFinite(record.count) && record.count > 1 ? record.count : 1,
|
|
63
|
+
sessionId: metadata.sessionId || record.sessionId || null,
|
|
64
|
+
permissionMode: metadata.permissionMode || record.permissionMode || null,
|
|
65
|
+
cwd: metadata.cwd || record.cwd || null,
|
|
66
|
+
source: metadata.source || record.source || "agent",
|
|
67
|
+
// Layer-by-layer explanation (settings-rules → shell-policy →
|
|
68
|
+
// approval-gate) when the denial carried one; null for older records.
|
|
69
|
+
chain: Array.isArray(record.chain) ? record.chain : null,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function sameRecentDenial(a, b) {
|
|
74
|
+
return (
|
|
75
|
+
a &&
|
|
76
|
+
b &&
|
|
77
|
+
a.tool === b.tool &&
|
|
78
|
+
a.summary === b.summary &&
|
|
79
|
+
a.via === b.via &&
|
|
80
|
+
a.rule === b.rule &&
|
|
81
|
+
a.sessionId === b.sessionId &&
|
|
82
|
+
a.permissionMode === b.permissionMode &&
|
|
83
|
+
a.cwd === b.cwd &&
|
|
84
|
+
a.source === b.source
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function parseLimit(value, fallback = DEFAULT_RECENT_DENIAL_LIMIT) {
|
|
89
|
+
const n = Number(value);
|
|
90
|
+
return Number.isFinite(n) && n > 0 ? Math.floor(n) : fallback;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function appendRecentDenials(records, metadata = {}, options = {}) {
|
|
94
|
+
const incoming = Array.isArray(records) ? records : [records];
|
|
95
|
+
const normalized = incoming
|
|
96
|
+
.map((record) => normalizeRecord(record, metadata))
|
|
97
|
+
.filter(Boolean);
|
|
98
|
+
if (!normalized.length) return readRecentDenials(options);
|
|
99
|
+
|
|
100
|
+
const max = parseLimit(options.max || options.limit);
|
|
101
|
+
const file = options.file || recentDenialsPath();
|
|
102
|
+
const store = readStore(file);
|
|
103
|
+
for (const record of normalized) {
|
|
104
|
+
const last = store.denials[store.denials.length - 1];
|
|
105
|
+
if (sameRecentDenial(last, record)) {
|
|
106
|
+
last.count = (last.count || 1) + (record.count || 1);
|
|
107
|
+
last.at = record.at;
|
|
108
|
+
if (record.reason) last.reason = record.reason;
|
|
109
|
+
} else {
|
|
110
|
+
store.denials.push(record);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
while (store.denials.length > max) store.denials.shift();
|
|
114
|
+
writeStore(store, file);
|
|
115
|
+
return store.denials.slice(-Math.min(max, store.denials.length));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function readRecentDenials(options = {}) {
|
|
119
|
+
const max = parseLimit(options.limit);
|
|
120
|
+
const store = readStore(options.file || recentDenialsPath());
|
|
121
|
+
return store.denials.slice(-Math.min(max, store.denials.length));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function clearRecentDenials(options = {}) {
|
|
125
|
+
const file = options.file || recentDenialsPath();
|
|
126
|
+
writeStore(emptyStore(), file);
|
|
127
|
+
return { file, cleared: true };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function formatRecentDenials(records, options = {}) {
|
|
131
|
+
const now = typeof options.now === "number" ? options.now : _deps.now();
|
|
132
|
+
const denials = Array.isArray(records) ? records : [];
|
|
133
|
+
if (!denials.length) return " (no recent policy denials)";
|
|
134
|
+
const lines = [
|
|
135
|
+
` Recent policy denials (most recent first, ${denials.length}):`,
|
|
136
|
+
];
|
|
137
|
+
for (let i = denials.length - 1; i >= 0; i--) {
|
|
138
|
+
const d = denials[i] || {};
|
|
139
|
+
const ago =
|
|
140
|
+
typeof d.at === "number"
|
|
141
|
+
? ` · ${Math.max(0, Math.round((now - d.at) / 1000))}s ago`
|
|
142
|
+
: "";
|
|
143
|
+
const where = d.rule ? `${d.via}:${d.rule}` : d.via || "policy";
|
|
144
|
+
const what = d.summary ? `${d.tool} ${d.summary}` : d.tool || "?";
|
|
145
|
+
const times = d.count > 1 ? ` x${d.count}` : "";
|
|
146
|
+
const session = d.sessionId ? ` · session ${d.sessionId}` : "";
|
|
147
|
+
const mode = d.permissionMode ? ` · mode ${d.permissionMode}` : "";
|
|
148
|
+
lines.push(` - ${what}${times} [${where}${session}${mode}${ago}]`);
|
|
149
|
+
if (d.cwd) lines.push(` cwd: ${d.cwd}`);
|
|
150
|
+
const chainLine = formatDenialChain(d.chain);
|
|
151
|
+
if (chainLine) lines.push(` chain: ${chainLine}`);
|
|
152
|
+
if (d.reason) lines.push(` ${d.reason}`);
|
|
153
|
+
}
|
|
154
|
+
return lines.join("\n");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function recentDenialRetentionLimit() {
|
|
158
|
+
return Math.max(DEFAULT_RECENT_DENIAL_LIMIT, MAX_RECENT_DENIALS);
|
|
159
|
+
}
|