chainlesschain 0.162.148 → 0.162.149

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 (231) 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-BJif14yR.js} +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-C9pBYocA.js → ActionButton-CXek6gJY.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-BWAkeXxK.js → Analytics-BgQhdAJi.js} +2 -2
  7. package/src/assets/web-panel/assets/{AppLayout-CivFGH6B.js → AppLayout-DTYl5NtR.js} +3 -3
  8. package/src/assets/web-panel/assets/{Audit-Cm8hRpZm.js → Audit-BzATQAeR.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-DXdFMsE8.js → Backup-BzdPEQhL.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-uAwSTeql.js → BaseInput-BM0KVh8E.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-KU8eeJJE.js → Chat-D7fHXHkz.js} +4 -4
  12. package/src/assets/web-panel/assets/ChatBubbleRenderer-Wm34RR-b.js +1 -0
  13. package/src/assets/web-panel/assets/{Checkbox-C6AMDkjd.js → Checkbox-CAiSQ9vO.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-t2moDxcO.js → Codegen-Df40CrEe.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-DCcsRRhN.js → Col-DIT2fNFU.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-DtB-8SAC.js → Community-CSTIbW2k.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-CZm8THYA.js → Compact-DtqXZZUi.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-LiQgC7g1.js → Compliance-CUtzw6o7.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-CA8SAxht.js → Cowork-CezmlnmU.js} +2 -2
  20. package/src/assets/web-panel/assets/{Cron-CtRaF1wD.js → Cron-P4BITarg.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-6-br6Hub.js → Crosschain-BRRiYwvq.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-Do2EccrL.js → DID-L5ijB9i4.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-wrXcbzGe.js → Dashboard-CFxro4ob.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-p3FsT245.js → Dropdown-BmAFCQ71.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-D95A2L8q.js → EmailListRenderer-DJBCEuon.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-cLuJW2zo.js → FamilyGuardDashboard-z4oLq6eT.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-Brgq_cbN.js → Federation-DsVCpHMF.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-DWLCkNgh.js → FormItemContext-DP3bP5th.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-BBqUfQd6.js → GenericCardRenderer-BAXP2Gc6.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-8F090w4I.js → Git-I3g_bAw9.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-CCogEbxb.js → Governance-jHS5-ioS.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-CfLD7v7C.js → Inference-CPL7sfx9.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-DDuHJewd.js → KnowledgeGraph-Jf236h4C.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-CE8KSEVC.js → Logs-Cm2tcSKg.js} +2 -2
  35. package/src/assets/web-panel/assets/{Marketplace-DmwpZmhT.js → Marketplace-CZK82rhi.js} +1 -1
  36. package/src/assets/web-panel/assets/{McpTools-Bf7AazU8.js → McpTools-CZCeji7a.js} +4 -4
  37. package/src/assets/web-panel/assets/{Memory-D0QU_00S.js → Memory-3l2KVS9k.js} +2 -2
  38. package/src/assets/web-panel/assets/{MobileBridge-yXS9xdhx.js → MobileBridge-VYdpoLh6.js} +1 -1
  39. package/src/assets/web-panel/assets/{MobileProjects-BuDUoGUP.js → MobileProjects-CiB9cmm1.js} +1 -1
  40. package/src/assets/web-panel/assets/{Mtc-BYyHy28p.js → Mtc-wCFZbBuL.js} +2 -2
  41. package/src/assets/web-panel/assets/{MtcAudit-CK0aqpiZ.js → MtcAudit-1a3THIyG.js} +4 -4
  42. package/src/assets/web-panel/assets/{Multisig-mNimKYeb.js → Multisig-DMzLB2hG.js} +3 -3
  43. package/src/assets/web-panel/assets/{NLProgramming-AioAJ0YA.js → NLProgramming-D9wZbf6l.js} +1 -1
  44. package/src/assets/web-panel/assets/{Notes-CVVNCLHE.js → Notes-hrFe2ZM-.js} +3 -3
  45. package/src/assets/web-panel/assets/{NotificationSettings-C4ABj-TK.js → NotificationSettings-CHGX5Jwm.js} +1 -1
  46. package/src/assets/web-panel/assets/OrderTableRenderer-CRIFE2Tj.js +1 -0
  47. package/src/assets/web-panel/assets/{Organization-uozl_gWK.js → Organization-By7FkhtY.js} +4 -4
  48. package/src/assets/web-panel/assets/{Overflow-BCZOM2hK.js → Overflow--khGSzJn.js} +1 -1
  49. package/src/assets/web-panel/assets/{P2P-CArcaiaj.js → P2P-CAG9dH35.js} +2 -2
  50. package/src/assets/web-panel/assets/{PdhVaultBrowser-Sgq2Srr8.js → PdhVaultBrowser-B3SQZmK9.js} +3 -3
  51. package/src/assets/web-panel/assets/{Permissions-BYrQrXnH.js → Permissions-D35ec6JA.js} +4 -4
  52. package/src/assets/web-panel/assets/{PersonalDataHub-BzHStAbz.js → PersonalDataHub-CxBF7hW_.js} +2 -2
  53. package/src/assets/web-panel/assets/{Pipeline-k7KqxX1M.js → Pipeline-s6EtOO1s.js} +1 -1
  54. package/src/assets/web-panel/assets/{Privacy-IumTUWqx.js → Privacy-BbJYmWmO.js} +1 -1
  55. package/src/assets/web-panel/assets/{ProjectInit-BRyImYTf.js → ProjectInit-VGQq1jMT.js} +2 -2
  56. package/src/assets/web-panel/assets/{ProjectSettings-BFIqTBFk.js → ProjectSettings-B1ew3Teh.js} +2 -2
  57. package/src/assets/web-panel/assets/Projects-CGcNIs_g.js +1 -0
  58. package/src/assets/web-panel/assets/{Providers-CyyFnUPs.js → Providers-mrO47FIK.js} +1 -1
  59. package/src/assets/web-panel/assets/{QrScannerModal-C82cRx-X.js → QrScannerModal-IJF2mHyw.js} +1 -1
  60. package/src/assets/web-panel/assets/{QuickAsk-DGxDF521.js → QuickAsk-CU0wN_Z0.js} +1 -1
  61. package/src/assets/web-panel/assets/{Recommend-Cns-Am1y.js → Recommend-DhN37R6G.js} +1 -1
  62. package/src/assets/web-panel/assets/{RemoteSession-R7OqAIlg.js → RemoteSession-D_xOX_hu.js} +2 -2
  63. package/src/assets/web-panel/assets/{Reputation-D7mgPIYV.js → Reputation-D_Ssv7uH.js} +1 -1
  64. package/src/assets/web-panel/assets/{Row-B3lEURyd.js → Row-Kx5dKxX7.js} +1 -1
  65. package/src/assets/web-panel/assets/{RssFeed-9_UVrpBt.js → RssFeed-BlGwqZkY.js} +2 -2
  66. package/src/assets/web-panel/assets/{Search-ClZeO80q.js → Search-hLFQzxpb.js} +1 -1
  67. package/src/assets/web-panel/assets/{Security-BNNJczEp.js → Security-DMlvsVt4.js} +3 -3
  68. package/src/assets/web-panel/assets/{Services-C5SjrWUj.js → Services-Bd0Qsj2m.js} +2 -2
  69. package/src/assets/web-panel/assets/{Skeleton-Ci_obQ-g.js → Skeleton-DFclq9X3.js} +1 -1
  70. package/src/assets/web-panel/assets/{Skills-DdebN15P.js → Skills-BNtln2qY.js} +1 -1
  71. package/src/assets/web-panel/assets/{Sla-OinZP2vi.js → Sla-Dch5H19G.js} +1 -1
  72. package/src/assets/web-panel/assets/{SpeechSettings-CxzbNF51.js → SpeechSettings-2UfQcU1g.js} +1 -1
  73. package/src/assets/web-panel/assets/{SyncSettings-D06N_66b.js → SyncSettings-CD9YQr4i.js} +2 -2
  74. package/src/assets/web-panel/assets/{Tasks-BdEdW0AZ.js → Tasks-BNu-7MzI.js} +1 -1
  75. package/src/assets/web-panel/assets/{Templates-F1ctKmPa.js → Templates-D1n9CaIR.js} +1 -1
  76. package/src/assets/web-panel/assets/{Tenant-CVz1Urot.js → Tenant-DenL3iBW.js} +1 -1
  77. package/src/assets/web-panel/assets/Terminal-ZzU0Io1Y.js +3 -0
  78. package/src/assets/web-panel/assets/{TimelineRenderer-Doitzjmf.js → TimelineRenderer-D87IfukO.js} +1 -1
  79. package/src/assets/web-panel/assets/{Tokens-BpyVRpVA.js → Tokens-BkiZc8E3.js} +1 -1
  80. package/src/assets/web-panel/assets/{Trigger-nWhWYTbU.js → Trigger-BNRSytGN.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trust-DK_G-wLx.js → Trust-Bwbd4X2m.js} +1 -1
  82. package/src/assets/web-panel/assets/{UkeySign-B5yBUojO.js → UkeySign-B8O2bs0o.js} +1 -1
  83. package/src/assets/web-panel/assets/{VideoEditing-C5D51qAe.js → VideoEditing-C2fL8zmH.js} +1 -1
  84. package/src/assets/web-panel/assets/{Wallet-CLbL9K7v.js → Wallet-BORYDdE1.js} +4 -4
  85. package/src/assets/web-panel/assets/{WebAuthn-DZUelI3V.js → WebAuthn-C0V5S2FM.js} +4 -4
  86. package/src/assets/web-panel/assets/{WorkflowEditor-8RPxt4KW.js → WorkflowEditor-2dR0fcHL.js} +1 -1
  87. package/src/assets/web-panel/assets/{chat-4N4tOA3d.js → chat-BmuAFAn8.js} +1 -1
  88. package/src/assets/web-panel/assets/{colors-zbbGVrua.js → colors-CN3smMcK.js} +1 -1
  89. package/src/assets/web-panel/assets/{compact-item-D7iMkbnE.js → compact-item-DCnxb4kW.js} +1 -1
  90. package/src/assets/web-panel/assets/{createContext-CBzPJv-w.js → createContext-vlR43pHn.js} +1 -1
  91. package/src/assets/web-panel/assets/devWarning-DxwmsL4j.js +1 -0
  92. package/src/assets/web-panel/assets/{hasIn-bHdrQSqZ.js → hasIn-D04tHYFd.js} +1 -1
  93. package/src/assets/web-panel/assets/{index-DbE5D0WL.js → index-0DnaZGkC.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-308gCGh7.js → index-430S68MN.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-kmh1TxRa.js → index-B78xL3s2.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-QFObYeo1.js → index-BTPEZTk1.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-BZb8F8YG.js → index-BXRg8GFQ.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-DE9j5YgT.js → index-BasvsWDM.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-CiG1IZao.js → index-BeGDEXFs.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-Bqmx24kh.js → index-BqhASEL4.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-BCr0geV9.js → index-BudvKQfG.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-S74FpAIn.js → index-C-6NO5il.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-DyoNgbXl.js → index-C5Sxb1sE.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-BtbbdLnf.js → index-CP9m9Ggr.js} +3 -3
  105. package/src/assets/web-panel/assets/{index-CkxK86vf.js → index-CTOCnIQ7.js} +1 -1
  106. package/src/assets/web-panel/assets/index-Cg2ldRfU.js +1 -0
  107. package/src/assets/web-panel/assets/{index-BTIjjXry.js → index-ClhAHDK3.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-B7b1hGry.js → index-CnXebZLL.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-CPxkoKgA.js → index-CngCC8hC.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-Ci009ijp.js → index-Cr-A0FYJ.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-CdmMoYN1.js → index-CsAsCPJ6.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-BBNr77E1.js → index-D28DeAAB.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-B1s0Bz9O.js → index-D5LZVZ0L.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-oDKNgCsP.js → index-D65_XseV.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-QNAG4JtR.js → index-D8vwp4qp.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-CQfu1U78.js → index-DDwLgQY9.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BnBOpqv3.js → index-DIpY0qM5.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-CQ6NcfWz.js → index-DTWg-18U.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-D4XKpj6c.js → index-DTtRscUa.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-D1YDh7Wr.js → index-DYAtmqiv.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-CvRMA9uT.js → index-D_n8_vfI.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-CahryTX0.js → index-DbADHwhr.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-BJ4ZGyW0.js → index-DbVsQBOH.js} +1 -1
  124. package/src/assets/web-panel/assets/index-Dwzo-dtJ.js +1 -0
  125. package/src/assets/web-panel/assets/{index-xYCm6W2h.js → index-HhNjnCfe.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-BS4_Mwk6.js → index-ToUh2b1-.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-DISWAYce.js → index-TslMTwNy.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-DmqlJed-.js → index-_FIkN3jd.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-CqdPyWm5.js → index-hpvvtAZ3.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-BsKehmmS.js → index-jzR7Ujuw.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-DUyjUkY7.js → index-ljuBiQW9.js} +1 -1
  132. package/src/assets/web-panel/assets/{initDefaultProps-BD55yNBt.js → initDefaultProps-C3wUb4je.js} +1 -1
  133. package/src/assets/web-panel/assets/{motion-B5Bbe77U.js → motion-D-jYFUNA.js} +1 -1
  134. package/src/assets/web-panel/assets/{move-CTvIqHEH.js → move-tqb8nwJ2.js} +1 -1
  135. package/src/assets/web-panel/assets/{mtc-parser-BkNyPQKB.js → mtc-parser-DSOjMJMz.js} +1 -1
  136. package/src/assets/web-panel/assets/{omit-BaXJXgHA.js → omit-CJ7VimIW.js} +1 -1
  137. package/src/assets/web-panel/assets/{pickAttrs-Bw6-YTxH.js → pickAttrs-qXiG1iT3.js} +1 -1
  138. package/src/assets/web-panel/assets/{placementArrow-BLdbkLqJ.js → placementArrow-CkGBcy1Z.js} +1 -1
  139. package/src/assets/web-panel/assets/{responsiveObserve-IVYkzntU.js → responsiveObserve-D3WvTlLO.js} +1 -1
  140. package/src/assets/web-panel/assets/{slide-DFMFwwtY.js → slide-BEE2ftge.js} +1 -1
  141. package/src/assets/web-panel/assets/{statusUtils-CURYRtZ1.js → statusUtils-CsfBpxiG.js} +1 -1
  142. package/src/assets/web-panel/assets/{styleChecker-Cfz63s1r.js → styleChecker-xPrIiJPI.js} +1 -1
  143. package/src/assets/web-panel/assets/{useFlexGapSupport-COJL0KE3.js → useFlexGapSupport-C5Yd-SCR.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFs-YLLojQQf.js → useFs-D_MDS8HI.js} +1 -1
  145. package/src/assets/web-panel/assets/{usePersonalDataHub-BF_21qUC.js → usePersonalDataHub-BqJh-usA.js} +1 -1
  146. package/src/assets/web-panel/assets/{vnode-9ZpkA7bb.js → vnode-chm_p-gz.js} +1 -1
  147. package/src/assets/web-panel/assets/{zoom-DhtAfhl8.js → zoom-3-YDNz0Z.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/agent.js +44 -5
  152. package/src/commands/codeintel.js +252 -0
  153. package/src/commands/eval.js +248 -0
  154. package/src/commands/plugin.js +354 -0
  155. package/src/commands/team.js +456 -0
  156. package/src/gateways/ws/message-dispatcher.js +45 -2
  157. package/src/gateways/ws/remote-session-protocol.js +37 -0
  158. package/src/gateways/ws/ws-server.js +1 -1
  159. package/src/harness/prompt-compressor.js +27 -1
  160. package/src/harness/remote-command-ledger.js +158 -0
  161. package/src/harness/remote-session-push-apns.js +246 -0
  162. package/src/harness/remote-session-push-errors.js +15 -0
  163. package/src/harness/remote-session-push-fcm.js +9 -25
  164. package/src/harness/remote-session-push-huawei.js +174 -0
  165. package/src/harness/remote-session-push-oppo.js +171 -0
  166. package/src/harness/remote-session-push-senders.js +42 -0
  167. package/src/harness/remote-session-push-vivo.js +179 -0
  168. package/src/harness/remote-session-push-web.js +299 -0
  169. package/src/harness/remote-session-push-xiaomi.js +99 -0
  170. package/src/index.js +6 -0
  171. package/src/lib/agent-core.js +2 -0
  172. package/src/lib/agent-sandbox.js +161 -5
  173. package/src/lib/agent-team/task-lease.js +434 -0
  174. package/src/lib/agent-team/team-budget.js +165 -0
  175. package/src/lib/agent-team/team-mailbox.js +106 -0
  176. package/src/lib/agent-team/team-runner.js +266 -0
  177. package/src/lib/agent-team/team-worktree.js +204 -0
  178. package/src/lib/agents.js +18 -0
  179. package/src/lib/async-hook-supervisor.cjs +305 -0
  180. package/src/lib/eval/runner.js +163 -0
  181. package/src/lib/eval/tasks.js +456 -0
  182. package/src/lib/eval/trend.js +185 -0
  183. package/src/lib/lsp/__tests__/code-intelligence.integration.test.js +129 -0
  184. package/src/lib/lsp/__tests__/code-intelligence.test.js +228 -0
  185. package/src/lib/lsp/__tests__/jsonrpc-stream.test.js +104 -0
  186. package/src/lib/lsp/__tests__/lsp-client.test.js +258 -0
  187. package/src/lib/lsp/__tests__/lsp-manager.test.js +264 -0
  188. package/src/lib/lsp/__tests__/lsp-server-registry.test.js +128 -0
  189. package/src/lib/lsp/code-intelligence.js +356 -0
  190. package/src/lib/lsp/jsonrpc-stream.js +139 -0
  191. package/src/lib/lsp/lsp-client.js +318 -0
  192. package/src/lib/lsp/lsp-manager.js +323 -0
  193. package/src/lib/lsp/lsp-server-registry.js +196 -0
  194. package/src/lib/plugin-monitor-supervisor.js +238 -0
  195. package/src/lib/plugin-runtime/agents.js +51 -0
  196. package/src/lib/plugin-runtime/bin.js +100 -0
  197. package/src/lib/plugin-runtime/hooks.js +100 -0
  198. package/src/lib/plugin-runtime/install.js +388 -0
  199. package/src/lib/plugin-runtime/lsp.js +75 -0
  200. package/src/lib/plugin-runtime/manifest.js +458 -0
  201. package/src/lib/plugin-runtime/mcp.js +89 -0
  202. package/src/lib/plugin-runtime/monitors.js +100 -0
  203. package/src/lib/plugin-runtime/policy.js +144 -0
  204. package/src/lib/plugin-runtime/reload.js +79 -0
  205. package/src/lib/plugin-runtime/scopes.js +197 -0
  206. package/src/lib/plugin-runtime/settings.js +119 -0
  207. package/src/lib/plugin-runtime/signature.js +107 -0
  208. package/src/lib/plugin-runtime/skills.js +43 -0
  209. package/src/lib/plugin-runtime/trust.js +140 -0
  210. package/src/lib/sandbox-egress-proxy.js +162 -0
  211. package/src/lib/sandbox-network-policy.js +134 -0
  212. package/src/lib/sandbox-v2.js +10 -4
  213. package/src/lib/settings-hook-events.cjs +78 -6
  214. package/src/lib/settings-hooks.cjs +29 -3
  215. package/src/lib/settings-loader.cjs +35 -1
  216. package/src/lib/skill-loader.js +18 -0
  217. package/src/lib/telemetry/span-recorder.js +237 -0
  218. package/src/repl/agent-repl.js +400 -4
  219. package/src/runtime/agent-core.js +546 -52
  220. package/src/runtime/coding-agent-contract-shared.cjs +82 -8
  221. package/src/runtime/coding-agent-policy.cjs +32 -7
  222. package/src/runtime/fallback-model.js +134 -7
  223. package/src/runtime/headless-runner.js +353 -108
  224. package/src/runtime/mcp-config.js +46 -0
  225. package/src/assets/web-panel/assets/ChatBubbleRenderer-DCNfbdlx.js +0 -1
  226. package/src/assets/web-panel/assets/OrderTableRenderer-DIp8Xcbd.js +0 -1
  227. package/src/assets/web-panel/assets/Projects-Dpid9CDg.js +0 -1
  228. package/src/assets/web-panel/assets/Terminal-BvAK_Zel.js +0 -3
  229. package/src/assets/web-panel/assets/devWarning-C2Z5LRgq.js +0 -1
  230. package/src/assets/web-panel/assets/index-BGp6Yr-Y.js +0 -1
  231. package/src/assets/web-panel/assets/index-BXiw9dQf.js +0 -1
@@ -0,0 +1,456 @@
1
+ /**
2
+ * `cc team` — run a declared task graph across N cooperating teammates with
3
+ * exclusive leases + dependency ordering (Phase 4, Agent Team).
4
+ *
5
+ * A task file is JSON:
6
+ * { "tasks": [ { "key": "build", "title": "...", "dependsOn": [],
7
+ * "command": "npm run build" | "prompt": "fix the bug in x" } ] }
8
+ *
9
+ * cc team plan --tasks graph.json # topological wave preview (no run)
10
+ * cc team run --tasks graph.json # dry-run: validate + schedule, no exec
11
+ * cc team run --tasks graph.json --exec # run each task's shell `command`
12
+ * cc team run --tasks graph.json --agent# hand each task's `prompt` to cc agent
13
+ *
14
+ * The lease/DAG guarantees (no double-processing, deps-before-dependents, crash
15
+ * reclaim) come from TaskLeaseRegistry; TeamRunner drives the N teammates.
16
+ */
17
+
18
+ import fs from "fs";
19
+ import path from "path";
20
+ import { fileURLToPath } from "url";
21
+ import { spawn } from "child_process";
22
+ import { TaskLeaseRegistry } from "../lib/agent-team/task-lease.js";
23
+ import { TeamRunner } from "../lib/agent-team/team-runner.js";
24
+ import { TeamWorktreeCoordinator } from "../lib/agent-team/team-worktree.js";
25
+ import { TeamBudget } from "../lib/agent-team/team-budget.js";
26
+ import { TeamMailbox } from "../lib/agent-team/team-mailbox.js";
27
+
28
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
29
+ const BIN = path.resolve(__dirname, "..", "..", "bin", "chainlesschain.js");
30
+
31
+ /** Load + validate a task-graph file into a registry (throws on bad input). */
32
+ function loadRegistry(file, { ttlMs } = {}) {
33
+ const abs = path.resolve(process.cwd(), file);
34
+ let doc;
35
+ try {
36
+ doc = JSON.parse(fs.readFileSync(abs, "utf8"));
37
+ } catch (err) {
38
+ throw new Error(`cannot read task file ${abs}: ${err.message}`);
39
+ }
40
+ const tasks = Array.isArray(doc) ? doc : doc.tasks;
41
+ if (!Array.isArray(tasks) || tasks.length === 0) {
42
+ throw new Error("task file must have a non-empty `tasks` array");
43
+ }
44
+ const reg = new TaskLeaseRegistry({ defaultTtlMs: ttlMs });
45
+ for (const t of tasks) {
46
+ const r = reg.addTask({
47
+ key: t.key,
48
+ title: t.title || t.key,
49
+ dependsOn: t.dependsOn || t.deps || [],
50
+ priority: t.priority,
51
+ metadata: { command: t.command || null, prompt: t.prompt || null },
52
+ });
53
+ if (!r.ok) {
54
+ throw new Error(
55
+ `task "${t.key || "(unnamed)"}" rejected: ${r.reason}` +
56
+ (r.cycle ? ` [${r.cycle.join(" → ")}]` : ""),
57
+ );
58
+ }
59
+ }
60
+ return reg;
61
+ }
62
+
63
+ /** Topological wave schedule (each wave = tasks whose deps are all in prior waves). */
64
+ function planWaves(reg) {
65
+ const done = new Set();
66
+ const waves = [];
67
+ const all = reg.list();
68
+ let remaining = all.map((t) => t.key);
69
+ let guard = all.length + 1;
70
+ while (remaining.length > 0 && guard-- > 0) {
71
+ const wave = remaining.filter((k) => {
72
+ const t = reg.getTask(k);
73
+ return t.dependsOn.every((d) => done.has(d));
74
+ });
75
+ if (wave.length === 0) break; // unreachable if the graph is a DAG
76
+ waves.push(wave);
77
+ wave.forEach((k) => done.add(k));
78
+ remaining = remaining.filter((k) => !wave.includes(k));
79
+ }
80
+ return waves;
81
+ }
82
+
83
+ /** Real executor: run a task's shell `command`, success = exit 0. */
84
+ function makeShellRunTask(logger) {
85
+ return function runTask({ task }) {
86
+ const command = task.metadata?.command || task?.command;
87
+ if (!command) {
88
+ throw new Error(`task "${task.key}" has no \`command\` to --exec`);
89
+ }
90
+ return new Promise((resolve, reject) => {
91
+ const child = spawn(command, {
92
+ cwd: process.cwd(),
93
+ shell: true,
94
+ env: process.env,
95
+ });
96
+ let err = "";
97
+ child.stderr?.on("data", (d) => (err += d.toString("utf8")));
98
+ child.on("error", (e) => reject(new Error(e.message)));
99
+ child.on("close", (code) => {
100
+ if (code === 0) resolve({ code });
101
+ else reject(new Error(err.trim() || `command exited ${code}`));
102
+ });
103
+ });
104
+ };
105
+ }
106
+
107
+ /** Spawn a headless `cc agent -p` for one prompt in `cwd`; resolve on exit 0. */
108
+ function spawnAgent(prompt, cwd, opts = {}) {
109
+ return new Promise((resolve, reject) => {
110
+ const args = [
111
+ BIN,
112
+ "agent",
113
+ "-p",
114
+ prompt,
115
+ "--permission-mode",
116
+ opts.permissionMode || "acceptEdits",
117
+ "--output-format",
118
+ "text",
119
+ ];
120
+ if (opts.model) args.push("--model", opts.model);
121
+ const child = spawn(process.execPath, args, {
122
+ cwd,
123
+ env: { ...process.env, CLAUDECODE: "1" },
124
+ windowsHide: true,
125
+ });
126
+ let err = "";
127
+ child.stderr?.on("data", (d) => (err += d.toString("utf8")));
128
+ child.on("error", (e) => reject(new Error(e.message)));
129
+ child.on("close", (code) =>
130
+ code === 0
131
+ ? resolve({ code })
132
+ : reject(new Error(err.trim() || `agent exited ${code}`)),
133
+ );
134
+ });
135
+ }
136
+
137
+ /** Real executor: hand a task's `prompt` to a headless `cc agent -p` in cwd. */
138
+ function makeAgentRunTask(opts = {}) {
139
+ return function runTask({ task }) {
140
+ const prompt = task.metadata?.prompt || task?.prompt;
141
+ if (!prompt) {
142
+ throw new Error(`task "${task.key}" has no \`prompt\` to --agent`);
143
+ }
144
+ return spawnAgent(prompt, process.cwd(), opts);
145
+ };
146
+ }
147
+
148
+ export function registerTeamCommand(program, { logger } = {}) {
149
+ const log = logger || console;
150
+ const team = program
151
+ .command("team")
152
+ .description(
153
+ "Run a declared task graph across N teammates (exclusive leases + dependency DAG)",
154
+ );
155
+
156
+ team
157
+ .command("plan")
158
+ .description("Show the topological wave schedule for a task graph (no run)")
159
+ .requiredOption("--tasks <file>", "Task-graph JSON file")
160
+ .option("--json", "Output the plan as JSON")
161
+ .action((options) => {
162
+ let reg;
163
+ try {
164
+ reg = loadRegistry(options.tasks);
165
+ } catch (err) {
166
+ (log.error || console.error)(err.message);
167
+ process.exitCode = 1;
168
+ return;
169
+ }
170
+ const waves = planWaves(reg);
171
+ if (options.json) {
172
+ console.log(
173
+ JSON.stringify({ waves, total: reg.list().length }, null, 2),
174
+ );
175
+ return;
176
+ }
177
+ (log.log || console.log)(
178
+ `Task graph: ${reg.list().length} task(s), ${waves.length} wave(s)`,
179
+ );
180
+ waves.forEach((w, i) => {
181
+ (log.log || console.log)(` wave ${i + 1}: ${w.join(", ")}`);
182
+ });
183
+ });
184
+
185
+ team
186
+ .command("run")
187
+ .description("Run a task graph with N cooperating teammates")
188
+ .requiredOption("--tasks <file>", "Task-graph JSON file")
189
+ .option("--teammates <n>", "Number of concurrent teammates", "2")
190
+ .option("--ttl <seconds>", "Lease TTL per task", "60")
191
+ .option("--exec", "Execute each task's shell `command` (real)")
192
+ .option(
193
+ "--agent",
194
+ "Hand each task's `prompt` to a headless cc agent (real)",
195
+ )
196
+ .option("--model <model>", "Model for --agent runs")
197
+ .option(
198
+ "--worktree",
199
+ "Run each task's `command` in its own git worktree (parallel isolation)",
200
+ )
201
+ .option(
202
+ "--merge",
203
+ "With --worktree: merge each clean branch back to base (conflicts reported, not forced)",
204
+ )
205
+ .option("--max-tasks <n>", "Budget: total task executions across the team")
206
+ .option("--max-tokens <n>", "Budget: total LLM tokens across the team")
207
+ .option(
208
+ "--max-usd <n>",
209
+ "Budget: total estimated USD spend across the team",
210
+ )
211
+ .option(
212
+ "--max-wall <seconds>",
213
+ "Budget: wall-clock seconds for the whole run",
214
+ )
215
+ .option(
216
+ "--state <file>",
217
+ "Persist team progress to a file (for --resume after a crash)",
218
+ )
219
+ .option(
220
+ "--resume",
221
+ "Restore progress from --state (completed tasks stay done; stale leases freed)",
222
+ )
223
+ .option("--json", "Emit the event stream as JSON lines")
224
+ .action(async (options) => {
225
+ const ttlMs = Math.max(1, Number(options.ttl) || 60) * 1000;
226
+ let reg;
227
+ let mailbox = new TeamMailbox();
228
+ let budget = new TeamBudget({
229
+ maxTasks: options.maxTasks,
230
+ maxTokens: options.maxTokens,
231
+ maxUsd: options.maxUsd,
232
+ maxWallMs: options.maxWall
233
+ ? Math.max(1, Number(options.maxWall)) * 1000
234
+ : null,
235
+ });
236
+ let priorMembers = [];
237
+ try {
238
+ // Resume from a prior (possibly crashed) run's state: completed tasks
239
+ // stay COMPLETED, and any lease left dangling by a crash is reclaimed so
240
+ // its task is re-run — the team-session-recovery acceptance path. The
241
+ // mailbox + budget are restored too so messages/spend stay consistent.
242
+ if (options.resume && options.state && fs.existsSync(options.state)) {
243
+ const snap = JSON.parse(fs.readFileSync(options.state, "utf8"));
244
+ const isV2 = snap && snap.version >= 2 && snap.registry;
245
+ reg = TaskLeaseRegistry.restore(isV2 ? snap.registry : snap);
246
+ if (isV2) {
247
+ if (snap.mailbox) mailbox = TeamMailbox.restore(snap.mailbox);
248
+ if (snap.budget)
249
+ budget = TeamBudget.restore(snap.budget, {
250
+ // CLI flags on the resume invocation override the prior caps;
251
+ // omitted flags keep them (never silently drop a safety cap).
252
+ overrides: {
253
+ maxTasks: options.maxTasks,
254
+ maxTokens: options.maxTokens,
255
+ maxUsd: options.maxUsd,
256
+ maxWallMs: options.maxWall
257
+ ? Math.max(1, Number(options.maxWall)) * 1000
258
+ : undefined,
259
+ },
260
+ });
261
+ priorMembers = Array.isArray(snap.members) ? snap.members : [];
262
+ }
263
+ // A teammate whose lease is still dangling here crashed last run — its
264
+ // task is about to be reclaimed, so report it LOST before the sweep.
265
+ const lostHolders = new Set();
266
+ for (const t of reg.list()) {
267
+ if (t.lease && t.lease.holder) lostHolders.add(t.lease.holder);
268
+ }
269
+ const freed = reg.reclaimExpired();
270
+ for (const h of lostHolders) {
271
+ if (options.json)
272
+ console.log(JSON.stringify({ type: "teammate:lost", holder: h }));
273
+ else (log.info || console.log)(` ⚠ teammate ${h} lost (crashed)`);
274
+ }
275
+ const s = reg.stats();
276
+ (log.info || console.log)(
277
+ `Resumed: ${s.completed}/${s.total} already done` +
278
+ (freed.length
279
+ ? `, ${freed.length} stale lease(s) reclaimed`
280
+ : ""),
281
+ );
282
+ } else {
283
+ reg = loadRegistry(options.tasks, { ttlMs });
284
+ }
285
+ } catch (err) {
286
+ (log.error || console.error)(err.message);
287
+ process.exitCode = 1;
288
+ return;
289
+ }
290
+
291
+ // Persist a snapshot after each task settles so a crash mid-run is
292
+ // resumable (persist-after-run alone would lose everything on a crash).
293
+ // v2 bundles registry + mailbox + budget + member lifecycle so a resume
294
+ // keeps the whole team state consistent, not just the task graph.
295
+ let runnerRef = null;
296
+ const persist = () => {
297
+ if (!options.state) return;
298
+ try {
299
+ fs.writeFileSync(
300
+ options.state,
301
+ JSON.stringify(
302
+ {
303
+ version: 2,
304
+ registry: reg.snapshot(),
305
+ mailbox: mailbox.snapshot(),
306
+ budget: budget.snapshot(),
307
+ members: runnerRef ? runnerRef.members() : priorMembers,
308
+ },
309
+ null,
310
+ 2,
311
+ ),
312
+ "utf8",
313
+ );
314
+ } catch (e) {
315
+ (log.error || console.error)(` state write failed: ${e.message}`);
316
+ }
317
+ };
318
+
319
+ // Pick the executor. Default is a dry-run (validate + schedule, no side
320
+ // effects) so `cc team run` is safe to explore, mirroring `cc eval --dry-run`.
321
+ let runTask;
322
+ let coord = null;
323
+ if (options.worktree) {
324
+ coord = new TeamWorktreeCoordinator(process.cwd());
325
+ if (!coord.isGitRepo()) {
326
+ (log.error || console.error)(
327
+ "--worktree requires a git repository (run inside one)",
328
+ );
329
+ process.exitCode = 1;
330
+ return;
331
+ }
332
+ if (options.agent) {
333
+ // --agent --worktree: each teammate drives an agent turn (its `prompt`)
334
+ // inside its OWN git worktree, so parallel edits never fight over the
335
+ // working tree, then integrate/merge the branches like --exec --worktree.
336
+ runTask = coord.makeRunTask({
337
+ runInWorktree: async ({ key, task, cwd }) => {
338
+ const prompt = task.metadata?.prompt || task?.prompt;
339
+ if (!prompt) {
340
+ throw new Error(`task "${key}" has no \`prompt\` to --agent`);
341
+ }
342
+ await spawnAgent(prompt, cwd, { model: options.model });
343
+ },
344
+ });
345
+ } else {
346
+ runTask = coord.makeRunTask();
347
+ }
348
+ } else if (options.exec) runTask = makeShellRunTask(log);
349
+ else if (options.agent)
350
+ runTask = makeAgentRunTask({ model: options.model });
351
+ else runTask = async () => ({ dryRun: true });
352
+
353
+ const teammates = Math.max(1, Number(options.teammates) || 2);
354
+ const runner = new TeamRunner(reg, {
355
+ teammates,
356
+ ttlMs,
357
+ runTask,
358
+ budget,
359
+ mailbox,
360
+ onEvent: (e) => {
361
+ if (options.json) console.log(JSON.stringify(e));
362
+ else if (e.type === "task:claimed")
363
+ (log.info || console.log)(` → ${e.key} [${e.holder}]`);
364
+ else if (e.type === "task:completed")
365
+ (log.info || console.log)(` ✔ ${e.key}`);
366
+ else if (e.type === "task:failed")
367
+ (log.info || console.log)(
368
+ ` ✗ ${e.key}${e.retry ? " (will retry)" : " (gave up)"}: ${e.error}`,
369
+ );
370
+ else if (e.type === "run:budget-exhausted")
371
+ (log.info || console.log)(
372
+ ` ⛔ budget reached (${e.reason || `max-tasks ${e.maxTasks}`}) — no new tasks claimed`,
373
+ );
374
+ if (e.type === "task:completed" || e.type === "task:failed")
375
+ persist();
376
+ },
377
+ });
378
+ runner.seedMembers(priorMembers);
379
+ runnerRef = runner;
380
+
381
+ const summary = await runner.run();
382
+ persist();
383
+
384
+ // Worktree integration: sequentially preview (and optionally merge) each
385
+ // committed branch back to base, then remove the worktrees. Conflicts are
386
+ // reported, never force-merged.
387
+ let integration = null;
388
+ if (coord) {
389
+ integration = coord.integrate({ merge: options.merge === true });
390
+ coord.cleanupAll();
391
+ if (!options.json) {
392
+ (log.log || console.log)("\nWorktree integration:");
393
+ for (const r of integration) {
394
+ if (!r.committed)
395
+ (log.info || console.log)(` · ${r.key}: no changes`);
396
+ else if (r.error)
397
+ (log.info || console.log)(
398
+ ` ✗ ${r.key} [${r.branch}]: ${r.error}`,
399
+ );
400
+ else if (!r.clean)
401
+ (log.info || console.log)(
402
+ ` ⚠ ${r.key} [${r.branch}]: conflicts in ${r.conflicts.length} file(s) — not merged`,
403
+ );
404
+ else
405
+ (log.info || console.log)(
406
+ ` ${r.merged ? "✔ merged" : "✔ clean"} ${r.key} [${r.branch}]`,
407
+ );
408
+ }
409
+ }
410
+ }
411
+
412
+ if (options.json && integration)
413
+ console.log(JSON.stringify({ integration }));
414
+ if (!options.json) {
415
+ const s = summary.stats;
416
+ (log.log || console.log)(
417
+ `\nTeam run: ${s.completed}/${s.total} completed` +
418
+ (s.cancelled ? `, ${s.cancelled} cancelled` : "") +
419
+ ` (${teammates} teammates, ${summary.executions} executions, peak ${summary.maxConcurrent} concurrent)`,
420
+ );
421
+ if (budget.enabled()) {
422
+ const b = budget.status();
423
+ const parts = [`${b.tasks} task(s)`, `${b.tokens} token(s)`];
424
+ if (b.maxUsd != null) parts.push(`$${b.spentUsd.toFixed(4)}`);
425
+ (log.log || console.log)(
426
+ `Budget: ${parts.join(", ")}` +
427
+ (summary.budgetStopped
428
+ ? ` — stopped early (${summary.budgetReason})`
429
+ : ""),
430
+ );
431
+ }
432
+ if (mailbox.size() > 0)
433
+ (log.log || console.log)(
434
+ `Messages: ${mailbox.size()} exchanged between teammates`,
435
+ );
436
+ } else {
437
+ console.log(
438
+ JSON.stringify({
439
+ summary: {
440
+ done: summary.done,
441
+ executions: summary.executions,
442
+ budgetStopped: summary.budgetStopped,
443
+ budgetReason: summary.budgetReason,
444
+ budget: budget.status(),
445
+ members: summary.members,
446
+ messages: mailbox.size(),
447
+ },
448
+ }),
449
+ );
450
+ }
451
+ // Non-zero exit when the graph didn't fully complete — usable as a gate.
452
+ if (!summary.done) process.exitCode = 1;
453
+ });
454
+
455
+ return program;
456
+ }
@@ -20,6 +20,49 @@ import {
20
20
  handleRemoteSessionRevoke,
21
21
  } from "./remote-session-protocol.js";
22
22
 
23
+ /**
24
+ * Reconnect-safe command execution. When a message carries a stable
25
+ * `commandId`, route it through the server's RemoteCommandLedger so the side
26
+ * effect runs AT MOST ONCE: a client that re-sends the same command after a
27
+ * dropped ACK / reconnect gets a `replayed` ack instead of a second execution
28
+ * (Phase 5 acceptance "断网恢复后不会重复执行工具调用"). Messages WITHOUT a
29
+ * commandId are unchanged — they call `_executeCommand` directly, so existing
30
+ * clients behave exactly as before. A revoked device / stale-seq command is
31
+ * rejected without executing.
32
+ */
33
+ async function executeWithIdempotency(server, id, ws, message, stream) {
34
+ const commandId = message.commandId;
35
+ if (!commandId) {
36
+ return server._executeCommand(id, ws, message.command, stream);
37
+ }
38
+ if (!server._commandLedger) {
39
+ const { RemoteCommandLedger } =
40
+ await import("../../harness/remote-command-ledger.js");
41
+ server._commandLedger = new RemoteCommandLedger();
42
+ }
43
+ const outcome = await server._commandLedger.apply(
44
+ { commandId, deviceId: message.deviceId ?? null, seq: message.seq },
45
+ () => server._executeCommand(id, ws, message.command, stream),
46
+ );
47
+ if (outcome.status === "replayed") {
48
+ server._send(ws, {
49
+ id,
50
+ type: "replayed",
51
+ commandId,
52
+ applyIndex: outcome.applyIndex,
53
+ });
54
+ } else if (outcome.status === "rejected") {
55
+ server._send(ws, {
56
+ id,
57
+ type: "error",
58
+ code: "COMMAND_REJECTED",
59
+ commandId,
60
+ message: outcome.reason,
61
+ });
62
+ }
63
+ return outcome;
64
+ }
65
+
23
66
  export function createWsMessageDispatcher(server) {
24
67
  return {
25
68
  async dispatch(clientId, ws, message) {
@@ -52,8 +95,8 @@ export function createWsMessageDispatcher(server) {
52
95
  auth: () => server._handleAuth(clientId, ws, message),
53
96
  ping: () =>
54
97
  server._send(ws, { id, type: "pong", serverTime: Date.now() }),
55
- execute: () => server._executeCommand(id, ws, message.command, false),
56
- stream: () => server._executeCommand(id, ws, message.command, true),
98
+ execute: () => executeWithIdempotency(server, id, ws, message, false),
99
+ stream: () => executeWithIdempotency(server, id, ws, message, true),
57
100
  cancel: () => server._cancelRequest(id, ws),
58
101
  "session-create": () => server._handleSessionCreate(id, ws, message),
59
102
  "session-resume": () => server._handleSessionResume(id, ws, message),
@@ -341,6 +341,43 @@ export async function handleRemoteSessionPublish(
341
341
  if (!message.event || typeof message.event.type !== "string") {
342
342
  throw new Error("event.type is required");
343
343
  }
344
+ // A paired device (incl. relay-only mobiles) refreshing its own vendor push
345
+ // token — e.g. after FCM onNewToken. Self-scoped: authorize proves
346
+ // membership and registerPush keys off the authenticated clientId, so a
347
+ // device can only ever set its OWN token. Omitting the token clears it.
348
+ if (message.event.type === "push.register") {
349
+ server.remoteSessions.authorize(
350
+ message.remoteSessionId,
351
+ clientId,
352
+ "observe",
353
+ );
354
+ const registered = server.remoteSessions.registerPush(
355
+ message.remoteSessionId,
356
+ clientId,
357
+ {
358
+ token: message.event.pushToken,
359
+ provider: message.event.pushProvider,
360
+ },
361
+ );
362
+ audit(server, {
363
+ sessionId: message.remoteSessionId,
364
+ actor: clientId,
365
+ action: "push.registered",
366
+ detail: {
367
+ hasPush: registered.hasPush,
368
+ provider: registered.provider,
369
+ via: "relay",
370
+ },
371
+ });
372
+ reply(
373
+ server,
374
+ ws,
375
+ message.id,
376
+ "remote-session-push-registered",
377
+ registered,
378
+ );
379
+ return;
380
+ }
344
381
  const requiredScope = CLIENT_EVENT_SCOPES[message.event.type];
345
382
  const { session } = server.remoteSessions.authorize(
346
383
  message.remoteSessionId,
@@ -37,7 +37,7 @@ import {
37
37
  RemoteSessionPushDispatcher,
38
38
  isApprovalRequestEvent,
39
39
  } from "../../harness/remote-session-push.js";
40
- import { createRemoteSessionPushSender } from "../../harness/remote-session-push-fcm.js";
40
+ import { createRemoteSessionPushSender } from "../../harness/remote-session-push-senders.js";
41
41
  import { RemoteSessionRelay } from "../remote-session-relay.js";
42
42
  import { handleRemoteSessionPublish } from "./remote-session-protocol.js";
43
43
  import { handleTaskDetail, handleTaskHistory } from "./task-protocol.js";
@@ -251,8 +251,25 @@ export class PromptCompressor {
251
251
  };
252
252
  }
253
253
 
254
+ // Pinned facts — a DETERMINISTIC fact-retention guarantee. A message marked
255
+ // `pinned:true` (or matched by an `options.isPinned` predicate) MUST survive
256
+ // compaction verbatim regardless of any strategy or the non-deterministic LLM
257
+ // summary. Every other retention here is positional (system / recent-N / last
258
+ // user) or summary-dependent, so an important fact that scrolls out of the
259
+ // recent window would otherwise survive only if the summary happens to keep
260
+ // it. We pull pins out BEFORE any strategy runs (so nothing can drop them),
261
+ // then re-insert them as a sticky block right after the system messages.
262
+ const isPinned =
263
+ typeof options.isPinned === "function"
264
+ ? options.isPinned
265
+ : (m) => m && (m.pinned === true || m._pin === true);
266
+ const pinned = messages.filter((m) => isPinned(m));
267
+ const working = pinned.length
268
+ ? messages.filter((m) => !isPinned(m))
269
+ : messages;
270
+
254
271
  const originalTokens = estimateMessagesTokens(messages);
255
- let result = [...messages];
272
+ let result = [...working];
256
273
  const applied = [];
257
274
 
258
275
  if (feature("CONTEXT_SNIP")) {
@@ -288,6 +305,15 @@ export class PromptCompressor {
288
305
  }
289
306
  }
290
307
 
308
+ // Re-insert pinned facts verbatim (order preserved) right after the leading
309
+ // system messages, so they end up as a sticky facts block at the top.
310
+ if (pinned.length) {
311
+ const sys = result.filter((m) => m.role === "system");
312
+ const nonSys = result.filter((m) => m.role !== "system");
313
+ result = [...sys, ...pinned, ...nonSys];
314
+ applied.push("pinned");
315
+ }
316
+
291
317
  // Tool-pair repair (opt-in) — callers compacting tool-laden histories
292
318
  // (e.g. the headless agent loop) pass this so truncation/snip never leaves
293
319
  // an orphaned tool result or unanswered tool_call for a strict API.