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,434 @@
1
+ /**
2
+ * TaskLeaseRegistry (Phase 4 — Agent Team) — the two capabilities the existing
3
+ * shared task list lacks, layered on top of it rather than reinventing it:
4
+ *
5
+ * 1. EXCLUSIVE LEASES WITH TTL. session-core's `SharedTaskList.claim()` is
6
+ * documented as "不互斥" (non-mutex) — it just stamps an assignee, so two
7
+ * teammates can claim the same task and duplicate the work. Here a task can
8
+ * be leased by AT MOST ONE holder at a time, the lease carries an expiry,
9
+ * and a crashed teammate's expired lease becomes reclaimable — satisfying
10
+ * the Phase 4 acceptance "多 Agent 不会重复处理已被有效 lease 的任务" and
11
+ * "teammate 崩溃后任务可回收并重新分配".
12
+ *
13
+ * 2. DEPENDENCY EDGES (a DAG). A task declares `dependsOn: [key,…]`; it is not
14
+ * claimable until every dependency is COMPLETED. Cycles are rejected at add
15
+ * time (a cyclic dep would deadlock — it could never become claimable).
16
+ *
17
+ * It COMPOSES a real `SharedTaskList` (optimistic-lock task store, status
18
+ * vocabulary, snapshot/restore, terminal-state enforcement) — the lease + edges
19
+ * live in each task's metadata, so persistence/recovery come for free. The clock
20
+ * is injected (`now`) so lease expiry is fully deterministic in tests.
21
+ *
22
+ * This is the scheduling brain; the claim→execute handoff to a real agent
23
+ * (SubAgentContext.run in a per-teammate worktree) is wired separately.
24
+ */
25
+
26
+ import { SharedTaskList, TASK_STATUS } from "@chainlesschain/session-core";
27
+
28
+ export const DEFAULT_LEASE_TTL_MS = 60000;
29
+ export const DEFAULT_MAX_ATTEMPTS = 3;
30
+
31
+ export class TaskLeaseRegistry {
32
+ constructor({
33
+ groupId = null,
34
+ now = () => Date.now(),
35
+ defaultTtlMs = DEFAULT_LEASE_TTL_MS,
36
+ maxAttempts = DEFAULT_MAX_ATTEMPTS,
37
+ } = {}) {
38
+ this._now = typeof now === "function" ? now : () => now;
39
+ this.defaultTtlMs = defaultTtlMs > 0 ? defaultTtlMs : DEFAULT_LEASE_TTL_MS;
40
+ this.maxAttempts = maxAttempts > 0 ? maxAttempts : DEFAULT_MAX_ATTEMPTS;
41
+ this._tasks = new SharedTaskList({ groupId, now: this._now });
42
+ this._byKey = new Map(); // stable user key → internal SharedTaskList id
43
+ }
44
+
45
+ /**
46
+ * Add a task to the graph. `key` is a stable, user-chosen identifier so
47
+ * `dependsOn` can reference tasks regardless of insertion order. Rejects a
48
+ * duplicate key or a dependency edge that would create a cycle.
49
+ *
50
+ * @returns {{ ok:boolean, key?:string, reason?:string, cycle?:string[] }}
51
+ */
52
+ addTask({
53
+ key,
54
+ title,
55
+ dependsOn = [],
56
+ priority = "normal",
57
+ metadata = {},
58
+ createdBy = null,
59
+ } = {}) {
60
+ const k = key || `t_${this._tasks.size() + 1}`;
61
+ if (this._byKey.has(k)) {
62
+ return { ok: false, reason: `duplicate task key "${k}"` };
63
+ }
64
+ if (!title || typeof title !== "string") {
65
+ return { ok: false, reason: "title required" };
66
+ }
67
+ const deps = Array.isArray(dependsOn) ? dependsOn.filter(Boolean) : [];
68
+ // Self-dependency and (once inserted) any back-edge that closes a cycle
69
+ // would deadlock the task. Check against the edges already present PLUS the
70
+ // proposed ones.
71
+ const cycle = this._detectCycleIfAdded(k, deps);
72
+ if (cycle) {
73
+ return { ok: false, reason: "dependency cycle", cycle };
74
+ }
75
+ const created = this._tasks.add({
76
+ title,
77
+ priority,
78
+ createdBy,
79
+ metadata: {
80
+ ...metadata,
81
+ key: k,
82
+ dependsOn: deps,
83
+ lease: null,
84
+ attempts: 0,
85
+ },
86
+ });
87
+ this._byKey.set(k, created.id);
88
+ return { ok: true, key: k, id: created.id };
89
+ }
90
+
91
+ /** Resolve a stable key → the current task object (or null). */
92
+ getTask(key) {
93
+ const id = this._byKey.get(key);
94
+ if (!id) return null;
95
+ const t = this._tasks.get(id);
96
+ if (!t) return null;
97
+ return this._view(t);
98
+ }
99
+
100
+ _view(t) {
101
+ const lease = t.metadata?.lease || null;
102
+ return {
103
+ key: t.metadata?.key,
104
+ id: t.id,
105
+ title: t.title,
106
+ status: t.status,
107
+ priority: t.priority,
108
+ dependsOn: t.metadata?.dependsOn || [],
109
+ attempts: t.metadata?.attempts || 0,
110
+ lease: lease ? { ...lease } : null,
111
+ assignee: t.assignee,
112
+ rev: t.rev,
113
+ // Full metadata is exposed so executors can read user payload
114
+ // (e.g. a task's shell `command` or agent `prompt`) — it carries the
115
+ // internal lease/dependsOn/key too, which callers simply ignore.
116
+ metadata: { ...t.metadata },
117
+ };
118
+ }
119
+
120
+ list() {
121
+ return this._tasks.list().map((t) => this._view(t));
122
+ }
123
+
124
+ _statusOf(key) {
125
+ const id = this._byKey.get(key);
126
+ const t = id ? this._tasks.get(id) : null;
127
+ return t ? t.status : null;
128
+ }
129
+
130
+ /** Unmet (not-yet-completed) dependency keys for a task. */
131
+ unmetDependencies(key) {
132
+ const t = this.getTask(key);
133
+ if (!t) return [];
134
+ return t.dependsOn.filter(
135
+ (d) => this._statusOf(d) !== TASK_STATUS.COMPLETED,
136
+ );
137
+ }
138
+
139
+ _leaseValid(lease, now) {
140
+ return !!(lease && lease.expiresAt > now);
141
+ }
142
+
143
+ /**
144
+ * Keys that could be leased right now: PENDING (or a task whose lease has
145
+ * expired) with all dependencies COMPLETED and no currently-valid lease.
146
+ */
147
+ claimable({ now = this._now() } = {}) {
148
+ const out = [];
149
+ for (const t of this._tasks.list()) {
150
+ const v = this._view(t);
151
+ if (
152
+ v.status === TASK_STATUS.COMPLETED ||
153
+ v.status === TASK_STATUS.CANCELLED
154
+ ) {
155
+ continue;
156
+ }
157
+ if (this._leaseValid(v.lease, now)) continue; // held by a live teammate
158
+ if (this.unmetDependencies(v.key).length > 0) continue; // blocked by deps
159
+ out.push(v.key);
160
+ }
161
+ return out;
162
+ }
163
+
164
+ /**
165
+ * Acquire (or renew, or steal an expired) exclusive lease.
166
+ * - blocked by unmet deps → { ok:false, reason:"blocked_by_deps", unmet }
167
+ * - terminal task → { ok:false, reason:"terminal" }
168
+ * - validly leased by someone else → { ok:false, reason:"leased", holder, expiresAt }
169
+ * - same holder → renew (extend expiry, bump renewals)
170
+ * - expired lease held by another → steal (stolen:true)
171
+ * @returns {{ ok:boolean, reason?:string, lease?:object, unmet?:string[], holder?:string, expiresAt?:number }}
172
+ */
173
+ acquire(key, { holder, ttlMs, now = this._now() } = {}) {
174
+ if (!holder) return { ok: false, reason: "holder required" };
175
+ const id = this._byKey.get(key);
176
+ const task = id ? this._tasks.get(id) : null;
177
+ if (!task) return { ok: false, reason: "not_found" };
178
+ if (
179
+ task.status === TASK_STATUS.COMPLETED ||
180
+ task.status === TASK_STATUS.CANCELLED
181
+ ) {
182
+ return { ok: false, reason: "terminal" };
183
+ }
184
+ const unmet = this.unmetDependencies(key);
185
+ if (unmet.length > 0) {
186
+ return { ok: false, reason: "blocked_by_deps", unmet };
187
+ }
188
+ const existing = task.metadata?.lease || null;
189
+ if (this._leaseValid(existing, now) && existing.holder !== holder) {
190
+ return {
191
+ ok: false,
192
+ reason: "leased",
193
+ holder: existing.holder,
194
+ expiresAt: existing.expiresAt,
195
+ };
196
+ }
197
+ const ttl = ttlMs > 0 ? ttlMs : this.defaultTtlMs;
198
+ const sameHolder = existing && existing.holder === holder;
199
+ const lease = {
200
+ holder,
201
+ acquiredAt: sameHolder ? existing.acquiredAt : now,
202
+ expiresAt: now + ttl,
203
+ renewals: sameHolder ? (existing.renewals || 0) + 1 : 0,
204
+ stolen: !!(existing && existing.holder !== holder),
205
+ };
206
+ return this._write(task, {
207
+ status: TASK_STATUS.IN_PROGRESS,
208
+ assignee: holder,
209
+ metadata: { ...task.metadata, lease },
210
+ })
211
+ ? { ok: true, lease }
212
+ : { ok: false, reason: "concurrent" };
213
+ }
214
+
215
+ /** Extend the lease you hold. Fails if you're not the current valid holder. */
216
+ renew(key, { holder, ttlMs, now = this._now() } = {}) {
217
+ const id = this._byKey.get(key);
218
+ const task = id ? this._tasks.get(id) : null;
219
+ if (!task) return { ok: false, reason: "not_found" };
220
+ const lease = task.metadata?.lease || null;
221
+ if (!this._leaseValid(lease, now) || lease.holder !== holder) {
222
+ return { ok: false, reason: "not_holder_or_expired" };
223
+ }
224
+ const ttl = ttlMs > 0 ? ttlMs : this.defaultTtlMs;
225
+ const next = {
226
+ ...lease,
227
+ expiresAt: now + ttl,
228
+ renewals: (lease.renewals || 0) + 1,
229
+ };
230
+ return this._write(task, { metadata: { ...task.metadata, lease: next } })
231
+ ? { ok: true, lease: next }
232
+ : { ok: false, reason: "concurrent" };
233
+ }
234
+
235
+ /** Voluntarily give up a lease — the task returns to PENDING for re-claim. */
236
+ release(key, { holder, now = this._now() } = {}) {
237
+ const id = this._byKey.get(key);
238
+ const task = id ? this._tasks.get(id) : null;
239
+ if (!task) return { ok: false, reason: "not_found" };
240
+ const lease = task.metadata?.lease || null;
241
+ if (!lease || lease.holder !== holder) {
242
+ return { ok: false, reason: "not_holder" };
243
+ }
244
+ return this._write(task, {
245
+ status: TASK_STATUS.PENDING,
246
+ assignee: null,
247
+ metadata: { ...task.metadata, lease: null },
248
+ })
249
+ ? { ok: true }
250
+ : { ok: false, reason: "concurrent" };
251
+ }
252
+
253
+ /** Complete a task you hold the lease on → terminal COMPLETED. */
254
+ complete(key, { holder, result = null, now = this._now() } = {}) {
255
+ const id = this._byKey.get(key);
256
+ const task = id ? this._tasks.get(id) : null;
257
+ if (!task) return { ok: false, reason: "not_found" };
258
+ const lease = task.metadata?.lease || null;
259
+ // Only the valid holder may complete — prevents a stale teammate (whose
260
+ // lease already expired and was reassigned) from marking another's work done.
261
+ if (!this._leaseValid(lease, now) || lease.holder !== holder) {
262
+ return { ok: false, reason: "not_holder_or_expired" };
263
+ }
264
+ return this._write(task, {
265
+ status: TASK_STATUS.COMPLETED,
266
+ metadata: { ...task.metadata, lease: null, result },
267
+ })
268
+ ? { ok: true }
269
+ : { ok: false, reason: "concurrent" };
270
+ }
271
+
272
+ /**
273
+ * Report a failed attempt. Under the attempt cap the task returns to PENDING
274
+ * (reclaimable for retry); at the cap it is CANCELLED (terminal) so the team
275
+ * doesn't loop forever on a doomed task.
276
+ * @returns {{ ok:boolean, retry?:boolean, attempts?:number, reason?:string }}
277
+ */
278
+ fail(key, { holder, error = null, now = this._now() } = {}) {
279
+ const id = this._byKey.get(key);
280
+ const task = id ? this._tasks.get(id) : null;
281
+ if (!task) return { ok: false, reason: "not_found" };
282
+ const lease = task.metadata?.lease || null;
283
+ if (!lease || lease.holder !== holder) {
284
+ return { ok: false, reason: "not_holder" };
285
+ }
286
+ const attempts = (task.metadata?.attempts || 0) + 1;
287
+ const willRetry = attempts < this.maxAttempts;
288
+ const ok = this._write(task, {
289
+ status: willRetry ? TASK_STATUS.PENDING : TASK_STATUS.CANCELLED,
290
+ assignee: null,
291
+ metadata: {
292
+ ...task.metadata,
293
+ lease: null,
294
+ attempts,
295
+ lastError: error ? String(error) : null,
296
+ },
297
+ });
298
+ if (!ok) return { ok: false, reason: "concurrent" };
299
+ return { ok: true, retry: willRetry, attempts };
300
+ }
301
+
302
+ /**
303
+ * Reclaim every task whose lease has EXPIRED (a crashed/lost teammate never
304
+ * renewed) back to PENDING so it can be re-assigned. Returns the reclaimed
305
+ * keys. This is the crash-recovery sweep.
306
+ */
307
+ reclaimExpired({ now = this._now() } = {}) {
308
+ const reclaimed = [];
309
+ for (const t of this._tasks.list()) {
310
+ const lease = t.metadata?.lease || null;
311
+ if (lease && !this._leaseValid(lease, now)) {
312
+ const fresh = this._tasks.get(t.id); // re-read for current rev
313
+ if (
314
+ this._write(fresh, {
315
+ status: TASK_STATUS.PENDING,
316
+ assignee: null,
317
+ metadata: { ...fresh.metadata, lease: null },
318
+ })
319
+ ) {
320
+ reclaimed.push(fresh.metadata?.key);
321
+ }
322
+ }
323
+ }
324
+ return reclaimed;
325
+ }
326
+
327
+ stats({ now = this._now() } = {}) {
328
+ const base = this._tasks.stats();
329
+ let leased = 0;
330
+ let expired = 0;
331
+ for (const t of this._tasks.list()) {
332
+ const lease = t.metadata?.lease || null;
333
+ if (lease) {
334
+ if (this._leaseValid(lease, now)) leased++;
335
+ else expired++;
336
+ }
337
+ }
338
+ return {
339
+ ...base,
340
+ leased,
341
+ expiredLeases: expired,
342
+ claimable: this.claimable({ now }).length,
343
+ };
344
+ }
345
+
346
+ /** Whether the graph is fully done (all terminal). */
347
+ allDone() {
348
+ return this._tasks
349
+ .list()
350
+ .every(
351
+ (t) =>
352
+ t.status === TASK_STATUS.COMPLETED ||
353
+ t.status === TASK_STATUS.CANCELLED,
354
+ );
355
+ }
356
+
357
+ snapshot() {
358
+ return {
359
+ registry: {
360
+ defaultTtlMs: this.defaultTtlMs,
361
+ maxAttempts: this.maxAttempts,
362
+ byKey: Array.from(this._byKey.entries()),
363
+ },
364
+ tasks: this._tasks.snapshot(),
365
+ };
366
+ }
367
+
368
+ static restore(snapshot, { now = () => Date.now() } = {}) {
369
+ const reg = new TaskLeaseRegistry({
370
+ now,
371
+ defaultTtlMs: snapshot?.registry?.defaultTtlMs,
372
+ maxAttempts: snapshot?.registry?.maxAttempts,
373
+ });
374
+ reg._tasks = SharedTaskList.restore(snapshot.tasks, { now: reg._now });
375
+ reg._byKey = new Map(snapshot?.registry?.byKey || []);
376
+ return reg;
377
+ }
378
+
379
+ // --- internals -----------------------------------------------------------
380
+
381
+ /** Optimistic write via the underlying list; false on rev conflict. */
382
+ _write(task, patch) {
383
+ try {
384
+ this._tasks.update(task.id, {
385
+ rev: task.rev,
386
+ patch,
387
+ actor: patch.assignee,
388
+ });
389
+ return true;
390
+ } catch {
391
+ // ConcurrencyError (rev changed under us) — caller re-reads and retries.
392
+ return false;
393
+ }
394
+ }
395
+
396
+ /**
397
+ * Would adding edges `key → dependsOn[i]` create a cycle? Walks the existing
398
+ * edge set augmented with the proposed edges. Returns the offending path or
399
+ * null. A self-edge counts as a cycle.
400
+ */
401
+ _detectCycleIfAdded(key, deps) {
402
+ // Build adjacency from current tasks, then add the candidate.
403
+ const adj = new Map();
404
+ for (const t of this._tasks.list()) {
405
+ adj.set(t.metadata?.key, t.metadata?.dependsOn || []);
406
+ }
407
+ adj.set(key, deps);
408
+ const WHITE = 0,
409
+ GRAY = 1,
410
+ BLACK = 2;
411
+ const color = new Map();
412
+ const stack = [];
413
+ let found = null;
414
+ const visit = (node) => {
415
+ color.set(node, GRAY);
416
+ stack.push(node);
417
+ for (const next of adj.get(node) || []) {
418
+ if (!adj.has(next)) continue; // edge to a not-yet-added task: no cycle via it
419
+ const c = color.get(next) || WHITE;
420
+ if (c === GRAY) {
421
+ found = [...stack.slice(stack.indexOf(next)), next];
422
+ return true;
423
+ }
424
+ if (c === WHITE && visit(next)) return true;
425
+ }
426
+ stack.pop();
427
+ color.set(node, BLACK);
428
+ return false;
429
+ };
430
+ // Only need to start from the candidate — a new cycle must pass through it.
431
+ if (visit(key)) return found;
432
+ return null;
433
+ }
434
+ }
@@ -0,0 +1,165 @@
1
+ /**
2
+ * TeamBudget (Phase 4 — Agent Team) — a team-wide spend cap across four
3
+ * independent dimensions, consulted by TeamRunner BEFORE it hands a teammate a
4
+ * new task so a runaway graph is bounded and a resumed session keeps counting
5
+ * from where it left off (Phase 4 acceptance "支持 token、时间、并发数和费用预算"
6
+ * + "团队会话恢复后…预算保持一致").
7
+ *
8
+ * - maxTasks total task EXECUTIONS across all teammates (task-count cap)
9
+ * - maxTokens total (input+output) LLM tokens folded from task results
10
+ * - maxUsd total estimated USD spend — delegated to the audited CostBudget
11
+ * (same pricing/cache-token rules as `--max-budget-usd`)
12
+ * - maxWallMs wall-clock since the first task started (time cap)
13
+ *
14
+ * Because a task's cost is only known once it returns, the team may overshoot a
15
+ * token/USD cap by at most one task — like CostBudget, it never STARTS a task
16
+ * once already over. A dimension left null is simply inactive. The clock is
17
+ * injected so the wall-clock cap is deterministic in tests, and snapshot/restore
18
+ * carries the running totals so `cc team run --resume` doesn't reset the budget.
19
+ */
20
+
21
+ import { CostBudget } from "../cost-budget.js";
22
+
23
+ const pos = (n) => {
24
+ const v = Number(n);
25
+ return Number.isFinite(v) && v > 0 ? v : null;
26
+ };
27
+
28
+ export class TeamBudget {
29
+ constructor({
30
+ maxTasks = null,
31
+ maxTokens = null,
32
+ maxUsd = null,
33
+ maxWallMs = null,
34
+ table = undefined,
35
+ now = () => Date.now(),
36
+ } = {}) {
37
+ this._now = typeof now === "function" ? now : () => now;
38
+ this.maxTasks = pos(maxTasks);
39
+ this.maxTokens = pos(maxTokens);
40
+ this.maxWallMs = pos(maxWallMs);
41
+ this.cost = new CostBudget({ limitUsd: maxUsd, table });
42
+ this.tasks = 0;
43
+ this.tokens = 0;
44
+ this.startedAt = null; // set on the first recorded task
45
+ }
46
+
47
+ /** Any dimension active? (an all-null budget never stops anything). */
48
+ enabled() {
49
+ return (
50
+ this.maxTasks != null ||
51
+ this.maxTokens != null ||
52
+ this.maxWallMs != null ||
53
+ this.cost.enabled()
54
+ );
55
+ }
56
+
57
+ /** Begin the wall-clock window (idempotent — first call wins). */
58
+ start(now = this._now()) {
59
+ if (this.startedAt == null) this.startedAt = now;
60
+ return this;
61
+ }
62
+
63
+ /**
64
+ * Fold one settled task into the running totals. `usage` (an LLM token-usage
65
+ * event) is optional — a plain `--exec` shell task carries none, so only the
66
+ * task-count / wall-clock dimensions move for it.
67
+ * @returns {this}
68
+ */
69
+ record({ usage = null, provider, model } = {}, now = this._now()) {
70
+ this.start(now);
71
+ this.tasks += 1;
72
+ if (usage) {
73
+ this.tokens +=
74
+ (Number(usage.input_tokens) || 0) + (Number(usage.output_tokens) || 0);
75
+ this.cost.add({ provider, model, usage });
76
+ }
77
+ return this;
78
+ }
79
+
80
+ /**
81
+ * Which cap (if any) is now reached — checked BEFORE starting a new task.
82
+ * @returns {null|"max-tasks"|"max-tokens"|"max-usd"|"max-wall-ms"}
83
+ */
84
+ reason(now = this._now()) {
85
+ if (this.maxTasks != null && this.tasks >= this.maxTasks) {
86
+ return "max-tasks";
87
+ }
88
+ if (this.maxTokens != null && this.tokens >= this.maxTokens) {
89
+ return "max-tokens";
90
+ }
91
+ if (this.cost.exceeded()) return "max-usd";
92
+ if (
93
+ this.maxWallMs != null &&
94
+ this.startedAt != null &&
95
+ now - this.startedAt >= this.maxWallMs
96
+ ) {
97
+ return "max-wall-ms";
98
+ }
99
+ return null;
100
+ }
101
+
102
+ /** True once any active cap has been reached. */
103
+ shouldStop(now = this._now()) {
104
+ return this.reason(now) != null;
105
+ }
106
+
107
+ status(now = this._now()) {
108
+ return {
109
+ tasks: this.tasks,
110
+ maxTasks: this.maxTasks,
111
+ tokens: this.tokens,
112
+ maxTokens: this.maxTokens,
113
+ spentUsd: this.cost.spentUsd,
114
+ maxUsd: this.cost.limitUsd,
115
+ elapsedMs: this.startedAt == null ? 0 : now - this.startedAt,
116
+ maxWallMs: this.maxWallMs,
117
+ reason: this.reason(now),
118
+ };
119
+ }
120
+
121
+ snapshot() {
122
+ return {
123
+ limits: {
124
+ maxTasks: this.maxTasks,
125
+ maxTokens: this.maxTokens,
126
+ maxUsd: this.cost.limitUsd,
127
+ maxWallMs: this.maxWallMs,
128
+ },
129
+ totals: {
130
+ tasks: this.tasks,
131
+ tokens: this.tokens,
132
+ spentUsd: this.cost.spentUsd,
133
+ startedAt: this.startedAt,
134
+ },
135
+ };
136
+ }
137
+
138
+ /**
139
+ * Restore running totals + caps from a snapshot. `overrides` lets the current
140
+ * CLI invocation RAISE/lower a cap on resume — a provided (non-nullish) value
141
+ * wins over the snapshot's; an omitted one keeps the prior cap (so a resume
142
+ * with no flags can't silently drop a safety cap).
143
+ */
144
+ static restore(snap, { now = () => Date.now(), table, overrides = {} } = {}) {
145
+ const pick = (o, s) => (o == null ? s : o);
146
+ const b = new TeamBudget({
147
+ maxTasks: pick(overrides.maxTasks, snap?.limits?.maxTasks),
148
+ maxTokens: pick(overrides.maxTokens, snap?.limits?.maxTokens),
149
+ maxUsd: pick(overrides.maxUsd, snap?.limits?.maxUsd),
150
+ maxWallMs: pick(overrides.maxWallMs, snap?.limits?.maxWallMs),
151
+ table,
152
+ now,
153
+ });
154
+ b.tasks = Number(snap?.totals?.tasks) || 0;
155
+ b.tokens = Number(snap?.totals?.tokens) || 0;
156
+ // Preserve prior USD spend so the cap keeps counting across a resume.
157
+ const spent = Number(snap?.totals?.spentUsd);
158
+ b.cost.spentUsd = Number.isFinite(spent) && spent >= 0 ? spent : 0;
159
+ if (b.cost.spentUsd > 0) b.cost.priced = true;
160
+ // A time cap must NOT keep counting wall-clock across a crash gap (the
161
+ // process was down); restart the window on the next recorded task.
162
+ b.startedAt = null;
163
+ return b;
164
+ }
165
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * TeamMailbox (Phase 4 — Agent Team) — directed + broadcast messaging between
3
+ * teammates so a coordinating agent can hand off context, flag a finding, or ask
4
+ * a peer to redo work (Phase 4 work-item "支持 Agent 间直接消息和定向通知",
5
+ * acceptance "团队会话恢复后…消息…保持一致").
6
+ *
7
+ * A message is addressed to a specific recipient (`to`) or broadcast (`to:"*"`).
8
+ * `drain(recipient)` returns that recipient's undelivered messages (direct +
9
+ * broadcasts it hasn't seen) and marks them delivered FOR THAT RECIPIENT — a
10
+ * broadcast is delivered independently to each teammate, so two teammates each
11
+ * receive it exactly once. Delivery cursors + the log are in the snapshot, so a
12
+ * resumed session re-delivers only what a teammate hadn't yet drained.
13
+ *
14
+ * Pure + deterministic: ids come from a monotonic counter (not a clock) and the
15
+ * clock is injected only for timestamps, so ordering is stable in tests.
16
+ */
17
+
18
+ export class TeamMailbox {
19
+ constructor({ now = () => Date.now() } = {}) {
20
+ this._now = typeof now === "function" ? now : () => now;
21
+ this._log = []; // ordered [{ id, from, to, subject, body, ts }]
22
+ this._seq = 0; // monotonic message id source
23
+ this._delivered = new Map(); // recipient → highest message id already drained
24
+ }
25
+
26
+ /**
27
+ * Post a message. `to` is a teammate id or "*" (broadcast). Returns the stored
28
+ * message (with its assigned id).
29
+ */
30
+ send({ from = null, to, subject = null, body = null } = {}) {
31
+ if (!to || typeof to !== "string") {
32
+ throw new Error("TeamMailbox.send: `to` recipient (or '*') is required");
33
+ }
34
+ const msg = {
35
+ id: ++this._seq,
36
+ from,
37
+ to,
38
+ subject,
39
+ body,
40
+ ts: this._now(),
41
+ };
42
+ this._log.push(msg);
43
+ return { ...msg };
44
+ }
45
+
46
+ _isFor(msg, recipient) {
47
+ return msg.to === recipient || msg.to === "*";
48
+ }
49
+
50
+ /** Peek at a recipient's undelivered messages WITHOUT marking them delivered. */
51
+ peek(recipient) {
52
+ const cursor = this._delivered.get(recipient) || 0;
53
+ return this._log
54
+ .filter(
55
+ (m) =>
56
+ m.id > cursor && m.from !== recipient && this._isFor(m, recipient),
57
+ )
58
+ .map((m) => ({ ...m }));
59
+ }
60
+
61
+ /**
62
+ * Return a recipient's undelivered messages and advance its delivery cursor so
63
+ * they are not returned again. A teammate never receives its own broadcast.
64
+ */
65
+ drain(recipient) {
66
+ const pending = this.peek(recipient);
67
+ if (pending.length > 0) {
68
+ const highest = pending[pending.length - 1].id;
69
+ const cursor = this._delivered.get(recipient) || 0;
70
+ this._delivered.set(recipient, Math.max(cursor, highest));
71
+ }
72
+ return pending;
73
+ }
74
+
75
+ /** How many messages a recipient has yet to drain. */
76
+ pendingCount(recipient) {
77
+ return this.peek(recipient).length;
78
+ }
79
+
80
+ /** The full ordered message log (for a status panel / audit). */
81
+ log() {
82
+ return this._log.map((m) => ({ ...m }));
83
+ }
84
+
85
+ size() {
86
+ return this._log.length;
87
+ }
88
+
89
+ snapshot() {
90
+ return {
91
+ log: this._log.map((m) => ({ ...m })),
92
+ seq: this._seq,
93
+ delivered: Array.from(this._delivered.entries()),
94
+ };
95
+ }
96
+
97
+ static restore(snap, { now = () => Date.now() } = {}) {
98
+ const mb = new TeamMailbox({ now });
99
+ mb._log = Array.isArray(snap?.log) ? snap.log.map((m) => ({ ...m })) : [];
100
+ mb._seq =
101
+ Number(snap?.seq) ||
102
+ mb._log.reduce((max, m) => Math.max(max, m.id || 0), 0);
103
+ mb._delivered = new Map(snap?.delivered || []);
104
+ return mb;
105
+ }
106
+ }