chainlesschain 0.162.155 → 0.162.157
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 +2 -2
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-Cth_zZeA.js → AIOps-CfMP2Oud.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-zfwA3Oxn.js → ActionButton-Zu0N8BQL.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-FeKc4aU2.js → Analytics-DZTwj5T_.js} +3 -3
- package/src/assets/web-panel/assets/AppLayout-CVFJh47v.js +9 -0
- package/src/assets/web-panel/assets/Artifacts-BbN5V7au.js +1 -0
- package/src/assets/web-panel/assets/Artifacts-CRkepl4t.css +1 -0
- package/src/assets/web-panel/assets/{Audit-C2gRq5Qi.js → Audit-BMfiLj7T.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-TeTIFiW-.js → BackgroundAgents-BslPCdGF.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-CLJk3k6i.js → Backup-CQ_fMC1r.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-5ms537NY.js → BaseInput-CsFsARiC.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-4Nr5U9PJ.js → Chat-CwfBobco.js} +6 -6
- package/src/assets/web-panel/assets/ChatBubbleRenderer-K6Nf3xeN.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-B2Bca5ev.js → Checkbox-DkSyjjPp.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-D6Uysdbv.js → Codegen-B7Fx3zZ6.js} +1 -1
- package/src/assets/web-panel/assets/{Col-B3XQ8xc7.js → Col-Dzfqn8Ep.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CqwKtoPz.js → Community-DhHPdzMM.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BZZINTus.js → Compact-Ct18_8y7.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-Dr3BXkWA.js → Compliance-CY64IqBi.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-rx_x9Wqc.js → Cowork-BYSmWlUl.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-BpZr7rHc.js → Cron-_4xtP3Ug.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-CeV5EYVg.js → Crosschain-B4qY-Ycr.js} +1 -1
- package/src/assets/web-panel/assets/{DID-fe2Qaowz.js → DID-CkCzWkx1.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-Fh_EdWVa.js → Dashboard-C1scT7Ke.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-N1PIwWYf.js → Dropdown-DBnQ2EWd.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-B6vK93Ed.js → EmailListRenderer-C9JhOTq0.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-e3iUZhAg.js → FamilyGuardDashboard-CWXv5RUv.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-uInc9YcN.js → Federation-fYdcbz9W.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-DmQZSX0z.js → FormItemContext-CJqOT3Yj.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-CV_Z8OAo.js → GenericCardRenderer-DGCsHc5K.js} +1 -1
- package/src/assets/web-panel/assets/{Git-jXAhNN8d.js → Git-EH2K-H0Q.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-Blw1esBB.js → Governance-DJn1X2pR.js} +1 -1
- package/src/assets/web-panel/assets/Inference-Dch1E8qd.js +1 -0
- package/src/assets/web-panel/assets/{KnowledgeGraph-5xrglMTK.js → KnowledgeGraph-QIur8j8-.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BGIG9798.js → Logs-DUgHldPg.js} +2 -2
- package/src/assets/web-panel/assets/Marketplace-DafrSfvd.js +1 -0
- package/src/assets/web-panel/assets/{McpTools-7U8tEhwK.js → McpTools-D2xRbfJx.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-BrvboPLE.js → Memory-C6RbVHAo.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-nzcd7d1H.js → MobileBridge-BPtVBKqn.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-DETVNKa7.js → MobileProjects-Cuw832cr.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-SZcCYec3.js → Mtc-D6O9fWFa.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-Cx_d3N17.js → MtcAudit-CWZcXIH1.js} +5 -5
- package/src/assets/web-panel/assets/{Multisig-DtcmSUo-.js → Multisig-DhIBxTFd.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-Dgx2QHl-.js → NLProgramming-wkBFS33e.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-oHTGEHoJ.js → Notes-B09FxncA.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-qKD-IlOl.js → NotificationSettings-oQwtH6-9.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-CiU4Sy1J.js → OrderTableRenderer-iFKeNf6Y.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-BBNXnltP.js → Organization-CsF-NIjz.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-Df2fDbvT.js → Overflow-bLxv9Dqi.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-DUHj6zjO.js → P2P-DVwQtm4q.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-B2nWgdoV.js → PdhVaultBrowser-Bgic3QKm.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-CiW0afny.js → Permissions-as80-9ep.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-cc1mHU_D.js → PersonalDataHub-BGmUw36a.js} +2 -2
- package/src/assets/web-panel/assets/{Pipeline-B93zTapU.js → Pipeline-BM96zBxG.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-Db1jXWfd.js → Privacy-BBOzBSWe.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-DFoZ1h9U.js → ProjectInit-GHX41V28.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-BkkUnmYs.js → ProjectSettings-DraqzxKD.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-Xa779XLC.js → Projects-BzzdUml4.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-DOF01IEN.js → Providers-CSeYNAtW.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-TohRsKd0.js → QrScannerModal-DlzBCdXh.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-DjLcr1ZU.js → QuickAsk-p8iB7od_.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend--Ism6bMx.js → Recommend-DjOVXacd.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-B8CL3YHM.js → RemoteSession-CIGk7gk-.js} +2 -2
- package/src/assets/web-panel/assets/Reputation-CjT3M0Di.js +1 -0
- package/src/assets/web-panel/assets/{Row-BioxZBBH.js → Row-tw8m6T0c.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-B2LipwH3.js → RssFeed-B47KBx_f.js} +2 -2
- package/src/assets/web-panel/assets/Search-D2sx1PBe.js +1 -0
- package/src/assets/web-panel/assets/{Security-TibHuJWP.js → Security-JmtL8g7D.js} +3 -3
- package/src/assets/web-panel/assets/{Services-DtkuWseW.js → Services-BxrNqjin.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-BIcPQae6.js → Skeleton-udk8L18t.js} +1 -1
- package/src/assets/web-panel/assets/Skills-DooLT9of.js +1 -0
- package/src/assets/web-panel/assets/{Sla-uUSU185m.js → Sla-ITVZE1xI.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-B7Bgc9mk.js → SpeechSettings-DCkfqY1n.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-K-XrJ2s9.js → SyncSettings-CkNlgFjM.js} +2 -2
- package/src/assets/web-panel/assets/Tasks-BMAoyCM_.js +1 -0
- package/src/assets/web-panel/assets/{Templates-CaFnyGDK.js → Templates-BnrFTlrz.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-C2X0PtEC.js → Tenant-BjNsy3-i.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-CKU5yVsT.js → Terminal-C0gxLL7V.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-8trZeYDC.js → TimelineRenderer-CUtj_KHA.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BUEEe9US.js → Tokens-DOg61zlq.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-COU97I7Y.js → Trigger-D4ldCewj.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DNWQChaP.js → Trust-3lk_Zd8j.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-DWX_VtCp.js → UkeySign-kWhFR6Li.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-D9eIDhsp.js → VideoEditing-D3GOFVWg.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-BOnCCgIL.js → Wallet-DzrZfBuh.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-DN4dD-W8.js → WebAuthn-CyVJVp_g.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-ClvDb_jX.js → WorkflowEditor-i_b0wi9b.js} +1 -1
- package/src/assets/web-panel/assets/{chat-DIxQ_Eeb.js → chat-DwPCK8I7.js} +1 -1
- package/src/assets/web-panel/assets/{colors-BOX3WKcP.js → colors-D0vaVqZU.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-Bny82VDi.js → compact-item-Don7nGW2.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-Cnoy3enS.js → createContext-D84R5qy2.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-BBwWehQ7.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-BJ7fkx3F.js → hasIn-BeUqVVKU.js} +1 -1
- package/src/assets/web-panel/assets/{icons-DEmcnxBf.js → icons-DG5UD-6K.js} +4 -4
- package/src/assets/web-panel/assets/{index-CZ6CHiCQ.js → index-4MkfPLGv.js} +1 -1
- package/src/assets/web-panel/assets/{index-7GLXvG8p.js → index-7QUwcGTg.js} +1 -1
- package/src/assets/web-panel/assets/{index-C1nHEOFE.js → index-B57al0Z5.js} +1 -1
- package/src/assets/web-panel/assets/{index-Kio1ekYg.js → index-BER98o4P.js} +1 -1
- package/src/assets/web-panel/assets/{index-D9QEc7Tq.js → index-BNpJf-sf.js} +1 -1
- package/src/assets/web-panel/assets/{index--CcGiwAI.js → index-BZ1N07Sp.js} +1 -1
- package/src/assets/web-panel/assets/{index-BWPey0Uf.js → index-BlfTf0ka.js} +1 -1
- package/src/assets/web-panel/assets/{index-B7Fah_BX.js → index-BohrJpBf.js} +1 -1
- package/src/assets/web-panel/assets/{index-BJ8-oLHI.js → index-Br3SOdER.js} +1 -1
- package/src/assets/web-panel/assets/{index-D2qNdrNW.js → index-C0_sKVi7.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQ-xLCVb.js → index-CAdCTTBC.js} +1 -1
- package/src/assets/web-panel/assets/{index-DSRIjAWO.js → index-CExWtm98.js} +1 -1
- package/src/assets/web-panel/assets/{index--Xb2lq6E.js → index-CL71rIwo.js} +1 -1
- package/src/assets/web-panel/assets/{index-BDkN6YOI.js → index-CfYRv-zA.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bk5yST66.js → index-CnnxzIx0.js} +1 -1
- package/src/assets/web-panel/assets/{index-2N9Y1cLx.js → index-CrnVEEQO.js} +1 -1
- package/src/assets/web-panel/assets/{index-CW6n-y1L.js → index-CzT56x-_.js} +1 -1
- package/src/assets/web-panel/assets/{index-OaNFXV8N.js → index-D4b5XqIy.js} +1 -1
- package/src/assets/web-panel/assets/{index-CEPTiBWy.js → index-D84BnuFg.js} +1 -1
- package/src/assets/web-panel/assets/{index-CZCTan8Q.js → index-D8r7WKk-.js} +1 -1
- package/src/assets/web-panel/assets/{index-rn6xd12s.js → index-DA0-EJKx.js} +1 -1
- package/src/assets/web-panel/assets/{index-BcmXbIwv.js → index-DBzBLX-K.js} +3 -3
- package/src/assets/web-panel/assets/{index-C--PPqwi.js → index-DFFzwrNs.js} +1 -1
- package/src/assets/web-panel/assets/index-DJ8etkBT.js +1 -0
- package/src/assets/web-panel/assets/{index-Ty4w18mR.js → index-DMT8n9vB.js} +4 -4
- package/src/assets/web-panel/assets/{index-qOBKrw-g.js → index-DQor_7NF.js} +1 -1
- package/src/assets/web-panel/assets/{index-CN6emwbq.js → index-DT40zd7K.js} +1 -1
- package/src/assets/web-panel/assets/{index-BM0mQhVv.js → index-De85D9Pc.js} +1 -1
- package/src/assets/web-panel/assets/{index-ceH9DsAi.js → index-IGGttsCv.js} +1 -1
- package/src/assets/web-panel/assets/{index-DBhkJqVF.js → index-L73NrmlW.js} +1 -1
- package/src/assets/web-panel/assets/{index-BbZynivw.js → index-LMo9bBLJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bvheiep2.js → index-MKZrJrP_.js} +1 -1
- package/src/assets/web-panel/assets/index-OeaK-zXZ.js +1 -0
- package/src/assets/web-panel/assets/{index-DxX8hhmL.js → index-WRw1pM5E.js} +1 -1
- package/src/assets/web-panel/assets/{index-B6WoW8DJ.js → index-YKLCotoo.js} +1 -1
- package/src/assets/web-panel/assets/{index-BP7HFsAK.js → index-hjgrIX5I.js} +1 -1
- package/src/assets/web-panel/assets/{index-CnjtAKdw.js → index-m9bn0F1p.js} +1 -1
- package/src/assets/web-panel/assets/{index-CEq5Vh0i.js → index-wesQQ2cH.js} +1 -1
- package/src/assets/web-panel/assets/{index-B1-h2QrC.js → index-x7VYlV41.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-BmxMBuUZ.js → initDefaultProps-DGeU4vPS.js} +1 -1
- package/src/assets/web-panel/assets/{motion-DnXNwo2k.js → motion-Dgqj9ab3.js} +1 -1
- package/src/assets/web-panel/assets/{move-WnIJwmZN.js → move-BFyEU6Lr.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-DAto-c2w.js → mtc-parser-CueCXXPC.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BtvW9W0H.js → omit-D2pU5OyE.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DFqI9-_C.js → pickAttrs-CKTf6zD6.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-Dax6wOK3.js → placementArrow-DieJaGCN.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-BUFWWC83.js → responsiveObserve-Bxr-rmU6.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DyARyuSz.js → slide-CDEDk3Se.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BZSDugpi.js → statusUtils-icf2eCTx.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-B7o_K-4R.js → styleChecker-qKvgmBhE.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-Dg7Bg8VG.js → useFlexGapSupport-1_UhiyJZ.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-DQYXo5Rk.js → useFs-CFGLXDWh.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-E8NBAnkq.js → usePersonalDataHub-UafOXvQ6.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-DD71_w1b.js → vnode-BfPqSx79.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-zc9EMd3q.js → zoom-CXsVcRB6.js} +1 -1
- package/src/assets/web-panel/index.html +2 -2
- package/src/command-manifest.json +22 -1
- package/src/commands/agenda.js +228 -0
- package/src/commands/agent.js +45 -4
- package/src/commands/artifacts.js +161 -0
- package/src/commands/batch.js +345 -0
- package/src/commands/browse.js +204 -2
- package/src/data/changelog.json +13 -2
- package/src/gateways/ws/artifact-protocol.js +219 -0
- package/src/gateways/ws/message-dispatcher.js +6 -0
- package/src/gateways/ws/ws-server.js +32 -0
- package/src/index.js +18 -0
- package/src/lib/accessibility.js +55 -0
- package/src/lib/agent-batch.js +235 -0
- package/src/lib/agent-notify.js +66 -0
- package/src/lib/agent-schedule-store.js +330 -0
- package/src/lib/artifact-store.js +236 -0
- package/src/lib/chrome-connector.js +314 -0
- package/src/lib/hook-runner.cjs +47 -7
- package/src/lib/ide-context.js +84 -0
- package/src/lib/permission-rules.cjs +26 -3
- package/src/lib/plugin-runtime/scopes.js +12 -0
- package/src/lib/safe-mode.js +40 -0
- package/src/lib/shell-selector.cjs +203 -0
- package/src/lib/skill-loader.js +19 -0
- package/src/lib/slash-dispatch.js +34 -0
- package/src/repl/agent-repl.js +99 -58
- package/src/runtime/agent-core.js +197 -8
- package/src/runtime/coding-agent-contract-shared.cjs +156 -0
- package/src/runtime/coding-agent-policy.cjs +43 -0
- package/src/runtime/headless-stream.js +51 -1
- package/src/runtime/policies/agent-policy.js +1 -0
- package/src/assets/web-panel/assets/AppLayout-C6WIrDZk.js +0 -9
- package/src/assets/web-panel/assets/ChatBubbleRenderer-BFEnA8LN.js +0 -1
- package/src/assets/web-panel/assets/Inference-CoK8Ia0Z.js +0 -1
- package/src/assets/web-panel/assets/Marketplace-DFqhJMuY.js +0 -1
- package/src/assets/web-panel/assets/Reputation-CaRxB3Ep.js +0 -1
- package/src/assets/web-panel/assets/Search-BpjLBV8u.js +0 -1
- package/src/assets/web-panel/assets/Skills-DoTWIQOX.js +0 -1
- package/src/assets/web-panel/assets/Tasks-B3hEXNcI.js +0 -1
- package/src/assets/web-panel/assets/devWarning-Dr9dzlQ5.js +0 -1
- package/src/assets/web-panel/assets/index-B92M3t6B.js +0 -1
- package/src/assets/web-panel/assets/index-d2UcGpK-.js +0 -1
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* artifact WS protocol — exposes the agent-published deliverable store
|
|
3
|
+
* (lib/artifact-store.js, P1 #10) to web-panel / IDE clients so an
|
|
4
|
+
* "Artifacts" panel can list deliverables, preview them (text inline /
|
|
5
|
+
* images as base64), and manage retention.
|
|
6
|
+
*
|
|
7
|
+
* Routes (request → reply):
|
|
8
|
+
* artifact-list {session?, kind?} → {type:"artifact-list", artifacts}
|
|
9
|
+
* artifact-show {artifactId} → {type:"artifact-show", artifact}
|
|
10
|
+
* artifact-content {artifactId, maxBytes?} → {type:"artifact-content", artifactId,
|
|
11
|
+
* mime, encoding, content, size,
|
|
12
|
+
* truncated, previewable}
|
|
13
|
+
* artifact-remove {artifactId} → {type:"artifact-remove", removed, found}
|
|
14
|
+
* artifact-clean {} → {type:"artifact-clean", removed}
|
|
15
|
+
*
|
|
16
|
+
* Preview policy: text mimes stream as utf8 (capped, `truncated` flagged);
|
|
17
|
+
* images as base64 (hard cap — an oversized image reports previewable:false
|
|
18
|
+
* instead of flooding the socket); everything else is metadata-only
|
|
19
|
+
* (previewable:false, the user has `cc artifacts open <id>` locally).
|
|
20
|
+
*
|
|
21
|
+
* The stored-copy filename comes from index.jsonl; serving is refused if it
|
|
22
|
+
* is not a plain basename, so a hand-tampered index row can never turn the
|
|
23
|
+
* content route into an arbitrary-file read.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import path from "node:path";
|
|
27
|
+
import { readFileSync, statSync } from "node:fs";
|
|
28
|
+
|
|
29
|
+
/** Text preview cap (utf8 chars ≈ bytes for the common case). */
|
|
30
|
+
export const TEXT_PREVIEW_CAP = 256 * 1024;
|
|
31
|
+
/** Image preview cap — beyond this the panel gets metadata only. */
|
|
32
|
+
export const IMAGE_PREVIEW_CAP = 8 * 1024 * 1024;
|
|
33
|
+
|
|
34
|
+
const TEXT_APPLICATION_MIMES = new Set([
|
|
35
|
+
"application/json",
|
|
36
|
+
"application/yaml",
|
|
37
|
+
"application/xml",
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
async function loadStore() {
|
|
41
|
+
const { ArtifactStore } = await import("../../lib/artifact-store.js");
|
|
42
|
+
return new ArtifactStore();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function sendError(server, id, ws, code, message) {
|
|
46
|
+
server._send(ws, { id, type: "error", code, message });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Classify how (whether) an artifact can be previewed inline. */
|
|
50
|
+
export function previewKindForMime(mime) {
|
|
51
|
+
const m = String(mime || "").toLowerCase();
|
|
52
|
+
if (m.startsWith("text/") || TEXT_APPLICATION_MIMES.has(m)) return "text";
|
|
53
|
+
if (m.startsWith("image/")) return "image";
|
|
54
|
+
return "none";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function handleArtifactList(server, id, ws, message) {
|
|
58
|
+
try {
|
|
59
|
+
const store = await loadStore();
|
|
60
|
+
let artifacts = store.list({ sessionId: message.session || undefined });
|
|
61
|
+
if (message.kind) {
|
|
62
|
+
artifacts = artifacts.filter((e) => e.kind === String(message.kind));
|
|
63
|
+
}
|
|
64
|
+
server._send(ws, { id, type: "artifact-list", artifacts });
|
|
65
|
+
} catch (err) {
|
|
66
|
+
sendError(server, id, ws, "ARTIFACT_LIST_FAILED", err.message);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function handleArtifactShow(server, id, ws, message) {
|
|
71
|
+
try {
|
|
72
|
+
if (!message.artifactId) {
|
|
73
|
+
return sendError(server, id, ws, "NO_ARTIFACT_ID", "artifactId required");
|
|
74
|
+
}
|
|
75
|
+
const store = await loadStore();
|
|
76
|
+
const entry = store.get(message.artifactId);
|
|
77
|
+
if (!entry) {
|
|
78
|
+
return sendError(
|
|
79
|
+
server,
|
|
80
|
+
id,
|
|
81
|
+
ws,
|
|
82
|
+
"ARTIFACT_NOT_FOUND",
|
|
83
|
+
`Artifact not found: ${message.artifactId}`,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
server._send(ws, {
|
|
87
|
+
id,
|
|
88
|
+
type: "artifact-show",
|
|
89
|
+
artifact: { ...entry, storedPath: store.storedPath(entry) },
|
|
90
|
+
});
|
|
91
|
+
} catch (err) {
|
|
92
|
+
sendError(server, id, ws, "ARTIFACT_SHOW_FAILED", err.message);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export async function handleArtifactContent(server, id, ws, message) {
|
|
97
|
+
try {
|
|
98
|
+
if (!message.artifactId) {
|
|
99
|
+
return sendError(server, id, ws, "NO_ARTIFACT_ID", "artifactId required");
|
|
100
|
+
}
|
|
101
|
+
const store = await loadStore();
|
|
102
|
+
const entry = store.get(message.artifactId);
|
|
103
|
+
if (!entry) {
|
|
104
|
+
return sendError(
|
|
105
|
+
server,
|
|
106
|
+
id,
|
|
107
|
+
ws,
|
|
108
|
+
"ARTIFACT_NOT_FOUND",
|
|
109
|
+
`Artifact not found: ${message.artifactId}`,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
// Traversal guard: the stored filename must be a plain basename. A
|
|
113
|
+
// tampered index row ("file":"../../secret") must not read outside the
|
|
114
|
+
// files dir.
|
|
115
|
+
if (
|
|
116
|
+
!entry.file ||
|
|
117
|
+
entry.file !== path.basename(entry.file) ||
|
|
118
|
+
entry.file.includes("..")
|
|
119
|
+
) {
|
|
120
|
+
return sendError(
|
|
121
|
+
server,
|
|
122
|
+
id,
|
|
123
|
+
ws,
|
|
124
|
+
"ARTIFACT_BAD_FILE",
|
|
125
|
+
"artifact index row has a non-basename stored filename — refusing to serve it",
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
const previewKind = previewKindForMime(entry.mime);
|
|
129
|
+
const base = {
|
|
130
|
+
id,
|
|
131
|
+
type: "artifact-content",
|
|
132
|
+
artifactId: entry.id,
|
|
133
|
+
mime: entry.mime,
|
|
134
|
+
size: entry.size,
|
|
135
|
+
};
|
|
136
|
+
if (previewKind === "none") {
|
|
137
|
+
return server._send(ws, {
|
|
138
|
+
...base,
|
|
139
|
+
previewable: false,
|
|
140
|
+
reason: `no inline preview for ${entry.mime} — use \`cc artifacts open ${entry.id}\` locally`,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
const storedPath = store.storedPath(entry);
|
|
144
|
+
let actualSize;
|
|
145
|
+
try {
|
|
146
|
+
actualSize = statSync(storedPath).size;
|
|
147
|
+
} catch {
|
|
148
|
+
return sendError(
|
|
149
|
+
server,
|
|
150
|
+
id,
|
|
151
|
+
ws,
|
|
152
|
+
"ARTIFACT_FILE_MISSING",
|
|
153
|
+
`stored copy is missing: ${entry.file}`,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
if (previewKind === "image") {
|
|
157
|
+
if (actualSize > IMAGE_PREVIEW_CAP) {
|
|
158
|
+
return server._send(ws, {
|
|
159
|
+
...base,
|
|
160
|
+
previewable: false,
|
|
161
|
+
reason: `image exceeds the ${IMAGE_PREVIEW_CAP} byte preview cap`,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
const body = readFileSync(storedPath);
|
|
165
|
+
return server._send(ws, {
|
|
166
|
+
...base,
|
|
167
|
+
previewable: true,
|
|
168
|
+
encoding: "base64",
|
|
169
|
+
truncated: false,
|
|
170
|
+
content: body.toString("base64"),
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
// text
|
|
174
|
+
const cap = Math.min(
|
|
175
|
+
Number(message.maxBytes) > 0 ? Number(message.maxBytes) : TEXT_PREVIEW_CAP,
|
|
176
|
+
TEXT_PREVIEW_CAP,
|
|
177
|
+
);
|
|
178
|
+
const text = readFileSync(storedPath, "utf-8");
|
|
179
|
+
const truncated = text.length > cap;
|
|
180
|
+
server._send(ws, {
|
|
181
|
+
...base,
|
|
182
|
+
previewable: true,
|
|
183
|
+
encoding: "utf8",
|
|
184
|
+
truncated,
|
|
185
|
+
content: truncated ? text.slice(0, cap) : text,
|
|
186
|
+
});
|
|
187
|
+
} catch (err) {
|
|
188
|
+
sendError(server, id, ws, "ARTIFACT_CONTENT_FAILED", err.message);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export async function handleArtifactRemove(server, id, ws, message) {
|
|
193
|
+
try {
|
|
194
|
+
if (!message.artifactId) {
|
|
195
|
+
return sendError(server, id, ws, "NO_ARTIFACT_ID", "artifactId required");
|
|
196
|
+
}
|
|
197
|
+
const store = await loadStore();
|
|
198
|
+
const found = store.remove(message.artifactId);
|
|
199
|
+
server._send(ws, {
|
|
200
|
+
id,
|
|
201
|
+
type: "artifact-remove",
|
|
202
|
+
artifactId: message.artifactId,
|
|
203
|
+
removed: found ? message.artifactId : null,
|
|
204
|
+
found,
|
|
205
|
+
});
|
|
206
|
+
} catch (err) {
|
|
207
|
+
sendError(server, id, ws, "ARTIFACT_REMOVE_FAILED", err.message);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export async function handleArtifactClean(server, id, ws, message) {
|
|
212
|
+
try {
|
|
213
|
+
const store = await loadStore();
|
|
214
|
+
const { removed } = store.cleanupExpired();
|
|
215
|
+
server._send(ws, { id, type: "artifact-clean", removed });
|
|
216
|
+
} catch (err) {
|
|
217
|
+
sendError(server, id, ws, "ARTIFACT_CLEAN_FAILED", err.message);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
@@ -185,6 +185,12 @@ export function createWsMessageDispatcher(server) {
|
|
|
185
185
|
"bg-stop": () => server._handleBgStop(clientId, id, ws, message),
|
|
186
186
|
"bg-rename": () => server._handleBgRename(id, ws, message),
|
|
187
187
|
"bg-resume": () => server._handleBgResume(id, ws, message),
|
|
188
|
+
"artifact-list": () => server._handleArtifactList(id, ws, message),
|
|
189
|
+
"artifact-show": () => server._handleArtifactShow(id, ws, message),
|
|
190
|
+
"artifact-content": () =>
|
|
191
|
+
server._handleArtifactContent(id, ws, message),
|
|
192
|
+
"artifact-remove": () => server._handleArtifactRemove(id, ws, message),
|
|
193
|
+
"artifact-clean": () => server._handleArtifactClean(id, ws, message),
|
|
188
194
|
"chat.intent.understand": () =>
|
|
189
195
|
server._handleChatIntentUnderstand(id, ws, message),
|
|
190
196
|
"chat.intent.understand-stream": () =>
|
|
@@ -106,6 +106,13 @@ import {
|
|
|
106
106
|
handleBgStopTurn,
|
|
107
107
|
handleBgView,
|
|
108
108
|
} from "./background-agent-protocol.js";
|
|
109
|
+
import {
|
|
110
|
+
handleArtifactClean,
|
|
111
|
+
handleArtifactContent,
|
|
112
|
+
handleArtifactList,
|
|
113
|
+
handleArtifactRemove,
|
|
114
|
+
handleArtifactShow,
|
|
115
|
+
} from "./artifact-protocol.js";
|
|
109
116
|
|
|
110
117
|
const __filename = fileURLToPath(import.meta.url);
|
|
111
118
|
const __dirname = dirname(__filename);
|
|
@@ -717,6 +724,31 @@ export class ChainlessChainWSServer extends EventEmitter {
|
|
|
717
724
|
return handleBgResume(this, id, ws, message);
|
|
718
725
|
}
|
|
719
726
|
|
|
727
|
+
/** @private — list agent-published artifacts (metadata only) */
|
|
728
|
+
async _handleArtifactList(id, ws, message) {
|
|
729
|
+
return handleArtifactList(this, id, ws, message);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
/** @private — one artifact's metadata + stored path */
|
|
733
|
+
async _handleArtifactShow(id, ws, message) {
|
|
734
|
+
return handleArtifactShow(this, id, ws, message);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
/** @private — inline preview content (text utf8 / image base64, capped) */
|
|
738
|
+
async _handleArtifactContent(id, ws, message) {
|
|
739
|
+
return handleArtifactContent(this, id, ws, message);
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
/** @private — remove one artifact (stored copy + index row) */
|
|
743
|
+
async _handleArtifactRemove(id, ws, message) {
|
|
744
|
+
return handleArtifactRemove(this, id, ws, message);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
/** @private — drop expired artifacts (TTL) */
|
|
748
|
+
async _handleArtifactClean(id, ws, message) {
|
|
749
|
+
return handleArtifactClean(this, id, ws, message);
|
|
750
|
+
}
|
|
751
|
+
|
|
720
752
|
/** @private */
|
|
721
753
|
async _handleCompressionStats(id, ws, message) {
|
|
722
754
|
return handleCompressionStats(this, id, ws, message);
|
package/src/index.js
CHANGED
|
@@ -156,6 +156,15 @@ import { registerServeCommand } from "./commands/serve.js";
|
|
|
156
156
|
// Remote Control unified entry (第四阶段 #1)
|
|
157
157
|
import { registerRemoteControlCommand } from "./commands/remote-control.js";
|
|
158
158
|
|
|
159
|
+
// Agenda: consumer for agent-scheduled wakeups/crons/monitors (第四阶段 #3)
|
|
160
|
+
import { registerAgendaCommand } from "./commands/agenda.js";
|
|
161
|
+
|
|
162
|
+
// Batch: dynamic worktree batch — split a change into parallel units (第四阶段 #4)
|
|
163
|
+
import { registerBatchCommand } from "./commands/batch.js";
|
|
164
|
+
|
|
165
|
+
// Artifacts: agent-published deliverable store (P1 #10)
|
|
166
|
+
import { registerArtifactsCommand } from "./commands/artifacts.js";
|
|
167
|
+
|
|
159
168
|
// Web UI
|
|
160
169
|
import { registerUiCommand } from "./commands/ui.js";
|
|
161
170
|
import { registerPackCommand } from "./commands/pack.js";
|
|
@@ -586,6 +595,15 @@ export function createProgram(opts = {}) {
|
|
|
586
595
|
// Remote Control unified entry
|
|
587
596
|
registerRemoteControlCommand(program);
|
|
588
597
|
|
|
598
|
+
// Agenda: fire agent-scheduled wakeups/crons/monitors
|
|
599
|
+
registerAgendaCommand(program);
|
|
600
|
+
|
|
601
|
+
// Batch: dynamic worktree batch
|
|
602
|
+
registerBatchCommand(program);
|
|
603
|
+
|
|
604
|
+
// Artifacts: agent-published deliverable store
|
|
605
|
+
registerArtifactsCommand(program);
|
|
606
|
+
|
|
589
607
|
// Web UI
|
|
590
608
|
registerUiCommand(program);
|
|
591
609
|
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cc agent --ax-screen-reader` — screen-reader friendly output.
|
|
3
|
+
*
|
|
4
|
+
* Screen readers linearize the terminal: colors are invisible, and anything
|
|
5
|
+
* that repaints in place (the context status line) reads as noisy duplicate
|
|
6
|
+
* lines. This mode therefore:
|
|
7
|
+
* - forces the `mono` theme (chalk level 0 — no ANSI color codes) in the
|
|
8
|
+
* interactive REPL (consumed via CC_SCREEN_READER in agent-repl.js);
|
|
9
|
+
* - disables the repainting status line (reuses the existing
|
|
10
|
+
* CC_STATUSLINE=0 kill-switch).
|
|
11
|
+
* Headless (-p) text output is already plain sequential lines.
|
|
12
|
+
*
|
|
13
|
+
* Ambient activation: CC_SCREEN_READER=1 (so wrappers/launchers can turn it
|
|
14
|
+
* on without threading a flag through every entry point).
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** env switches applied by --ax-screen-reader. */
|
|
18
|
+
export const SCREEN_READER_SWITCHES = Object.freeze({
|
|
19
|
+
CC_SCREEN_READER: "1", // REPL: force mono theme / no ANSI decorations
|
|
20
|
+
CC_STATUSLINE: "0", // in-place repainting status line off
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Apply screen-reader mode to an env (default process.env).
|
|
25
|
+
* @returns {string[]} the switch names applied (for the startup notice)
|
|
26
|
+
*/
|
|
27
|
+
export function applyScreenReaderMode(env = process.env) {
|
|
28
|
+
for (const [k, v] of Object.entries(SCREEN_READER_SWITCHES)) {
|
|
29
|
+
env[k] = v;
|
|
30
|
+
}
|
|
31
|
+
return Object.keys(SCREEN_READER_SWITCHES);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Whether screen-reader mode is requested — the explicit `--ax-screen-reader`
|
|
36
|
+
* flag or the ambient CC_SCREEN_READER env var (1 / true / yes / on).
|
|
37
|
+
* @param {{ axScreenReader?: boolean }} [opts]
|
|
38
|
+
* @param {object} [env=process.env]
|
|
39
|
+
* @returns {boolean}
|
|
40
|
+
*/
|
|
41
|
+
export function screenReaderRequested(opts = {}, env = process.env) {
|
|
42
|
+
if (opts && opts.axScreenReader === true) return true;
|
|
43
|
+
return screenReaderActive(env);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Whether screen-reader mode is ACTIVE in the environment (what downstream
|
|
48
|
+
* consumers — the REPL theme init — should check).
|
|
49
|
+
* @param {object} [env=process.env]
|
|
50
|
+
* @returns {boolean}
|
|
51
|
+
*/
|
|
52
|
+
export function screenReaderActive(env = process.env) {
|
|
53
|
+
const raw = env.CC_SCREEN_READER;
|
|
54
|
+
return raw != null && /^(1|true|yes|on)$/i.test(String(raw).trim());
|
|
55
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agent-batch — dynamic worktree batch core (gap-analysis 第四阶段 #4).
|
|
3
|
+
*
|
|
4
|
+
* Takes a large task already decomposed into independent UNITS and runs each in
|
|
5
|
+
* its OWN git worktree, in parallel (bounded concurrency), then aggregates the
|
|
6
|
+
* outcome: per-unit agent status, test result, diff stat, and — on integration
|
|
7
|
+
* — a merge-conflict preview against base. This is the `/batch` shape from
|
|
8
|
+
* Claude Code, built on the same worktree isolation `cc team --worktree` uses.
|
|
9
|
+
*
|
|
10
|
+
* Pure over injected deps (worktree lifecycle, agent/test spawns, git) so the
|
|
11
|
+
* whole fan-out + aggregation is unit-testable without a real repo or LLM.
|
|
12
|
+
*
|
|
13
|
+
* A `unit` is `{ key, prompt, test? }`. Concurrency is a simple worker pool.
|
|
14
|
+
* Integration is SEQUENTIAL (like TeamWorktreeCoordinator.integrate) so a later
|
|
15
|
+
* branch that conflicts with an already-merged one is reported, not clobbered.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** Run `items` through `worker` with at most `limit` in flight; preserves order. */
|
|
19
|
+
async function mapPool(items, limit, worker) {
|
|
20
|
+
const results = new Array(items.length);
|
|
21
|
+
let next = 0;
|
|
22
|
+
const runners = new Array(Math.max(1, Math.min(limit, items.length)))
|
|
23
|
+
.fill(0)
|
|
24
|
+
.map(async () => {
|
|
25
|
+
while (true) {
|
|
26
|
+
const i = next++;
|
|
27
|
+
if (i >= items.length) return;
|
|
28
|
+
results[i] = await worker(items[i], i);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
await Promise.all(runners);
|
|
32
|
+
return results;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function normalizeUnits(units) {
|
|
36
|
+
if (!Array.isArray(units) || units.length === 0) {
|
|
37
|
+
throw new Error("batch requires a non-empty `units` array");
|
|
38
|
+
}
|
|
39
|
+
const seen = new Set();
|
|
40
|
+
return units.map((u, i) => {
|
|
41
|
+
const key = String(u.key || `unit-${i + 1}`);
|
|
42
|
+
if (seen.has(key)) throw new Error(`duplicate unit key "${key}"`);
|
|
43
|
+
seen.add(key);
|
|
44
|
+
if (!u.prompt || typeof u.prompt !== "string") {
|
|
45
|
+
throw new Error(`unit "${key}" has no prompt`);
|
|
46
|
+
}
|
|
47
|
+
return { key, prompt: u.prompt, test: u.test || null };
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @param {object} opts
|
|
53
|
+
* @param {Array<{key,prompt,test?}>} opts.units
|
|
54
|
+
* @param {number} [opts.concurrency=4]
|
|
55
|
+
* @param {string} [opts.test] default test command for units without one
|
|
56
|
+
* @param {boolean} [opts.merge] merge clean branches back to base
|
|
57
|
+
* @param {(msg:object)=>void} [opts.onEvent] progress callback
|
|
58
|
+
* @param {object} deps injected effectful ops (see command wiring)
|
|
59
|
+
* @returns {Promise<{units:Array, summary:object}>}
|
|
60
|
+
*/
|
|
61
|
+
export async function runBatch(opts, deps) {
|
|
62
|
+
const units = normalizeUnits(opts.units);
|
|
63
|
+
const concurrency = Math.max(1, Number(opts.concurrency) || 4);
|
|
64
|
+
const onEvent = opts.onEvent || (() => {});
|
|
65
|
+
const {
|
|
66
|
+
createWorktree,
|
|
67
|
+
removeWorktree,
|
|
68
|
+
runAgent,
|
|
69
|
+
runTest,
|
|
70
|
+
diffStat,
|
|
71
|
+
commit,
|
|
72
|
+
previewMerge,
|
|
73
|
+
mergeBranch,
|
|
74
|
+
branchFor = (key) => `batch/${key}`,
|
|
75
|
+
} = deps;
|
|
76
|
+
|
|
77
|
+
onEvent({ type: "batch:start", units: units.length, concurrency });
|
|
78
|
+
|
|
79
|
+
const results = await mapPool(units, concurrency, async (unit) => {
|
|
80
|
+
const branch = branchFor(unit.key);
|
|
81
|
+
const record = {
|
|
82
|
+
key: unit.key,
|
|
83
|
+
branch,
|
|
84
|
+
status: "pending",
|
|
85
|
+
filesChanged: 0,
|
|
86
|
+
insertions: 0,
|
|
87
|
+
deletions: 0,
|
|
88
|
+
testCommand: unit.test || opts.test || null,
|
|
89
|
+
testPassed: null,
|
|
90
|
+
committed: false,
|
|
91
|
+
error: null,
|
|
92
|
+
};
|
|
93
|
+
let worktreePath = null;
|
|
94
|
+
try {
|
|
95
|
+
worktreePath = createWorktree(unit.key, branch);
|
|
96
|
+
onEvent({ type: "unit:start", key: unit.key, branch });
|
|
97
|
+
await runAgent(unit.prompt, worktreePath);
|
|
98
|
+
|
|
99
|
+
const stat = diffStat(worktreePath);
|
|
100
|
+
record.filesChanged = stat.filesChanged;
|
|
101
|
+
record.insertions = stat.insertions;
|
|
102
|
+
record.deletions = stat.deletions;
|
|
103
|
+
|
|
104
|
+
const testCmd = unit.test || opts.test || null;
|
|
105
|
+
if (testCmd) {
|
|
106
|
+
try {
|
|
107
|
+
await runTest(testCmd, worktreePath);
|
|
108
|
+
record.testPassed = true;
|
|
109
|
+
} catch (err) {
|
|
110
|
+
record.testPassed = false;
|
|
111
|
+
record.testError = err.message;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (stat.filesChanged > 0) {
|
|
116
|
+
record.committed = commit(worktreePath, `batch: ${unit.key}`);
|
|
117
|
+
}
|
|
118
|
+
// A unit whose tests FAILED is reported failed even though its worktree
|
|
119
|
+
// committed — integration below skips non-passing units so a red unit
|
|
120
|
+
// never merges into base.
|
|
121
|
+
record.status =
|
|
122
|
+
record.testPassed === false
|
|
123
|
+
? "test-failed"
|
|
124
|
+
: record.filesChanged === 0
|
|
125
|
+
? "no-changes"
|
|
126
|
+
: "done";
|
|
127
|
+
} catch (err) {
|
|
128
|
+
record.status = "error";
|
|
129
|
+
record.error = err.message;
|
|
130
|
+
}
|
|
131
|
+
onEvent({
|
|
132
|
+
type: "unit:done",
|
|
133
|
+
key: unit.key,
|
|
134
|
+
status: record.status,
|
|
135
|
+
testPassed: record.testPassed,
|
|
136
|
+
filesChanged: record.filesChanged,
|
|
137
|
+
});
|
|
138
|
+
return { record, worktreePath };
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Sequential integration: only units that DID change, committed, and did not
|
|
142
|
+
// fail tests are eligible. Preview each; merge clean ones when --merge.
|
|
143
|
+
for (const { record } of results) {
|
|
144
|
+
if (!record.committed || record.status === "test-failed") {
|
|
145
|
+
record.integration = { eligible: false, reason: record.status };
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
let preview;
|
|
149
|
+
try {
|
|
150
|
+
preview = previewMerge(record.branch);
|
|
151
|
+
} catch (err) {
|
|
152
|
+
record.integration = {
|
|
153
|
+
eligible: true,
|
|
154
|
+
clean: false,
|
|
155
|
+
merged: false,
|
|
156
|
+
conflicts: [],
|
|
157
|
+
error: err.message,
|
|
158
|
+
};
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const clean = preview?.success === true;
|
|
162
|
+
const conflicts = preview?.conflicts || [];
|
|
163
|
+
let merged = false;
|
|
164
|
+
if (clean && opts.merge) {
|
|
165
|
+
let mergeResult;
|
|
166
|
+
try {
|
|
167
|
+
mergeResult = mergeBranch(record.branch, `Merge batch ${record.key}`);
|
|
168
|
+
} catch (err) {
|
|
169
|
+
mergeResult = { success: false, message: err.message };
|
|
170
|
+
}
|
|
171
|
+
merged = mergeResult?.success === true;
|
|
172
|
+
if (!merged && (!conflicts || conflicts.length === 0)) {
|
|
173
|
+
record.integration = {
|
|
174
|
+
eligible: true,
|
|
175
|
+
clean,
|
|
176
|
+
merged,
|
|
177
|
+
conflicts: mergeResult?.conflicts || [],
|
|
178
|
+
error: mergeResult?.message || null,
|
|
179
|
+
};
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
record.integration = { eligible: true, clean, merged, conflicts };
|
|
184
|
+
onEvent({
|
|
185
|
+
type: "unit:integrated",
|
|
186
|
+
key: record.key,
|
|
187
|
+
clean,
|
|
188
|
+
merged,
|
|
189
|
+
conflicts: conflicts.length,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Best-effort teardown of every worktree we created (branches persist for the
|
|
194
|
+
// user to inspect/merge later unless already merged).
|
|
195
|
+
if (typeof removeWorktree === "function") {
|
|
196
|
+
for (const { record, worktreePath } of results) {
|
|
197
|
+
if (!worktreePath) continue;
|
|
198
|
+
try {
|
|
199
|
+
removeWorktree(worktreePath, {
|
|
200
|
+
deleteBranch: record.integration?.merged === true,
|
|
201
|
+
});
|
|
202
|
+
} catch {
|
|
203
|
+
// best-effort — a leftover worktree is recoverable via `git worktree`
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const records = results.map((r) => r.record);
|
|
209
|
+
const summary = summarize(records);
|
|
210
|
+
onEvent({ type: "batch:done", ...summary });
|
|
211
|
+
return { units: records, summary };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function summarize(records) {
|
|
215
|
+
const summary = {
|
|
216
|
+
total: records.length,
|
|
217
|
+
done: 0,
|
|
218
|
+
testFailed: 0,
|
|
219
|
+
noChanges: 0,
|
|
220
|
+
errored: 0,
|
|
221
|
+
merged: 0,
|
|
222
|
+
conflicted: 0,
|
|
223
|
+
};
|
|
224
|
+
for (const r of records) {
|
|
225
|
+
if (r.status === "done") summary.done += 1;
|
|
226
|
+
else if (r.status === "test-failed") summary.testFailed += 1;
|
|
227
|
+
else if (r.status === "no-changes") summary.noChanges += 1;
|
|
228
|
+
else if (r.status === "error") summary.errored += 1;
|
|
229
|
+
if (r.integration?.merged) summary.merged += 1;
|
|
230
|
+
if (r.integration?.conflicts?.length) summary.conflicted += 1;
|
|
231
|
+
}
|
|
232
|
+
return summary;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export const _internal = { mapPool, normalizeUnits, summarize };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agent-notify — shared backing for the `notify` agent tool + `cc agenda`
|
|
3
|
+
* monitor firing (gap-analysis 第四阶段 #3).
|
|
4
|
+
*
|
|
5
|
+
* Fans a notification out over the user's configured multi-channel notifiers
|
|
6
|
+
* (Telegram / WeCom / DingTalk / Feishu, via NotificationManager.fromEnv) and,
|
|
7
|
+
* when a paired-device push descriptor exists, the mobile push bridge. Pure
|
|
8
|
+
* over injected deps so it is unit-testable without any network.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { NotificationManager } from "./notifiers/index.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {{title:string, body?:string, level?:"info"|"success"|"failure"}} msg
|
|
15
|
+
* @param {{env?:object, manager?:object}} [deps]
|
|
16
|
+
* @returns {Promise<{delivered:string[], failed:string[], channels:number, results:Array}>}
|
|
17
|
+
*/
|
|
18
|
+
export async function sendAgentNotification(msg, deps = {}) {
|
|
19
|
+
const title = String(msg?.title || "").trim();
|
|
20
|
+
if (!title) throw new Error("notify requires a title");
|
|
21
|
+
const body = msg?.body ? String(msg.body) : "";
|
|
22
|
+
const level = ["info", "success", "failure"].includes(msg?.level)
|
|
23
|
+
? msg.level
|
|
24
|
+
: "info";
|
|
25
|
+
|
|
26
|
+
const manager =
|
|
27
|
+
deps.manager ||
|
|
28
|
+
NotificationManager.fromEnv({ env: deps.env || process.env });
|
|
29
|
+
if (!manager.isConfigured) {
|
|
30
|
+
return {
|
|
31
|
+
delivered: [],
|
|
32
|
+
failed: [],
|
|
33
|
+
channels: 0,
|
|
34
|
+
results: [],
|
|
35
|
+
note: "No notification channels configured (set TELEGRAM_BOT_TOKEN / WECOM_WEBHOOK_URL / DINGTALK_WEBHOOK_URL / FEISHU_WEBHOOK_URL).",
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const summary = {
|
|
40
|
+
taskId: msg?.taskId || null,
|
|
41
|
+
description: title,
|
|
42
|
+
detail: body,
|
|
43
|
+
message: body || title,
|
|
44
|
+
title,
|
|
45
|
+
};
|
|
46
|
+
const method =
|
|
47
|
+
level === "success"
|
|
48
|
+
? "notifySuccess"
|
|
49
|
+
: level === "failure"
|
|
50
|
+
? "notifyFailure"
|
|
51
|
+
: "notifyStart";
|
|
52
|
+
const results = await manager[method](summary);
|
|
53
|
+
|
|
54
|
+
const delivered = [];
|
|
55
|
+
const failed = [];
|
|
56
|
+
for (const r of results || []) {
|
|
57
|
+
if (r && r.ok !== false) delivered.push(r.channel);
|
|
58
|
+
else failed.push(r?.channel || "unknown");
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
delivered,
|
|
62
|
+
failed,
|
|
63
|
+
channels: (manager.activeChannels || []).length,
|
|
64
|
+
results: results || [],
|
|
65
|
+
};
|
|
66
|
+
}
|