chainlesschain 0.162.162 → 0.162.164

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 (237) hide show
  1. package/package.json +3 -1
  2. package/src/assets/web-panel/assets/{AIOps-ClI2ALgv.js → AIOps-BqRkaHGb.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-BDjRcJaK.js → ActionButton-DWO7OZLC.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-DikSRsaT.js → Analytics-C5oaVezM.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-9mLhMFmz.js → AppLayout-CsUESDoZ.js} +3 -3
  6. package/src/assets/web-panel/assets/{Artifacts-BSr88BrH.js → Artifacts-Dragqu5k.js} +1 -1
  7. package/src/assets/web-panel/assets/{Audit-CdvyimOF.js → Audit-CQsaHctY.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-D3_hCedh.js → BackgroundAgents-DdG1jNSM.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-Cyvv9RHf.js → Backup-DN6sjK4z.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-BZM14kt8.js → BaseInput-Cxm_ZTCl.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-DrQSokVz.js → Chat-DVXGmA56.js} +6 -6
  12. package/src/assets/web-panel/assets/{ChatBubbleRenderer-HwJR-C6B.js → ChatBubbleRenderer-BdaCL_iE.js} +1 -1
  13. package/src/assets/web-panel/assets/{Checkbox-bFLYeWMV.js → Checkbox-DEc4vD_A.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-7mN7xJXT.js → Codegen-CKiTcv36.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-DAB5ZWm7.js → Col-Br9UUNN0.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-B_1TymaK.js → Community-NeUH_70T.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-Bq1uSWLJ.js → Compact-BqR-z2Df.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-CyiE1jEe.js → Compliance-DcWB_SgZ.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-Cpk0D7vm.js → Cowork-BfhgcudF.js} +2 -2
  20. package/src/assets/web-panel/assets/{Cron-d_Lv3rdB.js → Cron-LaU29LgD.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-DMqhHjMR.js → Crosschain-B2ZB9uwW.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-Br-oPuBw.js → DID-BuHl38A2.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-CfLW_RaP.js → Dashboard-m627yqvZ.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-DZnTOExV.js → Dropdown-C7bxF8F5.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-DHIUHDv1.js → EmailListRenderer-DPLPQzLp.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-Bw3t3myu.js → FamilyGuardDashboard-cOiH6ike.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-D9cpQPAY.js → Federation-Bvkzg88z.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-Jdczr9Mr.js → FormItemContext-BTsiA7h_.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-BT8ZMpC5.js → GenericCardRenderer-BGnVE5qu.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-DjARip2b.js → Git-DS_j6r25.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-BgWbSRcj.js → Governance-CKRG3Rsl.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-CfouJciM.js → Inference-DW8INoNQ.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-50l7mrAr.js → KnowledgeGraph-BhEYeFhC.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-rq50H84B.js → Logs-wqi0uxuR.js} +2 -2
  35. package/src/assets/web-panel/assets/{MarkdownRenderer-DUPIEdQH.js → MarkdownRenderer-DS8dE8ty.js} +1 -1
  36. package/src/assets/web-panel/assets/{Marketplace-BYgPDmHU.js → Marketplace-CMlTuPLT.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-5DVgcFdx.js → McpTools-CSx4m5JJ.js} +3 -3
  38. package/src/assets/web-panel/assets/{Memory-B70LPk7f.js → Memory-vnQCUy-_.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-De8ssA5Y.js → MobileBridge-BV2NA742.js} +1 -1
  40. package/src/assets/web-panel/assets/MobileProjects-Bw0k8e1B.js +1 -0
  41. package/src/assets/web-panel/assets/{Mtc-RJsOTsIA.js → Mtc-CzK_FPB1.js} +2 -2
  42. package/src/assets/web-panel/assets/{MtcAudit-Crh-6BEK.js → MtcAudit-Cb7G9oL3.js} +5 -5
  43. package/src/assets/web-panel/assets/{Multisig-wuoR5l3x.js → Multisig-qJxlUyzr.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-U2Mji4fZ.js → NLProgramming-DqaBJ7Te.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-CdTZAyDH.js → Notes-CwhTJkRi.js} +3 -3
  46. package/src/assets/web-panel/assets/{NotificationSettings-C-FucWE7.js → NotificationSettings-YDs4Fal4.js} +1 -1
  47. package/src/assets/web-panel/assets/{OrderTableRenderer-DCYFTeaI.js → OrderTableRenderer-CvEALpq7.js} +1 -1
  48. package/src/assets/web-panel/assets/{Organization-an3OF85L.js → Organization-CEmQm9n8.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-C-1vmYFe.js → Overflow-zby_P_LA.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-Cwh47xKg.js → P2P-DsmDNFNT.js} +2 -2
  51. package/src/assets/web-panel/assets/{PdhVaultBrowser-Dm38GT0k.js → PdhVaultBrowser-FWwFlZGF.js} +3 -3
  52. package/src/assets/web-panel/assets/{Permissions-DS2Y8awD.js → Permissions-DuzQ6me5.js} +4 -4
  53. package/src/assets/web-panel/assets/{PersonalDataHub-BQNziA0I.js → PersonalDataHub-DZbeN613.js} +3 -3
  54. package/src/assets/web-panel/assets/{Pipeline-DF85L1ue.js → Pipeline-CoNSVAUS.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-BuLornF-.js → Privacy-OYJEAaMC.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-C3Y31IwX.js → ProjectInit-CWIapV--.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-BCPiI8aD.js → ProjectSettings-CX8xMxZn.js} +2 -2
  58. package/src/assets/web-panel/assets/{Projects-BZ6s4k6g.js → Projects-BLCN8YyY.js} +1 -1
  59. package/src/assets/web-panel/assets/{Providers-D3Wp022X.js → Providers-BV_sxp75.js} +1 -1
  60. package/src/assets/web-panel/assets/{QrScannerModal-CUJfQkH4.js → QrScannerModal-BTnRrHRp.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-UAc0EO8l.js → QuickAsk-D1KZg018.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-BaxKupcs.js → Recommend-COm5cuNg.js} +1 -1
  63. package/src/assets/web-panel/assets/{RemoteSession-C75NNnXZ.js → RemoteSession-0saWEkYI.js} +2 -2
  64. package/src/assets/web-panel/assets/{Reputation-BVXynZAk.js → Reputation-Dr7iKLS2.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-C5MRZ0bi.js → Row-C1vpPBcr.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-DAKkEYka.js → RssFeed-CxlWrtNj.js} +3 -3
  67. package/src/assets/web-panel/assets/{Search-DvWv9YOA.js → Search-BT2V8dPN.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-B5peafW1.js → Security-bm9V3c5k.js} +4 -4
  69. package/src/assets/web-panel/assets/{Services-UQmvPEaG.js → Services-CocH1yyX.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-C3bhWpuF.js → Skeleton-DHItlMdd.js} +1 -1
  71. package/src/assets/web-panel/assets/{Skills-DfkKf9_H.js → Skills-C59sLc4w.js} +1 -1
  72. package/src/assets/web-panel/assets/{Sla-DqMSjlBM.js → Sla-Crj4BMc8.js} +1 -1
  73. package/src/assets/web-panel/assets/{SpeechSettings-C-WbxWUX.js → SpeechSettings-DX4KFceY.js} +1 -1
  74. package/src/assets/web-panel/assets/{SyncSettings-CYuHZ4N2.js → SyncSettings-BD_iwQLr.js} +2 -2
  75. package/src/assets/web-panel/assets/{Tasks-CyeAyB9d.js → Tasks-BmCTzYsH.js} +1 -1
  76. package/src/assets/web-panel/assets/{Templates-BTCeQnpq.js → Templates-BGUaxZYl.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-BifQuA9Y.js → Tenant-BS0tJx2O.js} +1 -1
  78. package/src/assets/web-panel/assets/{Terminal-Bx_9Bocf.js → Terminal--tHSAsld.js} +2 -2
  79. package/src/assets/web-panel/assets/{TimelineRenderer-BX0RFCOm.js → TimelineRenderer-D9-MFxS4.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-CGHrCmUS.js → Tokens-CiCB485f.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger-DlqaY4SO.js → Trigger-SmCFuW_l.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-DfOmj-ay.js → Trust-DkK3Rqe0.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-B72jH5fE.js → UkeySign-CDM4LGdB.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-BT-63fqv.js → VideoEditing-IDB7DJXt.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-Dsul3sHr.js → Wallet-Bikj5cmt.js} +4 -4
  86. package/src/assets/web-panel/assets/{WebAuthn-Izkwwdgy.js → WebAuthn-C9abWlAw.js} +2 -2
  87. package/src/assets/web-panel/assets/{WorkflowEditor-D3FHSiH1.js → WorkflowEditor-DrbRKVky.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-CmVz5z3U.js → chat-CccCSDQf.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-CuLgyJBp.js → colors-CXObXLhb.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-Ml1YkXpw.js → compact-item-B5kfYAxC.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-BvjJ48bs.js → createContext-DmiPBfip.js} +1 -1
  92. package/src/assets/web-panel/assets/devWarning-CvKrADNa.js +1 -0
  93. package/src/assets/web-panel/assets/{hasIn-DVC0157C.js → hasIn-Q6lYteAc.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-C229ABfy.js → index-25Wlqps4.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-Ck_52dz_.js → index-33U29UNV.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-QXzlT9KD.js → index-4oXBDczR.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-4SBta3d7.js → index-BGYXvMc5.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-B0S8NzO6.js → index-BJCCa0s_.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-DJyN28Et.js → index-BJJ5DhKI.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-DC_OI9H2.js → index-BLOzViJ6.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-C_XuRgGn.js → index-BRrbUMoG.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-wvb_FtaV.js → index-BUFpT17Q.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-KMqhcF7l.js → index-BkRDiqGc.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-vG4XSJ1d.js → index-BsfvW6my.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-DYXHFr4r.js → index-By-kfPED.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-DuGszaWY.js → index-BzFTcQsc.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-CXkJH-lK.js → index-C2FXdboq.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-Dt5KMjwJ.js → index-C5XOlTHN.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-4wcgvPoF.js → index-CLhBDS2w.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-C_H_S-DY.js → index-CMq7UeoT.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-BydjdZnl.js → index-CVMTWNKl.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-BjHBpRYZ.js → index-Caghi-yL.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-Bzr0rB5T.js → index-CdB6FuSH.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-BbPtzB8M.js → index-CoK8-kWQ.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-BUt8kTIB.js → index-CsO3GUAY.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-C_vRgTl9.js → index-CwZBa20Z.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-DukDVXCv.js → index-D-kASucw.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-BbepULSc.js → index-D8eBw7vW.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-BfuKP4qe.js → index-D9wgifk4.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-BiC6dQZG.js → index-DAYCTc07.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-CmFNvNLZ.js → index-DF6Q8l2h.js} +1 -1
  122. package/src/assets/web-panel/assets/index-DTdFUWc_.js +1 -0
  123. package/src/assets/web-panel/assets/{index-KjgKdZe-.js → index-DgTLApS9.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-BpFJHiTv.js → index-DgxVJ8pg.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-Cw0Rjy87.js → index-DlJvIqoR.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-CFYVQMvu.js → index-Dw4eHfsG.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-CudmwNBV.js → index-Rh-ytwow.js} +3 -3
  128. package/src/assets/web-panel/assets/{index-DDGaOWBV.js → index-StAEt2pq.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-D3lD5qap.js → index-bzfw951k.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-BzG-x9p4.js → index-fSUMbMvv.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-B_EvG_GW.js → index-tFHy5Wol.js} +1 -1
  132. package/src/assets/web-panel/assets/index-zQ0M_kz-.js +1 -0
  133. package/src/assets/web-panel/assets/{initDefaultProps-BDgd2YZ8.js → initDefaultProps-BNq7X72N.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-ErtrXdYe.js → motion-DOf9viTc.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-_GPPNCgs.js → move-CRHTcW4x.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-DRLL-8V2.js → mtc-parser-Cyoozgm2.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-p86sl023.js → omit-6-TjFNru.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-BTu7cMLH.js → pickAttrs-DlUmO1zi.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-BEKz6Isr.js → placementArrow-CFOL8mIR.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve-Ck42c9MP.js → responsiveObserve-BtaFK5zd.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-1CEvuPsB.js → slide-BIbc2bur.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-BDfdx1GJ.js → statusUtils-BXX9d6-w.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-B4K46m5w.js → styleChecker-BGv10Bpn.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-DoIVpJNq.js → useFlexGapSupport-bKnKT8tw.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-DdAWi-ln.js → useFs-FY4IL4jn.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-iUu6k8dx.js → usePersonalDataHub-Bwl9TEgu.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-DZz4R6Tw.js → vnode-BM3OHxAT.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-DXlf9sA3.js → zoom-D1fbFiHB.js} +1 -1
  149. package/src/assets/web-panel/index.html +1 -1
  150. package/src/commands/agenda.js +320 -32
  151. package/src/commands/agent.js +105 -11
  152. package/src/commands/context.js +125 -14
  153. package/src/commands/doctor.js +85 -1
  154. package/src/commands/hook.js +181 -7
  155. package/src/commands/plugin.js +456 -1
  156. package/src/commands/review.js +363 -2
  157. package/src/commands/session.js +191 -0
  158. package/src/commands/team.js +19 -1
  159. package/src/data/changelog.json +10 -4
  160. package/src/gateways/ws/remote-session-protocol.js +39 -0
  161. package/src/gateways/ws/session-protocol.js +55 -2
  162. package/src/gateways/ws/ws-agent-handler.js +50 -0
  163. package/src/harness/jsonl-session-store.js +60 -0
  164. package/src/harness/worktree-isolator.js +140 -5
  165. package/src/lib/agent-schedule-store.js +318 -10
  166. package/src/lib/agent-team/team-worktree.js +37 -2
  167. package/src/lib/agent-tool-admission.js +125 -0
  168. package/src/lib/app-preview.js +360 -0
  169. package/src/lib/async-hook-supervisor.cjs +54 -0
  170. package/src/lib/capability-manifest.js +319 -0
  171. package/src/lib/capability-negotiation.js +23 -12
  172. package/src/lib/channels/channel-manager.js +54 -2
  173. package/src/lib/cli-context-engineering.js +9 -2
  174. package/src/lib/context-breakdown.js +116 -0
  175. package/src/lib/diagnostic-bundle.js +230 -0
  176. package/src/lib/doctor-checkup.js +275 -4
  177. package/src/lib/execution-location.js +286 -0
  178. package/src/lib/headless-manifest.js +13 -40
  179. package/src/lib/hook-event-log.cjs +193 -0
  180. package/src/lib/hook-stats-store.cjs +197 -0
  181. package/src/lib/idempotency.js +109 -0
  182. package/src/lib/interaction-adapter.js +34 -4
  183. package/src/lib/json-schema-output.js +46 -12
  184. package/src/lib/json-schema-validate.js +143 -3
  185. package/src/lib/jsonl-session-store.js +1 -0
  186. package/src/lib/lsp/__tests__/lsp-manager.test.js +58 -0
  187. package/src/lib/lsp/lsp-manager.js +56 -0
  188. package/src/lib/monitor-event.js +290 -0
  189. package/src/lib/operation-fingerprint.js +217 -0
  190. package/src/lib/plugin-runtime/bin.js +37 -0
  191. package/src/lib/plugin-runtime/capabilities.js +19 -0
  192. package/src/lib/plugin-runtime/capability-consent.js +203 -0
  193. package/src/lib/plugin-runtime/hooks.js +37 -0
  194. package/src/lib/plugin-runtime/lsp.js +38 -0
  195. package/src/lib/plugin-runtime/mcp.js +40 -0
  196. package/src/lib/plugin-runtime/monitors.js +37 -0
  197. package/src/lib/plugin-runtime/plugin-options.js +151 -0
  198. package/src/lib/plugin-runtime/policy.js +62 -0
  199. package/src/lib/plugin-runtime/scopes.js +20 -4
  200. package/src/lib/pr-automation-policy.js +224 -0
  201. package/src/lib/project-instructions.js +128 -0
  202. package/src/lib/remote-approval-bridge.js +66 -13
  203. package/src/lib/repl-goal.js +132 -0
  204. package/src/lib/repl-rewind.js +153 -0
  205. package/src/lib/review-comment-anchor.js +241 -0
  206. package/src/lib/review-pipeline.js +46 -7
  207. package/src/lib/runtime-checkup.js +221 -0
  208. package/src/lib/schedule-planner.js +17 -1
  209. package/src/lib/secret-scan.js +409 -0
  210. package/src/lib/session-branch.js +0 -0
  211. package/src/lib/settings-hook-events.cjs +13 -0
  212. package/src/lib/settings-loader.cjs +42 -0
  213. package/src/lib/side-effect-ledger-store.js +88 -0
  214. package/src/lib/side-effect-ledger.js +401 -0
  215. package/src/lib/skill-loader.js +32 -0
  216. package/src/lib/skill-path-scope.js +63 -0
  217. package/src/lib/sub-agent-context.js +110 -9
  218. package/src/lib/sub-agent-registry.js +19 -0
  219. package/src/lib/subagent-inheritance.js +112 -0
  220. package/src/lib/telemetry/span-recorder.js +17 -4
  221. package/src/lib/terminal-context.js +195 -0
  222. package/src/lib/turn-binding-store.js +119 -0
  223. package/src/lib/unattended-action-policy.js +182 -0
  224. package/src/lib/worktree-cleanup-safety.js +138 -0
  225. package/src/lib/worktree-isolator.js +8 -0
  226. package/src/lib/worktree-sparse.js +131 -0
  227. package/src/repl/agent-repl.js +280 -30
  228. package/src/repl/permission-tier.js +28 -0
  229. package/src/runtime/agent-core.js +565 -21
  230. package/src/runtime/coding-agent-contract-shared.cjs +60 -1
  231. package/src/runtime/diagnostics.js +60 -0
  232. package/src/runtime/headless-runner.js +393 -1
  233. package/src/runtime/headless-stream.js +60 -1
  234. package/src/assets/web-panel/assets/MobileProjects-D10VISps.js +0 -1
  235. package/src/assets/web-panel/assets/devWarning-DhiGrbsh.js +0 -1
  236. package/src/assets/web-panel/assets/index-B7a1TrU6.js +0 -1
  237. package/src/assets/web-panel/assets/index-C2B1KRja.js +0 -1
@@ -0,0 +1,197 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * hook-stats-store — a small, best-effort, cross-session RELIABILITY aggregate
5
+ * for async settings hooks, so `cc doctor` (a separate process) can surface
6
+ * hooks that have been slow, repeatedly failing, or circuit-broken. The gap's
7
+ * P2 "Doctor" wants "慢/熔断 Hook" health, but that state otherwise lives only
8
+ * in-memory in AsyncHookSupervisor for the life of one process.
9
+ *
10
+ * Design: the supervisor folds each completed hook run into an in-memory
11
+ * aggregate (bounded by the number of DISTINCT hooks, not runs) and persists it
12
+ * once on stopAll() — NOT per run — so there is no write amplification on the
13
+ * hook hot path. Persistence merges with whatever is already on disk, so counts
14
+ * accumulate across sessions. Reads/writes are best-effort (a corrupt/ío-failed
15
+ * file never blocks a hook or the agent).
16
+ *
17
+ * Pure aggregation (no clock/RNG/fs in the fold) so it's fully unit-testable;
18
+ * load/save take an injected fs. The default circuit threshold mirrors the
19
+ * doctor evaluator's `hookFailureThreshold`.
20
+ */
21
+
22
+ const os = require("node:os");
23
+ const path = require("node:path");
24
+ const fsDefault = require("node:fs");
25
+
26
+ const MAX_HOOKS = 200; // bound distinct-hook entries; drop least-recently-run
27
+ const DEFAULT_CIRCUIT_THRESHOLD = 3;
28
+ const CONFIG_DIR_NAME = ".chainlesschain";
29
+
30
+ /** Stable per-hook id from its (event, command). */
31
+ function hookKey(command, event) {
32
+ return `${event || "*"}::${String(command || "").trim()}`;
33
+ }
34
+
35
+ /** Default on-disk location (matches the ESM getHomeDir() layout). */
36
+ function defaultHookStatsPath() {
37
+ return path.join(os.homedir(), CONFIG_DIR_NAME, "hook-stats.json");
38
+ }
39
+
40
+ /** Fresh empty aggregate map. */
41
+ function emptyStats() {
42
+ return {};
43
+ }
44
+
45
+ /**
46
+ * Fold ONE completed run into the aggregate (mutates + returns `stats`). Pure:
47
+ * `now`/`ok`/`ms` are supplied by the caller. `consecutiveFailures` resets on
48
+ * any success — the circuit signal is "N failures in a row", not lifetime.
49
+ */
50
+ function aggregateRun(stats, { command, event, ok, ms, now }) {
51
+ if (!stats || typeof stats !== "object") stats = {};
52
+ const id = hookKey(command, event);
53
+ const dur = Number.isFinite(Number(ms)) ? Number(ms) : 0;
54
+ const at = Number.isFinite(Number(now)) ? Number(now) : 0;
55
+ const e = stats[id] || {
56
+ id,
57
+ command: command || "",
58
+ event: event || null,
59
+ runs: 0,
60
+ failures: 0,
61
+ consecutiveFailures: 0,
62
+ lastFailureAt: null,
63
+ totalMs: 0,
64
+ maxMs: 0,
65
+ lastRunAt: 0,
66
+ };
67
+ e.runs += 1;
68
+ e.totalMs += dur;
69
+ if (dur > e.maxMs) e.maxMs = dur;
70
+ if (at > e.lastRunAt) e.lastRunAt = at;
71
+ if (ok === true) {
72
+ e.consecutiveFailures = 0;
73
+ } else {
74
+ e.failures += 1;
75
+ e.consecutiveFailures += 1;
76
+ e.lastFailureAt = at;
77
+ }
78
+ stats[id] = e;
79
+ boundStats(stats);
80
+ return stats;
81
+ }
82
+
83
+ /** Keep only the MAX_HOOKS most-recently-run entries. */
84
+ function boundStats(stats) {
85
+ const keys = Object.keys(stats);
86
+ if (keys.length <= MAX_HOOKS) return stats;
87
+ keys
88
+ .sort((a, b) => (stats[a].lastRunAt || 0) - (stats[b].lastRunAt || 0))
89
+ .slice(0, keys.length - MAX_HOOKS)
90
+ .forEach((k) => delete stats[k]);
91
+ return stats;
92
+ }
93
+
94
+ /**
95
+ * Merge a session `delta` aggregate INTO a `base` aggregate (returns a new map).
96
+ * Counts add; maxMs/lastRunAt/lastFailureAt take the larger; the session's
97
+ * consecutiveFailures wins (it reflects the most recent streak).
98
+ */
99
+ function mergeStats(base, delta) {
100
+ const out = {};
101
+ for (const src of [base, delta]) {
102
+ if (!src || typeof src !== "object") continue;
103
+ for (const [id, e] of Object.entries(src)) {
104
+ if (!e || typeof e !== "object") continue;
105
+ const cur = out[id];
106
+ if (!cur) {
107
+ out[id] = { ...e };
108
+ continue;
109
+ }
110
+ cur.runs += e.runs || 0;
111
+ cur.failures += e.failures || 0;
112
+ cur.totalMs += e.totalMs || 0;
113
+ cur.maxMs = Math.max(cur.maxMs || 0, e.maxMs || 0);
114
+ cur.lastRunAt = Math.max(cur.lastRunAt || 0, e.lastRunAt || 0);
115
+ cur.lastFailureAt =
116
+ Math.max(cur.lastFailureAt || 0, e.lastFailureAt || 0) || null;
117
+ // `delta` is applied second, so its streak (most recent) wins.
118
+ cur.consecutiveFailures =
119
+ e.consecutiveFailures ?? cur.consecutiveFailures;
120
+ cur.command = e.command || cur.command;
121
+ cur.event = e.event ?? cur.event;
122
+ }
123
+ }
124
+ return boundStats(out);
125
+ }
126
+
127
+ /**
128
+ * Shape the aggregate for the doctor `checkHooks` evaluator: id, lifetime
129
+ * failure count, average duration, and a derived circuit-open flag.
130
+ */
131
+ function toCheckHooksInput(
132
+ stats,
133
+ { circuitThreshold = DEFAULT_CIRCUIT_THRESHOLD } = {},
134
+ ) {
135
+ if (!stats || typeof stats !== "object") return [];
136
+ return Object.values(stats).map((e) => ({
137
+ id: e.command || e.id,
138
+ event: e.event || null,
139
+ failures: e.failures || 0,
140
+ avgMs: e.runs > 0 ? e.totalMs / e.runs : 0,
141
+ circuitOpen: (e.consecutiveFailures || 0) >= circuitThreshold,
142
+ }));
143
+ }
144
+
145
+ /** Best-effort read of the on-disk aggregate (→ {} on missing/corrupt). */
146
+ function loadHookStats(file = defaultHookStatsPath(), fs = fsDefault) {
147
+ try {
148
+ if (!fs.existsSync(file)) return {};
149
+ const parsed = JSON.parse(fs.readFileSync(file, "utf-8"));
150
+ return parsed && typeof parsed === "object" ? parsed : {};
151
+ } catch {
152
+ return {};
153
+ }
154
+ }
155
+
156
+ /** Best-effort atomic write (tmp + rename). Never throws. */
157
+ function saveHookStats(stats, file = defaultHookStatsPath(), fs = fsDefault) {
158
+ try {
159
+ const dir = path.dirname(file);
160
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
161
+ const tmp = `${file}.tmp`;
162
+ fs.writeFileSync(tmp, JSON.stringify(stats), "utf-8");
163
+ fs.renameSync(tmp, file);
164
+ return true;
165
+ } catch {
166
+ return false;
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Load the on-disk aggregate, fold in a session `delta`, and write it back.
172
+ * Best-effort end-to-end — the single call a supervisor makes on stopAll().
173
+ */
174
+ function persistSessionStats(
175
+ delta,
176
+ file = defaultHookStatsPath(),
177
+ fs = fsDefault,
178
+ ) {
179
+ if (!delta || Object.keys(delta).length === 0) return false;
180
+ const merged = mergeStats(loadHookStats(file, fs), delta);
181
+ return saveHookStats(merged, file, fs);
182
+ }
183
+
184
+ module.exports = {
185
+ hookKey,
186
+ defaultHookStatsPath,
187
+ emptyStats,
188
+ aggregateRun,
189
+ mergeStats,
190
+ boundStats,
191
+ toCheckHooksInput,
192
+ loadHookStats,
193
+ saveHookStats,
194
+ persistSessionStats,
195
+ MAX_HOOKS,
196
+ DEFAULT_CIRCUIT_THRESHOLD,
197
+ };
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Content-addressed idempotency — the P0-2 "Diff Apply 使用内容哈希/版本号" +
3
+ * "外部 API 优先使用 Provider Idempotency Key" primitives.
4
+ *
5
+ * All PURE (deterministic, no clock / RNG / I/O):
6
+ *
7
+ * - `classifyEditReplay` — decide, from the CURRENT file content alone, whether
8
+ * a literal edit still applies, has ALREADY landed (a resumed replay), or
9
+ * genuinely conflicts. Lets a recovered worker skip an edit it already made
10
+ * instead of double-applying it or erroring confusingly.
11
+ * - `editIdempotencyKey` / `operationIdempotencyKey` — stable keys derived from
12
+ * the operation and a canonicalized (order-independent) view of its args, so
13
+ * a retry of the SAME effect yields the SAME key. An external provider that
14
+ * honors `Idempotency-Key` then de-dupes a resumed replay; internally the key
15
+ * tags each side-effect ledger op for duplicate detection.
16
+ */
17
+
18
+ import { createHash } from "node:crypto";
19
+
20
+ function sha256Hex(input) {
21
+ return createHash("sha256").update(String(input), "utf8").digest("hex");
22
+ }
23
+
24
+ /**
25
+ * Canonicalize a value so semantically-equal args hash identically regardless of
26
+ * key order. Arrays keep order (order is meaningful); object keys are sorted.
27
+ */
28
+ function canonicalize(v) {
29
+ if (v == null) return null;
30
+ if (Array.isArray(v)) return v.map(canonicalize);
31
+ if (typeof v === "object") {
32
+ const out = {};
33
+ for (const k of Object.keys(v).sort()) out[k] = canonicalize(v[k]);
34
+ return out;
35
+ }
36
+ return v;
37
+ }
38
+
39
+ /** Fate of an edit checked against the current on-disk content. */
40
+ export const EDIT_REPLAY = Object.freeze({
41
+ APPLY: "apply", // old_string present — the edit still applies
42
+ ALREADY_APPLIED: "already_applied", // old gone, new present — idempotent no-op
43
+ CONFLICT: "conflict", // neither present — a genuine mismatch, surface it
44
+ });
45
+
46
+ /**
47
+ * Decide the fate of a literal (old→new) edit against CURRENT content — the
48
+ * content-addressed idempotency check. On a resumed replay the edit already
49
+ * landed, so `old_string` is gone and `new_string` is present → already_applied
50
+ * (skip, never double-apply). PURE.
51
+ *
52
+ * Deletions (`new_string === ""`) are deliberately NOT auto-detected as
53
+ * already-applied: "old absent + nothing to look for" is indistinguishable from
54
+ * "old never existed", so those keep surfacing as a conflict (the safe default).
55
+ *
56
+ * @param {{content:string, oldString:string, newString:string, replaceAll?:boolean}} p
57
+ * @returns {"apply"|"already_applied"|"conflict"}
58
+ */
59
+ export function classifyEditReplay({ content, oldString, newString }) {
60
+ const text = String(content ?? "");
61
+ const oldS = String(oldString ?? "");
62
+ const newS = String(newString ?? "");
63
+ if (oldS && text.includes(oldS)) return EDIT_REPLAY.APPLY;
64
+ // old_string is absent. A present, non-empty new_string means the intended
65
+ // change is already in the file — the edit already happened.
66
+ if (newS && text.includes(newS)) return EDIT_REPLAY.ALREADY_APPLIED;
67
+ return EDIT_REPLAY.CONFLICT;
68
+ }
69
+
70
+ /** Stable idempotency key for a literal edit against a target file. */
71
+ export function editIdempotencyKey({ path, oldString, newString, replaceAll }) {
72
+ return (
73
+ "edit_" +
74
+ sha256Hex(
75
+ JSON.stringify([
76
+ String(path || ""),
77
+ String(oldString ?? ""),
78
+ String(newString ?? ""),
79
+ replaceAll === true,
80
+ ]),
81
+ ).slice(0, 40)
82
+ );
83
+ }
84
+
85
+ /**
86
+ * Deterministic idempotency key for an external, irreversible operation — stable
87
+ * across retries so a provider honoring `Idempotency-Key` de-dupes a resumed
88
+ * replay, and so the side-effect ledger can spot a duplicated commit. Derived
89
+ * from the tool + a canonicalized view of its args; NO wall-clock / RNG so the
90
+ * SAME logical effect always maps to the SAME key.
91
+ *
92
+ * `scope` is optional namespacing (e.g. a session id); OMIT it when you want a
93
+ * pure retry of the same call to collide with its earlier attempt.
94
+ *
95
+ * @param {{tool:string, args?:object, scope?:string}} p
96
+ * @returns {string}
97
+ */
98
+ export function operationIdempotencyKey({ tool, args, scope } = {}) {
99
+ return (
100
+ "op_" +
101
+ sha256Hex(
102
+ JSON.stringify([
103
+ String(tool || ""),
104
+ scope == null ? "" : String(scope),
105
+ canonicalize(args ?? null),
106
+ ]),
107
+ ).slice(0, 40)
108
+ );
109
+ }
@@ -14,6 +14,7 @@ import {
14
14
  LEGACY_TO_UNIFIED_TYPE,
15
15
  } from "../runtime/runtime-events.js";
16
16
  import { createAbortError } from "./abort-utils.js";
17
+ import { verifyApprovalBinding } from "./agent-authority.js";
17
18
  import { createEnvelope } from "@chainlesschain/session-core";
18
19
 
19
20
  // Phase 5: parallel service-envelope emission. Map WS agent-handler event
@@ -167,12 +168,21 @@ export class WebSocketInteractionAdapter extends InteractionAdapter {
167
168
  reject,
168
169
  timeoutId,
169
170
  kind: options.kind || null,
171
+ // Approval binding (authority §"权限来源"): when a permission gate raises
172
+ // this request WITH a binding (tool_call_id + args + policy digest), an
173
+ // *approve* answer must echo the SAME binding or it is a stale / mis-
174
+ // routed / argument-tampered verdict and is rejected in _resolvePending.
175
+ // null (the default for plain questions) → no binding check, unchanged.
176
+ binding: options.binding || null,
170
177
  });
171
178
 
172
179
  this._sendWs({
173
180
  ...message,
174
181
  sessionId: this.sessionId,
175
182
  requestId,
183
+ // Ride the binding out so the remote UI/bridge can echo it back on the
184
+ // answer (only present when the caller supplied one).
185
+ ...(options.binding ? { binding: options.binding } : {}),
176
186
  });
177
187
  });
178
188
  }
@@ -210,8 +220,8 @@ export class WebSocketInteractionAdapter extends InteractionAdapter {
210
220
  * Called by ws-server when a session-answer message arrives.
211
221
  * Resolves the corresponding pending promise.
212
222
  */
213
- resolveAnswer(requestId, answer) {
214
- this._resolvePending(requestId, answer, "question");
223
+ resolveAnswer(requestId, answer, binding = null) {
224
+ this._resolvePending(requestId, answer, "question", binding);
215
225
  }
216
226
 
217
227
  async requestHostTool(toolName, args = {}, extra = {}) {
@@ -321,7 +331,12 @@ export class WebSocketInteractionAdapter extends InteractionAdapter {
321
331
  }
322
332
  }
323
333
 
324
- _resolvePending(requestId, payload, expectedKind = null) {
334
+ _resolvePending(
335
+ requestId,
336
+ payload,
337
+ expectedKind = null,
338
+ incomingBinding = null,
339
+ ) {
325
340
  const pending = this._pending.get(requestId);
326
341
  if (!pending) {
327
342
  return;
@@ -337,11 +352,26 @@ export class WebSocketInteractionAdapter extends InteractionAdapter {
337
352
  return;
338
353
  }
339
354
 
355
+ // Approval-binding guard (authority §"权限来源"): if this request was raised
356
+ // with a binding AND the answer echoes a binding that does NOT match, the
357
+ // verdict is for a different / argument-tampered call — settle as a DENY
358
+ // (fail closed) instead of the approve it claims to be, mirroring the local
359
+ // headless approval gate. Backward-compatible: no request binding, or an
360
+ // answer with no echoed binding, resolves exactly as before.
361
+ let effective = payload;
362
+ if (
363
+ pending.binding &&
364
+ incomingBinding &&
365
+ !verifyApprovalBinding(pending.binding, incomingBinding)
366
+ ) {
367
+ effective = false;
368
+ }
369
+
340
370
  this._pending.delete(requestId);
341
371
  if (pending.timeoutId) {
342
372
  clearTimeout(pending.timeoutId);
343
373
  }
344
- pending.resolve(payload);
374
+ pending.resolve(effective);
345
375
  }
346
376
  }
347
377
 
@@ -7,13 +7,17 @@
7
7
  * `deps.writeOut` capture seam — the runner itself is untouched: each attempt
8
8
  * runs with output captured, the validated JSON is the only thing printed.
9
9
  *
10
- * Validator subset (enough for tool/script contracts, not full draft-2020):
11
- * type (object/array/string/number/integer/boolean/null), properties,
12
- * required, items, enum, const, additionalProperties:false. Zero deps.
10
+ * Constrained validation delegates to the richer Draft-2020-12-subset validator
11
+ * in [[json-schema-validate.js]] (type, properties, required, items/prefixItems,
12
+ * enum/const, additionalProperties, min/max, pattern, format, allOf/anyOf/oneOf/
13
+ * not, if/then/else, local $ref) so retries get precise coded/pointered errors —
14
+ * the model is told exactly WHICH JSON Pointer failed and WHY. The legacy
15
+ * `validateAgainstSchema` (loose `$.a.b` strings) stays exported for callers.
13
16
  */
14
17
 
15
18
  import fsDefault from "fs";
16
19
  import {
20
+ validate,
17
21
  validateSchema,
18
22
  buildStructuredResultEvent,
19
23
  computeSchemaDigest,
@@ -96,6 +100,20 @@ export function validateAgainstSchema(value, schema, path = "$") {
96
100
  return errors;
97
101
  }
98
102
 
103
+ /**
104
+ * Render the richer validator's coded/pointered error objects as human-readable
105
+ * `<JSON Pointer>: <message>` strings for the corrective retry prompt and the
106
+ * final failure report. The empty root pointer is shown as `(root)`.
107
+ *
108
+ * @param {Array<{instancePath?:string, message:string}>} errors
109
+ * @returns {string[]}
110
+ */
111
+ export function formatSchemaErrors(errors) {
112
+ return (errors || []).map(
113
+ (e) => `${e.instancePath || "(root)"}: ${e.message}`,
114
+ );
115
+ }
116
+
99
117
  /**
100
118
  * Return the first *balanced* JSON object/array substring in `text`, honoring
101
119
  * string literals so braces inside strings don't throw off the depth count.
@@ -203,18 +221,29 @@ export function loadSchemaFile(fs, schemaFile) {
203
221
  "No schema provided: pass --json-schema <file> or a schema object",
204
222
  );
205
223
  }
224
+ // Inline JSON support: a value that starts with `{` is treated as a literal
225
+ // schema object, never a path (a filesystem path can't start with `{`). This
226
+ // lets `--json-schema '{"type":"object",...}'` work without a temp file.
227
+ const trimmed = String(schemaFile).trim();
228
+ const isInline = trimmed.startsWith("{");
206
229
  let raw;
207
- try {
208
- raw = fs.readFileSync(schemaFile, "utf-8");
209
- } catch (e) {
210
- throw new Error(`Cannot read schema file "${schemaFile}": ${e.message}`);
230
+ if (isInline) {
231
+ raw = trimmed;
232
+ } else {
233
+ try {
234
+ raw = fs.readFileSync(schemaFile, "utf-8");
235
+ } catch (e) {
236
+ throw new Error(`Cannot read schema file "${schemaFile}": ${e.message}`);
237
+ }
211
238
  }
212
239
  let parsed;
213
240
  try {
214
241
  parsed = JSON.parse(raw);
215
242
  } catch (e) {
216
243
  throw new Error(
217
- `Invalid JSON in schema file "${schemaFile}": ${e.message}`,
244
+ isInline
245
+ ? `Invalid inline JSON schema: ${e.message}`
246
+ : `Invalid JSON in schema file "${schemaFile}": ${e.message}`,
218
247
  );
219
248
  }
220
249
  // Startup schema meta-validation (P2): reject a malformed schema up front
@@ -226,7 +255,9 @@ export function loadSchemaFile(fs, schemaFile) {
226
255
  .slice(0, 5)
227
256
  .map((e) => ` - ${e.schemaPath || "/"}: ${e.message}`)
228
257
  .join("\n");
229
- throw new Error(`Invalid JSON Schema in "${schemaFile}":\n${detail}`);
258
+ throw new Error(
259
+ `Invalid JSON Schema in ${isInline ? "inline schema" : `"${schemaFile}"`}:\n${detail}`,
260
+ );
230
261
  }
231
262
  return parsed;
232
263
  }
@@ -275,12 +306,15 @@ export async function runJsonSchemaConstrained(cfg = {}) {
275
306
  lastRaw = raw;
276
307
  const parsed = extractJsonPayload(raw);
277
308
  if (parsed.ok) {
278
- const errors = validateAgainstSchema(parsed.value, schema);
279
- if (errors.length === 0) {
309
+ // Richer validator: enforces the full supported subset (format assertions,
310
+ // if/then/else, min/max, pattern, combinators, …) and yields coded/pointered
311
+ // errors so the retry prompt tells the model exactly which pointer failed.
312
+ const { valid, errors } = validate(parsed.value, schema);
313
+ if (valid) {
280
314
  writeOut(`${JSON.stringify(parsed.value, null, 2)}\n`);
281
315
  return 0;
282
316
  }
283
- lastErrors = errors;
317
+ lastErrors = formatSchemaErrors(errors);
284
318
  } else {
285
319
  lastErrors = [parsed.error];
286
320
  }
@@ -22,9 +22,11 @@
22
22
  * Supported keywords: type (+ integer), enum, const, properties, required,
23
23
  * additionalProperties (bool|schema), patternProperties-free, items,
24
24
  * prefixItems, minItems/maxItems, uniqueItems, minLength/maxLength, pattern,
25
- * minimum/maximum/exclusiveMinimum/exclusiveMaximum, multipleOf,
26
- * minProperties/maxProperties, allOf/anyOf/oneOf/not, and local `$ref`
27
- * (`#/$defs/…`, `#/definitions/…`, or any in-document JSON Pointer).
25
+ * format (date-time/date/time/email/uri/uuid/ipv4/ipv6/hostname — asserted;
26
+ * unknown formats pass through as annotations), minimum/maximum/
27
+ * exclusiveMinimum/exclusiveMaximum, multipleOf, minProperties/maxProperties,
28
+ * allOf/anyOf/oneOf/not, if/then/else, and local `$ref` (`#/$defs/…`,
29
+ * `#/definitions/…`, or any in-document JSON Pointer).
28
30
  */
29
31
 
30
32
  import crypto from "crypto";
@@ -41,6 +43,97 @@ const KNOWN_TYPES = new Set([
41
43
 
42
44
  const MAX_REF_DEPTH = 128; // guards recursive $ref against cyclic data
43
45
 
46
+ // ─── format assertions ───────────────────────────────────────────────────────
47
+ // Draft 2020-12 `format` is asserted here (the protocol-facing structured-output
48
+ // contract wants it enforced, not merely annotated). A format not in this map is
49
+ // treated as an annotation and always passes. All checkers are pure strings→bool.
50
+
51
+ function isCalendarDate(y, m, d) {
52
+ if (m < 1 || m > 12 || d < 1) return false;
53
+ const leap = (y % 4 === 0 && y % 100 !== 0) || y % 400 === 0;
54
+ const days = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
55
+ return d <= days[m - 1];
56
+ }
57
+
58
+ function checkDate(v) {
59
+ const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(v);
60
+ return !!m && isCalendarDate(+m[1], +m[2], +m[3]);
61
+ }
62
+
63
+ function checkTime(v) {
64
+ const m = /^(\d{2}):(\d{2}):(\d{2})(\.\d+)?([Zz]|[+-]\d{2}:\d{2})?$/.exec(v);
65
+ if (!m) return false;
66
+ const hh = +m[1];
67
+ const mm = +m[2];
68
+ const ss = +m[3];
69
+ return hh <= 23 && mm <= 59 && ss <= 60; // 60 allows a leap second
70
+ }
71
+
72
+ function checkDateTime(v) {
73
+ const m = /^(.+)[Tt](.+)$/.exec(v);
74
+ if (!m) return false;
75
+ // RFC 3339 date-time requires an offset/Z on the time part.
76
+ if (!/([Zz]|[+-]\d{2}:\d{2})$/.test(m[2])) return false;
77
+ return checkDate(m[1]) && checkTime(m[2]);
78
+ }
79
+
80
+ function checkOctet(s) {
81
+ if (!/^\d{1,3}$/.test(s)) return false;
82
+ if (s.length > 1 && s[0] === "0") return false; // no leading zeros
83
+ return +s <= 255;
84
+ }
85
+
86
+ function checkIpv4(v) {
87
+ const parts = v.split(".");
88
+ return parts.length === 4 && parts.every(checkOctet);
89
+ }
90
+
91
+ function checkIpv6(v) {
92
+ if (typeof v !== "string" || v.length === 0) return false;
93
+ // At most one "::" compression; split on it.
94
+ const halves = v.split("::");
95
+ if (halves.length > 2) return false;
96
+ const hextet = /^[0-9a-fA-F]{1,4}$/;
97
+ if (halves.length === 2) {
98
+ const left = halves[0] === "" ? [] : halves[0].split(":");
99
+ const right = halves[1] === "" ? [] : halves[1].split(":");
100
+ if (
101
+ !left.every((g) => hextet.test(g)) ||
102
+ !right.every((g) => hextet.test(g))
103
+ )
104
+ return false;
105
+ return left.length + right.length <= 7; // "::" stands for ≥1 zero group
106
+ }
107
+ const groups = v.split(":");
108
+ return groups.length === 8 && groups.every((g) => hextet.test(g));
109
+ }
110
+
111
+ function checkHostname(v) {
112
+ if (typeof v !== "string" || v.length === 0 || v.length > 253) return false;
113
+ const labels = v.replace(/\.$/, "").split(".");
114
+ return labels.every((l) =>
115
+ /^[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?$/.test(l),
116
+ );
117
+ }
118
+
119
+ const FORMAT_CHECKERS = {
120
+ "date-time": checkDateTime,
121
+ date: checkDate,
122
+ time: checkTime,
123
+ email: (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v),
124
+ uri: (v) => /^[A-Za-z][A-Za-z0-9+.-]*:\S*$/.test(v),
125
+ uuid: (v) =>
126
+ /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(
127
+ v,
128
+ ),
129
+ ipv4: checkIpv4,
130
+ ipv6: checkIpv6,
131
+ hostname: checkHostname,
132
+ };
133
+
134
+ /** Known format names (exported for meta-validation / callers). */
135
+ export const KNOWN_FORMATS = new Set(Object.keys(FORMAT_CHECKERS));
136
+
44
137
  /** RFC 6901 JSON Pointer from a segment array ("" = document root). */
45
138
  export function jsonPointer(segments) {
46
139
  if (!segments || segments.length === 0) return "";
@@ -194,6 +287,32 @@ function _validate(value, schema, inst, sch, root, errors, depth) {
194
287
  _validateObject(value, schema, inst, sch, root, errors, depth);
195
288
 
196
289
  _validateCombinators(value, schema, inst, sch, root, errors, depth);
290
+ _validateConditional(value, schema, inst, sch, root, errors, depth);
291
+ }
292
+
293
+ /**
294
+ * if/then/else — Draft 2020-12 conditional application. `if` is a *test* whose
295
+ * own errors are never reported; if the value matches `if`, `then` applies,
296
+ * otherwise `else` applies. No `if` keyword ⇒ then/else are inert.
297
+ */
298
+ function _validateConditional(value, schema, inst, sch, root, errors, depth) {
299
+ if (schema.if === undefined) return;
300
+ const matched = _branchValid(value, schema.if, root, depth);
301
+ if (matched) {
302
+ if (schema.then !== undefined) {
303
+ _validate(
304
+ value,
305
+ schema.then,
306
+ inst,
307
+ [...sch, "then"],
308
+ root,
309
+ errors,
310
+ depth,
311
+ );
312
+ }
313
+ } else if (schema.else !== undefined) {
314
+ _validate(value, schema.else, inst, [...sch, "else"], root, errors, depth);
315
+ }
197
316
  }
198
317
 
199
318
  function _validateString(value, schema, inst, sch, errors) {
@@ -232,6 +351,19 @@ function _validateString(value, schema, inst, sch, errors) {
232
351
  );
233
352
  }
234
353
  }
354
+ if (typeof schema.format === "string") {
355
+ const check = FORMAT_CHECKERS[schema.format];
356
+ // Unknown format = annotation only (always passes), per Draft 2020-12.
357
+ if (check && !check(value)) {
358
+ _err(
359
+ errors,
360
+ "format",
361
+ inst,
362
+ [...sch, "format"],
363
+ `string is not a valid ${schema.format}`,
364
+ );
365
+ }
366
+ }
235
367
  }
236
368
 
237
369
  function _validateNumber(value, schema, inst, sch, errors) {
@@ -650,6 +782,14 @@ function _metaValidate(schema, segs, errors, depth) {
650
782
  }
651
783
  if (schema.not !== undefined)
652
784
  _metaValidate(schema.not, [...segs, "not"], errors, depth + 1);
785
+ if (schema.format !== undefined && typeof schema.format !== "string") {
786
+ _metaErr(errors, "format", [...segs, "format"], "format must be a string");
787
+ }
788
+ for (const k of ["if", "then", "else"]) {
789
+ if (schema[k] !== undefined) {
790
+ _metaValidate(schema[k], [...segs, k], errors, depth + 1);
791
+ }
792
+ }
653
793
  for (const defsKey of ["$defs", "definitions"]) {
654
794
  if (schema[defsKey] !== undefined) {
655
795
  if (
@@ -24,6 +24,7 @@ export {
24
24
  renameSession,
25
25
  pruneJsonlSessions,
26
26
  forkSession,
27
+ createBranchSession,
27
28
  sessionExists,
28
29
  getLastSessionId,
29
30
  migrateLegacySessions,