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
@@ -13,7 +13,9 @@ import { resolve } from "node:path";
13
13
  import {
14
14
  isGitRepo,
15
15
  gitExec,
16
+ gitExecArgs,
16
17
  assertSafeGitRef,
18
+ assertSafeGitPath,
17
19
  } from "../lib/git-integration.js";
18
20
 
19
21
  const WORKTREE_DIR = ".worktrees";
@@ -167,6 +169,7 @@ export function diffWorktree(repoDir, branchName, options = {}) {
167
169
  if (options.baseBranch) assertSafeGitRef(options.baseBranch, "base branch");
168
170
  const base = options.baseBranch || "HEAD";
169
171
  const filePath = options.filePath || null;
172
+ if (filePath) assertSafeGitPath(filePath, "file path");
170
173
  const fileArg = filePath ? ` -- "${filePath}"` : "";
171
174
  const statOutput = gitExec(
172
175
  `diff ${base}...${branchName} --stat --numstat${fileArg}`,
@@ -223,14 +226,13 @@ export function mergeWorktree(repoDir, branchName, options = {}) {
223
226
  const msg =
224
227
  options.message ||
225
228
  `feat: merge agent work from ${branchName} (squashed)`;
226
- gitExec(`commit -m "${msg.replace(/"/g, '\\"')}"`, repoDir);
229
+ // Free-text message → argv (no shell) so `$()`/backticks/quotes in it
230
+ // can't inject a command (the old `-m "…"` only escaped `"`).
231
+ gitExecArgs(["commit", "-m", msg], repoDir);
227
232
  } else {
228
233
  const msg =
229
234
  options.message || `Merge branch '${branchName}' (agent task)`;
230
- gitExec(
231
- `merge ${branchName} --no-edit -m "${msg.replace(/"/g, '\\"')}"`,
232
- repoDir,
233
- );
235
+ gitExecArgs(["merge", branchName, "--no-edit", "-m", msg], repoDir);
234
236
  }
235
237
 
236
238
  if (deleteBranch) {
@@ -393,6 +395,7 @@ export function applyWorktreeAutomationCandidate(
393
395
  const conflictType = options.conflictType || null;
394
396
  const baseBranch = _resolveBaseBranchForMerge(repoDir, options.baseBranch);
395
397
 
398
+ if (filePath) assertSafeGitPath(filePath, "file path");
396
399
  if (!filePath) {
397
400
  throw new Error("filePath is required");
398
401
  }
@@ -514,8 +517,10 @@ export function worktreeLog(repoDir, branchName, baseBranch = "HEAD") {
514
517
 
515
518
  function _fileStatus(repoDir, base, branch, filePath) {
516
519
  try {
517
- const output = gitExec(
518
- `diff ${base}...${branch} --name-status -- "${filePath}"`,
520
+ // filePath here comes from git's conflict output (a tracked file name);
521
+ // pass it via argv so an exotic / maliciously-named file can't inject.
522
+ const output = gitExecArgs(
523
+ ["diff", `${base}...${branch}`, "--name-status", "--", filePath],
519
524
  repoDir,
520
525
  );
521
526
  const status = output.trim().charAt(0);
@@ -603,7 +608,10 @@ function _buildConflictSummary(repoDir, filePath, branchName) {
603
608
 
604
609
  function _conflictStatusCode(repoDir, filePath) {
605
610
  try {
606
- const output = gitExec(`status --porcelain -- "${filePath}"`, repoDir);
611
+ const output = gitExecArgs(
612
+ ["status", "--porcelain", "--", filePath],
613
+ repoDir,
614
+ );
607
615
  const firstLine = output.split("\n").find((line) => line.trim());
608
616
  return firstLine ? firstLine.slice(0, 2) : "UU";
609
617
  } catch (_e) {
@@ -779,7 +787,10 @@ function _buildDiffPreview(repoDir, filePath, branchName) {
779
787
  };
780
788
 
781
789
  try {
782
- const diff = gitExec(`diff HEAD...${branchName} -- "${filePath}"`, repoDir);
790
+ const diff = gitExecArgs(
791
+ ["diff", `HEAD...${branchName}`, "--", filePath],
792
+ repoDir,
793
+ );
783
794
  preview.snippet =
784
795
  diff.length > 2000
785
796
  ? `${diff.slice(0, 2000)}\n... [diff truncated]`
@@ -3,8 +3,11 @@ export {
3
3
  AGENT_TOOLS,
4
4
  AGENT_TOOL_REGISTRY,
5
5
  MAX_SUB_AGENT_DEPTH,
6
+ MAX_SUB_AGENTS_PER_RUN,
6
7
  MAX_TOOL_RESULT_CHARS,
7
8
  capToolResultString,
9
+ safeStringifyToolResult,
10
+ tokenizeShellWords,
8
11
  reloadSkills,
9
12
  getAgentToolDefinitions,
10
13
  getAgentToolDescriptors,
@@ -8,6 +8,7 @@
8
8
  */
9
9
 
10
10
  import { EventEmitter } from "events";
11
+ import { firstBalancedJson } from "./json-schema-output.js";
11
12
 
12
13
  // Exported for test injection
13
14
  export const _deps = {
@@ -471,10 +472,10 @@ Reply with a JSON object: { "action": "retry|add_step|skip", "newParams": {...},
471
472
 
472
473
  _parseSteps(text) {
473
474
  try {
474
- // Extract JSON array from response
475
- const match = text.match(/\[[\s\S]*\]/);
476
- if (match) {
477
- return JSON.parse(match[0]);
475
+ // Extract the first balanced JSON array from the response
476
+ const jsonText = firstBalancedJson(text, "[");
477
+ if (jsonText) {
478
+ return JSON.parse(jsonText);
478
479
  }
479
480
  } catch (_err) {
480
481
  // Parse failure
@@ -484,8 +485,8 @@ Reply with a JSON object: { "action": "retry|add_step|skip", "newParams": {...},
484
485
 
485
486
  _parseJSON(text) {
486
487
  try {
487
- const match = text.match(/\{[\s\S]*\}/);
488
- if (match) return JSON.parse(match[0]);
488
+ const jsonText = firstBalancedJson(text, "{");
489
+ if (jsonText) return JSON.parse(jsonText);
489
490
  } catch (_err) {
490
491
  // Parse failure
491
492
  }
@@ -18,6 +18,7 @@
18
18
  */
19
19
 
20
20
  import { chatWithStreaming, chatStream } from "./chat-core.js";
21
+ import { firstBalancedJson } from "./json-schema-output.js";
21
22
 
22
23
  const DEFAULT_INTENT_TIMEOUT_MS = 15000;
23
24
 
@@ -76,8 +77,9 @@ function extractJson(content) {
76
77
  if (fenced) return fenced[1].trim();
77
78
  const generic = content.match(/```\s*([\s\S]*?)```/);
78
79
  if (generic) return generic[1].trim();
79
- const bare = content.match(/\{[\s\S]*\}/);
80
- return bare ? bare[0] : null;
80
+ // Balanced extraction stops at the first complete object instead of the old
81
+ // greedy /\{[\s\S]*\}/, which over-captured trailing prose with a stray }.
82
+ return firstBalancedJson(content, "{");
81
83
  }
82
84
 
83
85
  /**
@@ -256,6 +256,29 @@ export function createCheckpoint(cwd = process.cwd(), opts = {}) {
256
256
  /* non-critical */
257
257
  }
258
258
 
259
+ // Bound the session's checkpoint history when asked. Auto-checkpointing
260
+ // (one ref per mutating tool) passes a cap so a long agentic run can't
261
+ // accumulate unbounded refs — which also keeps nextId's per-create
262
+ // for-each-ref scan bounded. Best-effort: pruning never fails the
263
+ // checkpoint, and the dropped commit objects stay reachable via newer
264
+ // checkpoints' parent chain (only addressability by id is lost for the
265
+ // oldest entries). Manual `cc checkpoint create` omits the cap → unbounded.
266
+ if (Number.isFinite(opts.maxPerSession) && opts.maxPerSession > 0) {
267
+ try {
268
+ const rows = listRefs(root, session); // oldest-first (creatordate)
269
+ const excess = rows.length - opts.maxPerSession;
270
+ for (let i = 0; i < excess; i++) {
271
+ try {
272
+ git(["update-ref", "-d", rows[i].ref], { cwd: root });
273
+ } catch {
274
+ /* best-effort — a failed prune never affects the new checkpoint */
275
+ }
276
+ }
277
+ } catch {
278
+ /* pruning is entirely best-effort */
279
+ }
280
+ }
281
+
259
282
  return { id, ref, commit, tree, parent, label, session, createdAt, files };
260
283
  } finally {
261
284
  rmQuiet(tmpIndex);
@@ -51,6 +51,12 @@ const CREDENTIAL_BASENAMES = new Set([
51
51
  "id_dsa",
52
52
  "id_ecdsa",
53
53
  "id_ed25519", // private SSH keys (the `.pub` siblings have a different basename)
54
+ "id_ecdsa_sk",
55
+ "id_ed25519_sk", // FIDO/security-key SSH keys
56
+ ".htpasswd", // web-server basic-auth hashes
57
+ "kubeconfig", // bare KUBECONFIG file (the ~/.kube/config form is matched below)
58
+ ".terraformrc", // Terraform registry/API tokens
59
+ "terraform.rc",
54
60
  ]);
55
61
 
56
62
  // Extensions whose BYTES are the secret (private keys / cert bundles / keyrings).
@@ -67,6 +73,7 @@ const CREDENTIAL_PATH_RE = [
67
73
  /[\\/]\.docker[\\/]config\.json$/i,
68
74
  /[\\/]\.config[\\/]gh[\\/]hosts\.yml$/i, // GitHub CLI OAuth token
69
75
  /[\\/]\.gnupg[\\/]/i, // GPG keyring directory
76
+ /service[-_]account[^\\/]*\.json$/i, // GCP service-account key JSON
70
77
  ];
71
78
 
72
79
  // secret(s).{json,yml,yaml,env,txt}
@@ -2,7 +2,7 @@
2
2
  * Git integration — wraps system git commands for knowledge base versioning.
3
3
  */
4
4
 
5
- import { execSync } from "child_process";
5
+ import { execSync, spawnSync } from "child_process";
6
6
  import { existsSync, writeFileSync, chmodSync } from "fs";
7
7
  import { join } from "path";
8
8
 
@@ -41,6 +41,62 @@ export function assertSafeGitRef(ref, label = "git ref") {
41
41
  }
42
42
  }
43
43
 
44
+ /**
45
+ * Validate a repo-relative file path before it is interpolated into a shell git
46
+ * command (e.g. `diff … -- "${filePath}"`). Double-quoting does NOT neutralize
47
+ * `$()` / backticks / a closing `"`, so an unsanitized path from a WS frame is a
48
+ * shell-injection vector. Allow only ordinary path characters (no shell
49
+ * metacharacters), reject absolute paths, a leading `-` (flag injection), and
50
+ * `..` (traversal). Exported for reuse + tests.
51
+ *
52
+ * @param {string} p
53
+ * @param {string} [label="file path"]
54
+ */
55
+ export function assertSafeGitPath(p, label = "file path") {
56
+ if (typeof p !== "string" || p.length === 0) {
57
+ throw new Error(`Invalid ${label}: expected a non-empty string`);
58
+ }
59
+ if (
60
+ !/^[A-Za-z0-9._/+@=, -]+$/.test(p) || // safe path chars only — no shell metachars
61
+ p.startsWith("-") || // no flag injection (e.g. `-- --output=…`)
62
+ p.startsWith("/") || // no absolute path (posix)
63
+ /^[A-Za-z]:/.test(p) || // no absolute path (windows drive)
64
+ p.includes("..") // no traversal / range tricks
65
+ ) {
66
+ throw new Error(`Unsafe ${label}: ${JSON.stringify(p)}`);
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Run git with an ARGV array (no shell) — for commands that carry free-text the
72
+ * caller can't allowlist (a commit / merge message). spawnSync passes each arg
73
+ * verbatim, so `$()`, backticks, quotes, and `;` in the message are inert
74
+ * instead of being interpreted by a shell as they would be through `gitExec`.
75
+ * Throws git's stderr on failure, mirroring `gitExec`'s contract.
76
+ *
77
+ * @param {string[]} args
78
+ * @param {string} cwd
79
+ * @param {object} [opts] { input }
80
+ * @returns {string} trimmed stdout
81
+ */
82
+ export function gitExecArgs(args, cwd, opts = {}) {
83
+ const res = spawnSync("git", args, {
84
+ cwd,
85
+ encoding: "utf-8",
86
+ windowsHide: true,
87
+ maxBuffer: 64 * 1024 * 1024,
88
+ input: opts.input,
89
+ });
90
+ if (res.error) throw res.error;
91
+ if (res.status !== 0) {
92
+ const stderr = (res.stderr || res.stdout || "").toString().trim();
93
+ throw new Error(
94
+ stderr || `git ${args.join(" ")} failed (exit ${res.status})`,
95
+ );
96
+ }
97
+ return (res.stdout || "").toString().trim();
98
+ }
99
+
44
100
  /**
45
101
  * Run a git command and return stdout.
46
102
  */
@@ -107,7 +107,17 @@ function tryParseDecision(stdout) {
107
107
  * nonBlockingError?:boolean, error?:string }}
108
108
  */
109
109
  function runCommandHook(command, input = {}, opts = {}) {
110
- const { timeout = 60000, cwd, event } = opts;
110
+ const { cwd, event } = opts;
111
+ // spawnSync is SYNCHRONOUS — it blocks the whole agent until the hook returns
112
+ // or its timeout fires. Node treats a 0/undefined timeout as UNLIMITED, so a
113
+ // missing, zero, NaN, or negative value (e.g. a malformed settings.json
114
+ // `timeout`) would let a hung hook freeze the agent forever. Guarantee a
115
+ // positive, bounded timeout regardless of the caller's value (already in ms).
116
+ const _rawTimeout = Number(opts.timeout);
117
+ const timeout =
118
+ Number.isFinite(_rawTimeout) && _rawTimeout > 0
119
+ ? Math.min(_rawTimeout, 600000)
120
+ : 60000;
111
121
  if (!command) {
112
122
  return { decision: HOOK_DECISIONS.CONTINUE, reason: null, exitCode: 0 };
113
123
  }
@@ -9,6 +9,7 @@
9
9
 
10
10
  import { EventEmitter } from "events";
11
11
  import { createHash } from "crypto";
12
+ import { firstBalancedJson } from "./json-schema-output.js";
12
13
 
13
14
  /**
14
15
  * Plan session statuses
@@ -345,12 +346,12 @@ Keep plans concise (3-8 steps). Use appropriate tools for each step.`;
345
346
  ]);
346
347
 
347
348
  const content = response?.message?.content || response?.content || "";
348
- const jsonMatch = content.match(/\{[\s\S]*\}/);
349
- if (!jsonMatch) {
349
+ const jsonText = firstBalancedJson(content, "{");
350
+ if (!jsonText) {
350
351
  throw new Error("Failed to parse plan from LLM response");
351
352
  }
352
353
 
353
- return JSON.parse(jsonMatch[0]);
354
+ return JSON.parse(jsonText);
354
355
  }
355
356
 
356
357
  /**
@@ -80,6 +80,48 @@ export function validateAgainstSchema(value, schema, path = "$") {
80
80
  return errors;
81
81
  }
82
82
 
83
+ /**
84
+ * Return the first *balanced* JSON object/array substring in `text`, honoring
85
+ * string literals so braces inside strings don't throw off the depth count.
86
+ *
87
+ * Unlike a greedy `/\{[\s\S]*\}/` match (which spans to the LAST bracket and
88
+ * over-captures trailing prose or a second object — making JSON.parse throw),
89
+ * this stops at the first complete top-level value.
90
+ *
91
+ * @param {string} text
92
+ * @param {"{"|"["} [only] restrict to object- or array-openers when set
93
+ * @returns {string|null} the balanced substring, or null if none is found
94
+ */
95
+ export function firstBalancedJson(text, only) {
96
+ const s = String(text || "");
97
+ for (let i = 0; i < s.length; i++) {
98
+ const open = s[i];
99
+ if (open !== "{" && open !== "[") continue;
100
+ if (only && open !== only) continue;
101
+ const close = open === "{" ? "}" : "]";
102
+ let depth = 0;
103
+ let inStr = false;
104
+ let esc = false;
105
+ for (let j = i; j < s.length; j++) {
106
+ const c = s[j];
107
+ if (inStr) {
108
+ if (esc) esc = false;
109
+ else if (c === "\\") esc = true;
110
+ else if (c === '"') inStr = false;
111
+ continue;
112
+ }
113
+ if (c === '"') inStr = true;
114
+ else if (c === open) depth++;
115
+ else if (c === close) {
116
+ depth--;
117
+ if (depth === 0) return s.slice(i, j + 1);
118
+ }
119
+ }
120
+ // opener at i never closed — try the next opener
121
+ }
122
+ return null;
123
+ }
124
+
83
125
  /** Pull a JSON payload out of an LLM reply (bare, fenced, or embedded). */
84
126
  export function extractJsonPayload(text) {
85
127
  const raw = String(text || "").trim();
@@ -87,6 +129,11 @@ export function extractJsonPayload(text) {
87
129
  tries.push(raw);
88
130
  const fence = /```(?:json)?\s*([\s\S]*?)```/i.exec(raw);
89
131
  if (fence) tries.push(fence[1].trim());
132
+ // Prefer a balanced run (stops at the first complete value) over the greedy
133
+ // first-bracket..last-bracket slice below, which over-captures on trailing
134
+ // prose or a second object.
135
+ const balanced = firstBalancedJson(raw);
136
+ if (balanced) tries.push(balanced);
90
137
  const firstObj = raw.indexOf("{");
91
138
  const lastObj = raw.lastIndexOf("}");
92
139
  if (firstObj !== -1 && lastObj > firstObj)
@@ -12,6 +12,7 @@
12
12
  */
13
13
 
14
14
  import { extractToolNames } from "./skill-synthesizer.js";
15
+ import { firstBalancedJson } from "../json-schema-output.js";
15
16
 
16
17
  // ── _deps for test injection ────────────────────────
17
18
  const _deps = {
@@ -268,9 +269,9 @@ export class ReflectionEngine {
268
269
  try {
269
270
  const messages = buildReflectionPrompt(reportData);
270
271
  const response = await this.llmChat(messages);
271
- const jsonMatch = response.match(/\{[\s\S]*\}/);
272
- if (!jsonMatch) return null;
273
- return JSON.parse(jsonMatch[0]);
272
+ const jsonText = firstBalancedJson(response, "{");
273
+ if (!jsonText) return null;
274
+ return JSON.parse(jsonText);
274
275
  } catch {
275
276
  return null;
276
277
  }
@@ -12,6 +12,7 @@
12
12
  */
13
13
 
14
14
  import fs from "fs";
15
+ import { firstBalancedJson } from "../json-schema-output.js";
15
16
  import path from "path";
16
17
 
17
18
  // ── _deps for test injection ────────────────────────────
@@ -374,9 +375,9 @@ export class SkillImprover {
374
375
  if (!this.llmChat) return null;
375
376
  try {
376
377
  const response = await this.llmChat(messages);
377
- const jsonMatch = response.match(/\{[\s\S]*\}/);
378
- if (!jsonMatch) return null;
379
- return JSON.parse(jsonMatch[0]);
378
+ const jsonText = firstBalancedJson(response, "{");
379
+ if (!jsonText) return null;
380
+ return JSON.parse(jsonText);
380
381
  } catch {
381
382
  return null;
382
383
  }
@@ -17,6 +17,7 @@
17
17
  */
18
18
 
19
19
  import fs from "fs";
20
+ import { firstBalancedJson } from "../json-schema-output.js";
20
21
  import path from "path";
21
22
 
22
23
  // ── _deps for test injection ────────────────────────────
@@ -258,9 +259,9 @@ export class SkillSynthesizer {
258
259
 
259
260
  try {
260
261
  // Try to parse JSON from response
261
- const jsonMatch = response.match(/\{[\s\S]*\}/);
262
- if (!jsonMatch) return null;
263
- return JSON.parse(jsonMatch[0]);
262
+ const jsonText = firstBalancedJson(response, "{");
263
+ if (!jsonText) return null;
264
+ return JSON.parse(jsonText);
264
265
  } catch {
265
266
  return null;
266
267
  }