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,499 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { logger } from "../lib/logger.js";
|
|
4
|
+
|
|
5
|
+
function formatAge(ms) {
|
|
6
|
+
const n = Math.max(0, Math.round(Number(ms || 0) / 1000));
|
|
7
|
+
if (n < 60) return `${n}s`;
|
|
8
|
+
const m = Math.floor(n / 60);
|
|
9
|
+
const s = n % 60;
|
|
10
|
+
if (m < 60) return `${m}m${s ? ` ${s}s` : ""}`;
|
|
11
|
+
const h = Math.floor(m / 60);
|
|
12
|
+
const rest = m % 60;
|
|
13
|
+
return `${h}h${rest ? ` ${rest}m` : ""}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function formatBackgroundAgentLine(session, now = Date.now()) {
|
|
17
|
+
const elapsed = session?.endedAt
|
|
18
|
+
? session.endedAt - session.startedAt
|
|
19
|
+
: now - session.startedAt;
|
|
20
|
+
const title = session?.title ? ` ${session.title}` : "";
|
|
21
|
+
return `${session.id} ${String(session.status || "?").padEnd(9)} ${formatAge(elapsed).padStart(6)} ${session.cwd || ""}${title}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function printBackgroundAgents(sessions, { now = Date.now() } = {}) {
|
|
25
|
+
if (!sessions.length) {
|
|
26
|
+
logger.log(chalk.gray("No background agents."));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
for (const session of sessions) {
|
|
30
|
+
logger.log(formatBackgroundAgentLine(session, now));
|
|
31
|
+
if (session.pid || session.sessionId) {
|
|
32
|
+
logger.log(
|
|
33
|
+
chalk.gray(
|
|
34
|
+
` pid ${session.pid || "?"}${session.sessionId ? ` session ${session.sessionId}` : ""}`,
|
|
35
|
+
),
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function formatTimestamp(value) {
|
|
42
|
+
const n = Number(value);
|
|
43
|
+
return Number.isFinite(n) && n > 0 ? new Date(n).toISOString() : "-";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function formatBackgroundAgentDetails(
|
|
47
|
+
session,
|
|
48
|
+
logText = "",
|
|
49
|
+
options = {},
|
|
50
|
+
) {
|
|
51
|
+
const now = typeof options.now === "number" ? options.now : Date.now();
|
|
52
|
+
const elapsed = session?.endedAt
|
|
53
|
+
? session.endedAt - session.startedAt
|
|
54
|
+
: now - session.startedAt;
|
|
55
|
+
const lines = [
|
|
56
|
+
`Background agent ${session.id}`,
|
|
57
|
+
` status: ${session.status || "?"}`,
|
|
58
|
+
` title: ${session.title || "-"}`,
|
|
59
|
+
` cwd: ${session.cwd || "-"}`,
|
|
60
|
+
` pid: ${session.pid || "-"}`,
|
|
61
|
+
` workerPid: ${session.workerPid || "-"}`,
|
|
62
|
+
` agentPid: ${session.agentPid || "-"}`,
|
|
63
|
+
` session: ${session.sessionId || "-"}`,
|
|
64
|
+
` elapsed: ${formatAge(elapsed)}`,
|
|
65
|
+
` startedAt: ${formatTimestamp(session.startedAt)}`,
|
|
66
|
+
` endedAt: ${formatTimestamp(session.endedAt)}`,
|
|
67
|
+
` log: ${session.logFile || "-"}`,
|
|
68
|
+
];
|
|
69
|
+
if (session.phase) lines.push(` phase: ${session.phase}`);
|
|
70
|
+
if (Number.isFinite(Number(session.turnCount))) {
|
|
71
|
+
lines.push(` turns: ${session.turnCount}`);
|
|
72
|
+
}
|
|
73
|
+
if (session.transport?.pipe) {
|
|
74
|
+
lines.push(` transport: interactive attach available`);
|
|
75
|
+
}
|
|
76
|
+
if (session.lostReason) lines.push(` lostReason: ${session.lostReason}`);
|
|
77
|
+
if (Number.isFinite(Number(session.exitCode))) {
|
|
78
|
+
lines.push(` exitCode: ${session.exitCode}`);
|
|
79
|
+
}
|
|
80
|
+
lines.push("", "Recent output:");
|
|
81
|
+
lines.push(logText ? logText.trimEnd() : " (no log output)");
|
|
82
|
+
lines.push("", "Commands:");
|
|
83
|
+
lines.push(` cc attach ${session.id}`);
|
|
84
|
+
lines.push(` cc logs ${session.id} -n 100`);
|
|
85
|
+
if (session.status === "running") {
|
|
86
|
+
lines.push(` cc daemon stop ${session.id}`);
|
|
87
|
+
}
|
|
88
|
+
return lines.join("\n");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function parseLines(value, fallback) {
|
|
92
|
+
const n = Number(value);
|
|
93
|
+
return Number.isFinite(n) && n > 0 ? Math.floor(n) : fallback;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function loadSupervisor() {
|
|
97
|
+
return import("../lib/background-agent-supervisor.js");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function readLogFromOffset(file, offset) {
|
|
101
|
+
if (!existsSync(file)) return { text: "", offset };
|
|
102
|
+
const text = readFileSync(file, "utf8");
|
|
103
|
+
if (offset > text.length) offset = 0;
|
|
104
|
+
return { text: text.slice(offset), offset: text.length };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function followBackgroundAgent(id, options = {}) {
|
|
108
|
+
const {
|
|
109
|
+
readBackgroundAgentState,
|
|
110
|
+
effectiveBackgroundAgentState,
|
|
111
|
+
readBackgroundAgentLog,
|
|
112
|
+
logPath,
|
|
113
|
+
} = await loadSupervisor();
|
|
114
|
+
const lines = parseLines(options.lines, 100);
|
|
115
|
+
let state = effectiveBackgroundAgentState(readBackgroundAgentState(id));
|
|
116
|
+
if (!state) throw new Error(`Background agent not found: ${id}`);
|
|
117
|
+
const logFile = logPath(id);
|
|
118
|
+
const initial = readBackgroundAgentLog(id, { lines });
|
|
119
|
+
if (initial)
|
|
120
|
+
process.stdout.write(initial.endsWith("\n") ? initial : `${initial}\n`);
|
|
121
|
+
let offset = existsSync(logFile) ? readFileSync(logFile, "utf8").length : 0;
|
|
122
|
+
|
|
123
|
+
if (options.follow === false || state.status !== "running") {
|
|
124
|
+
return state;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
logger.log(chalk.gray(`Attached to ${id}. Streaming logs; Ctrl-C detaches.`));
|
|
128
|
+
while (true) {
|
|
129
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
130
|
+
const next = readLogFromOffset(logFile, offset);
|
|
131
|
+
offset = next.offset;
|
|
132
|
+
if (next.text) process.stdout.write(next.text);
|
|
133
|
+
state = effectiveBackgroundAgentState(readBackgroundAgentState(id));
|
|
134
|
+
if (!state || state.status !== "running") {
|
|
135
|
+
if (state) {
|
|
136
|
+
logger.log(chalk.gray(`\n${id} is ${state.status}.`));
|
|
137
|
+
}
|
|
138
|
+
return state;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Interactive attach over the session transport: stream the log AND accept
|
|
145
|
+
* typed follow-up prompts (plus /stop · /status · /detach). Returns false
|
|
146
|
+
* when the transport is unavailable so the caller can fall back to the
|
|
147
|
+
* log-only follow.
|
|
148
|
+
*/
|
|
149
|
+
async function interactiveAttach(id, state, options = {}) {
|
|
150
|
+
const { connectBackgroundSession } =
|
|
151
|
+
await import("../lib/background-session-transport.js");
|
|
152
|
+
const { readBackgroundAgentLog, logPath } = await loadSupervisor();
|
|
153
|
+
|
|
154
|
+
let closed = false;
|
|
155
|
+
let conn;
|
|
156
|
+
try {
|
|
157
|
+
conn = await connectBackgroundSession({
|
|
158
|
+
pipePath: state.transport.pipe,
|
|
159
|
+
token: state.transport.token,
|
|
160
|
+
onEvent: (message) => {
|
|
161
|
+
switch (message.type) {
|
|
162
|
+
case "turn-started":
|
|
163
|
+
logger.log(
|
|
164
|
+
chalk.gray(
|
|
165
|
+
`— turn ${message.turn} started${message.prompt ? `: ${message.prompt}` : ""}`,
|
|
166
|
+
),
|
|
167
|
+
);
|
|
168
|
+
return;
|
|
169
|
+
case "turn-ended":
|
|
170
|
+
logger.log(
|
|
171
|
+
chalk.gray(
|
|
172
|
+
`— turn ${message.turn} ended (exit ${message.exitCode})`,
|
|
173
|
+
),
|
|
174
|
+
);
|
|
175
|
+
return;
|
|
176
|
+
case "idle":
|
|
177
|
+
logger.log(
|
|
178
|
+
chalk.gray(
|
|
179
|
+
"— session idle; type a follow-up prompt, or Ctrl-C to detach (ends the session)",
|
|
180
|
+
),
|
|
181
|
+
);
|
|
182
|
+
return;
|
|
183
|
+
case "accepted":
|
|
184
|
+
logger.log(chalk.gray(`— prompt queued (#${message.queued})`));
|
|
185
|
+
return;
|
|
186
|
+
case "status":
|
|
187
|
+
logger.log(
|
|
188
|
+
chalk.gray(
|
|
189
|
+
`— ${message.id} phase=${message.phase} turn=${message.turn}`,
|
|
190
|
+
),
|
|
191
|
+
);
|
|
192
|
+
return;
|
|
193
|
+
case "stopping":
|
|
194
|
+
logger.log(chalk.gray("— stopping current turn"));
|
|
195
|
+
return;
|
|
196
|
+
case "closing":
|
|
197
|
+
return; // close handler prints the final state
|
|
198
|
+
case "error":
|
|
199
|
+
logger.log(chalk.yellow(`— ${message.message}`));
|
|
200
|
+
return;
|
|
201
|
+
default:
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
onClose: () => {
|
|
206
|
+
closed = true;
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
} catch {
|
|
210
|
+
return false; // transport gone (worker finalizing/finalized) — fall back
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const lines = parseLines(options.lines, 100);
|
|
214
|
+
const initial = readBackgroundAgentLog(id, { lines });
|
|
215
|
+
if (initial)
|
|
216
|
+
process.stdout.write(initial.endsWith("\n") ? initial : `${initial}\n`);
|
|
217
|
+
const logFile = logPath(id);
|
|
218
|
+
let offset = existsSync(logFile) ? readFileSync(logFile, "utf8").length : 0;
|
|
219
|
+
logger.log(
|
|
220
|
+
chalk.gray(
|
|
221
|
+
`Attached to ${id} (interactive). Type a prompt to continue the session; /stop · /status · Ctrl-C detaches.`,
|
|
222
|
+
),
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
const readline = await import("node:readline");
|
|
226
|
+
const rl = readline.createInterface({
|
|
227
|
+
input: process.stdin,
|
|
228
|
+
output: process.stdout,
|
|
229
|
+
});
|
|
230
|
+
rl.on("line", (line) => {
|
|
231
|
+
const text = line.trim();
|
|
232
|
+
if (!text) return;
|
|
233
|
+
if (text === "/detach" || text === "/exit" || text === "/quit") {
|
|
234
|
+
rl.close();
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if (text === "/stop") {
|
|
238
|
+
conn.send({ type: "stop" });
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (text === "/status") {
|
|
242
|
+
conn.send({ type: "status" });
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
conn.send({ type: "prompt", text });
|
|
246
|
+
});
|
|
247
|
+
const detach = () => rl.close();
|
|
248
|
+
rl.on("SIGINT", detach);
|
|
249
|
+
|
|
250
|
+
await new Promise((resolve) => {
|
|
251
|
+
rl.on("close", resolve);
|
|
252
|
+
const poll = setInterval(() => {
|
|
253
|
+
const next = readLogFromOffset(logFile, offset);
|
|
254
|
+
offset = next.offset;
|
|
255
|
+
if (next.text) process.stdout.write(next.text);
|
|
256
|
+
if (closed) {
|
|
257
|
+
clearInterval(poll);
|
|
258
|
+
rl.close();
|
|
259
|
+
}
|
|
260
|
+
}, 500);
|
|
261
|
+
poll.unref?.();
|
|
262
|
+
rl.on("close", () => clearInterval(poll));
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// Drain any output written between the last poll and detach.
|
|
266
|
+
const rest = readLogFromOffset(logFile, offset);
|
|
267
|
+
if (rest.text) process.stdout.write(rest.text);
|
|
268
|
+
try {
|
|
269
|
+
conn.close();
|
|
270
|
+
} catch {
|
|
271
|
+
/* already closed by the worker */
|
|
272
|
+
}
|
|
273
|
+
const { readBackgroundAgentState, effectiveBackgroundAgentState } =
|
|
274
|
+
await loadSupervisor();
|
|
275
|
+
const finalState = effectiveBackgroundAgentState(
|
|
276
|
+
readBackgroundAgentState(id),
|
|
277
|
+
);
|
|
278
|
+
if (finalState) logger.log(chalk.gray(`\n${id} is ${finalState.status}.`));
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export function registerBackgroundSessionCommands(program) {
|
|
283
|
+
program
|
|
284
|
+
.command("logs <id>")
|
|
285
|
+
.description("Print recent output from a background agent")
|
|
286
|
+
.option("-n, --lines <n>", "Number of lines", "100")
|
|
287
|
+
.action(async (id, options) => {
|
|
288
|
+
try {
|
|
289
|
+
const { readBackgroundAgentState, readBackgroundAgentLog } =
|
|
290
|
+
await loadSupervisor();
|
|
291
|
+
if (!readBackgroundAgentState(id)) {
|
|
292
|
+
throw new Error(`Background agent not found: ${id}`);
|
|
293
|
+
}
|
|
294
|
+
process.stdout.write(
|
|
295
|
+
readBackgroundAgentLog(id, { lines: parseLines(options.lines, 100) }),
|
|
296
|
+
);
|
|
297
|
+
} catch (error) {
|
|
298
|
+
logger.error(chalk.red(error.message));
|
|
299
|
+
process.exitCode = 1;
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
program
|
|
304
|
+
.command("attach <id>")
|
|
305
|
+
.description(
|
|
306
|
+
"Attach to a background agent — interactive when its session transport is available, log stream otherwise",
|
|
307
|
+
)
|
|
308
|
+
.option("-n, --lines <n>", "Initial lines to print", "100")
|
|
309
|
+
.option("--no-follow", "Print the initial log tail and exit")
|
|
310
|
+
.option("--no-input", "Log stream only; never send prompts")
|
|
311
|
+
.action(async (id, options) => {
|
|
312
|
+
try {
|
|
313
|
+
const { readBackgroundAgentState, effectiveBackgroundAgentState } =
|
|
314
|
+
await loadSupervisor();
|
|
315
|
+
const state = effectiveBackgroundAgentState(
|
|
316
|
+
readBackgroundAgentState(id),
|
|
317
|
+
);
|
|
318
|
+
if (!state) throw new Error(`Background agent not found: ${id}`);
|
|
319
|
+
const wantsInteractive =
|
|
320
|
+
options.input !== false &&
|
|
321
|
+
options.follow !== false &&
|
|
322
|
+
state.status === "running" &&
|
|
323
|
+
state.transport?.pipe &&
|
|
324
|
+
state.transport?.token &&
|
|
325
|
+
process.stdin.isTTY;
|
|
326
|
+
if (wantsInteractive) {
|
|
327
|
+
const attached = await interactiveAttach(id, state, options);
|
|
328
|
+
if (attached) return;
|
|
329
|
+
// transport refused (worker mid-finalize) — fall back to logs
|
|
330
|
+
}
|
|
331
|
+
await followBackgroundAgent(id, options);
|
|
332
|
+
} catch (error) {
|
|
333
|
+
logger.error(chalk.red(error.message));
|
|
334
|
+
process.exitCode = 1;
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
const daemon = program
|
|
339
|
+
.command("daemon")
|
|
340
|
+
.description("Inspect and stop background agent supervisor sessions");
|
|
341
|
+
|
|
342
|
+
daemon
|
|
343
|
+
.command("status")
|
|
344
|
+
.description("Show background agent supervisor status")
|
|
345
|
+
.option("--all", "Include completed, failed, stopped and lost sessions")
|
|
346
|
+
.option("--json", "Output as JSON")
|
|
347
|
+
.action(async (options) => {
|
|
348
|
+
try {
|
|
349
|
+
const { backgroundAgentsDir, listBackgroundAgents } =
|
|
350
|
+
await loadSupervisor();
|
|
351
|
+
const sessions = listBackgroundAgents({ all: options.all === true });
|
|
352
|
+
const running = sessions.filter((s) => s.status === "running").length;
|
|
353
|
+
if (options.json) {
|
|
354
|
+
console.log(
|
|
355
|
+
JSON.stringify(
|
|
356
|
+
{
|
|
357
|
+
supervisor: "background-agents",
|
|
358
|
+
dir: backgroundAgentsDir(),
|
|
359
|
+
running,
|
|
360
|
+
total: sessions.length,
|
|
361
|
+
sessions,
|
|
362
|
+
},
|
|
363
|
+
null,
|
|
364
|
+
2,
|
|
365
|
+
),
|
|
366
|
+
);
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
logger.log(
|
|
370
|
+
`${chalk.bold("Background agent supervisor")} ${running} running ${sessions.length} shown`,
|
|
371
|
+
);
|
|
372
|
+
logger.log(chalk.gray(` dir ${backgroundAgentsDir()}`));
|
|
373
|
+
printBackgroundAgents(sessions);
|
|
374
|
+
} catch (error) {
|
|
375
|
+
logger.error(chalk.red(error.message));
|
|
376
|
+
process.exitCode = 1;
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
daemon
|
|
381
|
+
.command("view <id>")
|
|
382
|
+
.description("Show a detailed background agent view")
|
|
383
|
+
.option("-n, --lines <n>", "Recent log lines to include", "40")
|
|
384
|
+
.option("--json", "Output as JSON")
|
|
385
|
+
.action(async (id, options) => {
|
|
386
|
+
try {
|
|
387
|
+
const {
|
|
388
|
+
readBackgroundAgentState,
|
|
389
|
+
effectiveBackgroundAgentState,
|
|
390
|
+
readBackgroundAgentLog,
|
|
391
|
+
} = await loadSupervisor();
|
|
392
|
+
const session = effectiveBackgroundAgentState(
|
|
393
|
+
readBackgroundAgentState(id),
|
|
394
|
+
);
|
|
395
|
+
if (!session) throw new Error(`Background agent not found: ${id}`);
|
|
396
|
+
const lines = parseLines(options.lines, 40);
|
|
397
|
+
const log = readBackgroundAgentLog(id, { lines });
|
|
398
|
+
if (options.json) {
|
|
399
|
+
console.log(JSON.stringify({ session, log }, null, 2));
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
logger.log(formatBackgroundAgentDetails(session, log));
|
|
403
|
+
} catch (error) {
|
|
404
|
+
logger.error(chalk.red(error.message));
|
|
405
|
+
process.exitCode = 1;
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
daemon
|
|
410
|
+
.command("rename <id> <title...>")
|
|
411
|
+
.description("Rename a background agent session")
|
|
412
|
+
.option("--json", "Output as JSON")
|
|
413
|
+
.action(async (id, title, options) => {
|
|
414
|
+
try {
|
|
415
|
+
const { renameBackgroundAgent } = await loadSupervisor();
|
|
416
|
+
const state = renameBackgroundAgent(
|
|
417
|
+
id,
|
|
418
|
+
Array.isArray(title) ? title.join(" ") : title,
|
|
419
|
+
);
|
|
420
|
+
if (options.json) {
|
|
421
|
+
console.log(JSON.stringify(state, null, 2));
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
logger.log(chalk.green(`Renamed background agent ${state.id}`));
|
|
425
|
+
logger.log(chalk.gray(` ${state.title}`));
|
|
426
|
+
} catch (error) {
|
|
427
|
+
logger.error(chalk.red(error.message));
|
|
428
|
+
process.exitCode = 1;
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
daemon
|
|
433
|
+
.command("resume <id> <prompt...>")
|
|
434
|
+
.description(
|
|
435
|
+
"Continue a finished/crashed background session as a new background run on the same conversation",
|
|
436
|
+
)
|
|
437
|
+
.option("--json", "Output as JSON")
|
|
438
|
+
.action(async (id, prompt, options) => {
|
|
439
|
+
try {
|
|
440
|
+
const { resumeBackgroundAgent } = await loadSupervisor();
|
|
441
|
+
const state = resumeBackgroundAgent(
|
|
442
|
+
id,
|
|
443
|
+
Array.isArray(prompt) ? prompt.join(" ") : prompt,
|
|
444
|
+
);
|
|
445
|
+
if (options.json) {
|
|
446
|
+
console.log(JSON.stringify(state, null, 2));
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
logger.log(
|
|
450
|
+
chalk.green(`Resumed session as background agent ${state.id}`),
|
|
451
|
+
);
|
|
452
|
+
logger.log(chalk.gray(` session ${state.sessionId}`));
|
|
453
|
+
logger.log(chalk.gray(` attach: cc attach ${state.id}`));
|
|
454
|
+
} catch (error) {
|
|
455
|
+
logger.error(chalk.red(error.message));
|
|
456
|
+
process.exitCode = 1;
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
daemon
|
|
461
|
+
.command("stop [id]")
|
|
462
|
+
.description("Stop one background agent, or all running agents with --any")
|
|
463
|
+
.option("--any", "Stop all running background agents")
|
|
464
|
+
.option("--json", "Output as JSON")
|
|
465
|
+
.action(async (id, options) => {
|
|
466
|
+
try {
|
|
467
|
+
const { listBackgroundAgents, stopBackgroundAgent } =
|
|
468
|
+
await loadSupervisor();
|
|
469
|
+
if (!id && options.any !== true) {
|
|
470
|
+
throw new Error("daemon stop requires <id> or --any");
|
|
471
|
+
}
|
|
472
|
+
const targets = options.any
|
|
473
|
+
? listBackgroundAgents().map((s) => s.id)
|
|
474
|
+
: [id];
|
|
475
|
+
const results = [];
|
|
476
|
+
for (const target of targets) {
|
|
477
|
+
results.push(stopBackgroundAgent(target));
|
|
478
|
+
}
|
|
479
|
+
if (options.json) {
|
|
480
|
+
console.log(JSON.stringify(results, null, 2));
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
if (results.length === 0) {
|
|
484
|
+
logger.log(chalk.gray("No running background agents."));
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
for (const result of results) {
|
|
488
|
+
if (result.stopped) {
|
|
489
|
+
logger.log(chalk.green(`Stopped background agent ${result.id}`));
|
|
490
|
+
} else {
|
|
491
|
+
logger.log(chalk.gray(`${result.id} is already ${result.status}`));
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
} catch (error) {
|
|
495
|
+
logger.error(chalk.red(error.message));
|
|
496
|
+
process.exitCode = 1;
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
}
|
package/src/commands/command.js
CHANGED
|
@@ -103,7 +103,10 @@ export function registerCommandCommand(program) {
|
|
|
103
103
|
.option("--no-bang", "Do not execute !`cmd` bang segments")
|
|
104
104
|
.option("--output-format <fmt>", "text | json | stream-json", "text")
|
|
105
105
|
.option("--model <model>", "Override the model")
|
|
106
|
-
.option(
|
|
106
|
+
.option(
|
|
107
|
+
"--permission-mode <mode>",
|
|
108
|
+
"manual | auto | dontAsk | default | plan | acceptEdits | bypassPermissions",
|
|
109
|
+
)
|
|
107
110
|
.action(async (name, args, options) => {
|
|
108
111
|
try {
|
|
109
112
|
const { getCommand, expandCommand } =
|
|
@@ -136,6 +136,55 @@ export function registerPermissionsCommand(program) {
|
|
|
136
136
|
}
|
|
137
137
|
});
|
|
138
138
|
|
|
139
|
+
// ── recent ────────────────────────────────────────────────────────────
|
|
140
|
+
cmd
|
|
141
|
+
.command("recent")
|
|
142
|
+
.alias("denials")
|
|
143
|
+
.description("Show recent policy denials recorded by agent runs")
|
|
144
|
+
.option("-n, --limit <n>", "Number of denials to show", "20")
|
|
145
|
+
.option("--json", "Output as JSON")
|
|
146
|
+
.option("--clear", "Clear the recent denial log")
|
|
147
|
+
.action(async (options) => {
|
|
148
|
+
try {
|
|
149
|
+
const {
|
|
150
|
+
clearRecentDenials,
|
|
151
|
+
formatRecentDenials,
|
|
152
|
+
readRecentDenials,
|
|
153
|
+
recentDenialsPath,
|
|
154
|
+
} = await import("../lib/permission-denial-store.js");
|
|
155
|
+
if (options.clear) {
|
|
156
|
+
const result = clearRecentDenials();
|
|
157
|
+
if (options.json) {
|
|
158
|
+
console.log(JSON.stringify(result, null, 2));
|
|
159
|
+
} else {
|
|
160
|
+
logger.log(chalk.green(`Cleared ${result.file}`));
|
|
161
|
+
}
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const limit = Math.max(1, Number(options.limit) || 20);
|
|
165
|
+
const denials = readRecentDenials({ limit });
|
|
166
|
+
if (options.json) {
|
|
167
|
+
console.log(
|
|
168
|
+
JSON.stringify(
|
|
169
|
+
{
|
|
170
|
+
file: recentDenialsPath(),
|
|
171
|
+
count: denials.length,
|
|
172
|
+
denials,
|
|
173
|
+
},
|
|
174
|
+
null,
|
|
175
|
+
2,
|
|
176
|
+
),
|
|
177
|
+
);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
logger.log(formatRecentDenials(denials));
|
|
181
|
+
logger.log(chalk.gray(` source: ${recentDenialsPath()}`));
|
|
182
|
+
} catch (err) {
|
|
183
|
+
logger.error(chalk.red(`permissions recent failed: ${err.message}`));
|
|
184
|
+
process.exitCode = 1;
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
|
|
139
188
|
// ── test (dry-run) ─────────────────────────────────────────────────────
|
|
140
189
|
cmd
|
|
141
190
|
.command("test <tool> [args...]")
|