nolo-cli 0.149.0-alpha.1 → 0.151.0-alpha.2

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 (258) hide show
  1. package/{_executeModel-OW3H7ICR.js → _executeModel-XHAFJ2MM.js} +134 -91
  2. package/accountResolver-7RYB4D2P.js +300 -0
  3. package/action-SXJD5N5R.js +23 -0
  4. package/{addContentAction-B2DRF7P6.js → addContentAction-ZV774PHH.js} +20 -14
  5. package/{addDialogAgentAction-2P2FLTLD.js → addDialogAgentAction-A6SMKAGH.js} +26 -22
  6. package/{agentDeleteCommand-7IDWF6E3.js → agentDeleteCommand-DPUUS56Z.js} +181 -93
  7. package/{agentEmailCommands-A6XBQZBN.js → agentEmailCommands-V4P5EFLP.js} +132 -112
  8. package/{agentGrantCommands-M3XFO6EN.js → agentGrantCommands-HOJ3JBBQ.js} +3 -3
  9. package/agentListCommands-TWFLUYIZ.js +802 -0
  10. package/{agentMachineCommands-DQPUIMAC.js → agentMachineCommands-MELPAZZI.js} +58 -62
  11. package/{agentProviderCommands-YK2BIIXZ.js → agentProviderCommands-HL6MBLCL.js} +149 -2
  12. package/{agentPullCommand-HF4A42D4.js → agentPullCommand-FA43RBRU.js} +7 -7
  13. package/agentRecordCommands-FSWSSQIO.js +154 -0
  14. package/agentRunCommand-GDBJVOAT.js +183 -0
  15. package/{agentRunControl-2SKCR4SB.js → agentRunControl-IFM7IGMG.js} +10 -2
  16. package/{agentRuntimeLocal-AK2QVJHY.js → agentRuntimeLocal-KKSQJMTE.js} +47 -50
  17. package/{agentSlice-O4RUQUMT.js → agentSlice-YO2ZLJMD.js} +23 -16
  18. package/{appDeleteCommands-N3XEVVYC.js → appDeleteCommands-4PL4KYRN.js} +58 -59
  19. package/{appDeployCommands-AWFIXYVW.js → appDeployCommands-EEEXCEB6.js} +67 -63
  20. package/{appDeployStatusCommands-CZRON2FO.js → appDeployStatusCommands-35ZX7T66.js} +56 -56
  21. package/{appGetCommands-XQIUY4TD.js → appGetCommands-WFCHXJWF.js} +56 -56
  22. package/{appListCommands-NNG4H36Y.js → appListCommands-XY2U6DPF.js} +57 -59
  23. package/{authCommand-UBJ4EWYZ.js → authCommand-QYN47CQD.js} +371 -68
  24. package/{authCommands-H6WKKCRW.js → authCommands-77CHYBGF.js} +12 -9
  25. package/{builtinSkillRegistry-ZMF6YGNO.js → builtinSkillRegistry-TDQ6JB2E.js} +13 -11
  26. package/{chromeCommands-2N6MEWRU.js → chromeCommands-OBBNJCLS.js} +314 -13
  27. package/chromeOpsCommands-3Z34RKSE.js +472 -0
  28. package/{chunk-ALXHV7NK.js → chunk-2GGXHQIW.js} +43 -4
  29. package/{localRuntimeAuthority-5W6IJRAD.js → chunk-2IXDJNZI.js} +154 -75
  30. package/{chunk-IVZJWTDI.js → chunk-36OMEXXR.js} +89 -21
  31. package/{chunk-H6A6X4EK.js → chunk-3MZAGMYY.js} +16 -0
  32. package/chunk-3PVYUPXK.js +40 -0
  33. package/chunk-3SZA4U64.js +1192 -0
  34. package/{chunk-GNMAFAIS.js → chunk-44WXLHFI.js} +1 -1
  35. package/{chunk-AGGIDPCZ.js → chunk-4JH2UMJR.js} +6 -0
  36. package/chunk-4LCOCOR5.js +50 -0
  37. package/chunk-4YRTZIVN.js +871 -0
  38. package/chunk-556RW72X.js +14110 -0
  39. package/{chunk-N7J63Q6C.js → chunk-56GBUMO5.js} +10 -1
  40. package/{chunk-W7XHHYWM.js → chunk-5BHQICF2.js} +5 -1
  41. package/{chunk-XUWRURDF.js → chunk-5EBLANFT.js} +1 -1
  42. package/{chunk-3AH7LNUB.js → chunk-5EMSBTQ6.js} +33 -14
  43. package/{chunk-YRRASA5Q.js → chunk-5MM7NNO6.js} +113 -52
  44. package/chunk-5MMAK75Q.js +15 -0
  45. package/{chunk-M5PVFLD4.js → chunk-64K7WX26.js} +90 -21
  46. package/{chunk-AKG5W4LG.js → chunk-6CCFTVBP.js} +76 -221
  47. package/chunk-6MJNEXUU.js +614 -0
  48. package/{chunk-62IL3KQV.js → chunk-6MXDOUMH.js} +15 -3
  49. package/{chunk-QOKAKDPE.js → chunk-6Q7UTJKR.js} +6 -94
  50. package/chunk-6QDYSCPW.js +223 -0
  51. package/{chunk-HYZIYH2H.js → chunk-74RAWNQN.js} +56 -63
  52. package/chunk-7DPNBQSV.js +9 -0
  53. package/chunk-7MOXKPHC.js +119 -0
  54. package/{chunk-ZUEK6A6C.js → chunk-7XKL25PW.js} +1973 -754
  55. package/{chunk-IAESIMK7.js → chunk-A4MW3YZL.js} +10 -14
  56. package/chunk-AGIM7IMJ.js +147 -0
  57. package/{chunk-7CHC4W22.js → chunk-AIZE5TDM.js} +3 -3
  58. package/{chunk-YAQJJDC2.js → chunk-AOCM7HPT.js} +214 -51
  59. package/{chunk-5GVBDNTO.js → chunk-AU7AXHUL.js} +1 -1
  60. package/{chunk-4H22P2JV.js → chunk-AUIJZACN.js} +2073 -1275
  61. package/{chunk-CIF4X6ZM.js → chunk-AWVFQMOV.js} +25 -3
  62. package/{chunk-RYZINGPM.js → chunk-BPMPOFWP.js} +347 -235
  63. package/{chunk-YUNJN43V.js → chunk-BU2YQBV4.js} +215 -28
  64. package/{chunk-XPRWFQQK.js → chunk-CBHGZRCE.js} +38 -12
  65. package/{chunk-Y3PMXGLH.js → chunk-CHCS6XPJ.js} +365 -144
  66. package/chunk-CHOZJMJS.js +78 -0
  67. package/{chunk-GBRG75BQ.js → chunk-D2MDMY2M.js} +1 -0
  68. package/{chunk-3SV7RM24.js → chunk-D2QN6CTI.js} +117 -72
  69. package/{chunk-5TEEECZM.js → chunk-E2AKSW7L.js} +10 -7
  70. package/{chunk-RSEEDUMS.js → chunk-EDX5XSJM.js} +2 -2
  71. package/{chunk-ATLTFRYJ.js → chunk-ES633ZS5.js} +280 -98
  72. package/{chunk-QDSINZM3.js → chunk-EUGTFFO6.js} +2 -23
  73. package/{chunk-UNYJU5L6.js → chunk-EYLGAEB4.js} +5 -1
  74. package/{chunk-CU3QB4HW.js → chunk-EYWJT7A2.js} +12 -12
  75. package/{chunk-BNGW5ASC.js → chunk-EZVK3N5L.js} +66 -18
  76. package/{chunk-6FGTMQCL.js → chunk-F32Z66T4.js} +6 -10
  77. package/chunk-FJMRBOPO.js +11507 -0
  78. package/{chunk-ZOVKKMF6.js → chunk-FY4SDK2E.js} +1 -1
  79. package/chunk-G6OOUQWJ.js +417 -0
  80. package/{chunk-PQWU42UQ.js → chunk-GCKHQECX.js} +6 -6
  81. package/chunk-GKXCHNTE.js +15 -0
  82. package/{chunk-TXH4IWJL.js → chunk-GQN2WLAI.js} +20 -11
  83. package/{chunk-P6GMCCAT.js → chunk-GQV5KHJU.js} +78 -12
  84. package/{chunk-B2XUQXQX.js → chunk-HIQ4GF53.js} +191 -68
  85. package/{chunk-MST6R3E4.js → chunk-HKX3ULQB.js} +34 -22
  86. package/{chunk-GMZLP5DV.js → chunk-HNUWUO75.js} +30 -19
  87. package/{chunk-5UTFL4QJ.js → chunk-I2ZMGXXF.js} +10 -5
  88. package/chunk-I42VC5KR.js +15 -0
  89. package/{chunk-3BXNTXYI.js → chunk-I4SN4NUQ.js} +1199 -56
  90. package/chunk-IA7TRNNR.js +11 -0
  91. package/{chunk-WIWM4FE3.js → chunk-IJERIUXS.js} +55 -748
  92. package/{chunk-CPUFH46E.js → chunk-ILU2NPWY.js} +10 -10
  93. package/{chunk-SLLGJZDT.js → chunk-ISQL4V7C.js} +4 -4
  94. package/chunk-IT3ZZRJE.js +12 -0
  95. package/{chunk-TZZ2WRBL.js → chunk-IZ7OJB6K.js} +20 -16
  96. package/{chunk-DY7ANLKG.js → chunk-JKMLSTZK.js} +43 -12
  97. package/{chunk-6TPOEJMQ.js → chunk-JZX2XLKC.js} +64 -23
  98. package/{chunk-27ONFIEY.js → chunk-K2AER7RX.js} +19 -12
  99. package/{chunk-O2OWEFII.js → chunk-KFHQQ3NO.js} +1 -1
  100. package/{chunk-QXGPFY6J.js → chunk-LGN377P3.js} +27 -17
  101. package/{chunk-L36OWPP3.js → chunk-LIQJTQRN.js} +118 -139
  102. package/{chunk-UASIEDKM.js → chunk-LTRUTOHX.js} +36 -31
  103. package/{chunk-BFU6C242.js → chunk-NMFV26UG.js} +1 -1
  104. package/{chunk-5J3PYNUP.js → chunk-NYYFIKHI.js} +17 -3
  105. package/{chunk-6O4IK4V2.js → chunk-OFFL4DCR.js} +1 -1
  106. package/{chunk-B6SAMB33.js → chunk-OWYYDMGJ.js} +209 -9
  107. package/{chunk-KQHGZWSV.js → chunk-OXME3PEE.js} +1 -1
  108. package/{chunk-CTUQJVQM.js → chunk-P5QACQDK.js} +1 -1
  109. package/{chunk-DXHL7G2L.js → chunk-PCKVQXYV.js} +274 -93
  110. package/{chunk-4JMDLJAD.js → chunk-PJNFNEJE.js} +114 -37
  111. package/{chunk-Z4CSIXYP.js → chunk-PLSVXHSA.js} +3 -3
  112. package/{chunk-BIDGLJQ3.js → chunk-PLXDDGBK.js} +45 -8
  113. package/chunk-PWTNI4N7.js +47 -0
  114. package/chunk-QFEW2ULV.js +42 -0
  115. package/{chunk-RGNEEJ3Y.js → chunk-QLQ573SU.js} +35 -9
  116. package/chunk-QQ56R7KS.js +88 -0
  117. package/chunk-QYPE3TS3.js +43 -0
  118. package/{chunk-4DPP773Q.js → chunk-RSHPU4PD.js} +59 -11
  119. package/chunk-RVWMAFIT.js +134 -0
  120. package/{chunk-ESHYE7WU.js → chunk-S73EPYNC.js} +10 -7
  121. package/{chunk-UQNS32WZ.js → chunk-SCMHQUMK.js} +1 -1
  122. package/{chunk-HTVYCXBP.js → chunk-SMENBO3M.js} +354 -120
  123. package/{chunk-OMRJYNCI.js → chunk-SSFDFOVT.js} +26 -31
  124. package/chunk-SW4S4XYB.js +1794 -0
  125. package/{chunk-TB3MIBRC.js → chunk-T6X3D7NB.js} +572 -430
  126. package/chunk-TY3TIUJK.js +89 -0
  127. package/{chunk-X2QRZXZH.js → chunk-TYJ5XFEA.js} +4 -4
  128. package/{chunk-ALFH24P3.js → chunk-TYYRSFBG.js} +197 -155
  129. package/{chunk-7KVZ5LDQ.js → chunk-UBUH2TCC.js} +18 -4
  130. package/chunk-UEILX4EV.js +15 -0
  131. package/chunk-UHTDNB5Y.js +140 -0
  132. package/{chunk-YYLHWIW6.js → chunk-UKZX3NHN.js} +1 -1
  133. package/{chunk-OHOVNO7I.js → chunk-UOKFKFPO.js} +6 -3
  134. package/{chunk-XISEYOGH.js → chunk-USHQX6VT.js} +5 -5
  135. package/chunk-UUPR3TD7.js +53 -0
  136. package/{chunk-HDLLCBRF.js → chunk-VEA7H5FG.js} +2 -2
  137. package/chunk-VHGD6FHV.js +116 -0
  138. package/chunk-VIWDGTDA.js +8 -0
  139. package/{chunk-GOH6RW6F.js → chunk-VJXHL6MH.js} +34 -11
  140. package/{chunk-4XEYAFLY.js → chunk-VL5KOY2C.js} +623 -94
  141. package/{chunk-EADNWBAZ.js → chunk-VP3BINY7.js} +13 -0
  142. package/{chunk-EOARMOVD.js → chunk-W4S6OTSC.js} +6 -3
  143. package/chunk-WD4XYDYC.js +567 -0
  144. package/chunk-WJZOQR2J.js +151 -0
  145. package/{chunk-AUDNYVRC.js → chunk-XETG4RIX.js} +5 -5
  146. package/{chunk-MX574M24.js → chunk-XI7DGLUK.js} +4 -4
  147. package/{chunk-3L2WKC3W.js → chunk-XPWGOTBQ.js} +8 -1
  148. package/chunk-XRUSXPCQ.js +514 -0
  149. package/{chunk-I4OPU5TK.js → chunk-XS47QOFV.js} +273 -11
  150. package/{chunk-S5XGT6GB.js → chunk-XZNCX52W.js} +30 -9
  151. package/{chunk-ID7LNBWR.js → chunk-Y2DXOPSO.js} +1920 -644
  152. package/{chunk-5OA5HMTL.js → chunk-YLMMECCH.js} +1 -1
  153. package/{chunk-CFT5HBGW.js → chunk-YVLQXTUB.js} +448 -12
  154. package/chunk-YVPQATSC.js +709 -0
  155. package/{chunk-3DAVPNFT.js → chunk-Z7OJUX6S.js} +122 -107
  156. package/chunk-ZEZC7IIM.js +126 -0
  157. package/{chunk-ALGORS6Y.js → chunk-ZF6526IQ.js} +43 -8
  158. package/chunk-ZIWPQEFU.js +165 -0
  159. package/chunk-ZKZ5X2HS.js +7012 -0
  160. package/chunk-ZRAU6T47.js +56 -0
  161. package/{cliExecutor-AWYR3T33.js → cliExecutor-BWBF2MTI.js} +4 -3
  162. package/{clipboardy-BX4NMYHE.js → clipboardy-GCO3GT3I.js} +3 -8
  163. package/{commandRegistry-2KBOXZQC.js → commandRegistry-XN7C5GHO.js} +183 -107
  164. package/contentThunks-5L7YQI3S.js +84 -0
  165. package/{cooldownCommands-ZLEPIB4A.js → cooldownCommands-JFKTYS2D.js} +2 -2
  166. package/{createAgentAutomationAction-5UGTB26P.js → createAgentAutomationAction-QOPOLCRK.js} +53 -44
  167. package/createDialogAction-MZ66VY5Q.js +76 -0
  168. package/{createPageAction-CDLDKPRM.js → createPageAction-VQDJIXMY.js} +65 -54
  169. package/cursorProvider-YGGNMVWX.js +6123 -0
  170. package/{db-VQSAOWAO.js → db-2QOTXYGH.js} +11 -5
  171. package/{dbSlice-DQ32LYTJ.js → dbSlice-J37OGIXH.js} +19 -13
  172. package/{deleteFile-NX6QHCWZ.js → deleteFile-6JWDDRVP.js} +10 -6
  173. package/dialogCommands-YZLSJB6X.js +113 -0
  174. package/{dialogSlice-N3YB65G5.js → dialogSlice-6RODVYCK.js} +53 -39
  175. package/{docCreateCommands-ZSLGVBZT.js → docCreateCommands-JB2RRMRJ.js} +54 -57
  176. package/{docDeleteCommands-5N4GVZBG.js → docDeleteCommands-AQENULNM.js} +56 -58
  177. package/{docReadCommands-YJSAA7AV.js → docReadCommands-DYD25YLQ.js} +53 -56
  178. package/{docStore-E3CEOV5Q.js → docStore-ZZA3OPM5.js} +25 -16
  179. package/{docUpdateCommands-WEMLSW6W.js → docUpdateCommands-3GVS23QL.js} +54 -57
  180. package/{fetchMessages-67MEFPZR.js → fetchMessages-IIJWQ3HR.js} +5 -4
  181. package/{getPricing-NMMFT3YI.js → getPricing-FKK3F5EJ.js} +11 -9
  182. package/{handleSendMessageAction-DQAD5ERJ.js → handleSendMessageAction-DVG5U26T.js} +422 -118
  183. package/index.js +11 -7
  184. package/{lib-FRRS27KN.js → lib-4YTWAGY7.js} +581 -248
  185. package/{localDialogRead-EBRS242U.js → localDialogRead-FSOMOPDZ.js} +2 -2
  186. package/localRuntimeAdapter-HXRHFKRC.js +244 -0
  187. package/localRuntimeAuthority-JDAD7CB6.js +16 -0
  188. package/{localRuntimeDb-3LRMNLCM.js → localRuntimeDb-CVC62PM5.js} +1 -1
  189. package/{machineCommands-HAP75VSG.js → machineCommands-5KBF4RCC.js} +503 -161
  190. package/{machineInfo-66ZFUMLE.js → machineInfo-UKPTZO5Y.js} +2 -2
  191. package/machineWsRunDispatch-4OQDEDOR.js +169 -0
  192. package/{memberThunks-5ZXPR63F.js → memberThunks-66DH6ABQ.js} +25 -18
  193. package/{models-ZPU5EDF4.js → models-LAS4B2B5.js} +2 -2
  194. package/package.json +1 -1
  195. package/{providerPresetApply-DD6NGWW6.js → providerPresetApply-MY66I6NT.js} +10 -10
  196. package/{providerRegistry-XDRLMYGJ.js → providerRegistry-QOE2FT57.js} +13 -9
  197. package/{read-EPTJRAEF.js → read-DC4NUGN6.js} +12 -8
  198. package/{readDocTool-W4NDXNEX.js → readDocTool-NCJ5NT25.js} +1 -1
  199. package/{readlineWorkspace-7BC66QSX.js → readlineWorkspace-RIE62QVZ.js} +5022 -921
  200. package/{removeDialogAgentAction-OSYNZRX7.js → removeDialogAgentAction-E2H3OZU6.js} +29 -28
  201. package/{runAgentClientLoop-6E6UC3CE.js → runAgentClientLoop-KLUUXBZS.js} +154 -97
  202. package/{runtimeDoctorCommands-ID23PNNZ.js → runtimeDoctorCommands-GIOGC5SC.js} +17 -17
  203. package/secretCommands-ZXZRLR3R.js +700 -0
  204. package/setDialogExtraReferencesAction-SITRPMYU.js +37 -0
  205. package/{setPrimaryDialogAgentAction-WZ7KI3YA.js → setPrimaryDialogAgentAction-U7N45TGC.js} +30 -29
  206. package/{chunk-SONWNI3L.js → settingThunks-Y7SVJOMZ.js} +223 -928
  207. package/{skillDiscovery-ZBU76COE.js → skillDiscovery-QC35EQY4.js} +2 -2
  208. package/{slateUtils-RIZES3PV.js → slateUtils-NGV2NMKM.js} +1 -1
  209. package/{spaceCommands-UJOVU4XI.js → spaceCommands-ZR5BR3GX.js} +59 -59
  210. package/{streamAgentChatTurn-3UXPYFNX.js → streamAgentChatTurn-MN2SSPGA.js} +309 -165
  211. package/{switchDialogAgentAction-GMR7WN3P.js → switchDialogAgentAction-OMGBD3N2.js} +26 -22
  212. package/{tableCommands-G7RV3RU3.js → tableCommands-U3QDY4L6.js} +19 -17
  213. package/{toolApiClient-CVGWM4PN.js → toolApiClient-7WSJJJBV.js} +6 -5
  214. package/tools-W2MOLERY.js +124 -0
  215. package/tuiSummaryLlmCaller-BM3VSTBU.js +130 -0
  216. package/{updateCommands-4EHDLF5J.js → updateCommands-NQXAGFM7.js} +3 -3
  217. package/{updateDialogTitleAction-UPFV7TDT.js → updateDialogTitleAction-Z4JX6REN.js} +102 -57
  218. package/workloadLifecycle-36DXRZKU.js +320 -0
  219. package/action-GI44T4DR.js +0 -31
  220. package/agentListCommands-IW3GQVUP.js +0 -427
  221. package/agentRecordCommands-VCA76W6B.js +0 -134
  222. package/agentRunCommand-YNLDKVR7.js +0 -161
  223. package/browser-image-compression-MFDTD7K6.js +0 -1763
  224. package/chatQueueReduxAdapter-TJRMXMYA.js +0 -186
  225. package/chromeOpsCommands-IXRLN57H.js +0 -289
  226. package/chunk-36KZDDO5.js +0 -2415
  227. package/chunk-ACQD6YI2.js +0 -25363
  228. package/chunk-CQ3WA2NE.js +0 -299
  229. package/chunk-CYSYR67N.js +0 -93
  230. package/chunk-JBTJAPUF.js +0 -35
  231. package/chunk-K2CSUCHJ.js +0 -620
  232. package/chunk-LTRXGFMS.js +0 -83
  233. package/chunk-M76ECQRD.js +0 -13658
  234. package/chunk-MEYIRKDN.js +0 -8967
  235. package/chunk-OCZKATRC.js +0 -129
  236. package/chunk-OTGFTBLI.js +0 -13
  237. package/chunk-PUOC4YRO.js +0 -394
  238. package/chunk-PZZTFEKK.js +0 -34
  239. package/chunk-URMS4XY2.js +0 -10
  240. package/chunk-UV6VISQJ.js +0 -20
  241. package/chunk-VJVNU77E.js +0 -83
  242. package/chunk-XYJSY4KG.js +0 -13807
  243. package/chunk-Y5GCAMGP.js +0 -9
  244. package/chunk-YPREESF4.js +0 -225
  245. package/contentThunks-BDIQRZR6.js +0 -73
  246. package/createDialogAction-B7VGVEVU.js +0 -66
  247. package/dialogCommands-G7YBVAMC.js +0 -102
  248. package/levelLazyShim-GZBKODGI.js +0 -7
  249. package/machineWsRunDispatch-L32G7C4P.js +0 -149
  250. package/serverStoreFactory-YMXDF4NI.js +0 -20
  251. package/setDialogExtraReferencesAction-M32UYNCG.js +0 -30
  252. package/settingSlice-Q6EKL7EQ.js +0 -204
  253. package/syncMapping-N6RGXGBW.js +0 -437
  254. package/tools-MKYDMWA4.js +0 -111
  255. package/tuiSummaryLlmCaller-3YFNHCTW.js +0 -128
  256. package/{chunk-SV5IJK6K.js → chunk-NBTY7YJF.js} +3 -3
  257. package/{chunk-2SYTUTLI.js → chunk-YBNHPX4S.js} +3 -3
  258. package/{offlineMarxistsAgentCommand-HGXZV4XJ.js → offlineMarxistsAgentCommand-2QTHJCHV.js} +4 -4
@@ -2,7 +2,7 @@ import {
2
2
  DEVIN_CONNECT_URL,
3
3
  parseSseDataLineJson,
4
4
  parseSseDataLineObject
5
- } from "./chunk-P6GMCCAT.js";
5
+ } from "./chunk-GQV5KHJU.js";
6
6
  import {
7
7
  applyChatCompletionDelta,
8
8
  applyResponsesToolEvent,
@@ -11,39 +11,50 @@ import {
11
11
  flushChatCompletionStream,
12
12
  providerHttpFailure,
13
13
  throwIfChatCompletionStreamFailed
14
- } from "./chunk-QXGPFY6J.js";
14
+ } from "./chunk-LGN377P3.js";
15
15
  import {
16
16
  readSseFrames
17
- } from "./chunk-GMZLP5DV.js";
17
+ } from "./chunk-HNUWUO75.js";
18
+ import {
19
+ evaluateFileWritePolicy,
20
+ summarizeToolArguments
21
+ } from "./chunk-CHOZJMJS.js";
22
+ import {
23
+ EXECUTION_BACKEND_KINDS
24
+ } from "./chunk-PWTNI4N7.js";
18
25
  import {
19
26
  hasImageInRuntimeMessages,
20
27
  resolveAgentContextWindow,
21
28
  stripImagePartsFromMessages
22
- } from "./chunk-OHOVNO7I.js";
23
- import {
24
- downgradeUnparsableToolCalls,
25
- hasParsableObjectArguments,
26
- repairTruncatedToolArguments,
27
- sanitizeForOutbound
28
- } from "./chunk-CQ3WA2NE.js";
29
+ } from "./chunk-UOKFKFPO.js";
29
30
  import {
30
31
  clipToolText,
31
32
  resolveAgentImageInputSupport,
32
33
  resolveHistoricalToolContentCap,
33
34
  resolveToolOutputProfile
34
- } from "./chunk-PQWU42UQ.js";
35
+ } from "./chunk-GCKHQECX.js";
35
36
  import {
36
- buildDialogFallbackTitleFromUserInput
37
- } from "./chunk-2MBTWINN.js";
37
+ applyShapingCounts,
38
+ attachProjectionFacts,
39
+ classifyOutboundErrorCategory,
40
+ createOutboundDiagnosticCall,
41
+ createRequestFacts,
42
+ createRequestShapingCounts,
43
+ projectUserPartForModel,
44
+ resolveOutboundDiagnosticSink,
45
+ writeThinkingFacts
46
+ } from "./chunk-G6OOUQWJ.js";
38
47
  import {
39
48
  createThinkParserState,
40
49
  createToolCallAccumulator,
41
50
  createToolCallTextParserState,
42
51
  extractThinkContent,
43
52
  finalizeAccumulatedToolCalls
44
- } from "./chunk-N7J63Q6C.js";
53
+ } from "./chunk-56GBUMO5.js";
45
54
  import {
46
55
  DELETE_SAFETY_RED_LINE,
56
+ PARALLEL_DISPATCH_DEFAULT_RULE,
57
+ PLATFORM_RISK_CONTROL_RULE,
47
58
  TOOL_GUIDED_SECTION_ORDER,
48
59
  buildIdentityBlock,
49
60
  buildRuntimeGuidanceBlocks,
@@ -51,21 +62,32 @@ import {
51
62
  detectDispatchIntentFromMessages,
52
63
  estimateContextTokens,
53
64
  hashStablePrefixContent,
54
- normalizeChatCompletionsBodyForProvider,
55
- requiresBareImageUrl,
56
- resolveToolGuidedSections,
57
- toBareImageUrlMessages
58
- } from "./chunk-K2CSUCHJ.js";
65
+ resolveToolGuidedSections
66
+ } from "./chunk-WD4XYDYC.js";
59
67
  import {
68
+ classifyUnparsableToolArgs,
69
+ collectStablePromptPrefix,
60
70
  convertMessagesToResponsesInput,
71
+ downgradeUnparsableToolCalls,
61
72
  extractReasoningFromResponseOutput,
62
73
  extractTextFromResponseOutput,
63
74
  extractToolCallsFromResponseOutput,
64
75
  getUsageRequestOptions,
76
+ hasParsableObjectArguments,
77
+ markDowngradedToolTurnOrigin,
78
+ normalizeChatCompletionsBodyForProvider,
79
+ repairTruncatedToolArguments,
80
+ requiresBareImageUrl,
81
+ resolveReasoningReplayOptions,
82
+ sanitizeForOutbound,
83
+ scanTruncation,
65
84
  shouldStripReasoningContentForOutbound,
85
+ stablePromptCacheKey,
86
+ toBareImageUrlMessages,
66
87
  toOpenAiCompatibleMessages,
67
- toResponsesTools
68
- } from "./chunk-PUOC4YRO.js";
88
+ toResponsesTools,
89
+ transferDowngradedToolTurnOrigin
90
+ } from "./chunk-3SZA4U64.js";
69
91
  import {
70
92
  BRIDGE_STREAM_MAX_CHARS,
71
93
  DEFAULT_READ_DIALOG_MAX_RESPONSE_CHARS,
@@ -80,14 +102,12 @@ import {
80
102
  parseNoloWorkspaceToolArguments,
81
103
  readTextStreamUpToChars,
82
104
  resolveNoloDialogInput
83
- } from "./chunk-RGNEEJ3Y.js";
105
+ } from "./chunk-QLQ573SU.js";
84
106
  import {
85
- buildProviderAuthHeaders,
86
- buildProviderExecutionPlan
87
- } from "./chunk-27ONFIEY.js";
107
+ buildDialogFallbackTitleFromUserInput
108
+ } from "./chunk-2MBTWINN.js";
88
109
  import {
89
110
  EMPTY_ASSISTANT_REPAIR_PROMPT,
90
- getModelContextWindow,
91
111
  hasAssistantVisibleOutput,
92
112
  normalizeUsage,
93
113
  readCacheCreationInputTokens,
@@ -95,18 +115,20 @@ import {
95
115
  resolveEmptyAssistantFallbackMessage,
96
116
  resolveEmptyAssistantOutcome,
97
117
  resolveTruncatedReasoningTailLog
98
- } from "./chunk-AKG5W4LG.js";
118
+ } from "./chunk-6CCFTVBP.js";
99
119
  import {
100
120
  COMPACTION_SUMMARY_SCHEMA_VERSION,
101
121
  COMPACTION_SUMMARY_SYSTEM_PROMPT,
122
+ TOOL_RESULT_TRUNCATE_CHARS,
102
123
  buildCompactionMetricsFromPlan,
103
124
  buildCompactionUserContent,
104
- clipCompactText,
125
+ computeSummaryInputTokenCap,
105
126
  createProviderCallTimingTracker,
106
127
  finalizeProviderCallTiming,
107
128
  formatCompactionMetricsLog,
108
129
  formatFileOperationsFromMessages,
109
130
  formatMessagesForSummaryWithTruncation,
131
+ getModelContextWindow,
110
132
  isMeaningfulResponsesOutput,
111
133
  observeMeaningfulProviderResponse,
112
134
  planCompression,
@@ -114,40 +136,40 @@ import {
114
136
  resolveCompressionTriggerRatio,
115
137
  truncateToolOutputForContext,
116
138
  withProviderCallTimingFields
117
- } from "./chunk-RYZINGPM.js";
118
- import {
119
- estimateTokenCount
120
- } from "./chunk-PSCNXBYA.js";
121
- import {
122
- toErrorMessage
123
- } from "./chunk-HNMXYYJR.js";
139
+ } from "./chunk-BPMPOFWP.js";
124
140
  import {
125
- resolveNoloStateDir
126
- } from "./chunk-ZWQUWF6W.js";
141
+ clipCompactText
142
+ } from "./chunk-5MMAK75Q.js";
127
143
  import {
128
- buildDialogSummaryLayer
129
- } from "./chunk-HDLLCBRF.js";
144
+ asRecordOrEmpty
145
+ } from "./chunk-KX6YBIHG.js";
130
146
  import {
131
- buildUserResponseLanguageContext
132
- } from "./chunk-4VJAC32I.js";
147
+ isAbortError
148
+ } from "./chunk-7DPNBQSV.js";
133
149
  import {
134
150
  canonicalizeToolName,
135
151
  canonicalizeToolNames
136
- } from "./chunk-XUWRURDF.js";
137
- import {
138
- asRecordOrEmpty
139
- } from "./chunk-KX6YBIHG.js";
152
+ } from "./chunk-5EBLANFT.js";
140
153
  import {
141
- asTrimmedNonEmptyStringArray,
142
154
  dialogMessageKey,
143
155
  dialogMessageRange
144
- } from "./chunk-3AH7LNUB.js";
156
+ } from "./chunk-5EMSBTQ6.js";
157
+ import {
158
+ asTrimmedNonEmptyStringArray
159
+ } from "./chunk-I42VC5KR.js";
160
+ import {
161
+ asOptionalPositiveFiniteNumber
162
+ } from "./chunk-U6VC4MU4.js";
145
163
  import {
146
164
  asOptionalFiniteNumber
147
165
  } from "./chunk-WWC7I6TA.js";
148
166
  import {
149
- asTrimmedLowercaseString
150
- } from "./chunk-QSJ3HFEF.js";
167
+ estimateTokenCount
168
+ } from "./chunk-PSCNXBYA.js";
169
+ import {
170
+ buildProviderAuthHeaders,
171
+ buildProviderExecutionPlan
172
+ } from "./chunk-K2AER7RX.js";
151
173
  import {
152
174
  isRecord
153
175
  } from "./chunk-KUG52IAP.js";
@@ -155,14 +177,26 @@ import {
155
177
  asTrimmedString
156
178
  } from "./chunk-3EUW3WEO.js";
157
179
  import {
158
- asOptionalPositiveFiniteNumber
159
- } from "./chunk-U6VC4MU4.js";
180
+ toErrorMessage
181
+ } from "./chunk-HNMXYYJR.js";
160
182
  import {
161
183
  normalizeServerOrigin
162
184
  } from "./chunk-CF7PE763.js";
163
185
  import {
164
186
  asOptionalTrimmedString
165
187
  } from "./chunk-G3SUCT4W.js";
188
+ import {
189
+ asTrimmedLowercaseString
190
+ } from "./chunk-QSJ3HFEF.js";
191
+ import {
192
+ resolveNoloStateDir
193
+ } from "./chunk-ZWQUWF6W.js";
194
+ import {
195
+ buildDialogSummaryLayer
196
+ } from "./chunk-VEA7H5FG.js";
197
+ import {
198
+ buildUserResponseLanguageContext
199
+ } from "./chunk-4VJAC32I.js";
166
200
  import {
167
201
  __export
168
202
  } from "./chunk-4VNS5WPM.js";
@@ -5154,8 +5188,8 @@ var runPromiseExitWith = (context3) => {
5154
5188
  const runFork3 = runForkWith(context3);
5155
5189
  return (effect, options) => {
5156
5190
  const fiber3 = runFork3(effect, options);
5157
- return new Promise((resolve5) => {
5158
- fiber3.addObserver((exit3) => resolve5(exit3));
5191
+ return new Promise((resolve6) => {
5192
+ fiber3.addObserver((exit3) => resolve6(exit3));
5159
5193
  });
5160
5194
  };
5161
5195
  };
@@ -12141,8 +12175,8 @@ var toReadableStreamWith = /* @__PURE__ */ dual((args2) => isStream(args2[0]), (
12141
12175
  });
12142
12176
  },
12143
12177
  pull() {
12144
- return new Promise((resolve5) => {
12145
- currentResolve = resolve5;
12178
+ return new Promise((resolve6) => {
12179
+ currentResolve = resolve6;
12146
12180
  latch.openUnsafe();
12147
12181
  });
12148
12182
  },
@@ -12546,7 +12580,7 @@ function sanitizeToolCallPairing(messages) {
12546
12580
  if (isContentBlank(stripped.content)) {
12547
12581
  continue;
12548
12582
  }
12549
- output.push(stripped);
12583
+ output.push(markDowngradedToolTurnOrigin(stripped));
12550
12584
  continue;
12551
12585
  }
12552
12586
  const normalizedAssistant = { ...m, tool_calls: matchedToolCalls };
@@ -12576,11 +12610,11 @@ function resolveSpillDirectory(options) {
12576
12610
  }
12577
12611
  return resolveNoloStateDir("spills");
12578
12612
  }
12579
- function countLines(text) {
12580
- if (!text) return 0;
12613
+ function countLines(text2) {
12614
+ if (!text2) return 0;
12581
12615
  let lines = 1;
12582
- for (let i = 0; i < text.length; i++) {
12583
- if (text.charCodeAt(i) === 10) {
12616
+ for (let i = 0; i < text2.length; i++) {
12617
+ if (text2.charCodeAt(i) === 10) {
12584
12618
  lines++;
12585
12619
  }
12586
12620
  }
@@ -12750,18 +12784,18 @@ var QUICK_PROBE_SUBSTRINGS = [
12750
12784
  "PASSWD",
12751
12785
  "SECRET_KEY"
12752
12786
  ];
12753
- function hasPotentialSecrets(text) {
12754
- if (typeof text !== "string" || text.length === 0) return false;
12755
- return QUICK_PROBE_SUBSTRINGS.some((probe) => text.includes(probe));
12787
+ function hasPotentialSecrets(text2) {
12788
+ if (typeof text2 !== "string" || text2.length === 0) return false;
12789
+ return QUICK_PROBE_SUBSTRINGS.some((probe) => text2.includes(probe));
12756
12790
  }
12757
- function scrubSecrets(text) {
12758
- if (typeof text !== "string" || text.length === 0) {
12759
- return { cleaned: text, redactedCount: 0, matchedKinds: [] };
12791
+ function scrubSecrets(text2) {
12792
+ if (typeof text2 !== "string" || text2.length === 0) {
12793
+ return { cleaned: text2, redactedCount: 0, matchedKinds: [] };
12760
12794
  }
12761
- if (!hasPotentialSecrets(text)) {
12762
- return { cleaned: text, redactedCount: 0, matchedKinds: [] };
12795
+ if (!hasPotentialSecrets(text2)) {
12796
+ return { cleaned: text2, redactedCount: 0, matchedKinds: [] };
12763
12797
  }
12764
- let cleaned = text;
12798
+ let cleaned = text2;
12765
12799
  let redactedCount = 0;
12766
12800
  const matchedKinds = [];
12767
12801
  for (const pattern of SECRET_PATTERNS) {
@@ -12820,15 +12854,15 @@ ${JSON.stringify(visible)}`;
12820
12854
  }
12821
12855
  function blocksToOpenAiMessages(blocks) {
12822
12856
  const out = [];
12823
- let text = "";
12857
+ let text2 = "";
12824
12858
  let reasoning = "";
12825
12859
  let pendingToolCalls = [];
12826
12860
  let pendingToolResults = [];
12827
12861
  const flushSegment = () => {
12828
- if (text === "" && pendingToolCalls.length === 0 && reasoning === "") return;
12862
+ if (text2 === "" && pendingToolCalls.length === 0 && reasoning === "") return;
12829
12863
  out.push({
12830
12864
  role: "assistant",
12831
- content: text || null,
12865
+ content: text2 || null,
12832
12866
  ...reasoning ? { reasoning_content: reasoning } : {},
12833
12867
  ...pendingToolCalls.length > 0 ? { tool_calls: pendingToolCalls } : {}
12834
12868
  });
@@ -12847,7 +12881,7 @@ function blocksToOpenAiMessages(blocks) {
12847
12881
  ...res?.metadata ? { tool_result_metadata: res.metadata } : {}
12848
12882
  });
12849
12883
  }
12850
- text = "";
12884
+ text2 = "";
12851
12885
  reasoning = "";
12852
12886
  pendingToolCalls = [];
12853
12887
  pendingToolResults = [];
@@ -12857,7 +12891,7 @@ function blocksToOpenAiMessages(blocks) {
12857
12891
  if (pendingToolCalls.length > 0) {
12858
12892
  flushSegment();
12859
12893
  }
12860
- text += block.text;
12894
+ text2 += block.text;
12861
12895
  continue;
12862
12896
  }
12863
12897
  if (block.type === "thinking") {
@@ -13045,6 +13079,7 @@ function summarizeHistoricalToolContent(content, toolName, metadata) {
13045
13079
  label: TOOL_OUTPUT_PROJECTION_LABEL
13046
13080
  });
13047
13081
  }
13082
+ var toolProjectionMemoMap = /* @__PURE__ */ new WeakMap();
13048
13083
  function prepareMessagesForProviderCall(messages) {
13049
13084
  const paired = sanitizeToolCallPairing(messages);
13050
13085
  let toolMessageCount = 0;
@@ -13055,27 +13090,39 @@ function prepareMessagesForProviderCall(messages) {
13055
13090
  const { context_reference: _contextReference, ...providerMessage } = message;
13056
13091
  const sanitizedContent = providerMessage.content == null ? "" : typeof providerMessage.content === "string" ? providerMessage.content : providerMessage.content;
13057
13092
  if (providerMessage.role !== "tool") {
13058
- return {
13093
+ return transferDowngradedToolTurnOrigin(message, {
13059
13094
  ...providerMessage,
13060
13095
  content: sanitizedContent
13061
- };
13096
+ });
13062
13097
  }
13063
13098
  toolMessageCount += 1;
13064
- rawToolContentChars += contentCharCount(sanitizedContent);
13065
- const projectedContent = projectToolContentForProvider({
13066
- content: sanitizedContent,
13067
- toolName: providerMessage.toolName,
13068
- metadata: providerMessage.tool_result_metadata,
13069
- maxChars: resolveToolOutputProfile(providerMessage.toolName).maxChars,
13070
- label: TOOL_OUTPUT_PROJECTION_LABEL
13071
- });
13072
- projectedToolContentChars += contentCharCount(projectedContent);
13073
- if (contentCharCount(projectedContent) < contentCharCount(sanitizedContent)) {
13099
+ let memo = toolProjectionMemoMap.get(message);
13100
+ if (!memo) {
13101
+ const rawChars = contentCharCount(sanitizedContent);
13102
+ const projectedContent = projectToolContentForProvider({
13103
+ content: sanitizedContent,
13104
+ toolName: providerMessage.toolName,
13105
+ metadata: providerMessage.tool_result_metadata,
13106
+ maxChars: resolveToolOutputProfile(providerMessage.toolName).maxChars,
13107
+ label: TOOL_OUTPUT_PROJECTION_LABEL
13108
+ });
13109
+ const projectedChars = contentCharCount(projectedContent);
13110
+ memo = {
13111
+ projectedContent,
13112
+ rawChars,
13113
+ projectedChars,
13114
+ isTruncated: projectedChars < rawChars
13115
+ };
13116
+ toolProjectionMemoMap.set(message, memo);
13117
+ }
13118
+ rawToolContentChars += memo.rawChars;
13119
+ projectedToolContentChars += memo.projectedChars;
13120
+ if (memo.isTruncated) {
13074
13121
  truncatedToolResults += 1;
13075
13122
  }
13076
13123
  return {
13077
13124
  ...providerMessage,
13078
- content: projectedContent
13125
+ content: memo.projectedContent
13079
13126
  };
13080
13127
  });
13081
13128
  return {
@@ -13110,13 +13157,36 @@ function prepareHistoryForNextTurn(history, contextReferenceResolver) {
13110
13157
  }
13111
13158
  function filterImagePartsFromMessages(messages, supportsImages) {
13112
13159
  if (supportsImages) return messages;
13113
- return stripImagePartsFromMessages(messages);
13160
+ return stripImagePartsFromMessages(messages).map(
13161
+ (stripped, index) => transferDowngradedToolTurnOrigin(messages[index], stripped)
13162
+ );
13163
+ }
13164
+ var MODEL_PROJECTED_USER_PART_TYPES = /* @__PURE__ */ new Set(["attachment", "media_job"]);
13165
+ var isModelProjectedUserPart = (part) => typeof part === "object" && part !== null && MODEL_PROJECTED_USER_PART_TYPES.has(
13166
+ part.type
13167
+ );
13168
+ function projectUserMessageForProvider(message) {
13169
+ if (message.role !== "user" || !Array.isArray(message.content)) return message;
13170
+ let projected = false;
13171
+ const nextContent = [];
13172
+ for (const part of message.content) {
13173
+ if (isModelProjectedUserPart(part)) {
13174
+ nextContent.push(...projectUserPartForModel(part));
13175
+ projected = true;
13176
+ } else {
13177
+ nextContent.push(part);
13178
+ }
13179
+ }
13180
+ return projected ? { ...message, content: nextContent } : message;
13114
13181
  }
13115
13182
  function composeProviderMessages(args2) {
13116
13183
  return downgradeUnparsableToolCalls([
13117
- ...args2.prefix,
13118
- ...prepareHistoryForNextTurn(args2.history, args2.contextReferenceResolver),
13119
- ...args2.suffix ?? []
13184
+ ...args2.prefix.map(projectUserMessageForProvider),
13185
+ ...prepareHistoryForNextTurn(
13186
+ args2.history,
13187
+ args2.contextReferenceResolver
13188
+ ).map(projectUserMessageForProvider),
13189
+ ...(args2.suffix ?? []).map(projectUserMessageForProvider)
13120
13190
  ]);
13121
13191
  }
13122
13192
  function buildMessages(args2) {
@@ -13147,6 +13217,7 @@ ${args2.input}` : [
13147
13217
  messages: composed2.messages,
13148
13218
  prefixCount: prefix2.length,
13149
13219
  poisonDowngraded: composed2.downgraded,
13220
+ poisonStats: composed2.stats,
13150
13221
  stableContextChars: stableContent.length,
13151
13222
  dynamicContextChars: dynamicContent.length,
13152
13223
  ...stableContent ? {
@@ -13168,6 +13239,7 @@ ${args2.input}` : [
13168
13239
  messages: composed.messages,
13169
13240
  prefixCount: prefix.length,
13170
13241
  poisonDowngraded: composed.downgraded,
13242
+ poisonStats: composed.stats,
13171
13243
  stableContextChars: (args2.prompt?.trim() ?? "").length,
13172
13244
  dynamicContextChars: blocks.join("\n\n").length,
13173
13245
  ...systemContent ? {
@@ -13177,26 +13249,6 @@ ${args2.input}` : [
13177
13249
  };
13178
13250
  }
13179
13251
 
13180
- // packages/agent-runtime/fileWritePolicy.ts
13181
- function evaluateFileWritePolicy(args2) {
13182
- if (args2.sessionApproved) return { permissionDecision: "allow" };
13183
- return {
13184
- permissionDecision: "ask",
13185
- permissionRequest: {
13186
- id: "permission-file-write-session",
13187
- tool: args2.tool,
13188
- action: "file_write",
13189
- title: "\u786E\u8BA4\u5199\u5165\u6587\u4EF6",
13190
- body: `\u672C\u4F1A\u8BDD\u9996\u6B21\u5199\u5165\u6587\u4EF6\uFF1A${args2.path}\u3002\u6279\u51C6\u540E\u672C\u4F1A\u8BDD\u540E\u7EED\u5199\u5165\u4E0D\u518D\u9010\u6B21\u786E\u8BA4\u3002`,
13191
- command: args2.path,
13192
- suggestedRule: {
13193
- scope: "session",
13194
- pattern: { capability: "file_write", target: "session" }
13195
- }
13196
- }
13197
- };
13198
- }
13199
-
13200
13252
  // packages/agent-runtime/toolArgsTruncationPolicy.ts
13201
13253
  import { createHash as createHash2 } from "node:crypto";
13202
13254
  var TRUNCATION_TAIL_DROP_ALLOWED_TOOLS = /* @__PURE__ */ new Set([
@@ -13205,45 +13257,6 @@ var TRUNCATION_TAIL_DROP_ALLOWED_TOOLS = /* @__PURE__ */ new Set([
13205
13257
  function isTailDropRepairAllowed(toolName) {
13206
13258
  return typeof toolName === "string" && TRUNCATION_TAIL_DROP_ALLOWED_TOOLS.has(toolName);
13207
13259
  }
13208
- function scanTruncation(input) {
13209
- const stack = [];
13210
- const commaAtDepth = [];
13211
- let inString = false;
13212
- let escaped = false;
13213
- const closersOf = () => stack.slice().reverse().map((ch) => ch === "{" ? "}" : "]").join("");
13214
- for (let i = 0; i < input.length; i += 1) {
13215
- const ch = input[i];
13216
- if (inString) {
13217
- if (escaped) escaped = false;
13218
- else if (ch === "\\") escaped = true;
13219
- else if (ch === '"') inString = false;
13220
- continue;
13221
- }
13222
- if (ch === '"') {
13223
- inString = true;
13224
- } else if (ch === "{" || ch === "[") {
13225
- stack.push(ch);
13226
- commaAtDepth[stack.length] = void 0;
13227
- } else if (ch === "}" || ch === "]") {
13228
- const open = stack.pop();
13229
- if (open !== (ch === "}" ? "{" : "[")) {
13230
- return { inString, malformed: true, tailArrayComma: null };
13231
- }
13232
- commaAtDepth[stack.length + 1] = void 0;
13233
- } else if (ch === "," && stack[stack.length - 1] === "[") {
13234
- commaAtDepth[stack.length] = { index: i, closers: closersOf() };
13235
- }
13236
- }
13237
- let tailArrayComma = null;
13238
- for (let d = stack.length; d >= 1; d -= 1) {
13239
- const c = commaAtDepth[d];
13240
- if (c && stack[d - 1] === "[") {
13241
- tailArrayComma = c;
13242
- break;
13243
- }
13244
- }
13245
- return { inString, malformed: false, tailArrayComma };
13246
- }
13247
13260
  function repairTruncatedToolArgumentsDroppingTail(toolName, raw) {
13248
13261
  if (!isTailDropRepairAllowed(toolName)) return null;
13249
13262
  if (typeof raw !== "string") return null;
@@ -13284,50 +13297,6 @@ function buildToolArgsDiagnosticLine(args2) {
13284
13297
  return parts2.join(" ");
13285
13298
  }
13286
13299
 
13287
- // packages/agent-runtime/parseToolArguments.ts
13288
- function parseToolArgumentsJson(raw) {
13289
- if (!raw?.trim()) return {};
13290
- try {
13291
- return asRecordOrEmpty(JSON.parse(raw));
13292
- } catch {
13293
- return {};
13294
- }
13295
- }
13296
-
13297
- // packages/agent-runtime/summarizeToolArguments.ts
13298
- function clip(value, max4 = 240) {
13299
- return clipCompactText(value, max4);
13300
- }
13301
- function summarizeToolArguments(toolName, rawArgs) {
13302
- const args2 = parseToolArgumentsJson(rawArgs);
13303
- const pick = (...keys3) => {
13304
- for (const key of keys3) {
13305
- const value = asOptionalTrimmedString(args2[key]);
13306
- if (value) return value;
13307
- }
13308
- return "";
13309
- };
13310
- const command = pick("command", "cmd", "runCommand", "executeCommand", "bash");
13311
- if (command) return clip(command);
13312
- const query = pick("query", "pattern", "search", "q");
13313
- if (query) return clip(query);
13314
- const filePath = pick("filePath", "file_path", "path", "filename", "file");
13315
- if (filePath) return clip(filePath);
13316
- const keys2 = Object.keys(args2);
13317
- if (keys2.length === 0) return "";
13318
- return clip(
13319
- keys2.slice(0, 6).map((key) => {
13320
- const value = args2[key];
13321
- if (typeof value === "string") return `${key}=${clip(value, 80)}`;
13322
- if (typeof value === "number" || typeof value === "boolean") {
13323
- return `${key}=${String(value)}`;
13324
- }
13325
- if (Array.isArray(value)) return `${key}[${value.length}]`;
13326
- return `${key}=${value === null ? "null" : typeof value}`;
13327
- }).join(" ")
13328
- );
13329
- }
13330
-
13331
13300
  // packages/agent-runtime/progressGuard.ts
13332
13301
  var DEFAULT_MAX_CONSECUTIVE_IDENTICAL_ROUNDS = 5;
13333
13302
  var DEFAULT_MAX_CONSECUTIVE_STAGNANT_TOOL_ROUNDS = 8;
@@ -13539,14 +13508,14 @@ function computeDeterministicRef(secret) {
13539
13508
  const hex = hasher.digest("hex").slice(0, 12);
13540
13509
  return `vault-cred-${hex}`;
13541
13510
  }
13542
- async function isolateInboundCredentials(text, broker) {
13543
- if (typeof text !== "string" || text.length === 0 || !broker) {
13544
- return { safeText: text, isolatedCount: 0, credentials: [] };
13511
+ async function isolateInboundCredentials(text2, broker) {
13512
+ if (typeof text2 !== "string" || text2.length === 0 || !broker) {
13513
+ return { safeText: text2, isolatedCount: 0, credentials: [] };
13545
13514
  }
13546
- if (!hasPotentialSecrets(text)) {
13547
- return { safeText: text, isolatedCount: 0, credentials: [] };
13515
+ if (!hasPotentialSecrets(text2)) {
13516
+ return { safeText: text2, isolatedCount: 0, credentials: [] };
13548
13517
  }
13549
- let safeText = text;
13518
+ let safeText = text2;
13550
13519
  const isolatedCredentials = [];
13551
13520
  for (const { kind, regex } of INBOUND_PATTERNS) {
13552
13521
  regex.lastIndex = 0;
@@ -13681,25 +13650,25 @@ var SHELL_STRING_EXECUTION_PATTERNS = [
13681
13650
  /\bpython[\d.]*\s+(?:-\S+\s+)*-c\b/i,
13682
13651
  /\b(?:perl|ruby)\s+(?:-\S+\s+)*-e\b/i
13683
13652
  ];
13684
- var stripQuotedSegments = (text) => {
13653
+ var stripQuotedSegments = (text2) => {
13685
13654
  let out = "";
13686
13655
  let i = 0;
13687
- while (i < text.length) {
13688
- const ch = text[i];
13656
+ while (i < text2.length) {
13657
+ const ch = text2[i];
13689
13658
  if (ch === "\\") {
13690
- out += text.slice(i, i + 2);
13659
+ out += text2.slice(i, i + 2);
13691
13660
  i += 2;
13692
13661
  } else if (ch === "'") {
13693
- const end3 = text.indexOf("'", i + 1);
13694
- if (end3 === -1) return out + text.slice(i);
13662
+ const end3 = text2.indexOf("'", i + 1);
13663
+ if (end3 === -1) return out + text2.slice(i);
13695
13664
  out += "''";
13696
13665
  i = end3 + 1;
13697
13666
  } else if (ch === '"') {
13698
13667
  let j = i + 1;
13699
- while (j < text.length && text[j] !== '"') {
13700
- j += text[j] === "\\" ? 2 : 1;
13668
+ while (j < text2.length && text2[j] !== '"') {
13669
+ j += text2[j] === "\\" ? 2 : 1;
13701
13670
  }
13702
- if (j >= text.length) return out + text.slice(i);
13671
+ if (j >= text2.length) return out + text2.slice(i);
13703
13672
  out += '""';
13704
13673
  i = j + 1;
13705
13674
  } else {
@@ -13710,7 +13679,7 @@ var stripQuotedSegments = (text) => {
13710
13679
  return out;
13711
13680
  };
13712
13681
  var normalizeUserInput = (value) => asTrimmedLowercaseString(value);
13713
- var containsAny = (text, patterns) => patterns.some((pattern) => text.includes(pattern));
13682
+ var containsAny = (text2, patterns) => patterns.some((pattern) => text2.includes(pattern));
13714
13683
  var hasExplicitDestructivePermission = (userInput) => {
13715
13684
  const normalized = normalizeUserInput(userInput);
13716
13685
  if (!normalized) return false;
@@ -13914,109 +13883,232 @@ async function evaluateCapabilityPolicy(capability, normalizedInput, ctx) {
13914
13883
  }
13915
13884
  }
13916
13885
 
13917
- // packages/agent-runtime/localWorkspaceTools.ts
13918
- import { existsSync as existsSync3 } from "node:fs";
13886
+ // packages/agent-runtime/capabilities/execShellCapability.ts
13919
13887
  import { createHash as createHash3 } from "node:crypto";
13920
- import { lstat, mkdir, readdir as readdir2, readFile as readFile3, realpath, stat as stat3, writeFile } from "node:fs/promises";
13921
- import { homedir } from "node:os";
13922
- import { dirname, isAbsolute as isAbsolute2, relative as relative3, resolve as resolve4, sep } from "node:path";
13923
- import { spawn as spawnChildProcess3 } from "node:child_process";
13924
13888
 
13925
- // packages/core/readRangeLedger.ts
13926
- function filterRetainedLedgerRecords(records, retainedCap) {
13927
- return records.filter((record) => record.chars <= retainedCap);
13928
- }
13929
- function isValidRange(range3) {
13930
- return Number.isInteger(range3.startLine) && Number.isInteger(range3.endLine) && range3.endLine >= range3.startLine;
13931
- }
13932
- function mergeLineRanges(ranges) {
13933
- const sorted = ranges.filter(isValidRange).slice().sort((a, b) => a.startLine - b.startLine);
13934
- const merged = [];
13935
- for (const range3 of sorted) {
13936
- const last = merged[merged.length - 1];
13937
- if (last && range3.startLine <= last.endLine + 1) {
13938
- last.endLine = Math.max(last.endLine, range3.endLine);
13939
- } else {
13940
- merged.push({ startLine: range3.startLine, endLine: range3.endLine });
13889
+ // packages/agent-runtime/execShellCompletionWarnings.ts
13890
+ var BG_ESCAPE_WORD_PATTERN = /(?:^|[\s;|(&/\\])(nohup|disown|setsid|start-process)(?=$|[\s;|)&])/i;
13891
+ var SHELL_SPLIT_CHARS = /* @__PURE__ */ new Set([";", "|", "&", "(", ")", "<", ">", "\n", " ", " "]);
13892
+ function maskQuotedAndEscaped(command) {
13893
+ const out = Array.from(command);
13894
+ let quote = null;
13895
+ for (let i = 0; i < out.length; i += 1) {
13896
+ const ch = out[i];
13897
+ if (quote) {
13898
+ out[i] = " ";
13899
+ if (ch === "\\" && quote !== "'") {
13900
+ if (i + 1 < out.length) out[i + 1] = " ";
13901
+ i += 1;
13902
+ } else if (ch === quote) {
13903
+ quote = null;
13904
+ }
13905
+ continue;
13906
+ }
13907
+ if (ch === "\\") {
13908
+ out[i] = " ";
13909
+ if (i + 1 < out.length) out[i + 1] = " ";
13910
+ i += 1;
13911
+ continue;
13912
+ }
13913
+ if (ch === "'" || ch === '"' || ch === "`") {
13914
+ quote = ch;
13915
+ out[i] = " ";
13916
+ continue;
13917
+ }
13918
+ if (ch === "#" && (i === 0 || /[\s;]/.test(out[i - 1] ?? " "))) {
13919
+ while (i < out.length && out[i] !== "\n") {
13920
+ out[i] = " ";
13921
+ i += 1;
13922
+ }
13941
13923
  }
13942
13924
  }
13943
- return merged;
13944
- }
13945
- function isLineRangeCovered(request3, covered) {
13946
- if (!isValidRange(request3)) return false;
13947
- let open = request3.startLine;
13948
- for (const range3 of mergeLineRanges(covered)) {
13949
- if (range3.endLine < open) continue;
13950
- if (range3.startLine > open) return false;
13951
- open = range3.endLine + 1;
13952
- if (open > request3.endLine) return true;
13953
- }
13954
- return open > request3.endLine;
13925
+ return out.join("");
13955
13926
  }
13956
-
13957
- // packages/agent-runtime/runtimeCompat.ts
13958
- import { existsSync as existsSync2, readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
13959
- import { join as join3 } from "node:path";
13960
- import { spawn } from "node:child_process";
13961
- var IS_WINDOWS = process.platform === "win32";
13962
- function isExecutableFile(candidate) {
13963
- if (!existsSync2(candidate)) return false;
13964
- try {
13965
- return statSync2(candidate).isFile();
13966
- } catch {
13967
- return false;
13927
+ function readRedirectTarget(original, start) {
13928
+ let i = start;
13929
+ while (i < original.length && /\s/.test(original[i] ?? "")) {
13930
+ i += 1;
13968
13931
  }
13969
- }
13970
- function resolveExecutableOnPath(name) {
13971
- if (IS_WINDOWS && !/\.[a-z]+$/i.test(name)) {
13972
- const exts = (process.env.PATHEXT || ".EXE;.CMD;.BAT").split(";").filter(Boolean);
13973
- for (const ext of exts) {
13974
- const found = resolveExecutableOnPath(name + ext);
13975
- if (found) return found;
13932
+ if (i >= original.length) return void 0;
13933
+ let text2 = "";
13934
+ let j = i;
13935
+ while (j < original.length) {
13936
+ const o = original[j];
13937
+ if (o === "'" || o === '"' || o === "`") {
13938
+ const quote = o;
13939
+ j += 1;
13940
+ while (j < original.length && original[j] !== quote) {
13941
+ if (original[j] === "\\" && quote !== "'" && j + 1 < original.length) {
13942
+ text2 += original[j + 1];
13943
+ j += 2;
13944
+ continue;
13945
+ }
13946
+ text2 += original[j];
13947
+ j += 1;
13948
+ }
13949
+ j += 1;
13950
+ continue;
13976
13951
  }
13977
- return null;
13978
- }
13979
- const pathEnv = process.env.PATH ?? "";
13980
- const sep2 = pathEnv.includes(";") && IS_WINDOWS ? ";" : ":";
13981
- const pathDirs = pathEnv.split(sep2).filter(Boolean);
13982
- const fallbackDirs = IS_WINDOWS ? [] : [
13983
- "/opt/homebrew/bin",
13984
- "/usr/local/bin",
13985
- "/usr/bin",
13986
- "/bin",
13987
- `${process.env.HOME ?? ""}/.local/bin`
13988
- ].filter(Boolean);
13989
- for (const dir of [...pathDirs, ...fallbackDirs]) {
13990
- const candidate = join3(dir, name);
13991
- if (isExecutableFile(candidate)) return candidate;
13952
+ if (/\s/.test(o) || SHELL_SPLIT_CHARS.has(o)) break;
13953
+ text2 += o;
13954
+ j += 1;
13992
13955
  }
13993
- return null;
13956
+ if (!text2 || text2 === "$") return void 0;
13957
+ return { text: text2, end: j };
13994
13958
  }
13995
- function createGlob(pattern) {
13996
- const regex = globToRegExp(pattern);
13997
- return {
13998
- match(path) {
13999
- return regex.test(path);
14000
- },
14001
- scanSync(opts) {
14002
- const dot = opts.dot ?? false;
14003
- const results2 = [];
14004
- walkDir(opts.cwd, "", dot, results2);
14005
- return results2.filter((rel) => regex.test(rel));
14006
- }
14007
- };
13959
+ function isIgnoredRedirectTarget(target) {
13960
+ return target === "/dev/null";
14008
13961
  }
14009
- function walkDir(dir, prefix, dot, out) {
14010
- let entries;
14011
- try {
14012
- entries = readdirSync2(dir, { withFileTypes: true, encoding: "utf8" });
14013
- } catch {
14014
- return;
14015
- }
14016
- for (const entry of entries) {
14017
- const name = String(entry.name);
14018
- if (!dot && name.startsWith(".")) continue;
14019
- const rel = prefix ? `${prefix}/${name}` : name;
13962
+ function detectExecShellSynchronousWarnings(command, shell) {
13963
+ const masked = maskQuotedAndEscaped(command);
13964
+ const isPowerShell = shell?.resolvedShell === "powershell";
13965
+ const redirectTargets = [];
13966
+ let backgrounded = false;
13967
+ let i = 0;
13968
+ while (i < masked.length) {
13969
+ const ch = masked[i];
13970
+ if (ch === "&") {
13971
+ const next = masked[i + 1];
13972
+ if (next === "&") {
13973
+ i += 2;
13974
+ continue;
13975
+ }
13976
+ if (next === ">") {
13977
+ let j = i + 2;
13978
+ if (masked[j] === ">") j += 1;
13979
+ const target = readRedirectTarget(command, j);
13980
+ if (target && !isIgnoredRedirectTarget(target.text)) redirectTargets.push(target.text);
13981
+ i = target ? target.end : j;
13982
+ continue;
13983
+ }
13984
+ const prev = masked[i - 1];
13985
+ if (prev === ">" || prev === "<") {
13986
+ i += 1;
13987
+ continue;
13988
+ }
13989
+ if (isPowerShell) {
13990
+ i += 1;
13991
+ continue;
13992
+ }
13993
+ backgrounded = true;
13994
+ i += 1;
13995
+ continue;
13996
+ }
13997
+ if (ch === ">") {
13998
+ const prev = masked[i - 1];
13999
+ if (prev === ">" || prev === "&" || prev === "<") {
14000
+ i += 1;
14001
+ continue;
14002
+ }
14003
+ let j = i + 1;
14004
+ if (masked[j] === ">") j += 1;
14005
+ if (masked[j] === "&") {
14006
+ const ampTarget = readRedirectTarget(command, j + 1);
14007
+ const isFdDuplication = ampTarget !== void 0 && (/^\d+$/.test(ampTarget.text) || ampTarget.text === "-");
14008
+ if (ampTarget && !isFdDuplication && !isIgnoredRedirectTarget(ampTarget.text)) {
14009
+ redirectTargets.push(ampTarget.text);
14010
+ }
14011
+ i = ampTarget ? ampTarget.end : j + 1;
14012
+ continue;
14013
+ }
14014
+ const target = readRedirectTarget(command, j);
14015
+ if (target && !isIgnoredRedirectTarget(target.text)) redirectTargets.push(target.text);
14016
+ i = target ? target.end : j;
14017
+ continue;
14018
+ }
14019
+ if (ch === "<") {
14020
+ i += masked[i + 1] === "<" || masked[i + 1] === "&" ? 2 : 1;
14021
+ continue;
14022
+ }
14023
+ i += 1;
14024
+ }
14025
+ const wordMatch = BG_ESCAPE_WORD_PATTERN.exec(masked);
14026
+ if (wordMatch) backgrounded = true;
14027
+ const warnings = [];
14028
+ if (redirectTargets.length > 0) {
14029
+ const target = redirectTargets[0];
14030
+ warnings.push({
14031
+ kind: "output-redirect",
14032
+ redirectTarget: target,
14033
+ message: `[wake-safety] This execShell call completed synchronously; its output was redirected to ${target}. That file is NOT delivered by any completion notification \u2014 no tracked background task was created. If you need the result, read that file in this turn.`
14034
+ });
14035
+ }
14036
+ if (backgrounded) {
14037
+ warnings.push({
14038
+ kind: "shell-backgrounding",
14039
+ message: `[wake-safety] Detected shell-internal backgrounding (lone \`&\`, nohup/disown/setsid/Start-Process or similar). Descendant processes are NOT tracked as platform tasks and will not produce completion notifications; text such as \`LAUNCHED\` printed by the command is plain stdout, not a platform receipt.`
14040
+ });
14041
+ }
14042
+ return warnings;
14043
+ }
14044
+
14045
+ // packages/agent-runtime/workspaceShell.ts
14046
+ import { resolve } from "node:path";
14047
+ import { spawn as spawnChildProcess } from "node:child_process";
14048
+
14049
+ // packages/agent-runtime/runtimeCompat.ts
14050
+ import { existsSync as existsSync2, readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
14051
+ import { join as join3 } from "node:path";
14052
+ import { spawn } from "node:child_process";
14053
+ var IS_WINDOWS = process.platform === "win32";
14054
+ function isExecutableFile(candidate) {
14055
+ if (!existsSync2(candidate)) return false;
14056
+ try {
14057
+ return statSync2(candidate).isFile();
14058
+ } catch {
14059
+ return false;
14060
+ }
14061
+ }
14062
+ function resolveExecutableOnPath(name) {
14063
+ if (IS_WINDOWS && !/\.[a-z]+$/i.test(name)) {
14064
+ const exts = (process.env.PATHEXT || ".EXE;.CMD;.BAT").split(";").filter(Boolean);
14065
+ for (const ext of exts) {
14066
+ const found = resolveExecutableOnPath(name + ext);
14067
+ if (found) return found;
14068
+ }
14069
+ return null;
14070
+ }
14071
+ const pathEnv = process.env.PATH ?? "";
14072
+ const sep2 = pathEnv.includes(";") && IS_WINDOWS ? ";" : ":";
14073
+ const pathDirs = pathEnv.split(sep2).filter(Boolean);
14074
+ const fallbackDirs = IS_WINDOWS ? [] : [
14075
+ "/opt/homebrew/bin",
14076
+ "/usr/local/bin",
14077
+ "/usr/bin",
14078
+ "/bin",
14079
+ `${process.env.HOME ?? ""}/.local/bin`
14080
+ ].filter(Boolean);
14081
+ for (const dir of [...pathDirs, ...fallbackDirs]) {
14082
+ const candidate = join3(dir, name);
14083
+ if (isExecutableFile(candidate)) return candidate;
14084
+ }
14085
+ return null;
14086
+ }
14087
+ function createGlob(pattern) {
14088
+ const regex = globToRegExp(pattern);
14089
+ return {
14090
+ match(path) {
14091
+ return regex.test(path);
14092
+ },
14093
+ scanSync(opts) {
14094
+ const dot = opts.dot ?? false;
14095
+ const results2 = [];
14096
+ walkDir(opts.cwd, "", dot, results2);
14097
+ return results2.filter((rel) => regex.test(rel));
14098
+ }
14099
+ };
14100
+ }
14101
+ function walkDir(dir, prefix, dot, out) {
14102
+ let entries;
14103
+ try {
14104
+ entries = readdirSync2(dir, { withFileTypes: true, encoding: "utf8" });
14105
+ } catch {
14106
+ return;
14107
+ }
14108
+ for (const entry of entries) {
14109
+ const name = String(entry.name);
14110
+ if (!dot && name.startsWith(".")) continue;
14111
+ const rel = prefix ? `${prefix}/${name}` : name;
14020
14112
  const full = join3(dir, name);
14021
14113
  if (entry.isDirectory()) {
14022
14114
  walkDir(full, rel, dot, out);
@@ -14107,9 +14199,9 @@ function spawnToWebStreams(options) {
14107
14199
  env: options.env,
14108
14200
  stdio: ["ignore", "pipe", "pipe"]
14109
14201
  });
14110
- const exited = new Promise((resolve5) => {
14111
- child.on("exit", (code) => resolve5(code ?? 1));
14112
- child.on("error", () => resolve5(127));
14202
+ const exited = new Promise((resolve6) => {
14203
+ child.on("exit", (code) => resolve6(code ?? 1));
14204
+ child.on("error", () => resolve6(127));
14113
14205
  });
14114
14206
  const toWeb = (stream) => {
14115
14207
  if (!stream) return null;
@@ -14128,220 +14220,26 @@ function spawnToWebStreams(options) {
14128
14220
  };
14129
14221
  }
14130
14222
 
14131
- // packages/agent-runtime/fileTextSearch.ts
14132
- import { readdir, readFile as readFile2, stat as stat2 } from "node:fs/promises";
14133
- import { basename, relative as relative2, resolve as resolve2 } from "node:path";
14134
- import { spawn as spawnChildProcess } from "node:child_process";
14135
-
14136
- // packages/agent-runtime/gitignore.ts
14137
- import { readFile, stat } from "node:fs/promises";
14138
- import { resolve } from "node:path";
14139
- async function readRootGitignorePatterns(workspaceRoot) {
14140
- const content = await readFile(resolve(workspaceRoot, ".gitignore"), "utf8").catch(() => null);
14141
- if (content === null) return [];
14142
- return content.split(/\r?\n/).map((line) => line.trim()).filter((line) => line && !line.startsWith("#") && !line.startsWith("!")).flatMap((line) => {
14143
- const unanchored = line.replace(/^\//, "");
14144
- if (unanchored.endsWith("/")) return [`${unanchored}**`];
14145
- if (unanchored.includes("**")) return [unanchored];
14146
- return [unanchored, `${unanchored}/**`];
14147
- });
14148
- }
14149
- async function hasRootGitignore(workspaceRoot) {
14150
- try {
14151
- await stat(resolve(workspaceRoot, ".gitignore"));
14152
- return true;
14153
- } catch {
14154
- return false;
14155
- }
14156
- }
14157
-
14158
- // packages/agent-runtime/fileTextSearch.ts
14159
- var normalizePath = (value) => value.replaceAll("\\", "/");
14160
- var pathFor = (root, file, absolute = false) => absolute ? normalizePath(file) : normalizePath(relative2(root, file) || basename(file));
14161
- var COMMAND_LIMIT_HEADROOM = 1;
14162
- async function runCommand(binary, args2, cwd, maxResults) {
14163
- const recordLimit = maxResults === void 0 ? 0 : Math.max(0, maxResults) + COMMAND_LIMIT_HEADROOM;
14164
- return await new Promise((done4) => {
14165
- let stdout = "", stderr = "", recordCount = 0, stopped = false;
14166
- try {
14167
- const child = spawnChildProcess(binary, args2, { cwd, stdio: ["ignore", "pipe", "pipe"] });
14168
- child.stdout.on("data", (chunk) => {
14169
- stdout += chunk.toString();
14170
- recordCount = parseMatches(cwd, stdout).length;
14171
- if (recordLimit > 0 && recordCount >= recordLimit && !stopped) {
14172
- stopped = true;
14173
- child.kill();
14174
- }
14175
- });
14176
- child.stderr.on("data", (chunk) => {
14177
- stderr += chunk.toString();
14178
- });
14179
- child.on("error", () => done4({ stdout, stderr, outcome: { kind: "spawn-failed" } }));
14180
- child.on("close", (exitCode) => done4({ stdout, stderr, outcome: stopped ? { kind: "stopped-by-limit" } : { kind: "completed", exitCode } }));
14181
- } catch {
14182
- done4({ stdout, stderr, outcome: { kind: "spawn-failed" } });
14183
- }
14184
- });
14223
+ // packages/agent-runtime/processTask.ts
14224
+ var PROCESS_TASK_TERMINAL_STATUSES = [
14225
+ "stopped",
14226
+ "exited",
14227
+ "failed"
14228
+ ];
14229
+ function isProcessTaskTerminalStatus(status) {
14230
+ return typeof status === "string" && PROCESS_TASK_TERMINAL_STATUSES.includes(status);
14185
14231
  }
14186
- function commandArgs(o, binary, target) {
14187
- const args2 = binary === "rg" ? ["--line-number", "--no-heading", "--color", "never", "--null"] : ["-RIn", "--binary-files=without-match", "--null"];
14188
- if (binary === "rg") args2.push("--no-require-git");
14189
- if (o.regex !== true) args2.push(binary === "rg" ? "--fixed-strings" : "-F");
14190
- else if (binary === "grep") args2.push("-E");
14191
- if (o.caseSensitive === false) args2.push("-i");
14192
- if (binary === "rg") {
14193
- if (o.includeIgnored) args2.push("--no-ignore");
14194
- for (const x of o.exclude ?? []) args2.push("--glob", `!${x}`);
14195
- if (o.include) args2.push("--glob", o.include);
14196
- } else {
14197
- for (const x of o.exclude ?? []) {
14198
- const clean = x.replace(/\/\*\*$/, "");
14199
- if (clean && !clean.includes("/")) args2.push("--exclude-dir", clean);
14200
- }
14201
- if (o.include) args2.push("--include", o.include);
14232
+ function reportedTaskStatus(source, exitCode) {
14233
+ if (source === "killed") {
14234
+ return "stopped";
14202
14235
  }
14203
- args2.push("--", o.query, target);
14204
- return args2;
14205
- }
14206
- function parseMatches(root, output) {
14207
- const records = [];
14208
- const pattern = /(?:^|\n)([^\0\n]+)\0(\d+):(.*?)(?=\n[^\0\n]+\0|$)/gs;
14209
- for (const match7 of output.matchAll(pattern)) {
14210
- records.push({ file: resolve2(root, match7[1] ?? ""), line: Number(match7[2]), text: (match7[3] ?? "").replace(/\n$/, "") });
14236
+ if (source === "exited") {
14237
+ return exitCode !== void 0 && exitCode !== 0 ? "failed" : "exited";
14211
14238
  }
14212
- return records;
14213
- }
14214
- async function decorate(root, raw, context3, absolute = false) {
14215
- const cache = /* @__PURE__ */ new Map(), out = [];
14216
- for (const x of raw) {
14217
- let lines = cache.get(x.file);
14218
- if (!lines) {
14219
- try {
14220
- lines = (await readFile2(x.file, "utf8")).split(/\r\n|\r|\n/);
14221
- } catch {
14222
- continue;
14223
- }
14224
- cache.set(x.file, lines);
14225
- }
14226
- const i = x.line - 1;
14227
- out.push({ path: pathFor(root, x.file, absolute), line: x.line, text: lines[i] ?? x.text, before: lines.slice(Math.max(0, i - context3), i), after: lines.slice(i + 1, i + context3 + 1) });
14239
+ if (source === "started" || source === "promoted") {
14240
+ return "running";
14228
14241
  }
14229
- return out;
14230
- }
14231
- function globMatch(pattern, value) {
14232
- const p = normalizePath(pattern).replace(/^!/, "");
14233
- let source = "";
14234
- for (let i = 0; i < p.length; i++) {
14235
- const c = p[i];
14236
- if (c === "*" && p[i + 1] === "*") {
14237
- i++;
14238
- if (p[i + 1] === "/") {
14239
- i++;
14240
- source += "(?:.*/)?";
14241
- } else source += ".*";
14242
- } else if (c === "*") source += "[^/]*";
14243
- else if (c === "?") source += "[^/]";
14244
- else source += c.replace(/[.+^${}()|[\]\\]/g, "\\$&");
14245
- }
14246
- const r = new RegExp(`^${source}$`);
14247
- return r.test(value) || r.test(basename(value));
14248
- }
14249
- var defaultReadTextFile = (filePath) => readFile2(filePath, "utf8").catch(() => "");
14250
- async function jsSearch(o, targetPath) {
14251
- const max4 = o.maxResults ?? Number.MAX_SAFE_INTEGER;
14252
- const excludes = o.exclude ?? [];
14253
- const readText = o.readTextFile ?? defaultReadTextFile;
14254
- const files = [];
14255
- const targetStat = await stat2(targetPath).catch(() => null);
14256
- if (!targetStat) return { matches: [], totalMatches: 0, truncated: false, engine: "js" };
14257
- const ignorePatterns = o.includeIgnored ? [] : await readRootGitignorePatterns(o.root);
14258
- const isIgnored = (rel) => ignorePatterns.some((pattern) => globMatch(pattern, rel));
14259
- const isSearchableFile = (filePath) => {
14260
- const rel = pathFor(o.root, filePath);
14261
- return !excludes.some((pattern) => globMatch(pattern, rel)) && !isIgnored(rel) && (!o.include || globMatch(o.include, rel));
14262
- };
14263
- if (targetStat.isFile()) {
14264
- if (isSearchableFile(targetPath)) files.push(targetPath);
14265
- } else {
14266
- const walk = async (dir) => {
14267
- for (const e of await readdir(dir, { withFileTypes: true })) {
14268
- const f = resolve2(dir, e.name), rel = pathFor(o.root, f);
14269
- if (e.isDirectory()) {
14270
- if (!excludes.some((p) => globMatch(p, rel))) await walk(f);
14271
- } else if (e.isFile() && !excludes.some((p) => globMatch(p, rel)) && !isIgnored(rel) && (!o.include || globMatch(o.include, rel))) {
14272
- files.push(f);
14273
- }
14274
- }
14275
- };
14276
- await walk(targetPath);
14277
- }
14278
- const matcher = o.regex ? new RegExp(o.query, o.caseSensitive === false ? "i" : "") : null;
14279
- const needle = o.caseSensitive === false ? o.query.toLowerCase() : o.query;
14280
- const out = [], context3 = o.contextLines ?? 0;
14281
- for (const file of files) {
14282
- const text = await readText(file);
14283
- if (!text || text.includes("\0")) continue;
14284
- const lines = text.split(/\r\n|\r|\n/);
14285
- for (let i = 0; i < lines.length; i++) {
14286
- const line = lines[i] ?? "";
14287
- const hit = matcher ? matcher.test(line) : o.caseSensitive === false ? line.toLowerCase().includes(needle) : line.includes(needle);
14288
- if (!hit) continue;
14289
- if (out.length >= max4) return { matches: out, totalMatches: max4, truncated: true, engine: "js" };
14290
- out.push({ path: pathFor(o.root, file, o.absolutePaths === true), line: i + 1, text: line, before: lines.slice(Math.max(0, i - context3), i), after: lines.slice(i + 1, i + context3 + 1) });
14291
- }
14292
- }
14293
- return { matches: out, totalMatches: out.length, truncated: false, engine: "js" };
14294
- }
14295
- var COMMAND_ENGINES = [
14296
- { engine: "ripgrep", binaryOption: "resolveRipgrepBinary", binaryName: "rg", argsKey: "rg" },
14297
- { engine: "grep", binaryOption: "resolveGrepBinary", binaryName: "grep", argsKey: "grep" }
14298
- ];
14299
- async function searchFileTextMatches(o) {
14300
- const targetPath = resolve2(o.root, o.target || ".");
14301
- const targetStat = await stat2(targetPath).catch(() => null);
14302
- if (!targetStat) return { matches: [], totalMatches: 0, truncated: false, engine: "js" };
14303
- if (targetStat.isFile()) return jsSearch(o, targetPath);
14304
- const run2 = o.runCommand ?? runCommand, max4 = o.maxResults;
14305
- for (const entry of COMMAND_ENGINES) {
14306
- const injectedResolver = o[entry.binaryOption];
14307
- const binary = injectedResolver ? injectedResolver() : resolveExecutableOnPath(entry.binaryName);
14308
- if (!binary) continue;
14309
- const result4 = await run2(binary, commandArgs(o, entry.argsKey, o.target || "."), o.root, max4);
14310
- const raw = parseMatches(o.root, result4.stdout);
14311
- const hardFailed = result4.outcome.kind === "spawn-failed" || result4.outcome.kind === "completed" && result4.outcome.exitCode !== 0 && result4.outcome.exitCode !== 1;
14312
- if (hardFailed) continue;
14313
- const truncated = raw.length > (max4 ?? Number.MAX_SAFE_INTEGER);
14314
- const matches = await decorate(o.root, raw.slice(0, max4), o.contextLines ?? 0, o.absolutePaths === true);
14315
- return {
14316
- matches,
14317
- totalMatches: truncated ? matches.length : raw.length,
14318
- truncated,
14319
- engine: entry.engine
14320
- };
14321
- }
14322
- return jsSearch(o, targetPath);
14323
- }
14324
-
14325
- // packages/agent-runtime/processTask.ts
14326
- var PROCESS_TASK_TERMINAL_STATUSES = [
14327
- "stopped",
14328
- "exited",
14329
- "failed"
14330
- ];
14331
- function isProcessTaskTerminalStatus(status) {
14332
- return typeof status === "string" && PROCESS_TASK_TERMINAL_STATUSES.includes(status);
14333
- }
14334
- function reportedTaskStatus(source, exitCode) {
14335
- if (source === "killed") {
14336
- return "stopped";
14337
- }
14338
- if (source === "exited") {
14339
- return exitCode !== void 0 && exitCode !== 0 ? "failed" : "exited";
14340
- }
14341
- if (source === "started" || source === "promoted") {
14342
- return "running";
14343
- }
14344
- return source;
14242
+ return source;
14345
14243
  }
14346
14244
  var DEFAULT_TASK_EVENT_RETENTION_MS = 5 * 60 * 1e3;
14347
14245
  var DEFAULT_MAX_TASK_EVENTS_TASKS = 200;
@@ -14445,20 +14343,20 @@ var ProcessTaskEventLog = class {
14445
14343
  };
14446
14344
 
14447
14345
  // packages/agent-runtime/processRegistry.ts
14448
- function toProcessTaskView(record) {
14449
- const capsule = record.resultCapsule;
14346
+ function toProcessTaskView(record2) {
14347
+ const capsule = record2.resultCapsule;
14450
14348
  return {
14451
- taskId: record.taskId,
14452
- pid: record.pid,
14453
- pgid: record.pgid,
14454
- command: record.command,
14455
- label: record.label,
14456
- startedAt: record.startedAt,
14457
- status: record.status,
14458
- ...record.exitCode !== void 0 ? { exitCode: record.exitCode } : {},
14459
- persist: record.persist,
14460
- promoted: record.promoted,
14461
- transient: record.transient,
14349
+ taskId: record2.taskId,
14350
+ pid: record2.pid,
14351
+ pgid: record2.pgid,
14352
+ command: record2.command,
14353
+ label: record2.label,
14354
+ startedAt: record2.startedAt,
14355
+ status: record2.status,
14356
+ ...record2.exitCode !== void 0 ? { exitCode: record2.exitCode } : {},
14357
+ persist: record2.persist,
14358
+ promoted: record2.promoted,
14359
+ transient: record2.transient,
14462
14360
  ...capsule?.durationMs !== void 0 ? { durationMs: capsule.durationMs } : {},
14463
14361
  ...capsule?.spill ? { outputRef: capsule.spill.displayPath } : {}
14464
14362
  };
@@ -14509,7 +14407,7 @@ var ProcessRegistry = class {
14509
14407
  * `started` event. Returns the stored record (with its stable taskId).
14510
14408
  */
14511
14409
  add(proc) {
14512
- const record = {
14410
+ const record2 = {
14513
14411
  taskId: proc.taskId ?? mintTaskId(proc.pid),
14514
14412
  pid: proc.pid,
14515
14413
  pgid: proc.pgid,
@@ -14522,10 +14420,10 @@ var ProcessRegistry = class {
14522
14420
  transient: proc.transient ?? false,
14523
14421
  owner: proc.owner ?? null
14524
14422
  };
14525
- this.processes.set(proc.pid, record);
14526
- this.byTaskId.set(record.taskId, proc.pid);
14527
- this.eventLog.append({ taskId: record.taskId, pid: proc.pid, type: "started" });
14528
- return { ...record };
14423
+ this.processes.set(proc.pid, record2);
14424
+ this.byTaskId.set(record2.taskId, proc.pid);
14425
+ this.eventLog.append({ taskId: record2.taskId, pid: proc.pid, type: "started" });
14426
+ return { ...record2 };
14529
14427
  }
14530
14428
  list() {
14531
14429
  return Array.from(this.processes.values()).map((proc) => ({ ...proc }));
@@ -14775,28 +14673,7 @@ function buildProcessTaskResultCapsule(args2) {
14775
14673
  };
14776
14674
  }
14777
14675
 
14778
- // packages/agent-runtime/processOwnership.ts
14779
- var asNonEmptyString = (value) => {
14780
- if (typeof value !== "string") return null;
14781
- const trimmed = value.trim();
14782
- return trimmed ? trimmed : null;
14783
- };
14784
- function readProcessOwner(runtimeContext) {
14785
- if (!runtimeContext || typeof runtimeContext !== "object") return null;
14786
- const dialogId = asNonEmptyString(runtimeContext.dialogId) ?? asNonEmptyString(runtimeContext.parentThreadId);
14787
- if (!dialogId) return null;
14788
- const turnId = asNonEmptyString(runtimeContext.turnId);
14789
- const ownerId = asNonEmptyString(runtimeContext.ownerId);
14790
- return {
14791
- dialogId,
14792
- ...turnId ? { turnId } : {},
14793
- ...ownerId ? { ownerId } : {}
14794
- };
14795
- }
14796
-
14797
14676
  // packages/agent-runtime/workspaceShell.ts
14798
- import { resolve as resolve3 } from "node:path";
14799
- import { spawn as spawnChildProcess2 } from "node:child_process";
14800
14677
  var EXEC_SHELL_TIMEOUT_ENV = "NOLO_EXEC_SHELL_TIMEOUT_MS";
14801
14678
  var EXEC_SHELL_DETACH_ENV = "NOLO_EXEC_SHELL_DETACH_MS";
14802
14679
  var DEFAULT_EXEC_SHELL_DETACH_MS = 12e4;
@@ -14920,8 +14797,12 @@ function wrapPowerShellCommand(command, shellKind = "pwsh") {
14920
14797
  command
14921
14798
  ].join("; ");
14922
14799
  }
14800
+ var cachedPowerShellExecutable;
14801
+ var cachedBashExecutable;
14923
14802
  function findPowerShellExecutable() {
14924
- return resolveExecutableOnPath("pwsh") || resolveExecutableOnPath("powershell.exe") || resolveExecutableOnPath("powershell");
14803
+ if (cachedPowerShellExecutable !== void 0) return cachedPowerShellExecutable;
14804
+ cachedPowerShellExecutable = resolveExecutableOnPath("pwsh") || resolveExecutableOnPath("powershell.exe") || resolveExecutableOnPath("powershell");
14805
+ return cachedPowerShellExecutable;
14925
14806
  }
14926
14807
  function buildPowerShellSpawnArgs(args2) {
14927
14808
  return [
@@ -14935,14 +14816,23 @@ function buildPowerShellSpawnArgs(args2) {
14935
14816
  wrapPowerShellCommand(args2.command, resolvePowerShellShellKind(args2.executable))
14936
14817
  ];
14937
14818
  }
14938
- function buildBashCommand(command) {
14939
- const executable = resolveExecutableOnPath("bash") || resolveExecutableOnPath("sh");
14940
- if (!executable) throw new Error("bash/sh is not available on this machine.");
14941
- return [executable, "-lc", command];
14819
+ function resolveShellLoginMode() {
14820
+ if (process.env.NOLO_SHELL_LOGIN_MODE === "login" || process.env.NOLO_LOGIN_SHELL === "1") {
14821
+ return "login";
14822
+ }
14823
+ return "fast";
14824
+ }
14825
+ function buildBashCommand(command, options) {
14826
+ if (cachedBashExecutable === void 0) {
14827
+ cachedBashExecutable = resolveExecutableOnPath("bash") || resolveExecutableOnPath("sh");
14828
+ }
14829
+ if (!cachedBashExecutable) throw new Error("bash/sh is not available on this machine.");
14830
+ const isLogin = options?.login ?? resolveShellLoginMode() === "login";
14831
+ return [cachedBashExecutable, isLogin ? "-lc" : "-c", command];
14942
14832
  }
14943
14833
  function buildWorkspaceShellPlan(args2) {
14944
14834
  if (args2.shell === "bash") {
14945
- const argv2 = buildBashCommand(args2.command);
14835
+ const argv2 = buildBashCommand(args2.command, { login: args2.login });
14946
14836
  return { argv: argv2, resolvedShell: "bash", executable: argv2[0] ?? "bash" };
14947
14837
  }
14948
14838
  if (args2.shell === "powershell" || process.platform === "win32") {
@@ -14955,7 +14845,7 @@ function buildWorkspaceShellPlan(args2) {
14955
14845
  shellKind: resolvePowerShellShellKind(executable)
14956
14846
  };
14957
14847
  }
14958
- const argv = buildBashCommand(args2.command);
14848
+ const argv = buildBashCommand(args2.command, { login: args2.login });
14959
14849
  return { argv, resolvedShell: "bash", executable: argv[0] ?? "bash" };
14960
14850
  }
14961
14851
  function buildWorkspaceShellCommand(args2) {
@@ -15100,13 +14990,13 @@ function readNodeStream(stream, maxBytes = DEFAULT_MAX_STREAM_BUFFER_BYTES) {
15100
14990
  const finalize2 = (truncated = false) => {
15101
14991
  if (settled) return;
15102
14992
  settled = true;
15103
- let text = Buffer.concat(chunks2).toString("utf8");
14993
+ let text2 = Buffer.concat(chunks2).toString("utf8");
15104
14994
  if (truncated) {
15105
- text += `
14995
+ text2 += `
15106
14996
 
15107
14997
  [stream pipe truncated: output exceeded ${formatLimit} buffer limit]`;
15108
14998
  }
15109
- resolveStream(text);
14999
+ resolveStream(text2);
15110
15000
  };
15111
15001
  stream.on("data", (chunk) => {
15112
15002
  if (settled) return;
@@ -15192,8 +15082,8 @@ async function runWorkspaceCommand(args2) {
15192
15082
  ...args2.commandPrefix ?? [],
15193
15083
  ...args2.command
15194
15084
  ];
15195
- const proc = spawnChildProcess2(command[0] ?? "", command.slice(1), {
15196
- cwd: resolve3(args2.workspaceRoot),
15085
+ const proc = spawnChildProcess(command[0] ?? "", command.slice(1), {
15086
+ cwd: resolve(args2.workspaceRoot),
15197
15087
  stdio: [
15198
15088
  args2.stdin === void 0 ? "ignore" : "pipe",
15199
15089
  "pipe",
@@ -15407,25 +15297,495 @@ Use listProcesses to inspect or stop it.`,
15407
15297
  ` : ""}command timed out after ${timeoutMs ?? "unknown"}ms
15408
15298
  ` : rawStderr;
15409
15299
  return {
15410
- stdout,
15411
- stderr,
15412
- exitCode,
15413
- timedOut,
15414
- aborted,
15415
- content: truncateToolOutput(
15416
- [
15417
- stdout.trim() ? `stdout:
15418
- ${stdout.trim()}` : "",
15419
- stderr.trim() ? `stderr:
15420
- ${stderr.trim()}` : "",
15421
- `exitCode: ${exitCode}`
15422
- ].filter(Boolean).join("\n\n"),
15423
- args2.outputLimit,
15424
- {
15425
- toolName: "execShell",
15426
- workspaceRoot: args2.workspaceRoot
15427
- }
15428
- )
15300
+ stdout,
15301
+ stderr,
15302
+ exitCode,
15303
+ timedOut,
15304
+ aborted,
15305
+ content: truncateToolOutput(
15306
+ [
15307
+ stdout.trim() ? `stdout:
15308
+ ${stdout.trim()}` : "",
15309
+ stderr.trim() ? `stderr:
15310
+ ${stderr.trim()}` : "",
15311
+ `exitCode: ${exitCode}`
15312
+ ].filter(Boolean).join("\n\n"),
15313
+ args2.outputLimit,
15314
+ {
15315
+ toolName: "execShell",
15316
+ workspaceRoot: args2.workspaceRoot
15317
+ }
15318
+ )
15319
+ };
15320
+ }
15321
+
15322
+ // packages/agent-runtime/capabilities/execShellCapability.ts
15323
+ function buildExecShellToolDefinition(toolName = "execShell") {
15324
+ return {
15325
+ type: "function",
15326
+ function: {
15327
+ name: toolName,
15328
+ description: `Execute a shell command from the workspace root. Prefer one compound command (e.g. 'git status && git diff --stat') to perform complete verification in one step instead of multiple small roundtrips. Do not cd into guessed paths; commands already run from the workspace root. Commands block until exit. Long-running commands (sleep over ${IMMEDIATE_DETACH_SLEEP_THRESHOLD_SECONDS}s, dev servers, watchers) automatically detach to background returning {detached: true, pid, label}; for persistent services, prefer launchProcess. On Windows the resolved shell may be Windows PowerShell 5.1 (metadata.shellKind="powershell5"): it does not support '&&'/'||' or $PSStyle \u2014 join commands with ';' or split into multiple calls. metadata.shellKind="pwsh" means PowerShell 7+ with full syntax. Read resolvedShell/shellKind from any execShell result before writing Windows-specific syntax. LIFECYCLE CONTRACT (misreading this contract silenced a real dialog for 10 hours): (1) A normal command waits synchronously for at most 120 seconds; if it ends inside that window the call IS finished \u2014 a result containing exitCode means the command has already completed, never "still running"; parse it immediately (no later event will remind you). (2) Only a result with metadata.detached === true AND a taskId represents a tracked background task that may trigger completion wake-up (terminal completion turn carrying a bounded result capsule); that handle may be waited on with taskWait and will produce a completion notification (a launchProcess taskId only yields a notice on your next real turn, never a new turn). (3) Text the command itself prints (LAUNCHED, STARTED, BACKGROUND, ...) is plain stdout with zero lifecycle meaning \u2014 never read it as a platform receipt. (4) Shell-level backgrounding (\`&\`, nohup, disown, setsid, self-daemonizing scripts, PowerShell Start-Process) is NOT tracked: those descendants produce no completion notification, and the outer shell finishing tells you nothing about them. Use background: true to turn a finite job into a tracked background task immediately. When a synchronous result's core output was redirected to a file, read that file in the SAME turn ("read it later" means it is lost) \u2014 never end the turn promising a later automatic report without a taskId.`,
15329
+ parameters: {
15330
+ type: "object",
15331
+ properties: {
15332
+ command: {
15333
+ type: "string",
15334
+ description: "Shell command to run (non-empty)."
15335
+ },
15336
+ cmd: {
15337
+ type: "string",
15338
+ description: "Compatibility alias for command."
15339
+ },
15340
+ background: {
15341
+ type: "boolean",
15342
+ description: "Immediately detach this command into a tracked background task (no 120s synchronous wait): returns {detached: true, taskId}, and on completion the platform resumes this conversation with a bounded result capsule (exitCode, duration, stdout/stderr tail). Default false. Use for finite jobs (tests, builds, batch) whose result you need later; NOT for dev servers/watchers \u2014 use launchProcess for those."
15343
+ }
15344
+ }
15345
+ }
15346
+ }
15347
+ };
15348
+ }
15349
+ function normalizeExecShellInput(input) {
15350
+ if (input === null || input === void 0) {
15351
+ throw new Error("execShell requires a non-empty command.");
15352
+ }
15353
+ if (typeof input === "string") {
15354
+ const trimmed = input.trim();
15355
+ if (!trimmed) {
15356
+ throw new Error("execShell requires a non-empty command.");
15357
+ }
15358
+ if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
15359
+ try {
15360
+ const parsed = JSON.parse(trimmed);
15361
+ if (parsed && typeof parsed === "object") {
15362
+ return normalizeExecShellInput(parsed);
15363
+ }
15364
+ } catch {
15365
+ }
15366
+ }
15367
+ return { command: trimmed };
15368
+ }
15369
+ if (typeof input === "object") {
15370
+ const record2 = input;
15371
+ let rawCommand = resolveShellCommandArg(record2);
15372
+ if (typeof rawCommand !== "string" || !rawCommand.trim()) {
15373
+ const fallback = record2.command ?? record2.cmd;
15374
+ if (typeof fallback === "string" && fallback.trim()) {
15375
+ rawCommand = fallback;
15376
+ }
15377
+ }
15378
+ const command = typeof rawCommand === "string" ? rawCommand.trim() : "";
15379
+ if (!command) {
15380
+ throw new Error("execShell requires a non-empty command.");
15381
+ }
15382
+ const activity = extractActivity(record2);
15383
+ const background = record2.background === true || typeof record2.background === "string" && record2.background.trim().toLowerCase() === "true";
15384
+ const login = typeof record2.login === "boolean" ? record2.login : typeof record2.login === "string" ? record2.login.trim().toLowerCase() === "true" : void 0;
15385
+ return {
15386
+ command,
15387
+ ...record2.shell !== void 0 ? { shell: record2.shell } : {},
15388
+ ...login !== void 0 ? { login } : {},
15389
+ ...activity ? { activity } : {},
15390
+ ...record2.input !== void 0 ? { input: record2.input } : {},
15391
+ ...background ? { background: true } : {}
15392
+ };
15393
+ }
15394
+ throw new Error("execShell requires a non-empty command.");
15395
+ }
15396
+ function execShellExecutionBinding(input, ctx) {
15397
+ return JSON.stringify({
15398
+ input,
15399
+ workspaceRoot: ctx.workspaceRoot || process.cwd(),
15400
+ commandTimeoutMs: ctx.commandTimeoutMs,
15401
+ commandOutputLimit: ctx.commandOutputLimit,
15402
+ commandPrefix: ctx.commandPrefix,
15403
+ restrictToWorkspace: ctx.restrictToWorkspace,
15404
+ detachMs: ctx.detachMs,
15405
+ owner: ctx.owner,
15406
+ confirmed: ctx.confirmed,
15407
+ enableDestructiveShellGuard: ctx.enableDestructiveShellGuard,
15408
+ blockDestructiveWithoutConfirmation: ctx.blockDestructiveWithoutConfirmation,
15409
+ envDigest: createHash3("sha256").update(JSON.stringify(process.env)).digest("hex")
15410
+ });
15411
+ }
15412
+ var execShellCapability = {
15413
+ name: "execShell",
15414
+ getToolDefinition(toolName = "execShell") {
15415
+ return buildExecShellToolDefinition(toolName);
15416
+ },
15417
+ normalizeInput(input) {
15418
+ return normalizeExecShellInput(input);
15419
+ },
15420
+ async invoke(ctx, normalized) {
15421
+ const workspaceRoot = ctx.workspaceRoot || process.cwd();
15422
+ const command = normalized.command;
15423
+ if (ctx.restrictToWorkspace) {
15424
+ const escapeToken = findWorkspaceShellEscapeToken(command);
15425
+ if (escapeToken) {
15426
+ return buildWorkspaceShellEscapeBlockedResult({ command, token: escapeToken });
15427
+ }
15428
+ }
15429
+ const interactiveAuthCommand = extractInteractiveGhAuthCommand(command);
15430
+ if (interactiveAuthCommand) {
15431
+ const blocked = buildInteractiveCommandBlockedResult(interactiveAuthCommand);
15432
+ return {
15433
+ ...blocked,
15434
+ metadata: {
15435
+ ...blocked.metadata,
15436
+ ...normalized.activity ? { activity: normalized.activity } : {}
15437
+ }
15438
+ };
15439
+ }
15440
+ const shellPlan = buildWorkspaceShellPlan({
15441
+ toolName: "execShell",
15442
+ command,
15443
+ shell: normalized.shell,
15444
+ ...typeof normalized.login === "boolean" ? { login: normalized.login } : {}
15445
+ });
15446
+ const background = normalized.background === true;
15447
+ const executionArgs = {
15448
+ workspaceRoot,
15449
+ command: shellPlan.argv,
15450
+ timeoutMs: resolveExecShellTimeoutMs(ctx.commandTimeoutMs),
15451
+ outputLimit: ctx.commandOutputLimit,
15452
+ commandPrefix: ctx.commandPrefix,
15453
+ abortSignal: ctx.abortSignal,
15454
+ detachMs: background ? 0 : isImmediateDetachShellCommand({ command }) ? 0 : ctx.detachMs,
15455
+ stdin: typeof normalized.input === "string" ? normalized.input : void 0,
15456
+ // Ownership of the turn that invoked this command: an auto-detached
15457
+ // command keeps its parent dialog so its terminal can resume that
15458
+ // conversation (see processOwnership.ts). Absent ctx.owner = unowned.
15459
+ owner: ctx.owner ?? null
15460
+ };
15461
+ const authority = ctx.execShellApproval;
15462
+ if (authority) {
15463
+ const binding = execShellExecutionBinding(normalized, ctx);
15464
+ const payload = JSON.stringify({ binding, executionArgs });
15465
+ const signal = ctx.abortSignal;
15466
+ const consume = authority.consume;
15467
+ const ticket = await authority.request(payload);
15468
+ if (ctx.execShellApproval !== authority || ctx.abortSignal !== signal || signal?.aborted || execShellExecutionBinding(normalized, ctx) !== binding || JSON.stringify({ binding, executionArgs }) !== payload) {
15469
+ throw new Error("Shell approval final input changed");
15470
+ }
15471
+ if (authority.consume !== consume) throw new Error("Shell approval authority changed");
15472
+ consume.call(authority, ticket, payload);
15473
+ }
15474
+ const result4 = await runWorkspaceCommand(executionArgs);
15475
+ const warnings = result4.detached || result4.spawnFailed ? [] : detectExecShellSynchronousWarnings(command, {
15476
+ resolvedShell: shellPlan.resolvedShell
15477
+ });
15478
+ return {
15479
+ content: warnings.length > 0 ? [result4.content, ...warnings.map((warning) => warning.message)].join("\n\n") : result4.content,
15480
+ metadata: {
15481
+ command,
15482
+ exitCode: result4.exitCode,
15483
+ timedOut: result4.timedOut,
15484
+ resolvedShell: shellPlan.resolvedShell,
15485
+ ...shellPlan.shellKind ? { shellKind: shellPlan.shellKind } : {},
15486
+ ...result4.aborted ? { aborted: true } : {},
15487
+ ...result4.detached ? {
15488
+ detached: true,
15489
+ pid: result4.pid,
15490
+ label: result4.label,
15491
+ // Phase 0: stable taskId from the pre-registered Execution
15492
+ // Envelope (additive; existing detached fields untouched).
15493
+ taskId: result4.taskId,
15494
+ status: "running",
15495
+ // P0 machine-readable execution state: an explicit positive
15496
+ // signal instead of requiring the model to infer "not detached"
15497
+ // from absence. Mirrors the real lifecycle contract.
15498
+ executionState: "detached",
15499
+ trackedBackgroundTask: true,
15500
+ completionNotification: "terminal-resume"
15501
+ } : {
15502
+ // P0 machine-readable execution state: the command already
15503
+ // finished inside the synchronous window; no background task
15504
+ // exists, so no completion notification will ever arrive.
15505
+ executionState: "synchronous-complete",
15506
+ trackedBackgroundTask: false,
15507
+ completionNotification: "none"
15508
+ },
15509
+ ...normalized.activity ? { activity: normalized.activity } : {}
15510
+ }
15511
+ };
15512
+ }
15513
+ };
15514
+
15515
+ // packages/agent-runtime/capabilities/capabilitySdk.ts
15516
+ var BUILTIN_CAPABILITIES = [
15517
+ execShellCapability
15518
+ ];
15519
+ async function invokeCapability(capabilityOrName, input, ctx = {}) {
15520
+ const capability = typeof capabilityOrName === "string" ? BUILTIN_CAPABILITIES.find((c) => c.name === capabilityOrName) : capabilityOrName;
15521
+ if (!capability) {
15522
+ throw new Error(`Capability "${String(capabilityOrName)}" not found.`);
15523
+ }
15524
+ const normalizedInput = capability.normalizeInput(input);
15525
+ const authority = ctx.execShellApproval;
15526
+ const binding = authority && capability.name === "execShell" ? execShellExecutionBinding(normalizedInput, ctx) : void 0;
15527
+ const signal = ctx.abortSignal;
15528
+ const confirm = ctx.confirmDestructiveAction;
15529
+ await evaluateCapabilityPolicy(capability, normalizedInput, ctx);
15530
+ if (ctx.onInvoke) {
15531
+ await ctx.onInvoke(capability.name, normalizedInput);
15532
+ }
15533
+ if (binding !== void 0 && (ctx.execShellApproval !== authority || ctx.abortSignal !== signal || ctx.confirmDestructiveAction !== confirm || signal?.aborted || execShellExecutionBinding(normalizedInput, ctx) !== binding)) {
15534
+ throw new Error("Shell approval input changed during policy/hook");
15535
+ }
15536
+ return capability.invoke(ctx, normalizedInput);
15537
+ }
15538
+
15539
+ // packages/agent-runtime/localWorkspaceTools.ts
15540
+ import { existsSync as existsSync3 } from "node:fs";
15541
+ import { createHash as createHash4 } from "node:crypto";
15542
+ import { lstat, mkdir, readdir as readdir2, readFile as readFile3, realpath, stat as stat3, writeFile } from "node:fs/promises";
15543
+ import { homedir } from "node:os";
15544
+ import { dirname, isAbsolute as isAbsolute2, relative as relative3, resolve as resolve4, sep } from "node:path";
15545
+ import { spawn as spawnChildProcess3 } from "node:child_process";
15546
+
15547
+ // packages/core/readRangeLedger.ts
15548
+ function filterRetainedLedgerRecords(records, retainedCap) {
15549
+ return records.filter((record2) => record2.chars <= retainedCap);
15550
+ }
15551
+ function isValidRange(range3) {
15552
+ return Number.isInteger(range3.startLine) && Number.isInteger(range3.endLine) && range3.endLine >= range3.startLine;
15553
+ }
15554
+ function mergeLineRanges(ranges) {
15555
+ const sorted = ranges.filter(isValidRange).slice().sort((a, b) => a.startLine - b.startLine);
15556
+ const merged = [];
15557
+ for (const range3 of sorted) {
15558
+ const last = merged[merged.length - 1];
15559
+ if (last && range3.startLine <= last.endLine + 1) {
15560
+ last.endLine = Math.max(last.endLine, range3.endLine);
15561
+ } else {
15562
+ merged.push({ startLine: range3.startLine, endLine: range3.endLine });
15563
+ }
15564
+ }
15565
+ return merged;
15566
+ }
15567
+ function isLineRangeCovered(request3, covered) {
15568
+ if (!isValidRange(request3)) return false;
15569
+ let open = request3.startLine;
15570
+ for (const range3 of mergeLineRanges(covered)) {
15571
+ if (range3.endLine < open) continue;
15572
+ if (range3.startLine > open) return false;
15573
+ open = range3.endLine + 1;
15574
+ if (open > request3.endLine) return true;
15575
+ }
15576
+ return open > request3.endLine;
15577
+ }
15578
+
15579
+ // packages/agent-runtime/fileTextSearch.ts
15580
+ import { readdir, readFile as readFile2, stat as stat2 } from "node:fs/promises";
15581
+ import { basename, relative as relative2, resolve as resolve3 } from "node:path";
15582
+ import { spawn as spawnChildProcess2 } from "node:child_process";
15583
+
15584
+ // packages/agent-runtime/gitignore.ts
15585
+ import { readFile, stat } from "node:fs/promises";
15586
+ import { resolve as resolve2 } from "node:path";
15587
+ async function readRootGitignorePatterns(workspaceRoot) {
15588
+ const content = await readFile(resolve2(workspaceRoot, ".gitignore"), "utf8").catch(() => null);
15589
+ if (content === null) return [];
15590
+ return content.split(/\r?\n/).map((line) => line.trim()).filter((line) => line && !line.startsWith("#") && !line.startsWith("!")).flatMap((line) => {
15591
+ const unanchored = line.replace(/^\//, "");
15592
+ if (unanchored.endsWith("/")) return [`${unanchored}**`];
15593
+ if (unanchored.includes("**")) return [unanchored];
15594
+ return [unanchored, `${unanchored}/**`];
15595
+ });
15596
+ }
15597
+ async function hasRootGitignore(workspaceRoot) {
15598
+ try {
15599
+ await stat(resolve2(workspaceRoot, ".gitignore"));
15600
+ return true;
15601
+ } catch {
15602
+ return false;
15603
+ }
15604
+ }
15605
+
15606
+ // packages/agent-runtime/fileTextSearch.ts
15607
+ var normalizePath = (value) => value.replaceAll("\\", "/");
15608
+ var pathFor = (root, file, absolute = false) => absolute ? normalizePath(file) : normalizePath(relative2(root, file) || basename(file));
15609
+ var COMMAND_LIMIT_HEADROOM = 1;
15610
+ async function runCommand(binary, args2, cwd, maxResults) {
15611
+ const recordLimit = maxResults === void 0 ? 0 : Math.max(0, maxResults) + COMMAND_LIMIT_HEADROOM;
15612
+ return await new Promise((done4) => {
15613
+ let stdout = "", stderr = "", recordCount = 0, stopped = false;
15614
+ try {
15615
+ const child = spawnChildProcess2(binary, args2, { cwd, stdio: ["ignore", "pipe", "pipe"] });
15616
+ child.stdout.on("data", (chunk) => {
15617
+ stdout += chunk.toString();
15618
+ recordCount = parseMatches(cwd, stdout).length;
15619
+ if (recordLimit > 0 && recordCount >= recordLimit && !stopped) {
15620
+ stopped = true;
15621
+ child.kill();
15622
+ }
15623
+ });
15624
+ child.stderr.on("data", (chunk) => {
15625
+ stderr += chunk.toString();
15626
+ });
15627
+ child.on("error", () => done4({ stdout, stderr, outcome: { kind: "spawn-failed" } }));
15628
+ child.on("close", (exitCode) => done4({ stdout, stderr, outcome: stopped ? { kind: "stopped-by-limit" } : { kind: "completed", exitCode } }));
15629
+ } catch {
15630
+ done4({ stdout, stderr, outcome: { kind: "spawn-failed" } });
15631
+ }
15632
+ });
15633
+ }
15634
+ function commandArgs(o, binary, target) {
15635
+ const args2 = binary === "rg" ? ["--line-number", "--no-heading", "--color", "never", "--null"] : ["-RIn", "--binary-files=without-match", "--null"];
15636
+ if (binary === "rg") args2.push("--no-require-git");
15637
+ if (o.regex !== true) args2.push(binary === "rg" ? "--fixed-strings" : "-F");
15638
+ else if (binary === "grep") args2.push("-E");
15639
+ if (o.caseSensitive === false) args2.push("-i");
15640
+ if (binary === "rg") {
15641
+ if (o.includeIgnored) args2.push("--no-ignore");
15642
+ for (const x of o.exclude ?? []) args2.push("--glob", `!${x}`);
15643
+ if (o.include) args2.push("--glob", o.include);
15644
+ } else {
15645
+ for (const x of o.exclude ?? []) {
15646
+ const clean = x.replace(/\/\*\*$/, "");
15647
+ if (clean && !clean.includes("/")) args2.push("--exclude-dir", clean);
15648
+ }
15649
+ if (o.include) args2.push("--include", o.include);
15650
+ }
15651
+ args2.push("--", o.query, target);
15652
+ return args2;
15653
+ }
15654
+ function parseMatches(root, output) {
15655
+ const records = [];
15656
+ const pattern = /(?:^|\n)([^\0\n]+)\0(\d+):(.*?)(?=\n[^\0\n]+\0|$)/gs;
15657
+ for (const match7 of output.matchAll(pattern)) {
15658
+ records.push({ file: resolve3(root, match7[1] ?? ""), line: Number(match7[2]), text: (match7[3] ?? "").replace(/\n$/, "") });
15659
+ }
15660
+ return records;
15661
+ }
15662
+ async function decorate(root, raw, context3, absolute = false) {
15663
+ const cache = /* @__PURE__ */ new Map(), out = [];
15664
+ for (const x of raw) {
15665
+ let lines = cache.get(x.file);
15666
+ if (!lines) {
15667
+ try {
15668
+ lines = (await readFile2(x.file, "utf8")).split(/\r\n|\r|\n/);
15669
+ } catch {
15670
+ continue;
15671
+ }
15672
+ cache.set(x.file, lines);
15673
+ }
15674
+ const i = x.line - 1;
15675
+ out.push({ path: pathFor(root, x.file, absolute), line: x.line, text: lines[i] ?? x.text, before: lines.slice(Math.max(0, i - context3), i), after: lines.slice(i + 1, i + context3 + 1) });
15676
+ }
15677
+ return out;
15678
+ }
15679
+ function globMatch(pattern, value) {
15680
+ const p = normalizePath(pattern).replace(/^!/, "");
15681
+ let source = "";
15682
+ for (let i = 0; i < p.length; i++) {
15683
+ const c = p[i];
15684
+ if (c === "*" && p[i + 1] === "*") {
15685
+ i++;
15686
+ if (p[i + 1] === "/") {
15687
+ i++;
15688
+ source += "(?:.*/)?";
15689
+ } else source += ".*";
15690
+ } else if (c === "*") source += "[^/]*";
15691
+ else if (c === "?") source += "[^/]";
15692
+ else source += c.replace(/[.+^${}()|[\]\\]/g, "\\$&");
15693
+ }
15694
+ const r = new RegExp(`^${source}$`);
15695
+ return r.test(value) || r.test(basename(value));
15696
+ }
15697
+ var defaultReadTextFile = (filePath) => readFile2(filePath, "utf8").catch(() => "");
15698
+ async function jsSearch(o, targetPath) {
15699
+ const max4 = o.maxResults ?? Number.MAX_SAFE_INTEGER;
15700
+ const excludes = o.exclude ?? [];
15701
+ const readText = o.readTextFile ?? defaultReadTextFile;
15702
+ const files = [];
15703
+ const targetStat = await stat2(targetPath).catch(() => null);
15704
+ if (!targetStat) return { matches: [], totalMatches: 0, truncated: false, engine: "js" };
15705
+ const ignorePatterns = o.includeIgnored ? [] : await readRootGitignorePatterns(o.root);
15706
+ const isIgnored = (rel) => ignorePatterns.some((pattern) => globMatch(pattern, rel));
15707
+ const isSearchableFile = (filePath) => {
15708
+ const rel = pathFor(o.root, filePath);
15709
+ return !excludes.some((pattern) => globMatch(pattern, rel)) && !isIgnored(rel) && (!o.include || globMatch(o.include, rel));
15710
+ };
15711
+ if (targetStat.isFile()) {
15712
+ if (isSearchableFile(targetPath)) files.push(targetPath);
15713
+ } else {
15714
+ const walk = async (dir) => {
15715
+ for (const e of await readdir(dir, { withFileTypes: true })) {
15716
+ const f = resolve3(dir, e.name), rel = pathFor(o.root, f);
15717
+ if (e.isDirectory()) {
15718
+ if (!excludes.some((p) => globMatch(p, rel))) await walk(f);
15719
+ } else if (e.isFile() && !excludes.some((p) => globMatch(p, rel)) && !isIgnored(rel) && (!o.include || globMatch(o.include, rel))) {
15720
+ files.push(f);
15721
+ }
15722
+ }
15723
+ };
15724
+ await walk(targetPath);
15725
+ }
15726
+ const matcher = o.regex ? new RegExp(o.query, o.caseSensitive === false ? "i" : "") : null;
15727
+ const needle = o.caseSensitive === false ? o.query.toLowerCase() : o.query;
15728
+ const out = [], context3 = o.contextLines ?? 0;
15729
+ for (const file of files) {
15730
+ const text2 = await readText(file);
15731
+ if (!text2 || text2.includes("\0")) continue;
15732
+ const lines = text2.split(/\r\n|\r|\n/);
15733
+ for (let i = 0; i < lines.length; i++) {
15734
+ const line = lines[i] ?? "";
15735
+ const hit = matcher ? matcher.test(line) : o.caseSensitive === false ? line.toLowerCase().includes(needle) : line.includes(needle);
15736
+ if (!hit) continue;
15737
+ if (out.length >= max4) return { matches: out, totalMatches: max4, truncated: true, engine: "js" };
15738
+ out.push({ path: pathFor(o.root, file, o.absolutePaths === true), line: i + 1, text: line, before: lines.slice(Math.max(0, i - context3), i), after: lines.slice(i + 1, i + context3 + 1) });
15739
+ }
15740
+ }
15741
+ return { matches: out, totalMatches: out.length, truncated: false, engine: "js" };
15742
+ }
15743
+ var COMMAND_ENGINES = [
15744
+ { engine: "ripgrep", binaryOption: "resolveRipgrepBinary", binaryName: "rg", argsKey: "rg" },
15745
+ { engine: "grep", binaryOption: "resolveGrepBinary", binaryName: "grep", argsKey: "grep" }
15746
+ ];
15747
+ async function searchFileTextMatches(o) {
15748
+ const targetPath = resolve3(o.root, o.target || ".");
15749
+ const targetStat = await stat2(targetPath).catch(() => null);
15750
+ if (!targetStat) return { matches: [], totalMatches: 0, truncated: false, engine: "js" };
15751
+ if (targetStat.isFile()) return jsSearch(o, targetPath);
15752
+ const run2 = o.runCommand ?? runCommand, max4 = o.maxResults;
15753
+ for (const entry of COMMAND_ENGINES) {
15754
+ const injectedResolver = o[entry.binaryOption];
15755
+ const binary = injectedResolver ? injectedResolver() : resolveExecutableOnPath(entry.binaryName);
15756
+ if (!binary) continue;
15757
+ const result4 = await run2(binary, commandArgs(o, entry.argsKey, o.target || "."), o.root, max4);
15758
+ const raw = parseMatches(o.root, result4.stdout);
15759
+ const hardFailed = result4.outcome.kind === "spawn-failed" || result4.outcome.kind === "completed" && result4.outcome.exitCode !== 0 && result4.outcome.exitCode !== 1;
15760
+ if (hardFailed) continue;
15761
+ const truncated = raw.length > (max4 ?? Number.MAX_SAFE_INTEGER);
15762
+ const matches = await decorate(o.root, raw.slice(0, max4), o.contextLines ?? 0, o.absolutePaths === true);
15763
+ return {
15764
+ matches,
15765
+ totalMatches: truncated ? matches.length : raw.length,
15766
+ truncated,
15767
+ engine: entry.engine
15768
+ };
15769
+ }
15770
+ return jsSearch(o, targetPath);
15771
+ }
15772
+
15773
+ // packages/agent-runtime/processOwnership.ts
15774
+ var asNonEmptyString = (value) => {
15775
+ if (typeof value !== "string") return null;
15776
+ const trimmed = value.trim();
15777
+ return trimmed ? trimmed : null;
15778
+ };
15779
+ function readProcessOwner(runtimeContext) {
15780
+ if (!runtimeContext || typeof runtimeContext !== "object") return null;
15781
+ const dialogId = asNonEmptyString(runtimeContext.dialogId) ?? asNonEmptyString(runtimeContext.parentThreadId);
15782
+ if (!dialogId) return null;
15783
+ const turnId = asNonEmptyString(runtimeContext.turnId);
15784
+ const ownerId = asNonEmptyString(runtimeContext.ownerId);
15785
+ return {
15786
+ dialogId,
15787
+ ...turnId ? { turnId } : {},
15788
+ ...ownerId ? { ownerId } : {}
15429
15789
  };
15430
15790
  }
15431
15791
 
@@ -15434,20 +15794,34 @@ var TASK_WAIT_DEFAULT_TIMEOUT_MS = 5 * 6e4;
15434
15794
  var TASK_WAIT_MAX_TIMEOUT_MS = 5 * 6e4;
15435
15795
  var TASK_WAIT_POLL_INTERVAL_MS = 250;
15436
15796
  var TASK_STOP_GRACE_MS = 2e3;
15797
+ function resolveCleanupReport(evidence, previousStatus) {
15798
+ const diagnostic = evidence.diagnostic ? { diagnostic: evidence.diagnostic } : {};
15799
+ if (evidence.confirmedStopped) {
15800
+ return { status: "stopped", pending: false };
15801
+ }
15802
+ if (evidence.alreadyTerminal) {
15803
+ const status = previousStatus ?? "stopped";
15804
+ return { status, pending: status === "stop-unverified", idempotent: true, ...diagnostic };
15805
+ }
15806
+ if (evidence.ttlExpired) {
15807
+ return { status: "expired", pending: false, ...diagnostic };
15808
+ }
15809
+ return { status: "stop-unverified", pending: true, ...diagnostic };
15810
+ }
15437
15811
  var TERMINAL_EVENT_TYPES = /* @__PURE__ */ new Set(["exited", "killed"]);
15438
15812
  function defaultSleep(ms) {
15439
- return new Promise((resolve5) => setTimeout(resolve5, ms));
15813
+ return new Promise((resolve6) => setTimeout(resolve6, ms));
15440
15814
  }
15441
15815
  function defaultSignalSender(pid, signal) {
15442
15816
  process.kill(pid, signal);
15443
15817
  }
15444
- function statusOf(record) {
15445
- return reportedTaskStatus(record.status, record.exitCode);
15818
+ function statusOf(record2) {
15819
+ return reportedTaskStatus(record2.status, record2.exitCode);
15446
15820
  }
15447
15821
  function inspectTask(registry2, taskId) {
15448
- const record = registry2.getByTaskId(taskId);
15822
+ const record2 = registry2.getByTaskId(taskId);
15449
15823
  const hasEvents = registry2.hasTaskEvents(taskId);
15450
- return { record, hasEvents, known: hasEvents || record !== void 0 };
15824
+ return { record: record2, hasEvents, known: hasEvents || record2 !== void 0 };
15451
15825
  }
15452
15826
  function notFoundDetail(taskId) {
15453
15827
  return `No task ${taskId} is known: it was never started, or both its envelope and its event trail have been dropped. Call tasks to list current handles.`;
@@ -15489,13 +15863,13 @@ async function runTaskWait(args2) {
15489
15863
  for (let poll2 = 0; poll2 < maxPolls; poll2 += 1) {
15490
15864
  const events = registry2.getTaskEvents(taskId, 0);
15491
15865
  if (!registry2.hasTaskEvents(taskId)) {
15492
- const record = registry2.getByTaskId(taskId);
15866
+ const record2 = registry2.getByTaskId(taskId);
15493
15867
  return {
15494
15868
  outcome: "evicted",
15495
15869
  taskId,
15496
15870
  cursor: 0,
15497
15871
  events: [],
15498
- ...record ? { status: statusOf(record) } : {},
15872
+ ...record2 ? { status: statusOf(record2) } : {},
15499
15873
  detail: evictedDetail(taskId)
15500
15874
  };
15501
15875
  }
@@ -15540,7 +15914,7 @@ function runTaskLogs(args2) {
15540
15914
  const { registry: registry2, taskId } = args2;
15541
15915
  const cursorRaw = typeof args2.cursor === "number" ? args2.cursor : typeof args2.cursor === "string" && args2.cursor.trim() !== "" ? Number(args2.cursor) : Number.NaN;
15542
15916
  const cursor = Number.isFinite(cursorRaw) && cursorRaw > 0 ? Math.floor(cursorRaw) : 0;
15543
- const { record, hasEvents, known } = inspectTask(registry2, taskId);
15917
+ const { record: record2, hasEvents, known } = inspectTask(registry2, taskId);
15544
15918
  if (!known) {
15545
15919
  return { outcome: "not-found", taskId, detail: notFoundDetail(taskId) };
15546
15920
  }
@@ -15550,14 +15924,14 @@ function runTaskLogs(args2) {
15550
15924
  taskId,
15551
15925
  cursor: 0,
15552
15926
  events: [],
15553
- ...record ? { status: statusOf(record) } : {},
15927
+ ...record2 ? { status: statusOf(record2) } : {},
15554
15928
  detail: evictedDetail(taskId)
15555
15929
  };
15556
15930
  }
15557
15931
  const events = registry2.getTaskEvents(taskId, cursor);
15558
15932
  const allEvents = registry2.getTaskEvents(taskId, 0);
15559
15933
  const terminal = lastTerminalEvent(allEvents);
15560
- const status = terminal ? reportedTaskStatus(terminal.type, terminal.exitCode) : record ? statusOf(record) : "running";
15934
+ const status = terminal ? reportedTaskStatus(terminal.type, terminal.exitCode) : record2 ? statusOf(record2) : "running";
15561
15935
  return {
15562
15936
  outcome: "ok",
15563
15937
  taskId,
@@ -15576,410 +15950,106 @@ function formatTaskLogsContent(result4, options) {
15576
15950
  const spill = spillToolOutput({
15577
15951
  content: full,
15578
15952
  toolName: "taskLogs",
15579
- workspaceRoot: options?.workspaceRoot
15580
- });
15581
- const omitted = result4.outcome === "ok" ? result4.events.length : 0;
15582
- const head3 = JSON.stringify({
15583
- ...result4,
15584
- events: [],
15585
- eventsOmitted: omitted,
15586
- logRef: spill.displayPath
15587
- });
15588
- return {
15589
- content: `${head3}${formatToolOverflowMarker({
15590
- spillRef: spill.displayPath,
15591
- totalChars: spill.totalChars,
15592
- totalLines: spill.totalLines,
15593
- omittedChars: Math.max(0, full.length - head3.length),
15594
- toolName: "taskLogs"
15595
- })}`.trimEnd(),
15596
- logRef: spill.displayPath
15597
- };
15598
- } catch {
15599
- return { content: truncateToolOutput(full, effectiveLimit) };
15600
- }
15601
- }
15602
- function isProcessGroupAlive(pgid, kill = defaultSignalSender) {
15603
- try {
15604
- kill(-pgid, 0);
15605
- return true;
15606
- } catch (error) {
15607
- return error.code === "EPERM";
15608
- }
15609
- }
15610
- async function runTaskStop(args2) {
15611
- const { registry: registry2, taskId } = args2;
15612
- const sleep3 = args2.sleep ?? defaultSleep;
15613
- const kill = args2.kill ?? defaultSignalSender;
15614
- const graceMs = Math.max(0, args2.graceMs ?? TASK_STOP_GRACE_MS);
15615
- const record = registry2.getByTaskId(taskId);
15616
- if (!record) {
15617
- return { outcome: "not-found", taskId, detail: notFoundDetail(taskId) };
15618
- }
15619
- const termed = registry2.kill(record.pid, "SIGTERM");
15620
- if (!termed) {
15621
- const reason = record.transient ? "transient-foreground" : record.status !== "running" ? "already-terminal" : "kill-refused";
15622
- const detail = reason === "transient-foreground" ? `${taskId} is a foreground command still inside its grace window, not a background task; its lifecycle belongs to the running execShell call.` : reason === "already-terminal" ? `${taskId} is already ${statusOf(record)}; nothing to stop.` : `Registry refused to stop ${taskId}.`;
15623
- return {
15624
- outcome: "not-stoppable",
15625
- taskId,
15626
- pid: record.pid,
15627
- status: statusOf(record),
15628
- reason,
15629
- detail
15630
- };
15631
- }
15632
- if (graceMs > 0) await sleep3(graceMs);
15633
- if (!isProcessGroupAlive(record.pgid, kill)) {
15634
- return {
15635
- outcome: "stopped",
15636
- taskId,
15637
- pid: record.pid,
15638
- status: reportedTaskStatus("killed"),
15639
- signal: "SIGTERM",
15640
- escalated: false
15641
- };
15642
- }
15643
- try {
15644
- kill(-record.pgid, "SIGKILL");
15645
- } catch {
15646
- }
15647
- return {
15648
- outcome: "stopped",
15649
- taskId,
15650
- pid: record.pid,
15651
- status: reportedTaskStatus("killed"),
15652
- signal: "SIGKILL",
15653
- escalated: true
15654
- };
15655
- }
15656
- function runTasks(args2) {
15657
- const tasks = args2.registry.listBackground().map((record) => ({
15658
- taskId: record.taskId,
15659
- pid: record.pid,
15660
- label: record.label,
15661
- status: statusOf(record),
15662
- startedAt: record.startedAt,
15663
- persist: record.persist
15664
- }));
15665
- return { tasks, count: tasks.length };
15666
- }
15667
-
15668
- // packages/agent-runtime/execShellCompletionWarnings.ts
15669
- var BG_ESCAPE_WORD_PATTERN = /(?:^|[\s;|(&/\\])(nohup|disown|setsid|start-process)(?=$|[\s;|)&])/i;
15670
- var SHELL_SPLIT_CHARS = /* @__PURE__ */ new Set([";", "|", "&", "(", ")", "<", ">", "\n", " ", " "]);
15671
- function maskQuotedAndEscaped(command) {
15672
- const out = Array.from(command);
15673
- let quote = null;
15674
- for (let i = 0; i < out.length; i += 1) {
15675
- const ch = out[i];
15676
- if (quote) {
15677
- out[i] = " ";
15678
- if (ch === "\\" && quote !== "'") {
15679
- if (i + 1 < out.length) out[i + 1] = " ";
15680
- i += 1;
15681
- } else if (ch === quote) {
15682
- quote = null;
15683
- }
15684
- continue;
15685
- }
15686
- if (ch === "\\") {
15687
- out[i] = " ";
15688
- if (i + 1 < out.length) out[i + 1] = " ";
15689
- i += 1;
15690
- continue;
15691
- }
15692
- if (ch === "'" || ch === '"' || ch === "`") {
15693
- quote = ch;
15694
- out[i] = " ";
15695
- continue;
15696
- }
15697
- if (ch === "#" && (i === 0 || /[\s;]/.test(out[i - 1] ?? " "))) {
15698
- while (i < out.length && out[i] !== "\n") {
15699
- out[i] = " ";
15700
- i += 1;
15701
- }
15702
- }
15703
- }
15704
- return out.join("");
15705
- }
15706
- function readRedirectTarget(original, start) {
15707
- let i = start;
15708
- while (i < original.length && /\s/.test(original[i] ?? "")) {
15709
- i += 1;
15710
- }
15711
- if (i >= original.length) return void 0;
15712
- let text = "";
15713
- let j = i;
15714
- while (j < original.length) {
15715
- const o = original[j];
15716
- if (o === "'" || o === '"' || o === "`") {
15717
- const quote = o;
15718
- j += 1;
15719
- while (j < original.length && original[j] !== quote) {
15720
- if (original[j] === "\\" && quote !== "'" && j + 1 < original.length) {
15721
- text += original[j + 1];
15722
- j += 2;
15723
- continue;
15724
- }
15725
- text += original[j];
15726
- j += 1;
15727
- }
15728
- j += 1;
15729
- continue;
15730
- }
15731
- if (/\s/.test(o) || SHELL_SPLIT_CHARS.has(o)) break;
15732
- text += o;
15733
- j += 1;
15734
- }
15735
- if (!text || text === "$") return void 0;
15736
- return { text, end: j };
15737
- }
15738
- function isIgnoredRedirectTarget(target) {
15739
- return target === "/dev/null";
15740
- }
15741
- function detectExecShellSynchronousWarnings(command, shell) {
15742
- const masked = maskQuotedAndEscaped(command);
15743
- const isPowerShell = shell?.resolvedShell === "powershell";
15744
- const redirectTargets = [];
15745
- let backgrounded = false;
15746
- let i = 0;
15747
- while (i < masked.length) {
15748
- const ch = masked[i];
15749
- if (ch === "&") {
15750
- const next = masked[i + 1];
15751
- if (next === "&") {
15752
- i += 2;
15753
- continue;
15754
- }
15755
- if (next === ">") {
15756
- let j = i + 2;
15757
- if (masked[j] === ">") j += 1;
15758
- const target = readRedirectTarget(command, j);
15759
- if (target && !isIgnoredRedirectTarget(target.text)) redirectTargets.push(target.text);
15760
- i = target ? target.end : j;
15761
- continue;
15762
- }
15763
- const prev = masked[i - 1];
15764
- if (prev === ">" || prev === "<") {
15765
- i += 1;
15766
- continue;
15767
- }
15768
- if (isPowerShell) {
15769
- i += 1;
15770
- continue;
15771
- }
15772
- backgrounded = true;
15773
- i += 1;
15774
- continue;
15775
- }
15776
- if (ch === ">") {
15777
- const prev = masked[i - 1];
15778
- if (prev === ">" || prev === "&" || prev === "<") {
15779
- i += 1;
15780
- continue;
15781
- }
15782
- let j = i + 1;
15783
- if (masked[j] === ">") j += 1;
15784
- if (masked[j] === "&") {
15785
- const ampTarget = readRedirectTarget(command, j + 1);
15786
- const isFdDuplication = ampTarget !== void 0 && (/^\d+$/.test(ampTarget.text) || ampTarget.text === "-");
15787
- if (ampTarget && !isFdDuplication && !isIgnoredRedirectTarget(ampTarget.text)) {
15788
- redirectTargets.push(ampTarget.text);
15789
- }
15790
- i = ampTarget ? ampTarget.end : j + 1;
15791
- continue;
15792
- }
15793
- const target = readRedirectTarget(command, j);
15794
- if (target && !isIgnoredRedirectTarget(target.text)) redirectTargets.push(target.text);
15795
- i = target ? target.end : j;
15796
- continue;
15797
- }
15798
- if (ch === "<") {
15799
- i += masked[i + 1] === "<" || masked[i + 1] === "&" ? 2 : 1;
15800
- continue;
15801
- }
15802
- i += 1;
15803
- }
15804
- const wordMatch = BG_ESCAPE_WORD_PATTERN.exec(masked);
15805
- if (wordMatch) backgrounded = true;
15806
- const warnings = [];
15807
- if (redirectTargets.length > 0) {
15808
- const target = redirectTargets[0];
15809
- warnings.push({
15810
- kind: "output-redirect",
15811
- redirectTarget: target,
15812
- message: `[wake-safety] This execShell call completed synchronously; its output was redirected to ${target}. That file is NOT delivered by any completion notification \u2014 no tracked background task was created. If you need the result, read that file in this turn.`
15813
- });
15814
- }
15815
- if (backgrounded) {
15816
- warnings.push({
15817
- kind: "shell-backgrounding",
15818
- message: `[wake-safety] Detected shell-internal backgrounding (lone \`&\`, nohup/disown/setsid/Start-Process or similar). Descendant processes are NOT tracked as platform tasks and will not produce completion notifications; text such as \`LAUNCHED\` printed by the command is plain stdout, not a platform receipt.`
15953
+ workspaceRoot: options?.workspaceRoot
15954
+ });
15955
+ const omitted = result4.outcome === "ok" ? result4.events.length : 0;
15956
+ const head3 = JSON.stringify({
15957
+ ...result4,
15958
+ events: [],
15959
+ eventsOmitted: omitted,
15960
+ logRef: spill.displayPath
15819
15961
  });
15962
+ return {
15963
+ content: `${head3}${formatToolOverflowMarker({
15964
+ spillRef: spill.displayPath,
15965
+ totalChars: spill.totalChars,
15966
+ totalLines: spill.totalLines,
15967
+ omittedChars: Math.max(0, full.length - head3.length),
15968
+ toolName: "taskLogs"
15969
+ })}`.trimEnd(),
15970
+ logRef: spill.displayPath
15971
+ };
15972
+ } catch {
15973
+ return { content: truncateToolOutput(full, effectiveLimit) };
15820
15974
  }
15821
- return warnings;
15822
- }
15823
-
15824
- // packages/agent-runtime/capabilities/execShellCapability.ts
15825
- function buildExecShellToolDefinition(toolName = "execShell") {
15826
- return {
15827
- type: "function",
15828
- function: {
15829
- name: toolName,
15830
- description: `Execute a shell command from the workspace root. Prefer one compound command (e.g. 'git status && git diff --stat') to perform complete verification in one step instead of multiple small roundtrips. Do not cd into guessed paths; commands already run from the workspace root. Commands block until exit. Long-running commands (sleep over ${IMMEDIATE_DETACH_SLEEP_THRESHOLD_SECONDS}s, dev servers, watchers) automatically detach to background returning {detached: true, pid, label}; for persistent services, prefer launchProcess. On Windows the resolved shell may be Windows PowerShell 5.1 (metadata.shellKind="powershell5"): it does not support '&&'/'||' or $PSStyle \u2014 join commands with ';' or split into multiple calls. metadata.shellKind="pwsh" means PowerShell 7+ with full syntax. Read resolvedShell/shellKind from any execShell result before writing Windows-specific syntax. LIFECYCLE CONTRACT (misreading this contract silenced a real dialog for 10 hours): (1) A normal command waits synchronously for at most 120 seconds; if it ends inside that window the call IS finished \u2014 a result containing exitCode means the command has already completed, never "still running". (2) Only a result with metadata.detached === true AND a taskId represents a tracked background task that may trigger completion wake-up (terminal completion turn carrying a bounded result capsule); that handle may be waited on with taskWait and will produce a completion notification. (3) Text the command itself prints (LAUNCHED, STARTED, BACKGROUND, ...) is plain stdout with zero lifecycle meaning \u2014 never read it as a platform receipt. (4) Shell-level backgrounding (\`&\`, nohup, disown, setsid, self-daemonizing scripts, PowerShell Start-Process) is NOT tracked: those descendants produce no completion notification, and the outer shell finishing tells you nothing about them. Use background: true to turn a finite job into a tracked background task immediately. When a synchronous result's core output was redirected to a file, read that file in the SAME turn \u2014 never end the turn promising a later automatic report without a taskId.`,
15831
- parameters: {
15832
- type: "object",
15833
- properties: {
15834
- command: {
15835
- type: "string",
15836
- description: "Shell command to run (non-empty)."
15837
- },
15838
- cmd: {
15839
- type: "string",
15840
- description: "Compatibility alias for command."
15841
- },
15842
- background: {
15843
- type: "boolean",
15844
- description: "Immediately detach this command into a tracked background task (no 120s synchronous wait): returns {detached: true, taskId}, and on completion the platform resumes this conversation with a bounded result capsule (exitCode, duration, stdout/stderr tail). Default false. Use for finite jobs (tests, builds, batch) whose result you need later; NOT for dev servers/watchers \u2014 use launchProcess for those."
15845
- }
15846
- }
15847
- }
15848
- }
15849
- };
15850
15975
  }
15851
- function normalizeExecShellInput(input) {
15852
- if (input === null || input === void 0) {
15853
- throw new Error("execShell requires a non-empty command.");
15976
+ function isProcessGroupAlive(pgid, kill = defaultSignalSender) {
15977
+ try {
15978
+ kill(-pgid, 0);
15979
+ return true;
15980
+ } catch (error) {
15981
+ return error.code === "EPERM";
15854
15982
  }
15855
- if (typeof input === "string") {
15856
- const trimmed = input.trim();
15857
- if (!trimmed) {
15858
- throw new Error("execShell requires a non-empty command.");
15859
- }
15860
- if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
15861
- try {
15862
- const parsed = JSON.parse(trimmed);
15863
- if (parsed && typeof parsed === "object") {
15864
- return normalizeExecShellInput(parsed);
15865
- }
15866
- } catch {
15867
- }
15868
- }
15869
- return { command: trimmed };
15983
+ }
15984
+ async function runTaskStop(args2) {
15985
+ const { registry: registry2, taskId } = args2;
15986
+ const sleep3 = args2.sleep ?? defaultSleep;
15987
+ const kill = args2.kill ?? defaultSignalSender;
15988
+ const graceMs = Math.max(0, args2.graceMs ?? TASK_STOP_GRACE_MS);
15989
+ const record2 = registry2.getByTaskId(taskId);
15990
+ if (!record2) {
15991
+ return {
15992
+ outcome: "not-found",
15993
+ taskId,
15994
+ detail: notFoundDetail(taskId),
15995
+ cleanup: resolveCleanupReport({ confirmedStopped: false, diagnostic: "task-not-found" })
15996
+ };
15870
15997
  }
15871
- if (typeof input === "object") {
15872
- const record = input;
15873
- let rawCommand = resolveShellCommandArg(record);
15874
- if (typeof rawCommand !== "string" || !rawCommand.trim()) {
15875
- const fallback = record.command ?? record.cmd;
15876
- if (typeof fallback === "string" && fallback.trim()) {
15877
- rawCommand = fallback;
15878
- }
15879
- }
15880
- const command = typeof rawCommand === "string" ? rawCommand.trim() : "";
15881
- if (!command) {
15882
- throw new Error("execShell requires a non-empty command.");
15883
- }
15884
- const activity = extractActivity(record);
15885
- const background = record.background === true || typeof record.background === "string" && record.background.trim().toLowerCase() === "true";
15998
+ const termed = registry2.kill(record2.pid, "SIGTERM");
15999
+ if (!termed) {
16000
+ const reason = record2.transient ? "transient-foreground" : record2.status !== "running" ? "already-terminal" : "kill-refused";
16001
+ const detail = reason === "transient-foreground" ? `${taskId} is a foreground command still inside its grace window, not a background task; its lifecycle belongs to the running execShell call.` : reason === "already-terminal" ? `${taskId} is already ${statusOf(record2)}; nothing to stop.` : `Registry refused to stop ${taskId}.`;
15886
16002
  return {
15887
- command,
15888
- ...record.shell !== void 0 ? { shell: record.shell } : {},
15889
- ...activity ? { activity } : {},
15890
- ...record.input !== void 0 ? { input: record.input } : {},
15891
- ...background ? { background: true } : {}
16003
+ outcome: "not-stoppable",
16004
+ taskId,
16005
+ pid: record2.pid,
16006
+ status: statusOf(record2),
16007
+ reason,
16008
+ detail,
16009
+ // 终态 → 幂等路径(原样返回,不覆盖、不重复发事件);其余拒绝理由保留诊断码,
16010
+ // 绝不把「没停成」写成「已停止」(§6.3、§11.4 条款 4)。
16011
+ cleanup: resolveCleanupReport(
16012
+ reason === "already-terminal" ? { confirmedStopped: false, alreadyTerminal: true } : { confirmedStopped: false, diagnostic: reason }
16013
+ )
15892
16014
  };
15893
16015
  }
15894
- throw new Error("execShell requires a non-empty command.");
15895
- }
15896
- var execShellCapability = {
15897
- name: "execShell",
15898
- getToolDefinition(toolName = "execShell") {
15899
- return buildExecShellToolDefinition(toolName);
15900
- },
15901
- normalizeInput(input) {
15902
- return normalizeExecShellInput(input);
15903
- },
15904
- async invoke(ctx, normalized) {
15905
- const workspaceRoot = ctx.workspaceRoot || process.cwd();
15906
- const command = normalized.command;
15907
- if (ctx.restrictToWorkspace) {
15908
- const escapeToken = findWorkspaceShellEscapeToken(command);
15909
- if (escapeToken) {
15910
- return buildWorkspaceShellEscapeBlockedResult({ command, token: escapeToken });
15911
- }
15912
- }
15913
- const interactiveAuthCommand = extractInteractiveGhAuthCommand(command);
15914
- if (interactiveAuthCommand) {
15915
- const blocked = buildInteractiveCommandBlockedResult(interactiveAuthCommand);
15916
- return {
15917
- ...blocked,
15918
- metadata: {
15919
- ...blocked.metadata,
15920
- ...normalized.activity ? { activity: normalized.activity } : {}
15921
- }
15922
- };
15923
- }
15924
- const shellPlan = buildWorkspaceShellPlan({
15925
- toolName: "execShell",
15926
- command,
15927
- shell: normalized.shell
15928
- });
15929
- const background = normalized.background === true;
15930
- const result4 = await runWorkspaceCommand({
15931
- workspaceRoot,
15932
- command: shellPlan.argv,
15933
- timeoutMs: resolveExecShellTimeoutMs(ctx.commandTimeoutMs),
15934
- outputLimit: ctx.commandOutputLimit,
15935
- commandPrefix: ctx.commandPrefix,
15936
- abortSignal: ctx.abortSignal,
15937
- detachMs: background ? 0 : isImmediateDetachShellCommand({ command }) ? 0 : ctx.detachMs,
15938
- stdin: typeof normalized.input === "string" ? normalized.input : void 0,
15939
- // Ownership of the turn that invoked this command: an auto-detached
15940
- // command keeps its parent dialog so its terminal can resume that
15941
- // conversation (see processOwnership.ts). Absent ctx.owner = unowned.
15942
- owner: ctx.owner ?? null
15943
- });
15944
- const warnings = result4.detached || result4.spawnFailed ? [] : detectExecShellSynchronousWarnings(command, {
15945
- resolvedShell: shellPlan.resolvedShell
15946
- });
16016
+ if (graceMs > 0) await sleep3(graceMs);
16017
+ if (!isProcessGroupAlive(record2.pgid, kill)) {
15947
16018
  return {
15948
- content: warnings.length > 0 ? [result4.content, ...warnings.map((warning) => warning.message)].join("\n\n") : result4.content,
15949
- metadata: {
15950
- command,
15951
- exitCode: result4.exitCode,
15952
- timedOut: result4.timedOut,
15953
- resolvedShell: shellPlan.resolvedShell,
15954
- ...shellPlan.shellKind ? { shellKind: shellPlan.shellKind } : {},
15955
- ...result4.aborted ? { aborted: true } : {},
15956
- ...result4.detached ? {
15957
- detached: true,
15958
- pid: result4.pid,
15959
- label: result4.label,
15960
- // Phase 0: stable taskId from the pre-registered Execution
15961
- // Envelope (additive; existing detached fields untouched).
15962
- taskId: result4.taskId,
15963
- status: "running",
15964
- // P0 machine-readable execution state: an explicit positive
15965
- // signal instead of requiring the model to infer "not detached"
15966
- // from absence. Mirrors the real lifecycle contract.
15967
- executionState: "detached",
15968
- trackedBackgroundTask: true,
15969
- completionNotification: "terminal-resume"
15970
- } : {
15971
- // P0 machine-readable execution state: the command already
15972
- // finished inside the synchronous window; no background task
15973
- // exists, so no completion notification will ever arrive.
15974
- executionState: "synchronous-complete",
15975
- trackedBackgroundTask: false,
15976
- completionNotification: "none"
15977
- },
15978
- ...normalized.activity ? { activity: normalized.activity } : {}
15979
- }
16019
+ outcome: "stopped",
16020
+ taskId,
16021
+ pid: record2.pid,
16022
+ status: reportedTaskStatus("killed"),
16023
+ signal: "SIGTERM",
16024
+ escalated: false,
16025
+ cleanup: resolveCleanupReport({ confirmedStopped: true })
15980
16026
  };
15981
16027
  }
15982
- };
16028
+ try {
16029
+ kill(-record2.pgid, "SIGKILL");
16030
+ } catch {
16031
+ }
16032
+ return {
16033
+ outcome: "stopped",
16034
+ taskId,
16035
+ pid: record2.pid,
16036
+ status: reportedTaskStatus("killed"),
16037
+ signal: "SIGKILL",
16038
+ escalated: true,
16039
+ cleanup: resolveCleanupReport({ confirmedStopped: true })
16040
+ };
16041
+ }
16042
+ function runTasks(args2) {
16043
+ const tasks = args2.registry.listBackground().map((record2) => ({
16044
+ taskId: record2.taskId,
16045
+ pid: record2.pid,
16046
+ label: record2.label,
16047
+ status: statusOf(record2),
16048
+ startedAt: record2.startedAt,
16049
+ persist: record2.persist
16050
+ }));
16051
+ return { tasks, count: tasks.length };
16052
+ }
15983
16053
 
15984
16054
  // packages/agent-runtime/localWorkspaceToolDefs.ts
15985
16055
  var WORKSPACE_TOOL_NAMES = [
@@ -16316,23 +16386,6 @@ function filterDeclaredWorkspaceToolNames(args2) {
16316
16386
  );
16317
16387
  }
16318
16388
 
16319
- // packages/agent-runtime/capabilities/capabilitySdk.ts
16320
- var BUILTIN_CAPABILITIES = [
16321
- execShellCapability
16322
- ];
16323
- async function invokeCapability(capabilityOrName, input, ctx = {}) {
16324
- const capability = typeof capabilityOrName === "string" ? BUILTIN_CAPABILITIES.find((c) => c.name === capabilityOrName) : capabilityOrName;
16325
- if (!capability) {
16326
- throw new Error(`Capability "${String(capabilityOrName)}" not found.`);
16327
- }
16328
- const normalizedInput = capability.normalizeInput(input);
16329
- await evaluateCapabilityPolicy(capability, normalizedInput, ctx);
16330
- if (ctx.onInvoke) {
16331
- await ctx.onInvoke(capability.name, normalizedInput);
16332
- }
16333
- return capability.invoke(ctx, normalizedInput);
16334
- }
16335
-
16336
16389
  // packages/agent-runtime/localWorkspaceTools.ts
16337
16390
  async function hasRootGitignore2(workspaceRoot) {
16338
16391
  return hasRootGitignore(workspaceRoot);
@@ -16518,20 +16571,20 @@ function collectIntegerArg(args2) {
16518
16571
  var LINES_ARG_SYNTAX = '"40-120", "120-", "-50", or "50"';
16519
16572
  var LINES_RANGE_FORMAT = /^(\d+)?\s*-\s*(\d+)?$/;
16520
16573
  function parseLinesArg(value) {
16521
- const text = asTrimmedString(value);
16522
- if (!text) {
16574
+ const text2 = asTrimmedString(value);
16575
+ if (!text2) {
16523
16576
  return {
16524
16577
  warning: `Ignored lines: expected ${LINES_ARG_SYNTAX}, received ${describeRejectedArgValue(value)}.`
16525
16578
  };
16526
16579
  }
16527
16580
  const reject = {
16528
- warning: `Ignored lines: expected ${LINES_ARG_SYNTAX}, received ${JSON.stringify(text)}.`
16581
+ warning: `Ignored lines: expected ${LINES_ARG_SYNTAX}, received ${JSON.stringify(text2)}.`
16529
16582
  };
16530
- if (/^\d+$/.test(text)) {
16531
- const count = Number(text);
16583
+ if (/^\d+$/.test(text2)) {
16584
+ const count = Number(text2);
16532
16585
  return count > 0 ? { slice: { maxLines: Math.min(count, 2e3) } } : reject;
16533
16586
  }
16534
- const range3 = LINES_RANGE_FORMAT.exec(text);
16587
+ const range3 = LINES_RANGE_FORMAT.exec(text2);
16535
16588
  if (!range3) return reject;
16536
16589
  const [, rawStart, rawEnd] = range3;
16537
16590
  if (rawStart === void 0 && rawEnd === void 0) return reject;
@@ -16545,7 +16598,7 @@ function parseLinesArg(value) {
16545
16598
  const endLine = Number(rawEnd);
16546
16599
  if (endLine < startLine) {
16547
16600
  return {
16548
- warning: `Ignored lines ${JSON.stringify(text)}: the end line must be greater than or equal to the start line.`
16601
+ warning: `Ignored lines ${JSON.stringify(text2)}: the end line must be greater than or equal to the start line.`
16549
16602
  };
16550
16603
  }
16551
16604
  return { slice: { startLine, endLine } };
@@ -16628,11 +16681,11 @@ function countAndReplaceEolTolerant(args2) {
16628
16681
  });
16629
16682
  return { count, result: result4 };
16630
16683
  }
16631
- function normalizeEolToLf(text) {
16632
- return text.replace(/\r\n/g, "\n");
16684
+ function normalizeEolToLf(text2) {
16685
+ return text2.replace(/\r\n/g, "\n");
16633
16686
  }
16634
- function convertEol(text, eol) {
16635
- const lf = normalizeEolToLf(text);
16687
+ function convertEol(text2, eol) {
16688
+ const lf = normalizeEolToLf(text2);
16636
16689
  return eol === "\r\n" ? lf.replace(/\n/g, "\r\n") : lf;
16637
16690
  }
16638
16691
  function pluralizeReplacement(count) {
@@ -16640,8 +16693,8 @@ function pluralizeReplacement(count) {
16640
16693
  }
16641
16694
  var EDIT_SNIPPET_MAX_LINES = 24;
16642
16695
  var EDIT_SNIPPET_WIDTH = 160;
16643
- function clipEditSnippet(text) {
16644
- const lines = text.split(/\r?\n/);
16696
+ function clipEditSnippet(text2) {
16697
+ const lines = text2.split(/\r?\n/);
16645
16698
  if (lines.at(-1) === "") lines.pop();
16646
16699
  return lines.slice(0, EDIT_SNIPPET_MAX_LINES).map((line) => line.length > EDIT_SNIPPET_WIDTH ? `${line.slice(0, EDIT_SNIPPET_WIDTH)}\u2026` : line).join("\n");
16647
16700
  }
@@ -16755,8 +16808,8 @@ async function runWorkspacePackageScript(args2) {
16755
16808
  }
16756
16809
  };
16757
16810
  }
16758
- function parseLastJsonObject(text) {
16759
- const trimmed = text.trim();
16811
+ function parseLastJsonObject(text2) {
16812
+ const trimmed = text2.trim();
16760
16813
  for (let index = trimmed.lastIndexOf("{"); index >= 0; index = trimmed.lastIndexOf("{", index - 1)) {
16761
16814
  try {
16762
16815
  const parsed = JSON.parse(trimmed.slice(index));
@@ -16798,9 +16851,12 @@ function expandHomePath(requestedPath) {
16798
16851
  if (requestedPath.startsWith("~/")) return resolve4(homedir(), requestedPath.slice(2));
16799
16852
  return requestedPath;
16800
16853
  }
16854
+ function resolveRequestedWorkspacePath(workspaceRoot, requestedPath) {
16855
+ return resolve4(resolve4(workspaceRoot), expandHomePath(requestedPath));
16856
+ }
16801
16857
  async function resolveLocalWorkspaceToolPath(args2) {
16802
16858
  const workspaceRoot = resolve4(args2.workspaceRoot);
16803
- const targetPath = resolve4(workspaceRoot, expandHomePath(args2.requestedPath));
16859
+ const targetPath = resolveRequestedWorkspacePath(workspaceRoot, args2.requestedPath);
16804
16860
  const insideWorkspace = isPathInsideWorkspace({ workspaceRoot, targetPath });
16805
16861
  const trustedSpillPath = !insideWorkspace && args2.allowRuntimeOwnedSpill === true ? await resolveRuntimeOwnedSpillPath(targetPath) : null;
16806
16862
  if (!insideWorkspace && trustedSpillPath === null) {
@@ -16901,7 +16957,7 @@ async function readFileTool(args2) {
16901
16957
  const force = parsed.force === true;
16902
16958
  const ledger = args2.readLedger;
16903
16959
  const requestRange = { startLine: sliced.startLine, endLine: sliced.endLine };
16904
- const fingerprint = createHash3("sha1").update(content).digest("hex").slice(0, 20);
16960
+ const fingerprint = createHash4("sha1").update(content).digest("hex").slice(0, 20);
16905
16961
  let entry = ledger?.get(relativePath);
16906
16962
  if (entry && entry.fingerprint !== fingerprint) {
16907
16963
  entry = void 0;
@@ -16976,13 +17032,13 @@ async function writeFileTool(args2) {
16976
17032
  }
16977
17033
  };
16978
17034
  }
16979
- function extractSnippetWords(text) {
17035
+ function extractSnippetWords(text2) {
16980
17036
  const tokens = [];
16981
- const latinWords = text.match(/[A-Za-z0-9_]{2,}/g);
17037
+ const latinWords = text2.match(/[A-Za-z0-9_]{2,}/g);
16982
17038
  if (latinWords) {
16983
17039
  for (const w of latinWords) tokens.push(w.toLowerCase());
16984
17040
  }
16985
- const cjkChars = text.match(new RegExp("\\p{Script=Han}", "gu"));
17041
+ const cjkChars = text2.match(new RegExp("\\p{Script=Han}", "gu"));
16986
17042
  if (cjkChars) {
16987
17043
  for (let i = 0; i < cjkChars.length - 1; i++) {
16988
17044
  tokens.push(cjkChars[i] + cjkChars[i + 1]);
@@ -17886,7 +17942,7 @@ function validateToolArguments(args2) {
17886
17942
  return { ok: true, args: {} };
17887
17943
  }
17888
17944
  const raw = typeof rawArguments === "string" ? rawArguments.trim() : "";
17889
- let record = {};
17945
+ let record2 = {};
17890
17946
  if (raw !== "") {
17891
17947
  let parsed;
17892
17948
  try {
@@ -17897,18 +17953,18 @@ function validateToolArguments(args2) {
17897
17953
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
17898
17954
  return { ok: true, args: {} };
17899
17955
  }
17900
- record = parsed;
17956
+ record2 = parsed;
17901
17957
  }
17902
17958
  for (const field of requiredFields) {
17903
- if (record[field] !== void 0) continue;
17904
- if (isAliasSatisfied(record, field)) continue;
17959
+ if (record2[field] !== void 0) continue;
17960
+ if (isAliasSatisfied(record2, field)) continue;
17905
17961
  return {
17906
17962
  ok: false,
17907
17963
  message: `${toolName} \u53C2\u6570\u6821\u9A8C\u5931\u8D25\uFF1A\u7F3A\u5C11\u5FC5\u586B\u53C2\u6570 "${field}"\u3002\u8BF7\u8865\u4E0A\u540E\u91CD\u65B0\u8C03\u7528 ${toolName}\u3002`
17908
17964
  };
17909
17965
  }
17910
17966
  for (const [field, spec] of Object.entries(properties)) {
17911
- const value = record[field];
17967
+ const value = record2[field];
17912
17968
  if (value === void 0 || value === null) continue;
17913
17969
  if (!spec || typeof spec !== "object" || Array.isArray(spec)) continue;
17914
17970
  const declared = spec.type;
@@ -17922,7 +17978,7 @@ function validateToolArguments(args2) {
17922
17978
  };
17923
17979
  }
17924
17980
  }
17925
- return { ok: true, args: record };
17981
+ return { ok: true, args: record2 };
17926
17982
  }
17927
17983
  function buildToolParametersIndex(tools) {
17928
17984
  const index = /* @__PURE__ */ new Map();
@@ -18062,6 +18118,39 @@ function resolveLocalToolPolicy(args2) {
18062
18118
  reason: `${toolName} is not enabled for local runtime runs. The agent must declare the tool in its manifest; in restricted mode (NOLO_LOCAL_TOOL_MODE=restricted) non-default tools also require adding to NOLO_LOCAL_ALLOWED_TOOLS (e.g. NOLO_LOCAL_ALLOWED_TOOLS=someCustomTool) and restarting the session so the env var loads.`
18063
18119
  };
18064
18120
  }
18121
+ async function runExecShellGates(args2) {
18122
+ const opts = {
18123
+ ...args2.abortSignal ? { abortSignal: args2.abortSignal } : {},
18124
+ ...args2.detachMs !== void 0 ? { detachMs: args2.detachMs } : {},
18125
+ ...args2.confirmed !== void 0 ? { confirmed: args2.confirmed } : {},
18126
+ ...args2.enableDestructiveShellGuard !== void 0 ? { enableDestructiveShellGuard: args2.enableDestructiveShellGuard } : {},
18127
+ ...args2.confirmDestructiveAction ? { confirmDestructiveAction: args2.confirmDestructiveAction } : {},
18128
+ ...args2.blockDestructiveWithoutConfirmation !== void 0 ? { blockDestructiveWithoutConfirmation: args2.blockDestructiveWithoutConfirmation } : {}
18129
+ };
18130
+ if (normalizeLocalToolName(args2.toolName) !== "execShell") {
18131
+ return { kind: "pass", opts };
18132
+ }
18133
+ if (args2.execShellApproval) {
18134
+ const result4 = await invokeCapability("execShell", args2.callArguments, {
18135
+ ...opts,
18136
+ workspaceRoot: args2.workspaceRoot,
18137
+ commandOutputLimit: args2.commandOutputLimit,
18138
+ execShellApproval: args2.execShellApproval
18139
+ });
18140
+ return { kind: "handled", result: result4 };
18141
+ }
18142
+ let parsedInput = args2.callArguments;
18143
+ if (typeof parsedInput === "string") {
18144
+ try {
18145
+ parsedInput = JSON.parse(parsedInput);
18146
+ } catch {
18147
+ parsedInput = { command: parsedInput };
18148
+ }
18149
+ }
18150
+ await evaluateExecShellDestructiveGuard(parsedInput, opts);
18151
+ opts.confirmed = true;
18152
+ return { kind: "pass", opts };
18153
+ }
18065
18154
  async function executeLocalToolWithPolicy(args2) {
18066
18155
  if (args2.runToolNames !== void 0 && !args2.runToolNames.map(normalizeLocalToolName).includes(normalizeLocalToolName(args2.call.name))) {
18067
18156
  throw new Error(`${args2.call.name} is outside the final run tool surface.`);
@@ -18078,39 +18167,33 @@ async function executeLocalToolWithPolicy(args2) {
18078
18167
  }
18079
18168
  const parametersSchema = args2.toolParametersIndex?.get(decision.toolName);
18080
18169
  if (parametersSchema !== void 0) {
18081
- const gate = validateToolArguments({
18082
- toolName: decision.toolName,
18083
- rawArguments: args2.call.arguments,
18084
- parameters: parametersSchema
18085
- });
18086
- if (!gate.ok) throw new Error(gate.message);
18087
- }
18088
- const opts = {
18089
- ...args2.abortSignal ? { abortSignal: args2.abortSignal } : {},
18090
- ...args2.detachMs !== void 0 ? { detachMs: args2.detachMs } : {},
18091
- ...args2.confirmed !== void 0 ? { confirmed: args2.confirmed } : {},
18092
- ...args2.enableDestructiveShellGuard !== void 0 ? { enableDestructiveShellGuard: args2.enableDestructiveShellGuard } : {},
18093
- ...args2.confirmDestructiveAction ? { confirmDestructiveAction: args2.confirmDestructiveAction } : {},
18094
- ...args2.blockDestructiveWithoutConfirmation !== void 0 ? { blockDestructiveWithoutConfirmation: args2.blockDestructiveWithoutConfirmation } : {}
18095
- };
18096
- if (decision.toolName === "execShell") {
18097
- let parsedInput = args2.call.arguments;
18098
- if (typeof parsedInput === "string") {
18099
- try {
18100
- parsedInput = JSON.parse(parsedInput);
18101
- } catch {
18102
- parsedInput = { command: parsedInput };
18103
- }
18104
- }
18105
- await evaluateExecShellDestructiveGuard(parsedInput, opts);
18106
- opts.confirmed = true;
18170
+ const gate2 = validateToolArguments({
18171
+ toolName: decision.toolName,
18172
+ rawArguments: args2.call.arguments,
18173
+ parameters: parametersSchema
18174
+ });
18175
+ if (!gate2.ok) throw new Error(gate2.message);
18107
18176
  }
18177
+ const gate = await runExecShellGates({
18178
+ toolName: decision.toolName,
18179
+ callArguments: args2.call.arguments,
18180
+ execShellApproval: args2.execShellApproval,
18181
+ workspaceRoot: args2.workspaceRoot,
18182
+ commandOutputLimit: args2.commandOutputLimit,
18183
+ abortSignal: args2.abortSignal,
18184
+ detachMs: args2.detachMs,
18185
+ confirmed: args2.confirmed,
18186
+ enableDestructiveShellGuard: args2.enableDestructiveShellGuard,
18187
+ confirmDestructiveAction: args2.confirmDestructiveAction,
18188
+ blockDestructiveWithoutConfirmation: args2.blockDestructiveWithoutConfirmation
18189
+ });
18190
+ if (gate.kind === "handled") return gate.result;
18108
18191
  return executor(
18109
18192
  {
18110
18193
  ...args2.call,
18111
18194
  name: decision.toolName
18112
18195
  },
18113
- opts
18196
+ gate.opts
18114
18197
  );
18115
18198
  }
18116
18199
 
@@ -18161,16 +18244,16 @@ function shouldReturnToolExecutionErrors(adapter) {
18161
18244
  function emitLoopEvent(boundary, event, bridge) {
18162
18245
  boundary.emit(bridge ? { event, bridge } : { event });
18163
18246
  }
18164
- function clip2(value, max4 = 240) {
18247
+ function clip(value, max4 = 240) {
18165
18248
  return clipCompactText(value, max4);
18166
18249
  }
18167
18250
  function projectSafeToolObservationMetadata(metadata) {
18168
18251
  if (!metadata || typeof metadata !== "object") return void 0;
18169
18252
  const safe = {};
18170
18253
  if (typeof metadata.exitCode === "number") safe.exitCode = metadata.exitCode;
18171
- if (typeof metadata.actionGate === "string") safe.actionGate = clip2(metadata.actionGate, 240);
18172
- if (typeof metadata.command === "string") safe.command = clip2(metadata.command, 240);
18173
- if (typeof metadata.path === "string") safe.path = clip2(metadata.path, 240);
18254
+ if (typeof metadata.actionGate === "string") safe.actionGate = clip(metadata.actionGate, 240);
18255
+ if (typeof metadata.command === "string") safe.command = clip(metadata.command, 240);
18256
+ if (typeof metadata.path === "string") safe.path = clip(metadata.path, 240);
18174
18257
  if (typeof metadata.truncated === "boolean") safe.truncated = metadata.truncated;
18175
18258
  if (typeof metadata.byteCount === "number") safe.byteCount = metadata.byteCount;
18176
18259
  if (typeof metadata.lineCount === "number") safe.lineCount = metadata.lineCount;
@@ -18186,7 +18269,7 @@ function summarizeToolResult(content, metadata) {
18186
18269
  const lines = trimmed.split(/\r?\n/).length;
18187
18270
  parts2.push(`${lines} line${lines === 1 ? "" : "s"}`);
18188
18271
  parts2.push(`${trimmed.length} chars`);
18189
- const tail = clip2(trimmed.slice(-160), 160);
18272
+ const tail = clip(trimmed.slice(-160), 160);
18190
18273
  if (tail) parts2.push(`tail="${tail}"`);
18191
18274
  } else {
18192
18275
  parts2.push("empty");
@@ -18310,7 +18393,10 @@ async function executeToolCall(args2) {
18310
18393
  })
18311
18394
  );
18312
18395
  throw new Error(
18313
- `\u6A21\u578B\u751F\u6210\u7684 tool_call arguments \u4E0D\u662F\u5408\u6CD5 JSON\uFF08\u7591\u4F3C\u4E0A\u6E38\u6D41\u5F0F\u622A\u65AD\uFF0C\u539F\u59CB\u957F\u5EA6 ${rawPoisonArguments.length}\uFF09\u3002\u8BF7\u91CD\u65B0\u5B8C\u6574\u8C03\u7528 ${toolName}\uFF0C\u786E\u4FDD arguments \u662F\u95ED\u5408\u7684 JSON \u5BF9\u8C61\uFF1B\u82E5\u56E0\u53C2\u6570\u8FC7\u957F\u88AB\u622A\u65AD\uFF0C\u5148\u7CBE\u7B80\u53C2\u6570\uFF08\u4E0D\u8981\u5185\u5D4C diff/\u65E5\u5FD7\u7B49\u5927\u6BB5\u6587\u672C\uFF0C\u6539\u4F20\u8DEF\u5F84\u8BA9\u5BF9\u65B9\u81EA\u884C\u8BFB\u53D6\uFF09\u518D\u91CD\u8BD5\u3002`
18396
+ // 只按类别换文案(判定分支、错误类型、可重试性、落库内容、日志级别全部不变):
18397
+ // 两类都只描述**形状**、不判原因——分类只看得出「字符串/括号是否闭合」,
18398
+ // 分不出「上游传输层丢字节」还是「模型自己写错了闭合符」。
18399
+ classifyUnparsableToolArgs(rawPoisonArguments) === "truncated" ? `\u6A21\u578B\u751F\u6210\u7684 tool_call arguments \u4E0D\u662F\u5408\u6CD5 JSON\uFF08\u5F62\u72B6\uFF1A\u7591\u4F3C\u5C3E\u90E8\u4E0D\u5B8C\u6574\u2014\u2014\u5B57\u7B26\u4E32\u6216\u62EC\u53F7\u672A\u95ED\u5408\uFF0C\u7591\u4F3C\u6D41\u5F0F\u4E2D\u65AD\uFF1B\u539F\u59CB\u957F\u5EA6 ${rawPoisonArguments.length}\uFF09\u3002\u8BF7\u91CD\u65B0\u5B8C\u6574\u8C03\u7528 ${toolName}\uFF0C\u786E\u4FDD arguments \u662F\u95ED\u5408\u7684 JSON \u5BF9\u8C61\uFF1B\u82E5\u56E0\u53C2\u6570\u8FC7\u957F\u88AB\u622A\u65AD\uFF0C\u5148\u7CBE\u7B80\u53C2\u6570\uFF08\u4E0D\u8981\u5185\u5D4C diff/\u65E5\u5FD7\u7B49\u5927\u6BB5\u6587\u672C\uFF0C\u6539\u4F20\u8DEF\u5F84\u8BA9\u5BF9\u65B9\u81EA\u884C\u8BFB\u53D6\uFF09\u518D\u91CD\u8BD5\u3002` : `\u6A21\u578B\u751F\u6210\u7684 tool_call arguments \u4E0D\u662F\u5408\u6CD5 JSON\uFF08\u5F62\u72B6\uFF1A\u7ED3\u6784\u5B8C\u6574\u4F46 token \u975E\u6CD5\u2014\u2014\u975E\u95ED\u5408\u95EE\u9898\uFF1B\u539F\u59CB\u957F\u5EA6 ${rawPoisonArguments.length}\uFF09\u3002\u8BF7\u91CD\u65B0\u5B8C\u6574\u8C03\u7528 ${toolName}\uFF0C\u786E\u4FDD\u6BCF\u4E2A\u5B57\u7B26\u4E32\u503C\u90FD\u6309 JSON \u89C4\u5219\u8F6C\u4E49\uFF08\u6362\u884C\u7B49\u63A7\u5236\u5B57\u7B26\u5199\u6210 \\n \u7B49\u8F6C\u4E49\u5E8F\u5217\uFF09\uFF0C\u533A\u95F4/\u7248\u672C\u53F7\u7B49\u975E JSON \u5B57\u9762\u91CF\u5199\u6210\u5B57\u7B26\u4E32\uFF08\u5982 "lines": "300-400"\uFF09\uFF0Carguments \u662F\u95ED\u5408\u7684 JSON \u5BF9\u8C61\u3002`
18314
18400
  );
18315
18401
  }
18316
18402
  }
@@ -18552,7 +18638,7 @@ function createTurnUsageLedger(args2) {
18552
18638
  let compactionUsage;
18553
18639
  const providerRecords = [];
18554
18640
  return {
18555
- recordProviderCall: ({ usage, model, provider }) => {
18641
+ recordProviderCall: ({ usage, model, provider, telemetry }) => {
18556
18642
  turnUsage = mergeTurnUsage(turnUsage, usage);
18557
18643
  lastContextUsage = usage;
18558
18644
  if (usage && Object.keys(usage).length > 0) {
@@ -18561,6 +18647,7 @@ function createTurnUsageLedger(args2) {
18561
18647
  usage,
18562
18648
  model: model || args2.model || "unknown",
18563
18649
  ...provider || args2.provider ? { provider: provider || args2.provider } : {},
18650
+ ...telemetry && Object.keys(telemetry).length > 0 ? { telemetry } : {},
18564
18651
  ...args2.stablePrefixHash ? {
18565
18652
  stablePrefixHash: args2.stablePrefixHash,
18566
18653
  stablePrefixEstimatedTokens: args2.stablePrefixEstimatedTokens
@@ -18588,7 +18675,7 @@ function createTurnUsageLedger(args2) {
18588
18675
  }
18589
18676
 
18590
18677
  // packages/agent-runtime/leafFinalHandoff.ts
18591
- var LEAF_FINAL_HANDOFF_INSTRUCTIONS = `Leaf subtask final: return only the result, blockers/findings, and minimal evidence the parent needs. Do not narrate your process or restate the task. Keep success minimal (a one-line result is enough); on failure include the blocker, severity when relevant, and an evidence reference (file:line, test command, commit, artifact, or run/dialog). If the task explicitly requires a long-form deliverable (document, translation, dataset, code, or other artifact), provide that deliverable in full and keep only the handoff around it concise. During execution, follow the brief as given: only when new evidence would materially change the goal, risk, irreversibility, or DoD should you surface it as a blocker/finding in the handoff \u2014 do not silently expand scope or re-litigate the settled direction.`;
18678
+ var LEAF_FINAL_HANDOFF_INSTRUCTIONS = `Leaf subtask final: return only the result, blockers/findings, and minimal evidence the parent needs. Do not narrate your process or restate the task. Keep success minimal (a one-line result is enough); on failure include the blocker, severity when relevant, and an evidence reference (file:line, test command, commit, artifact, or run/dialog). If the task explicitly requires a long-form deliverable (document, translation, dataset, code, or other artifact), provide that deliverable in full and keep only the handoff around it concise. During execution, follow the brief as given: report only results incremental to the brief, new evidence, overturned assumptions, and remaining questions; only when new evidence would materially change the goal, risk, irreversibility, or DoD should you surface it as a blocker/finding \u2014 do not silently expand scope or re-litigate the settled direction. Keep success concise, but include evidence for required DoD items.`;
18592
18679
 
18593
18680
  // packages/agent-runtime/contextBlockScope.ts
18594
18681
  var isContextBlockScope = (value) => {
@@ -18605,7 +18692,7 @@ var normalizeContextBlockScopes = (legacyBlocks = void 0, scopes = void 0) => {
18605
18692
  };
18606
18693
 
18607
18694
  // packages/agent-runtime/localAutoCompaction.ts
18608
- import { createHash as createHash4 } from "node:crypto";
18695
+ import { createHash as createHash5, randomUUID } from "node:crypto";
18609
18696
  function toPlanCompressionMessages(history) {
18610
18697
  return history.map((message, index) => ({
18611
18698
  id: `local-${index}`,
@@ -18620,7 +18707,7 @@ function hashSummarySourceSlice(history, summarizedBeforeId) {
18620
18707
  const found = bridged.findIndex((m) => m.id === summarizedBeforeId);
18621
18708
  if (found === -1) return void 0;
18622
18709
  const slice = bridged.slice(0, found + 1);
18623
- return createHash4("sha256").update(JSON.stringify(slice)).digest("hex");
18710
+ return createHash5("sha256").update(JSON.stringify(slice)).digest("hex");
18624
18711
  }
18625
18712
  function validateStoredSummary(args2) {
18626
18713
  const { history, stored } = args2;
@@ -18671,6 +18758,39 @@ function isColdResume(history, nowMs) {
18671
18758
  }
18672
18759
  return false;
18673
18760
  }
18761
+ async function runSummaryFallback(fallback, content, options) {
18762
+ try {
18763
+ const text2 = await fallback(content, options);
18764
+ if (typeof text2 !== "string") return null;
18765
+ const trimmed = text2.trim();
18766
+ return trimmed.length > 0 ? trimmed : null;
18767
+ } catch (error) {
18768
+ console.warn("[localLoop] auto-compaction summary fallback failed", {
18769
+ attemptId: options.attemptId,
18770
+ stage: "fallback"
18771
+ });
18772
+ return null;
18773
+ }
18774
+ }
18775
+ var COMPACTION_RETRY_BACKOFF_MS = 6e4;
18776
+ var COMPACTION_RETRY_BACKOFF_MAX_ENTRIES = 256;
18777
+ var compactionRetryBackoff = /* @__PURE__ */ new Map();
18778
+ function isCompactionBackedOff(dialogId, now) {
18779
+ for (const [key, until2] of compactionRetryBackoff) {
18780
+ if (until2 <= now) compactionRetryBackoff.delete(key);
18781
+ }
18782
+ const until = compactionRetryBackoff.get(dialogId);
18783
+ return until !== void 0 && until > now;
18784
+ }
18785
+ function recordCompactionFailure(dialogId, now) {
18786
+ compactionRetryBackoff.delete(dialogId);
18787
+ while (compactionRetryBackoff.size >= COMPACTION_RETRY_BACKOFF_MAX_ENTRIES) {
18788
+ const oldest = compactionRetryBackoff.keys().next().value;
18789
+ if (oldest === void 0) break;
18790
+ compactionRetryBackoff.delete(oldest);
18791
+ }
18792
+ compactionRetryBackoff.set(dialogId, now + COMPACTION_RETRY_BACKOFF_MS);
18793
+ }
18674
18794
  async function maybeAutoCompactLocalHistory(args2) {
18675
18795
  const { adapter, dialogId, history } = args2;
18676
18796
  const emitPhase = (phase) => {
@@ -18775,7 +18895,56 @@ async function maybeAutoCompactLocalHistory(args2) {
18775
18895
  if (!plan.shouldCompress) {
18776
18896
  return projectExisting();
18777
18897
  }
18898
+ const retryNow = args2.now?.() ?? Date.now();
18899
+ if (isCompactionBackedOff(dialogId, retryNow)) {
18900
+ const projected = projectExisting();
18901
+ const diag = plan.diagnostics;
18902
+ return {
18903
+ ...projected,
18904
+ decision: {
18905
+ outcome: "skipped",
18906
+ skipReason: "retry-backoff",
18907
+ ...diag ? {
18908
+ estimatedTokens: diag.totalUsed,
18909
+ historyBudget: diag.historyBudget,
18910
+ triggerRatio: diag.triggerRatio,
18911
+ ...diag.realUsageRatio !== void 0 ? { realUsageRatio: diag.realUsageRatio } : {}
18912
+ } : {}
18913
+ }
18914
+ };
18915
+ }
18778
18916
  emitPhase({ kind: "compaction-start" });
18917
+ const compactionAttemptId = randomUUID();
18918
+ let compactionT0 = Date.now();
18919
+ let compactionInputTokens;
18920
+ let compactionPrevSummaryTokens;
18921
+ let compactionInputSource;
18922
+ let compactionPrevSummarySource;
18923
+ let primaryElapsedMs;
18924
+ let fallbackElapsedMs;
18925
+ let saveElapsedMs;
18926
+ const logCompactionTiming = (outcome, fields) => {
18927
+ if (compactionInputTokens === void 0 && compactionInputSource !== void 0) {
18928
+ compactionInputTokens = estimateTokenCount(compactionInputSource);
18929
+ }
18930
+ if (compactionPrevSummaryTokens === void 0 && compactionPrevSummarySource !== void 0) {
18931
+ compactionPrevSummaryTokens = estimateTokenCount(compactionPrevSummarySource);
18932
+ }
18933
+ console.log(
18934
+ `[compaction-timing] ${JSON.stringify({
18935
+ attemptId: compactionAttemptId,
18936
+ model: fields.model ?? args2.model ?? null,
18937
+ inputTokens: compactionInputTokens ?? null,
18938
+ prevSummaryTokens: compactionPrevSummaryTokens ?? null,
18939
+ outputChars: fields.outputChars ?? null,
18940
+ elapsedMs: Date.now() - compactionT0,
18941
+ primaryElapsedMs: primaryElapsedMs ?? null,
18942
+ fallbackElapsedMs: fallbackElapsedMs ?? null,
18943
+ saveElapsedMs: saveElapsedMs ?? null,
18944
+ outcome
18945
+ })}`
18946
+ );
18947
+ };
18779
18948
  try {
18780
18949
  if (args2.abortSignal?.aborted) {
18781
18950
  throw Object.assign(new Error("auto-compaction aborted"), { compactionReason: "aborted" });
@@ -18785,7 +18954,11 @@ async function maybeAutoCompactLocalHistory(args2) {
18785
18954
  throw Object.assign(new Error("auto-compaction aborted"), { compactionReason: "aborted" });
18786
18955
  }
18787
18956
  const msgsToCompress = plan.msgsToCompress;
18788
- const messagesText = formatMessagesForSummaryWithTruncation(msgsToCompress);
18957
+ const messagesText = formatMessagesForSummaryWithTruncation(
18958
+ msgsToCompress,
18959
+ TOOL_RESULT_TRUNCATE_CHARS,
18960
+ computeSummaryInputTokenCap(contextWindow)
18961
+ );
18789
18962
  const fileOpsText = formatFileOperationsFromMessages(
18790
18963
  msgsToCompress,
18791
18964
  canonicalizeToolName
@@ -18796,58 +18969,102 @@ async function maybeAutoCompactLocalHistory(args2) {
18796
18969
  fileOpsText
18797
18970
  });
18798
18971
  emitPhase({ kind: "waiting-provider" });
18972
+ compactionInputSource = messagesText;
18973
+ compactionPrevSummarySource = existingSummary;
18974
+ compactionT0 = Date.now();
18799
18975
  const timeoutMs = typeof args2.timeoutMs === "number" && Number.isFinite(args2.timeoutMs) && args2.timeoutMs > 0 ? args2.timeoutMs : void 0;
18800
18976
  const requestController = new AbortController();
18801
18977
  let timeoutHandle;
18802
18978
  let abortListener;
18979
+ if (args2.abortSignal?.aborted) requestController.abort(args2.abortSignal.reason);
18803
18980
  const request3 = provider.complete(
18804
18981
  [{ role: "system", content: COMPACTION_SUMMARY_SYSTEM_PROMPT }, { role: "user", content: promptContent }],
18805
- { signal: requestController.signal, ...timeoutMs ? { timeoutMs } : {} }
18982
+ {
18983
+ signal: requestController.signal,
18984
+ ...timeoutMs ? { timeoutMs } : {},
18985
+ // 仅日志用的 attempt id:让 compaction 埋点与 transport 诊断日志 /
18986
+ // 服务端 x-request-id 可对上,用于关联请求级证据。不进 prompt/摘要/持久化。
18987
+ requestId: compactionAttemptId,
18988
+ // Room for factual handoff, exact commands and file operations.
18989
+ max_tokens: 8192,
18990
+ reasoning_effort: "low"
18991
+ }
18806
18992
  ).then(
18807
18993
  (value) => ({ kind: "done", value }),
18808
18994
  (error) => ({ kind: "provider-error", error })
18809
18995
  );
18810
18996
  const guards = [];
18811
- if (timeoutMs) guards.push(new Promise((resolve5) => {
18997
+ if (timeoutMs) guards.push(new Promise((resolve6) => {
18812
18998
  timeoutHandle = setTimeout(() => {
18813
18999
  requestController.abort();
18814
- resolve5({ kind: "timeout" });
19000
+ resolve6({ kind: "timeout" });
18815
19001
  }, timeoutMs);
18816
19002
  }));
18817
- if (args2.abortSignal) guards.push(new Promise((resolve5) => {
19003
+ if (args2.abortSignal) guards.push(new Promise((resolve6) => {
18818
19004
  abortListener = () => {
18819
19005
  requestController.abort(args2.abortSignal?.reason);
18820
- resolve5({ kind: "aborted" });
19006
+ resolve6({ kind: "aborted" });
18821
19007
  };
18822
19008
  args2.abortSignal?.addEventListener("abort", abortListener, { once: true });
19009
+ if (args2.abortSignal?.aborted) abortListener();
18823
19010
  }));
18824
19011
  const outcome = guards.length ? await Promise.race([request3, ...guards]) : await request3;
19012
+ primaryElapsedMs = Date.now() - compactionT0;
18825
19013
  if (timeoutHandle) clearTimeout(timeoutHandle);
18826
19014
  if (abortListener) args2.abortSignal?.removeEventListener("abort", abortListener);
18827
- if (outcome.kind !== "done") {
18828
- const reason = outcome.kind === "timeout" ? "timeout" : outcome.kind === "aborted" ? "aborted" : "provider-error";
18829
- const detail = outcome.kind === "provider-error" ? outcome.error instanceof Error ? outcome.error.message : String(outcome.error) : reason === "timeout" ? `auto-compaction timed out after ${timeoutMs}ms` : "auto-compaction aborted";
18830
- throw Object.assign(new Error(detail), { compactionReason: reason });
19015
+ const result4 = outcome.kind === "done" ? outcome.value : void 0;
19016
+ let newSummary = result4?.finish_reason !== "length" && typeof result4?.content === "string" ? result4.content.trim() : "";
19017
+ const primaryFailure = outcome.kind === "aborted" || args2.abortSignal?.aborted ? "aborted" : newSummary ? null : outcome.kind === "done" ? "provider-error" : outcome.kind;
19018
+ if (primaryFailure === "aborted") {
19019
+ throw Object.assign(new Error("auto-compaction aborted"), {
19020
+ compactionReason: "aborted"
19021
+ });
18831
19022
  }
18832
- const result4 = outcome.value;
18833
- const newSummary = typeof result4.content === "string" ? result4.content.trim() : "";
18834
- if (!newSummary) {
18835
- console.warn(
18836
- "[localLoop] auto-compaction produced empty summary; keeping prior projection"
18837
- );
18838
- const failureReason = "provider-error";
18839
- emitPhase({ kind: "compaction-failed", reason: failureReason });
18840
- return {
18841
- ...projectExisting(),
18842
- failureMessage: "summary model returned empty content",
18843
- failureReason,
18844
- decision: {
18845
- outcome: "failed",
18846
- reason: failureReason,
18847
- detail: "summary model returned empty content"
19023
+ const summaryFallback = args2.summaryFallback;
19024
+ if (primaryFailure && summaryFallback) {
19025
+ const fallbackT0 = Date.now();
19026
+ const fallbackSummary = await runSummaryFallback(
19027
+ summaryFallback,
19028
+ promptContent,
19029
+ {
19030
+ signal: args2.abortSignal,
19031
+ deadlineMs: Date.now() + 3e4,
19032
+ attemptId: compactionAttemptId
18848
19033
  }
18849
- };
19034
+ );
19035
+ fallbackElapsedMs = Date.now() - fallbackT0;
19036
+ if (args2.abortSignal?.aborted) {
19037
+ throw Object.assign(new Error("auto-compaction aborted"), {
19038
+ compactionReason: "aborted"
19039
+ });
19040
+ }
19041
+ if (fallbackSummary) newSummary = fallbackSummary;
19042
+ }
19043
+ if (!newSummary) {
19044
+ recordCompactionFailure(dialogId, retryNow);
19045
+ if (outcome.kind === "done") {
19046
+ console.warn(
19047
+ "[localLoop] auto-compaction produced empty summary; keeping prior projection"
19048
+ );
19049
+ logCompactionTiming("empty", {});
19050
+ const failureReason = "provider-error";
19051
+ emitPhase({ kind: "compaction-failed", reason: failureReason });
19052
+ return {
19053
+ ...projectExisting(),
19054
+ failureMessage: "summary model returned empty content",
19055
+ failureReason,
19056
+ decision: {
19057
+ outcome: "failed",
19058
+ reason: failureReason,
19059
+ detail: "summary model returned empty content"
19060
+ }
19061
+ };
19062
+ }
19063
+ const reason = primaryFailure ?? "provider-error";
19064
+ const detail = outcome.kind === "provider-error" ? outcome.error instanceof Error ? outcome.error.message : String(outcome.error) : `auto-compaction timed out after ${timeoutMs}ms`;
19065
+ throw Object.assign(new Error(detail), { compactionReason: reason });
18850
19066
  }
19067
+ const saveT0 = Date.now();
18851
19068
  await adapter.saveDialogSummary({
18852
19069
  dialogId,
18853
19070
  summary: newSummary,
@@ -18861,15 +19078,20 @@ async function maybeAutoCompactLocalHistory(args2) {
18861
19078
  })(),
18862
19079
  schemaVersion: COMPACTION_SUMMARY_SCHEMA_VERSION
18863
19080
  });
19081
+ saveElapsedMs = Date.now() - saveT0;
18864
19082
  const metricsReason = invalidSummary ? "invalid_summary" : coldResume ? "cold_resume" : "context_budget";
18865
19083
  const metrics = buildCompactionMetricsFromPlan({
18866
19084
  reason: metricsReason,
18867
19085
  previousSummary: existingSummary,
18868
19086
  plan,
18869
19087
  newSummary,
18870
- summaryUsage: result4.usage
19088
+ summaryUsage: result4?.usage
18871
19089
  });
18872
19090
  console.log(formatCompactionMetricsLog(metrics));
19091
+ logCompactionTiming("success", {
19092
+ model: result4?.model ?? args2.model ?? null,
19093
+ outputChars: newSummary.length
19094
+ });
18873
19095
  emitPhase({ kind: "compaction-end" });
18874
19096
  return {
18875
19097
  history: projectHistoryWithSummary({
@@ -18882,7 +19104,7 @@ async function maybeAutoCompactLocalHistory(args2) {
18882
19104
  reason: metricsReason,
18883
19105
  beforeTokens: metrics.previousSummaryTokens + metrics.compressedTokens,
18884
19106
  afterTokens: metrics.newSummaryTokens + metrics.retainedTokens,
18885
- ...result4.usage ? { usage: result4.usage } : {},
19107
+ ...result4?.usage ? { usage: result4.usage } : {},
18886
19108
  metrics,
18887
19109
  decision: {
18888
19110
  outcome: "compressed",
@@ -18899,6 +19121,8 @@ async function maybeAutoCompactLocalHistory(args2) {
18899
19121
  console.warn("[localLoop] auto-compaction failed:", error);
18900
19122
  const failureMessage = error instanceof Error && error.message ? error.message.slice(0, 200) : String(error).slice(0, 200);
18901
19123
  const failureReason = error instanceof Error && "compactionReason" in error ? error.compactionReason : "provider-error";
19124
+ logCompactionTiming(failureReason, {});
19125
+ if (failureReason !== "aborted") recordCompactionFailure(dialogId, retryNow);
18902
19126
  emitPhase({ kind: "compaction-failed", reason: failureReason });
18903
19127
  return {
18904
19128
  ...projectExisting(),
@@ -18985,6 +19209,7 @@ function createTurnCompactionController(args2) {
18985
19209
  realContextUsagePercent,
18986
19210
  abortSignal: args2.abortSignal,
18987
19211
  timeoutMs: args2.timeoutMs,
19212
+ summaryFallback: args2.summaryFallback,
18988
19213
  onPhase: (phase) => emitPhase(phase, "initial")
18989
19214
  });
18990
19215
  emitObservation(compacted);
@@ -19017,6 +19242,7 @@ function createTurnCompactionController(args2) {
19017
19242
  ...ratio !== void 0 ? { realContextUsagePercent: ratio } : {},
19018
19243
  abortSignal: args2.abortSignal,
19019
19244
  timeoutMs: args2.timeoutMs,
19245
+ summaryFallback: args2.summaryFallback,
19020
19246
  onPhase: (phase) => emitPhase(phase, "in-loop")
19021
19247
  });
19022
19248
  if (compacted.usage) {
@@ -19029,8 +19255,9 @@ function createTurnCompactionController(args2) {
19029
19255
  contextReferenceResolver: args2.contextReferenceResolver
19030
19256
  });
19031
19257
  if (rebuilt.downgraded > 0) {
19258
+ const stats = rebuilt.stats;
19032
19259
  console.warn(
19033
- `[nolo] downgraded ${rebuilt.downgraded} tool_call(s) with unparsable JSON arguments from outbound history after compaction; persisted history untouched`
19260
+ `[nolo] rewrote ${stats.rewrittenToolCalls} tool calls in ${stats.rewrittenAssistantMessages} assistant messages and ${stats.rewrittenToolMessages} tool-result messages after compaction (bad arguments: truncated=${stats.badArguments.truncated}, malformed=${stats.badArguments.malformed}); persisted history untouched`
19034
19261
  );
19035
19262
  }
19036
19263
  args2.replaceWorkingView(rebuilt.messages);
@@ -19043,11 +19270,132 @@ function createTurnCompactionController(args2) {
19043
19270
  return { runInitial, maybeCompactInLoop };
19044
19271
  }
19045
19272
 
19273
+ // packages/agent-runtime/providerCallTelemetry.ts
19274
+ import { createHash as createHash6 } from "node:crypto";
19275
+ var shortHash = (value) => createHash6("sha1").update(value).digest("hex").slice(0, 12);
19276
+ var hashMessage = (message) => {
19277
+ let text2;
19278
+ try {
19279
+ text2 = JSON.stringify(message) ?? "";
19280
+ } catch {
19281
+ text2 = String(message);
19282
+ }
19283
+ return shortHash(text2);
19284
+ };
19285
+ function computeDivergeAt(prev, cur) {
19286
+ const n = Math.min(prev.length, cur.length);
19287
+ for (let i = 0; i < n; i += 1) {
19288
+ if (prev[i] !== cur[i]) return i;
19289
+ }
19290
+ return cur.length >= prev.length ? -1 : n;
19291
+ }
19292
+ var DIVERGE_TRACKER_MAX_DIALOGS = 50;
19293
+ function createDivergeTracker(max4 = DIVERGE_TRACKER_MAX_DIALOGS) {
19294
+ const map8 = /* @__PURE__ */ new Map();
19295
+ return {
19296
+ observe(dialogKey, hashes) {
19297
+ const prev = map8.get(dialogKey);
19298
+ map8.delete(dialogKey);
19299
+ map8.set(dialogKey, hashes);
19300
+ while (map8.size > max4) {
19301
+ const oldest = map8.keys().next().value;
19302
+ if (oldest === void 0) break;
19303
+ map8.delete(oldest);
19304
+ }
19305
+ if (!prev) return { divergeAt: null };
19306
+ return { divergeAt: computeDivergeAt(prev, hashes), prevMsgCount: prev.length };
19307
+ },
19308
+ size: () => map8.size
19309
+ };
19310
+ }
19311
+ var processDivergeTracker = createDivergeTracker();
19312
+ function buildRequestTelemetry(args2) {
19313
+ try {
19314
+ const hashes = args2.messages.map(hashMessage);
19315
+ let toolResultChars = 0;
19316
+ let toolResultCount = 0;
19317
+ let contentChars = 0;
19318
+ for (const m of args2.messages) {
19319
+ const chars = contentCharCount(m.content);
19320
+ contentChars += chars;
19321
+ if (m.role === "tool") {
19322
+ toolResultChars += chars;
19323
+ toolResultCount += 1;
19324
+ }
19325
+ }
19326
+ const diverge = (args2.tracker ?? processDivergeTracker).observe(args2.dialogKey, hashes);
19327
+ return {
19328
+ ...args2.toolsHash ? { toolsHash: args2.toolsHash } : {},
19329
+ msgCount: args2.messages.length,
19330
+ contentChars,
19331
+ toolResultChars,
19332
+ toolResultCount,
19333
+ ...diverge
19334
+ };
19335
+ } catch {
19336
+ return void 0;
19337
+ }
19338
+ }
19339
+ function hashToolDefinitions(tools) {
19340
+ try {
19341
+ return shortHash(JSON.stringify(tools));
19342
+ } catch {
19343
+ return void 0;
19344
+ }
19345
+ }
19346
+
19046
19347
  // packages/agent-runtime/localLoop.ts
19047
19348
  var LOCAL_AGENT_CONFIG_MISSING_CODE = "LOCAL_AGENT_CONFIG_MISSING";
19048
19349
  var LLM_REQUEST_TIMEOUT = "LLM_REQUEST_TIMEOUT";
19049
19350
  var DEFAULT_LLM_REQUEST_TIMEOUT_MS = 10 * 60 * 1e3;
19050
19351
  var DEFAULT_COMPACTION_TIMEOUT_MS = 60 * 1e3;
19352
+ var DEFAULT_ROUND_BUDGET_WARNING_THRESHOLD = 25;
19353
+ function buildRoundBudgetWarningPrompt(threshold) {
19354
+ return `[\u7CFB\u7EDF\u63D0\u793A] \u5F53\u524D\u5355\u6B21\u4EFB\u52A1\u7684\u8FDE\u7EED\u5DE5\u5177\u8C03\u7528\u5DF2\u8FBE ${threshold} \u8F6E\u3002\u4E3A\u9632\u6B62\u4E0A\u4E0B\u6587\u590D\u5408\u81A8\u80C0\u4E0E\u6210\u672C\u5931\u63A7\uFF0C\u8BF7\u5728\u672C\u8F6E\u6216\u4E0B\u4E00\u8F6E\u5185\u6536\u655B\u5F53\u524D\u6267\u884C\u7ED3\u679C\uFF0C\u8F93\u51FA\u5DF2\u9A8C\u8BC1\u4E8B\u5B9E\u3001\u9636\u6BB5\u6027\u7ED3\u8BBA\u6216\u5F53\u524D\u5361\u70B9\uFF0C\u5B8C\u6210\u9636\u6BB5\u4EA4\u63A5\u3002`;
19355
+ }
19356
+ var CONCURRENT_READONLY_TOOLS = /* @__PURE__ */ new Set([
19357
+ "readFile",
19358
+ "read_file",
19359
+ "readWorkspaceFile",
19360
+ "globFiles",
19361
+ "readDoc",
19362
+ "readSkillDoc",
19363
+ "listSpaces",
19364
+ "readSpace",
19365
+ "listTables",
19366
+ "queryTableRows",
19367
+ "listDialogs",
19368
+ "readDialog",
19369
+ "queryDialogsBySubjectRef",
19370
+ "listAgents",
19371
+ "readAgent",
19372
+ "cliWhoami",
19373
+ "cliDoctor",
19374
+ "listProcesses",
19375
+ "fetchWebpage",
19376
+ "exa_search"
19377
+ ]);
19378
+ function partitionToolCallsIntoBatches(toolCalls) {
19379
+ const batches = [];
19380
+ let currentReadBatch = [];
19381
+ for (let i = 0; i < toolCalls.length; i += 1) {
19382
+ const toolCall = toolCalls[i];
19383
+ const isReadOnly = CONCURRENT_READONLY_TOOLS.has(toolCall.function.name);
19384
+ if (isReadOnly) {
19385
+ currentReadBatch.push({ index: i, toolCall });
19386
+ } else {
19387
+ if (currentReadBatch.length > 0) {
19388
+ batches.push({ isConcurrent: true, calls: currentReadBatch });
19389
+ currentReadBatch = [];
19390
+ }
19391
+ batches.push({ isConcurrent: false, calls: [{ index: i, toolCall }] });
19392
+ }
19393
+ }
19394
+ if (currentReadBatch.length > 0) {
19395
+ batches.push({ isConcurrent: true, calls: currentReadBatch });
19396
+ }
19397
+ return batches;
19398
+ }
19051
19399
  function resolveLlmRequestTimeoutMs(input) {
19052
19400
  const raw = input.llmRequestTimeoutMs ?? input.timeoutMs;
19053
19401
  return typeof raw === "number" && Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_LLM_REQUEST_TIMEOUT_MS;
@@ -19056,6 +19404,10 @@ function resolveCompactionTimeoutMs(input) {
19056
19404
  const raw = input.compactionTimeoutMs;
19057
19405
  return typeof raw === "number" && Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_COMPACTION_TIMEOUT_MS;
19058
19406
  }
19407
+ function readHostCompactionSummaryFallback(adapter) {
19408
+ const value = adapter.compactionSummaryFallback;
19409
+ return typeof value === "function" ? value : void 0;
19410
+ }
19059
19411
  async function runCompleteWithTimeout(args2) {
19060
19412
  const { provider, messages, options, timeoutMs, round, input, boundary, context: context3, providerName, model } = args2;
19061
19413
  emitLoopEvent(boundary, {
@@ -19137,6 +19489,30 @@ function extractUserInputText(content) {
19137
19489
  if (!Array.isArray(content)) return "";
19138
19490
  return content.flatMap((part) => part?.type === "text" && part.text ? [part.text] : []).join("\n").trim();
19139
19491
  }
19492
+ function safeToolNamesHash(names) {
19493
+ try {
19494
+ return names.length ? shortHash([...names].sort().join(",")) : void 0;
19495
+ } catch {
19496
+ return void 0;
19497
+ }
19498
+ }
19499
+ function finalizeCallTelemetry(base, result4, round, startedAt) {
19500
+ try {
19501
+ const endTs = Date.now();
19502
+ const toolCalls = (result4.tool_calls ?? []).map((call) => call?.function?.name ?? call?.name).filter((name) => typeof name === "string");
19503
+ const providerToolsHash = result4.toolsHash;
19504
+ return {
19505
+ ...base,
19506
+ ...typeof providerToolsHash === "string" ? { toolsHash: providerToolsHash } : {},
19507
+ round,
19508
+ endTs,
19509
+ durationMs: endTs - startedAt,
19510
+ ...toolCalls.length ? { toolCalls } : {}
19511
+ };
19512
+ } catch {
19513
+ return void 0;
19514
+ }
19515
+ }
19140
19516
  function attachDialogIdToError(error, dialogId) {
19141
19517
  if (!dialogId) return;
19142
19518
  if (typeof error === "object" && error !== null) {
@@ -19171,13 +19547,20 @@ async function persistFailedLocalTurn(args2) {
19171
19547
  ...args2.input.spaceId ? { spaceId: args2.input.spaceId } : {},
19172
19548
  ...args2.input.category ? { category: args2.input.category } : {},
19173
19549
  ...args2.input.inheritedFromDialogKey ? { inheritedFromDialogKey: args2.input.inheritedFromDialogKey } : {},
19174
- ...args2.input.parentDialogId ? { parentDialogId: args2.input.parentDialogId } : {}
19550
+ ...args2.input.parentDialogId ? { parentDialogId: args2.input.parentDialogId } : {},
19551
+ ...typeof args2.input.freshDialog === "boolean" ? { freshDialog: args2.input.freshDialog } : {}
19175
19552
  });
19176
19553
  return saved?.dialogId;
19177
19554
  } catch {
19178
19555
  return args2.input.continueDialogId;
19179
19556
  }
19180
19557
  }
19558
+ async function loadAdapterDialogHistory(adapter, dialogId, strictContinueHistory) {
19559
+ if (adapter.loadDialogHistoryWithSource) {
19560
+ return await adapter.loadDialogHistoryWithSource(dialogId, { strictContinueHistory });
19561
+ }
19562
+ return { messages: await adapter.loadDialogHistory(dialogId) };
19563
+ }
19181
19564
  async function runLocalAgentTurn(input) {
19182
19565
  const legacyCallbacks = {
19183
19566
  onLoopEvent: input.onLoopEvent,
@@ -19261,9 +19644,22 @@ async function runLocalAgentTurn(input) {
19261
19644
  let sanitizedInput = await sanitizeInbound(input.input);
19262
19645
  let sanitizedPersistedInput = input.persistedInput === void 0 ? void 0 : await sanitizeInbound(input.persistedInput);
19263
19646
  let history = [];
19647
+ let dialogHistorySource;
19264
19648
  try {
19265
- history = input.continueDialogId ? await input.adapter.loadDialogHistory(input.continueDialogId) : [];
19649
+ if (input.continueDialogId) {
19650
+ const loaded = await loadAdapterDialogHistory(
19651
+ input.adapter,
19652
+ input.continueDialogId,
19653
+ input.strictContinueHistory
19654
+ );
19655
+ if (input.strictContinueHistory && (loaded.source === "none" || loaded.source === "partial")) {
19656
+ throw new Error(`Strict continuation history unavailable: ${loaded.source}`);
19657
+ }
19658
+ history = loaded.messages;
19659
+ dialogHistorySource = loaded.source;
19660
+ }
19266
19661
  } catch (error) {
19662
+ if (input.strictContinueHistory) throw error;
19267
19663
  const dialogId = await persistFailedLocalTurn({
19268
19664
  adapter: input.adapter,
19269
19665
  agentKey: agentConfig.key,
@@ -19276,6 +19672,15 @@ async function runLocalAgentTurn(input) {
19276
19672
  throw error;
19277
19673
  }
19278
19674
  loopTiming.mark("loadDialogHistory", 0);
19675
+ if (input.continueDialogId && history.length === 0 && dialogHistorySource === "none") {
19676
+ console.warn(
19677
+ `[localLoop] dialog history unavailable for ${input.continueDialogId} (source: none); continuing without prior context.`
19678
+ );
19679
+ } else if (input.continueDialogId && dialogHistorySource === "partial") {
19680
+ console.warn(
19681
+ `[localLoop] dialog history incomplete for ${input.continueDialogId} (source: partial); only ${history.length} message(s) available, earlier context may be missing.`
19682
+ );
19683
+ }
19279
19684
  const canonicalHistory = history;
19280
19685
  const identityBlock = buildIdentityBlock({
19281
19686
  agentName: agentConfig.name,
@@ -19312,6 +19717,11 @@ async function runLocalAgentTurn(input) {
19312
19717
  // 删除安全红线:系统层最高优先级约束,位于所有块之前(含身份信息块),
19313
19718
  // owner 要求常驻「根本提示词」的最顶部(2026-09-26 批量误删事故)。
19314
19719
  { content: DELETE_SAFETY_RED_LINE, cacheScope: "session" },
19720
+ // 并行是默认:系统层固定块,紧随红线(localLoop 无 static 作用域,用 session 占位,
19721
+ // 「最顶」由数组首位保证——与红线同一约定)。
19722
+ { content: PARALLEL_DISPATCH_DEFAULT_RULE, cacheScope: "session" },
19723
+ // 平台风控纪律:无条件系统层块,沿用 session 占位约定。
19724
+ { content: PLATFORM_RISK_CONTROL_RULE, cacheScope: "session" },
19315
19725
  { content: identityBlock, cacheScope: "session" },
19316
19726
  ...guidanceScopes,
19317
19727
  ...callerScopes
@@ -19335,6 +19745,11 @@ async function runLocalAgentTurn(input) {
19335
19745
  compressionTriggerRatio,
19336
19746
  abortSignal: input.abortSignal,
19337
19747
  timeoutMs: resolveCompactionTimeoutMs(input),
19748
+ // 摘要 fallback:优先显式入参;否则读 host adapter 的会话级注入(CLI 本地
19749
+ // adapter 专有字段,见 localRuntimeAdapter.setCliAutoCompactionSummaryFallback)。
19750
+ // desktop/其他 host 不带该字段 → undefined → 自动压缩行为与注入前完全一致,
19751
+ // 绝不在这里新建第二条摘要通道。
19752
+ summaryFallback: input.compactionSummaryFallback ?? readHostCompactionSummaryFallback(input.adapter),
19338
19753
  boundary: observationBoundary,
19339
19754
  getContextUsage: () => usageLedger.lastContextUsage(),
19340
19755
  getCanonicalCompactionHistory: () => [
@@ -19368,8 +19783,9 @@ async function runLocalAgentTurn(input) {
19368
19783
  let messages = builtMessages.messages;
19369
19784
  loopTiming.mark("buildMessages", 0);
19370
19785
  if (builtMessages.poisonDowngraded > 0) {
19786
+ const stats = builtMessages.poisonStats;
19371
19787
  console.warn(
19372
- `[nolo] downgraded ${builtMessages.poisonDowngraded} tool_call(s) with unparsable JSON arguments from outbound history (suspected upstream stream truncation); persisted history untouched`
19788
+ `[nolo] rewrote ${stats.rewrittenToolCalls} tool calls in ${stats.rewrittenAssistantMessages} assistant messages and ${stats.rewrittenToolMessages} tool-result messages (bad arguments: truncated=${stats.badArguments.truncated}, malformed=${stats.badArguments.malformed}); persisted history untouched`
19373
19789
  );
19374
19790
  }
19375
19791
  const supportsImages = resolveAgentImageInputSupport({
@@ -19429,19 +19845,27 @@ async function runLocalAgentTurn(input) {
19429
19845
  return false;
19430
19846
  }
19431
19847
  let injected = false;
19432
- for (const text of pending) {
19433
- if (typeof text !== "string" || !text.trim()) continue;
19434
- const safeText = scrubSecrets(text).cleaned;
19848
+ for (const text2 of pending) {
19849
+ if (typeof text2 !== "string" || !text2.trim()) continue;
19850
+ const safeText = scrubSecrets(text2).cleaned;
19435
19851
  transcript.push({ role: "user", content: safeText });
19436
19852
  injected = true;
19437
19853
  }
19438
19854
  return injected;
19439
19855
  };
19856
+ const newDialogTelemetryKey = `new:${crypto.randomUUID()}`;
19857
+ let roundBudgetWarningInjected = false;
19858
+ const roundWarningThreshold = input.roundBudgetWarningThreshold ?? DEFAULT_ROUND_BUDGET_WARNING_THRESHOLD;
19440
19859
  while (true) {
19441
19860
  partialContent = "";
19442
19861
  throwIfAborted(input);
19443
19862
  loopTiming.mark("roundStart", round);
19444
19863
  applyPendingInjections();
19864
+ if (roundWarningThreshold > 0 && round >= roundWarningThreshold && !roundBudgetWarningInjected) {
19865
+ const prompt = buildRoundBudgetWarningPrompt(roundWarningThreshold);
19866
+ transcript.push({ role: "user", content: prompt });
19867
+ roundBudgetWarningInjected = true;
19868
+ }
19445
19869
  const preparedMessages = prepareMessagesForProviderCall(transcript.working());
19446
19870
  const baseRequestMessages = filterImagePartsFromMessages(
19447
19871
  preparedMessages.messages,
@@ -19470,6 +19894,13 @@ async function runLocalAgentTurn(input) {
19470
19894
  input.onToolEvent || input.onObservationEvent || input.observationBoundary
19471
19895
  );
19472
19896
  let llmStreamActivity = 0;
19897
+ const callStartedAt = Date.now();
19898
+ const requestTelemetry = buildRequestTelemetry({
19899
+ // 无 dialogId 的新对话用本 turn 唯一键,避免不同新对话共用一个槽误报漂移。
19900
+ dialogKey: input.continueDialogId || newDialogTelemetryKey,
19901
+ messages: requestMessages,
19902
+ toolsHash: safeToolNamesHash(agentTools)
19903
+ });
19473
19904
  result4 = await runCompleteWithTimeout({
19474
19905
  provider,
19475
19906
  messages: requestMessages,
@@ -19525,7 +19956,8 @@ async function runLocalAgentTurn(input) {
19525
19956
  usageLedger.recordProviderCall({
19526
19957
  usage: result4.usage,
19527
19958
  model: result4.model,
19528
- provider: result4.provider
19959
+ provider: result4.provider,
19960
+ telemetry: finalizeCallTelemetry(requestTelemetry, result4, round, callStartedAt)
19529
19961
  });
19530
19962
  const assistantGuardVerdict = progressGuard.observeAssistantResponse(result4);
19531
19963
  if (assistantGuardVerdict.action === "stall") {
@@ -19654,8 +20086,9 @@ ${reasoningTailLog}
19654
20086
  });
19655
20087
  const executedToolResults = [];
19656
20088
  loopTiming.mark("toolLoopStart", round);
19657
- for (const toolCall of toolCalls) {
19658
- const toolName = toolCall.function.name;
20089
+ const slotResults = new Array(toolCalls.length);
20090
+ const runSingleTool = async ({ index, toolCall }) => {
20091
+ throwIfAborted(input);
19659
20092
  const { toolResult, toolExecMs } = await executeToolCall({
19660
20093
  input,
19661
20094
  toolCall,
@@ -19670,6 +20103,17 @@ ${reasoningTailLog}
19670
20103
  ...result4.finish_reason ? { finishReason: result4.finish_reason } : {}
19671
20104
  }
19672
20105
  });
20106
+ slotResults[index] = {
20107
+ toolName: toolCall.function.name,
20108
+ toolResult,
20109
+ toolExecMs
20110
+ };
20111
+ };
20112
+ const commitSlot = (i) => {
20113
+ const toolCall = toolCalls[i];
20114
+ const slot = slotResults[i];
20115
+ if (!slot) return;
20116
+ const { toolName, toolResult, toolExecMs } = slot;
19673
20117
  const observedMetadata = toolExecMs === void 0 ? toolResult.metadata : { ...toolResult.metadata ?? {}, [TOOL_DURATION_METADATA_KEY]: toolExecMs };
19674
20118
  executedToolResults.push({
19675
20119
  toolName,
@@ -19688,6 +20132,29 @@ ${reasoningTailLog}
19688
20132
  toolName,
19689
20133
  ...observedMetadata ? { tool_result_metadata: observedMetadata } : {}
19690
20134
  });
20135
+ };
20136
+ const batches = partitionToolCallsIntoBatches(toolCalls);
20137
+ for (const batch of batches) {
20138
+ throwIfAborted(input);
20139
+ let batchError;
20140
+ try {
20141
+ if (batch.isConcurrent && batch.calls.length > 1) {
20142
+ await Promise.all(batch.calls.map(runSingleTool));
20143
+ } else {
20144
+ for (const call of batch.calls) {
20145
+ await runSingleTool(call);
20146
+ }
20147
+ }
20148
+ } catch (error) {
20149
+ batchError = error;
20150
+ } finally {
20151
+ for (const call of batch.calls) {
20152
+ if (slotResults[call.index]) {
20153
+ commitSlot(call.index);
20154
+ }
20155
+ }
20156
+ }
20157
+ if (batchError) throw batchError;
19691
20158
  }
19692
20159
  const toolExecutionGuardVerdict = progressGuard.observeToolExecution(
19693
20160
  toolCalls,
@@ -19779,7 +20246,8 @@ ${reasoningTailLog}
19779
20246
  ...input.spaceId ? { spaceId: input.spaceId } : {},
19780
20247
  ...input.category ? { category: input.category } : {},
19781
20248
  ...input.inheritedFromDialogKey ? { inheritedFromDialogKey: input.inheritedFromDialogKey } : {},
19782
- ...input.parentDialogId ? { parentDialogId: input.parentDialogId } : {}
20249
+ ...input.parentDialogId ? { parentDialogId: input.parentDialogId } : {},
20250
+ ...typeof input.freshDialog === "boolean" ? { freshDialog: input.freshDialog } : {}
19783
20251
  });
19784
20252
  loopTiming.mark("saveTurn", round);
19785
20253
  await loopTiming.flush();
@@ -19794,6 +20262,7 @@ ${reasoningTailLog}
19794
20262
  dialogId: saved.dialogId,
19795
20263
  title: saved.title,
19796
20264
  ...saved.titlePatchPromise ? { titlePatchPromise: saved.titlePatchPromise } : {},
20265
+ ...dialogHistorySource ? { dialogHistorySource } : {},
19797
20266
  turnMessages
19798
20267
  };
19799
20268
  } finally {
@@ -19845,6 +20314,18 @@ function responsesStreamFailureStatus(failure) {
19845
20314
  return 502;
19846
20315
  }
19847
20316
  var codexStreamFailureStatus = responsesStreamFailureStatus;
20317
+ function extractOutputTextFromItem(item) {
20318
+ const parts2 = item.content;
20319
+ if (!Array.isArray(parts2)) return "";
20320
+ return parts2.filter(
20321
+ (part) => !!part && typeof part === "object" && part.type === "output_text" && typeof part.text === "string"
20322
+ ).map((part) => String(part.text)).join("");
20323
+ }
20324
+ function hasNumericTokenCount(u) {
20325
+ return ["input_tokens", "output_tokens", "prompt_tokens", "completion_tokens", "total_tokens"].some(
20326
+ (key) => typeof u[key] === "number" && Number.isFinite(u[key])
20327
+ );
20328
+ }
19848
20329
  function createResponsesStreamCollector(callbacks) {
19849
20330
  let content = "";
19850
20331
  let reasoning = "";
@@ -19852,6 +20333,11 @@ function createResponsesStreamCollector(callbacks) {
19852
20333
  let usage;
19853
20334
  let failure;
19854
20335
  let completedResponse;
20336
+ const requireResponseCompleted = callbacks?.requireResponseCompleted === true;
20337
+ const authoritativeCompletedTools = callbacks?.authoritativeCompletedTools === true;
20338
+ const failurePrefix = callbacks?.fallbackPrefix ?? "Responses";
20339
+ let sawTerminalSuccess = false;
20340
+ let sawTerminalEvent = false;
19855
20341
  let sawMeaningfulDelta = false;
19856
20342
  const observeMeaningfulOnce = () => {
19857
20343
  if (sawMeaningfulDelta) return;
@@ -19859,18 +20345,38 @@ function createResponsesStreamCollector(callbacks) {
19859
20345
  callbacks?.onMeaningfulDelta?.();
19860
20346
  };
19861
20347
  const processEvent = (ev) => {
20348
+ callbacks?.onEvent?.(ev);
19862
20349
  if (isMeaningfulResponsesOutput(ev)) observeMeaningfulOnce();
19863
20350
  const type = String(ev.type ?? "");
19864
20351
  if (type === "error") {
19865
- failure = failure ?? toResponsesStreamFailure(ev.error, callbacks?.fallbackPrefix);
20352
+ sawTerminalEvent = true;
20353
+ failure = failure ?? toResponsesStreamFailure(ev.error, callbacks?.fallbackPrefix) ?? {
20354
+ code: "response_error",
20355
+ message: `${callbacks?.fallbackPrefix ?? "Responses"} upstream emitted an error event.`
20356
+ };
19866
20357
  return;
19867
20358
  }
19868
20359
  if (type === "response.failed" || type === "response.incomplete") {
20360
+ sawTerminalEvent = true;
19869
20361
  const response = ev.response;
19870
20362
  failure = failure ?? toResponsesStreamFailure(response?.error, callbacks?.fallbackPrefix) ?? toResponsesStreamFailure(response?.incomplete_details, callbacks?.fallbackPrefix) ?? {
19871
20363
  code: type === "response.incomplete" ? "response_incomplete" : "response_failed",
19872
20364
  message: `${callbacks?.fallbackPrefix ?? "Responses"} upstream ended with ${type}.`
19873
20365
  };
20366
+ {
20367
+ const u = response?.usage;
20368
+ if (u && hasNumericTokenCount(u)) {
20369
+ const prompt = typeof u.input_tokens === "number" ? u.input_tokens : typeof u.prompt_tokens === "number" ? u.prompt_tokens : 0;
20370
+ const completion = typeof u.output_tokens === "number" ? u.output_tokens : typeof u.completion_tokens === "number" ? u.completion_tokens : 0;
20371
+ usage = {
20372
+ prompt_tokens: prompt,
20373
+ completion_tokens: completion,
20374
+ total_tokens: typeof u.total_tokens === "number" ? u.total_tokens : prompt + completion,
20375
+ ...u.input_tokens_details && typeof u.input_tokens_details === "object" ? { input_tokens_details: u.input_tokens_details } : {},
20376
+ ...u.output_tokens_details && typeof u.output_tokens_details === "object" ? { output_tokens_details: u.output_tokens_details } : {}
20377
+ };
20378
+ }
20379
+ }
19874
20380
  return;
19875
20381
  }
19876
20382
  if (type === "response.output_text.delta" && typeof ev.delta === "string") {
@@ -19883,16 +20389,38 @@ function createResponsesStreamCollector(callbacks) {
19883
20389
  callbacks?.onReasoningDelta?.(ev.delta);
19884
20390
  return;
19885
20391
  }
19886
- if (type === "response.output_item.added" || type === "response.output_item.done" || type === "response.function_call_arguments.delta" || type === "response.function_call_arguments.done") {
20392
+ if (type === "response.output_item.added" || type === "response.output_item.done") {
20393
+ const item = ev.item;
20394
+ if (item?.type === "message" && !content) {
20395
+ const itemText = extractOutputTextFromItem(item);
20396
+ if (itemText) {
20397
+ content = itemText;
20398
+ callbacks?.onTextDelta?.(itemText);
20399
+ }
20400
+ }
20401
+ applyResponsesToolEvent(toolCallsAcc, ev);
20402
+ return;
20403
+ }
20404
+ if (type === "response.function_call_arguments.delta" || type === "response.function_call_arguments.done") {
19887
20405
  applyResponsesToolEvent(toolCallsAcc, ev);
19888
20406
  return;
19889
20407
  }
19890
20408
  if (type === "response.completed") {
20409
+ sawTerminalEvent = true;
19891
20410
  const response = ev.response;
20411
+ const envelopeStatus = response && typeof response.status === "string" ? response.status : void 0;
20412
+ if (requireResponseCompleted && envelopeStatus !== void 0 && envelopeStatus !== "completed") {
20413
+ failure = failure ?? {
20414
+ code: "response_not_completed",
20415
+ message: `${failurePrefix} emitted response.completed with status "${envelopeStatus}".`
20416
+ };
20417
+ return;
20418
+ }
20419
+ sawTerminalSuccess = true;
19892
20420
  if (response) {
19893
20421
  completedResponse = response;
19894
20422
  const u = response.usage;
19895
- if (u) {
20423
+ if (u && hasNumericTokenCount(u)) {
19896
20424
  const prompt = typeof u.input_tokens === "number" ? u.input_tokens : typeof u.prompt_tokens === "number" ? u.prompt_tokens : 0;
19897
20425
  const completion = typeof u.output_tokens === "number" ? u.output_tokens : typeof u.completion_tokens === "number" ? u.completion_tokens : 0;
19898
20426
  const total = typeof u.total_tokens === "number" ? u.total_tokens : prompt + completion;
@@ -19913,29 +20441,58 @@ function createResponsesStreamCollector(callbacks) {
19913
20441
  let finalToolCalls = finalizeResponsesToolCalls(toolCallsAcc);
19914
20442
  if (completedResponse) {
19915
20443
  const completedToolCalls = extractToolCallsFromResponseOutput(completedResponse);
19916
- const completedById = new Map(completedToolCalls.map((call) => [call.id, call]));
19917
- finalToolCalls = finalToolCalls.map((call) => {
19918
- const completed = completedById.get(call.id);
19919
- if (!completed) return call;
19920
- let accumulatedParses = false;
19921
- let completedParses = false;
19922
- try {
19923
- JSON.parse(call.function.arguments);
19924
- accumulatedParses = true;
19925
- } catch {
20444
+ if (authoritativeCompletedTools) {
20445
+ for (const call of completedToolCalls) {
20446
+ const name = call.function?.name;
20447
+ if (typeof name !== "string" || !name.trim()) {
20448
+ failure = failure ?? {
20449
+ code: "tool_name_missing",
20450
+ message: `${failurePrefix} response.completed contained a tool call without a name.`
20451
+ };
20452
+ break;
20453
+ }
20454
+ const args2 = call.function?.arguments;
20455
+ if (typeof args2 === "string" && args2.trim()) {
20456
+ try {
20457
+ JSON.parse(args2);
20458
+ } catch {
20459
+ failure = failure ?? {
20460
+ code: "tool_arguments_malformed",
20461
+ message: `${failurePrefix} response.completed contained malformed tool arguments for "${name}".`
20462
+ };
20463
+ break;
20464
+ }
20465
+ }
19926
20466
  }
19927
- try {
19928
- JSON.parse(completed.function.arguments);
19929
- completedParses = true;
19930
- } catch {
20467
+ finalToolCalls = completedToolCalls;
20468
+ } else {
20469
+ const completedById = new Map(completedToolCalls.map((call) => [call.id, call]));
20470
+ finalToolCalls = finalToolCalls.map((call) => {
20471
+ const completed = completedById.get(call.id);
20472
+ if (!completed) return call;
20473
+ let accumulatedParses = false;
20474
+ let completedParses = false;
20475
+ try {
20476
+ JSON.parse(call.function.arguments);
20477
+ accumulatedParses = true;
20478
+ } catch {
20479
+ }
20480
+ try {
20481
+ JSON.parse(completed.function.arguments);
20482
+ completedParses = true;
20483
+ } catch {
20484
+ return call;
20485
+ }
20486
+ if (completedParses && (!accumulatedParses || completed.function.arguments.startsWith(call.function.arguments))) {
20487
+ return { ...call, function: { ...call.function, arguments: completed.function.arguments } };
20488
+ }
19931
20489
  return call;
19932
- }
19933
- if (completedParses && (!accumulatedParses || completed.function.arguments.startsWith(call.function.arguments))) {
19934
- return { ...call, function: { ...call.function, arguments: completed.function.arguments } };
19935
- }
19936
- return call;
19937
- });
19938
- if (finalToolCalls.length === 0) finalToolCalls = completedToolCalls;
20490
+ });
20491
+ if (finalToolCalls.length === 0) finalToolCalls = completedToolCalls;
20492
+ }
20493
+ }
20494
+ if (authoritativeCompletedTools && failure) {
20495
+ finalToolCalls = [];
19939
20496
  }
19940
20497
  if (!content && completedResponse) {
19941
20498
  content = extractTextFromResponseOutput(completedResponse);
@@ -19943,35 +20500,42 @@ function createResponsesStreamCollector(callbacks) {
19943
20500
  if (!reasoning && completedResponse) {
19944
20501
  reasoning = extractReasoningFromResponseOutput(completedResponse);
19945
20502
  }
20503
+ const streamComplete = sawTerminalSuccess;
20504
+ const streamTruncated = !sawTerminalEvent;
19946
20505
  return {
19947
20506
  content,
19948
20507
  ...reasoning ? { reasoning_content: reasoning } : {},
19949
20508
  ...finalToolCalls.length > 0 ? { tool_calls: finalToolCalls } : {},
19950
20509
  ...usage ? { usage } : {},
19951
- finish_reason: finalToolCalls.length > 0 ? "tool_calls" : "stop",
19952
- stream_complete: true,
20510
+ ...streamComplete ? { finish_reason: finalToolCalls.length > 0 ? "tool_calls" : "stop" } : {},
20511
+ ...streamComplete ? { stream_complete: true } : {},
20512
+ ...streamTruncated ? { stream_truncated: true } : {},
20513
+ ...completedResponse ? { completed_response: completedResponse } : {},
20514
+ ...completedResponse && typeof completedResponse.id === "string" ? { response_id: completedResponse.id } : {},
19953
20515
  ...failure ? { failure } : {}
19954
20516
  };
19955
20517
  };
19956
20518
  return {
19957
20519
  processEvent,
19958
- finalize: finalize2
19959
- };
19960
- }
19961
- function aggregateResponsesStream(events, options) {
19962
- const collector = createResponsesStreamCollector({
19963
- fallbackPrefix: options?.fallbackPrefix ?? "Codex",
19964
- ...options?.onMeaningfulDelta ? { onMeaningfulDelta: options.onMeaningfulDelta } : {}
19965
- });
19966
- for (const ev of events) {
19967
- collector.processEvent(ev);
19968
- }
19969
- const finalized = collector.finalize();
19970
- return {
19971
- text: finalized.content,
19972
- toolCalls: finalized.tool_calls ?? [],
19973
- usage: finalized.usage,
19974
- failure: finalized.failure
20520
+ finalize: finalize2,
20521
+ /**
20522
+ * legacy 非标准 provider 可能沿用 chat wire 的 `[DONE]` 收尾哨兵。
20523
+ * 原生 Responses(含 CF)的权威终局仍是 response.completed —— 认这个哨兵
20524
+ * 不为 CF 开豁免:CF 被截断时既没有 completed 也没有 [DONE]。
20525
+ */
20526
+ markLegacyDone: () => {
20527
+ sawTerminalEvent = true;
20528
+ if (requireResponseCompleted) {
20529
+ if (!sawTerminalSuccess) {
20530
+ failure = failure ?? {
20531
+ code: "response_completed_missing",
20532
+ message: `${failurePrefix} upstream ended with a legacy [DONE] sentinel without response.completed.`
20533
+ };
20534
+ }
20535
+ return;
20536
+ }
20537
+ sawTerminalSuccess = true;
20538
+ }
19975
20539
  };
19976
20540
  }
19977
20541
  async function readResponsesSseCompletion(args2) {
@@ -19979,10 +20543,18 @@ async function readResponsesSseCompletion(args2) {
19979
20543
  onTextDelta: args2.onTextDelta,
19980
20544
  onReasoningDelta: args2.onReasoningDelta,
19981
20545
  ...args2.onMeaningfulDelta ? { onMeaningfulDelta: args2.onMeaningfulDelta } : {},
19982
- fallbackPrefix: args2.fallbackPrefix ?? "Responses"
20546
+ ...args2.onEvent ? { onEvent: args2.onEvent } : {},
20547
+ fallbackPrefix: args2.fallbackPrefix ?? "Responses",
20548
+ ...args2.requireResponseCompleted === true ? { requireResponseCompleted: true } : {},
20549
+ ...args2.authoritativeCompletedTools === true ? { authoritativeCompletedTools: true } : {}
19983
20550
  });
19984
20551
  for await (const frame of readSseFrames(args2.response)) {
19985
20552
  for (const line of frame.split("\n")) {
20553
+ const trimmedLine = line.trim();
20554
+ if (trimmedLine === "[DONE]" || trimmedLine === "data: [DONE]") {
20555
+ collector.markLegacyDone();
20556
+ continue;
20557
+ }
19986
20558
  const parsed = parseSseDataLineObject(line);
19987
20559
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
19988
20560
  collector.processEvent(parsed);
@@ -19996,6 +20568,7 @@ async function readResponsesSseCompletion(args2) {
19996
20568
  err.status = status;
19997
20569
  err.code = result4.failure.code;
19998
20570
  err.type = result4.failure.type;
20571
+ if (result4.usage) err.usage = result4.usage;
19999
20572
  throw err;
20000
20573
  }
20001
20574
  return result4;
@@ -20009,6 +20582,25 @@ function resolveOpenAiCompatibleWire(config) {
20009
20582
  if (config.wire) return config.wire;
20010
20583
  return isOpenAiResponsesEndpoint(config.endpoint) ? "responses" : "chat.completions";
20011
20584
  }
20585
+ var PROMPT_CACHE_KEY_PROVIDERS = /* @__PURE__ */ new Set([
20586
+ "opendesign",
20587
+ "opencode",
20588
+ "opencode-go",
20589
+ "runinfra",
20590
+ "deepseek",
20591
+ "openai"
20592
+ ]);
20593
+ function shouldInjectDirectPromptCacheKey(provider, endpoint) {
20594
+ const p = (provider || "").toLowerCase().trim();
20595
+ if (PROMPT_CACHE_KEY_PROVIDERS.has(p)) return true;
20596
+ if (typeof endpoint === "string") {
20597
+ const ep = endpoint.toLowerCase();
20598
+ if (ep.includes("open-design.ai") || ep.includes("opencode.ai") || ep.includes("deepseek.com")) {
20599
+ return true;
20600
+ }
20601
+ }
20602
+ return false;
20603
+ }
20012
20604
  function buildOpenAiCompatibleChatCompletionRequest(args2) {
20013
20605
  const isResponses = resolveOpenAiCompatibleWire(args2.providerConfig) === "responses";
20014
20606
  const shouldStripReasoning = shouldStripReasoningContentForOutbound(
@@ -20017,7 +20609,13 @@ function buildOpenAiCompatibleChatCompletionRequest(args2) {
20017
20609
  );
20018
20610
  const sanitizedMessages = sanitizeForOutbound(args2.messages, args2.tools);
20019
20611
  const messages = toOpenAiCompatibleMessages(sanitizedMessages, {
20020
- stripReasoningContent: shouldStripReasoning
20612
+ stripReasoningContent: shouldStripReasoning,
20613
+ ...args2.shapingCounts ? { shapingCounts: args2.shapingCounts } : {},
20614
+ ...resolveReasoningReplayOptions(
20615
+ args2.providerConfig.provider,
20616
+ args2.providerConfig.model,
20617
+ shouldStripReasoning
20618
+ )
20021
20619
  });
20022
20620
  const rawBody = {
20023
20621
  model: args2.providerConfig.model,
@@ -20032,7 +20630,20 @@ function buildOpenAiCompatibleChatCompletionRequest(args2) {
20032
20630
  ...!isResponses && args2.stream ? getUsageRequestOptions(args2.providerConfig.usageProvider ?? args2.providerConfig.provider, {
20033
20631
  api: "chat-completions",
20034
20632
  endpoint: args2.providerConfig.endpoint
20035
- }) : {}
20633
+ }) : {},
20634
+ ...!isResponses && !args2.providerConfig.requestOptions?.prompt_cache_key && shouldInjectDirectPromptCacheKey(args2.providerConfig.provider, args2.providerConfig.endpoint) ? {
20635
+ // 直连通道缓存路由亲和:第三方聚合网关(OpenDesign、OpenCode、RunInfra 等)
20636
+ // 后端是多副本集群,前缀缓存是单节点本地的。键取跨轮稳定的部分(model + system 稳定前缀 + 工具定义),
20637
+ // 确保同任务多轮工具循环始终打到同一后端缓存节点,避免缓存漂移击穿。
20638
+ prompt_cache_key: stablePromptCacheKey(
20639
+ [
20640
+ args2.providerConfig.model,
20641
+ collectStablePromptPrefix(sanitizedMessages),
20642
+ args2.tools ?? []
20643
+ ],
20644
+ "nolo-direct"
20645
+ )
20646
+ } : {}
20036
20647
  };
20037
20648
  const body = isResponses ? buildResponsesRequestBody(rawBody, args2.providerConfig.model) : normalizeChatCompletionsBodyForProvider({
20038
20649
  body: rawBody,
@@ -20040,6 +20651,10 @@ function buildOpenAiCompatibleChatCompletionRequest(args2) {
20040
20651
  model: args2.providerConfig.model,
20041
20652
  endpoint: args2.providerConfig.endpoint
20042
20653
  });
20654
+ if (args2.requestFacts) {
20655
+ applyShapingCounts(args2.requestFacts, args2.shapingCounts);
20656
+ writeThinkingFacts(args2.requestFacts, body);
20657
+ }
20043
20658
  const endpoint = args2.providerConfig.endpoint;
20044
20659
  const headers = {
20045
20660
  "Content-Type": "application/json",
@@ -20128,6 +20743,9 @@ async function readOpenAiCompatibleSseCompletion(args2) {
20128
20743
  };
20129
20744
  for await (const frame of readSseFrames(args2.response)) {
20130
20745
  processOpenAiCompatibleSseEvent(frame, state);
20746
+ if (state.sawDone) {
20747
+ break;
20748
+ }
20131
20749
  }
20132
20750
  flushChatCompletionStream(state);
20133
20751
  throwIfChatCompletionStreamFailed(state);
@@ -20146,14 +20764,32 @@ async function executeOpenAiCompatibleChatCompletion(args2) {
20146
20764
  const isResponses = resolveOpenAiCompatibleWire(args2.providerConfig) === "responses";
20147
20765
  const timingTracker = createProviderCallTimingTracker();
20148
20766
  const observeMeaningful = () => observeMeaningfulProviderResponse(timingTracker);
20767
+ const diagnosticSink = resolveOutboundDiagnosticSink(args2.outboundDiagnostics);
20768
+ const diagnosticCall = diagnosticSink ? createOutboundDiagnosticCall({
20769
+ sink: diagnosticSink,
20770
+ seam: "local-runtime",
20771
+ host: args2.outboundDiagnostics?.host ?? "client",
20772
+ wire: isResponses ? "responses" : "chat.completions",
20773
+ provider: args2.providerConfig.provider,
20774
+ model: args2.providerConfig.model,
20775
+ callId: args2.outboundDiagnostics?.callId
20776
+ }) : void 0;
20777
+ let diagnosticAttempt = 0;
20778
+ const terminalIfEnabled = (outcome) => diagnosticCall?.terminal(diagnosticAttempt, outcome);
20149
20779
  const send = async (apiKey2) => {
20780
+ diagnosticAttempt += 1;
20781
+ const requestFacts = diagnosticCall ? createRequestFacts() : void 0;
20782
+ if (requestFacts) attachProjectionFacts(requestFacts, args2.projectionStats);
20150
20783
  const request3 = buildOpenAiCompatibleChatCompletionRequest({
20151
20784
  providerConfig: apiKey2 === args2.providerConfig.apiKey ? args2.providerConfig : { ...args2.providerConfig, apiKey: apiKey2 },
20152
20785
  messages: args2.messages,
20153
20786
  tools: args2.tools,
20154
20787
  stream: args2.stream,
20155
- sessionId: args2.sessionId
20788
+ sessionId: args2.sessionId,
20789
+ ...requestFacts ? { requestFacts } : {},
20790
+ ...requestFacts ? { shapingCounts: createRequestShapingCounts() } : {}
20156
20791
  });
20792
+ if (requestFacts) diagnosticCall?.dispatch(diagnosticAttempt, requestFacts);
20157
20793
  return args2.fetchImpl(request3.url, {
20158
20794
  ...request3.init,
20159
20795
  ...args2.signal ? { signal: args2.signal } : {}
@@ -20163,30 +20799,57 @@ async function executeOpenAiCompatibleChatCompletion(args2) {
20163
20799
  if (args2.resolveApiKey) {
20164
20800
  apiKey = await args2.resolveApiKey({ force: false }) ?? apiKey;
20165
20801
  }
20166
- let res = await send(apiKey);
20802
+ let res;
20803
+ try {
20804
+ res = await send(apiKey);
20805
+ } catch (error) {
20806
+ terminalIfEnabled(isAbortError(error) ? "aborted" : "network-error");
20807
+ throw error;
20808
+ }
20167
20809
  if (res.status === 401 && args2.resolveApiKey) {
20168
20810
  const refreshed = await args2.resolveApiKey({ force: true });
20169
20811
  if (refreshed && refreshed !== apiKey) {
20812
+ diagnosticCall?.httpResult(diagnosticAttempt, {
20813
+ status: res.status,
20814
+ headers: res.headers,
20815
+ errorCategory: "authentication"
20816
+ });
20817
+ terminalIfEnabled("http-error");
20170
20818
  await res.body?.cancel().catch(() => {
20171
20819
  });
20172
- res = await send(refreshed);
20820
+ try {
20821
+ res = await send(refreshed);
20822
+ } catch (error) {
20823
+ terminalIfEnabled(isAbortError(error) ? "aborted" : "network-error");
20824
+ throw error;
20825
+ }
20173
20826
  }
20174
20827
  }
20175
20828
  if (!res.ok) {
20176
- const raw2 = await res.text().catch(() => "");
20177
- let parsed = raw2;
20829
+ const raw = await res.text().catch(() => "");
20830
+ let parsed = raw;
20178
20831
  try {
20179
- parsed = JSON.parse(raw2);
20832
+ parsed = JSON.parse(raw);
20180
20833
  } catch {
20181
20834
  }
20835
+ diagnosticCall?.httpResult(diagnosticAttempt, {
20836
+ status: res.status,
20837
+ headers: res.headers,
20838
+ errorCategory: classifyOutboundErrorCategory(res.status, raw)
20839
+ });
20840
+ terminalIfEnabled("http-error");
20182
20841
  await args2.onHttpResult?.({ status: res.status, body: parsed });
20183
20842
  throw providerHttpFailure({
20184
20843
  label: "local provider",
20185
20844
  status: res.status,
20186
- raw: raw2,
20845
+ raw,
20187
20846
  messages: args2.messages
20188
20847
  });
20189
20848
  }
20849
+ diagnosticCall?.httpResult(diagnosticAttempt, {
20850
+ status: res.status,
20851
+ headers: res.headers
20852
+ });
20190
20853
  await args2.onHttpResult?.({ status: res.status });
20191
20854
  const contentType = res.headers.get("content-type") ?? "";
20192
20855
  const isEventStream = contentType.includes("text/event-stream");
@@ -20195,65 +20858,73 @@ async function executeOpenAiCompatibleChatCompletion(args2) {
20195
20858
  result4.usage,
20196
20859
  finalizeProviderCallTiming(timingTracker)
20197
20860
  );
20861
+ terminalIfEnabled("completed");
20198
20862
  return { ...result4, ...usage ? { usage } : {} };
20199
20863
  };
20200
- if (isEventStream) {
20201
- if (isResponses) {
20202
- const streamed2 = await readResponsesSseCompletion({
20864
+ try {
20865
+ if (isEventStream) {
20866
+ if (isResponses) {
20867
+ const streamed2 = await readResponsesSseCompletion({
20868
+ response: res,
20869
+ ...args2.onTextDelta ? { onTextDelta: args2.onTextDelta } : {},
20870
+ ...args2.onReasoningDelta ? { onReasoningDelta: args2.onReasoningDelta } : {},
20871
+ onMeaningfulDelta: observeMeaningful
20872
+ });
20873
+ return withTiming({
20874
+ content: streamed2.content,
20875
+ model: args2.providerConfig.model,
20876
+ provider: args2.providerConfig.provider,
20877
+ ...streamed2.tool_calls ? { tool_calls: streamed2.tool_calls } : {},
20878
+ ...streamed2.reasoning_content ? { reasoning_content: streamed2.reasoning_content } : {},
20879
+ ...streamed2.usage ? { usage: streamed2.usage } : {},
20880
+ ...streamed2.stream_complete ? { stream_complete: true } : {},
20881
+ ...streamed2.finish_reason ? { finish_reason: streamed2.finish_reason } : {},
20882
+ trace: args2.messages
20883
+ });
20884
+ }
20885
+ const streamed = await readOpenAiCompatibleSseCompletion({
20203
20886
  response: res,
20204
20887
  ...args2.onTextDelta ? { onTextDelta: args2.onTextDelta } : {},
20205
20888
  ...args2.onReasoningDelta ? { onReasoningDelta: args2.onReasoningDelta } : {},
20206
20889
  onMeaningfulDelta: observeMeaningful
20207
20890
  });
20208
20891
  return withTiming({
20209
- content: streamed2.content,
20892
+ content: streamed.content,
20210
20893
  model: args2.providerConfig.model,
20211
20894
  provider: args2.providerConfig.provider,
20212
- ...streamed2.tool_calls ? { tool_calls: streamed2.tool_calls } : {},
20213
- ...streamed2.reasoning_content ? { reasoning_content: streamed2.reasoning_content } : {},
20214
- ...streamed2.usage ? { usage: streamed2.usage } : {},
20215
- ...streamed2.stream_complete ? { stream_complete: true } : {},
20216
- ...streamed2.finish_reason ? { finish_reason: streamed2.finish_reason } : {},
20895
+ ...streamed.tool_calls ? { tool_calls: streamed.tool_calls } : {},
20896
+ ...streamed.reasoning_content ? { reasoning_content: streamed.reasoning_content } : {},
20897
+ ...streamed.usage ? { usage: streamed.usage } : {},
20898
+ ...streamed.stream_complete ? { stream_complete: true } : {},
20899
+ ...streamed.finish_reason ? { finish_reason: streamed.finish_reason } : {},
20217
20900
  trace: args2.messages
20218
20901
  });
20219
20902
  }
20220
- const streamed = await readOpenAiCompatibleSseCompletion({
20221
- response: res,
20222
- ...args2.onTextDelta ? { onTextDelta: args2.onTextDelta } : {},
20223
- ...args2.onReasoningDelta ? { onReasoningDelta: args2.onReasoningDelta } : {},
20224
- onMeaningfulDelta: observeMeaningful
20225
- });
20226
- return withTiming({
20227
- content: streamed.content,
20228
- model: args2.providerConfig.model,
20229
- provider: args2.providerConfig.provider,
20230
- ...streamed.tool_calls ? { tool_calls: streamed.tool_calls } : {},
20231
- ...streamed.reasoning_content ? { reasoning_content: streamed.reasoning_content } : {},
20232
- ...streamed.usage ? { usage: streamed.usage } : {},
20233
- ...streamed.stream_complete ? { stream_complete: true } : {},
20234
- ...streamed.finish_reason ? { finish_reason: streamed.finish_reason } : {},
20235
- trace: args2.messages
20236
- });
20237
- }
20238
- const raw = await res.text().catch(() => "");
20239
- let data = {};
20240
- try {
20241
- data = raw ? JSON.parse(raw) : {};
20242
- } catch {
20243
- data = {};
20244
- }
20245
- if (isResponses) {
20246
- return withTiming(parseOpenAiCompatibleResponsesResponse({
20903
+ const raw = await res.text().catch(() => "");
20904
+ let data = {};
20905
+ try {
20906
+ data = raw ? JSON.parse(raw) : {};
20907
+ } catch {
20908
+ data = {};
20909
+ }
20910
+ if (isResponses) {
20911
+ return withTiming(parseOpenAiCompatibleResponsesResponse({
20912
+ providerConfig: args2.providerConfig,
20913
+ data,
20914
+ trace: args2.messages
20915
+ }));
20916
+ }
20917
+ return withTiming(parseOpenAiCompatibleChatCompletionResponse({
20247
20918
  providerConfig: args2.providerConfig,
20248
20919
  data,
20249
20920
  trace: args2.messages
20250
20921
  }));
20922
+ } catch (error) {
20923
+ terminalIfEnabled(
20924
+ args2.signal?.aborted || isAbortError(error) ? "aborted" : "stream-error"
20925
+ );
20926
+ throw error;
20251
20927
  }
20252
- return withTiming(parseOpenAiCompatibleChatCompletionResponse({
20253
- providerConfig: args2.providerConfig,
20254
- data,
20255
- trace: args2.messages
20256
- }));
20257
20928
  }
20258
20929
 
20259
20930
  // packages/agent-runtime/openAiCompatibleProviderConfig.ts
@@ -20405,24 +21076,24 @@ public class NoloCredNative {
20405
21076
  `.trim();
20406
21077
 
20407
21078
  // packages/agent-runtime/dialogMessageRecord.ts
20408
- function dialogMessageRecordToAgentRuntimeMessage(record) {
20409
- if (!record || typeof record !== "object") return null;
20410
- if (record.role !== "user" && record.role !== "assistant" && record.role !== "tool") return null;
21079
+ function dialogMessageRecordToAgentRuntimeMessage(record2) {
21080
+ if (!record2 || typeof record2 !== "object") return null;
21081
+ if (record2.role !== "user" && record2.role !== "assistant" && record2.role !== "tool") return null;
20411
21082
  return {
20412
- role: record.role,
20413
- content: record.content ?? "",
20414
- ...record.contextReference !== void 0 ? { context_reference: record.contextReference } : {},
20415
- ...typeof record.reasoning_content === "string" ? { reasoning_content: record.reasoning_content } : {},
20416
- ...typeof record.toolCallId === "string" ? { tool_call_id: record.toolCallId } : {},
20417
- ...Array.isArray(record.tool_calls) ? { tool_calls: record.tool_calls } : {},
20418
- ...typeof record.toolName === "string" ? { toolName: record.toolName } : {},
21083
+ role: record2.role,
21084
+ content: record2.content ?? "",
21085
+ ...record2.contextReference !== void 0 ? { context_reference: record2.contextReference } : {},
21086
+ ...typeof record2.reasoning_content === "string" ? { reasoning_content: record2.reasoning_content } : {},
21087
+ ...typeof record2.toolCallId === "string" ? { tool_call_id: record2.toolCallId } : {},
21088
+ ...Array.isArray(record2.tool_calls) ? { tool_calls: record2.tool_calls } : {},
21089
+ ...typeof record2.toolName === "string" ? { toolName: record2.toolName } : {},
20419
21090
  // 写侧(dialogWritePlan)把 tool_result_metadata 以 `metadata` 字段落库;
20420
21091
  // 回读必须还原,否则跨 turn 投影丢失 metadata 后缀,同一 tool 消息的字节
20421
21092
  // 在 turn 边界漂移 → provider 前缀缓存断裂(stable projection 契约,
20422
21093
  // 见 docs/plans/2026-09-05-tool-output-cache-stability.md)。
20423
- ...record.metadata && typeof record.metadata === "object" ? { tool_result_metadata: record.metadata } : {},
21094
+ ...record2.metadata && typeof record2.metadata === "object" ? { tool_result_metadata: record2.metadata } : {},
20424
21095
  ...(() => {
20425
- const raw = record.createdAt;
21096
+ const raw = record2.createdAt;
20426
21097
  const ms = typeof raw === "number" ? raw : typeof raw === "string" ? Date.parse(raw) : NaN;
20427
21098
  return Number.isFinite(ms) ? { createdAt: ms } : {};
20428
21099
  })()
@@ -20512,11 +21183,24 @@ function mergeSubjectRefs(...groups) {
20512
21183
  function buildRuntimeSubjectRefs(runtimeContext) {
20513
21184
  return mergeSubjectRefs(runtimeContext?.subjectRefs);
20514
21185
  }
21186
+ function withoutSelfLineage(record2, dialogId) {
21187
+ const next = { ...record2 };
21188
+ for (const field of ["parentDialogId", "rootDialogId"]) {
21189
+ if (asOptionalTrimmedString(next[field]) === dialogId) delete next[field];
21190
+ }
21191
+ return next;
21192
+ }
20515
21193
  function buildDialogLineageFields(args2) {
21194
+ const selfDialogId = asOptionalTrimmedString(args2.dialogId);
20516
21195
  const inheritedFromDialogKey = asOptionalTrimmedString(args2.input.inheritedFromDialogKey);
20517
- const parentDialogId = asOptionalTrimmedString(args2.input.parentDialogId);
21196
+ const rawParentDialogId = asOptionalTrimmedString(args2.input.parentDialogId);
21197
+ const parentDialogId = rawParentDialogId && rawParentDialogId !== selfDialogId ? rawParentDialogId : void 0;
20518
21198
  if (!inheritedFromDialogKey && !parentDialogId) return {};
20519
- const rootDialogId = asOptionalTrimmedString(args2.existingDialog?.rootDialogId) ?? asOptionalTrimmedString(args2.existingDialog?.parentDialogId) ?? parentDialogId;
21199
+ const existingParentDialogId = asOptionalTrimmedString(args2.existingDialog?.parentDialogId);
21200
+ const existingRootDialogId = asOptionalTrimmedString(args2.existingDialog?.rootDialogId);
21201
+ const rootDialogId = [existingRootDialogId, existingParentDialogId, parentDialogId].find(
21202
+ (id) => id && id !== selfDialogId
21203
+ ) ?? void 0;
20520
21204
  return {
20521
21205
  ...inheritedFromDialogKey ? { inheritedFromDialogKey } : {},
20522
21206
  ...parentDialogId ? { parentDialogId } : {},
@@ -20616,7 +21300,7 @@ function buildAgentRuntimeDialogWritePlan(args2) {
20616
21300
  args2.existingDialog
20617
21301
  );
20618
21302
  const dialogRecord = {
20619
- ...asRecordOrEmpty(args2.existingDialog),
21303
+ ...withoutSelfLineage(asRecordOrEmpty(args2.existingDialog), dialogId),
20620
21304
  id: dialogId,
20621
21305
  dbKey: dialogKey,
20622
21306
  type: "dialog",
@@ -20638,7 +21322,8 @@ function buildAgentRuntimeDialogWritePlan(args2) {
20638
21322
  ...subjectRefs ? { subjectRefs } : {},
20639
21323
  ...buildDialogLineageFields({
20640
21324
  input: args2.input,
20641
- existingDialog: args2.existingDialog
21325
+ existingDialog: args2.existingDialog,
21326
+ dialogId
20642
21327
  }),
20643
21328
  ...typeof args2.input.result.toolCallCount === "number" ? { toolCallCount: args2.input.result.toolCallCount } : {},
20644
21329
  localRuntime: {
@@ -20680,22 +21365,22 @@ function resolveHybridServers(defaultServer, preferredServerOrigin, fallbackServ
20680
21365
  ].filter((value) => value.trim().length > 0);
20681
21366
  return [...new Set(raw.map(normalizeServerOrigin))];
20682
21367
  }
20683
- function normalizeHybridRecord(dbKey, record, serverOrigin) {
20684
- if (!record || typeof record !== "object") return null;
21368
+ function normalizeHybridRecord(dbKey, record2, serverOrigin) {
21369
+ if (!record2 || typeof record2 !== "object") return null;
20685
21370
  return {
20686
- ...record,
20687
- dbKey: typeof record.dbKey === "string" && record.dbKey ? record.dbKey : dbKey,
21371
+ ...record2,
21372
+ dbKey: typeof record2.dbKey === "string" && record2.dbKey ? record2.dbKey : dbKey,
20688
21373
  ...serverOrigin ? { serverOrigin } : {}
20689
21374
  };
20690
21375
  }
20691
- function getHybridRecordTimestamp(record) {
20692
- if (!record || typeof record !== "object") return 0;
21376
+ function getHybridRecordTimestamp(record2) {
21377
+ if (!record2 || typeof record2 !== "object") return 0;
20693
21378
  const candidates = [
20694
- record.updatedAt,
20695
- record.updated_at,
20696
- record.createdAt,
20697
- record.created,
20698
- record?.meta?.createdAt
21379
+ record2.updatedAt,
21380
+ record2.updated_at,
21381
+ record2.createdAt,
21382
+ record2.created,
21383
+ record2?.meta?.createdAt
20699
21384
  ];
20700
21385
  for (const candidate of candidates) {
20701
21386
  const asNumber = asOptionalPositiveFiniteNumber(candidate);
@@ -20707,10 +21392,10 @@ function getHybridRecordTimestamp(record) {
20707
21392
  }
20708
21393
  return 0;
20709
21394
  }
20710
- function isHybridTombstoneRecord(record) {
20711
- if (!record || typeof record !== "object") return false;
20712
- if (typeof record.deletedAt === "string") return record.deletedAt.trim().length > 0;
20713
- return Boolean(record.deletedAt);
21395
+ function isHybridTombstoneRecord(record2) {
21396
+ if (!record2 || typeof record2 !== "object") return false;
21397
+ if (typeof record2.deletedAt === "string") return record2.deletedAt.trim().length > 0;
21398
+ return Boolean(record2.deletedAt);
20714
21399
  }
20715
21400
  function shouldReplaceWithHybridRecord(nextRecord, currentRecord) {
20716
21401
  const nextTs = getHybridRecordTimestamp(nextRecord);
@@ -20794,8 +21479,8 @@ function createHybridRecordStore(deps) {
20794
21479
  }
20795
21480
  return normalizedLocalRecord ?? null;
20796
21481
  },
20797
- write: async (dbKey, record) => {
20798
- const nextRecord = normalizeHybridRecord(dbKey, record) ?? { dbKey };
21482
+ write: async (dbKey, record2) => {
21483
+ const nextRecord = normalizeHybridRecord(dbKey, record2) ?? { dbKey };
20799
21484
  await deps.db.put(dbKey, nextRecord);
20800
21485
  return nextRecord;
20801
21486
  },
@@ -20943,10 +21628,10 @@ function buildLoadSkillExecutor(args2) {
20943
21628
  metadata: { loadSkill: true, resolved: false }
20944
21629
  };
20945
21630
  }
20946
- const { resolveSkillByName } = await import("./skillDiscovery-ZBU76COE.js");
21631
+ const { resolveSkillByName } = await import("./skillDiscovery-QC35EQY4.js");
20947
21632
  const resolved = resolveSkillByName(args2.cwd, name);
20948
21633
  if (!resolved) {
20949
- const { resolveBuiltinSkillByName } = await import("./builtinSkillRegistry-ZMF6YGNO.js");
21634
+ const { resolveBuiltinSkillByName } = await import("./builtinSkillRegistry-TDQ6JB2E.js");
20950
21635
  const builtin = resolveBuiltinSkillByName(name);
20951
21636
  if (builtin) {
20952
21637
  return {
@@ -20989,7 +21674,7 @@ ${body}`,
20989
21674
  };
20990
21675
  }
20991
21676
  async function formatUnknownSkillMessage(cwd, requestedName) {
20992
- const { discoverSkills } = await import("./skillDiscovery-ZBU76COE.js");
21677
+ const { discoverSkills } = await import("./skillDiscovery-QC35EQY4.js");
20993
21678
  let available = [];
20994
21679
  try {
20995
21680
  available = discoverSkills(cwd).map((s) => s.name);
@@ -21004,8 +21689,27 @@ No skills were discovered in this workspace.`;
21004
21689
  Available skills: ${available.join(", ")}`;
21005
21690
  }
21006
21691
 
21692
+ // packages/agent-runtime/sessionIdentity.ts
21693
+ import { createHash as createHash7 } from "node:crypto";
21694
+ var SALT = "nolo-session-identity-v1";
21695
+ function isStableSessionIdentityEnabled(env = process.env) {
21696
+ const v = env.NOLO_STABLE_SESSION_IDENTITY?.trim().toLowerCase();
21697
+ return !(v === "0" || v === "false" || v === "off");
21698
+ }
21699
+ function deriveStableUuid(sessionKey, purpose) {
21700
+ const h = createHash7("sha256").update(`${SALT}\0${purpose}\0${sessionKey}`).digest();
21701
+ h[6] = h[6] & 15 | 80;
21702
+ h[8] = h[8] & 63 | 128;
21703
+ const x = h.subarray(0, 16).toString("hex");
21704
+ return `${x.slice(0, 8)}-${x.slice(8, 12)}-${x.slice(12, 16)}-${x.slice(16, 20)}-${x.slice(20, 32)}`;
21705
+ }
21706
+ function resolveSessionKey(args2) {
21707
+ const d = args2.dialogId?.trim();
21708
+ return d ? `dialog:${d}` : void 0;
21709
+ }
21710
+
21007
21711
  // packages/agent-runtime/devin/devinProvider.ts
21008
- import { randomBytes, randomUUID } from "node:crypto";
21712
+ import { randomBytes, randomUUID as randomUUID2 } from "node:crypto";
21009
21713
  var CLIENT_NAME = "chisel";
21010
21714
  var CLIENT_VERSION = "3000.11.3";
21011
21715
  function writeVarint(fieldNum, value) {
@@ -21064,10 +21768,13 @@ function encodeDevinClientMetadata(token) {
21064
21768
  writeStringField(31, generateFingerprint())
21065
21769
  ]);
21066
21770
  }
21067
- function encodeDevinChatMessage(msg) {
21771
+ function devinId(id, purpose) {
21772
+ return id?.sessionKey && isStableSessionIdentityEnabled() ? deriveStableUuid(id.sessionKey, purpose) : randomUUID2();
21773
+ }
21774
+ function encodeDevinChatMessage(msg, identity2, index = 0) {
21068
21775
  const source = msg.role === "assistant" ? 2 : msg.role === "tool" ? 4 : 1;
21069
21776
  return Buffer.concat([
21070
- writeStringField(1, randomUUID()),
21777
+ writeStringField(1, devinId(identity2, `msg:${index}:${msg.role}`)),
21071
21778
  writeVarint(2, source),
21072
21779
  writeStringField(3, msg.text),
21073
21780
  ...(msg.toolCalls ?? []).map(
@@ -21123,19 +21830,20 @@ function buildCompletionConfig(params) {
21123
21830
  }
21124
21831
  function buildGetChatMessageRequest(params) {
21125
21832
  const { token, messages, model, tools = [], temperature } = params;
21833
+ const identity2 = { sessionKey: params.sessionKey };
21126
21834
  const metadata = encodeDevinClientMetadata(token);
21127
21835
  let systemPrompt = "";
21128
21836
  const filteredMessages = [];
21129
21837
  for (const m of messages) {
21130
- const text = typeof m.content === "string" ? m.content : Array.isArray(m.content) ? m.content.filter((p) => p?.type === "text").map((p) => p.text).join("\n") : "";
21838
+ const text2 = typeof m.content === "string" ? m.content : Array.isArray(m.content) ? m.content.filter((p) => p?.type === "text").map((p) => p.text).join("\n") : "";
21131
21839
  if (m.role === "system") {
21132
21840
  systemPrompt = systemPrompt ? `${systemPrompt}
21133
21841
 
21134
- ${text}` : text;
21842
+ ${text2}` : text2;
21135
21843
  } else if (m.role === "user" || m.role === "assistant" || m.role === "tool") {
21136
21844
  filteredMessages.push({
21137
21845
  role: m.role,
21138
- text,
21846
+ text: text2,
21139
21847
  ...m.tool_call_id ? { toolCallId: m.tool_call_id } : {},
21140
21848
  ...m.tool_calls?.length ? { toolCalls: m.tool_calls } : {}
21141
21849
  });
@@ -21152,10 +21860,10 @@ ${item.text}`;
21152
21860
  coalesced.push({ ...item });
21153
21861
  }
21154
21862
  }
21155
- const chatMessageBuffers = coalesced.map((m) => encodeDevinChatMessage(m));
21863
+ const chatMessageBuffers = coalesced.map((m, i) => encodeDevinChatMessage(m, identity2, i));
21156
21864
  const completionConfig = buildCompletionConfig({ temperature });
21157
21865
  const modelConfig = Buffer.concat([
21158
- writeStringField(1, randomUUID()),
21866
+ writeStringField(1, randomUUID2()),
21159
21867
  writeVarint(2, 1),
21160
21868
  writeVarint(3, 4)
21161
21869
  ]);
@@ -21167,7 +21875,7 @@ ${item.text}`;
21167
21875
  writeMessageField(8, completionConfig),
21168
21876
  ...tools.map((tool) => writeMessageField(10, encodeDevinToolDefinition(tool))),
21169
21877
  writeMessageField(15, modelConfig),
21170
- writeStringField(16, randomUUID()),
21878
+ writeStringField(16, randomUUID2()),
21171
21879
  writeVarint(20, 1),
21172
21880
  writeStringField(21, model)
21173
21881
  ]);
@@ -21268,12 +21976,12 @@ function readDevinUpstreamFailure(error) {
21268
21976
  return failure;
21269
21977
  }
21270
21978
  function parseDevinConnectTrailer(payload) {
21271
- const text = payload.toString("utf8").trim();
21272
- if (!text || text === "{}") return null;
21979
+ const text2 = payload.toString("utf8").trim();
21980
+ if (!text2 || text2 === "{}") return null;
21273
21981
  try {
21274
- const parsed = JSON.parse(text);
21982
+ const parsed = JSON.parse(text2);
21275
21983
  if (!parsed.error) {
21276
- return new Error(`Devin Connect invalid terminal trailer: ${text}`);
21984
+ return new Error(`Devin Connect invalid terminal trailer: ${text2}`);
21277
21985
  }
21278
21986
  const code = typeof parsed.error.code === "string" ? parsed.error.code : "unknown";
21279
21987
  const message = typeof parsed.error.message === "string" ? parsed.error.message : "Unknown error";
@@ -21283,7 +21991,7 @@ function parseDevinConnectTrailer(payload) {
21283
21991
  code === "resource_exhausted" ? { status: 429, body: parsed.error } : void 0
21284
21992
  );
21285
21993
  } catch {
21286
- return new Error(`Devin Connect invalid terminal trailer: ${text}`);
21994
+ return new Error(`Devin Connect invalid terminal trailer: ${text2}`);
21287
21995
  }
21288
21996
  }
21289
21997
  function decodeDevinResponsePayload(payload) {
@@ -21328,13 +22036,13 @@ function decodeDevinResponsePayload(payload) {
21328
22036
  const valBytes = payload.subarray(offset, offset + len);
21329
22037
  offset += len;
21330
22038
  if (fieldNum === 3) {
21331
- const text = valBytes.toString("utf8");
21332
- content += text;
21333
- events.push({ type: "content", text });
22039
+ const text2 = valBytes.toString("utf8");
22040
+ content += text2;
22041
+ events.push({ type: "content", text: text2 });
21334
22042
  } else if (fieldNum === 9) {
21335
- const text = valBytes.toString("utf8");
21336
- reasoning += text;
21337
- events.push({ type: "reasoning", text });
22043
+ const text2 = valBytes.toString("utf8");
22044
+ reasoning += text2;
22045
+ events.push({ type: "reasoning", text: text2 });
21338
22046
  } else if (fieldNum === 6) {
21339
22047
  let nestedOffset = 0;
21340
22048
  let id;
@@ -21362,10 +22070,10 @@ function decodeDevinResponsePayload(payload) {
21362
22070
  }
21363
22071
  const nestedValue = valBytes.subarray(nestedOffset, nestedOffset + nestedLength);
21364
22072
  nestedOffset += nestedLength;
21365
- const text = nestedValue.toString("utf8");
21366
- if (nestedField === 1) id = text;
21367
- else if (nestedField === 2) name = text;
21368
- else if (nestedField === 3) argsDelta = (argsDelta ?? "") + text;
22073
+ const text2 = nestedValue.toString("utf8");
22074
+ if (nestedField === 1) id = text2;
22075
+ else if (nestedField === 2) name = text2;
22076
+ else if (nestedField === 3) argsDelta = (argsDelta ?? "") + text2;
21369
22077
  }
21370
22078
  if (id !== void 0 && name !== void 0) {
21371
22079
  events.push({ type: "tool-call-start", id, name });
@@ -21382,6 +22090,11 @@ function decodeDevinResponsePayload(payload) {
21382
22090
  }
21383
22091
  return { content, reasoning, finishReason, events, usage };
21384
22092
  }
22093
+ function isMeaningfulDevinEvent(event) {
22094
+ if (event.type === "reasoning" || event.type === "content") return event.text.length > 0;
22095
+ if (event.type === "tool-call-start") return !!event.name;
22096
+ return event.argsDelta.length > 0;
22097
+ }
21385
22098
  function createDevinProvider(options) {
21386
22099
  const { token, model = "swe-2-max", fetchImpl = fetch, tools = [], temperature } = options;
21387
22100
  return {
@@ -21393,9 +22106,11 @@ function createDevinProvider(options) {
21393
22106
  messages,
21394
22107
  model: selectedModel,
21395
22108
  tools,
21396
- temperature
22109
+ temperature,
22110
+ sessionKey: resolveSessionKey({ dialogId: opts?.dialogId })
21397
22111
  });
21398
22112
  const frame = buildDevinConnectFrame(requestPayload);
22113
+ const timingTracker = createProviderCallTimingTracker();
21399
22114
  const response = await fetchImpl(DEVIN_CONNECT_URL, {
21400
22115
  method: "POST",
21401
22116
  headers: {
@@ -21418,7 +22133,7 @@ function createDevinProvider(options) {
21418
22133
  if (!response.body) {
21419
22134
  throw new Error("Devin Connect response body is null");
21420
22135
  }
21421
- let text = "";
22136
+ let text2 = "";
21422
22137
  let reasoning = "";
21423
22138
  let finishReason = null;
21424
22139
  let frameUsage = null;
@@ -21449,10 +22164,11 @@ function createDevinProvider(options) {
21449
22164
  if (delta.usage?.completion !== void 0) {
21450
22165
  frameUsage = delta.usage;
21451
22166
  }
21452
- text += delta.content;
22167
+ text2 += delta.content;
21453
22168
  reasoning += delta.reasoning;
21454
22169
  if (delta.finishReason) finishReason = delta.finishReason;
21455
22170
  for (const event of delta.events) {
22171
+ if (isMeaningfulDevinEvent(event)) observeMeaningfulProviderResponse(timingTracker);
21456
22172
  if (event.type === "reasoning") {
21457
22173
  opts?.onReasoningDelta?.(event.text);
21458
22174
  } else if (event.type === "content") {
@@ -21498,18 +22214,19 @@ function createDevinProvider(options) {
21498
22214
  }
21499
22215
  };
21500
22216
  });
21501
- if (!text && !reasoning && toolCalls.length === 0) {
22217
+ if (!text2 && !reasoning && toolCalls.length === 0) {
21502
22218
  throw new Error("Devin Connect upstream completed without content or tool calls");
21503
22219
  }
21504
- const usage = frameUsage ? {
22220
+ const timing = finalizeProviderCallTiming(timingTracker);
22221
+ const usage = frameUsage ? withProviderCallTimingFields({
21505
22222
  prompt_tokens: frameUsage.prompt + frameUsage.cacheRead,
21506
22223
  completion_tokens: frameUsage.completion ?? 0,
21507
22224
  total_tokens: frameUsage.prompt + frameUsage.cacheRead + (frameUsage.completion ?? 0) + frameUsage.cacheWrite,
21508
22225
  ...frameUsage.cacheRead > 0 ? { prompt_tokens_details: { cached_tokens: frameUsage.cacheRead } } : {},
21509
22226
  ...frameUsage.cacheWrite > 0 ? { cache_creation_input_tokens: frameUsage.cacheWrite } : {}
21510
- } : void 0;
22227
+ }, timing) : void 0;
21511
22228
  return {
21512
- content: text,
22229
+ content: text2,
21513
22230
  model: selectedModel,
21514
22231
  provider: "devin",
21515
22232
  ...usage ? { usage } : {},
@@ -21528,6 +22245,73 @@ function createDevinProvider(options) {
21528
22245
  };
21529
22246
  }
21530
22247
 
22248
+ // packages/agent-runtime/executionLeaseValidation.ts
22249
+ import { isAbsolute as isAbsolute3, relative as relative4, resolve as resolve5 } from "node:path";
22250
+ function withinRoot(root, child) {
22251
+ if (!isAbsolute3(root) || !isAbsolute3(child)) return false;
22252
+ const canonicalRoot = resolve5(root);
22253
+ const canonicalChild = resolve5(child);
22254
+ const suffix = relative4(canonicalRoot, canonicalChild);
22255
+ return suffix === "" || suffix !== ".." && !suffix.startsWith("../") && !suffix.startsWith("..\\") && !isAbsolute3(suffix);
22256
+ }
22257
+ function subsetOf(allowed, actual) {
22258
+ return actual.every(
22259
+ (root) => isAbsolute3(root) && allowed.some((parent) => withinRoot(parent, root))
22260
+ );
22261
+ }
22262
+ function validateConfiguredRootExecutionLease(ticket, lease) {
22263
+ if (lease.ticketId !== ticket.ticketId || lease.grantId !== ticket.grantId || !lease.leaseId || !lease.backendId) return false;
22264
+ const { filesystem } = ticket;
22265
+ if (filesystem.readScope === "runtime-workspace" || filesystem.readScope === "legacy-unscoped" || filesystem.writeScope === "runtime-workspace") return false;
22266
+ if (filesystem.readScope === "none" && lease.readableRoots.length > 0) return false;
22267
+ if (filesystem.writeScope === "none" && lease.writableRoots.length > 0) return false;
22268
+ if (filesystem.readScope === "configured-roots" && !subsetOf(filesystem.readableRoots, lease.readableRoots)) return false;
22269
+ if (filesystem.writeScope === "configured-roots" && !subsetOf(filesystem.writableRoots, lease.writableRoots)) return false;
22270
+ return true;
22271
+ }
22272
+
22273
+ // packages/agent-runtime/ownerOriginExecution.ts
22274
+ var OWNER_ORIGIN_EXECUTION_VERSION = 1;
22275
+ var TOOL_LOCATION_STATUSES = ["ready", "unavailable", "stopped"];
22276
+ function record(value) {
22277
+ return typeof value === "object" && value !== null && !Array.isArray(value);
22278
+ }
22279
+ function text(value) {
22280
+ return typeof value === "string" && value.trim().length > 0 && value === value.trim();
22281
+ }
22282
+ function absoluteSnapshotPath(value) {
22283
+ if (!text(value) || value.includes("\0")) return false;
22284
+ const drive = /^[A-Za-z]:[\\/]/.test(value);
22285
+ const unc = value.startsWith("\\\\");
22286
+ if (!(value.startsWith("/") || drive || unc)) return false;
22287
+ if (unc && !/^\\\\[^\\/]+[\\/][^\\/]+(?:[\\/]|$)/.test(value)) return false;
22288
+ const segments = value.split(drive || unc ? /[\\/]/ : /\//);
22289
+ return !segments.some((segment) => segment === "." || segment === "..");
22290
+ }
22291
+ function projectToolExecutionLocation(value) {
22292
+ if (!record(value) || !TOOL_LOCATION_STATUSES.includes(value.status) || !EXECUTION_BACKEND_KINDS.includes(value.hostType) || !text(value.deviceId) || !text(value.deviceName) || !record(value.workspace) || !text(value.workspace.id) || !absoluteSnapshotPath(value.workspace.root) || !absoluteSnapshotPath(value.workspace.cwd)) return { status: "unknown" };
22293
+ return {
22294
+ status: value.status,
22295
+ hostType: value.hostType,
22296
+ deviceId: value.deviceId,
22297
+ deviceName: value.deviceName,
22298
+ workspace: { id: value.workspace.id, root: value.workspace.root, cwd: value.workspace.cwd }
22299
+ };
22300
+ }
22301
+ var OWNER_ROUTE_VERSION_HEADER = "X-Nolo-Owner-Route-Version";
22302
+ var OWNER_ROUTE_REQUEST_ID_HEADER = "X-Nolo-Owner-Route-Request-Id";
22303
+ function verifyOwnerRouteAck(headers, expectedRequestId) {
22304
+ const version = headers.get(OWNER_ROUTE_VERSION_HEADER);
22305
+ const requestId = headers.get(OWNER_ROUTE_REQUEST_ID_HEADER)?.trim();
22306
+ if (version !== String(OWNER_ORIGIN_EXECUTION_VERSION) || !requestId) {
22307
+ return { ok: false, code: "owner_route_unsupported" };
22308
+ }
22309
+ if (expectedRequestId !== void 0 && requestId !== expectedRequestId) {
22310
+ return { ok: false, code: "owner_origin_mismatch" };
22311
+ }
22312
+ return { ok: true, version: OWNER_ORIGIN_EXECUTION_VERSION, requestId };
22313
+ }
22314
+
21531
22315
  export {
21532
22316
  readCommandActionGatePayload,
21533
22317
  readActionGate,
@@ -21536,29 +22320,36 @@ export {
21536
22320
  Exit_exports,
21537
22321
  Effect_exports,
21538
22322
  formatToolOverflowMarker,
21539
- evaluateFileWritePolicy,
21540
- summarizeToolArguments,
21541
22323
  isDestructiveShellCommand,
21542
22324
  evaluateShellCommandPolicy,
22325
+ getProcessRegistry,
21543
22326
  filterRetainedLedgerRecords,
21544
22327
  isLineRangeCovered,
21545
- getProcessRegistry,
22328
+ WORKSPACE_TOOL_NAMES,
21546
22329
  buildLocalWorkspaceToolset,
21547
22330
  buildLocalWorkspacePolicyToolNames,
21548
22331
  buildLocalWorkspaceOpenAiTools,
21549
22332
  PATH_FIELD_ALIASES,
22333
+ NEW_TEXT_FIELD_ALIASES,
22334
+ resolveRequestedWorkspacePath,
21550
22335
  internalSearchWorkspace,
21551
22336
  internalListWorkspaceEntries,
21552
22337
  createLocalWorkspaceToolExecutors,
21553
22338
  buildToolParametersIndex,
21554
22339
  getAgentRuntimeToolParametersIndex,
22340
+ normalizeLocalToolName,
22341
+ isCommandExecutingTool,
21555
22342
  resolveLocalToolPolicy,
22343
+ runExecShellGates,
21556
22344
  executeLocalToolWithPolicy,
21557
22345
  LOCAL_TURN_ABORTED_CODE,
22346
+ hashToolDefinitions,
21558
22347
  LOCAL_AGENT_CONFIG_MISSING_CODE,
21559
22348
  runLocalAgentTurn,
22349
+ responsesStreamFailureStatus,
21560
22350
  codexStreamFailureStatus,
21561
- aggregateResponsesStream,
22351
+ createResponsesStreamCollector,
22352
+ readResponsesSseCompletion,
21562
22353
  buildOpenAiCompatibleChatCompletionRequest,
21563
22354
  parseOpenAiCompatibleChatCompletionResponse,
21564
22355
  readOpenAiCompatibleSseCompletion,
@@ -21574,6 +22365,13 @@ export {
21574
22365
  buildLocalDialogWritePlan,
21575
22366
  buildNoloWorkspaceCliToolExecutors,
21576
22367
  buildLoadSkillExecutor,
22368
+ isStableSessionIdentityEnabled,
22369
+ deriveStableUuid,
22370
+ resolveSessionKey,
21577
22371
  readDevinUpstreamFailure,
21578
- createDevinProvider
22372
+ createDevinProvider,
22373
+ validateConfiguredRootExecutionLease,
22374
+ OWNER_ORIGIN_EXECUTION_VERSION,
22375
+ projectToolExecutionLocation,
22376
+ verifyOwnerRouteAck
21579
22377
  };