chainlesschain 0.162.148 → 0.162.150

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.
Files changed (322) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/src/assets/web-panel/.build-hash +1 -1
  4. package/src/assets/web-panel/assets/{AIOps-DwpCCp64.js → AIOps-CBLvrjeG.js} +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-C9pBYocA.js → ActionButton-DYv2GtE0.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-BWAkeXxK.js → Analytics-CL6PZ-Ww.js} +3 -3
  7. package/src/assets/web-panel/assets/{AppLayout-CivFGH6B.js → AppLayout-xuyHt4uA.js} +3 -3
  8. package/src/assets/web-panel/assets/{Audit-Cm8hRpZm.js → Audit-DOvSVycq.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-DXdFMsE8.js → Backup-DMhfTswr.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-uAwSTeql.js → BaseInput-CQxDdm63.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-KU8eeJJE.js → Chat-BEeNhK2a.js} +5 -5
  12. package/src/assets/web-panel/assets/ChatBubbleRenderer-8nul8eTq.js +1 -0
  13. package/src/assets/web-panel/assets/{Checkbox-C6AMDkjd.js → Checkbox-CL04O-ER.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-t2moDxcO.js → Codegen-C50o-n7x.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-DCcsRRhN.js → Col-CH-bDkzs.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-DtB-8SAC.js → Community-DqAIMvSe.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-CZm8THYA.js → Compact-C43fo_my.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-LiQgC7g1.js → Compliance-BERnMrdP.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-CA8SAxht.js → Cowork-Baw3w1gp.js} +4 -4
  20. package/src/assets/web-panel/assets/{Cron-CtRaF1wD.js → Cron-BEqAHF1-.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-6-br6Hub.js → Crosschain-CvnmKwK4.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-Do2EccrL.js → DID-iMPxgVdt.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-wrXcbzGe.js → Dashboard--4SJX3YR.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-p3FsT245.js → Dropdown-BgLV6xgz.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-D95A2L8q.js → EmailListRenderer-Bvb8oUD8.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-cLuJW2zo.js → FamilyGuardDashboard-DpXPjJB5.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-Brgq_cbN.js → Federation-BbFfJ1Xi.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-DWLCkNgh.js → FormItemContext-DeGq1B-q.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-BBqUfQd6.js → GenericCardRenderer-CNW7s9zM.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-8F090w4I.js → Git-L1PjW-lT.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-CCogEbxb.js → Governance-PK-X58to.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-CfLD7v7C.js → Inference-BePWEH-d.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-DDuHJewd.js → KnowledgeGraph-LF_sBvdL.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-CE8KSEVC.js → Logs-BKkfk9hy.js} +2 -2
  35. package/src/assets/web-panel/assets/{Marketplace-DmwpZmhT.js → Marketplace-BNMqUDla.js} +1 -1
  36. package/src/assets/web-panel/assets/{McpTools-Bf7AazU8.js → McpTools-w4WPiyz2.js} +5 -5
  37. package/src/assets/web-panel/assets/{Memory-D0QU_00S.js → Memory-DUKMVGNi.js} +2 -2
  38. package/src/assets/web-panel/assets/{MobileBridge-yXS9xdhx.js → MobileBridge-BWhiEyTJ.js} +1 -1
  39. package/src/assets/web-panel/assets/{MobileProjects-BuDUoGUP.js → MobileProjects-Ocf5JEkX.js} +1 -1
  40. package/src/assets/web-panel/assets/{Mtc-BYyHy28p.js → Mtc-D2B6MMhU.js} +4 -4
  41. package/src/assets/web-panel/assets/{MtcAudit-CK0aqpiZ.js → MtcAudit-DdYjCq1O.js} +4 -4
  42. package/src/assets/web-panel/assets/{Multisig-mNimKYeb.js → Multisig-Cb1hfuUZ.js} +3 -3
  43. package/src/assets/web-panel/assets/{NLProgramming-AioAJ0YA.js → NLProgramming-CTeImGp7.js} +1 -1
  44. package/src/assets/web-panel/assets/{Notes-CVVNCLHE.js → Notes-BjYNc7eQ.js} +4 -4
  45. package/src/assets/web-panel/assets/{NotificationSettings-C4ABj-TK.js → NotificationSettings-Dn_NtRXQ.js} +1 -1
  46. package/src/assets/web-panel/assets/{OrderTableRenderer-DIp8Xcbd.js → OrderTableRenderer-DVYYIizh.js} +1 -1
  47. package/src/assets/web-panel/assets/{Organization-uozl_gWK.js → Organization-Be2Kmr5j.js} +4 -4
  48. package/src/assets/web-panel/assets/{Overflow-BCZOM2hK.js → Overflow-Cu_-qRlR.js} +1 -1
  49. package/src/assets/web-panel/assets/{P2P-CArcaiaj.js → P2P-DD2HoGzE.js} +2 -2
  50. package/src/assets/web-panel/assets/{PdhVaultBrowser-Sgq2Srr8.js → PdhVaultBrowser-BRI7DVth.js} +3 -3
  51. package/src/assets/web-panel/assets/{Permissions-BYrQrXnH.js → Permissions-C9Srcs2M.js} +4 -4
  52. package/src/assets/web-panel/assets/{PersonalDataHub-BzHStAbz.js → PersonalDataHub-CUqcMgo1.js} +3 -3
  53. package/src/assets/web-panel/assets/{Pipeline-k7KqxX1M.js → Pipeline-HBrQGLXt.js} +1 -1
  54. package/src/assets/web-panel/assets/{Privacy-IumTUWqx.js → Privacy-D-B0vjwh.js} +1 -1
  55. package/src/assets/web-panel/assets/{ProjectInit-BRyImYTf.js → ProjectInit-eTeLIu9e.js} +2 -2
  56. package/src/assets/web-panel/assets/{ProjectSettings-BFIqTBFk.js → ProjectSettings-DrRqCsC6.js} +2 -2
  57. package/src/assets/web-panel/assets/{Projects-Dpid9CDg.js → Projects-1G9DNOhI.js} +1 -1
  58. package/src/assets/web-panel/assets/{Providers-CyyFnUPs.js → Providers-RkTZzpxP.js} +1 -1
  59. package/src/assets/web-panel/assets/{QrScannerModal-C82cRx-X.js → QrScannerModal-BSCUgsgC.js} +1 -1
  60. package/src/assets/web-panel/assets/{QuickAsk-DGxDF521.js → QuickAsk-CHgyM3Bz.js} +1 -1
  61. package/src/assets/web-panel/assets/{Recommend-Cns-Am1y.js → Recommend-0lry4OZq.js} +1 -1
  62. package/src/assets/web-panel/assets/{RemoteSession-R7OqAIlg.js → RemoteSession-Cn45UroZ.js} +2 -2
  63. package/src/assets/web-panel/assets/{Reputation-D7mgPIYV.js → Reputation-Q-Zjb707.js} +1 -1
  64. package/src/assets/web-panel/assets/{Row-B3lEURyd.js → Row-BdM70oda.js} +1 -1
  65. package/src/assets/web-panel/assets/{RssFeed-9_UVrpBt.js → RssFeed-DmOAKKap.js} +2 -2
  66. package/src/assets/web-panel/assets/{Search-ClZeO80q.js → Search-Dwavbm07.js} +1 -1
  67. package/src/assets/web-panel/assets/{Security-BNNJczEp.js → Security-DukXFCnk.js} +3 -3
  68. package/src/assets/web-panel/assets/{Services-C5SjrWUj.js → Services-BIlRnITu.js} +2 -2
  69. package/src/assets/web-panel/assets/{Skeleton-Ci_obQ-g.js → Skeleton-YYVQdhhQ.js} +1 -1
  70. package/src/assets/web-panel/assets/{Skills-DdebN15P.js → Skills-B8_iYHz2.js} +1 -1
  71. package/src/assets/web-panel/assets/{Sla-OinZP2vi.js → Sla-BdVrhT31.js} +1 -1
  72. package/src/assets/web-panel/assets/{SpeechSettings-CxzbNF51.js → SpeechSettings-CD3ggRx7.js} +1 -1
  73. package/src/assets/web-panel/assets/{SyncSettings-D06N_66b.js → SyncSettings-Bp02VZFH.js} +2 -2
  74. package/src/assets/web-panel/assets/{Tasks-BdEdW0AZ.js → Tasks-DEVmCEsr.js} +1 -1
  75. package/src/assets/web-panel/assets/{Templates-F1ctKmPa.js → Templates-B6czWc4N.js} +1 -1
  76. package/src/assets/web-panel/assets/{Tenant-CVz1Urot.js → Tenant-BCJLv17r.js} +1 -1
  77. package/src/assets/web-panel/assets/Terminal-CD132d2Y.js +3 -0
  78. package/src/assets/web-panel/assets/{TimelineRenderer-Doitzjmf.js → TimelineRenderer-ZB-CvkZl.js} +1 -1
  79. package/src/assets/web-panel/assets/{Tokens-BpyVRpVA.js → Tokens-BnSY0S-s.js} +1 -1
  80. package/src/assets/web-panel/assets/{Trigger-nWhWYTbU.js → Trigger-VZw9Oy2w.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trust-DK_G-wLx.js → Trust-pZq7Hjd2.js} +1 -1
  82. package/src/assets/web-panel/assets/{UkeySign-B5yBUojO.js → UkeySign-DnQaqk4q.js} +1 -1
  83. package/src/assets/web-panel/assets/{VideoEditing-C5D51qAe.js → VideoEditing-BEUfmvJV.js} +1 -1
  84. package/src/assets/web-panel/assets/{Wallet-CLbL9K7v.js → Wallet-CoZKMXJ2.js} +4 -4
  85. package/src/assets/web-panel/assets/{WebAuthn-DZUelI3V.js → WebAuthn-CBOGVx1I.js} +5 -5
  86. package/src/assets/web-panel/assets/{WorkflowEditor-8RPxt4KW.js → WorkflowEditor-huSKn7TT.js} +1 -1
  87. package/src/assets/web-panel/assets/{chat-4N4tOA3d.js → chat-A_3w6FBO.js} +1 -1
  88. package/src/assets/web-panel/assets/{colors-zbbGVrua.js → colors-D1Bem9Oy.js} +1 -1
  89. package/src/assets/web-panel/assets/{compact-item-D7iMkbnE.js → compact-item-CJYJj0oO.js} +1 -1
  90. package/src/assets/web-panel/assets/{createContext-CBzPJv-w.js → createContext-BkbFiKT4.js} +1 -1
  91. package/src/assets/web-panel/assets/devWarning-WDr3_M_N.js +1 -0
  92. package/src/assets/web-panel/assets/{hasIn-bHdrQSqZ.js → hasIn-CKvUafVY.js} +1 -1
  93. package/src/assets/web-panel/assets/{index-DbE5D0WL.js → index-AjrSPnvv.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-BCr0geV9.js → index-B8i0lViZ.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-CiG1IZao.js → index-B9Svn0zc.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-B1s0Bz9O.js → index-BHdHSX06.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-QFObYeo1.js → index-BHdailyo.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-BnBOpqv3.js → index-BJwlEnYo.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-CdmMoYN1.js → index-BOoAQjJz.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-DUyjUkY7.js → index-BSgWxAOG.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-CqdPyWm5.js → index-BdutMsne.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-DmqlJed-.js → index-BiDWxzbn.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-S74FpAIn.js → index-By5a0T_W.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-kmh1TxRa.js → index-C-WIY-FQ.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-BTIjjXry.js → index-C37f6iey.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-BS4_Mwk6.js → index-C9bWmTcO.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-BBNr77E1.js → index-CBaosWRO.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-QNAG4JtR.js → index-CBlBVJ_m.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-Bqmx24kh.js → index-CD62EEGo.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-CQfu1U78.js → index-CUj-l7GM.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-BJ4ZGyW0.js → index-CVS8Ymuq.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-Ci009ijp.js → index-ChkXUj3f.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-oDKNgCsP.js → index-Cj0OZ-37.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-BZb8F8YG.js → index-Co3OFL0t.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-308gCGh7.js → index-Crp8CbRg.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-CPxkoKgA.js → index-D22zNXiS.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-xYCm6W2h.js → index-DTs_D1OL.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-BtbbdLnf.js → index-DfgCdFrN.js} +3 -3
  119. package/src/assets/web-panel/assets/{index-BsKehmmS.js → index-DjYrecNb.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-CkxK86vf.js → index-DpW9cf3e.js} +1 -1
  121. package/src/assets/web-panel/assets/index-Dwix3p4g.js +1 -0
  122. package/src/assets/web-panel/assets/{index-CvRMA9uT.js → index-F--HuPG1.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-DE9j5YgT.js → index-Fuk6t_3K.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-B7b1hGry.js → index-JEAGMii9.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-D1YDh7Wr.js → index-PF-mpv8K.js} +1 -1
  126. package/src/assets/web-panel/assets/index-SHaoZ0CA.js +1 -0
  127. package/src/assets/web-panel/assets/{index-CQ6NcfWz.js → index-b-sbVdjQ.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-DyoNgbXl.js → index-pyvMVX8r.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-DISWAYce.js → index-qNTtOVi_.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-D4XKpj6c.js → index-tgO8iza6.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-CahryTX0.js → index-ttDQVhZy.js} +1 -1
  132. package/src/assets/web-panel/assets/{initDefaultProps-BD55yNBt.js → initDefaultProps-DtrnfsZH.js} +1 -1
  133. package/src/assets/web-panel/assets/{motion-B5Bbe77U.js → motion-D4zqSaX3.js} +1 -1
  134. package/src/assets/web-panel/assets/{move-CTvIqHEH.js → move-CGFGOUQj.js} +1 -1
  135. package/src/assets/web-panel/assets/{mtc-parser-BkNyPQKB.js → mtc-parser-DH2HvkJl.js} +1 -1
  136. package/src/assets/web-panel/assets/{omit-BaXJXgHA.js → omit-jJ2at5HX.js} +1 -1
  137. package/src/assets/web-panel/assets/{pickAttrs-Bw6-YTxH.js → pickAttrs-CRyMT1Fv.js} +1 -1
  138. package/src/assets/web-panel/assets/{placementArrow-BLdbkLqJ.js → placementArrow-DZ_CX0Pt.js} +1 -1
  139. package/src/assets/web-panel/assets/{responsiveObserve-IVYkzntU.js → responsiveObserve-wruHk-h5.js} +1 -1
  140. package/src/assets/web-panel/assets/{slide-DFMFwwtY.js → slide-BGgx8q67.js} +1 -1
  141. package/src/assets/web-panel/assets/{statusUtils-CURYRtZ1.js → statusUtils-Do5M7gKm.js} +1 -1
  142. package/src/assets/web-panel/assets/{styleChecker-Cfz63s1r.js → styleChecker-CRa-GZpX.js} +1 -1
  143. package/src/assets/web-panel/assets/{useFlexGapSupport-COJL0KE3.js → useFlexGapSupport-BxPcq6pj.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFs-YLLojQQf.js → useFs-v5O2ZcJm.js} +1 -1
  145. package/src/assets/web-panel/assets/{usePersonalDataHub-BF_21qUC.js → usePersonalDataHub--z2FU0Px.js} +1 -1
  146. package/src/assets/web-panel/assets/{vnode-9ZpkA7bb.js → vnode-1emidVKd.js} +1 -1
  147. package/src/assets/web-panel/assets/{zoom-DhtAfhl8.js → zoom-CcJaRyHs.js} +1 -1
  148. package/src/assets/web-panel/index.html +1 -1
  149. package/src/assets/web-panel/remote-session-sw.js +43 -0
  150. package/src/command-manifest.json +22 -1
  151. package/src/commands/a2a.js +19 -4
  152. package/src/commands/activitypub.js +20 -3
  153. package/src/commands/agent.js +71 -10
  154. package/src/commands/audit.js +40 -24
  155. package/src/commands/codeintel.js +320 -0
  156. package/src/commands/collab.js +15 -2
  157. package/src/commands/compliance.js +5 -0
  158. package/src/commands/config.js +4 -1
  159. package/src/commands/dao.js +86 -14
  160. package/src/commands/dev.js +2 -0
  161. package/src/commands/did.js +7 -1
  162. package/src/commands/dlp.js +23 -1
  163. package/src/commands/economy.js +29 -3
  164. package/src/commands/eval.js +255 -0
  165. package/src/commands/evomap.js +26 -0
  166. package/src/commands/governance.js +17 -3
  167. package/src/commands/hardening.js +18 -0
  168. package/src/commands/incentive.js +5 -0
  169. package/src/commands/init.js +46 -2
  170. package/src/commands/kg.js +4 -0
  171. package/src/commands/loop.js +6 -1
  172. package/src/commands/lowcode.js +15 -2
  173. package/src/commands/marketplace.js +40 -6
  174. package/src/commands/matrix.js +20 -1
  175. package/src/commands/memory.js +4 -1
  176. package/src/commands/mtc.js +7 -1
  177. package/src/commands/nostr.js +31 -4
  178. package/src/commands/note.js +7 -4
  179. package/src/commands/plugin.js +474 -0
  180. package/src/commands/pqc.js +5 -0
  181. package/src/commands/reputation.js +10 -1
  182. package/src/commands/scim.js +6 -0
  183. package/src/commands/session.js +32 -10
  184. package/src/commands/siem.js +11 -0
  185. package/src/commands/sla.js +18 -3
  186. package/src/commands/social.js +38 -5
  187. package/src/commands/sso.js +10 -2
  188. package/src/commands/stress.js +23 -4
  189. package/src/commands/team.js +463 -0
  190. package/src/commands/tech.js +2 -0
  191. package/src/commands/tenant.js +10 -1
  192. package/src/commands/terraform.js +6 -0
  193. package/src/commands/update.js +1 -1
  194. package/src/commands/zkp.js +20 -0
  195. package/src/gateways/ws/message-dispatcher.js +48 -2
  196. package/src/gateways/ws/remote-session-protocol.js +140 -42
  197. package/src/gateways/ws/ws-server.js +1 -1
  198. package/src/harness/jsonl-session-store.js +12 -5
  199. package/src/harness/plugin-manager.js +16 -4
  200. package/src/harness/prompt-compressor.js +27 -1
  201. package/src/harness/remote-command-ledger.js +189 -0
  202. package/src/harness/remote-session-push-apns.js +246 -0
  203. package/src/harness/remote-session-push-errors.js +15 -0
  204. package/src/harness/remote-session-push-fcm.js +9 -25
  205. package/src/harness/remote-session-push-huawei.js +174 -0
  206. package/src/harness/remote-session-push-oppo.js +171 -0
  207. package/src/harness/remote-session-push-senders.js +42 -0
  208. package/src/harness/remote-session-push-vivo.js +179 -0
  209. package/src/harness/remote-session-push-web.js +299 -0
  210. package/src/harness/remote-session-push-xiaomi.js +99 -0
  211. package/src/harness/worktree-isolator.js +16 -6
  212. package/src/index.js +6 -0
  213. package/src/lib/__tests__/logger.test.js +5 -2
  214. package/src/lib/a2a-protocol.js +25 -1
  215. package/src/lib/activitypub-bridge.js +61 -0
  216. package/src/lib/agent-core.js +4 -0
  217. package/src/lib/agent-economy.js +262 -29
  218. package/src/lib/agent-network.js +7 -1
  219. package/src/lib/agent-sandbox.js +161 -5
  220. package/src/lib/agent-team/task-lease.js +434 -0
  221. package/src/lib/agent-team/team-budget.js +165 -0
  222. package/src/lib/agent-team/team-mailbox.js +106 -0
  223. package/src/lib/agent-team/team-runner.js +282 -0
  224. package/src/lib/agent-team/team-worktree.js +236 -0
  225. package/src/lib/agents.js +18 -0
  226. package/src/lib/async-hook-supervisor.cjs +391 -0
  227. package/src/lib/audit-logger.js +7 -6
  228. package/src/lib/autonomous-developer.js +60 -0
  229. package/src/lib/chat-core.js +17 -4
  230. package/src/lib/collaboration-governance.js +56 -0
  231. package/src/lib/community-governance.js +68 -0
  232. package/src/lib/compliance-manager.js +63 -0
  233. package/src/lib/cross-chain.js +5 -0
  234. package/src/lib/dao-governance.js +151 -2
  235. package/src/lib/did-manager.js +23 -10
  236. package/src/lib/dlp-engine.js +70 -0
  237. package/src/lib/eval/runner.js +251 -0
  238. package/src/lib/eval/tasks.js +626 -0
  239. package/src/lib/eval/trend.js +200 -0
  240. package/src/lib/evolution-system.js +92 -0
  241. package/src/lib/evomap-federation.js +55 -0
  242. package/src/lib/evomap-governance.js +94 -0
  243. package/src/lib/fatal-handler.js +17 -1
  244. package/src/lib/hardening-manager.js +73 -0
  245. package/src/lib/hierarchical-memory.js +14 -8
  246. package/src/lib/knowledge-exporter.js +8 -2
  247. package/src/lib/knowledge-graph.js +79 -0
  248. package/src/lib/llm-providers.js +23 -14
  249. package/src/lib/logger.js +4 -1
  250. package/src/lib/lsp/__tests__/benchmark.test.js +88 -0
  251. package/src/lib/lsp/__tests__/code-intelligence.integration.test.js +129 -0
  252. package/src/lib/lsp/__tests__/code-intelligence.test.js +228 -0
  253. package/src/lib/lsp/__tests__/jsonrpc-stream.test.js +104 -0
  254. package/src/lib/lsp/__tests__/lsp-client.test.js +269 -0
  255. package/src/lib/lsp/__tests__/lsp-manager.test.js +333 -0
  256. package/src/lib/lsp/__tests__/lsp-server-registry.test.js +128 -0
  257. package/src/lib/lsp/benchmark.js +257 -0
  258. package/src/lib/lsp/code-intelligence.js +356 -0
  259. package/src/lib/lsp/jsonrpc-stream.js +139 -0
  260. package/src/lib/lsp/lsp-client.js +339 -0
  261. package/src/lib/lsp/lsp-manager.js +375 -0
  262. package/src/lib/lsp/lsp-server-registry.js +196 -0
  263. package/src/lib/matrix-bridge.js +84 -0
  264. package/src/lib/memory-manager.js +5 -3
  265. package/src/lib/nostr-bridge.js +53 -0
  266. package/src/lib/permission-engine.js +22 -4
  267. package/src/lib/plugin-monitor-supervisor.js +238 -0
  268. package/src/lib/plugin-runtime/__tests__/remote-source.test.js +256 -0
  269. package/src/lib/plugin-runtime/agents.js +51 -0
  270. package/src/lib/plugin-runtime/bin.js +100 -0
  271. package/src/lib/plugin-runtime/hooks.js +100 -0
  272. package/src/lib/plugin-runtime/install.js +396 -0
  273. package/src/lib/plugin-runtime/lsp.js +75 -0
  274. package/src/lib/plugin-runtime/manifest.js +458 -0
  275. package/src/lib/plugin-runtime/mcp.js +89 -0
  276. package/src/lib/plugin-runtime/monitors.js +100 -0
  277. package/src/lib/plugin-runtime/policy.js +152 -0
  278. package/src/lib/plugin-runtime/reload.js +79 -0
  279. package/src/lib/plugin-runtime/remote-source.js +240 -0
  280. package/src/lib/plugin-runtime/scopes.js +197 -0
  281. package/src/lib/plugin-runtime/settings.js +119 -0
  282. package/src/lib/plugin-runtime/signature.js +107 -0
  283. package/src/lib/plugin-runtime/skills.js +43 -0
  284. package/src/lib/plugin-runtime/trust.js +140 -0
  285. package/src/lib/pqc-manager.js +64 -0
  286. package/src/lib/reputation-optimizer.js +101 -0
  287. package/src/lib/sandbox-egress-proxy.js +262 -0
  288. package/src/lib/sandbox-fs-policy.js +112 -0
  289. package/src/lib/sandbox-network-policy.js +151 -0
  290. package/src/lib/sandbox-v2.js +24 -18
  291. package/src/lib/scim-manager.js +36 -0
  292. package/src/lib/session-manager.js +5 -2
  293. package/src/lib/settings-hook-events.cjs +78 -6
  294. package/src/lib/settings-hooks.cjs +30 -3
  295. package/src/lib/settings-loader.cjs +35 -1
  296. package/src/lib/siem-exporter.js +45 -0
  297. package/src/lib/skill-loader.js +38 -2
  298. package/src/lib/skill-marketplace.js +83 -0
  299. package/src/lib/sla-manager.js +88 -0
  300. package/src/lib/social-manager.js +74 -0
  301. package/src/lib/stress-tester.js +65 -0
  302. package/src/lib/tech-learning-engine.js +75 -0
  303. package/src/lib/telemetry/span-recorder.js +237 -0
  304. package/src/lib/tenant-saas.js +107 -0
  305. package/src/lib/terraform-manager.js +67 -0
  306. package/src/lib/token-incentive.js +180 -29
  307. package/src/lib/wallet-manager.js +81 -35
  308. package/src/lib/zkp-engine.js +87 -0
  309. package/src/repl/agent-repl.js +417 -6
  310. package/src/runtime/__tests__/auto-pin.test.js +104 -0
  311. package/src/runtime/agent-core.js +771 -104
  312. package/src/runtime/auto-pin.js +117 -0
  313. package/src/runtime/coding-agent-contract-shared.cjs +82 -8
  314. package/src/runtime/coding-agent-policy.cjs +32 -7
  315. package/src/runtime/fallback-model.js +134 -7
  316. package/src/runtime/headless-runner.js +388 -108
  317. package/src/runtime/mcp-config.js +46 -0
  318. package/src/assets/web-panel/assets/ChatBubbleRenderer-DCNfbdlx.js +0 -1
  319. package/src/assets/web-panel/assets/Terminal-BvAK_Zel.js +0 -3
  320. package/src/assets/web-panel/assets/devWarning-C2Z5LRgq.js +0 -1
  321. package/src/assets/web-panel/assets/index-BGp6Yr-Y.js +0 -1
  322. package/src/assets/web-panel/assets/index-BXiw9dQf.js +0 -1
@@ -83,7 +83,8 @@ const { evaluateShellCommandPolicy } = sharedShellPolicy;
83
83
  const { evaluatePermissionRules } = sharedPermissionRules;
84
84
  const { collectHooks, umbrellaFor } = sharedSettingsHooks;
85
85
  const { runHooks: runCommandHooks } = sharedHookRunner;
86
- const { runObserveHooks } = sharedHookEvents;
86
+ const { runObserveHooks, aggregateContext, partitionAsyncHooks } =
87
+ sharedHookEvents;
87
88
 
88
89
  // ─── Background shell tasks ────────────────────────────────────────────────
89
90
  //
@@ -220,6 +221,77 @@ export function killAllBackgroundShellTasks() {
220
221
  return killed;
221
222
  }
222
223
 
224
+ // SYNCHRONOUS whole-tree kill for use inside a process 'exit' / signal handler,
225
+ // where only synchronous work runs to completion (the async `spawn`/`taskkill`
226
+ // in _killTask would be cut off the instant the process terminates, leaving the
227
+ // grandchild orphaned). POSIX signals the process group with SIGKILL; Windows
228
+ // uses spawnSync taskkill /T so the shell's children die too.
229
+ function _killTaskSync(task) {
230
+ const child = task?.child;
231
+ if (!child || child.killed || task?.status !== "running") return false;
232
+ try {
233
+ if (process.platform === "win32") {
234
+ if (child.pid) {
235
+ spawnSync("taskkill", ["/pid", String(child.pid), "/T", "/F"], {
236
+ windowsHide: true,
237
+ });
238
+ } else {
239
+ child.kill();
240
+ }
241
+ } else if (child.pid) {
242
+ // Negative pid → whole process group (requires the detached spawn above).
243
+ try {
244
+ process.kill(-child.pid, "SIGKILL");
245
+ } catch (_err) {
246
+ child.kill("SIGKILL");
247
+ }
248
+ } else {
249
+ child.kill("SIGKILL");
250
+ }
251
+ return true;
252
+ } catch (_err) {
253
+ return false;
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Synchronously kill every still-running background shell task. Safe to call
259
+ * from a process 'exit' or signal handler (uses spawnSync/process.kill, not the
260
+ * async spawn path which a terminating process would cut off). This is the
261
+ * orphan-reclaim net for the paths the normal `finally` reaper can't cover: a
262
+ * Ctrl-C / SIGTERM unwinds no `finally`, so without it a backgrounded dev server
263
+ * outlives a killed `cc agent -p`.
264
+ * @returns {number} count of tasks signalled
265
+ */
266
+ export function killAllBackgroundShellTasksSync() {
267
+ let killed = 0;
268
+ for (const task of _backgroundShellTasks.values()) {
269
+ if (_killTaskSync(task)) {
270
+ killed += 1;
271
+ }
272
+ }
273
+ return killed;
274
+ }
275
+
276
+ // Install a one-time process 'exit' net that synchronously reaps background
277
+ // shell tasks. `finally` blocks (headless/REPL) already reap on normal
278
+ // completion, but an explicit process.exit() elsewhere (serve shutdown, the
279
+ // headless signal handler that converts Ctrl-C → exit) would otherwise leave a
280
+ // backgrounded command orphaned. Installed lazily on the first background task,
281
+ // so a process that never backgrounds anything pays nothing.
282
+ let _bgExitReaperHooked = false;
283
+ function _ensureBgExitReaper() {
284
+ if (_bgExitReaperHooked) return;
285
+ _bgExitReaperHooked = true;
286
+ process.once("exit", () => {
287
+ try {
288
+ killAllBackgroundShellTasksSync();
289
+ } catch {
290
+ /* best-effort teardown on exit */
291
+ }
292
+ });
293
+ }
294
+
223
295
  // Idle-reap tuning (Claude-Code 2.1.193 "automatic memory-pressure reaping for
224
296
  // idle background shell commands"). A running task is a reap candidate when it
225
297
  // has produced no output for BG_IDLE_REAP_MS AND the system is under memory
@@ -323,16 +395,12 @@ async function runSettingsPreToolUseHooks(name, args, context, cwd) {
323
395
  ideResult: ide.result,
324
396
  };
325
397
  }
326
- const confirm = context.permissionConfirm || context.shellConfirm || null;
327
- const ok =
328
- typeof confirm === "function"
329
- ? await confirm({
330
- tool: name,
331
- args,
332
- rule: `hook:${outcome.hook}`,
333
- reason: outcome.reason || "a PreToolUse hook requests confirmation",
334
- })
335
- : false;
398
+ const ok = await requestInteractivePermission(name, args, context, cwd, {
399
+ tool: name,
400
+ args,
401
+ rule: `hook:${outcome.hook}`,
402
+ reason: outcome.reason || "a PreToolUse hook requests confirmation",
403
+ });
336
404
  return ok
337
405
  ? { blocked: false }
338
406
  : {
@@ -344,6 +412,87 @@ async function runSettingsPreToolUseHooks(name, args, context, cwd) {
344
412
  return { blocked: false };
345
413
  }
346
414
 
415
+ /**
416
+ * Run settings.json `PermissionRequest` hooks (Claude-Code parity). Fires at the
417
+ * exact moment a tool call would prompt the user for approval — BEFORE the
418
+ * prompt — so a policy hook can auto-approve (`allow`/`approve`), auto-reject
419
+ * (`deny`/`block`), or defer (`ask` / no decision) to the normal prompt.
420
+ * Returns `{ decision: "allow" | "deny" | null }`. No matching hook (or no
421
+ * `settingsHooks`) → `{ decision: null }` and the caller prompts exactly as
422
+ * before (default behaviour is byte-for-byte unchanged).
423
+ * @returns {{decision:("allow"|"deny"|null), reason?:string, hook?:string}}
424
+ */
425
+ function runSettingsPermissionRequestHooks(name, args, context, cwd, reason) {
426
+ const matched = collectHooks(
427
+ context.settingsHooks,
428
+ "PermissionRequest",
429
+ name,
430
+ );
431
+ if (!matched || matched.length === 0) return { decision: null };
432
+ const payload = {
433
+ hook_event_name: "PermissionRequest",
434
+ tool_name: umbrellaFor(name),
435
+ raw_tool_name: name,
436
+ tool_input: args,
437
+ permission_reason: reason || null,
438
+ cwd,
439
+ session_id: context.sessionId || null,
440
+ };
441
+ const outcome = runCommandHooks(matched, payload, {
442
+ cwd,
443
+ event: "PermissionRequest",
444
+ });
445
+ // Precedence: deny > ask > allow > defer. The runner normalizes deny/block →
446
+ // "block" and short-circuits block/ask, but it COLLAPSES an "allow" into the
447
+ // aggregate "continue" (it only short-circuits block/ask) — so an explicit
448
+ // auto-approve must be recovered from the per-hook `results`. A deny or ask
449
+ // by any hook still wins over an allow (safety: never let an allow override a
450
+ // gate that another hook wanted to block or prompt for).
451
+ if (outcome.decision === "block") {
452
+ return { decision: "deny", reason: outcome.reason, hook: outcome.hook };
453
+ }
454
+ if (outcome.decision === "ask") {
455
+ return { decision: null }; // a hook asked → defer to the confirmer
456
+ }
457
+ const allowHook =
458
+ outcome.decision === "allow"
459
+ ? { command: outcome.hook }
460
+ : (outcome.results || []).find((r) => r && r.decision === "allow");
461
+ if (allowHook) {
462
+ return { decision: "allow", hook: allowHook.command || null };
463
+ }
464
+ return { decision: null }; // no actionable decision → defer to the confirmer
465
+ }
466
+
467
+ /**
468
+ * Resolve an interactive permission gate. Gives `PermissionRequest` hooks first
469
+ * say (auto-allow → true without prompting, auto-deny → false without
470
+ * prompting), then falls back to the injected confirmer with the identical
471
+ * arguments the call site would have passed. When no `PermissionRequest` hook
472
+ * matches this is byte-for-byte the previous `confirm(confirmArgs)` call, so
473
+ * every existing permission gate keeps its exact behaviour absent a hook.
474
+ * @returns {Promise<boolean>}
475
+ */
476
+ async function requestInteractivePermission(
477
+ name,
478
+ args,
479
+ context,
480
+ cwd,
481
+ confirmArgs,
482
+ ) {
483
+ const verdict = runSettingsPermissionRequestHooks(
484
+ name,
485
+ args,
486
+ context,
487
+ cwd,
488
+ confirmArgs?.reason,
489
+ );
490
+ if (verdict.decision === "allow") return true;
491
+ if (verdict.decision === "deny") return false;
492
+ const confirm = context.permissionConfirm || context.shellConfirm || null;
493
+ return typeof confirm === "function" ? await confirm(confirmArgs) : false;
494
+ }
495
+
347
496
  // ─── Tool definitions ────────────────────────────────────────────────────
348
497
 
349
498
  export const AGENT_TOOLS = getCodingAgentFunctionToolDefinitions();
@@ -514,6 +663,8 @@ Key behaviors:
514
663
  - For long-running commands (builds, full test suites, dev servers) set run_shell { run_in_background: true } to get a task_id back immediately, then poll output and completion with check_shell { task_id }. Kill a backgrounded server with check_shell { task_id, kill: true } when finished
515
664
  - When asked about git status, diff, log, or other repository operations, use the git tool instead of run_shell
516
665
  - When asked about files or code, use read_file and search_files to find information
666
+ - Before renaming or changing a symbol, use code_intelligence (action: references/definition) to find every real usage instead of guessing with text search. It degrades to "unavailable" when no language server is installed — fall back to search_files then.
667
+ - After an edit, if the tool result includes a "newDiagnostics" array, you just introduced (or exposed) those errors/warnings — read them and fix before moving on. You can also run code_intelligence (action: diagnostics) on any file to check it on demand.
517
668
  - You have multi-layer skills (built-in, marketplace, global, project-level) — use list_skills to discover them and run_skill to execute them
518
669
  - Always explain what you're doing and show results
519
670
  - Be concise but thorough
@@ -1079,16 +1230,12 @@ export async function executeTool(name, args, context = {}) {
1079
1230
  source: "settings rule",
1080
1231
  });
1081
1232
  if (ide) return ide.result;
1082
- const confirm = context.permissionConfirm || context.shellConfirm || null;
1083
- const ok =
1084
- typeof confirm === "function"
1085
- ? await confirm({
1086
- tool: name,
1087
- args,
1088
- rule: settingsVerdict.rule,
1089
- reason: `settings rule ${settingsVerdict.rule} requires confirmation`,
1090
- })
1091
- : false;
1233
+ const ok = await requestInteractivePermission(name, args, context, cwd, {
1234
+ tool: name,
1235
+ args,
1236
+ rule: settingsVerdict.rule,
1237
+ reason: `settings rule ${settingsVerdict.rule} requires confirmation`,
1238
+ });
1092
1239
  if (!ok) {
1093
1240
  return {
1094
1241
  error: `[Permission] Tool "${name}" requires confirmation (settings rule: ${settingsVerdict.rule}) but this run is non-interactive — denied. Do not retry; tell the user this action needs their approval.`,
@@ -1120,16 +1267,12 @@ export async function executeTool(name, args, context = {}) {
1120
1267
  await import("../lib/sensitive-file-guard.js");
1121
1268
  const sensReason = sensitiveFileReason(args.path);
1122
1269
  if (sensReason) {
1123
- const confirm = context.permissionConfirm || context.shellConfirm || null;
1124
- const ok =
1125
- typeof confirm === "function"
1126
- ? await confirm({
1127
- tool: name,
1128
- args,
1129
- rule: null,
1130
- reason: `sensitive file: ${sensReason}`,
1131
- })
1132
- : false;
1270
+ const ok = await requestInteractivePermission(name, args, context, cwd, {
1271
+ tool: name,
1272
+ args,
1273
+ rule: null,
1274
+ reason: `sensitive file: ${sensReason}`,
1275
+ });
1133
1276
  if (!ok) {
1134
1277
  return {
1135
1278
  error: `[Sensitive File] Writing "${args.path}" requires confirmation (${sensReason}) — denied. Add a settings allow rule to pre-authorize.`,
@@ -1152,16 +1295,12 @@ export async function executeTool(name, args, context = {}) {
1152
1295
  !planManager.isActive() &&
1153
1296
  isDangerousGitCommand(args?.command)
1154
1297
  ) {
1155
- const confirm = context.permissionConfirm || context.shellConfirm || null;
1156
- const ok =
1157
- typeof confirm === "function"
1158
- ? await confirm({
1159
- tool: name,
1160
- args,
1161
- rule: null,
1162
- reason: `destructive git command: git ${normalizeGitCommand(args.command)}`,
1163
- })
1164
- : false;
1298
+ const ok = await requestInteractivePermission(name, args, context, cwd, {
1299
+ tool: name,
1300
+ args,
1301
+ rule: null,
1302
+ reason: `destructive git command: git ${normalizeGitCommand(args.command)}`,
1303
+ });
1165
1304
  if (!ok) {
1166
1305
  return {
1167
1306
  error: `[Destructive Git] "git ${normalizeGitCommand(args.command)}" discards work irrecoverably and requires confirmation — denied. Add a settings allow rule to pre-authorize.`,
@@ -1196,17 +1335,18 @@ export async function executeTool(name, args, context = {}) {
1196
1335
  credReason = hit ? hit.reason : null;
1197
1336
  }
1198
1337
  if (credReason) {
1199
- const confirm =
1200
- context.permissionConfirm || context.shellConfirm || null;
1201
- const ok =
1202
- typeof confirm === "function"
1203
- ? await confirm({
1204
- tool: name,
1205
- args,
1206
- rule: null,
1207
- reason: `credential access: ${credReason}`,
1208
- })
1209
- : false;
1338
+ const ok = await requestInteractivePermission(
1339
+ name,
1340
+ args,
1341
+ context,
1342
+ cwd,
1343
+ {
1344
+ tool: name,
1345
+ args,
1346
+ rule: null,
1347
+ reason: `credential access: ${credReason}`,
1348
+ },
1349
+ );
1210
1350
  if (!ok) {
1211
1351
  const what =
1212
1352
  name === "read_file" ? `Reading "${args.path}"` : "This command";
@@ -1302,6 +1442,7 @@ export async function executeTool(name, args, context = {}) {
1302
1442
  subAgentDepth: context.subAgentDepth || 0,
1303
1443
  subAgentBudget: context.subAgentBudget || null,
1304
1444
  interactiveApproval: context.interactiveApproval || false,
1445
+ settingsHooks: context.settingsHooks || null,
1305
1446
  });
1306
1447
  } catch (err) {
1307
1448
  if (hookDb) {
@@ -1354,24 +1495,36 @@ export async function executeTool(name, args, context = {}) {
1354
1495
  try {
1355
1496
  const matched = collectHooks(context.settingsHooks, "PostToolUse", name);
1356
1497
  if (matched && matched.length > 0) {
1357
- const outcome = runCommandHooks(
1358
- matched,
1359
- {
1360
- hook_event_name: "PostToolUse",
1361
- tool_name: umbrellaFor(name),
1362
- raw_tool_name: name,
1363
- tool_input: args,
1364
- tool_response:
1365
- typeof toolResult === "object"
1366
- ? JSON.stringify(toolResult).substring(0, 2000)
1367
- : String(toolResult).substring(0, 2000),
1498
+ // `async:true` PostToolUse hooks must NOT block the tool loop — split
1499
+ // them off the synchronous decision path. Without this an async hook
1500
+ // ran synchronously here, defeating fire-and-forget (a background lint /
1501
+ // test after each edit would stall the turn).
1502
+ const { sync, async: asyncHooks } = partitionAsyncHooks(matched);
1503
+ const payload = {
1504
+ hook_event_name: "PostToolUse",
1505
+ tool_name: umbrellaFor(name),
1506
+ raw_tool_name: name,
1507
+ tool_input: args,
1508
+ tool_response:
1509
+ typeof toolResult === "object"
1510
+ ? JSON.stringify(toolResult).substring(0, 2000)
1511
+ : String(toolResult).substring(0, 2000),
1512
+ cwd,
1513
+ session_id: context.sessionId || null,
1514
+ };
1515
+ if (sync.length > 0) {
1516
+ const outcome = runCommandHooks(sync, payload, {
1368
1517
  cwd,
1369
- session_id: context.sessionId || null,
1370
- },
1371
- { cwd, event: "PostToolUse" },
1372
- );
1373
- if (outcome.decision === "block" && outcome.reason) {
1374
- toolResult.hookFeedback = outcome.reason;
1518
+ event: "PostToolUse",
1519
+ });
1520
+ if (outcome.decision === "block" && outcome.reason) {
1521
+ toolResult.hookFeedback = outcome.reason;
1522
+ }
1523
+ }
1524
+ // Fire-and-forget the async hooks onto the REPL-owned supervisor when
1525
+ // one is wired; results/rewakes drain into the next turn's context.
1526
+ if (asyncHooks.length > 0 && context.hookSupervisor) {
1527
+ context.hookSupervisor.dispatch(asyncHooks, payload, { cwd });
1375
1528
  }
1376
1529
  }
1377
1530
  } catch (_err) {
@@ -1482,6 +1635,58 @@ export function writeFileVerified(filePath, content, fsImpl = fs) {
1482
1635
  return { size: actual };
1483
1636
  }
1484
1637
 
1638
+ // ── Edit-concurrency (read-freshness) guard ──────────────────────────────
1639
+ //
1640
+ // Per-session map: resolved file path → the mtimeMs the agent last OBSERVED for
1641
+ // it (via read_file, or the write/edit it just made). If a file's on-disk mtime
1642
+ // is NEWER than that when the agent tries to edit it, an external process wrote
1643
+ // the file between the agent's read and its edit — so the edit is refused and
1644
+ // the agent is told to re-read, instead of clobbering the concurrent change on
1645
+ // top of a stale understanding. A file the agent never observed is NOT blocked
1646
+ // (first-touch / create stays frictionless). Disable with CC_EDIT_FRESHNESS=0.
1647
+ const _fileObservedMtimes = new Map();
1648
+
1649
+ /** Record the current mtime of a file the agent just read or wrote. */
1650
+ function _recordFileObservation(filePath, fsImpl = fs) {
1651
+ try {
1652
+ _fileObservedMtimes.set(filePath, fsImpl.statSync(filePath).mtimeMs);
1653
+ } catch {
1654
+ /* best-effort — a stat failure just means no freshness baseline */
1655
+ }
1656
+ }
1657
+
1658
+ /**
1659
+ * Return an error string when `filePath` changed on disk since the agent last
1660
+ * observed it (external concurrent edit), else null (fresh, un-observed, or
1661
+ * disabled).
1662
+ */
1663
+ function _checkFileFreshness(filePath, fsImpl = fs) {
1664
+ if (process.env.CC_EDIT_FRESHNESS === "0") return null;
1665
+ const known = _fileObservedMtimes.get(filePath);
1666
+ if (known === undefined) return null; // never observed → don't block
1667
+ let current;
1668
+ try {
1669
+ current = fsImpl.statSync(filePath).mtimeMs;
1670
+ } catch {
1671
+ return null;
1672
+ }
1673
+ if (current > known) {
1674
+ return (
1675
+ `File changed on disk since you last read it: ${filePath}. Another ` +
1676
+ `process modified it after your last read — re-read the file before ` +
1677
+ `editing so your change is based on its current content (this guard ` +
1678
+ `prevents silently clobbering a concurrent edit; set CC_EDIT_FRESHNESS=0 ` +
1679
+ `to disable).`
1680
+ );
1681
+ }
1682
+ return null;
1683
+ }
1684
+
1685
+ /** Clear the read-freshness map (new session / tests). */
1686
+ export function _resetFileFreshness() {
1687
+ _fileObservedMtimes.clear();
1688
+ }
1689
+
1485
1690
  // Short, stable-ish id for a freshly inserted notebook cell (nbformat 4.5+).
1486
1691
  function _newNotebookCellId() {
1487
1692
  return Math.random().toString(36).slice(2, 10);
@@ -1693,6 +1898,139 @@ export function _ingestSearchOutput(output, ctx) {
1693
1898
  /**
1694
1899
  * Inner tool execution — no hooks, no plan-mode checks.
1695
1900
  */
1901
+ // ─── Shared code-intelligence (LSP) pool ──────────────────────────────────
1902
+ //
1903
+ // A language server is expensive to start (spawns a process + indexes the
1904
+ // project), so the `code_intelligence` tool reuses ONE warm CodeIntelligence
1905
+ // per project root across tool calls within a run. To avoid the resource-leak
1906
+ // trap (orphaned server processes / dangling timers), each root auto-disposes
1907
+ // after an idle window, and a process-exit hook is registered once as a
1908
+ // backstop. `coldStart:true` makes the first query per file wait for the
1909
+ // project to load; warmed queries return immediately.
1910
+ const _codeIntelPool = new Map(); // root -> { ci, idleTimer }
1911
+ const CODE_INTEL_IDLE_MS = 60_000;
1912
+ let _codeIntelExitHooked = false;
1913
+
1914
+ export async function _getSharedCodeIntel(cwd) {
1915
+ const root = path.resolve(cwd || process.cwd());
1916
+ let entry = _codeIntelPool.get(root);
1917
+ if (!entry) {
1918
+ const { CodeIntelligence } =
1919
+ await import("../lib/lsp/code-intelligence.js");
1920
+ // Re-check AFTER the await: two concurrent callers (the parallel read-only
1921
+ // batch runs several `code_intelligence` calls at once) both observed an
1922
+ // empty pool before this `await import`, so without this guard both would
1923
+ // construct a CodeIntelligence — each spawning its own language server — and
1924
+ // the second `set` would orphan the first (leaked server process) while its
1925
+ // stale idle-timer, still bound to `root`, later evicts the wrong (warm,
1926
+ // in-use) entry. This is exactly the orphaned-server/dangling-timer trap the
1927
+ // pool exists to prevent. Construction below has NO further await, so once
1928
+ // one continuation wins the re-check the other reuses its entry — the window
1929
+ // is fully closed.
1930
+ entry = _codeIntelPool.get(root);
1931
+ if (!entry) {
1932
+ entry = {
1933
+ ci: new CodeIntelligence({ projectRoot: root, coldStart: true }),
1934
+ idleTimer: null,
1935
+ };
1936
+ _codeIntelPool.set(root, entry);
1937
+ if (!_codeIntelExitHooked) {
1938
+ _codeIntelExitHooked = true;
1939
+ process.once("exit", () => {
1940
+ for (const e of _codeIntelPool.values()) {
1941
+ try {
1942
+ e.ci.dispose();
1943
+ } catch {
1944
+ /* best-effort teardown on exit */
1945
+ }
1946
+ }
1947
+ });
1948
+ }
1949
+ }
1950
+ }
1951
+ if (entry.idleTimer) clearTimeout(entry.idleTimer);
1952
+ entry.idleTimer = setTimeout(() => {
1953
+ _codeIntelPool.delete(root);
1954
+ entry.ci.dispose().catch(() => {});
1955
+ }, CODE_INTEL_IDLE_MS);
1956
+ if (typeof entry.idleTimer.unref === "function") entry.idleTimer.unref();
1957
+ return entry.ci;
1958
+ }
1959
+
1960
+ /** Dispose every pooled language server now (used by tests + explicit shutdown). */
1961
+ export async function disposeSharedCodeIntel() {
1962
+ const entries = [..._codeIntelPool.values()];
1963
+ _codeIntelPool.clear();
1964
+ for (const e of entries) {
1965
+ if (e.idleTimer) clearTimeout(e.idleTimer);
1966
+ }
1967
+ await Promise.all(entries.map((e) => e.ci.dispose().catch(() => {})));
1968
+ }
1969
+
1970
+ // Hard wall-clock cap so post-edit diagnostics never stall the agent: a cold
1971
+ // language server can take seconds to load a project on the FIRST edit; after
1972
+ // that the server is warm and answers in well under a second.
1973
+ const EDIT_DIAGNOSTICS_WALL_MS = 6000;
1974
+
1975
+ /**
1976
+ * Best-effort "did this edit introduce a new error?" check. Run after a
1977
+ * successful workspace edit so the model sees fresh type/syntax errors in the
1978
+ * SAME turn instead of discovering them on the next build (the plan's
1979
+ * "编辑后执行增量诊断并将相关错误回喂 Agent"). Returns a compact array of
1980
+ * error/warning diagnostics, or null.
1981
+ *
1982
+ * Zero cost when no language server is installed for the file (probes first,
1983
+ * never cold-starts a server that isn't there) and fully bounded in time.
1984
+ * Disable entirely with CC_EDIT_DIAGNOSTICS=0.
1985
+ */
1986
+ async function _postEditDiagnostics(filePath, cwd) {
1987
+ if (process.env.CC_EDIT_DIAGNOSTICS === "0") return null;
1988
+ let hasServer = false;
1989
+ try {
1990
+ const { languageIdForFile, resolveServer } =
1991
+ await import("../lib/lsp/lsp-server-registry.js");
1992
+ const languageId = languageIdForFile(filePath);
1993
+ if (!languageId) return null; // unsupported file type — no LSP, no cost
1994
+ hasServer = Boolean(
1995
+ resolveServer(languageId, path.resolve(cwd || process.cwd())),
1996
+ );
1997
+ } catch {
1998
+ return null;
1999
+ }
2000
+ if (!hasServer) return null;
2001
+
2002
+ const query = (async () => {
2003
+ try {
2004
+ const ci = await _getSharedCodeIntel(cwd);
2005
+ const res = await ci.refreshFile(filePath, { timeoutMs: 3000 });
2006
+ if (!res || res.available === false) return null;
2007
+ const diags = (res.diagnostics || []).filter(
2008
+ (d) => d.severity === "error" || d.severity === "warning",
2009
+ );
2010
+ return diags.length ? diags.slice(0, 20) : null;
2011
+ } catch {
2012
+ return null;
2013
+ }
2014
+ })();
2015
+
2016
+ let capTimer;
2017
+ const cap = new Promise((resolve) => {
2018
+ capTimer = setTimeout(() => resolve(null), EDIT_DIAGNOSTICS_WALL_MS);
2019
+ if (typeof capTimer.unref === "function") capTimer.unref();
2020
+ });
2021
+ try {
2022
+ return await Promise.race([query, cap]);
2023
+ } finally {
2024
+ clearTimeout(capTimer);
2025
+ }
2026
+ }
2027
+
2028
+ /** Merge post-edit diagnostics into a successful edit result (no-op if none). */
2029
+ async function _withPostEditDiagnostics(result, filePath, cwd) {
2030
+ const newDiagnostics = await _postEditDiagnostics(filePath, cwd);
2031
+ return newDiagnostics ? { ...result, newDiagnostics } : result;
2032
+ }
2033
+
1696
2034
  async function executeToolInner(
1697
2035
  name,
1698
2036
  args,
@@ -1717,6 +2055,7 @@ async function executeToolInner(
1717
2055
  subAgentDepth = 0,
1718
2056
  subAgentBudget = null,
1719
2057
  interactiveApproval = false,
2058
+ settingsHooks = null,
1720
2059
  },
1721
2060
  ) {
1722
2061
  const localToolDescriptor =
@@ -1769,6 +2108,9 @@ async function executeToolInner(
1769
2108
  });
1770
2109
  }
1771
2110
  const content = fs.readFileSync(filePath, "utf8");
2111
+ // Record the mtime so a later edit can detect an external change that
2112
+ // happened between this read and the edit (read-freshness guard).
2113
+ _recordFileObservation(filePath);
1772
2114
  // Jupyter notebooks: render a compact cell listing (index/id/type/source,
1773
2115
  // outputs summarized) so the model can find cells for notebook_edit
1774
2116
  // without ingesting raw JSON / base64 output blobs. `raw:true` returns the
@@ -1832,16 +2174,25 @@ async function executeToolInner(
1832
2174
  case "write_file": {
1833
2175
  const filePath = path.resolve(cwd, args.path);
1834
2176
  const dir = path.dirname(filePath);
2177
+ // Overwriting an existing file: refuse if it changed on disk since the
2178
+ // agent last observed it (external concurrent edit).
2179
+ if (fs.existsSync(filePath)) {
2180
+ const stale = _checkFileFreshness(filePath);
2181
+ if (stale) return attachDescriptor({ error: stale });
2182
+ }
1835
2183
  if (!fs.existsSync(dir)) {
1836
2184
  fs.mkdirSync(dir, { recursive: true });
1837
2185
  }
1838
2186
  const wrote = writeFileVerified(filePath, args.content);
1839
2187
  if (wrote.error) return attachDescriptor({ error: wrote.error });
1840
- return attachDescriptor({
1841
- success: true,
1842
- path: filePath,
1843
- size: wrote.size,
1844
- });
2188
+ _recordFileObservation(filePath);
2189
+ return attachDescriptor(
2190
+ await _withPostEditDiagnostics(
2191
+ { success: true, path: filePath, size: wrote.size },
2192
+ filePath,
2193
+ cwd,
2194
+ ),
2195
+ );
1845
2196
  }
1846
2197
 
1847
2198
  case "notebook_edit": {
@@ -1876,6 +2227,8 @@ async function executeToolInner(
1876
2227
  if (typeof args.new_string !== "string") {
1877
2228
  return attachDescriptor({ error: "new_string must be a string" });
1878
2229
  }
2230
+ const staleEdit = _checkFileFreshness(filePath);
2231
+ if (staleEdit) return attachDescriptor({ error: staleEdit });
1879
2232
  const content = fs.readFileSync(filePath, "utf8");
1880
2233
  const replaceAll = args.replace_all === true;
1881
2234
  const { count, newContent } = applyLiteralEdit(
@@ -1897,12 +2250,19 @@ async function executeToolInner(
1897
2250
  }
1898
2251
  const wrote = writeFileVerified(filePath, newContent);
1899
2252
  if (wrote.error) return attachDescriptor({ error: wrote.error });
1900
- return attachDescriptor({
1901
- success: true,
1902
- path: filePath,
1903
- size: wrote.size,
1904
- replaced: replaceAll ? count : 1,
1905
- });
2253
+ _recordFileObservation(filePath);
2254
+ return attachDescriptor(
2255
+ await _withPostEditDiagnostics(
2256
+ {
2257
+ success: true,
2258
+ path: filePath,
2259
+ size: wrote.size,
2260
+ replaced: replaceAll ? count : 1,
2261
+ },
2262
+ filePath,
2263
+ cwd,
2264
+ ),
2265
+ );
1906
2266
  }
1907
2267
 
1908
2268
  case "edit_file_hashed": {
@@ -1922,6 +2282,8 @@ async function executeToolInner(
1922
2282
  if (typeof args.new_line !== "string") {
1923
2283
  return attachDescriptor({ error: "new_line must be a string" });
1924
2284
  }
2285
+ const staleHashed = _checkFileFreshness(filePath);
2286
+ if (staleHashed) return attachDescriptor({ error: staleHashed });
1925
2287
  const original = fs.readFileSync(filePath, "utf8");
1926
2288
  const result = replaceByHash(original, {
1927
2289
  anchorHash: args.anchor_hash,
@@ -1946,13 +2308,20 @@ async function executeToolInner(
1946
2308
  }
1947
2309
  const wrote = writeFileVerified(filePath, result.content);
1948
2310
  if (wrote.error) return attachDescriptor({ error: wrote.error });
1949
- return attachDescriptor({
1950
- success: true,
1951
- path: filePath,
1952
- size: wrote.size,
1953
- lineNumber: result.lineNumber,
1954
- previousContent: result.previousContent,
1955
- });
2311
+ _recordFileObservation(filePath);
2312
+ return attachDescriptor(
2313
+ await _withPostEditDiagnostics(
2314
+ {
2315
+ success: true,
2316
+ path: filePath,
2317
+ size: wrote.size,
2318
+ lineNumber: result.lineNumber,
2319
+ previousContent: result.previousContent,
2320
+ },
2321
+ filePath,
2322
+ cwd,
2323
+ ),
2324
+ );
1956
2325
  }
1957
2326
 
1958
2327
  case "run_shell": {
@@ -2108,6 +2477,9 @@ async function executeToolInner(
2108
2477
  task.endedAt = new Date().toISOString();
2109
2478
  }
2110
2479
  _backgroundShellTasks.set(id, task);
2480
+ // Arm the process-exit net so a Ctrl-C / hard exit can't orphan this
2481
+ // task (the normal `finally` reaper is bypassed by signals).
2482
+ _ensureBgExitReaper();
2111
2483
  return attachDescriptor(
2112
2484
  {
2113
2485
  background: true,
@@ -2125,20 +2497,65 @@ async function executeToolInner(
2125
2497
  if (sandbox) {
2126
2498
  const { executeSandboxedShell, sandboxSummary } =
2127
2499
  await import("../lib/agent-sandbox.js");
2128
- const result = executeSandboxedShell(args.command, sandbox, {
2129
- cwd: args.cwd || cwd,
2130
- timeout: _resolveShellTimeout(args.timeout),
2131
- maxBuffer: 1024 * 1024,
2132
- env: {
2133
- CLAUDECODE: "1",
2134
- ...(sessionId
2135
- ? {
2136
- CC_SESSION_ID: String(sessionId),
2137
- CLAUDE_CODE_SESSION_ID: String(sessionId),
2138
- }
2139
- : {}),
2140
- },
2141
- });
2500
+ // Domain-restricted networking is ENFORCED by routing the sandboxed
2501
+ // process's egress through a local filtering proxy (see
2502
+ // sandbox-egress-proxy.js). Start it only when the policy actually
2503
+ // restricts domains and network is on; tear it down after the command.
2504
+ const sboxPolicy = sandbox.policy || {};
2505
+ const needsEgress =
2506
+ sandbox.network === true &&
2507
+ (sboxPolicy.allowedDomains?.length || 0) +
2508
+ (sboxPolicy.deniedDomains?.length || 0) >
2509
+ 0;
2510
+ let egressProxy = null;
2511
+ let proxyHandle = null;
2512
+ if (needsEgress) {
2513
+ try {
2514
+ const { createEgressProxy } =
2515
+ await import("../lib/sandbox-egress-proxy.js");
2516
+ proxyHandle = createEgressProxy(
2517
+ {
2518
+ allowedDomains: sboxPolicy.allowedDomains || [],
2519
+ deniedDomains: sboxPolicy.deniedDomains || [],
2520
+ allowPrivate: sboxPolicy.allowPrivate === true,
2521
+ },
2522
+ { bindHost: "0.0.0.0" }, // reachable from the container/netns
2523
+ );
2524
+ const listened = await proxyHandle.listen();
2525
+ egressProxy = { port: listened.port };
2526
+ } catch {
2527
+ // If the proxy can't start, leave egressProxy null so the sandbox
2528
+ // fails closed (refuses) rather than running without enforcement.
2529
+ proxyHandle = null;
2530
+ egressProxy = null;
2531
+ }
2532
+ }
2533
+ let result;
2534
+ try {
2535
+ result = executeSandboxedShell(args.command, sandbox, {
2536
+ cwd: args.cwd || cwd,
2537
+ timeout: _resolveShellTimeout(args.timeout),
2538
+ maxBuffer: 1024 * 1024,
2539
+ egressProxy,
2540
+ env: {
2541
+ CLAUDECODE: "1",
2542
+ ...(sessionId
2543
+ ? {
2544
+ CC_SESSION_ID: String(sessionId),
2545
+ CLAUDE_CODE_SESSION_ID: String(sessionId),
2546
+ }
2547
+ : {}),
2548
+ },
2549
+ });
2550
+ } finally {
2551
+ if (proxyHandle) {
2552
+ try {
2553
+ await proxyHandle.close();
2554
+ } catch {
2555
+ /* best-effort teardown */
2556
+ }
2557
+ }
2558
+ }
2142
2559
  const common = {
2143
2560
  sandbox: sandboxSummary(sandbox),
2144
2561
  shellCommandPolicy: shellPolicy,
@@ -2199,6 +2616,14 @@ async function executeToolInner(
2199
2616
  return attachDescriptor(
2200
2617
  {
2201
2618
  error: err.message.substring(0, 2000),
2619
+ // Surface stdout too: a failing command (test runner / linter /
2620
+ // build) usually prints WHAT failed to stdout and only the summary
2621
+ // to stderr, so dropping it on non-zero exit blinds the agent to the
2622
+ // actual failure. Only attach it when there IS output (a timeout with
2623
+ // no output keeps the field absent), mirroring the success path.
2624
+ ...(err.stdout
2625
+ ? { stdout: String(err.stdout).substring(0, 30000) }
2626
+ : {}),
2202
2627
  stderr: (err.stderr || "").substring(0, 2000),
2203
2628
  exitCode: err.status,
2204
2629
  shellCommandPolicy: shellPolicy,
@@ -2351,6 +2776,7 @@ async function executeToolInner(
2351
2776
  llmOptions,
2352
2777
  subAgentDepth,
2353
2778
  subAgentBudget,
2779
+ settingsHooks,
2354
2780
  }),
2355
2781
  );
2356
2782
  }
@@ -2615,6 +3041,95 @@ async function executeToolInner(
2615
3041
  return attachDescriptor(isContent ? { matches: hits } : { files: hits });
2616
3042
  }
2617
3043
 
3044
+ case "code_intelligence": {
3045
+ const action = String(args.action || "").trim();
3046
+ const positionActions = new Set([
3047
+ "definition",
3048
+ "references",
3049
+ "hover",
3050
+ "rename_preview",
3051
+ ]);
3052
+ // Validate up front so the model gets a precise correction instead of a
3053
+ // cryptic crash deep in the LSP layer.
3054
+ if (action !== "workspace_symbols" && !args.file) {
3055
+ return attachDescriptor({
3056
+ error: `code_intelligence action "${action}" requires "file".`,
3057
+ });
3058
+ }
3059
+ if (
3060
+ positionActions.has(action) &&
3061
+ (args.line == null || args.col == null)
3062
+ ) {
3063
+ return attachDescriptor({
3064
+ error: `code_intelligence action "${action}" requires 1-based "line" and "col".`,
3065
+ });
3066
+ }
3067
+ if (action === "workspace_symbols" && !args.query) {
3068
+ return attachDescriptor({
3069
+ error: `code_intelligence action "workspace_symbols" requires "query".`,
3070
+ });
3071
+ }
3072
+ if (action === "rename_preview" && !args.new_name) {
3073
+ return attachDescriptor({
3074
+ error: `code_intelligence action "rename_preview" requires "new_name".`,
3075
+ });
3076
+ }
3077
+ const ci = await _getSharedCodeIntel(cwd);
3078
+ const file = args.file ? path.resolve(cwd, args.file) : null;
3079
+ let res;
3080
+ try {
3081
+ switch (action) {
3082
+ case "definition":
3083
+ res = await ci.definition(file, args.line, args.col);
3084
+ break;
3085
+ case "references":
3086
+ res = await ci.references(file, args.line, args.col);
3087
+ break;
3088
+ case "hover":
3089
+ res = await ci.hover(file, args.line, args.col);
3090
+ break;
3091
+ case "document_symbols":
3092
+ res = await ci.documentSymbols(file);
3093
+ break;
3094
+ case "workspace_symbols":
3095
+ res = await ci.workspaceSymbols(String(args.query));
3096
+ break;
3097
+ case "diagnostics":
3098
+ res = await ci.diagnostics(file);
3099
+ break;
3100
+ case "rename_preview":
3101
+ res = await ci.renamePreview(
3102
+ file,
3103
+ args.line,
3104
+ args.col,
3105
+ args.new_name,
3106
+ );
3107
+ break;
3108
+ default:
3109
+ return attachDescriptor({
3110
+ error:
3111
+ `Unknown code_intelligence action "${action}". Valid: definition, ` +
3112
+ `references, hover, document_symbols, workspace_symbols, ` +
3113
+ `diagnostics, rename_preview.`,
3114
+ });
3115
+ }
3116
+ } catch (err) {
3117
+ return attachDescriptor({
3118
+ error: `code_intelligence failed: ${err.message}`,
3119
+ });
3120
+ }
3121
+ // No language server installed for this file — tell the agent to fall back
3122
+ // rather than looping on an empty result.
3123
+ if (res && res.available === false) {
3124
+ return attachDescriptor({
3125
+ unavailable: true,
3126
+ reason: res.reason,
3127
+ hint: "No language server available — use search_files / read_file instead.",
3128
+ });
3129
+ }
3130
+ return attachDescriptor(res);
3131
+ }
3132
+
2618
3133
  case "list_dir": {
2619
3134
  const dirPath = args.path ? path.resolve(cwd, args.path) : cwd;
2620
3135
  if (!fs.existsSync(dirPath)) {
@@ -3296,6 +3811,39 @@ async function _executeSpawnSubAgent(args, ctx) {
3296
3811
  const parentSessionId = ctx.sessionId || null;
3297
3812
  const interaction = ctx.interaction || null;
3298
3813
 
3814
+ // settings.json SubagentStart hooks (Claude-Code parity): fire BEFORE the
3815
+ // sub-agent runs, so a policy hook can VETO the spawn (`block`) or INJECT
3816
+ // extra context that gets prepended to the child's inherited context. This is
3817
+ // the mirror of the existing SubagentStop fire (which runs after the summary
3818
+ // returns). Best-effort — a hook error never blocks the spawn.
3819
+ if (ctx.settingsHooks) {
3820
+ try {
3821
+ const startOutcome = runObserveHooks(
3822
+ ctx.settingsHooks,
3823
+ "SubagentStart",
3824
+ {
3825
+ session_id: parentSessionId,
3826
+ role: role || args.agent || null,
3827
+ subagent_task: String(task || "").substring(0, 2000),
3828
+ },
3829
+ { cwd: ctx.cwd },
3830
+ );
3831
+ if (startOutcome.decision === "block" && startOutcome.reason) {
3832
+ return {
3833
+ error: `spawn_sub_agent blocked by SubagentStart hook: ${startOutcome.reason}`,
3834
+ };
3835
+ }
3836
+ const injected = aggregateContext(startOutcome.results);
3837
+ if (injected) {
3838
+ resolvedContext = resolvedContext
3839
+ ? `${resolvedContext}\n---\n${injected}`
3840
+ : injected;
3841
+ }
3842
+ } catch (_err) {
3843
+ // SubagentStart hooks are best-effort — never break the spawn.
3844
+ }
3845
+ }
3846
+
3299
3847
  // Inherit the parent's provider / base-url / key; a named subagent's `model:`
3300
3848
  // frontmatter (mdModel) overrides just the model, else keep the parent's.
3301
3849
  const parentLlm = ctx.llmOptions || {};
@@ -4629,6 +5177,7 @@ export function _normalizeAnthropicResponse(data) {
4629
5177
  const _CHECKPOINT_READ_ONLY = new Set([
4630
5178
  "read_file",
4631
5179
  "search_files",
5180
+ "code_intelligence",
4632
5181
  "list_dir",
4633
5182
  "list_skills",
4634
5183
  "search_sessions",
@@ -4724,6 +5273,54 @@ async function _getAutoCompactor(options) {
4724
5273
  return compressor;
4725
5274
  }
4726
5275
 
5276
+ /**
5277
+ * Run `fn` inside an OpenTelemetry span when `options.recorder` is attached,
5278
+ * else run it bare (zero overhead on the un-instrumented path). `onResult`
5279
+ * gets (span, result) to stamp result-derived attributes (token usage, tool
5280
+ * status). An exception is recorded on the span (category `errCategory`) and
5281
+ * re-thrown so the loop's own error handling is unchanged. Kept dependency-free
5282
+ * — the recorder is the OTel-shaped TelemetryRecorder passed in by the caller
5283
+ * (eval / a future `--otlp` agent flag); the real agent loop now EMITS
5284
+ * model/tool/retry spans, not just eval.
5285
+ */
5286
+ async function _withSpan(recorder, name, attrs, fn, onResult, errCategory) {
5287
+ if (!recorder || typeof recorder.startSpan !== "function") return fn();
5288
+ const span = recorder.startSpan(name, attrs);
5289
+ try {
5290
+ const r = await fn();
5291
+ if (onResult) {
5292
+ try {
5293
+ onResult(span, r);
5294
+ } catch {
5295
+ /* attribute stamping is best-effort */
5296
+ }
5297
+ }
5298
+ span.end();
5299
+ return r;
5300
+ } catch (err) {
5301
+ try {
5302
+ span.recordException(err, errCategory || "error");
5303
+ span.end();
5304
+ } catch {
5305
+ /* span teardown is best-effort */
5306
+ }
5307
+ throw err;
5308
+ }
5309
+ }
5310
+
5311
+ /** Normalize the varied provider usage shapes into input/output/cache tokens. */
5312
+ function _usageTokens(usage) {
5313
+ if (!usage || typeof usage !== "object") return null;
5314
+ const input = usage.prompt_tokens ?? usage.input_tokens ?? null;
5315
+ const output = usage.completion_tokens ?? usage.output_tokens ?? null;
5316
+ const cacheRead =
5317
+ usage.cache_read_input_tokens ??
5318
+ usage.prompt_tokens_details?.cached_tokens ??
5319
+ null;
5320
+ const cacheWrite = usage.cache_creation_input_tokens ?? null;
5321
+ return { input, output, cacheRead, cacheWrite };
5322
+ }
5323
+
4727
5324
  export async function* agentLoop(messages, options) {
4728
5325
  // Shared iteration budget — replaces hardcoded MAX_ITERATIONS.
4729
5326
  // When options.iterationBudget is provided (e.g. from parent agent),
@@ -4732,6 +5329,9 @@ export async function* agentLoop(messages, options) {
4732
5329
  await import("../lib/iteration-budget.js");
4733
5330
  const budget = options.iterationBudget || new IterationBudget();
4734
5331
  const signal = options.signal || null;
5332
+ // Optional OpenTelemetry recorder (TelemetryRecorder). When present, the loop
5333
+ // emits model/tool/retry spans + a per-run counter; when absent, zero cost.
5334
+ const recorder = options.recorder || null;
4735
5335
  const toolContext = {
4736
5336
  hookDb: options.hookDb || null,
4737
5337
  skillLoader: options.skillLoader || _defaultSkillLoader,
@@ -4768,6 +5368,10 @@ export async function* agentLoop(messages, options) {
4768
5368
  permissionRules: options.permissionRules || null,
4769
5369
  permissionConfirm: options.permissionConfirm || null,
4770
5370
  settingsHooks: options.settingsHooks || null,
5371
+ // Async-hook supervisor (REPL-owned): lets PostToolUse `async:true` hooks
5372
+ // run fire-and-forget instead of blocking the tool loop. Optional — when
5373
+ // absent, async PostToolUse hooks are simply skipped (never run sync).
5374
+ hookSupervisor: options.hookSupervisor || null,
4771
5375
  autoCheckpoint: options.autoCheckpoint || false,
4772
5376
  checkpointSession:
4773
5377
  options.checkpointSession || options.sessionId || "agent",
@@ -4849,6 +5453,9 @@ export async function* agentLoop(messages, options) {
4849
5453
  await import("../lib/runnable-provider.js");
4850
5454
  llmCall = makeRunnableProviderFallback(llmCall, {
4851
5455
  onFallback: ({ from, to, reason, fromModel, toModel }) => {
5456
+ // Telemetry: a provider/model fallback is a reliability signal — count
5457
+ // it (keyed by reason) so a run's OTLP export shows retry/fallback rate.
5458
+ if (recorder) recorder.counter("agent.model.fallback", 1, { reason });
4852
5459
  // Switching VENDORS (or relabelling via baseUrl) must NEVER be silent —
4853
5460
  // a user who configured volcengine deserves to know it ran on another
4854
5461
  // provider/model. Build a human message and hand it to the driver's
@@ -4994,10 +5601,22 @@ export async function* agentLoop(messages, options) {
4994
5601
  reason: preCompactReason,
4995
5602
  };
4996
5603
  }
5604
+ // Auto-pin (OPT-IN): when enabled, the original task (first user turn)
5605
+ // is pinned so compaction can't drop it. Off by default → no predicate
5606
+ // is passed and compaction is byte-identical to before.
5607
+ let pinOpts = {};
5608
+ if (options.autoPin) {
5609
+ const { buildAutoPinPredicate } = await import("./auto-pin.js");
5610
+ const isPinned = buildAutoPinPredicate(messages, options.autoPin);
5611
+ if (isPinned) pinOpts = { isPinned };
5612
+ }
4997
5613
  const { messages: compacted, stats } =
4998
5614
  !needFull || preCompactBlocked
4999
5615
  ? { messages, stats: { saved: 0 } }
5000
- : await compactor.compress(messages, { preserveToolPairs: true });
5616
+ : await compactor.compress(messages, {
5617
+ preserveToolPairs: true,
5618
+ ...pinOpts,
5619
+ });
5001
5620
  if (stats.saved > 0 && compacted.length < messages.length) {
5002
5621
  messages.splice(0, messages.length, ...compacted);
5003
5622
  // Persist the compaction so a later --resume rebuilds from the
@@ -5064,7 +5683,38 @@ export async function* agentLoop(messages, options) {
5064
5683
  }
5065
5684
  }
5066
5685
 
5067
- const result = await llmCall(callMessages, options);
5686
+ const result = await _withSpan(
5687
+ recorder,
5688
+ "agent.model",
5689
+ {
5690
+ "gen_ai.system": options.provider || "ollama",
5691
+ "gen_ai.request.model": options.model || "unknown",
5692
+ "agent.iteration": budget.consumed,
5693
+ },
5694
+ () => llmCall(callMessages, options),
5695
+ (span, r) => {
5696
+ const t = _usageTokens(r?.usage);
5697
+ if (t) {
5698
+ if (t.input != null)
5699
+ span.setAttribute("gen_ai.usage.input_tokens", t.input);
5700
+ if (t.output != null)
5701
+ span.setAttribute("gen_ai.usage.output_tokens", t.output);
5702
+ // Cache read/write tokens — the prompt-caching hit rate the plan's
5703
+ // reliability telemetry cares about.
5704
+ if (t.cacheRead != null)
5705
+ span.setAttribute("gen_ai.usage.cache_read_tokens", t.cacheRead);
5706
+ if (t.cacheWrite != null)
5707
+ span.setAttribute("gen_ai.usage.cache_write_tokens", t.cacheWrite);
5708
+ }
5709
+ span.setAttribute(
5710
+ "agent.has_tool_calls",
5711
+ Array.isArray(r?.message?.tool_calls) &&
5712
+ r.message.tool_calls.length > 0,
5713
+ );
5714
+ },
5715
+ "model_error",
5716
+ );
5717
+ if (recorder) recorder.counter("agent.model.calls", 1);
5068
5718
  throwIfAborted(signal);
5069
5719
  const msg = result?.message;
5070
5720
 
@@ -5303,11 +5953,24 @@ export async function* agentLoop(messages, options) {
5303
5953
  let toolResult;
5304
5954
  let toolError = null;
5305
5955
  try {
5306
- toolResult = await executeTool(toolName, toolArgs, toolContext);
5956
+ toolResult = await _withSpan(
5957
+ recorder,
5958
+ "agent.tool",
5959
+ { "tool.name": toolName },
5960
+ () => executeTool(toolName, toolArgs, toolContext),
5961
+ (span, r) => {
5962
+ span.setAttribute(
5963
+ "tool.is_error",
5964
+ !!(r && typeof r === "object" && r.error),
5965
+ );
5966
+ },
5967
+ "tool_error",
5968
+ );
5307
5969
  } catch (err) {
5308
5970
  toolResult = { error: err.message };
5309
5971
  toolError = err.message;
5310
5972
  }
5973
+ if (recorder) recorder.counter("agent.tool.calls", 1);
5311
5974
 
5312
5975
  throwIfAborted(signal);
5313
5976
 
@@ -5371,6 +6034,10 @@ export function formatToolArgs(name, args) {
5371
6034
  return args.command;
5372
6035
  case "search_files":
5373
6036
  return args.pattern;
6037
+ case "code_intelligence":
6038
+ return args.action === "workspace_symbols"
6039
+ ? `${args.action} ${args.query || ""}`.trim()
6040
+ : `${args.action} ${args.file || ""}${args.line != null ? `:${args.line}:${args.col}` : ""}`.trim();
5374
6041
  case "list_dir":
5375
6042
  return args.path || ".";
5376
6043
  case "run_skill":