chainlesschain 0.162.188 → 0.162.193

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 (337) hide show
  1. package/README.md +61 -11
  2. package/bin/chainlesschain.js +7 -52
  3. package/completions/_cc +209 -0
  4. package/completions/cc.bash +18 -0
  5. package/completions/cc.fish +13 -0
  6. package/completions/cc.ps1 +226 -0
  7. package/package.json +10 -2
  8. package/src/assets/web-panel/assets/{AIOps-Brr1LVcE.js → AIOps-DBLkaTje.js} +1 -1
  9. package/src/assets/web-panel/assets/{ActionButton-DQU74DKG.js → ActionButton-BAy-Qebo.js} +1 -1
  10. package/src/assets/web-panel/assets/{Analytics-CYp9178y.js → Analytics-Dfb1Mlau.js} +3 -3
  11. package/src/assets/web-panel/assets/{AppLayout-DW4SCYsW.js → AppLayout-BbFkN8m0.js} +5 -5
  12. package/src/assets/web-panel/assets/{Artifacts-CYlD_ed1.js → Artifacts-kM4-cEEH.js} +1 -1
  13. package/src/assets/web-panel/assets/{Audit-CWl1rZOi.js → Audit-BSQfYWf3.js} +1 -1
  14. package/src/assets/web-panel/assets/{BackgroundAgents-BOR-LPsa.js → BackgroundAgents-CWDbI_fG.js} +1 -1
  15. package/src/assets/web-panel/assets/{Backup-DLpG7j1X.js → Backup-BxLPbq_Q.js} +1 -1
  16. package/src/assets/web-panel/assets/{BaseInput-B35K9wdr.js → BaseInput-DuIbdrp6.js} +1 -1
  17. package/src/assets/web-panel/assets/{Chat-CwUJb9gB.js → Chat-NEmjGahU.js} +5 -5
  18. package/src/assets/web-panel/assets/{ChatBubbleRenderer-1v6aMS1m.js → ChatBubbleRenderer-DfJEo-5_.js} +1 -1
  19. package/src/assets/web-panel/assets/{Checkbox-pi3-tPNW.js → Checkbox-CaoeG3Ns.js} +1 -1
  20. package/src/assets/web-panel/assets/{Codegen-FyNF35eL.js → Codegen-BFf_g3xY.js} +1 -1
  21. package/src/assets/web-panel/assets/{Col-tGt_JDEr.js → Col-CNOd8mdQ.js} +1 -1
  22. package/src/assets/web-panel/assets/{Community-CC7LonQX.js → Community-BhpDqz-i.js} +1 -1
  23. package/src/assets/web-panel/assets/{Compact-BGXrHHjE.js → Compact-CQ42T7Z6.js} +1 -1
  24. package/src/assets/web-panel/assets/{Compliance-DSW0TBZ8.js → Compliance-DI5PcLMN.js} +1 -1
  25. package/src/assets/web-panel/assets/{Cowork-Bhk7rbvE.js → Cowork-wYYZe0wR.js} +3 -3
  26. package/src/assets/web-panel/assets/{Cron-DxyiQ2NZ.js → Cron-Dlq03DD_.js} +2 -2
  27. package/src/assets/web-panel/assets/{Crosschain-B2ZkxHok.js → Crosschain-BzqS8PP3.js} +1 -1
  28. package/src/assets/web-panel/assets/{DID-yOJV99eJ.js → DID-It55Yem7.js} +2 -2
  29. package/src/assets/web-panel/assets/{Dashboard-CKsttz7D.js → Dashboard-9HkH6oxn.js} +2 -2
  30. package/src/assets/web-panel/assets/{Dropdown-CVFldXSL.js → Dropdown-00e2YWRB.js} +1 -1
  31. package/src/assets/web-panel/assets/{EmailListRenderer-Bufv638J.js → EmailListRenderer-By-ugRDL.js} +1 -1
  32. package/src/assets/web-panel/assets/{FamilyGuardDashboard-4ncjoIjL.js → FamilyGuardDashboard-C396yu5m.js} +1 -1
  33. package/src/assets/web-panel/assets/{Federation-DsknEDoX.js → Federation-BME5sbZY.js} +1 -1
  34. package/src/assets/web-panel/assets/{FormItemContext-Cqe5jWXA.js → FormItemContext-B4TEZCr-.js} +1 -1
  35. package/src/assets/web-panel/assets/{GenericCardRenderer-NAJaFKxc.js → GenericCardRenderer-_9CX9ieY.js} +1 -1
  36. package/src/assets/web-panel/assets/{Git-CszKQpnl.js → Git-BrS6WVSr.js} +2 -2
  37. package/src/assets/web-panel/assets/{Governance-BXGodXi6.js → Governance-BMdCWnKF.js} +1 -1
  38. package/src/assets/web-panel/assets/{Inference-CkoYuG2c.js → Inference-Bcp3vzcA.js} +1 -1
  39. package/src/assets/web-panel/assets/{KnowledgeGraph-DFlEr_T0.js → KnowledgeGraph-DlLuqGY5.js} +1 -1
  40. package/src/assets/web-panel/assets/{Logs-CVaENddA.js → Logs-D-W_6x09.js} +2 -2
  41. package/src/assets/web-panel/assets/MarkdownRenderer-DElGPxCZ.js +1 -0
  42. package/src/assets/web-panel/assets/{Marketplace-CDlLT0fw.js → Marketplace-Dzn2guvI.js} +1 -1
  43. package/src/assets/web-panel/assets/{McpTools-DdwOyszN.js → McpTools-uPict-KL.js} +6 -6
  44. package/src/assets/web-panel/assets/{Memory-DKQnkKuG.js → Memory-BCw43kOb.js} +2 -2
  45. package/src/assets/web-panel/assets/{MobileBridge-DGseqlzJ.js → MobileBridge-DmV8Wv7F.js} +1 -1
  46. package/src/assets/web-panel/assets/{MobileProjects-COBbEOlj.js → MobileProjects-C-HaCmSM.js} +1 -1
  47. package/src/assets/web-panel/assets/{Mtc-O61Vwyli.js → Mtc-C6jxJ34Y.js} +5 -5
  48. package/src/assets/web-panel/assets/{MtcAudit-Dw2SyasS.js → MtcAudit-Za8ExwhQ.js} +5 -5
  49. package/src/assets/web-panel/assets/{Multisig-hSu_l8kf.js → Multisig-KKDbuEgb.js} +3 -3
  50. package/src/assets/web-panel/assets/{NLProgramming-CX1Epurr.js → NLProgramming-Dj97zbCk.js} +1 -1
  51. package/src/assets/web-panel/assets/{Notes-R9xX8o8_.js → Notes-DBXvMraU.js} +4 -4
  52. package/src/assets/web-panel/assets/{NotificationSettings-jtSNLCuK.js → NotificationSettings-DOxITLYl.js} +1 -1
  53. package/src/assets/web-panel/assets/OrderTableRenderer-7DVjZwMM.js +1 -0
  54. package/src/assets/web-panel/assets/{Organization-Pu2WO-U3.js → Organization-D1i73mjb.js} +4 -4
  55. package/src/assets/web-panel/assets/{Overflow-BjnqUyFp.js → Overflow-kW6Q3rvg.js} +1 -1
  56. package/src/assets/web-panel/assets/{P2P-jjkvz44c.js → P2P-C-v_8p2v.js} +2 -2
  57. package/src/assets/web-panel/assets/PdhVaultBrowser-tUbbY64W.js +7 -0
  58. package/src/assets/web-panel/assets/{Permissions-D4yDpivd.js → Permissions-DkHxEZ58.js} +4 -4
  59. package/src/assets/web-panel/assets/{PersonalDataHub-0qsL6iH1.js → PersonalDataHub-DWuMPGrK.js} +5 -5
  60. package/src/assets/web-panel/assets/{Pipeline-Cx7Ybrk2.js → Pipeline-30IYXWWm.js} +1 -1
  61. package/src/assets/web-panel/assets/{Privacy-PbfR_M_V.js → Privacy-DZNQ4RrH.js} +1 -1
  62. package/src/assets/web-panel/assets/{ProjectInit-CyV6Gsub.js → ProjectInit-De2MCElf.js} +2 -2
  63. package/src/assets/web-panel/assets/{ProjectSettings-C7KZ2Wdk.js → ProjectSettings-5uHk9wsC.js} +2 -2
  64. package/src/assets/web-panel/assets/{Projects-DQDjO-yK.js → Projects-XjDfrFLb.js} +1 -1
  65. package/src/assets/web-panel/assets/{Providers-DyFVBBMX.js → Providers-Bgwj6gcE.js} +1 -1
  66. package/src/assets/web-panel/assets/{QrScannerModal-CzB1BC9_.js → QrScannerModal-gKql9umv.js} +1 -1
  67. package/src/assets/web-panel/assets/{QuickAsk-Bk636mC1.js → QuickAsk-BWvmwkh7.js} +1 -1
  68. package/src/assets/web-panel/assets/{Recommend-DoWxHIT3.js → Recommend-Uok4hZPd.js} +1 -1
  69. package/src/assets/web-panel/assets/{RemoteSession-oOVIv9wq.js → RemoteSession-DLJzTk9a.js} +3 -3
  70. package/src/assets/web-panel/assets/{Reputation-BucoReGG.js → Reputation-ZVwIPMSO.js} +1 -1
  71. package/src/assets/web-panel/assets/{Row-DzmOdJU7.js → Row-CN04HxWR.js} +1 -1
  72. package/src/assets/web-panel/assets/{RssFeed-C1tK7KGU.js → RssFeed-BdSrEG4g.js} +3 -3
  73. package/src/assets/web-panel/assets/{Search-CSnInOqk.js → Search-5PwVd0IR.js} +1 -1
  74. package/src/assets/web-panel/assets/{Security-dlejr15l.js → Security-DdBo4zjN.js} +3 -3
  75. package/src/assets/web-panel/assets/{Services-BioPZuNh.js → Services-D3letU7M.js} +2 -2
  76. package/src/assets/web-panel/assets/{Skeleton-DWMibwpC.js → Skeleton--Jb83f1v.js} +1 -1
  77. package/src/assets/web-panel/assets/{Skills-BAAnx1B2.js → Skills-Bg-CulHX.js} +1 -1
  78. package/src/assets/web-panel/assets/{Sla-jWNp4hNO.js → Sla-7QOa7S-X.js} +1 -1
  79. package/src/assets/web-panel/assets/{SpeechSettings-y5kqKha-.js → SpeechSettings-DQwppjRw.js} +1 -1
  80. package/src/assets/web-panel/assets/{SyncSettings-B9GNsc3e.js → SyncSettings-dNPQGL-s.js} +2 -2
  81. package/src/assets/web-panel/assets/{Tasks-BDBYKRen.js → Tasks-Ggs-Z48q.js} +1 -1
  82. package/src/assets/web-panel/assets/{Templates-DRiE5eaN.js → Templates-78TYHgRO.js} +1 -1
  83. package/src/assets/web-panel/assets/{Tenant-BGlAvExF.js → Tenant-xHwwv9vO.js} +1 -1
  84. package/src/assets/web-panel/assets/{Terminal-B8uzrbNY.js → Terminal-BJXSJfm4.js} +2 -2
  85. package/src/assets/web-panel/assets/{TimelineRenderer-Df48iveY.js → TimelineRenderer-fLbjRhJw.js} +1 -1
  86. package/src/assets/web-panel/assets/{Tokens-CcmHndN6.js → Tokens-oMSBSBmB.js} +1 -1
  87. package/src/assets/web-panel/assets/{Trigger-DPkWxmQ2.js → Trigger-CfnY_wX_.js} +1 -1
  88. package/src/assets/web-panel/assets/{Trust-DTb5a5AV.js → Trust-AdyTgYKO.js} +1 -1
  89. package/src/assets/web-panel/assets/{UkeySign-CBmg5vch.js → UkeySign-C5K_81dS.js} +1 -1
  90. package/src/assets/web-panel/assets/{VideoEditing-B0mZrz4O.js → VideoEditing-vcGVDqki.js} +1 -1
  91. package/src/assets/web-panel/assets/{Wallet-Bxs7_mpd.js → Wallet-hhOc3hlY.js} +4 -4
  92. package/src/assets/web-panel/assets/{WebAuthn-C8x3cMxU.js → WebAuthn-CVloC11s.js} +3 -3
  93. package/src/assets/web-panel/assets/{WorkflowEditor-DPrNznrP.js → WorkflowEditor-CYeE32R5.js} +1 -1
  94. package/src/assets/web-panel/assets/{chat-D3bYWu7S.js → chat-CNROR5bN.js} +1 -1
  95. package/src/assets/web-panel/assets/{colors-DY-PwMu4.js → colors-Cjx-cnwU.js} +1 -1
  96. package/src/assets/web-panel/assets/{compact-item-LDk10Kw6.js → compact-item-DOIfelvD.js} +1 -1
  97. package/src/assets/web-panel/assets/{createContext-C-2MZnFq.js → createContext-DfA7UZ8u.js} +1 -1
  98. package/src/assets/web-panel/assets/devWarning-ChcCBGs7.js +1 -0
  99. package/src/assets/web-panel/assets/{hasIn-BQIBjcnB.js → hasIn-BMSCzcfM.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-CQVb2dWp.js → index-5-HXgOX5.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-BdXnYMCZ.js → index-AWg-0M1S.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-Cx6D_dx2.js → index-B4iYzpkL.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-_HRO_cIu.js → index-B5Udeb_6.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-DlRYqt3S.js → index-B7gf77CJ.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-BrnRO9yE.js → index-BEMwBBHk.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-VCh5zNJo.js → index-BM1y_9hM.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-BRI3ngNg.js → index-BUonmKSE.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-C3GNvExG.js → index-BbQ2eQ8H.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-DSvo_bp0.js → index-BemJ2u_E.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-L6qixyLc.js → index-BolaOQxx.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-BI4KgcYC.js → index-BxOk9vkG.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-CY4KnaO9.js → index-By61ojUx.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-L3smcoQX.js → index-C2K9SNVX.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-BX2b9Orc.js → index-C3L5gdiD.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-Cz4b6Z_7.js → index-C7xn0Nub.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-COG61sRv.js → index-CAnvhwg6.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-C7FC8cP2.js → index-CEKMcOyU.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-BN-xdSWZ.js → index-CNC5onm4.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-DY2FIrmz.js → index-CVL27kXC.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-CiTvBAr-.js → index-CVm1_i3t.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-DhRt9cYJ.js → index-CdYBye8B.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-B5pgtZnY.js → index-ClNMrqEY.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-7xKu7Fns.js → index-Cs6l7uRu.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-D4K1weKc.js → index-CshQbilz.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-k7C_oDCO.js → index-Cyweh0Hy.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-D3Oq-DLL.js → index-DBpWXFed.js} +3 -3
  127. package/src/assets/web-panel/assets/{index-DYyLJPlF.js → index-DHtpHZAQ.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-Dh-tkpKQ.js → index-DLmn0BoK.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-8JC9BJa1.js → index-DPAWBrMf.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-Dd2Km97U.js → index-DjEsxO5h.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-KFZO_lcx.js → index-FBc_CJBd.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-DTIZpPtG.js → index-Icdoqmzt.js} +1 -1
  133. package/src/assets/web-panel/assets/index-Q2IajL54.js +1 -0
  134. package/src/assets/web-panel/assets/index-bRkHgOMc.js +1 -0
  135. package/src/assets/web-panel/assets/{index-lmm9p-jm.js → index-dCd8cSUq.js} +1 -1
  136. package/src/assets/web-panel/assets/{index-HasPt4-h.js → index-m2vG9gG_.js} +1 -1
  137. package/src/assets/web-panel/assets/{index-CaslYch6.js → index-mTpHoEzr.js} +1 -1
  138. package/src/assets/web-panel/assets/{index-CbR6ROOa.js → index-ti_lnTWl.js} +1 -1
  139. package/src/assets/web-panel/assets/{initDefaultProps-zQPSHf2C.js → initDefaultProps-Bu8k7H1Y.js} +1 -1
  140. package/src/assets/web-panel/assets/{motion-CKCStGVk.js → motion-s3iVK41k.js} +1 -1
  141. package/src/assets/web-panel/assets/{move-BufVDGYF.js → move-B6B-E5Jz.js} +1 -1
  142. package/src/assets/web-panel/assets/{mtc-parser-DcaFxy5D.js → mtc-parser-BiYgTidA.js} +1 -1
  143. package/src/assets/web-panel/assets/{omit-EScpkGdo.js → omit-pU1RdUDA.js} +1 -1
  144. package/src/assets/web-panel/assets/{pickAttrs-72hG_qfT.js → pickAttrs-Cdoq_SeM.js} +1 -1
  145. package/src/assets/web-panel/assets/{placementArrow-CMFWiH20.js → placementArrow-DIuvAn2K.js} +1 -1
  146. package/src/assets/web-panel/assets/{responsiveObserve-DKQHS41p.js → responsiveObserve-8w16YR2D.js} +1 -1
  147. package/src/assets/web-panel/assets/{slide-QVAFHTIQ.js → slide-VZ01cZe3.js} +1 -1
  148. package/src/assets/web-panel/assets/{statusUtils-DIz38e2G.js → statusUtils-lh8YMN8Z.js} +1 -1
  149. package/src/assets/web-panel/assets/{styleChecker-zK5cyLby.js → styleChecker-CmDyreec.js} +1 -1
  150. package/src/assets/web-panel/assets/{useFlexGapSupport-DhZngk62.js → useFlexGapSupport-hiJWMU5y.js} +1 -1
  151. package/src/assets/web-panel/assets/{useFs-BvV2cvkg.js → useFs-Thnu-Iaj.js} +1 -1
  152. package/src/assets/web-panel/assets/{usePersonalDataHub-CRmxb9l3.js → usePersonalDataHub-LBdOxjwY.js} +1 -1
  153. package/src/assets/web-panel/assets/{vnode-CgQIbobP.js → vnode-Cg6lFY_X.js} +1 -1
  154. package/src/assets/web-panel/assets/{zoom-D4XrrO0R.js → zoom-DVfsR1YO.js} +1 -1
  155. package/src/assets/web-panel/index.html +1 -1
  156. package/src/cli-help.js +181 -0
  157. package/src/command-help-index.json +181 -0
  158. package/src/command-manifest.json +1107 -175
  159. package/src/command-surface-policy.js +197 -0
  160. package/src/commands/agent.js +98 -33
  161. package/src/commands/artifacts.js +400 -2
  162. package/src/commands/background-session.js +41 -3
  163. package/src/commands/checkpoint-managed.js +1059 -0
  164. package/src/commands/checkpoint-restore-recovery.js +1476 -0
  165. package/src/commands/checkpoint.js +950 -22
  166. package/src/commands/compact.js +101 -25
  167. package/src/commands/config.js +506 -20
  168. package/src/commands/doctor.js +4 -1
  169. package/src/commands/hub.js +1 -1
  170. package/src/commands/mcp.js +688 -24
  171. package/src/commands/pack.js +16 -2
  172. package/src/commands/plugin.js +20 -12
  173. package/src/commands/session-mcp-recovery.js +218 -0
  174. package/src/commands/session.js +470 -110
  175. package/src/commands/setup.js +36 -10
  176. package/src/commands/skill.js +181 -110
  177. package/src/commands/status.js +21 -5
  178. package/src/commands/team-distributed.js +3324 -0
  179. package/src/commands/team.js +1130 -74
  180. package/src/commands/update.js +68 -6
  181. package/src/constants.js +11 -0
  182. package/src/data/changelog.json +26 -0
  183. package/src/gateways/ws/action-protocol.js +10 -0
  184. package/src/gateways/ws/session-protocol.js +555 -27
  185. package/src/gateways/ws/ws-agent-handler.js +541 -14
  186. package/src/gateways/ws/ws-session-gateway.js +129 -22
  187. package/src/gateways/ws/ws-session-state.js +431 -0
  188. package/src/harness/background-task-manager.js +233 -36
  189. package/src/harness/jsonl-session-store.js +2551 -167
  190. package/src/harness/mcp-client.js +802 -61
  191. package/src/harness/prompt-compressor.js +227 -27
  192. package/src/harness/session-index.js +89 -38
  193. package/src/harness/session-list-index.js +254 -0
  194. package/src/harness/structured-handoff.js +410 -0
  195. package/src/lazy-dispatch.js +747 -84
  196. package/src/lib/__tests__/logger.test.js +50 -2
  197. package/src/lib/advisor-runtime.js +1047 -0
  198. package/src/lib/agent-core.js +2 -0
  199. package/src/lib/agent-sandbox.js +120 -26
  200. package/src/lib/agent-session-export.js +20 -0
  201. package/src/lib/agent-team/task-lease.js +271 -3
  202. package/src/lib/agent-team/team-adjudication.js +1501 -0
  203. package/src/lib/agent-team/team-budget.js +4 -1
  204. package/src/lib/agent-team/team-control-store.js +582 -0
  205. package/src/lib/agent-team/team-distributed-queue.js +5246 -0
  206. package/src/lib/agent-team/team-process-checkpoint.js +211 -0
  207. package/src/lib/agent-team/team-runner.js +543 -24
  208. package/src/lib/agent-team/team-worktree.js +1403 -95
  209. package/src/lib/artifact-store.js +131 -8
  210. package/src/lib/background-agent-supervisor.js +185 -13
  211. package/src/lib/background-interaction-journal.js +37 -9
  212. package/src/lib/background-launch-profile.js +1058 -0
  213. package/src/lib/checkpoint-restore-already-completed-controller.js +902 -0
  214. package/src/lib/checkpoint-restore-orchestrator.js +987 -0
  215. package/src/lib/checkpoint-restore-partial-rollback-controller.js +1953 -0
  216. package/src/lib/checkpoint-restore-recovery-controller.js +689 -0
  217. package/src/lib/checkpoint-restore-recovery.js +884 -0
  218. package/src/lib/checkpoint-restore-retention.js +126 -0
  219. package/src/lib/checkpoint-restore-saga.js +5079 -0
  220. package/src/lib/checkpoint-restore-session-recovery.js +1178 -0
  221. package/src/lib/checkpoint-restore-workspace-target-verifier.js +270 -0
  222. package/src/lib/checkpoint-store.js +1688 -97
  223. package/src/lib/checkpoint-timeline-authority.js +522 -0
  224. package/src/lib/checkpoint-timeline.js +642 -0
  225. package/src/lib/config-keys.js +18 -119
  226. package/src/lib/config-manager.js +446 -27
  227. package/src/lib/config-redaction.js +57 -0
  228. package/src/lib/config-schema.js +853 -0
  229. package/src/lib/cowork-mcp-tools.js +5 -0
  230. package/src/lib/cowork-task-runner.js +458 -63
  231. package/src/lib/delivery-coordinator.js +1234 -0
  232. package/src/lib/delivery-evidence.js +450 -0
  233. package/src/lib/doctor-checkup.js +199 -14
  234. package/src/lib/executable-path.js +34 -0
  235. package/src/lib/fatal-handler.js +16 -0
  236. package/src/lib/file-checkpoint.js +4909 -186
  237. package/src/lib/file-lines.js +125 -0
  238. package/src/lib/headless-config-command.js +16 -5
  239. package/src/lib/hook-runner.cjs +139 -38
  240. package/src/lib/hooks-v2-workspace-context.js +10 -0
  241. package/src/lib/impacted-gate-selector.js +424 -0
  242. package/src/lib/interaction-adapter.js +62 -0
  243. package/src/lib/jsonl-session-store.js +1 -35
  244. package/src/lib/logger.js +66 -14
  245. package/src/lib/managed-checkpoint-render.js +60 -0
  246. package/src/lib/managed-tool-checkpoint.js +194 -0
  247. package/src/lib/mcp-call-ledger-store.js +1069 -0
  248. package/src/lib/mcp-call-ledger.js +747 -0
  249. package/src/lib/mcp-client.js +5 -0
  250. package/src/lib/mcp-conflict-scheduler.js +468 -0
  251. package/src/lib/mcp-effect-contract.js +102 -0
  252. package/src/lib/mcp-headers-helper-trust.js +145 -0
  253. package/src/lib/mcp-headers-helper.js +616 -0
  254. package/src/lib/mcp-host-recovery-runtime.js +131 -0
  255. package/src/lib/mcp-ledger-recovery-admission.js +1065 -0
  256. package/src/lib/mcp-recovery-adjudication.js +443 -0
  257. package/src/lib/output-context.js +86 -0
  258. package/src/lib/packer/file-identity.js +170 -0
  259. package/src/lib/packer/index.js +3 -0
  260. package/src/lib/packer/native-update-state.js +277 -0
  261. package/src/lib/packer/pack-update-applier.js +1983 -136
  262. package/src/lib/packer/pack-update-checker.js +34 -0
  263. package/src/lib/packer/pack-update-downloader.js +875 -86
  264. package/src/lib/packer/pack-update-signature.js +86 -0
  265. package/src/lib/packer/pkg-config-generator.js +6 -0
  266. package/src/lib/paths.js +65 -10
  267. package/src/lib/plan-mode.js +831 -14
  268. package/src/lib/plan-persistence.js +540 -0
  269. package/src/lib/plugin-runtime/hooks.js +227 -69
  270. package/src/lib/plugin-runtime/plugin-options.js +189 -44
  271. package/src/lib/pr-automation-policy.js +146 -8
  272. package/src/lib/process-execution-broker/credential-agent.js +7 -0
  273. package/src/lib/process-execution-broker/index.js +491 -13
  274. package/src/lib/process-execution-broker/linux-generic-bwrap-runtime.js +28 -1
  275. package/src/lib/process-execution-broker/linux-generic-bwrap.js +28 -4
  276. package/src/lib/process-execution-broker/platform-sandbox.js +92 -70
  277. package/src/lib/process-execution-broker/windows-sandbox-helper.dll +0 -0
  278. package/src/lib/process-execution-broker/windows-sandbox-helper.exe +0 -0
  279. package/src/lib/process-execution-broker/windows-sandbox.cs +284 -19
  280. package/src/lib/process-execution-broker/workspace-transaction.js +4459 -0
  281. package/src/lib/project-instructions.js +165 -29
  282. package/src/lib/project-mcp-trust.js +67 -0
  283. package/src/lib/public-ide-capability-manifest.js +272 -0
  284. package/src/lib/public-ide-capability-validation.js +351 -0
  285. package/src/lib/secret-store.js +267 -43
  286. package/src/lib/secure-file-identity.js +433 -0
  287. package/src/lib/secure-fs.js +1089 -0
  288. package/src/lib/security-audit.js +58 -0
  289. package/src/lib/service-manager.js +34 -13
  290. package/src/lib/session-budget-runtime.js +1452 -0
  291. package/src/lib/session-host-snapshot.js +388 -0
  292. package/src/lib/session-message-provenance.js +429 -0
  293. package/src/lib/session-projection.js +804 -0
  294. package/src/lib/session-resource-budget.js +1495 -0
  295. package/src/lib/settings-hooks.cjs +138 -22
  296. package/src/lib/settings-loader.cjs +25 -2
  297. package/src/lib/side-effect-ledger-store.js +45 -23
  298. package/src/lib/skill-execution-identity.js +217 -0
  299. package/src/lib/skill-loader.js +299 -18
  300. package/src/lib/sub-agent-context.js +236 -28
  301. package/src/lib/sub-agent-registry.js +5 -0
  302. package/src/lib/subagent-contract.js +94 -0
  303. package/src/lib/todo-manager.js +185 -18
  304. package/src/lib/todo-persistence.js +417 -0
  305. package/src/lib/turn-binding-store.js +41 -23
  306. package/src/lib/turn-binding.js +160 -2
  307. package/src/lib/with-file-lock.js +165 -24
  308. package/src/lib/ws-chat-handler.js +21 -0
  309. package/src/program-base.js +18 -0
  310. package/src/repl/advisor-command.js +103 -0
  311. package/src/repl/agent-repl.js +2175 -146
  312. package/src/repl/btw-command.js +93 -10
  313. package/src/repl/clipboard-image.js +119 -0
  314. package/src/repl/config-summary.js +64 -108
  315. package/src/repl/prompt-editor.js +233 -0
  316. package/src/repl/prompt-interactions.js +344 -0
  317. package/src/repl/prompt-stash.js +354 -0
  318. package/src/repl/prompt-suggestions.js +312 -0
  319. package/src/repl/repl-keybindings.js +227 -0
  320. package/src/repl/session-recap.js +217 -0
  321. package/src/repl/slash-command-registry.js +22 -0
  322. package/src/repl/terminal-layout.js +88 -0
  323. package/src/runtime/agent-core.js +1630 -447
  324. package/src/runtime/coding-agent-contract-shared.cjs +13 -1
  325. package/src/runtime/coding-agent-managed-tool-policy.cjs +7 -2
  326. package/src/runtime/diagnostics.js +32 -13
  327. package/src/runtime/headless-runner.js +310 -118
  328. package/src/runtime/headless-stream.js +356 -95
  329. package/src/runtime/mcp-config.js +148 -14
  330. package/src/runtime/policies/agent-policy.js +14 -0
  331. package/src/runtime/status-diagnostics-lite.js +137 -0
  332. package/src/assets/web-panel/assets/MarkdownRenderer-7iWGwZ-r.js +0 -1
  333. package/src/assets/web-panel/assets/OrderTableRenderer-BU2Yqq5H.js +0 -1
  334. package/src/assets/web-panel/assets/PdhVaultBrowser-69ca_WeR.js +0 -7
  335. package/src/assets/web-panel/assets/devWarning-B4dXIJqD.js +0 -1
  336. package/src/assets/web-panel/assets/index-C-C7DaG6.js +0 -1
  337. package/src/assets/web-panel/assets/index-DkfWzWtd.js +0 -1
@@ -1,7 +1,28 @@
1
+ import {
2
+ TODO_PERSISTENCE_ERROR_CODES,
3
+ TODO_SNAPSHOT_SCHEMA,
4
+ TODO_SNAPSHOT_VERSION,
5
+ TodoPersistenceError,
6
+ TodoSnapshotPersistence,
7
+ resolveTodoStateDir,
8
+ } from "./todo-persistence.js";
9
+
10
+ export {
11
+ TODO_PERSISTENCE_ERROR_CODES,
12
+ TODO_SNAPSHOT_SCHEMA,
13
+ TODO_SNAPSHOT_VERSION,
14
+ TodoPersistenceError,
15
+ TodoSnapshotPersistence,
16
+ resolveTodoStateDir,
17
+ todoSnapshotPath,
18
+ } from "./todo-persistence.js";
19
+
1
20
  /**
2
21
  * Session TODO Manager
3
22
  *
4
- * In-memory per-session TODO list. One instance per sessionId.
23
+ * Session-scoped TODO list. Named sessions use a versioned, revision-CAS
24
+ * snapshot by default; the unnamed/default store and `{ memoryOnly: true }`
25
+ * retain the original in-memory behavior.
5
26
  * Inspired by open-agents todo_write tool.
6
27
  *
7
28
  * Contract:
@@ -19,10 +40,83 @@ const VALID_STATUSES = Object.freeze([
19
40
 
20
41
  const _stores = new Map();
21
42
 
22
- export function getTodoStore(sessionId) {
23
- const key = sessionId || "__default__";
43
+ function cloneTodos(todos) {
44
+ return todos.map((todo) => ({
45
+ id: todo.id,
46
+ content: todo.content,
47
+ status: todo.status,
48
+ }));
49
+ }
50
+
51
+ function resolveStoreTarget(sessionId, options = {}) {
52
+ if (
53
+ !sessionId ||
54
+ options.memoryOnly === true ||
55
+ options.persistence === false
56
+ ) {
57
+ return {
58
+ key: `memory:${sessionId || "__default__"}`,
59
+ persistence: null,
60
+ };
61
+ }
62
+ const supplied = options.persistence;
63
+ const persistence =
64
+ supplied && typeof supplied === "object"
65
+ ? supplied
66
+ : new TodoSnapshotPersistence({
67
+ ...(options.persistenceOptions || {}),
68
+ ...(options.stateDir ? { stateDir: options.stateDir } : {}),
69
+ validateTodos,
70
+ });
71
+ const identity =
72
+ persistence.identity ||
73
+ persistence.stateDir ||
74
+ resolveTodoStateDir({ stateDir: options.stateDir });
75
+ return {
76
+ key: `persistent:${identity}:${sessionId}`,
77
+ persistence,
78
+ };
79
+ }
80
+
81
+ function persistenceFailure(error) {
82
+ const known =
83
+ error instanceof TodoPersistenceError ||
84
+ String(error?.code || "").startsWith("TODO_");
85
+ return {
86
+ success: false,
87
+ code: known ? error.code : TODO_PERSISTENCE_ERROR_CODES.WRITE_FAILED,
88
+ error: known ? error.message : "TODO persistence failed",
89
+ ...(Number.isSafeInteger(error?.expectedRevision)
90
+ ? { expectedRevision: error.expectedRevision }
91
+ : {}),
92
+ ...(Number.isSafeInteger(error?.actualRevision)
93
+ ? { actualRevision: error.actualRevision }
94
+ : {}),
95
+ ...(typeof error?.recoveryStrategy === "string"
96
+ ? { recoveryStrategy: error.recoveryStrategy }
97
+ : {}),
98
+ };
99
+ }
100
+
101
+ export function getTodoStore(sessionId, options = {}) {
102
+ const { key, persistence } = resolveStoreTarget(sessionId, options);
24
103
  if (!_stores.has(key)) {
25
- _stores.set(key, { todos: [] });
104
+ if (persistence) {
105
+ const snapshot = persistence.load(sessionId);
106
+ _stores.set(key, {
107
+ todos: cloneTodos(snapshot.todos),
108
+ revision: snapshot.revision,
109
+ persistence,
110
+ sessionId,
111
+ });
112
+ } else {
113
+ _stores.set(key, {
114
+ todos: [],
115
+ revision: 0,
116
+ persistence: null,
117
+ sessionId: sessionId || null,
118
+ });
119
+ }
26
120
  }
27
121
  return _stores.get(key);
28
122
  }
@@ -64,33 +158,106 @@ export function validateTodos(todos) {
64
158
  return { valid: true };
65
159
  }
66
160
 
67
- export function writeTodos(sessionId, todos) {
161
+ export function writeTodos(sessionId, todos, options = {}) {
68
162
  const check = validateTodos(todos);
69
163
  if (!check.valid) {
70
164
  return { success: false, error: check.error };
71
165
  }
72
- const store = getTodoStore(sessionId);
73
- store.todos = todos.map((t) => ({
74
- id: t.id,
75
- content: t.content,
76
- status: t.status,
77
- }));
166
+ const hasExpectedRevision = Object.prototype.hasOwnProperty.call(
167
+ options,
168
+ "expectedRevision",
169
+ );
170
+ if (
171
+ hasExpectedRevision &&
172
+ (!Number.isSafeInteger(options.expectedRevision) ||
173
+ options.expectedRevision < 0)
174
+ ) {
175
+ return {
176
+ success: false,
177
+ code: TODO_PERSISTENCE_ERROR_CODES.REVISION_CONFLICT,
178
+ error: "expectedRevision must be a non-negative safe integer",
179
+ };
180
+ }
181
+ let store;
182
+ try {
183
+ store = getTodoStore(sessionId, options);
184
+ } catch (error) {
185
+ return persistenceFailure(error);
186
+ }
187
+ const nextTodos = cloneTodos(todos);
188
+ if (store.persistence) {
189
+ const expectedRevision = hasExpectedRevision
190
+ ? options.expectedRevision
191
+ : store.revision;
192
+ let snapshot;
193
+ try {
194
+ snapshot = store.persistence.compareAndSwap(
195
+ sessionId,
196
+ expectedRevision,
197
+ nextTodos,
198
+ );
199
+ } catch (error) {
200
+ return persistenceFailure(error);
201
+ }
202
+ store.todos = cloneTodos(snapshot.todos);
203
+ store.revision = snapshot.revision;
204
+ } else {
205
+ if (hasExpectedRevision && options.expectedRevision !== store.revision) {
206
+ return {
207
+ success: false,
208
+ code: TODO_PERSISTENCE_ERROR_CODES.REVISION_CONFLICT,
209
+ error: "TODO snapshot revision conflict",
210
+ expectedRevision: options.expectedRevision,
211
+ actualRevision: store.revision,
212
+ };
213
+ }
214
+ store.todos = nextTodos;
215
+ store.revision += 1;
216
+ }
78
217
  return {
79
218
  success: true,
80
219
  count: store.todos.length,
81
220
  summary: summarizeTodos(store.todos),
221
+ revision: store.revision,
82
222
  };
83
223
  }
84
224
 
85
- export function getTodos(sessionId) {
86
- const store = getTodoStore(sessionId);
87
- return store.todos.map((t) => ({ ...t }));
225
+ export function getTodos(sessionId, options = {}) {
226
+ const store = getTodoStore(sessionId, options);
227
+ if (store.persistence && options.refresh !== false) {
228
+ const snapshot = store.persistence.load(sessionId);
229
+ store.todos = cloneTodos(snapshot.todos);
230
+ store.revision = snapshot.revision;
231
+ }
232
+ return cloneTodos(store.todos);
88
233
  }
89
234
 
90
- export function clearTodos(sessionId) {
91
- const store = getTodoStore(sessionId);
92
- store.todos = [];
93
- return { success: true };
235
+ export function getTodoSnapshot(sessionId, options = {}) {
236
+ const todos = getTodos(sessionId, options);
237
+ const store = getTodoStore(sessionId, { ...options, refresh: false });
238
+ return {
239
+ schema: TODO_SNAPSHOT_SCHEMA,
240
+ version: TODO_SNAPSHOT_VERSION,
241
+ sessionId: sessionId || null,
242
+ revision: store.revision,
243
+ todos,
244
+ };
245
+ }
246
+
247
+ export function clearTodos(sessionId, options = {}) {
248
+ const result = writeTodos(sessionId, [], options);
249
+ if (!result.success) return result;
250
+ return { success: true, revision: result.revision };
251
+ }
252
+
253
+ export function recoverTodoSnapshot(sessionId, strategy, options = {}) {
254
+ const { key, persistence } = resolveStoreTarget(sessionId, options);
255
+ if (!persistence) {
256
+ return { strategy, recovered: 0, memoryOnly: true };
257
+ }
258
+ const result = persistence.recover(sessionId, strategy);
259
+ _stores.delete(key);
260
+ return result;
94
261
  }
95
262
 
96
263
  export function summarizeTodos(todos) {
@@ -0,0 +1,417 @@
1
+ import fsDefault from "node:fs";
2
+ import pathDefault from "node:path";
3
+ import { randomUUID as randomUUIDDefault } from "node:crypto";
4
+ import { getStatePath as getStatePathDefault } from "./paths.js";
5
+ import { withFileLock as withFileLockDefault } from "./with-file-lock.js";
6
+
7
+ export const TODO_SNAPSHOT_SCHEMA = "chainlesschain.session-todos";
8
+ export const TODO_SNAPSHOT_VERSION = 1;
9
+
10
+ export const TODO_PERSISTENCE_ERROR_CODES = Object.freeze({
11
+ INVALID_SESSION: "TODO_SESSION_ID_INVALID",
12
+ CORRUPT: "TODO_SNAPSHOT_CORRUPT",
13
+ RECOVERY_REQUIRED: "TODO_SNAPSHOT_RECOVERY_REQUIRED",
14
+ REVISION_CONFLICT: "TODO_REVISION_CONFLICT",
15
+ WRITE_FAILED: "TODO_SNAPSHOT_WRITE_FAILED",
16
+ LOCK_FAILED: "TODO_SNAPSHOT_LOCK_FAILED",
17
+ RECOVERY_FAILED: "TODO_SNAPSHOT_RECOVERY_FAILED",
18
+ });
19
+
20
+ export class TodoPersistenceError extends Error {
21
+ constructor(code, message, options = {}) {
22
+ super(message, options.cause ? { cause: options.cause } : undefined);
23
+ this.name = "TodoPersistenceError";
24
+ this.code = code;
25
+ for (const [key, value] of Object.entries(options.details || {})) {
26
+ this[key] = value;
27
+ }
28
+ }
29
+ }
30
+
31
+ function persistenceError(code, message, cause, details) {
32
+ return new TodoPersistenceError(code, message, { cause, details });
33
+ }
34
+
35
+ function isKnownPersistenceError(error) {
36
+ return error instanceof TodoPersistenceError;
37
+ }
38
+
39
+ export function isUnsafeTodoSessionId(sessionId) {
40
+ return (
41
+ typeof sessionId !== "string" ||
42
+ sessionId.length === 0 ||
43
+ sessionId.includes("/") ||
44
+ sessionId.includes("\\") ||
45
+ sessionId.includes("..") ||
46
+ sessionId.includes("\0")
47
+ );
48
+ }
49
+
50
+ function assertSafeSessionId(sessionId) {
51
+ if (isUnsafeTodoSessionId(sessionId)) {
52
+ throw persistenceError(
53
+ TODO_PERSISTENCE_ERROR_CODES.INVALID_SESSION,
54
+ "TODO persistence requires a safe non-empty session id",
55
+ null,
56
+ {
57
+ sessionId:
58
+ typeof sessionId === "string" ? sessionId.slice(0, 80) : null,
59
+ },
60
+ );
61
+ }
62
+ return sessionId;
63
+ }
64
+
65
+ /** Resolve the state directory through an injectable seam for hosts and tests. */
66
+ export function resolveTodoStateDir(options = {}) {
67
+ const path = options.path || pathDefault;
68
+ if (typeof options.stateDir === "string" && options.stateDir.trim()) {
69
+ return path.resolve(options.stateDir);
70
+ }
71
+ const env = options.env || process.env;
72
+ const dataDir = env.CHAINLESSCHAIN_DATA_DIR;
73
+ if (typeof dataDir === "string" && dataDir.trim()) {
74
+ return path.join(path.resolve(dataDir), "todos");
75
+ }
76
+ const getStatePath = options.getStatePath || getStatePathDefault;
77
+ return path.join(path.resolve(getStatePath()), "todos");
78
+ }
79
+
80
+ export function todoSnapshotPath(sessionId, options = {}) {
81
+ const path = options.path || pathDefault;
82
+ return path.join(
83
+ resolveTodoStateDir({ ...options, path }),
84
+ `${assertSafeSessionId(sessionId)}.json`,
85
+ );
86
+ }
87
+
88
+ function cloneTodos(todos) {
89
+ return todos.map((todo) => ({
90
+ id: todo.id,
91
+ content: todo.content,
92
+ status: todo.status,
93
+ }));
94
+ }
95
+
96
+ function emptySnapshot(sessionId) {
97
+ return {
98
+ schema: TODO_SNAPSHOT_SCHEMA,
99
+ version: TODO_SNAPSHOT_VERSION,
100
+ sessionId,
101
+ revision: 0,
102
+ updatedAt: 0,
103
+ todos: [],
104
+ };
105
+ }
106
+
107
+ /**
108
+ * Versioned, revision-CAS snapshot store for one session's canonical TODO list.
109
+ * It never logs snapshot contents. Corrupt canonical files and orphaned atomic
110
+ * write files remain visible and block writes until explicitly recovered.
111
+ */
112
+ export class TodoSnapshotPersistence {
113
+ constructor(options = {}) {
114
+ this._fs = options.fs || fsDefault;
115
+ this._path = options.path || pathDefault;
116
+ this._env = options.env || process.env;
117
+ this._getStatePath = options.getStatePath || getStatePathDefault;
118
+ this._randomUUID = options.randomUUID || randomUUIDDefault;
119
+ this._now = options.now || (() => Date.now());
120
+ this._platform = options.platform || process.platform;
121
+ this._withFileLock = options.withFileLock || withFileLockDefault;
122
+ this._beforeRename = options.beforeRename || null;
123
+ this._validateTodos = options.validateTodos || null;
124
+ this.stateDir = resolveTodoStateDir({
125
+ stateDir: options.stateDir,
126
+ env: this._env,
127
+ path: this._path,
128
+ getStatePath: this._getStatePath,
129
+ });
130
+ this.identity = this.stateDir;
131
+ }
132
+
133
+ snapshotPath(sessionId) {
134
+ return todoSnapshotPath(sessionId, {
135
+ stateDir: this.stateDir,
136
+ path: this._path,
137
+ });
138
+ }
139
+
140
+ _ensureDirectory() {
141
+ this._fs.mkdirSync(this.stateDir, {
142
+ recursive: true,
143
+ mode: 0o700,
144
+ });
145
+ }
146
+
147
+ _temporaryPaths(filePath) {
148
+ const prefix = `.${this._path.basename(filePath)}.`;
149
+ let names;
150
+ try {
151
+ names = this._fs.readdirSync(this.stateDir);
152
+ } catch (error) {
153
+ if (error?.code === "ENOENT") return [];
154
+ throw error;
155
+ }
156
+ return names
157
+ .filter((name) => name.startsWith(prefix) && name.endsWith(".tmp"))
158
+ .sort()
159
+ .map((name) => this._path.join(this.stateDir, name));
160
+ }
161
+
162
+ _validateSnapshot(sessionId, value) {
163
+ const todoValidation =
164
+ value && Array.isArray(value.todos) && this._validateTodos
165
+ ? this._validateTodos(value.todos)
166
+ : { valid: Array.isArray(value?.todos) };
167
+ if (
168
+ !value ||
169
+ typeof value !== "object" ||
170
+ Array.isArray(value) ||
171
+ value.schema !== TODO_SNAPSHOT_SCHEMA ||
172
+ value.version !== TODO_SNAPSHOT_VERSION ||
173
+ value.sessionId !== sessionId ||
174
+ !Number.isSafeInteger(value.revision) ||
175
+ value.revision < 0 ||
176
+ !Number.isFinite(value.updatedAt) ||
177
+ !todoValidation.valid
178
+ ) {
179
+ throw persistenceError(
180
+ TODO_PERSISTENCE_ERROR_CODES.CORRUPT,
181
+ "TODO snapshot failed schema validation",
182
+ );
183
+ }
184
+ return {
185
+ schema: TODO_SNAPSHOT_SCHEMA,
186
+ version: TODO_SNAPSHOT_VERSION,
187
+ sessionId,
188
+ revision: value.revision,
189
+ updatedAt: value.updatedAt,
190
+ todos: cloneTodos(value.todos),
191
+ };
192
+ }
193
+
194
+ _readUnlocked(sessionId, filePath) {
195
+ const temporaryPaths = this._temporaryPaths(filePath);
196
+ if (temporaryPaths.length > 0) {
197
+ throw persistenceError(
198
+ TODO_PERSISTENCE_ERROR_CODES.RECOVERY_REQUIRED,
199
+ "TODO snapshot has an incomplete atomic write; explicit recovery is required",
200
+ null,
201
+ {
202
+ filePath,
203
+ temporaryPaths,
204
+ recoveryStrategy: "discard-temporary",
205
+ },
206
+ );
207
+ }
208
+
209
+ let serialized;
210
+ try {
211
+ serialized = this._fs.readFileSync(filePath, "utf8");
212
+ } catch (error) {
213
+ if (error?.code === "ENOENT") return emptySnapshot(sessionId);
214
+ throw persistenceError(
215
+ TODO_PERSISTENCE_ERROR_CODES.CORRUPT,
216
+ "Could not read TODO snapshot",
217
+ error,
218
+ { filePath, recoveryStrategy: "quarantine-corrupt" },
219
+ );
220
+ }
221
+
222
+ try {
223
+ return this._validateSnapshot(sessionId, JSON.parse(serialized));
224
+ } catch (error) {
225
+ if (isKnownPersistenceError(error)) {
226
+ error.filePath = filePath;
227
+ error.recoveryStrategy = "quarantine-corrupt";
228
+ throw error;
229
+ }
230
+ throw persistenceError(
231
+ TODO_PERSISTENCE_ERROR_CODES.CORRUPT,
232
+ "Could not parse TODO snapshot",
233
+ error,
234
+ { filePath, recoveryStrategy: "quarantine-corrupt" },
235
+ );
236
+ }
237
+ }
238
+
239
+ _locked(sessionId, operation) {
240
+ this._ensureDirectory();
241
+ const filePath = this.snapshotPath(sessionId);
242
+ try {
243
+ return this._withFileLock(filePath, () => operation(filePath), {
244
+ _fs: this._fs,
245
+ failIfUnavailable: true,
246
+ timeoutMs: 5_000,
247
+ retryMs: 2,
248
+ maxRetryMs: 32,
249
+ retryJitterMs: 4,
250
+ });
251
+ } catch (error) {
252
+ if (isKnownPersistenceError(error)) throw error;
253
+ throw persistenceError(
254
+ TODO_PERSISTENCE_ERROR_CODES.LOCK_FAILED,
255
+ "Could not acquire or release the TODO snapshot lock",
256
+ error,
257
+ { filePath },
258
+ );
259
+ }
260
+ }
261
+
262
+ load(sessionId) {
263
+ assertSafeSessionId(sessionId);
264
+ return this._locked(sessionId, (filePath) =>
265
+ this._readUnlocked(sessionId, filePath),
266
+ );
267
+ }
268
+
269
+ _writeAtomic(filePath, snapshot) {
270
+ const temporaryPath = this._path.join(
271
+ this.stateDir,
272
+ `.${this._path.basename(filePath)}.${process.pid}.${this._randomUUID()}.tmp`,
273
+ );
274
+ let descriptor = null;
275
+ let renamed = false;
276
+ try {
277
+ descriptor = this._fs.openSync(temporaryPath, "wx", 0o600);
278
+ this._fs.writeFileSync(
279
+ descriptor,
280
+ `${JSON.stringify(snapshot, null, 2)}\n`,
281
+ "utf8",
282
+ );
283
+ this._fs.fsyncSync(descriptor);
284
+ this._fs.closeSync(descriptor);
285
+ descriptor = null;
286
+ if (this._beforeRename) this._beforeRename(temporaryPath, filePath);
287
+ // Same-directory rename is the atomic commit point on POSIX and Windows.
288
+ this._fs.renameSync(temporaryPath, filePath);
289
+ renamed = true;
290
+
291
+ // Directory fsync is unsupported on Windows. Elsewhere it is best-effort
292
+ // after the commit point; a failure must not invite an unsafe retry.
293
+ if (this._platform !== "win32") {
294
+ let directoryDescriptor = null;
295
+ try {
296
+ directoryDescriptor = this._fs.openSync(this.stateDir, "r");
297
+ this._fs.fsyncSync(directoryDescriptor);
298
+ } catch {
299
+ // Snapshot content is already atomically committed.
300
+ } finally {
301
+ if (directoryDescriptor != null) {
302
+ try {
303
+ this._fs.closeSync(directoryDescriptor);
304
+ } catch {
305
+ // Best-effort descriptor cleanup after a committed rename.
306
+ }
307
+ }
308
+ }
309
+ }
310
+ } catch (error) {
311
+ if (descriptor != null) {
312
+ try {
313
+ this._fs.closeSync(descriptor);
314
+ } catch {
315
+ // Preserve the original persistence failure.
316
+ }
317
+ }
318
+ if (!renamed) {
319
+ try {
320
+ this._fs.unlinkSync(temporaryPath);
321
+ } catch {
322
+ // A cleanup failure leaves a visible recovery-required temp file.
323
+ }
324
+ }
325
+ throw persistenceError(
326
+ TODO_PERSISTENCE_ERROR_CODES.WRITE_FAILED,
327
+ "Could not atomically persist TODO snapshot",
328
+ error,
329
+ { filePath, temporaryPath },
330
+ );
331
+ }
332
+ }
333
+
334
+ compareAndSwap(sessionId, expectedRevision, todos) {
335
+ assertSafeSessionId(sessionId);
336
+ if (!Number.isSafeInteger(expectedRevision) || expectedRevision < 0) {
337
+ throw persistenceError(
338
+ TODO_PERSISTENCE_ERROR_CODES.REVISION_CONFLICT,
339
+ "TODO write requires a non-negative expected revision",
340
+ null,
341
+ { expectedRevision },
342
+ );
343
+ }
344
+ const validation = this._validateTodos
345
+ ? this._validateTodos(todos)
346
+ : { valid: Array.isArray(todos) };
347
+ if (!validation.valid) {
348
+ throw persistenceError(
349
+ TODO_PERSISTENCE_ERROR_CODES.CORRUPT,
350
+ "Refused to persist an invalid TODO list",
351
+ );
352
+ }
353
+ const nextTodos = cloneTodos(todos);
354
+ return this._locked(sessionId, (filePath) => {
355
+ const current = this._readUnlocked(sessionId, filePath);
356
+ if (current.revision !== expectedRevision) {
357
+ throw persistenceError(
358
+ TODO_PERSISTENCE_ERROR_CODES.REVISION_CONFLICT,
359
+ "TODO snapshot revision conflict",
360
+ null,
361
+ {
362
+ filePath,
363
+ expectedRevision,
364
+ actualRevision: current.revision,
365
+ },
366
+ );
367
+ }
368
+ const next = {
369
+ schema: TODO_SNAPSHOT_SCHEMA,
370
+ version: TODO_SNAPSHOT_VERSION,
371
+ sessionId,
372
+ revision: current.revision + 1,
373
+ updatedAt: this._now(),
374
+ todos: nextTodos,
375
+ };
376
+ this._writeAtomic(filePath, next);
377
+ return this._validateSnapshot(sessionId, next);
378
+ });
379
+ }
380
+
381
+ recover(sessionId, strategy) {
382
+ assertSafeSessionId(sessionId);
383
+ return this._locked(sessionId, (filePath) => {
384
+ if (strategy === "discard-temporary") {
385
+ const temporaryPaths = this._temporaryPaths(filePath);
386
+ for (const temporaryPath of temporaryPaths) {
387
+ this._fs.unlinkSync(temporaryPath);
388
+ }
389
+ return { strategy, recovered: temporaryPaths.length, filePath };
390
+ }
391
+ if (strategy === "quarantine-corrupt") {
392
+ try {
393
+ this._readUnlocked(sessionId, filePath);
394
+ } catch (error) {
395
+ if (error?.code !== TODO_PERSISTENCE_ERROR_CODES.CORRUPT) throw error;
396
+ const quarantinePath = `${filePath}.corrupt.${this._now()}.${this._randomUUID()}`;
397
+ try {
398
+ this._fs.renameSync(filePath, quarantinePath);
399
+ } catch (cause) {
400
+ throw persistenceError(
401
+ TODO_PERSISTENCE_ERROR_CODES.RECOVERY_FAILED,
402
+ "Could not quarantine corrupt TODO snapshot",
403
+ cause,
404
+ { filePath, quarantinePath },
405
+ );
406
+ }
407
+ return { strategy, recovered: 1, filePath, quarantinePath };
408
+ }
409
+ return { strategy, recovered: 0, filePath };
410
+ }
411
+ throw persistenceError(
412
+ TODO_PERSISTENCE_ERROR_CODES.RECOVERY_FAILED,
413
+ "Unknown TODO snapshot recovery strategy",
414
+ );
415
+ });
416
+ }
417
+ }