chainlesschain 0.165.2 → 0.165.4

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 (210) hide show
  1. package/README.md +151 -29
  2. package/package.json +2 -2
  3. package/src/assets/web-panel/assets/{AIOps-lS1R0Cza.js → AIOps-C__nnraW.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-BmlgBPsj.js → ActionButton-CsKWHEZ-.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-CNKczv1S.js → Analytics-BhIjfjWO.js} +3 -3
  6. package/src/assets/web-panel/assets/{AppLayout-BuZGJARQ.js → AppLayout-BjuXFzWZ.js} +4 -4
  7. package/src/assets/web-panel/assets/{Artifacts-Dm73Sl-O.js → Artifacts-k6whDkZJ.js} +1 -1
  8. package/src/assets/web-panel/assets/{Audit-BGMW7A5e.js → Audit-C4FBMYRh.js} +1 -1
  9. package/src/assets/web-panel/assets/{BackgroundAgents-BQ6M54JR.js → BackgroundAgents-C_vRrSXk.js} +1 -1
  10. package/src/assets/web-panel/assets/{Backup-DBzGP0bR.js → Backup-CngFZeVp.js} +1 -1
  11. package/src/assets/web-panel/assets/{BaseInput-D1mLCoOV.js → BaseInput-B_H9EBRm.js} +1 -1
  12. package/src/assets/web-panel/assets/{Chat-BUfcN7p8.js → Chat-DLEC5kt7.js} +4 -4
  13. package/src/assets/web-panel/assets/ChatBubbleRenderer-xijcWkJe.js +1 -0
  14. package/src/assets/web-panel/assets/{Checkbox-Du0Yg5Pi.js → Checkbox-m3ClR4xh.js} +1 -1
  15. package/src/assets/web-panel/assets/{Codegen-B2RXziHW.js → Codegen-CmLSFrgp.js} +1 -1
  16. package/src/assets/web-panel/assets/{Col-DWhekOrR.js → Col-DFGQteHN.js} +1 -1
  17. package/src/assets/web-panel/assets/{Community-HQhrkon4.js → Community-EUUVORHE.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compact-izHNb4uz.js → Compact-2PSg0Yr7.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compliance-BDwLy41-.js → Compliance-CcKur7gB.js} +1 -1
  20. package/src/assets/web-panel/assets/{Cowork-DFJhLa7K.js → Cowork-DK_Fezfa.js} +3 -3
  21. package/src/assets/web-panel/assets/{Cron-DYY9RG7-.js → Cron-Dbw89xl7.js} +2 -2
  22. package/src/assets/web-panel/assets/{Crosschain-rr7tJTtv.js → Crosschain-CH66h_Ec.js} +1 -1
  23. package/src/assets/web-panel/assets/{DID-D2sfHRLi.js → DID-D1igRb17.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dashboard-q_eGdhha.js → Dashboard-DVAsrobk.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dropdown-Cufrh2Bz.js → Dropdown-Dl7W8AQs.js} +1 -1
  26. package/src/assets/web-panel/assets/{EmailListRenderer-B-kdv10f.js → EmailListRenderer-D5tpaEQC.js} +1 -1
  27. package/src/assets/web-panel/assets/{FamilyGuardDashboard-D21XjgfG.js → FamilyGuardDashboard-O_VQf8gq.js} +1 -1
  28. package/src/assets/web-panel/assets/{Federation-CAwRTLZH.js → Federation-BTVcgS9n.js} +1 -1
  29. package/src/assets/web-panel/assets/{FormItemContext-vx3daNDF.js → FormItemContext-BRnkJAIz.js} +1 -1
  30. package/src/assets/web-panel/assets/{GenericCardRenderer-CRWY2wU4.js → GenericCardRenderer-8OWSCkDj.js} +1 -1
  31. package/src/assets/web-panel/assets/{Git-Q6SwauyD.js → Git-WXLYTQj2.js} +2 -2
  32. package/src/assets/web-panel/assets/{Governance-BJ1q4IQZ.js → Governance-BezaUhtc.js} +1 -1
  33. package/src/assets/web-panel/assets/{Inference-CSWjROgD.js → Inference-CuRay3Gu.js} +1 -1
  34. package/src/assets/web-panel/assets/{KnowledgeGraph-fzuQsWuY.js → KnowledgeGraph-DfWLDW1m.js} +1 -1
  35. package/src/assets/web-panel/assets/{Logs-DVF8Mnns.js → Logs-C55y2pPY.js} +2 -2
  36. package/src/assets/web-panel/assets/MarkdownRenderer-hDpWdiBc.js +1 -0
  37. package/src/assets/web-panel/assets/{Marketplace-BQP7LOA4.js → Marketplace-Dk08Uycg.js} +1 -1
  38. package/src/assets/web-panel/assets/{McpTools-BNlJCmNc.js → McpTools-C11fSB5V.js} +4 -4
  39. package/src/assets/web-panel/assets/{Memory-DLLg5WQ4.js → Memory-mr0eZc97.js} +2 -2
  40. package/src/assets/web-panel/assets/{MobileBridge-CR2skNAF.js → MobileBridge-C0iCSRbb.js} +1 -1
  41. package/src/assets/web-panel/assets/{MobileProjects-BxVavObl.js → MobileProjects-CY-xMG8Z.js} +1 -1
  42. package/src/assets/web-panel/assets/{Mtc-B17pgqMI.js → Mtc-Bplqc_Wc.js} +4 -4
  43. package/src/assets/web-panel/assets/{MtcAudit-4UmV5cIL.js → MtcAudit-BlzwsoSM.js} +2 -2
  44. package/src/assets/web-panel/assets/{Multisig-DoMEXEcU.js → Multisig-BBLmTty6.js} +3 -3
  45. package/src/assets/web-panel/assets/{NLProgramming-h9DMXFX4.js → NLProgramming-DtT1uf1W.js} +1 -1
  46. package/src/assets/web-panel/assets/{Notes-Cihn-xX8.js → Notes-DcqQAxTK.js} +3 -3
  47. package/src/assets/web-panel/assets/{NotificationSettings-BE6ZGa3R.js → NotificationSettings-cb0kWoZe.js} +1 -1
  48. package/src/assets/web-panel/assets/{OrderTableRenderer-CnwojoSI.js → OrderTableRenderer-kw9HZQNN.js} +1 -1
  49. package/src/assets/web-panel/assets/{Organization-B2vpwmjb.js → Organization-DlTlbkUN.js} +4 -4
  50. package/src/assets/web-panel/assets/{Overflow-C4z28R0-.js → Overflow-pp1qs-oY.js} +1 -1
  51. package/src/assets/web-panel/assets/{P2P-BVlFkwhW.js → P2P-BESLnyYz.js} +2 -2
  52. package/src/assets/web-panel/assets/{PdhVaultBrowser-BXbEdL5B.js → PdhVaultBrowser-B-J1mGuf.js} +3 -3
  53. package/src/assets/web-panel/assets/{Permissions-C28xs4lp.js → Permissions-B36PeT1_.js} +4 -4
  54. package/src/assets/web-panel/assets/{PersonalDataHub-Dgl_rBjM.js → PersonalDataHub-CTK2ad-3.js} +3 -3
  55. package/src/assets/web-panel/assets/{Pipeline-3QUIhrKI.js → Pipeline-zJNGXsac.js} +1 -1
  56. package/src/assets/web-panel/assets/{Privacy-8Gh33OZ5.js → Privacy-b9fz0Qf0.js} +1 -1
  57. package/src/assets/web-panel/assets/{ProjectInit-BNmg4tZi.js → ProjectInit-DjZbZgiB.js} +2 -2
  58. package/src/assets/web-panel/assets/{ProjectSettings-vgMfeCf9.js → ProjectSettings-C-hHJXlD.js} +2 -2
  59. package/src/assets/web-panel/assets/Projects-BC9KSDM6.js +1 -0
  60. package/src/assets/web-panel/assets/{Providers-CXoOa8eX.js → Providers-Dz-CAYYU.js} +1 -1
  61. package/src/assets/web-panel/assets/{QrScannerModal-BhfTF0oS.js → QrScannerModal-A0yoQ3-3.js} +1 -1
  62. package/src/assets/web-panel/assets/{QuickAsk-C746HEAj.js → QuickAsk-DrFgFJNC.js} +1 -1
  63. package/src/assets/web-panel/assets/{Recommend-BSej8Eo0.js → Recommend-KC8hF1g4.js} +1 -1
  64. package/src/assets/web-panel/assets/{RemoteSession-f3gmnlG3.js → RemoteSession-DGTPIk6u.js} +3 -3
  65. package/src/assets/web-panel/assets/{Reputation-CyvybpkN.js → Reputation-QmbwM7Yk.js} +1 -1
  66. package/src/assets/web-panel/assets/{Row-DwV2PeYn.js → Row-BxjJylia.js} +1 -1
  67. package/src/assets/web-panel/assets/{RssFeed-11GrzjZy.js → RssFeed-B7Wda1J6.js} +3 -3
  68. package/src/assets/web-panel/assets/{Search-BjKXkrAY.js → Search-_plQ_YSA.js} +1 -1
  69. package/src/assets/web-panel/assets/{Security-eWiFb9Yk.js → Security-CD2YJZfx.js} +2 -2
  70. package/src/assets/web-panel/assets/{Services-CdCsMAtt.js → Services-qQE7NmwE.js} +2 -2
  71. package/src/assets/web-panel/assets/{Skeleton-4Q2UTKQ5.js → Skeleton-DgQqkFf7.js} +1 -1
  72. package/src/assets/web-panel/assets/{Skills-v4rtYrRj.js → Skills-C4C0oFFt.js} +1 -1
  73. package/src/assets/web-panel/assets/{Sla-CRhsFvd_.js → Sla-Px2N2B5c.js} +1 -1
  74. package/src/assets/web-panel/assets/SpeechSettings-DN6C3lQd.js +1 -0
  75. package/src/assets/web-panel/assets/{SyncSettings-D16zonl7.js → SyncSettings-jm5aH_m7.js} +2 -2
  76. package/src/assets/web-panel/assets/{Tasks-Xxozz0i_.js → Tasks-DlkJcv67.js} +1 -1
  77. package/src/assets/web-panel/assets/{Templates-CXJpPtCT.js → Templates-DyrTHUBs.js} +1 -1
  78. package/src/assets/web-panel/assets/{Tenant-XRV0L1ZF.js → Tenant-mC434PtL.js} +1 -1
  79. package/src/assets/web-panel/assets/{Terminal-CYuO1i_w.js → Terminal-o0_s3NgK.js} +2 -2
  80. package/src/assets/web-panel/assets/{TimelineRenderer-BXqitWsu.js → TimelineRenderer-DAGlAXOw.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tokens-BvPrDHIy.js → Tokens-BnulS_bs.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trigger-DhCrSTf5.js → Trigger-suQbVXYM.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trust-DmsuZD6K.js → Trust-DbIJAPo5.js} +1 -1
  84. package/src/assets/web-panel/assets/{UkeySign-D_yrts5C.js → UkeySign-CQNpbIHi.js} +1 -1
  85. package/src/assets/web-panel/assets/{VideoEditing-B01ihPrm.js → VideoEditing-D8aWCERk.js} +1 -1
  86. package/src/assets/web-panel/assets/{Wallet-DpUjKa-N.js → Wallet-CuLpYfZc.js} +2 -2
  87. package/src/assets/web-panel/assets/{WebAuthn-CL07yON6.js → WebAuthn-CsfG8ZMc.js} +2 -2
  88. package/src/assets/web-panel/assets/{WorkflowEditor-CLMflnRr.js → WorkflowEditor-BYsEAZbd.js} +1 -1
  89. package/src/assets/web-panel/assets/{chat-DtUqe8h_.js → chat-FT4Um3-I.js} +1 -1
  90. package/src/assets/web-panel/assets/{colors-DPKrgVfy.js → colors-CKbS_Wlt.js} +1 -1
  91. package/src/assets/web-panel/assets/{compact-item-BTt2GtOm.js → compact-item-CFJtzuJM.js} +1 -1
  92. package/src/assets/web-panel/assets/{createContext-D_VeUc1J.js → createContext-B9kSRW6K.js} +1 -1
  93. package/src/assets/web-panel/assets/devWarning-DJZ0gAMu.js +1 -0
  94. package/src/assets/web-panel/assets/{hasIn-1kndiylj.js → hasIn-DR-x0Xpi.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-dVc-CXY4.js → index-7jHr_A_k.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-DFIZ_1Sf.js → index-B3ZX81mK.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-fk2Rjacw.js → index-B7osrXqy.js} +3 -3
  98. package/src/assets/web-panel/assets/{index-CHG-KcS4.js → index-BGbPnC5x.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-Dbbymxpj.js → index-BI5hP_qy.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-BaZAbw9A.js → index-BK3TiKVq.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-CzjkKUmR.js → index-BVPU43b1.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-BAXfRKfG.js → index-BW-t9n-q.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-8nz_fmlj.js → index-BfFCQHL3.js} +1 -1
  104. package/src/assets/web-panel/assets/index-Bizb4hkw.js +1 -0
  105. package/src/assets/web-panel/assets/{index-DdkplL9R.js → index-Bt4fS2eL.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-DG2MZQ3f.js → index-BwR7yc2h.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-DfKBRdSS.js → index-Bzz0C1BZ.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-GsoUQ2M7.js → index-C2JvmYRc.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-l1BETrDb.js → index-CGaXUsIs.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-4bna2_SR.js → index-CJmIQ9XR.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-DfGl8CqG.js → index-C_enk9I_.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-D95OFht7.js → index-Cr2FLUsD.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-DsJCDUJG.js → index-CvrCCqJt.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-sDoMHTNA.js → index-D4LfPvWA.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-By5n8up9.js → index-DMGmg497.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-ocf06EVQ.js → index-DShSkww-.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-Bhudc_Ez.js → index-DTxNy0ZU.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-CX81qSM4.js → index-DW4BWjTk.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-BlDNVgBt.js → index-DWdqx7zI.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-CcO04eDn.js → index-Dap9c3eD.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-Dwqmtk65.js → index-DcfxhKXR.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-2eyAXE3d.js → index-DdkIhfi-.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-brCInrzq.js → index-DeSgw-WV.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-DJ8vXGOa.js → index-DiNUEpSQ.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-BHpBMWCh.js → index-DjEP7WkH.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-m31Ka8hZ.js → index-DtkFqPVA.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-CtF7EN49.js → index-KqDaCApw.js} +1 -1
  128. package/src/assets/web-panel/assets/index-OHSl-xCh.js +1 -0
  129. package/src/assets/web-panel/assets/{index-B9KvcS7R.js → index-OSzjTzGM.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-CauoBQkH.js → index-QBSeNVO3.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-FzdwcSy2.js → index-WE08gMJP.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-DxdTdBix.js → index-r2VI1Ikk.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-BepLZwgB.js → index-tqSEHPRT.js} +1 -1
  134. package/src/assets/web-panel/assets/{initDefaultProps-CvyYJd7z.js → initDefaultProps-Dd7n7VgI.js} +1 -1
  135. package/src/assets/web-panel/assets/{motion-DYZkhUdz.js → motion-BwxouRw1.js} +1 -1
  136. package/src/assets/web-panel/assets/{move-slnJRVT1.js → move-DObN0PG9.js} +1 -1
  137. package/src/assets/web-panel/assets/{mtc-parser-DVc0CKmy.js → mtc-parser-B3nOUQc7.js} +1 -1
  138. package/src/assets/web-panel/assets/{omit-BCXAtHCq.js → omit-o9WU2Nbl.js} +1 -1
  139. package/src/assets/web-panel/assets/{pickAttrs-DBsemz_K.js → pickAttrs-CL_vvfQ_.js} +1 -1
  140. package/src/assets/web-panel/assets/{placementArrow-BRtkJ-nv.js → placementArrow-CYrM2tyK.js} +1 -1
  141. package/src/assets/web-panel/assets/{responsiveObserve-Cebq7EZn.js → responsiveObserve-k_06cQ96.js} +1 -1
  142. package/src/assets/web-panel/assets/{slide-DT7qvId2.js → slide-CPKG0pxS.js} +1 -1
  143. package/src/assets/web-panel/assets/{statusUtils-pQiITLkG.js → statusUtils-BDDEeLTw.js} +1 -1
  144. package/src/assets/web-panel/assets/{styleChecker-c-19BVKQ.js → styleChecker-Cmb7RTh7.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFlexGapSupport-DsS5-0bE.js → useFlexGapSupport-e2IedC4N.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFs-CxGiMe5n.js → useFs-CE_gxVnm.js} +1 -1
  147. package/src/assets/web-panel/assets/{usePersonalDataHub-D9btVID5.js → usePersonalDataHub-BkMIIKMz.js} +1 -1
  148. package/src/assets/web-panel/assets/{vnode-CUYFsFEN.js → vnode-DW886XV1.js} +1 -1
  149. package/src/assets/web-panel/assets/{zoom-CXmn_35j.js → zoom-Dwo_adaf.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 +105 -35
  153. package/src/commands/artifacts.js +363 -33
  154. package/src/commands/cowork.js +202 -36
  155. package/src/commands/session-budget.js +155 -0
  156. package/src/commands/session-location.js +948 -0
  157. package/src/commands/session.js +72 -46
  158. package/src/data/changelog.json +26 -0
  159. package/src/gateways/ws/artifact-protocol.js +77 -13
  160. package/src/gateways/ws/session-protocol.js +391 -24
  161. package/src/gateways/ws/ws-agent-handler.js +191 -14
  162. package/src/gateways/ws/ws-server.js +14 -3
  163. package/src/gateways/ws/ws-session-gateway.js +101 -5
  164. package/src/harness/jsonl-session-store.js +1210 -0
  165. package/src/lib/agent-team/team-runner.js +43 -13
  166. package/src/lib/artifact-access-ledger.js +461 -0
  167. package/src/lib/artifact-cleanup-ledger.js +861 -0
  168. package/src/lib/artifact-deletion-ledger.js +677 -0
  169. package/src/lib/artifact-store.js +256 -51
  170. package/src/lib/background-agent-keeper-protocol.js +75 -4
  171. package/src/lib/background-agent-launch-barrier.js +74 -0
  172. package/src/lib/background-agent-supervisor.js +149 -5
  173. package/src/lib/background-turn-bootstrap-protocol.js +4 -0
  174. package/src/lib/command-lifecycle-report.js +8 -4
  175. package/src/lib/cowork-adapter.js +9 -5
  176. package/src/lib/cowork-task-runner.js +20 -0
  177. package/src/lib/cowork-workflow.js +178 -20
  178. package/src/lib/direct-model-usage.js +62 -22
  179. package/src/lib/dynamic-workflow-runtime.js +3184 -111
  180. package/src/lib/execution-location-result-apply.js +339 -0
  181. package/src/lib/execution-location-result-artifact.js +271 -0
  182. package/src/lib/execution-location-result-review.js +253 -0
  183. package/src/lib/execution-location-result-store.js +331 -0
  184. package/src/lib/execution-location-result.js +617 -0
  185. package/src/lib/execution-location-target.js +236 -1
  186. package/src/lib/managed-tool-checkpoint.js +47 -4
  187. package/src/lib/process-execution-broker/workspace-transaction.js +3 -0
  188. package/src/lib/session-budget-production-root.js +376 -0
  189. package/src/lib/session-budget-runtime.js +109 -18
  190. package/src/lib/session-budget-usage.js +63 -0
  191. package/src/lib/session-host-snapshot.js +19 -2
  192. package/src/lib/session-resource-budget.js +766 -105
  193. package/src/lib/sub-agent-context.js +69 -22
  194. package/src/lib/web-ui-server.js +12 -1
  195. package/src/lib/with-file-lock.js +175 -26
  196. package/src/repl/agent-repl.js +237 -16
  197. package/src/runtime/agent-core.js +123 -0
  198. package/src/runtime/headless-runner.js +135 -46
  199. package/src/runtime/headless-stream.js +133 -25
  200. package/src/runtime/policies/agent-policy.js +10 -0
  201. package/src/workers/background-agent-keeper.js +112 -19
  202. package/src/workers/background-agent-worker.js +383 -134
  203. package/src/workers/background-turn-bootstrap.js +13 -8
  204. package/src/assets/web-panel/assets/ChatBubbleRenderer-CY5m8ZHg.js +0 -1
  205. package/src/assets/web-panel/assets/MarkdownRenderer-BLif4QSg.js +0 -1
  206. package/src/assets/web-panel/assets/Projects-BhtAVilc.js +0 -1
  207. package/src/assets/web-panel/assets/SpeechSettings-DLjKsDVu.js +0 -1
  208. package/src/assets/web-panel/assets/devWarning-Bj5nUW2v.js +0 -1
  209. package/src/assets/web-panel/assets/index-CPcM-eqo.js +0 -1
  210. package/src/assets/web-panel/assets/index-xeSyVoRe.js +0 -1
@@ -24,6 +24,7 @@ import fs from "fs";
24
24
  import os from "os";
25
25
  import path from "path";
26
26
  import crypto from "crypto";
27
+ import { withFileLock } from "./with-file-lock.js";
27
28
 
28
29
  export const ARTIFACT_KINDS = Object.freeze([
29
30
  "report",
@@ -62,6 +63,7 @@ export function publicArtifactMetadata(entry) {
62
63
  expiresAt,
63
64
  immutable,
64
65
  recordDigest,
66
+ lineage,
65
67
  } = entry;
66
68
  return {
67
69
  id,
@@ -76,6 +78,7 @@ export function publicArtifactMetadata(entry) {
76
78
  expiresAt,
77
79
  immutable: immutable === true,
78
80
  recordDigest: recordDigest || null,
81
+ ...(lineage ? { lineage } : {}),
79
82
  };
80
83
  }
81
84
 
@@ -120,10 +123,36 @@ function normalizeKind(kind) {
120
123
  return ARTIFACT_KINDS.includes(k) ? k : "other";
121
124
  }
122
125
 
126
+ function normalizeLineage(lineage) {
127
+ if (lineage == null) return null;
128
+ if (typeof lineage !== "object" || Array.isArray(lineage)) {
129
+ throw new Error("artifact lineage must be an object");
130
+ }
131
+ let normalized;
132
+ try {
133
+ normalized = JSON.parse(JSON.stringify(lineage));
134
+ } catch {
135
+ throw new Error("artifact lineage must be JSON serializable");
136
+ }
137
+ if (
138
+ !normalized ||
139
+ typeof normalized !== "object" ||
140
+ Array.isArray(normalized)
141
+ ) {
142
+ throw new Error("artifact lineage must be a JSON object");
143
+ }
144
+ return normalized;
145
+ }
146
+
123
147
  export class ArtifactStore {
124
- constructor({ dir = null, now = () => Date.now() } = {}) {
148
+ constructor({
149
+ dir = null,
150
+ now = () => Date.now(),
151
+ indexLock = withFileLock,
152
+ } = {}) {
125
153
  this.dir = dir || artifactsDir();
126
154
  this._now = typeof now === "function" ? now : () => now;
155
+ this._indexLock = indexLock;
127
156
  }
128
157
 
129
158
  _indexFile() {
@@ -138,6 +167,31 @@ export class ArtifactStore {
138
167
  fs.mkdirSync(this._filesDir(), { recursive: true });
139
168
  }
140
169
 
170
+ _withIndexLock(callback) {
171
+ this._ensureDirs();
172
+ return this._indexLock(this._indexFile(), callback, {
173
+ failIfUnavailable: true,
174
+ timeoutMs: 30_000,
175
+ retryMs: 1,
176
+ maxRetryMs: 8,
177
+ retryJitterMs: 4,
178
+ });
179
+ }
180
+
181
+ /**
182
+ * Run a read-only callback against one locked index generation. The callback
183
+ * may inspect stored bytes before it returns; all ArtifactStore mutations use
184
+ * this same lock and therefore cannot replace that generation mid-read.
185
+ */
186
+ withIndexSnapshot(callback) {
187
+ if (typeof callback !== "function") {
188
+ throw new TypeError("artifact index snapshot requires a callback");
189
+ }
190
+ return this._withIndexLock(() =>
191
+ callback(Object.freeze(this._readEntries())),
192
+ );
193
+ }
194
+
141
195
  /**
142
196
  * Publish a file as an artifact (copy + metadata row).
143
197
  * @param {{ filePath: string, title?: string, kind?: string,
@@ -162,14 +216,16 @@ export class ArtifactStore {
162
216
  );
163
217
  }
164
218
  const body = fs.readFileSync(abs);
165
- return this._publishData(body, {
166
- fileName: path.basename(abs),
167
- title,
168
- kind,
169
- sessionId,
170
- ttlDays,
171
- sourcePath: abs,
172
- });
219
+ return this._withIndexLock(() =>
220
+ this._publishDataUnlocked(body, {
221
+ fileName: path.basename(abs),
222
+ title,
223
+ kind,
224
+ sessionId,
225
+ ttlDays,
226
+ sourcePath: abs,
227
+ }),
228
+ );
173
229
  }
174
230
 
175
231
  /**
@@ -191,20 +247,78 @@ export class ArtifactStore {
191
247
  if (typeof data !== "string" && !Buffer.isBuffer(data)) {
192
248
  throw new Error("publishData requires string or Buffer data");
193
249
  }
194
- return this._publishData(Buffer.from(data), {
195
- fileName,
196
- title,
197
- kind,
198
- mime,
199
- sessionId,
200
- ttlDays,
201
- immutable,
202
- recordDigest,
203
- sourcePath: null,
250
+ return this._withIndexLock(() =>
251
+ this._publishDataUnlocked(Buffer.from(data), {
252
+ fileName,
253
+ title,
254
+ kind,
255
+ mime,
256
+ sessionId,
257
+ ttlDays,
258
+ immutable,
259
+ recordDigest,
260
+ sourcePath: null,
261
+ }),
262
+ );
263
+ }
264
+
265
+ /**
266
+ * Publish generated bytes once for a stable authority digest.
267
+ *
268
+ * The strict cross-process index lock makes response-loss retries return the
269
+ * same metadata row instead of creating duplicate managed artifacts. The
270
+ * caller still owns the semantic validation of the persisted lineage and
271
+ * must verifyIntegrity() before treating the returned entry as readable.
272
+ */
273
+ publishDataOnce({
274
+ data,
275
+ fileName = "artifact.bin",
276
+ title,
277
+ kind,
278
+ mime,
279
+ sessionId = null,
280
+ ttlDays,
281
+ immutable = false,
282
+ recordDigest,
283
+ lineage,
284
+ } = {}) {
285
+ if (typeof data !== "string" && !Buffer.isBuffer(data)) {
286
+ throw new Error("publishDataOnce requires string or Buffer data");
287
+ }
288
+ const stableDigest = String(recordDigest || "");
289
+ if (!/^sha256:[a-f0-9]{64}$/u.test(stableDigest)) {
290
+ throw new Error("publishDataOnce requires a canonical recordDigest");
291
+ }
292
+ const normalizedLineage = normalizeLineage(lineage);
293
+ return this._withIndexLock(() => {
294
+ const matches = this._readEntries().filter(
295
+ (entry) => entry.recordDigest === stableDigest,
296
+ );
297
+ if (matches.length > 1) {
298
+ throw new Error(
299
+ `artifact authority ${stableDigest} has duplicate index rows`,
300
+ );
301
+ }
302
+ if (matches.length === 1) {
303
+ return Object.freeze({ entry: matches[0], published: false });
304
+ }
305
+ const entry = this._publishDataUnlocked(Buffer.from(data), {
306
+ fileName,
307
+ title,
308
+ kind,
309
+ mime,
310
+ sessionId,
311
+ ttlDays,
312
+ immutable,
313
+ recordDigest: stableDigest,
314
+ lineage: normalizedLineage,
315
+ sourcePath: null,
316
+ });
317
+ return Object.freeze({ entry, published: true });
204
318
  });
205
319
  }
206
320
 
207
- _publishData(
321
+ _publishDataUnlocked(
208
322
  body,
209
323
  {
210
324
  fileName,
@@ -215,6 +329,7 @@ export class ArtifactStore {
215
329
  ttlDays,
216
330
  immutable = false,
217
331
  recordDigest = null,
332
+ lineage = null,
218
333
  sourcePath = null,
219
334
  },
220
335
  ) {
@@ -247,10 +362,15 @@ export class ArtifactStore {
247
362
  expiresAt: new Date(createdAt + days * 24 * 60 * 60 * 1000).toISOString(),
248
363
  immutable: immutable === true,
249
364
  recordDigest: recordDigest ? String(recordDigest) : null,
365
+ ...(lineage ? { lineage: normalizeLineage(lineage) } : {}),
250
366
  };
251
367
  this._ensureDirs();
252
368
  const storedPath = path.join(this._filesDir(), entry.file);
253
- fs.writeFileSync(storedPath, body, { flag: "wx" });
369
+ fs.writeFileSync(storedPath, body, {
370
+ flag: "wx",
371
+ mode: 0o600,
372
+ flush: true,
373
+ });
254
374
  if (entry.immutable) {
255
375
  try {
256
376
  fs.chmodSync(storedPath, 0o444);
@@ -259,12 +379,37 @@ export class ArtifactStore {
259
379
  // support; immutable here means append-only/no update API, not WORM.
260
380
  }
261
381
  }
262
- fs.appendFileSync(this._indexFile(), JSON.stringify(entry) + "\n", "utf-8");
382
+ let descriptor = null;
383
+ try {
384
+ descriptor = fs.openSync(
385
+ this._indexFile(),
386
+ fs.constants.O_WRONLY |
387
+ fs.constants.O_APPEND |
388
+ fs.constants.O_CREAT |
389
+ Number(fs.constants.O_NOFOLLOW || 0),
390
+ 0o600,
391
+ );
392
+ const indexStat = fs.fstatSync(descriptor);
393
+ if (!indexStat.isFile() || Number(indexStat.nlink) !== 1) {
394
+ throw new Error("artifact index identity is invalid");
395
+ }
396
+ fs.writeFileSync(descriptor, `${JSON.stringify(entry)}\n`, "utf-8");
397
+ fs.fsyncSync(descriptor);
398
+ } catch (error) {
399
+ try {
400
+ this._deleteStoredFile(entry);
401
+ } catch {
402
+ /* best-effort rollback of an unpublished stored copy */
403
+ }
404
+ throw error;
405
+ } finally {
406
+ if (descriptor !== null) fs.closeSync(descriptor);
407
+ }
263
408
  return entry;
264
409
  }
265
410
 
266
411
  /** All (non-corrupt) metadata rows, oldest first. */
267
- list({ sessionId } = {}) {
412
+ _readEntries({ sessionId } = {}) {
268
413
  let raw;
269
414
  try {
270
415
  raw = fs.readFileSync(this._indexFile(), "utf-8");
@@ -285,6 +430,10 @@ export class ArtifactStore {
285
430
  return sessionId ? out.filter((e) => e.sessionId === sessionId) : out;
286
431
  }
287
432
 
433
+ list(options = {}) {
434
+ return this._withIndexLock(() => this._readEntries(options));
435
+ }
436
+
288
437
  /** One entry by id (or null). */
289
438
  get(id) {
290
439
  return this.list().find((e) => e.id === id) || null;
@@ -348,47 +497,103 @@ export class ArtifactStore {
348
497
 
349
498
  /** Remove one artifact (file + index row). @returns {boolean} found */
350
499
  remove(id) {
351
- const entries = this.list();
352
- const target = entries.find((e) => e.id === id);
353
- if (!target) return false;
354
- try {
355
- this._deleteStoredFile(target);
356
- } catch {
357
- /* best-effort — the index row is the source of truth */
358
- }
359
- this._rewrite(entries.filter((e) => e.id !== id));
360
- return true;
500
+ return this._withIndexLock(() => {
501
+ const entries = this._readEntries();
502
+ const matches = entries.filter((entry) => entry.id === id);
503
+ if (matches.length === 0) return false;
504
+ if (matches.length > 1) {
505
+ throw new Error(`artifact id ${id} has duplicate index rows`);
506
+ }
507
+ const target = matches[0];
508
+ this._rewriteUnlocked(entries.filter((entry) => entry.id !== id));
509
+ try {
510
+ this._deleteStoredFile(target);
511
+ } catch {
512
+ /* best-effort — the locked index generation is the source of truth */
513
+ }
514
+ return true;
515
+ });
361
516
  }
362
517
 
363
518
  /**
364
- * Remove expired artifacts (expiresAt in the past).
519
+ * Low-level legacy primitive for removing expired artifacts.
520
+ * Official CLI and WebSocket callers must use artifact cleanup settlement so
521
+ * retries preserve a frozen scope and leave content-free audit evidence.
365
522
  * @returns {{ removed: number }}
366
523
  */
367
524
  cleanupExpired() {
368
- const entries = this.list();
369
- const now = this._now();
370
- const keep = [];
371
- let removed = 0;
372
- for (const e of entries) {
373
- const exp = Date.parse(e.expiresAt || "");
374
- if (Number.isFinite(exp) && exp <= now) {
525
+ return this._withIndexLock(() => {
526
+ const entries = this._readEntries();
527
+ const now = this._now();
528
+ const keep = [];
529
+ const expired = [];
530
+ for (const entry of entries) {
531
+ const exp = Date.parse(entry.expiresAt || "");
532
+ if (Number.isFinite(exp) && exp <= now) {
533
+ expired.push(entry);
534
+ } else {
535
+ keep.push(entry);
536
+ }
537
+ }
538
+ if (expired.length > 0) this._rewriteUnlocked(keep);
539
+ for (const entry of expired) {
375
540
  try {
376
- this._deleteStoredFile(e);
541
+ this._deleteStoredFile(entry);
377
542
  } catch {
378
- /* best-effort */
543
+ /* best-effort — the locked index generation is the source of truth */
379
544
  }
380
- removed += 1;
381
- } else {
382
- keep.push(e);
383
545
  }
384
- }
385
- if (removed > 0) this._rewrite(keep);
386
- return { removed };
546
+ return { removed: expired.length };
547
+ });
387
548
  }
388
549
 
389
- _rewrite(entries) {
550
+ _rewriteUnlocked(entries) {
390
551
  this._ensureDirs();
391
552
  const text = entries.map((e) => JSON.stringify(e)).join("\n");
392
- fs.writeFileSync(this._indexFile(), text ? text + "\n" : "", "utf-8");
553
+ const indexFile = this._indexFile();
554
+ const temporary = `${indexFile}.${process.pid}.${crypto
555
+ .randomBytes(8)
556
+ .toString("hex")}.tmp`;
557
+ let descriptor = null;
558
+ try {
559
+ descriptor = fs.openSync(
560
+ temporary,
561
+ fs.constants.O_WRONLY |
562
+ fs.constants.O_CREAT |
563
+ fs.constants.O_EXCL |
564
+ Number(fs.constants.O_NOFOLLOW || 0),
565
+ 0o600,
566
+ );
567
+ fs.writeFileSync(descriptor, text ? `${text}\n` : "", "utf-8");
568
+ fs.fsyncSync(descriptor);
569
+ fs.closeSync(descriptor);
570
+ descriptor = null;
571
+ fs.renameSync(temporary, indexFile);
572
+ this._syncIndexDirectory();
573
+ } finally {
574
+ if (descriptor !== null) fs.closeSync(descriptor);
575
+ try {
576
+ fs.rmSync(temporary, { force: true });
577
+ } catch {
578
+ /* best-effort cleanup of an unpublished index generation */
579
+ }
580
+ }
581
+ }
582
+
583
+ _syncIndexDirectory() {
584
+ let descriptor = null;
585
+ try {
586
+ descriptor = fs.openSync(this.dir, "r");
587
+ fs.fsyncSync(descriptor);
588
+ } catch (error) {
589
+ if (
590
+ process.platform !== "win32" ||
591
+ !["EACCES", "EINVAL", "ENOTSUP", "EPERM"].includes(error?.code)
592
+ ) {
593
+ throw error;
594
+ }
595
+ } finally {
596
+ if (descriptor !== null) fs.closeSync(descriptor);
597
+ }
393
598
  }
394
599
  }
@@ -1,6 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { lstatSync, mkdirSync, rmdirSync } from "node:fs";
3
- import { posix } from "node:path";
3
+ import { join, posix, resolve } from "node:path";
4
4
 
5
5
  export const BACKGROUND_AGENT_KEEPER_PROTOCOL_VERSION = 1;
6
6
  export const BACKGROUND_AGENT_KEEPER_HELLO = "background-agent-keeper-hello";
@@ -12,8 +12,22 @@ export const BACKGROUND_AGENT_KEEPER_ARMED = "background-agent-keeper-armed";
12
12
  export const BACKGROUND_AGENT_KEEPER_RETIRE = "background-agent-keeper-retire";
13
13
  export const BACKGROUND_AGENT_KEEPER_RETIRED =
14
14
  "background-agent-keeper-retired";
15
+ export const BACKGROUND_AGENT_KEEPER_LAUNCH_CLAIM =
16
+ "background-agent-keeper-launch-claim";
15
17
  export const BACKGROUND_AGENT_KEEPER_HEARTBEAT_INTERVAL_MS = 1_000;
16
- export const BACKGROUND_AGENT_KEEPER_HEARTBEAT_TIMEOUT_MS = 15_000;
18
+ // Do not launch an OS process-identity probe while authenticated application
19
+ // heartbeats are fresh. On Windows the cached WMIC/CIM fallback can otherwise
20
+ // make 20 keepers synchronously spawn probe helpers together every ten seconds.
21
+ export const BACKGROUND_AGENT_KEEPER_IDENTITY_PROBE_DELAY_MS = 15_000;
22
+ // This application-level fence is a backstop for the rare case where Windows
23
+ // retains both the named-pipe handle and an exact process object after worker
24
+ // death. Normal death still closes the socket immediately; a stale channel
25
+ // gets the delayed PID/start-time probe before this final fence. A 20-worker
26
+ // hosted Windows launch has demonstrated
27
+ // 37-second scheduling tails, so 15 seconds could kill a live, healthy worker
28
+ // while it was merely descheduled. Keep the backstop bounded but above that
29
+ // measured contention window.
30
+ export const BACKGROUND_AGENT_KEEPER_HEARTBEAT_TIMEOUT_MS = 60_000;
17
31
 
18
32
  // RETIRE is the only keeper request that performs destructive OS cleanup. Its
19
33
  // deadline is deliberately independent from the short HELLO/ARM request
@@ -22,7 +36,7 @@ export const BACKGROUND_AGENT_KEEPER_HEARTBEAT_TIMEOUT_MS = 15_000;
22
36
  //
23
37
  // 2 targets * (strict identity + taskkill + cleanup confirmation identity) +
24
38
  // 2 cleanup-critical persistence retry windows + cleanup confirmation +
25
- // scheduling margin = 120 seconds.
39
+ // scheduling margin = 128 seconds.
26
40
  //
27
41
  // Keeping these limits in the shared protocol contract prevents either side
28
42
  // from silently reintroducing the old 10-second client / longer keeper race.
@@ -36,7 +50,12 @@ export const BACKGROUND_AGENT_KEEPER_IDENTITY_PROBE_TIMEOUT_MS =
36
50
  export const BACKGROUND_AGENT_KEEPER_STATE_LOCK_TIMEOUT_MS = 5_000;
37
51
  export const BACKGROUND_AGENT_KEEPER_PERSIST_RETRY_TIMEOUT_MS =
38
52
  3 * BACKGROUND_AGENT_KEEPER_STATE_LOCK_TIMEOUT_MS;
39
- export const BACKGROUND_AGENT_KEEPER_CLEANUP_CONFIRM_TIMEOUT_MS = 2_000;
53
+ // A 20-Agent macOS formal run can leave successfully SIGKILLed process groups
54
+ // observable for several seconds while launchd reaps their orphaned leaders.
55
+ // Two seconds made that bounded kernel/reaper delay look like an escaped tree.
56
+ // Ten seconds stays inside the formal observer's 150-second budget while
57
+ // retaining a finite fail-closed confirmation window.
58
+ export const BACKGROUND_AGENT_KEEPER_CLEANUP_CONFIRM_TIMEOUT_MS = 10_000;
40
59
  export const BACKGROUND_AGENT_KEEPER_RETIRE_TIMEOUT_MARGIN_MS = 8_000;
41
60
  export const BACKGROUND_AGENT_KEEPER_RETIRE_TIMEOUT_MS =
42
61
  BACKGROUND_AGENT_KEEPER_CLEANUP_TARGET_LIMIT *
@@ -92,6 +111,58 @@ function requiredTimestamp(value, label) {
92
111
  return normalized;
93
112
  }
94
113
 
114
+ export function backgroundAgentKeeperLaunchClaimPath(
115
+ id,
116
+ keeperGeneration,
117
+ directory,
118
+ ) {
119
+ const safeId = requiredSafeString(id, "id");
120
+ const safeGeneration = requiredSafeString(
121
+ keeperGeneration,
122
+ "keeper generation",
123
+ );
124
+ const requestedDirectory =
125
+ typeof directory === "string" ? directory.trim() : "";
126
+ if (!requestedDirectory || requestedDirectory.includes("\0")) {
127
+ throw new TypeError("invalid background agent keeper state directory");
128
+ }
129
+ const stateDirectory = resolve(requestedDirectory);
130
+ return join(
131
+ stateDirectory,
132
+ `.${safeId}.keeper-${safeGeneration}.launch-claim.json`,
133
+ );
134
+ }
135
+
136
+ export function createBackgroundAgentKeeperLaunchClaim(binding = {}) {
137
+ return Object.freeze({
138
+ type: BACKGROUND_AGENT_KEEPER_LAUNCH_CLAIM,
139
+ protocolVersion: BACKGROUND_AGENT_KEEPER_PROTOCOL_VERSION,
140
+ id: requiredSafeString(binding.id, "id"),
141
+ workerGeneration: requiredSafeString(
142
+ binding.workerGeneration,
143
+ "worker generation",
144
+ ),
145
+ keeperGeneration: requiredSafeString(
146
+ binding.keeperGeneration,
147
+ "keeper generation",
148
+ ),
149
+ keeperPid: requiredPositiveInteger(binding.keeperPid, "keeper pid"),
150
+ token: requiredSafeString(binding.token, "token", SAFE_TOKEN),
151
+ });
152
+ }
153
+
154
+ export function matchesBackgroundAgentKeeperLaunchClaim(value, expected = {}) {
155
+ let actual;
156
+ let binding;
157
+ try {
158
+ actual = createBackgroundAgentKeeperLaunchClaim(value);
159
+ binding = createBackgroundAgentKeeperLaunchClaim(expected);
160
+ } catch {
161
+ return false;
162
+ }
163
+ return Object.keys(binding).every((key) => actual[key] === binding[key]);
164
+ }
165
+
95
166
  function digestKeeperPath(value, length) {
96
167
  return createHash("sha256").update(value).digest("hex").slice(0, length);
97
168
  }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Read-only startup barrier shared by the detached background worker and its
3
+ * keeper. The launcher publishes both exact child PIDs in one state
4
+ * transaction before clearing `launchFinalizationUncertain`. Until then the
5
+ * children must not contend for the same state lock.
6
+ */
7
+
8
+ export const BACKGROUND_AGENT_LAUNCH_BARRIER_TIMEOUT_MS = 30_000;
9
+
10
+ const DEFAULT_POLL_MS = 25;
11
+ const PID_FIELDS = new Set(["workerPid", "keeperPid"]);
12
+
13
+ function delay(milliseconds) {
14
+ return new Promise((resolve) => setTimeout(resolve, milliseconds));
15
+ }
16
+
17
+ export async function waitForBackgroundAgentLaunchBarrier(options = {}) {
18
+ const id = String(options.id || "").trim();
19
+ const workerGeneration = String(options.workerGeneration || "").trim();
20
+ const expectedPid = Number(options.expectedPid);
21
+ const pidField = String(options.pidField || "");
22
+ const readState = options.readState;
23
+ const readyWhen = options.readyWhen;
24
+ const now = options.now || Date.now;
25
+ const sleep = options.sleep || delay;
26
+ const timeoutMs = Math.max(
27
+ 1,
28
+ Number(options.timeoutMs) || BACKGROUND_AGENT_LAUNCH_BARRIER_TIMEOUT_MS,
29
+ );
30
+ const pollMs = Math.max(1, Number(options.pollMs) || DEFAULT_POLL_MS);
31
+
32
+ if (!id) {
33
+ throw new TypeError("background launch barrier id is required");
34
+ }
35
+ if (!PID_FIELDS.has(pidField)) {
36
+ throw new TypeError("background launch barrier pid field is invalid");
37
+ }
38
+ if (!Number.isSafeInteger(expectedPid) || expectedPid <= 0) {
39
+ throw new TypeError("background launch barrier pid is invalid");
40
+ }
41
+ if (typeof readState !== "function") {
42
+ throw new TypeError("background launch barrier state reader is required");
43
+ }
44
+ if (readyWhen !== undefined && typeof readyWhen !== "function") {
45
+ throw new TypeError("background launch barrier readiness check is invalid");
46
+ }
47
+
48
+ const deadline = now() + timeoutMs;
49
+ for (;;) {
50
+ const state = readState(id);
51
+ if (!state || state.status !== "running") {
52
+ return Object.freeze({ status: "terminal", state: state || null });
53
+ }
54
+ // Jobs written before generation fencing are allowed to observe an
55
+ // already-terminal record above, but can never enter a running record.
56
+ if (!workerGeneration || state.workerGeneration !== workerGeneration) {
57
+ return Object.freeze({ status: "terminal", state });
58
+ }
59
+ if (state.launchFinalizationUncertain !== true) {
60
+ if (Number(state[pidField]) !== expectedPid) {
61
+ return Object.freeze({ status: "identity-mismatch", state });
62
+ }
63
+ if (!readyWhen || readyWhen(state)) {
64
+ return Object.freeze({ status: "ready", state });
65
+ }
66
+ }
67
+
68
+ const remaining = deadline - now();
69
+ if (remaining <= 0) {
70
+ return Object.freeze({ status: "timeout", state });
71
+ }
72
+ await sleep(Math.min(pollMs, remaining));
73
+ }
74
+ }