chainlesschain 0.165.6 → 0.165.9

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 (251) hide show
  1. package/package.json +2 -1
  2. package/src/assets/web-panel/.build-hash +1 -1
  3. package/src/assets/web-panel/assets/{AIOps-vfoDb6HZ.js → AIOps-DxwjrnEA.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-Cm5f7p93.js → ActionButton-JdbYowzn.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-yRuvgWxa.js → Analytics-C06GCahv.js} +3 -3
  6. package/src/assets/web-panel/assets/{AppLayout-BROsC4h1.js → AppLayout-BUVEEyi1.js} +4 -4
  7. package/src/assets/web-panel/assets/{Artifacts-CimHApb1.js → Artifacts-D_ORzkJd.js} +1 -1
  8. package/src/assets/web-panel/assets/{Audit-DI8kr8zI.js → Audit-8o_Y5ejG.js} +1 -1
  9. package/src/assets/web-panel/assets/{BackgroundAgents-DSLzKy-n.js → BackgroundAgents-DrkHd5VU.js} +1 -1
  10. package/src/assets/web-panel/assets/{Backup-DkH_n2r1.js → Backup-CKBZCX11.js} +1 -1
  11. package/src/assets/web-panel/assets/{BaseInput-CKSrUoRA.js → BaseInput-C8HkI399.js} +1 -1
  12. package/src/assets/web-panel/assets/{Chat-ClwfMXUS.js → Chat-zb2H2Wux.js} +6 -6
  13. package/src/assets/web-panel/assets/{ChatBubbleRenderer-CjMaqfL7.js → ChatBubbleRenderer-BDcNpU_F.js} +1 -1
  14. package/src/assets/web-panel/assets/{Checkbox-BYTehcQG.js → Checkbox-MwPtQmNz.js} +1 -1
  15. package/src/assets/web-panel/assets/{Codegen-B8AMcp6J.js → Codegen-3p5QCkFK.js} +1 -1
  16. package/src/assets/web-panel/assets/{Col-BNSogPo3.js → Col-BPLlhn52.js} +1 -1
  17. package/src/assets/web-panel/assets/{Community-JxPvGb8O.js → Community-C-3dBNOM.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compact-DF7bFAgE.js → Compact-BUcy-F_b.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compliance-Bvhqnwrq.js → Compliance-CI8NxT8m.js} +1 -1
  20. package/src/assets/web-panel/assets/{Cowork-BDS0CJvM.js → Cowork-DaDEBS-Q.js} +3 -3
  21. package/src/assets/web-panel/assets/{Cron-DqvRfxoQ.js → Cron-BSv9HXu5.js} +2 -2
  22. package/src/assets/web-panel/assets/{Crosschain-CDcIvnqS.js → Crosschain-yuiKlJmv.js} +1 -1
  23. package/src/assets/web-panel/assets/{DID-D7jaUxU3.js → DID-D6LfldB0.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dashboard-B5A18wEV.js → Dashboard-CBhLqG61.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dropdown-_6IVkGag.js → Dropdown-BG8pIl3O.js} +1 -1
  26. package/src/assets/web-panel/assets/{EmailListRenderer-C9PeiKCG.js → EmailListRenderer-CY92VIE6.js} +1 -1
  27. package/src/assets/web-panel/assets/{FamilyGuardDashboard-DXjuEHrX.js → FamilyGuardDashboard-1Q_ehauM.js} +1 -1
  28. package/src/assets/web-panel/assets/{Federation-B4IAR9zv.js → Federation-D3XirJIU.js} +1 -1
  29. package/src/assets/web-panel/assets/{FormItemContext-CxQeSNx1.js → FormItemContext-DfI9UuGZ.js} +1 -1
  30. package/src/assets/web-panel/assets/{GenericCardRenderer-B8GS-Y3N.js → GenericCardRenderer-IeCZk2m7.js} +1 -1
  31. package/src/assets/web-panel/assets/{Git-BS8SouUX.js → Git-DzK4IJAZ.js} +2 -2
  32. package/src/assets/web-panel/assets/{Governance-Dq9aYb5Q.js → Governance-BHf5KfmQ.js} +1 -1
  33. package/src/assets/web-panel/assets/{Inference-BrCEe9AJ.js → Inference-BmaxSALS.js} +1 -1
  34. package/src/assets/web-panel/assets/{KnowledgeGraph-DybOpiY2.js → KnowledgeGraph-BgePFLA9.js} +1 -1
  35. package/src/assets/web-panel/assets/{Logs-DBpzbRfx.js → Logs-nqgxpOc4.js} +2 -2
  36. package/src/assets/web-panel/assets/MarkdownRenderer-xkX6izBt.js +1 -0
  37. package/src/assets/web-panel/assets/{Marketplace-Cc7CCSCu.js → Marketplace-Bu8to-86.js} +1 -1
  38. package/src/assets/web-panel/assets/McpTools-BFWTzEk0.js +4 -0
  39. package/src/assets/web-panel/assets/{Memory-KCqlp9ol.js → Memory-B026G2GA.js} +2 -2
  40. package/src/assets/web-panel/assets/{MobileBridge-Bys84ySN.js → MobileBridge-CM2Eeg6I.js} +1 -1
  41. package/src/assets/web-panel/assets/{MobileProjects-B-6qU-je.js → MobileProjects-B79Ob3hy.js} +1 -1
  42. package/src/assets/web-panel/assets/{Mtc-BgY-fvCq.js → Mtc-CxevSZuV.js} +4 -4
  43. package/src/assets/web-panel/assets/{MtcAudit-B8pCmqQJ.js → MtcAudit-X396cPEX.js} +4 -4
  44. package/src/assets/web-panel/assets/{Multisig-BbOmYutA.js → Multisig-HWIB_aZ1.js} +3 -3
  45. package/src/assets/web-panel/assets/{NLProgramming-BocLQz_M.js → NLProgramming-gnYD1Xwy.js} +1 -1
  46. package/src/assets/web-panel/assets/{Notes-CSwbxEop.js → Notes-Dn2Gf_J1.js} +3 -3
  47. package/src/assets/web-panel/assets/{NotificationSettings-CqFNSYG3.js → NotificationSettings-BRKBQ5rq.js} +1 -1
  48. package/src/assets/web-panel/assets/{OrderTableRenderer-C6gMfAk7.js → OrderTableRenderer-Dy___Cgo.js} +1 -1
  49. package/src/assets/web-panel/assets/{Organization-QrDXVdMJ.js → Organization-cqfccxnt.js} +4 -4
  50. package/src/assets/web-panel/assets/{Overflow-Cxntotjt.js → Overflow-CSX4FHN3.js} +1 -1
  51. package/src/assets/web-panel/assets/{P2P-CIdBOwNq.js → P2P-Bwa4aghp.js} +2 -2
  52. package/src/assets/web-panel/assets/{PdhVaultBrowser-CT5aP8rD.js → PdhVaultBrowser-Dhszyjig.js} +3 -3
  53. package/src/assets/web-panel/assets/{Permissions-DH7OM8XU.js → Permissions-CXzTyk6e.js} +4 -4
  54. package/src/assets/web-panel/assets/{PersonalDataHub-CnJqbWce.js → PersonalDataHub-CFCAAJbb.js} +4 -4
  55. package/src/assets/web-panel/assets/{Pipeline-5C3HG1kH.js → Pipeline-BPJbx6xt.js} +1 -1
  56. package/src/assets/web-panel/assets/{Privacy-B4yIp7xK.js → Privacy-BbiXvEHz.js} +1 -1
  57. package/src/assets/web-panel/assets/{ProjectInit-BMvt-2M0.js → ProjectInit-B-s7-wSD.js} +2 -2
  58. package/src/assets/web-panel/assets/{ProjectSettings-CvSTEMRG.js → ProjectSettings-Dyug206y.js} +2 -2
  59. package/src/assets/web-panel/assets/{Projects-BKBJxEek.js → Projects-BtAI5Fwo.js} +1 -1
  60. package/src/assets/web-panel/assets/{Providers-BdwUqFWN.js → Providers-JpIPmz6N.js} +1 -1
  61. package/src/assets/web-panel/assets/{QrScannerModal-DtnCKlTm.js → QrScannerModal-CZdoN64A.js} +1 -1
  62. package/src/assets/web-panel/assets/{QuickAsk-sTbgwyNr.js → QuickAsk-BwvnoRIT.js} +1 -1
  63. package/src/assets/web-panel/assets/{Recommend-Bzr14gSb.js → Recommend-ASMlMQrD.js} +1 -1
  64. package/src/assets/web-panel/assets/{RemoteSession-C_TF5IaJ.js → RemoteSession-BiJzKFKi.js} +3 -3
  65. package/src/assets/web-panel/assets/{Reputation-GCAM4H2K.js → Reputation-C80TWLIO.js} +1 -1
  66. package/src/assets/web-panel/assets/{Row-Dir0r6OQ.js → Row-C1VNmjmX.js} +1 -1
  67. package/src/assets/web-panel/assets/{RssFeed-CpGnlecX.js → RssFeed-T2MyGUlG.js} +3 -3
  68. package/src/assets/web-panel/assets/{Search-DMMamC6u.js → Search-DXM6O83L.js} +1 -1
  69. package/src/assets/web-panel/assets/{Security-yqay5yG-.js → Security-nv69kU7W.js} +3 -3
  70. package/src/assets/web-panel/assets/{Services-C5i2wh9G.js → Services-AyHWe7Qj.js} +2 -2
  71. package/src/assets/web-panel/assets/{Skeleton-vUqk35yj.js → Skeleton-D6PWLxFI.js} +1 -1
  72. package/src/assets/web-panel/assets/{Skills-DzcSkhot.js → Skills-BqVeOmNQ.js} +1 -1
  73. package/src/assets/web-panel/assets/{Sla-CB6qbYNr.js → Sla-BegIPl9M.js} +1 -1
  74. package/src/assets/web-panel/assets/{SpeechSettings-BHUI7cox.js → SpeechSettings-CKJn3xk-.js} +1 -1
  75. package/src/assets/web-panel/assets/{SyncSettings-D-RrsxTJ.js → SyncSettings-Th4cnAMl.js} +2 -2
  76. package/src/assets/web-panel/assets/{Tasks-DFxdOJiq.js → Tasks-BuKfBroh.js} +1 -1
  77. package/src/assets/web-panel/assets/{Templates-SKAob_Aq.js → Templates-CMuWN8Ac.js} +1 -1
  78. package/src/assets/web-panel/assets/{Tenant-BPfsfHQ-.js → Tenant-C4je8E8Q.js} +1 -1
  79. package/src/assets/web-panel/assets/{Terminal-8iw7FpUf.js → Terminal-agJESOcO.js} +2 -2
  80. package/src/assets/web-panel/assets/{TimelineRenderer-C4Gjj5Su.js → TimelineRenderer-D2qDuVZ_.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tokens-COYhN18e.js → Tokens-Cvmn99rH.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trigger-B-JzXcf6.js → Trigger-D_QYIHH8.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trust-BJ5bg0nI.js → Trust-BmzvQLnX.js} +1 -1
  84. package/src/assets/web-panel/assets/{UkeySign-BG_ByRMf.js → UkeySign-D_LopHwH.js} +1 -1
  85. package/src/assets/web-panel/assets/{VideoEditing-B3kOnSiU.js → VideoEditing-DoWl3PhQ.js} +1 -1
  86. package/src/assets/web-panel/assets/{Wallet-CcoGFnK7.js → Wallet-5NYcAkxe.js} +4 -4
  87. package/src/assets/web-panel/assets/{WebAuthn-DdT8vRC_.js → WebAuthn-B-_Q3ewp.js} +4 -4
  88. package/src/assets/web-panel/assets/{WorkflowEditor-CZu3M9N8.js → WorkflowEditor-CHET-gi2.js} +1 -1
  89. package/src/assets/web-panel/assets/{chat-DxJt3Qba.js → chat-C6nvb-n6.js} +1 -1
  90. package/src/assets/web-panel/assets/{colors-DdYfwJaB.js → colors-CTDE9LTX.js} +1 -1
  91. package/src/assets/web-panel/assets/{compact-item-BG3SUzwj.js → compact-item-KnemdF8D.js} +1 -1
  92. package/src/assets/web-panel/assets/{createContext-By51z2-1.js → createContext-DUJR3Uek.js} +1 -1
  93. package/src/assets/web-panel/assets/devWarning-1DqLH1KX.js +1 -0
  94. package/src/assets/web-panel/assets/{hasIn-B0apZ3KJ.js → hasIn-CFNTaby8.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-ZxtP8jG7.js → index-65HO56Bv.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-Da_WBdLX.js → index-B8RtbSkV.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-BBPf3TpM.js → index-BHqSqnW7.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-BP94CrBV.js → index-BTetEUWd.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-p1_8SbI1.js → index-BXWg1crv.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-BWF8_mkx.js → index-BdiC4LOs.js} +1 -1
  101. package/src/assets/web-panel/assets/index-BhJtcPWo.js +1 -0
  102. package/src/assets/web-panel/assets/{index-SQqOlR3d.js → index-BuizJo2E.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-CpjZhgNd.js → index-BwZM1rod.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-CzmQElZy.js → index-C96N9xeL.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-bP_VlDb4.js → index-CHfUdnJH.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-ZFbps9po.js → index-CKvBF3Ov.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-DaIz0NAW.js → index-CSD9_I2_.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-BewKYrX9.js → index-Cc7dbl_p.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-CNIxXMmN.js → index-CcF0D7UZ.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-Q2SCX-MI.js → index-Ceu92a8D.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-DE76D-kA.js → index-CnLpuTAU.js} +1 -1
  112. package/src/assets/web-panel/assets/index-Cq8MWPDT.js +1 -0
  113. package/src/assets/web-panel/assets/{index-BkvcEo4p.js → index-CquKnw5r.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-Cp6e-hGw.js → index-CyEtHvEE.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-BLIbGOLX.js → index-DF-fcTvQ.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-DCn8IcxV.js → index-DF1Y0Vuw.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-Bissefox.js → index-DJyPeBBk.js} +3 -3
  118. package/src/assets/web-panel/assets/{index-BZ_0qm4g.js → index-DP-PbO11.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-kf57LJ93.js → index-DZgCRMxk.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-BYINoRLK.js → index-DfJzS3Qa.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-3jWkIu5j.js → index-DqPpJkte.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-B5rLiEo0.js → index-DzReoLyh.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-CW93tZDv.js → index-JGTiDeOW.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-C1C9i79A.js → index-NUR0If7O.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-CAhHirVH.js → index-NtAcnJYW.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-RD6O-0C6.js → index-OmuhFYKE.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-oxRrmFO9.js → index-Wlc7o_C8.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-B3rWuPMW.js → index-bxTPfdri.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-BIrJfqO4.js → index-k6uorYmf.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-BfWYsB9t.js → index-ox6mIwE2.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-Bos5_aMV.js → index-rVDD7AAR.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-CWEuZWCK.js → index-u0h6QqVV.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-Dz49VOyQ.js → index-v-mtxmWH.js} +1 -1
  134. package/src/assets/web-panel/assets/{initDefaultProps-BWJktcg8.js → initDefaultProps-qEt8dI-t.js} +1 -1
  135. package/src/assets/web-panel/assets/{motion-BEXBzUFV.js → motion-DetKPWy-.js} +1 -1
  136. package/src/assets/web-panel/assets/{move-BUKwn2sx.js → move-BeNYeDV2.js} +1 -1
  137. package/src/assets/web-panel/assets/{mtc-parser-FTI7btqQ.js → mtc-parser-C0q0MK_T.js} +1 -1
  138. package/src/assets/web-panel/assets/{omit-CsLtSPH3.js → omit-DLKkrKpE.js} +1 -1
  139. package/src/assets/web-panel/assets/{pickAttrs-DWZxsT_S.js → pickAttrs-Bw1tWsY8.js} +1 -1
  140. package/src/assets/web-panel/assets/{placementArrow-Bcfs43CX.js → placementArrow-DeesBLbd.js} +1 -1
  141. package/src/assets/web-panel/assets/{responsiveObserve-a9bcC1p-.js → responsiveObserve-bqLhxHhn.js} +1 -1
  142. package/src/assets/web-panel/assets/{slide-BDTzgOXN.js → slide-DGCXRoQm.js} +1 -1
  143. package/src/assets/web-panel/assets/{statusUtils-Iq1j_HUg.js → statusUtils-H-re3Egl.js} +1 -1
  144. package/src/assets/web-panel/assets/{styleChecker-DAZEYxeh.js → styleChecker-BXg4qGX_.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFlexGapSupport-DVkjyDrh.js → useFlexGapSupport-CKLb2KA6.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFs-wXTpNTHt.js → useFs-CTAhHeyw.js} +1 -1
  147. package/src/assets/web-panel/assets/{usePersonalDataHub-CtQM_aZr.js → usePersonalDataHub-BQ4pDh5g.js} +1 -1
  148. package/src/assets/web-panel/assets/{vnode-hjEoG85v.js → vnode-jrSK6j4j.js} +1 -1
  149. package/src/assets/web-panel/assets/{zoom-BHVpyKg_.js → zoom-fOQE24pw.js} +1 -1
  150. package/src/assets/web-panel/index.html +1 -1
  151. package/src/command-help-index.json +3 -3
  152. package/src/commands/agent.js +91 -60
  153. package/src/commands/git.js +39 -0
  154. package/src/commands/plugin.js +17 -2
  155. package/src/commands/session-group.js +131 -0
  156. package/src/commands/session-location.js +48 -0
  157. package/src/commands/session.js +11 -0
  158. package/src/commands/sync-providers.js +4 -1
  159. package/src/commands/update.js +1 -0
  160. package/src/data/changelog.json +40 -1
  161. package/src/gateways/ws/mcp-topic-handlers.js +167 -0
  162. package/src/gateways/ws/ws-agent-handler.js +20 -12
  163. package/src/harness/jsonl-session-store.js +279 -37
  164. package/src/harness/mcp-client.js +701 -91
  165. package/src/harness/session-index.js +15 -4
  166. package/src/lib/agent-network.js +166 -29
  167. package/src/lib/agent-router.js +62 -6
  168. package/src/lib/agent-sandbox.js +7 -1
  169. package/src/lib/agent-worktree.js +8 -1
  170. package/src/lib/auto-mode-safety-classifier.js +3 -17
  171. package/src/lib/background-agent-keeper-protocol.js +26 -6
  172. package/src/lib/background-agent-supervisor.js +17 -1
  173. package/src/lib/browser-evidence.js +1405 -0
  174. package/src/lib/chrome-connector.js +465 -79
  175. package/src/lib/claude-code-bridge.js +159 -54
  176. package/src/lib/claude-project-auto-memory.js +481 -0
  177. package/src/lib/claude-project-storage-layout.js +104 -0
  178. package/src/lib/cloud/cloud-client.js +40 -0
  179. package/src/lib/cowork-workflow.js +69 -6
  180. package/src/lib/execution-location-local-supervisor.mjs +226 -0
  181. package/src/lib/execution-location-runner-lifecycle.js +991 -0
  182. package/src/lib/execution-location-target-preflight.js +333 -0
  183. package/src/lib/execution-location-target.js +783 -13
  184. package/src/lib/external-agent-adapters.js +269 -0
  185. package/src/lib/file-lines.js +6 -1
  186. package/src/lib/gateway-client-resilience.js +571 -0
  187. package/src/lib/gitlab-mr-worktree.js +176 -0
  188. package/src/lib/hooks-v2-producers.js +119 -15
  189. package/src/lib/hooks-v2-workspace-context.js +44 -1
  190. package/src/lib/host-resource-budget.js +295 -0
  191. package/src/lib/ide-context.js +15 -2
  192. package/src/lib/ipfs-storage.js +127 -25
  193. package/src/lib/llm-config-defaults.js +43 -0
  194. package/src/lib/mcp-lifecycle-authority.js +887 -0
  195. package/src/lib/mcp-oauth.js +101 -20
  196. package/src/lib/mcp-tls.js +103 -3
  197. package/src/lib/orchestrator.js +47 -0
  198. package/src/lib/paths.js +260 -32
  199. package/src/lib/permission-request.js +48 -0
  200. package/src/lib/plugin-runtime/bin.js +3 -1
  201. package/src/lib/plugin-runtime/hooks.js +3 -1
  202. package/src/lib/plugin-runtime/install.js +117 -1
  203. package/src/lib/plugin-runtime/lsp.js +4 -4
  204. package/src/lib/plugin-runtime/marketplace-archive-source.js +40 -5
  205. package/src/lib/plugin-runtime/marketplace-catalog.js +13 -2
  206. package/src/lib/plugin-runtime/marketplace-command-descriptor.js +343 -0
  207. package/src/lib/plugin-runtime/marketplace-command-source.js +580 -0
  208. package/src/lib/plugin-runtime/marketplace-source-adapter.js +33 -8
  209. package/src/lib/plugin-runtime/mcp.js +3 -1
  210. package/src/lib/plugin-runtime/monitors.js +3 -1
  211. package/src/lib/plugin-runtime/remote-source.js +151 -6
  212. package/src/lib/plugin-runtime/settings.js +3 -1
  213. package/src/lib/plugin-runtime/trust.js +274 -13
  214. package/src/lib/process-execution-broker/credential-agent.js +210 -41
  215. package/src/lib/process-execution-broker/index.js +15 -1
  216. package/src/lib/process-execution-broker/linux-cgroup-v2.js +391 -0
  217. package/src/lib/process-execution-broker/platform-sandbox.js +50 -28
  218. package/src/lib/project-mcp-trust.js +165 -10
  219. package/src/lib/resume-session.js +9 -6
  220. package/src/lib/session-core-singletons.js +26 -11
  221. package/src/lib/session-host-lease.js +52 -16
  222. package/src/lib/session-lifecycle-scope.js +153 -0
  223. package/src/lib/session-message-fabric.js +168 -21
  224. package/src/lib/session-projection.js +244 -4
  225. package/src/lib/session-store-guard.js +3 -1
  226. package/src/lib/session-workbench-store.js +466 -0
  227. package/src/lib/shell-approval.js +7 -5
  228. package/src/lib/spellcheck.js +211 -0
  229. package/src/lib/sub-agent-context.js +9 -9
  230. package/src/lib/sync-cli-db.js +485 -129
  231. package/src/lib/sync-credentials.js +31 -20
  232. package/src/lib/web-fetch.js +112 -40
  233. package/src/lib/web-search.js +127 -61
  234. package/src/lib/workflow-engine.js +42 -11
  235. package/src/lib/workspace-trust.js +793 -0
  236. package/src/repl/agent-repl.js +124 -5
  237. package/src/repl/permission-prompt.js +10 -12
  238. package/src/runtime/agent-core.js +311 -18
  239. package/src/runtime/agent-runtime.js +12 -1
  240. package/src/runtime/bootstrap.js +37 -13
  241. package/src/runtime/coding-agent-contract-shared.cjs +11 -4
  242. package/src/runtime/headless-runner.js +234 -36
  243. package/src/runtime/headless-stream.js +181 -6
  244. package/src/runtime/mcp-config.js +298 -8
  245. package/src/runtime/policies/agent-policy.js +8 -0
  246. package/src/runtime/status-diagnostics-lite.js +9 -1
  247. package/src/assets/web-panel/assets/MarkdownRenderer-CFyt8iUd.js +0 -1
  248. package/src/assets/web-panel/assets/McpTools-DFaIhOfu.js +0 -7
  249. package/src/assets/web-panel/assets/devWarning-DorRZcI9.js +0 -1
  250. package/src/assets/web-panel/assets/index-BETiL0a3.js +0 -1
  251. package/src/assets/web-panel/assets/index-BhAVHuW8.js +0 -1
@@ -12,7 +12,12 @@
12
12
  import fs from "node:fs";
13
13
  import path from "node:path";
14
14
  import { createHash, randomUUID } from "node:crypto";
15
- import { ensureDir, getHomeDir } from "./paths.js";
15
+ import { ensureDir } from "./paths.js";
16
+ import {
17
+ ensureTrustedSessionLifecycleScope,
18
+ resolveTrustedSessionLifecycleScope,
19
+ sessionLifecycleFabricDirectory,
20
+ } from "./session-lifecycle-scope.js";
16
21
  import { withFileLock } from "./with-file-lock.js";
17
22
 
18
23
  export const SESSION_MESSAGE_FABRIC_SCHEMA =
@@ -31,6 +36,7 @@ export const SESSION_MESSAGE_RECEIPT_STATUSES = Object.freeze([
31
36
  "held",
32
37
  "refused",
33
38
  "full",
39
+ "rate_limited",
34
40
  "expired",
35
41
  ]);
36
42
 
@@ -38,6 +44,8 @@ export const SESSION_MESSAGE_FABRIC_LIMITS = Object.freeze({
38
44
  maxMessageBytes: 256 * 1024,
39
45
  maxPendingPerRecipient: 100,
40
46
  maxReceiptHistory: 5_000,
47
+ maxMessagesPerSenderWindow: 120,
48
+ senderRateWindowMs: 60_000,
41
49
  defaultTtlMs: 24 * 60 * 60 * 1000,
42
50
  maxTtlMs: 7 * 24 * 60 * 60 * 1000,
43
51
  });
@@ -153,6 +161,9 @@ function emptyState(now) {
153
161
  channels: [],
154
162
  messages: [],
155
163
  receipts: [],
164
+ // Per-sender fixed windows are durable so a new CLI process cannot evade
165
+ // the same-host admission limit by reopening the fabric state file.
166
+ rateBuckets: [],
156
167
  };
157
168
  return { ...state, digest: digestValue(state) };
158
169
  }
@@ -326,6 +337,41 @@ function updateReceipt(state, message, status, reason, now) {
326
337
  return receipt;
327
338
  }
328
339
 
340
+ function pruneRateBuckets(state, now, windowMs) {
341
+ if (!Array.isArray(state.rateBuckets) || state.rateBuckets.length === 0) {
342
+ return false;
343
+ }
344
+ const before = state.rateBuckets.length;
345
+ state.rateBuckets = state.rateBuckets.filter(
346
+ (bucket) => now < bucket.windowStartedAt + windowMs,
347
+ );
348
+ return state.rateBuckets.length !== before;
349
+ }
350
+
351
+ function consumeSenderRate(state, senderAuthority, now, limit, windowMs) {
352
+ state.rateBuckets ??= [];
353
+ let bucket = state.rateBuckets.find(
354
+ (entry) => entry.senderAuthority === senderAuthority,
355
+ );
356
+ if (!bucket || now >= bucket.windowStartedAt + windowMs) {
357
+ if (!bucket) {
358
+ bucket = { senderAuthority, windowStartedAt: now, count: 0 };
359
+ state.rateBuckets.push(bucket);
360
+ } else {
361
+ bucket.windowStartedAt = now;
362
+ bucket.count = 0;
363
+ }
364
+ }
365
+ if (bucket.count >= limit) {
366
+ return {
367
+ allowed: false,
368
+ retryAfterMs: Math.max(1, bucket.windowStartedAt + windowMs - now),
369
+ };
370
+ }
371
+ bucket.count += 1;
372
+ return { allowed: true, retryAfterMs: 0 };
373
+ }
374
+
329
375
  function trimReceiptHistory(state, limit) {
330
376
  if (state.receipts.length <= limit) return false;
331
377
  const protectedIds = new Set(
@@ -368,12 +414,18 @@ function compactSettledMessages(state) {
368
414
  }
369
415
 
370
416
  function applyExpectedMessage(state, channel, message, recipient, now) {
371
- if (recipient.policy === "hold") {
372
- updateReceipt(state, message, "held", "policy_hold", now);
373
- return false;
374
- }
375
417
  if (recipient.policy === "refuse") {
376
418
  updateReceipt(state, message, "refused", "policy_refuse", now);
419
+ } else if (recipient.policy === "hold") {
420
+ updateReceipt(state, message, "held", "policy_hold", now);
421
+ return false;
422
+ } else if (recipient.online !== true) {
423
+ // Durable admission is not delivery. In particular, an offline target
424
+ // must never produce a success-looking `delivered` receipt merely because
425
+ // this host appended the payload to its local queue. Reconnect promotes
426
+ // the exact retained sequence through the same policy gate.
427
+ updateReceipt(state, message, "held", "recipient_offline", now);
428
+ return false;
377
429
  } else {
378
430
  const receipt = updateReceipt(state, message, "delivered", null, now);
379
431
  message.deliveredAt = now;
@@ -455,6 +507,7 @@ function validateState(state) {
455
507
  !Array.isArray(state.channels) ||
456
508
  !Array.isArray(state.messages) ||
457
509
  !Array.isArray(state.receipts) ||
510
+ (state.rateBuckets != null && !Array.isArray(state.rateBuckets)) ||
458
511
  typeof state.digest !== "string"
459
512
  ) {
460
513
  fail("invalid envelope");
@@ -520,29 +573,56 @@ function validateState(state) {
520
573
  fail("invalid receipt");
521
574
  }
522
575
  }
576
+ const bucketAuthorities = new Set();
577
+ for (const bucket of state.rateBuckets || []) {
578
+ if (
579
+ typeof bucket?.senderAuthority !== "string" ||
580
+ bucketAuthorities.has(bucket.senderAuthority) ||
581
+ !Number.isSafeInteger(bucket.windowStartedAt) ||
582
+ bucket.windowStartedAt < 0 ||
583
+ !Number.isSafeInteger(bucket.count) ||
584
+ bucket.count < 1
585
+ ) {
586
+ fail("invalid rate bucket");
587
+ }
588
+ bucketAuthorities.add(bucket.senderAuthority);
589
+ }
523
590
  return state;
524
591
  }
525
592
 
526
- export function defaultSessionMessageFabricPath() {
527
- return path.join(getHomeDir(), "session-message-fabric", "state.json");
593
+ export function defaultSessionMessageFabricPath(options = {}) {
594
+ return resolveTrustedSessionLifecycleScope(options).messageFabricStatePath;
528
595
  }
529
596
 
530
597
  export class SessionMessageFabric {
531
- constructor({
532
- statePath = defaultSessionMessageFabricPath(),
533
- now = () => Date.now(),
534
- createId = randomUUID,
535
- lock = withFileLock,
536
- lockTimeoutMs = 10_000,
537
- maxMessageBytes = SESSION_MESSAGE_FABRIC_LIMITS.maxMessageBytes,
538
- maxPendingPerRecipient = SESSION_MESSAGE_FABRIC_LIMITS.maxPendingPerRecipient,
539
- maxReceiptHistory = SESSION_MESSAGE_FABRIC_LIMITS.maxReceiptHistory,
540
- defaultTtlMs = SESSION_MESSAGE_FABRIC_LIMITS.defaultTtlMs,
541
- maxTtlMs = SESSION_MESSAGE_FABRIC_LIMITS.maxTtlMs,
542
- } = {}) {
598
+ constructor(options = {}) {
599
+ const {
600
+ statePath: configuredStatePath,
601
+ launchEnv,
602
+ cwd,
603
+ now = () => Date.now(),
604
+ createId = randomUUID,
605
+ lock = withFileLock,
606
+ lockTimeoutMs = 10_000,
607
+ maxMessageBytes = SESSION_MESSAGE_FABRIC_LIMITS.maxMessageBytes,
608
+ maxPendingPerRecipient = SESSION_MESSAGE_FABRIC_LIMITS.maxPendingPerRecipient,
609
+ maxReceiptHistory = SESSION_MESSAGE_FABRIC_LIMITS.maxReceiptHistory,
610
+ maxMessagesPerSenderWindow = SESSION_MESSAGE_FABRIC_LIMITS.maxMessagesPerSenderWindow,
611
+ senderRateWindowMs = SESSION_MESSAGE_FABRIC_LIMITS.senderRateWindowMs,
612
+ defaultTtlMs = SESSION_MESSAGE_FABRIC_LIMITS.defaultTtlMs,
613
+ maxTtlMs = SESSION_MESSAGE_FABRIC_LIMITS.maxTtlMs,
614
+ } = options;
615
+ const usesDefaultStatePath = configuredStatePath === undefined;
616
+ const lifecycleScope = usesDefaultStatePath
617
+ ? resolveTrustedSessionLifecycleScope({ launchEnv, cwd })
618
+ : null;
619
+ const statePath = usesDefaultStatePath
620
+ ? lifecycleScope.messageFabricStatePath
621
+ : configuredStatePath;
543
622
  this.statePath = path.resolve(statePath);
623
+ this._lifecycleScope = lifecycleScope;
544
624
  this._secureDefaultDirectory =
545
- this.statePath === path.resolve(defaultSessionMessageFabricPath());
625
+ usesDefaultStatePath && lifecycleScope.kind === "legacy";
546
626
  this._now = now;
547
627
  this._createId = createId;
548
628
  this._lock = lock;
@@ -560,6 +640,14 @@ export class SessionMessageFabric {
560
640
  maxReceiptHistory,
561
641
  SESSION_MESSAGE_FABRIC_LIMITS.maxReceiptHistory,
562
642
  ),
643
+ maxMessagesPerSenderWindow: positiveInteger(
644
+ maxMessagesPerSenderWindow,
645
+ SESSION_MESSAGE_FABRIC_LIMITS.maxMessagesPerSenderWindow,
646
+ ),
647
+ senderRateWindowMs: positiveInteger(
648
+ senderRateWindowMs,
649
+ SESSION_MESSAGE_FABRIC_LIMITS.senderRateWindowMs,
650
+ ),
563
651
  defaultTtlMs: positiveInteger(
564
652
  defaultTtlMs,
565
653
  SESSION_MESSAGE_FABRIC_LIMITS.defaultTtlMs,
@@ -573,6 +661,14 @@ export class SessionMessageFabric {
573
661
 
574
662
  _ensureDirectory() {
575
663
  const directory = path.dirname(this.statePath);
664
+ if (this._lifecycleScope?.kind === "project") {
665
+ ensureTrustedSessionLifecycleScope(this._lifecycleScope, {
666
+ extraDirectories: [
667
+ sessionLifecycleFabricDirectory(this._lifecycleScope),
668
+ ],
669
+ });
670
+ return;
671
+ }
576
672
  if (this._secureDefaultDirectory) {
577
673
  ensureDir(directory);
578
674
  return;
@@ -667,13 +763,24 @@ export class SessionMessageFabric {
667
763
  }
668
764
  const now = this._now();
669
765
  const swept = sweepExpired(state, now);
766
+ const prunedRateBuckets = pruneRateBuckets(
767
+ state,
768
+ now,
769
+ this._limits.senderRateWindowMs,
770
+ );
670
771
  const outcome = mutator(state, now) || { value: undefined };
671
772
  const compacted = compactSettledMessages(state);
672
773
  const trimmed = trimReceiptHistory(
673
774
  state,
674
775
  this._limits.maxReceiptHistory,
675
776
  );
676
- if (swept || compacted || trimmed || outcome.changed === true) {
777
+ if (
778
+ swept ||
779
+ prunedRateBuckets ||
780
+ compacted ||
781
+ trimmed ||
782
+ outcome.changed === true
783
+ ) {
677
784
  state.revision += 1;
678
785
  state.updatedAt = now;
679
786
  state.digest = digestValue(stateWithoutDigest(state));
@@ -757,6 +864,13 @@ export class SessionMessageFabric {
757
864
  };
758
865
  state.endpoints.push(endpoint);
759
866
  }
867
+ for (const channel of state.channels.filter(
868
+ (entry) =>
869
+ entry.recipientAuthority === endpoint.authorityId &&
870
+ entry.recipientEpoch === endpoint.epoch,
871
+ )) {
872
+ promoteChannel(state, channel, now);
873
+ }
760
874
  return {
761
875
  changed: true,
762
876
  value: { ...clone(endpoint), address: endpointAddress(endpoint) },
@@ -783,6 +897,13 @@ export class SessionMessageFabric {
783
897
  const endpoint = requireEndpoint(state, selector);
784
898
  endpoint.online = true;
785
899
  endpoint.updatedAt = now;
900
+ for (const channel of state.channels.filter(
901
+ (entry) =>
902
+ entry.recipientAuthority === endpoint.authorityId &&
903
+ entry.recipientEpoch === endpoint.epoch,
904
+ )) {
905
+ promoteChannel(state, channel, now);
906
+ }
786
907
  return {
787
908
  changed: true,
788
909
  value: { ...clone(endpoint), address: endpointAddress(endpoint) },
@@ -913,6 +1034,32 @@ export class SessionMessageFabric {
913
1034
  }
914
1035
  return { value: clone(duplicate) };
915
1036
  }
1037
+ const rate = consumeSenderRate(
1038
+ state,
1039
+ sender.authorityId,
1040
+ now,
1041
+ this._limits.maxMessagesPerSenderWindow,
1042
+ this._limits.senderRateWindowMs,
1043
+ );
1044
+ if (!rate.allowed) {
1045
+ const receipt = {
1046
+ messageId: id,
1047
+ senderAuthority: sender.authorityId,
1048
+ recipientAuthority: recipient?.authorityId || null,
1049
+ recipientEpoch: recipient?.epoch || null,
1050
+ sequence: requestedSequence,
1051
+ payloadDigest,
1052
+ status: "rate_limited",
1053
+ reason: "sender_rate_limit",
1054
+ retryAfterMs: rate.retryAfterMs,
1055
+ createdAt: now,
1056
+ updatedAt: now,
1057
+ idleNotifiedAt: null,
1058
+ readAt: null,
1059
+ };
1060
+ state.receipts.push(receipt);
1061
+ return { changed: true, value: clone(receipt) };
1062
+ }
916
1063
  if (!recipient) {
917
1064
  const receipt = {
918
1065
  messageId: id,
@@ -84,6 +84,29 @@ function asText(value) {
84
84
  return typeof value === "string" && value.trim() ? value.trim() : null;
85
85
  }
86
86
 
87
+ function boundedText(value, maxChars = 240) {
88
+ let text = null;
89
+ if (typeof value === "string") text = value;
90
+ else if (value && typeof value === "object") {
91
+ text =
92
+ value.summary ||
93
+ value.title ||
94
+ value.question ||
95
+ value.text ||
96
+ value.answer ||
97
+ null;
98
+ }
99
+ if (typeof text !== "string") return null;
100
+ const normalized = text
101
+ .replace(/[\u0000-\u001f\u007f]+/gu, " ")
102
+ .replace(/\s+/gu, " ")
103
+ .trim();
104
+ if (!normalized) return null;
105
+ return normalized.length <= maxChars
106
+ ? normalized
107
+ : `${normalized.slice(0, Math.max(0, maxChars - 1))}…`;
108
+ }
109
+
87
110
  function asCount(value) {
88
111
  const number = Number(value);
89
112
  return Number.isFinite(number) && number > 0 ? Math.floor(number) : 0;
@@ -646,6 +669,178 @@ function messagingSummary(sessionIds, fabric) {
646
669
  };
647
670
  }
648
671
 
672
+ function pendingInteractionCount(source) {
673
+ if (Array.isArray(source?.pendingInteractions)) {
674
+ return Math.min(1_000, source.pendingInteractions.length);
675
+ }
676
+ return Math.min(1_000, asCount(source?.pendingInteractions));
677
+ }
678
+
679
+ function liveToolSummary(source) {
680
+ const candidate =
681
+ source?.liveTool || source?.currentTool || source?.recent?.call || null;
682
+ const name = boundedText(candidate?.name || candidate?.tool || candidate, 96);
683
+ if (!name) return null;
684
+ return {
685
+ name,
686
+ status: boundedText(candidate?.status || source?.toolStatus, 48),
687
+ };
688
+ }
689
+
690
+ function focusSummary(source, state) {
691
+ const pendingQuestion = boundedText(source?.pendingQuestion, 240);
692
+ const todos = Array.isArray(source?.todos) ? source.todos : [];
693
+ const latestTodo = boundedText(
694
+ source?.latestTodo || source?.todo?.latest || todos.at(-1),
695
+ 240,
696
+ );
697
+ const settledAnswer = boundedText(
698
+ source?.settledAnswer || source?.lastAnswer || source?.answerSummary,
699
+ 240,
700
+ );
701
+ const liveTool = liveToolSummary(source);
702
+ return {
703
+ active: ["working", "needs_input", "blocked"].includes(state),
704
+ liveTool,
705
+ latestTodo,
706
+ pendingQuestion,
707
+ settledAnswer,
708
+ };
709
+ }
710
+
711
+ function locationSummary(source, kind) {
712
+ const hint = String(
713
+ source?.executionLocation?.kind ||
714
+ source?.executionLocation ||
715
+ source?.location?.kind ||
716
+ source?.location ||
717
+ source?.origin ||
718
+ "",
719
+ )
720
+ .trim()
721
+ .toLowerCase();
722
+ const locationKind =
723
+ source?.cloud === true || hint === "cloud"
724
+ ? "cloud"
725
+ : kind === "remote"
726
+ ? "remote"
727
+ : "local";
728
+ const connection = String(source?.connection || source?.connectivity || "")
729
+ .trim()
730
+ .toLowerCase();
731
+ const offline =
732
+ source?.offline === true ||
733
+ source?.online === false ||
734
+ ["offline", "disconnected", "unreachable"].includes(connection) ||
735
+ (kind === "remote" && source?.alive !== true);
736
+ const online =
737
+ !offline &&
738
+ (source?.online === true ||
739
+ ["online", "connected"].includes(connection) ||
740
+ (kind === "remote" && source?.alive === true) ||
741
+ locationKind === "cloud");
742
+ return {
743
+ kind: locationKind,
744
+ status: offline ? "offline" : online ? "online" : "local",
745
+ };
746
+ }
747
+
748
+ function groupingSummary(workbench, error) {
749
+ if (error) {
750
+ return {
751
+ authority: "cli",
752
+ connected: false,
753
+ revision: null,
754
+ generation: 0,
755
+ reason: String(error).slice(0, 240),
756
+ items: [],
757
+ assignments: [],
758
+ };
759
+ }
760
+ if (workbench == null) {
761
+ const items = [];
762
+ const assignments = [];
763
+ return {
764
+ authority: "cli",
765
+ connected: true,
766
+ revision: projectionRevision({
767
+ schema: "chainlesschain.session-workbench-state/v1",
768
+ generation: 0,
769
+ groups: [],
770
+ memberships: {},
771
+ }),
772
+ generation: 0,
773
+ items,
774
+ assignments,
775
+ };
776
+ }
777
+ if (
778
+ typeof workbench !== "object" ||
779
+ workbench.authority !== "cli" ||
780
+ workbench.connected !== true ||
781
+ typeof workbench.revision !== "string" ||
782
+ !Number.isSafeInteger(workbench.generation) ||
783
+ workbench.generation < 0 ||
784
+ !Array.isArray(workbench.items) ||
785
+ workbench.items.length > 128 ||
786
+ !Array.isArray(workbench.assignments) ||
787
+ workbench.assignments.length > 4096
788
+ ) {
789
+ return groupingSummary(null, "session group projection is malformed");
790
+ }
791
+ const ids = new Set();
792
+ const items = [];
793
+ for (const raw of workbench.items) {
794
+ const id = asText(raw?.id);
795
+ const name = boundedText(raw?.name, 80);
796
+ const order = Number(raw?.order);
797
+ if (
798
+ !id ||
799
+ !/^group-[a-zA-Z0-9_-]+$/u.test(id) ||
800
+ !name ||
801
+ !Number.isSafeInteger(order) ||
802
+ order < 0 ||
803
+ ids.has(id)
804
+ ) {
805
+ return groupingSummary(null, "session group projection is malformed");
806
+ }
807
+ ids.add(id);
808
+ items.push({ id, name, order });
809
+ }
810
+ items.sort(
811
+ (left, right) =>
812
+ left.order - right.order || left.name.localeCompare(right.name),
813
+ );
814
+ const assignments = [];
815
+ const seenSessions = new Set();
816
+ for (const raw of workbench.assignments) {
817
+ const sessionId = asText(raw?.sessionId);
818
+ const groupId = asText(raw?.groupId);
819
+ if (
820
+ !sessionId ||
821
+ sessionId.length > 512 ||
822
+ !groupId ||
823
+ !ids.has(groupId) ||
824
+ seenSessions.has(sessionId)
825
+ ) {
826
+ return groupingSummary(null, "session group projection is malformed");
827
+ }
828
+ seenSessions.add(sessionId);
829
+ assignments.push({ sessionId, groupId });
830
+ }
831
+ assignments.sort((left, right) =>
832
+ left.sessionId.localeCompare(right.sessionId),
833
+ );
834
+ return {
835
+ authority: "cli",
836
+ connected: true,
837
+ revision: workbench.revision,
838
+ generation: workbench.generation,
839
+ items,
840
+ assignments,
841
+ };
842
+ }
843
+
649
844
  function lastEventFor(source, state) {
650
845
  const at =
651
846
  toIso(source?.lastEvent?.at) ||
@@ -663,7 +858,7 @@ function lastEventFor(source, state) {
663
858
  function projectOne(
664
859
  kind,
665
860
  source,
666
- { artifacts, prLinks, sessionMessageFabric },
861
+ { artifacts, prLinks, sessionMessageFabric, groupAssignments },
667
862
  ) {
668
863
  let sourceId;
669
864
  if (kind === "remote") {
@@ -707,6 +902,11 @@ function projectOne(
707
902
  sourceId,
708
903
  linkedSessionId,
709
904
  );
905
+ const approval = approvalSummary(source, state);
906
+ const messaging = messagingSummary(linkedSessionIds, sessionMessageFabric);
907
+ const focus = focusSummary(source, state);
908
+ const groupId =
909
+ groupAssignments.get(canonicalSessionId(kind, sourceId)) || null;
710
910
  const projected = {
711
911
  id: canonicalSessionId(kind, sourceId),
712
912
  sourceId,
@@ -735,8 +935,19 @@ function projectOne(
735
935
  kind === "dynamic_workflow"
736
936
  ? { count: asCount(source?.artifacts?.count), latest: null }
737
937
  : artifactSummary(linkedSessionIds, artifacts),
738
- approval: approvalSummary(source, state),
739
- messaging: messagingSummary(linkedSessionIds, sessionMessageFabric),
938
+ approval,
939
+ messaging,
940
+ groupId,
941
+ attention: {
942
+ unread: messaging.unread,
943
+ needsApproval: approval.pending,
944
+ pendingInteractions: Math.min(
945
+ 1_000,
946
+ pendingInteractionCount(source) + (focus.pendingQuestion ? 1 : 0),
947
+ ),
948
+ },
949
+ focus,
950
+ location: locationSummary(source, kind),
740
951
  pr: prSummary(linkedSessionIds, prLinks),
741
952
  workflow: dynamicWorkflowSummary(source),
742
953
  lastEvent: lastEventFor(source, state),
@@ -763,10 +974,23 @@ export function buildSessionProjection({
763
974
  artifacts = [],
764
975
  prLinks = {},
765
976
  sessionMessageFabric = null,
977
+ sessionWorkbench = null,
766
978
  sourceErrors = {},
767
979
  generatedAt = new Date().toISOString(),
768
980
  } = {}) {
769
- const context = { artifacts, prLinks, sessionMessageFabric };
981
+ const groups = groupingSummary(
982
+ sessionWorkbench,
983
+ sourceErrors.sessionWorkbench,
984
+ );
985
+ const groupAssignments = new Map(
986
+ groups.assignments.map((entry) => [entry.sessionId, entry.groupId]),
987
+ );
988
+ const context = {
989
+ artifacts,
990
+ prLinks,
991
+ sessionMessageFabric,
992
+ groupAssignments,
993
+ };
770
994
  const sessions = [
771
995
  ...(Array.isArray(local) ? local : []).map((source) =>
772
996
  projectOne("local", source, context),
@@ -828,6 +1052,11 @@ export function buildSessionProjection({
828
1052
  : 0,
829
1053
  sourceErrors.sessionMessageFabric,
830
1054
  ),
1055
+ sessionWorkbench: sourceStatus(
1056
+ groups.connected ? groups.items.length : 0,
1057
+ sourceErrors.sessionWorkbench ||
1058
+ (groups.connected ? null : groups.reason),
1059
+ ),
831
1060
  };
832
1061
  const revision = projectionRevision({
833
1062
  schema: SESSION_PROJECTION_SCHEMA,
@@ -836,6 +1065,7 @@ export function buildSessionProjection({
836
1065
  id: session.id,
837
1066
  revision: session.revision,
838
1067
  })),
1068
+ groupRevision: groups.revision,
839
1069
  });
840
1070
  return {
841
1071
  schema: SESSION_PROJECTION_SCHEMA,
@@ -845,6 +1075,7 @@ export function buildSessionProjection({
845
1075
  generatedAt: toIso(generatedAt) || new Date().toISOString(),
846
1076
  revision,
847
1077
  sources,
1078
+ groups,
848
1079
  sessions,
849
1080
  };
850
1081
  }
@@ -962,6 +1193,15 @@ export function disconnectedSessionProjection(reasonText = "CLI unavailable") {
962
1193
  revision: projectionRevision({ connected: false, reason }),
963
1194
  reason,
964
1195
  sources: {},
1196
+ groups: {
1197
+ authority: "cli",
1198
+ connected: false,
1199
+ revision: null,
1200
+ generation: 0,
1201
+ reason,
1202
+ items: [],
1203
+ assignments: [],
1204
+ },
965
1205
  sessions: [],
966
1206
  };
967
1207
  }
@@ -11,9 +11,11 @@
11
11
 
12
12
  import { resolve, relative, isAbsolute, join } from "node:path";
13
13
  import { getHomeDir } from "./paths.js";
14
+ import { resolveClaudeProjectStorageDir } from "./claude-project-storage-layout.js";
14
15
 
15
16
  export function sessionStoreDir() {
16
- return join(getHomeDir(), "sessions");
17
+ const homeDir = getHomeDir();
18
+ return resolveClaudeProjectStorageDir(homeDir) || join(homeDir, "sessions");
17
19
  }
18
20
 
19
21
  /**