chainlesschain 0.162.161 → 0.162.163

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 (209) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/{AIOps-DMn528q-.js → AIOps-BQ0kgfA1.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-D9ZcDCJL.js → ActionButton-Dr1MDANe.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-A_K-8PX0.js → Analytics-CZ5vsaXW.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-D4pFW3X6.js → AppLayout-BIBga881.js} +5 -5
  6. package/src/assets/web-panel/assets/{Artifacts-Dz46COAd.js → Artifacts-vpXHRU3u.js} +1 -1
  7. package/src/assets/web-panel/assets/{Audit-cbMjbyg0.js → Audit-B8exuoub.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-Dtf88GAj.js → BackgroundAgents-923evRRN.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-C5KRxn52.js → Backup-sr_sswlQ.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-Dd2UK3BN.js → BaseInput-CnW-4XKT.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-lptKTFdG.js → Chat-FZCsTZIL.js} +5 -5
  12. package/src/assets/web-panel/assets/ChatBubbleRenderer-DDPd9qT0.js +1 -0
  13. package/src/assets/web-panel/assets/{Checkbox-_yspG6rG.js → Checkbox-DFrxB59H.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-BGq_GAha.js → Codegen-_b6WEel8.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-CePor71g.js → Col-CkbnnZ-K.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-CLOWhIT7.js → Community-BxM_82zW.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-CivJt2vT.js → Compact-DYinP45R.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-Btmo1GGE.js → Compliance-DyXp3sSP.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-Ct35OlG4.js → Cowork-BEr2DhW7.js} +2 -2
  20. package/src/assets/web-panel/assets/{Cron-CTc2GmhB.js → Cron-B-Vwo1hN.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-ZtiBETk1.js → Crosschain-DbLxKddd.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-BNqMQQXw.js → DID-BsQ_LFw9.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-BmzZd-9V.js → Dashboard-lVlXQHu5.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-CMVgvP2M.js → Dropdown-Beo_foO7.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-CcqQFAfT.js → EmailListRenderer-DszHIXED.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-CTqB1_4V.js → FamilyGuardDashboard-DcAwoh81.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-CiKOOzlb.js → Federation-CuJLa2eb.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-Wk-OT0gr.js → FormItemContext-Cxd1lxhc.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-DbEQT6ue.js → GenericCardRenderer-BBvq5Jsw.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-0ZvCPSD9.js → Git-WqPKetx6.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-CmVuKkPb.js → Governance-CWYtY3ke.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-CTWE3Jwg.js → Inference-BnX1iiNV.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-CBEIF00L.js → KnowledgeGraph-BJuM8GN5.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-DLhxDwVG.js → Logs-DqLMNntm.js} +2 -2
  35. package/src/assets/web-panel/assets/MarkdownRenderer-BqYwEYou.js +1 -0
  36. package/src/assets/web-panel/assets/{Marketplace-BgsCBrnc.js → Marketplace-_vGfmhNl.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-ClBUGDS2.js → McpTools-BeJDOkDj.js} +3 -3
  38. package/src/assets/web-panel/assets/{Memory-CnG_ZnYw.js → Memory-Da0EEZDO.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-BUOAbR1Z.js → MobileBridge-DSuzcCCy.js} +1 -1
  40. package/src/assets/web-panel/assets/{MobileProjects-Dnt_9-WB.js → MobileProjects-D0RAk4e8.js} +1 -1
  41. package/src/assets/web-panel/assets/{Mtc-CLjt4kCD.js → Mtc-BE9YgdA0.js} +2 -2
  42. package/src/assets/web-panel/assets/{MtcAudit-f24U9Aes.js → MtcAudit-fC2BpO5S.js} +2 -2
  43. package/src/assets/web-panel/assets/{Multisig-GrmGQbSq.js → Multisig-D24Xxqw3.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-DADaWn-n.js → NLProgramming-Bs3oGvS8.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-xtf5RX6C.js → Notes-BFjmAA-3.js} +3 -3
  46. package/src/assets/web-panel/assets/{NotificationSettings-Cd27Rgtx.js → NotificationSettings-BX_9Bhv0.js} +1 -1
  47. package/src/assets/web-panel/assets/{OrderTableRenderer-fXRxlDjl.js → OrderTableRenderer-wjkZwJXI.js} +1 -1
  48. package/src/assets/web-panel/assets/{Organization-joCFlNB0.js → Organization-DBcdsVEP.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-B1HoURq0.js → Overflow-DDyaF9vC.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-DiwsKbT7.js → P2P-BB78L4he.js} +2 -2
  51. package/src/assets/web-panel/assets/{PdhVaultBrowser-Xe-BsW6S.js → PdhVaultBrowser-DqTiZFum.js} +3 -3
  52. package/src/assets/web-panel/assets/{Permissions-C23qi8kl.js → Permissions-INbnWv4j.js} +3 -3
  53. package/src/assets/web-panel/assets/{PersonalDataHub-BS1syX2f.js → PersonalDataHub-BST-VmTq.js} +2 -2
  54. package/src/assets/web-panel/assets/{Pipeline-DRYwwDyh.js → Pipeline-CE7_tS2k.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-BQKaDHPH.js → Privacy-hK5L8dOB.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-BC6F9xiG.js → ProjectInit-BvkkUoDn.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-ClFPc1Nd.js → ProjectSettings-u7_EUtkm.js} +2 -2
  58. package/src/assets/web-panel/assets/Projects-5Fmt_8Nw.js +1 -0
  59. package/src/assets/web-panel/assets/{Providers-CLWuOUNt.js → Providers-B9eanarR.js} +1 -1
  60. package/src/assets/web-panel/assets/{QrScannerModal-DiqULTxb.js → QrScannerModal-C0dX5N2f.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-9A03P-E_.js → QuickAsk-CKTpG-PZ.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-CJZAnbq_.js → Recommend-Px_w_XQM.js} +1 -1
  63. package/src/assets/web-panel/assets/{RemoteSession-C9A1R77h.js → RemoteSession-Di0fnYkM.js} +2 -2
  64. package/src/assets/web-panel/assets/{Reputation-BIZc6Rky.js → Reputation-vesnWjXt.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-DfIPNtuG.js → Row-exbY6LkT.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-C5znFcQ1.js → RssFeed-B3O3x70t.js} +2 -2
  67. package/src/assets/web-panel/assets/{Search-DSs2z3OX.js → Search-uRA0DhNz.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-CV6LcrLY.js → Security-CViqdH6K.js} +4 -4
  69. package/src/assets/web-panel/assets/{Services-cmhoX0Xj.js → Services-CkXS97WN.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-tCsg2po7.js → Skeleton-DxW5WYnE.js} +1 -1
  71. package/src/assets/web-panel/assets/{Skills-BK1KUDiy.js → Skills-CLxmGFse.js} +1 -1
  72. package/src/assets/web-panel/assets/{Sla-yRgyeA82.js → Sla-Cr5fziXq.js} +1 -1
  73. package/src/assets/web-panel/assets/{SpeechSettings-c1mo1mlp.js → SpeechSettings-H0JSb3cp.js} +1 -1
  74. package/src/assets/web-panel/assets/{SyncSettings-koIAfuT3.js → SyncSettings-DMYWMae7.js} +2 -2
  75. package/src/assets/web-panel/assets/{Tasks-DkkreAaB.js → Tasks-CbYyDdF4.js} +1 -1
  76. package/src/assets/web-panel/assets/{Templates-fy5Ctf9M.js → Templates-Cpc8JjiK.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-Bw70Jf6S.js → Tenant-CRh77Jda.js} +1 -1
  78. package/src/assets/web-panel/assets/{Terminal-BKh6Rx-4.js → Terminal-4D8tNBO5.js} +2 -2
  79. package/src/assets/web-panel/assets/{TimelineRenderer-Dg54WP0c.js → TimelineRenderer-D41jzzbG.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-BvvD5SxJ.js → Tokens-C_jWEy6b.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger-B7Ug9x4e.js → Trigger-Dk1ZUhsy.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-BjvuMORY.js → Trust-CLVy4l7m.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-CZqhkx8p.js → UkeySign-C-FeqFDj.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-ClR50lhX.js → VideoEditing-CqVZzyMm.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-DlfppYjx.js → Wallet-DX9BW9pu.js} +2 -2
  86. package/src/assets/web-panel/assets/{WebAuthn-BirgVr8B.js → WebAuthn-7hQDpWw_.js} +2 -2
  87. package/src/assets/web-panel/assets/{WorkflowEditor-B8DP_bax.js → WorkflowEditor-6XqUAO0j.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-DA8AUZzL.js → chat-5R_vkaJG.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-C38pLGp1.js → colors-huUPZdC-.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-81rtr98o.js → compact-item-CfJlgisE.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-D34ixq8t.js → createContext-jO8heofr.js} +1 -1
  92. package/src/assets/web-panel/assets/devWarning-DeIrCfH-.js +1 -0
  93. package/src/assets/web-panel/assets/{hasIn-D4bzqTrZ.js → hasIn-DY8Vxqx7.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-E1OJRXs4.js → index-A48PtXw9.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-D3zCCZRS.js → index-B300RPnT.js} +3 -3
  96. package/src/assets/web-panel/assets/{index-Cd3KuNK5.js → index-B74OXFxC.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-Dmo3dFHb.js → index-BAON7TzU.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-Ddk0e_Pi.js → index-BCqxyvqp.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-9qoVpPt6.js → index-BHE8OIls.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-DBFSo1NF.js → index-BQS-uPOK.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-DUQLXLQM.js → index-BRXOP09X.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-D6GfvEQG.js → index-BYLBPpt8.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-DFyWKOBD.js → index-BfCx4pFT.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-BP-zkXK8.js → index-BliaT02i.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-DMgnRwrK.js → index-BmqGerpC.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-BIP62a6r.js → index-BsBtRWbA.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-DkHqMnn3.js → index-BtEXSoPL.js} +1 -1
  108. package/src/assets/web-panel/assets/index-BxUKMRpP.js +1 -0
  109. package/src/assets/web-panel/assets/{index-B8mkLM4x.js → index-Bxax9vQ6.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-ZRJRItm0.js → index-C2iVSFpy.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-CP5alL4t.js → index-CA6sO5VD.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-BS3M5H9V.js → index-CA90TZxG.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-DvfCEJG-.js → index-CLhBh-qE.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-tKiC8T-4.js → index-CRElRhyS.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-B0QArPQt.js → index-Cbe9G0V4.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-COe_cOLJ.js → index-ChvHubX3.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BvL_3J_n.js → index-Cis_Ip7H.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-F76w1NiL.js → index-Cjxbu68P.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-BlxEphl7.js → index-CzKmteNS.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-CuS-UPoj.js → index-D0pet5Uy.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-CbhE3j5j.js → index-D1VLuww0.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-D_2VYPr1.js → index-DXBcvUnf.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-DakfFZ-k.js → index-Db1cLWir.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-fI5sXMLg.js → index-DoI2sZm7.js} +1 -1
  125. package/src/assets/web-panel/assets/index-DscU9iPI.js +1 -0
  126. package/src/assets/web-panel/assets/{index-CoVgZw_b.js → index-KRHroryI.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-BvD7b2IK.js → index-d1xkbtNz.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-BtGxuQrO.js → index-dKx57io7.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-BqIhLAbA.js → index-j3O2EEJe.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-BXd2_LmH.js → index-p85YaaKw.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-DFHa1HIg.js → index-v3pTaEQm.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-D0Ih7Hwn.js → index-vXs3B8GJ.js} +1 -1
  133. package/src/assets/web-panel/assets/{initDefaultProps-ByyvflIR.js → initDefaultProps-MCB7-raZ.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-B3FQm2By.js → motion-UWDF9swV.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-BSfns6Ef.js → move-CsTmgXZU.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-CTG1Ieri.js → mtc-parser-fQy6KGLg.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-DObM_2j6.js → omit-woPnEjd1.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-DOfelQ1t.js → pickAttrs-Bs2dZ1gf.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-CHZILds_.js → placementArrow-C7EVgPkg.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve-REbucKSx.js → responsiveObserve-BH2kYCTN.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-C6EZzD7o.js → slide-BFBP2V_e.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-BxkWS-g3.js → statusUtils-xdDxDYse.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-Chklyoq6.js → styleChecker-aAWgPPQf.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-CRVwKrrI.js → useFlexGapSupport-DkGSAWoO.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-DV24ik_J.js → useFs-BrOih40N.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-C7vIo4Pg.js → usePersonalDataHub-eIYrgMqO.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-By-v5GlF.js → vnode-B8lutNSE.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-B1w9Am3r.js → zoom-CffNre7V.js} +1 -1
  149. package/src/assets/web-panel/index.html +1 -1
  150. package/src/commands/agenda.js +254 -23
  151. package/src/commands/agent.js +105 -11
  152. package/src/commands/context.js +125 -14
  153. package/src/commands/hook.js +181 -7
  154. package/src/commands/plugin.js +239 -1
  155. package/src/commands/review.js +352 -2
  156. package/src/data/changelog.json +14 -4
  157. package/src/gateways/ws/remote-session-protocol.js +39 -0
  158. package/src/gateways/ws/session-protocol.js +5 -2
  159. package/src/lib/agent-schedule-store.js +289 -10
  160. package/src/lib/agents.js +19 -0
  161. package/src/lib/background-agent-supervisor.js +34 -7
  162. package/src/lib/channels/channel-manager.js +54 -2
  163. package/src/lib/cli-context-engineering.js +9 -2
  164. package/src/lib/context-breakdown.js +116 -0
  165. package/src/lib/docs-drift.js +166 -0
  166. package/src/lib/doctor-checkup.js +128 -0
  167. package/src/lib/hook-event-bus.cjs +0 -0
  168. package/src/lib/hook-event-log.cjs +193 -0
  169. package/src/lib/interaction-adapter.js +34 -4
  170. package/src/lib/json-schema-output.js +75 -12
  171. package/src/lib/json-schema-validate.js +831 -0
  172. package/src/lib/lsp/__tests__/diagnostics-scheduler.test.js +93 -0
  173. package/src/lib/lsp/diagnostics-scheduler.js +127 -0
  174. package/src/lib/operation-fingerprint.js +95 -0
  175. package/src/lib/plugin-runtime/capabilities.js +371 -0
  176. package/src/lib/plugin-runtime/capability-consent.js +175 -0
  177. package/src/lib/plugin-runtime/manifest.js +30 -0
  178. package/src/lib/plugin-runtime/policy.js +62 -0
  179. package/src/lib/plugin-runtime/scopes.js +20 -4
  180. package/src/lib/project-instructions.js +128 -0
  181. package/src/lib/remote-approval-bridge.js +16 -0
  182. package/src/lib/repl-goal.js +132 -0
  183. package/src/lib/repl-rewind.js +52 -0
  184. package/src/lib/review-pipeline.js +227 -0
  185. package/src/lib/runtime-checkup.js +318 -0
  186. package/src/lib/schedule-planner.js +17 -1
  187. package/src/lib/session-lifecycle.js +305 -0
  188. package/src/lib/settings-hook-events.cjs +52 -13
  189. package/src/lib/settings-loader.cjs +42 -0
  190. package/src/lib/side-effect-ledger-store.js +88 -0
  191. package/src/lib/side-effect-ledger.js +296 -0
  192. package/src/lib/sub-agent-context.js +74 -5
  193. package/src/lib/subagent-contract.js +383 -0
  194. package/src/lib/subagent-inheritance.js +112 -0
  195. package/src/lib/telemetry-ids.js +190 -0
  196. package/src/lib/turn-binding-store.js +119 -0
  197. package/src/lib/turn-binding.js +397 -0
  198. package/src/repl/agent-repl.js +136 -0
  199. package/src/repl/permission-tier.js +28 -0
  200. package/src/runtime/agent-core.js +387 -14
  201. package/src/runtime/coding-agent-contract-shared.cjs +60 -1
  202. package/src/runtime/headless-runner.js +267 -0
  203. package/src/runtime/headless-stream.js +60 -1
  204. package/src/assets/web-panel/assets/ChatBubbleRenderer-C2KLtwHz.js +0 -1
  205. package/src/assets/web-panel/assets/MarkdownRenderer-DxywvhTU.js +0 -1
  206. package/src/assets/web-panel/assets/Projects-Dn-f5aYe.js +0 -1
  207. package/src/assets/web-panel/assets/devWarning-CHDIEOeN.js +0 -1
  208. package/src/assets/web-panel/assets/index-CY1NURWZ.js +0 -1
  209. package/src/assets/web-panel/assets/index-DokUYqpq.js +0 -1
@@ -0,0 +1,296 @@
1
+ /**
2
+ * Side-effect ledger — crash-safe accounting of irreversible actions so a
3
+ * recovered worker never repeats a side-effect it already issued (P0
4
+ * state-machine slice: CLAUDE_CODE_CLI_INCREMENTAL_GAP_ANALYSIS_2026-07-12
5
+ * §"后台 Agent 状态机" — "副作用台账(prepared→started→committed|failed|unknown)
6
+ * 与崩溃恢复重放").
7
+ *
8
+ * The gap's acceptance is concrete: kill the worker/supervisor mid `file-write`,
9
+ * `git push`, or `package-install`, and after recovery the effect must not be
10
+ * applied twice. The classic bug is a blind retry — the process died AFTER
11
+ * `git push` reached the remote but BEFORE it recorded success, so a naive
12
+ * "resume the turn" pushes again.
13
+ *
14
+ * The fix is a two-phase record around every side-effect:
15
+ *
16
+ * prepare(opId) → start(opId) → commit(opId) | fail(opId) | unknown(opId)
17
+ *
18
+ * and an HONEST recovery planner. The invariant: a `started`-but-not-settled op
19
+ * is INDETERMINATE — we cannot know if the effect landed — so unless the op is
20
+ * explicitly idempotent it must be INSPECTED by a human/verifier, never blindly
21
+ * redone. `prepared`-but-never-`started` ops are safe to redo (no effect issued
22
+ * yet); `committed` ops are skipped; cleanly `failed` ops redo only when the
23
+ * caller vouched they roll back atomically.
24
+ *
25
+ * Everything here is PURE (no timers, no I/O): the ledger is in-memory +
26
+ * serializable so a caller persists it alongside the session record. I/O lives
27
+ * in [[side-effect-ledger-store.js]].
28
+ */
29
+
30
+ /** Lifecycle states of one side-effecting operation. */
31
+ export const SIDE_EFFECT_STATE = Object.freeze({
32
+ PREPARED: "prepared", // intent recorded; nothing issued yet
33
+ STARTED: "started", // effect issued; outcome not yet known
34
+ COMMITTED: "committed", // effect confirmed applied
35
+ FAILED: "failed", // effect confirmed NOT applied (clean failure)
36
+ UNKNOWN: "unknown", // outcome indeterminate (e.g. crash mid-flight)
37
+ });
38
+
39
+ /** Recovery actions the reconciler emits for each unsettled op. */
40
+ export const RECOVERY_ACTION = Object.freeze({
41
+ SKIP: "skip", // already committed — do nothing
42
+ REDO: "redo", // safe to run again (no effect issued, or idempotent)
43
+ INSPECT: "inspect", // indeterminate — verify before any repeat
44
+ });
45
+
46
+ /**
47
+ * Default idempotency for well-known side-effect kinds. Only actions that are
48
+ * naturally safe to re-run without duplicating an external effect are `true`.
49
+ * `file-write` is idempotent ONLY when a pre-write checkpoint exists (the write
50
+ * can be re-applied from the same source over a restored tree); a raw append
51
+ * has no such guarantee, so the default is conservative `false`.
52
+ */
53
+ const KIND_IDEMPOTENT = new Map([
54
+ ["git-push", false],
55
+ ["package-install", false],
56
+ ["network-mutation", false],
57
+ ["payment", false],
58
+ ["file-write", false],
59
+ ["file-write-checkpointed", true],
60
+ ["read", true],
61
+ ["compute", true],
62
+ ]);
63
+
64
+ /** Whether a kind is idempotent by default (unknown kinds → conservative false). */
65
+ export function kindIsIdempotent(kind) {
66
+ return KIND_IDEMPOTENT.get(String(kind || "")) === true;
67
+ }
68
+
69
+ function nowFrom(clock) {
70
+ return typeof clock === "function" ? Number(clock()) || 0 : 0;
71
+ }
72
+
73
+ /**
74
+ * The in-memory, serializable ledger. A caller records the two-phase lifecycle
75
+ * around each side-effect and, after a crash, hands the rebuilt ledger to
76
+ * `reconcileSideEffects` for a recovery plan.
77
+ *
78
+ * Timestamps are optional and only stamped when a `clock` is injected — keeping
79
+ * the ledger deterministic in tests (no `Date.now()`).
80
+ */
81
+ export class SideEffectLedger {
82
+ constructor({ clock } = {}) {
83
+ this._ops = new Map();
84
+ this._order = [];
85
+ this._clock = clock || null;
86
+ }
87
+
88
+ _rec(opId) {
89
+ return this._ops.get(String(opId)) || null;
90
+ }
91
+
92
+ /**
93
+ * Record intent to perform a side-effect. Idempotent on opId (re-preparing an
94
+ * existing op is a no-op, so a retry loop can't reset a committed op).
95
+ *
96
+ * @param {string} opId stable id (survives restart — e.g. `${turnId}:${seq}`)
97
+ * @param {{kind?:string, key?:string, idempotent?:boolean, meta?:object}} [info]
98
+ */
99
+ prepare(opId, info = {}) {
100
+ const id = String(opId);
101
+ if (this._ops.has(id)) return this;
102
+ const kind = info.kind == null ? null : String(info.kind);
103
+ this._ops.set(id, {
104
+ opId: id,
105
+ kind,
106
+ key: info.key == null ? null : String(info.key),
107
+ idempotent:
108
+ typeof info.idempotent === "boolean"
109
+ ? info.idempotent
110
+ : kindIsIdempotent(kind),
111
+ state: SIDE_EFFECT_STATE.PREPARED,
112
+ reason: null,
113
+ meta: info.meta && typeof info.meta === "object" ? { ...info.meta } : {},
114
+ preparedAt: nowFrom(this._clock),
115
+ settledAt: null,
116
+ });
117
+ this._order.push(id);
118
+ return this;
119
+ }
120
+
121
+ /** Mark the effect as ISSUED (outcome not yet known). Only from `prepared`. */
122
+ start(opId) {
123
+ return this._advance(opId, SIDE_EFFECT_STATE.STARTED, [
124
+ SIDE_EFFECT_STATE.PREPARED,
125
+ ]);
126
+ }
127
+
128
+ /** Confirm the effect applied. Only from `started`. */
129
+ commit(opId) {
130
+ return this._advance(
131
+ opId,
132
+ SIDE_EFFECT_STATE.COMMITTED,
133
+ [SIDE_EFFECT_STATE.STARTED],
134
+ { settle: true },
135
+ );
136
+ }
137
+
138
+ /** Confirm the effect did NOT apply (clean failure). Only from `started`. */
139
+ fail(opId, reason) {
140
+ return this._advance(
141
+ opId,
142
+ SIDE_EFFECT_STATE.FAILED,
143
+ [SIDE_EFFECT_STATE.STARTED],
144
+ { settle: true, reason },
145
+ );
146
+ }
147
+
148
+ /**
149
+ * Explicitly mark an op's outcome indeterminate. Accepts `prepared`/`started`
150
+ * (a recovery pass sweeping unsettled ops), never overrides a settled op.
151
+ */
152
+ unknown(opId, reason) {
153
+ return this._advance(
154
+ opId,
155
+ SIDE_EFFECT_STATE.UNKNOWN,
156
+ [SIDE_EFFECT_STATE.PREPARED, SIDE_EFFECT_STATE.STARTED],
157
+ { settle: true, reason },
158
+ );
159
+ }
160
+
161
+ _advance(opId, next, allowedFrom, { settle = false, reason } = {}) {
162
+ const rec = this._rec(opId);
163
+ if (!rec) return this;
164
+ if (!allowedFrom.includes(rec.state)) return this; // illegal transition → ignore
165
+ rec.state = next;
166
+ if (reason != null) rec.reason = String(reason);
167
+ if (settle) rec.settledAt = nowFrom(this._clock);
168
+ return this;
169
+ }
170
+
171
+ get(opId) {
172
+ const rec = this._rec(opId);
173
+ return rec ? { ...rec, meta: { ...rec.meta } } : null;
174
+ }
175
+
176
+ list() {
177
+ return this._order.map((id) => this.get(id));
178
+ }
179
+
180
+ /** Ops that never reached a terminal state — the recovery surface. */
181
+ unsettled() {
182
+ return this.list().filter(
183
+ (o) =>
184
+ o.state === SIDE_EFFECT_STATE.PREPARED ||
185
+ o.state === SIDE_EFFECT_STATE.STARTED,
186
+ );
187
+ }
188
+
189
+ toJSON() {
190
+ return { ops: this._order.map((id) => this.get(id)) };
191
+ }
192
+
193
+ static fromJSON(json, { clock } = {}) {
194
+ const ledger = new SideEffectLedger({ clock });
195
+ for (const o of json?.ops || []) {
196
+ if (!o || o.opId == null) continue;
197
+ const id = String(o.opId);
198
+ if (ledger._ops.has(id)) continue;
199
+ ledger._ops.set(id, {
200
+ opId: id,
201
+ kind: o.kind == null ? null : String(o.kind),
202
+ key: o.key == null ? null : String(o.key),
203
+ idempotent: o.idempotent === true,
204
+ state: Object.values(SIDE_EFFECT_STATE).includes(o.state)
205
+ ? o.state
206
+ : SIDE_EFFECT_STATE.UNKNOWN,
207
+ reason: o.reason == null ? null : String(o.reason),
208
+ meta: o.meta && typeof o.meta === "object" ? { ...o.meta } : {},
209
+ preparedAt: Number(o.preparedAt) || 0,
210
+ settledAt: o.settledAt == null ? null : Number(o.settledAt) || 0,
211
+ });
212
+ ledger._order.push(id);
213
+ }
214
+ return ledger;
215
+ }
216
+ }
217
+
218
+ /**
219
+ * Plan recovery for one op record (from SideEffectLedger.get()/list()). HONEST:
220
+ * an indeterminate `started` op is INSPECT unless it is explicitly idempotent.
221
+ *
222
+ * @param {object} op
223
+ * @returns {{opId:string, action:string, reason:string}}
224
+ */
225
+ export function planOpRecovery(op) {
226
+ const opId = op?.opId ?? null;
227
+ switch (op?.state) {
228
+ case SIDE_EFFECT_STATE.COMMITTED:
229
+ return {
230
+ opId,
231
+ action: RECOVERY_ACTION.SKIP,
232
+ reason: "already committed",
233
+ };
234
+ case SIDE_EFFECT_STATE.PREPARED:
235
+ // Intent only — no external effect was issued, so a clean re-run is safe.
236
+ return {
237
+ opId,
238
+ action: RECOVERY_ACTION.REDO,
239
+ reason: "prepared but never started — no effect issued",
240
+ };
241
+ case SIDE_EFFECT_STATE.FAILED:
242
+ // A clean failure means the effect did not land; idempotent ops re-run.
243
+ return op.idempotent
244
+ ? {
245
+ opId,
246
+ action: RECOVERY_ACTION.REDO,
247
+ reason: "failed cleanly and idempotent — safe to retry",
248
+ }
249
+ : {
250
+ opId,
251
+ action: RECOVERY_ACTION.INSPECT,
252
+ reason: "failed — verify no partial effect before retrying",
253
+ };
254
+ case SIDE_EFFECT_STATE.STARTED:
255
+ case SIDE_EFFECT_STATE.UNKNOWN:
256
+ default:
257
+ // Crashed mid-flight: the effect MAY have landed. Only idempotent ops are
258
+ // safe to redo; everything else must be inspected, never blindly repeated.
259
+ return op?.idempotent
260
+ ? {
261
+ opId,
262
+ action: RECOVERY_ACTION.REDO,
263
+ reason: "indeterminate but idempotent — safe to re-run",
264
+ }
265
+ : {
266
+ opId,
267
+ action: RECOVERY_ACTION.INSPECT,
268
+ reason:
269
+ "started but not confirmed — effect may have applied; do not repeat blindly",
270
+ };
271
+ }
272
+ }
273
+
274
+ /**
275
+ * Build a recovery plan for every op in a (crash-rebuilt) ledger.
276
+ *
277
+ * @param {SideEffectLedger|{ops:Array}} ledger
278
+ * @returns {{plans:Array, redo:string[], inspect:string[], skip:string[]}}
279
+ */
280
+ export function reconcileSideEffects(ledger) {
281
+ const ops =
282
+ ledger instanceof SideEffectLedger
283
+ ? ledger.list()
284
+ : Array.isArray(ledger?.ops)
285
+ ? ledger.ops
286
+ : [];
287
+ const plans = ops.map((o) => planOpRecovery(o));
288
+ const bucket = (action) =>
289
+ plans.filter((p) => p.action === action).map((p) => p.opId);
290
+ return {
291
+ plans,
292
+ redo: bucket(RECOVERY_ACTION.REDO),
293
+ inspect: bucket(RECOVERY_ACTION.INSPECT),
294
+ skip: bucket(RECOVERY_ACTION.SKIP),
295
+ };
296
+ }
@@ -45,8 +45,10 @@ export class SubAgentContext {
45
45
  * @param {number} [options.maxIterations] - Iteration limit (fallback if no budget)
46
46
  * @param {import('./iteration-budget.js').IterationBudget} [options.iterationBudget] - Shared iteration budget (takes priority over maxIterations)
47
47
  * @param {number} [options.tokenBudget] - Optional token budget
48
- * @param {object} [options.db] - Database instance
48
+ * @param {object} [options.db] - Database instance (memory recall source)
49
49
  * @param {object} [options.permanentMemory] - Permanent memory instance
50
+ * @param {boolean} [options.memoryEnabled=true] - When false, the child's
51
+ * context engine suppresses hierarchical-memory recall (contract memory:false)
50
52
  * @param {object} [options.llmOptions] - LLM provider/model/key options
51
53
  * @param {string} [options.cwd] - Working directory
52
54
  * @param {boolean} [options.useWorktree] - Force worktree isolation (overrides flag)
@@ -77,6 +79,14 @@ export class SubAgentContext {
77
79
  // Shared run-wide TOTAL-sub-agent counter (one object across the whole tree)
78
80
  // so this sub-agent's own spawns draw from the same breadth pool.
79
81
  this.subAgentBudget = options.subAgentBudget || null;
82
+ // This context's EFFECTIVE subagent contract — the ceiling handed to its OWN
83
+ // nested spawns (threaded into the loop options so a nested spawn_sub_agent
84
+ // reads it as ctx.subAgentContract).
85
+ this.subAgentContract = options.subAgentContract || null;
86
+ // Skill capability INTERSECT: null = unrestricted; a list (possibly empty)
87
+ // restricts run_skill/list_skills in this context's loop to those skills.
88
+ this.skillAllowlist =
89
+ options.skillAllowlist != null ? options.skillAllowlist : null;
80
90
  this.cwd = options.cwd || process.cwd();
81
91
  this.status = "active";
82
92
  this.result = null;
@@ -93,10 +103,17 @@ export class SubAgentContext {
93
103
  // Independent message history — never shared with parent
94
104
  this.messages = [];
95
105
 
96
- // Independent context engine — does not inherit parent's compaction/errors
106
+ // Independent context engine — does not inherit parent's compaction/errors.
107
+ // Memory recall is gated by the subagent contract: the spawn passes a `db`
108
+ // and memoryEnabled:true only when the resolved contract grants memory
109
+ // (context:fork from a memory-bearing parent, or explicit memory:true).
110
+ // Default (silent-`fresh`→memory:false) → no db + memoryEnabled:false → no
111
+ // recall, which is today's byte-identical behavior for a plain sub-agent.
112
+ this._memoryEnabled = options.memoryEnabled !== false;
97
113
  this.contextEngine = new CLIContextEngineering({
98
114
  db: options.db || null,
99
115
  permanentMemory: options.permanentMemory || null,
116
+ memoryEnabled: this._memoryEnabled,
100
117
  scope: {
101
118
  taskId: this.id,
102
119
  role: this.role,
@@ -145,6 +162,22 @@ export class SubAgentContext {
145
162
  : {};
146
163
  this._mcpClient = options.mcpClient || null;
147
164
 
165
+ // Inherited settings hooks (Pre/PostToolUse) for this child loop. null =
166
+ // no hooks (the spawn default). The spawn path passes the parent's hooks
167
+ // filtered by the contract's `hooks` allow-list; forwarded into agentLoop.
168
+ this._settingsHooks = options.settingsHooks || null;
169
+
170
+ // permissionMode confirmer for this child loop. null = no confirmer (the
171
+ // spawn default → the child denies ask/sensitive-file/git gates). The spawn
172
+ // sets this only for the autopilot (bypassPermissions) ALLOW confirmer.
173
+ this._permissionConfirm = options.permissionConfirm || null;
174
+
175
+ // permissionMode ApprovalGate for this child loop. null = ungated (the spawn
176
+ // default). The spawn sets a dedicated confirmer-less gate (seeded with the
177
+ // mode's tier) for the strict/trusted tiers so run_shell/browser_act are
178
+ // gated headlessly (CONFIRM→no-confirmer→DENY).
179
+ this._approvalGate = options.approvalGate || null;
180
+
148
181
  // Build isolated system prompt
149
182
  const basePrompt = buildSystemPrompt(this.cwd);
150
183
  const profilePrompt = this._profile?.systemPrompt
@@ -176,9 +209,25 @@ export class SubAgentContext {
176
209
  );
177
210
  }
178
211
 
179
- // If worktree isolation is enabled, wrap execution in isolated worktree
180
- if (this._useWorktree && isGitRepo(this._repoDir)) {
181
- return this._runInWorktree(userPrompt, loopOptions);
212
+ // If worktree isolation is enabled, wrap execution in isolated worktree.
213
+ if (this._useWorktree) {
214
+ if (isGitRepo(this._repoDir)) {
215
+ return this._runInWorktree(userPrompt, loopOptions);
216
+ }
217
+ // FAIL CLOSED: isolation was explicitly requested but this is not a git
218
+ // repo. Never silently fall back to the parent checkout — refuse.
219
+ this.status = "failed";
220
+ this.completedAt = new Date().toISOString();
221
+ this.result = {
222
+ summary:
223
+ "Worktree isolation was requested but the working directory is not a git repository — refusing to run in the parent checkout.",
224
+ artifacts: [],
225
+ tokenCount: this._tokenCount,
226
+ toolsUsed: [...new Set(this._toolsUsed)],
227
+ iterationCount: this._iterationCount,
228
+ isolationError: true,
229
+ };
230
+ return this.result;
182
231
  }
183
232
 
184
233
  return this._runCore(userPrompt, loopOptions);
@@ -270,6 +319,10 @@ export class SubAgentContext {
270
319
  // Shared total-sub-agent counter so a nested spawn_sub_agent draws from
271
320
  // (and is bounded by) the run's single breadth pool.
272
321
  subAgentBudget: this.subAgentBudget,
322
+ // This context's effective contract = the ceiling for its nested spawns.
323
+ subAgentContract: this.subAgentContract,
324
+ // Skill allow-list restricting run_skill/list_skills in this loop.
325
+ skillAllowlist: this.skillAllowlist,
273
326
  ...loopOptions,
274
327
  };
275
328
  if (this.iterationBudget) {
@@ -298,6 +351,22 @@ export class SubAgentContext {
298
351
  if (this._mcpClient) {
299
352
  options.mcpClient = this._mcpClient;
300
353
  }
354
+ // Inherited Pre/PostToolUse hooks (spawn passes the parent's, filtered by
355
+ // the contract's `hooks` allow-list). Only set when non-null so a plain
356
+ // sub-agent keeps its no-hooks default.
357
+ if (this._settingsHooks) {
358
+ options.settingsHooks = this._settingsHooks;
359
+ }
360
+ // permissionMode confirmer (spawn sets it only for bypassPermissions). Only
361
+ // forward when present so a plain sub-agent keeps its implicit-deny default.
362
+ if (this._permissionConfirm) {
363
+ options.permissionConfirm = this._permissionConfirm;
364
+ }
365
+ // permissionMode ApprovalGate (spawn sets it for strict/trusted tiers). Only
366
+ // forward when present so a plain sub-agent stays ungated (byte-identical).
367
+ if (this._approvalGate) {
368
+ options.approvalGate = this._approvalGate;
369
+ }
301
370
 
302
371
  try {
303
372
  // Use a separate messages array for the agent loop