nolo-cli 0.213.0 → 0.223.0-alpha.1

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 (262) hide show
  1. package/README.md +3 -2
  2. package/{_executeModel-CUJLCFWR.js → _executeModel-4GGQQHOB.js} +111 -82
  3. package/{accountResolver-4LKIHEKL.js → accountResolver-EK2S4PWF.js} +6 -6
  4. package/{action-DF5UYSJF.js → action-CK64LS6S.js} +12 -11
  5. package/{addContentAction-WERAONNY.js → addContentAction-GDHDOF4U.js} +18 -17
  6. package/{addDialogAgentAction-7VI7YRML.js → addDialogAgentAction-CAKRU3ZT.js} +21 -20
  7. package/{agentDeleteCommand-XJORXPGU.js → agentDeleteCommand-BPV2XZL7.js} +128 -119
  8. package/{agentEmailCommands-XTYYFR6Z.js → agentEmailCommands-EJOS75YL.js} +132 -124
  9. package/{agentFavoriteCommands-EDK743YA.js → agentFavoriteCommands-2LKGKSUD.js} +7 -8
  10. package/{agentGrantCommands-RQWK2TEV.js → agentGrantCommands-QQV4N4JW.js} +5 -5
  11. package/{agentListCommands-OBLRBLHS.js → agentListCommands-HIZODBF3.js} +155 -92
  12. package/{agentMachineCommands-5FMT3OT5.js → agentMachineCommands-ZMQ2CY7Y.js} +62 -54
  13. package/{agentProviderCommands-Q5O33MED.js → agentProviderCommands-WKJOAWBP.js} +5 -5
  14. package/{agentPullCommand-6EM3URVK.js → agentPullCommand-XN6ZPBUW.js} +7 -7
  15. package/agentRecordCommands-OMXVDLDX.js +176 -0
  16. package/agentRunCommand-CB34YWMV.js +206 -0
  17. package/{agentRunControl-TAMHJQSR.js → agentRunControl-JNONH3RP.js} +30 -15
  18. package/{agentRuntimeLocal-SOO2OPJG.js → agentRuntimeLocal-QZPFWMLG.js} +52 -45
  19. package/{agentSlice-CHR6KOZN.js → agentSlice-RD2CDU7T.js} +20 -19
  20. package/{appDeleteCommands-GUP6P5YE.js → appDeleteCommands-QCCFDIEQ.js} +61 -53
  21. package/{appDeployCommands-MH2FX4O5.js → appDeployCommands-V2T7TNMQ.js} +61 -53
  22. package/{appDeployStatusCommands-SUHN3HPJ.js → appDeployStatusCommands-6PGH7ZCG.js} +61 -53
  23. package/{appGetCommands-YWDEX6Z4.js → appGetCommands-3L7WWJA6.js} +61 -53
  24. package/{appListCommands-ZQVMLPAY.js → appListCommands-ENJCK4PO.js} +61 -53
  25. package/{authCommand-PWX2ZNUP.js → authCommand-6V47SYGK.js} +29 -28
  26. package/{authCommands-HV3EAU3V.js → authCommands-GAJ3NIXO.js} +14 -13
  27. package/{builtinSkillRegistry-2QETTQPM.js → builtinSkillRegistry-2ECA42FC.js} +11 -13
  28. package/{chromeCommands-REEAH4SX.js → chromeCommands-G5UDKVSV.js} +3 -3
  29. package/{chromeOpsCommands-DIVM2K3T.js → chromeOpsCommands-YKX2QX3A.js} +20 -10
  30. package/{chunk-CRHOO2ZR.js → chunk-27PCUMJI.js} +2 -2
  31. package/chunk-2CI3ZFWD.js +97 -0
  32. package/{chunk-NSAPDIN2.js → chunk-2GJKRKI6.js} +541 -31
  33. package/{chunk-SM3NSZJV.js → chunk-2UCBGYO4.js} +20 -1
  34. package/{chunk-PYRUW7XN.js → chunk-2VUZSVAS.js} +136 -54
  35. package/{chunk-YB3YZK56.js → chunk-2WZXXAQD.js} +1 -67
  36. package/chunk-2YGBI2XB.js +30 -0
  37. package/chunk-2ZKFCIJJ.js +13 -0
  38. package/{chunk-QG4AOSON.js → chunk-34J5K2YA.js} +13 -32
  39. package/{chunk-LOEO7LRC.js → chunk-3IK6ILVW.js} +4 -1
  40. package/{chunk-CTPXME7U.js → chunk-3LMUPOHG.js} +29 -6
  41. package/chunk-44X52WOJ.js +1621 -0
  42. package/{chunk-6VLJ6YA7.js → chunk-4DESLZTK.js} +9 -9
  43. package/{chunk-MYOH447Z.js → chunk-4JVNURX4.js} +1 -1
  44. package/chunk-5CHGAKRT.js +364 -0
  45. package/{chunk-YDH3FDFQ.js → chunk-5FUN7EG4.js} +1 -1
  46. package/{chunk-INUFILG6.js → chunk-5RKUPJYL.js} +1 -1
  47. package/{chunk-ZC3CMUKU.js → chunk-6OVNSQHN.js} +84 -2
  48. package/{chunk-MOIJOHA5.js → chunk-6WU5572L.js} +36 -25
  49. package/{chunk-IXNGOHV5.js → chunk-7EEZJ6BS.js} +11 -152
  50. package/{chunk-Y6ORVLSS.js → chunk-7ET2U67U.js} +1 -1
  51. package/{chunk-G7MLNNEX.js → chunk-7JLY4WYF.js} +5 -5
  52. package/{chunk-P5L7CXOC.js → chunk-7Z6EW7AH.js} +3 -2
  53. package/{chunk-NJLL224W.js → chunk-AD5EWDFD.js} +2 -2
  54. package/{chunk-J2NHJ7WX.js → chunk-AIHYNKGV.js} +4 -4
  55. package/{chunk-HPFCI4SK.js → chunk-AK5O76CC.js} +3 -3
  56. package/{chunk-Y63ZSOR6.js → chunk-AL3TYDIF.js} +1 -1
  57. package/{chunk-ALQ3657A.js → chunk-AMEOV5VZ.js} +32 -12
  58. package/chunk-AN74ON4G.js +675 -0
  59. package/{chunk-HRMJUN5J.js → chunk-AQZ6BMRK.js} +1 -1
  60. package/{chunk-XHQC4PXX.js → chunk-B4GOW4MT.js} +424 -194
  61. package/{chunk-4KB6WQUL.js → chunk-BGP2EDFG.js} +1 -1
  62. package/{chunk-NBTY7YJF.js → chunk-BR3GT6MR.js} +1 -0
  63. package/{chunk-7RFNTILS.js → chunk-BUDVRYES.js} +2 -2
  64. package/{chunk-SVYO6AO7.js → chunk-BV3FLP2L.js} +41 -1
  65. package/{chunk-RZOV7OMV.js → chunk-C4VAR65S.js} +83 -141
  66. package/{chunk-COFEN36Z.js → chunk-C4WTX5TU.js} +2 -2
  67. package/{chunk-ZL5RAEEB.js → chunk-CIRB724Q.js} +1 -1
  68. package/{chunk-F6UUQOG6.js → chunk-CKT6QUE2.js} +4 -4
  69. package/{chunk-X7SDOJUM.js → chunk-CMZ3Z5PT.js} +14 -14
  70. package/{secretCommands-LXDWOLZ6.js → chunk-D726I5AZ.js} +191 -16
  71. package/{chunk-TAKB25RM.js → chunk-DNKTQ3IN.js} +4 -4
  72. package/chunk-DVQH4BTR.js +1137 -0
  73. package/{chunk-WYDI6TGM.js → chunk-DWX2HEVC.js} +91 -132
  74. package/{chunk-5MQRBSMW.js → chunk-EIAI3PPH.js} +111 -113
  75. package/chunk-EUCFKKDM.js +247 -0
  76. package/chunk-EWQ3HGNZ.js +90 -0
  77. package/{chunk-RPQXPZZF.js → chunk-F3GRJO4Z.js} +908 -13
  78. package/{chunk-YTF4WH7J.js → chunk-FIYSMXHY.js} +1 -1
  79. package/{chunk-75XIDFBW.js → chunk-FJ23FZXF.js} +72 -28
  80. package/{chunk-INN4LTAJ.js → chunk-FJK526XH.js} +2 -2
  81. package/chunk-FS2XP5D7.js +142 -0
  82. package/{chunk-FUGMREBO.js → chunk-FYF2TONJ.js} +5 -5
  83. package/{chunk-PCLE46CE.js → chunk-GBWGPTA7.js} +2 -1
  84. package/{chunk-TX5EYMGT.js → chunk-GYVCJABP.js} +30 -254
  85. package/{chunk-3UKL3HPD.js → chunk-HOCHUZQ7.js} +8 -78
  86. package/{chunk-EHSZD5OI.js → chunk-HXER7YJF.js} +4 -4
  87. package/chunk-HYIETXA6.js +99 -0
  88. package/{chunk-H2TJUNCH.js → chunk-I4EADJ4P.js} +6 -6
  89. package/{chunk-TCZHTTUU.js → chunk-I7WBYGBP.js} +8 -8
  90. package/{chunk-6DWMYIUY.js → chunk-IILMY4W4.js} +14 -9
  91. package/chunk-JA3ICVJY.js +271 -0
  92. package/{chunk-ZRSNONOB.js → chunk-JWXITTQB.js} +8 -3
  93. package/{chunk-H4UOTUOX.js → chunk-KEN3N2TN.js} +148 -18
  94. package/{chunk-LEC5ALIN.js → chunk-KEPOVMF4.js} +1 -1
  95. package/{chunk-UHJGXRJ4.js → chunk-KQSYAIZR.js} +3 -3
  96. package/{chunk-4VJAC32I.js → chunk-L5I543VB.js} +3 -2
  97. package/chunk-LKA64WH4.js +227 -0
  98. package/{chunk-QHPRUL4O.js → chunk-LVL6GFCM.js} +4 -4
  99. package/{chunk-H5HF7TJA.js → chunk-MAIITKBT.js} +2 -2
  100. package/chunk-MBG66ZBH.js +226 -0
  101. package/{chunk-YUA6OAH5.js → chunk-MDLS23JK.js} +2 -2
  102. package/{chunk-VL5KOY2C.js → chunk-MK2Q7ZJD.js} +67 -5
  103. package/{chunk-MDB67IWP.js → chunk-MS4NZNKI.js} +10 -10
  104. package/{chunk-VO4WSAVP.js → chunk-MYHNBEXD.js} +3 -3
  105. package/{chunk-ZXOUUDCG.js → chunk-MZLE5HQ3.js} +3654 -231
  106. package/{chunk-AZMYUSVF.js → chunk-N5Z2V73W.js} +1 -1
  107. package/{chunk-V5Y3R6BT.js → chunk-NIZ2BXFD.js} +213 -210
  108. package/{chunk-KFLT5KAD.js → chunk-NWP2OYOX.js} +20 -20
  109. package/{chunk-G2BQWAVT.js → chunk-O6J3OTOP.js} +4 -4
  110. package/{chunk-VNYYOX3V.js → chunk-OB6DVWPF.js} +141 -48
  111. package/{chunk-YWSEPJ7C.js → chunk-OBWT45ZU.js} +3 -3
  112. package/{chunk-ZNWI6OVT.js → chunk-ODJ7BVSI.js} +80 -11
  113. package/{chunk-XJWWBO34.js → chunk-OJKUU3LP.js} +5 -7
  114. package/{chunk-HUBHAQK2.js → chunk-ON37YXGX.js} +29 -29
  115. package/{chunk-UYD5HQ6T.js → chunk-P5ZJRTCH.js} +5429 -1752
  116. package/{chunk-6XIE5ZGI.js → chunk-PDCV7P5W.js} +1 -1
  117. package/{chunk-WRYQ35LQ.js → chunk-PJCIP64Z.js} +859 -57
  118. package/{chunk-7QTZCMKL.js → chunk-PJVKMGPZ.js} +1089 -1639
  119. package/{chunk-A7TFUNZO.js → chunk-PPJYZRSQ.js} +6 -2
  120. package/chunk-QHEN7NZ7.js +46 -0
  121. package/{chunk-OQPKUBPB.js → chunk-QNWG52I4.js} +19 -7
  122. package/{chunk-YGILMZ7N.js → chunk-QVBXJNZ2.js} +1 -1
  123. package/{chunk-7BT37FK3.js → chunk-RDH5CQ4T.js} +648 -131
  124. package/{chunk-C6KNNXWU.js → chunk-RGQ4UUWZ.js} +25 -2
  125. package/{chunk-OWYYDMGJ.js → chunk-S2NTVGA2.js} +4 -2
  126. package/{chunk-KC4XDTT3.js → chunk-SOF4OWDM.js} +6 -4
  127. package/{chunk-RLBNLZVC.js → chunk-SQYIK6HO.js} +1 -1
  128. package/{chunk-YJAFKXMU.js → chunk-SVT62ULE.js} +50 -48
  129. package/{chunk-KIM6CLW5.js → chunk-SY3C4SQ7.js} +42 -15
  130. package/{chunk-JY5NGFFH.js → chunk-SYTHYBMI.js} +5 -5
  131. package/{chunk-WMTBPBMP.js → chunk-T6DXNI6Z.js} +52 -50
  132. package/chunk-TLE77UJ5.js +230 -0
  133. package/{chunk-4VNS5WPM.js → chunk-U67V476Y.js} +1 -8
  134. package/{chunk-F7CI2ZC5.js → chunk-UAIHKQIA.js} +2 -1
  135. package/{chunk-R5O4QAWG.js → chunk-UB3D4TRQ.js} +11 -77
  136. package/{chunk-WPORQFYN.js → chunk-UVLYNWIE.js} +2 -2
  137. package/{chunk-NOKUNJON.js → chunk-UYOICCYG.js} +29 -19
  138. package/{chunk-4I7ERB7F.js → chunk-VREDDN25.js} +1 -1
  139. package/{chunk-O2TZF4AI.js → chunk-WAE3GFCJ.js} +3 -3
  140. package/{chunk-H7PPIYWP.js → chunk-WI67URFP.js} +20 -20
  141. package/{chunk-7OCO4TDT.js → chunk-WJVYAO6P.js} +1 -1
  142. package/{chunk-T43B7LB2.js → chunk-WKGQSYTB.js} +1 -1
  143. package/{chunk-BBJ22CLB.js → chunk-WUUH4LWZ.js} +60 -30
  144. package/{chunk-4T2WNOY7.js → chunk-X7UF53VA.js} +33 -22
  145. package/{chunk-TY3TIUJK.js → chunk-XI2BWJM5.js} +65 -0
  146. package/{chunk-LLRPJECG.js → chunk-XWVCYUUU.js} +16 -159
  147. package/{chunk-J5JTMQIA.js → chunk-YFLVRVYK.js} +1 -1
  148. package/{chunk-EYLGAEB4.js → chunk-YGMBIXCF.js} +132 -0
  149. package/chunk-YL3EFYL3.js +199 -0
  150. package/{chunk-MMA3LZ3W.js → chunk-YNIYE47Z.js} +1 -1
  151. package/{chunk-W4S457LU.js → chunk-YU4ZPWVF.js} +41 -5
  152. package/{chunk-7Y4YTDQF.js → chunk-YWWDC3O7.js} +281 -159
  153. package/chunk-YZMNK53S.js +68 -0
  154. package/{chunk-RSHTSUEG.js → chunk-Z2PITYZN.js} +2060 -602
  155. package/{chunk-FY7F4Q6X.js → chunk-Z4PCXPUE.js} +2 -2
  156. package/{cliAgentConfigHelpers-WJBZQTLW.js → cliAgentConfigHelpers-TKYL43MT.js} +15 -16
  157. package/{cliAuthorityBrokerClient-PM3U6Q6J.js → cliAuthorityBrokerClient-XHH7MH2P.js} +1 -1
  158. package/{cliAuthorityStoreDriver-TKLXNNTG.js → cliAuthorityStoreDriver-6FE3BJNB.js} +1 -1
  159. package/{cliEnvHelpers-BVYTMUPJ.js → cliEnvHelpers-VPSJ4XWC.js} +3 -3
  160. package/{cliExecutor-BWBF2MTI.js → cliExecutor-4MAX3PZL.js} +2 -2
  161. package/cliLocalAgentRecordReader-MHHD4T7R.js +90 -0
  162. package/{commandRegistry-W63CYRZJ.js → commandRegistry-5DA5UP7S.js} +105 -97
  163. package/{contentThunks-U7PL5MQD.js → contentThunks-5GUQ7FWJ.js} +47 -43
  164. package/{cooldownCommands-TIGUHXTD.js → cooldownCommands-X7P5M2V5.js} +5 -4
  165. package/{createAgentAutomationAction-OLBQDD6R.js → createAgentAutomationAction-UCLTHQRI.js} +51 -49
  166. package/{createDialogAction-SVH6B3SB.js → createDialogAction-UPN774AF.js} +46 -48
  167. package/{createPageAction-F4DIWZ2Z.js → createPageAction-JRQ2TA4O.js} +50 -48
  168. package/{cursorProvider-YGGNMVWX.js → cursorProvider-2RACMEMF.js} +1 -1
  169. package/{db-HUCI5R4G.js → db-CS4ZJQKV.js} +1 -1
  170. package/{dbSlice-BVH2YZPQ.js → dbSlice-WNON7TOL.js} +17 -16
  171. package/{deleteFile-5YOEKV4U.js → deleteFile-Q3YDB4ML.js} +9 -9
  172. package/{desktopFolderProjects-SFY5EZS3.js → desktopFolderProjects-4POAMVJN.js} +2 -2
  173. package/{dialogCommands-KHIEPY3M.js → dialogCommands-VRI7VCXH.js} +64 -57
  174. package/dialogNotifyCommand-6TMCX4D2.js +36 -0
  175. package/{dialogSlice-ZCTDV2PZ.js → dialogSlice-LFNVUSAN.js} +42 -40
  176. package/{dialogUsageCommand-7KCPYGHG.js → dialogUsageCommand-HTVPR2CX.js} +11 -10
  177. package/{docCreateCommands-DPMZI7G5.js → docCreateCommands-AMPXMOX3.js} +61 -55
  178. package/{docDeleteCommands-IRQQLUBT.js → docDeleteCommands-I5WFAEHP.js} +63 -57
  179. package/{docReadCommands-LK37XH6L.js → docReadCommands-X53ELNIE.js} +61 -55
  180. package/{docStore-V2KECILA.js → docStore-2VAWVLWH.js} +21 -20
  181. package/{docUpdateCommands-O6T3HJHH.js → docUpdateCommands-6QPNDC7M.js} +64 -58
  182. package/{fetchMessages-NR4KNQLO.js → fetchMessages-TQEUZD7H.js} +8 -7
  183. package/{getPricing-2MNMXEVG.js → getPricing-MB2GBXOK.js} +6 -6
  184. package/{handleSendMessageAction-2PE25WM3.js → handleSendMessageAction-AAJOHXOB.js} +194 -302
  185. package/{hostCredentialCreate-S233UMAP.js → hostCredentialCreate-GLMJ22M6.js} +123 -114
  186. package/index.js +29 -29
  187. package/{keys-BEGDOTRL.js → keys-RWF7UCB4.js} +5 -4
  188. package/legacyMigration-TF4SYJCZ.js +19 -0
  189. package/{lib-5KKIBFIT.js → lib-OT5OTXRU.js} +7087 -6664
  190. package/{localDialogRead-7NNDVVF7.js → localDialogRead-SVTPA5GY.js} +3 -3
  191. package/{localFirstLog-DHNOY2NA.js → localFirstLog-OIETGK66.js} +1 -1
  192. package/localRecall-2J6QPOK2.js +15 -0
  193. package/{localRuntimeAdapter-KY6WXNBR.js → localRuntimeAdapter-BMP57P42.js} +119 -110
  194. package/{localRuntimeAuthority-OYH4AE4G.js → localRuntimeAuthority-IBJAT62G.js} +1 -1
  195. package/{localRuntimeDb-VBIMCWUC.js → localRuntimeDb-N7KHDXS2.js} +2 -2
  196. package/{machineCommands-Q6HA3IXF.js → machineCommands-3MLIAK5Q.js} +128 -119
  197. package/{machineInfo-UKPTZO5Y.js → machineInfo-LWHTEBHJ.js} +1 -1
  198. package/machineWsRunDispatch-FKIGROQF.js +191 -0
  199. package/{memberThunks-7EDJADWU.js → memberThunks-ZGLZ36PK.js} +29 -28
  200. package/{memoryCommands-ASCE5STX.js → memoryCommands-5WC3T66U.js} +4 -4
  201. package/models-ULVWLSQ6.js +9 -0
  202. package/{oauthTokenStore-OEERZFUC.js → oauthTokenStore-RUYNYALS.js} +1 -1
  203. package/{offlineMarxistsAgentCommand-2QTHJCHV.js → offlineMarxistsAgentCommand-4IC3Y4C6.js} +4 -4
  204. package/overlay-THLKYIWY.js +11 -0
  205. package/package.json +2 -2
  206. package/{pdf-U6KSUEZZ.js → pdf-KE3QI2V7.js} +1 -1
  207. package/{processSpawn-ZU5QFM43.js → processSpawn-6GEO77KV.js} +1 -1
  208. package/{profileConfig-KN3N2BLF.js → profileConfig-KOWK4HOG.js} +1 -1
  209. package/{projectArchive-DRTDIMEV.js → projectArchive-HK63GMF4.js} +52 -50
  210. package/{providerPresetApply-YXGV6NXB.js → providerPresetApply-OIFQ73LH.js} +19 -12
  211. package/{providerRegistry-S6UL6UNA.js → providerRegistry-KZ3SDZ3D.js} +10 -10
  212. package/{read-4V2RMG7N.js → read-MRQHJMJ2.js} +10 -10
  213. package/{readDocTool-BH6ATGQO.js → readDocTool-4BGTXTXA.js} +2 -2
  214. package/{readlineWorkspace-2W4WRCOU.js → readlineWorkspace-AKAHSCNY.js} +7982 -6274
  215. package/{removeDialogAgentAction-LYAZGNKW.js → removeDialogAgentAction-B6H7DXFM.js} +23 -22
  216. package/{runAgentClientLoop-J2VQBS2Z.js → runAgentClientLoop-UVAWGEON.js} +81 -79
  217. package/{runtimeDoctorCommands-QAI3MBKQ.js → runtimeDoctorCommands-FW6XKDUG.js} +12 -12
  218. package/{runtimeModeArgs-2Y7BALTO.js → runtimeModeArgs-X7DL7PUN.js} +1 -1
  219. package/secretCommands-LJXOOYA7.js +24 -0
  220. package/{setDialogExtraReferencesAction-OCRPGPYH.js → setDialogExtraReferencesAction-EVCY3TRW.js} +22 -21
  221. package/{setPrimaryDialogAgentAction-HFJDQIBU.js → setPrimaryDialogAgentAction-KPFVPHCJ.js} +23 -22
  222. package/{settingThunks-RAMHMTGM.js → settingThunks-ENOAK2WV.js} +97 -88
  223. package/{skillDiscovery-XKHROSH2.js → skillDiscovery-B5V23AXP.js} +4 -4
  224. package/{skillSummaryMarker-42HKSXYY.js → skillSummaryMarker-62UW7HI7.js} +1 -1
  225. package/{slateToRenderMarkdown-Y5IYN73U.js → slateToRenderMarkdown-R3X74MCX.js} +1 -1
  226. package/{slateUtils-NGV2NMKM.js → slateUtils-ESQAJ6RN.js} +1 -1
  227. package/{spaceCommands-KVJZVKVO.js → spaceCommands-DTTNAS2L.js} +61 -53
  228. package/store-JRVN62NM.js +46 -0
  229. package/{streamAgentChatTurn-GHBMJJ6S.js → streamAgentChatTurn-R5UY25VB.js} +875 -618
  230. package/{switchDialogAgentAction-EBRMW3RA.js → switchDialogAgentAction-SZ44XDRY.js} +21 -20
  231. package/{tableCommands-MP6VFSVT.js → tableCommands-HITDSLHS.js} +10 -10
  232. package/tombstones-FQQMEDOW.js +32 -0
  233. package/{toolApiClient-HGNO53TC.js → toolApiClient-OGQEBQVX.js} +4 -4
  234. package/{tools-EWKCPKMS.js → tools-HWFG6GMJ.js} +75 -74
  235. package/tuiSummaryLlmCaller-HQQZAMYF.js +44 -0
  236. package/{updateCommands-ZQYU6SCI.js → updateCommands-RJVCRSBP.js} +10 -8
  237. package/{updateDialogTitleAction-5GGMBTDF.js → updateDialogTitleAction-KNPOAMVS.js} +48 -46
  238. package/{usageCommand-F4IPVMIZ.js → usageCommand-FWZCB7AM.js} +11 -10
  239. package/{usageRebuildCommand-HAW2AD5N.js → usageRebuildCommand-SP2BLNJU.js} +12 -11
  240. package/userDataMerge-HT2ZVHBJ.js +13 -0
  241. package/{windowsSelfUpdate-257DV2T6.js → windowsSelfUpdate-KERHIAB4.js} +3 -3
  242. package/{workloadLifecycle-4C42SVBD.js → workloadLifecycle-DYNEYHV5.js} +6 -6
  243. package/agentRecordCommands-ZQ7SVYGG.js +0 -167
  244. package/agentRunCommand-WS7IGO2W.js +0 -196
  245. package/chunk-3CGG2PAL.js +0 -594
  246. package/chunk-5XBUWM6I.js +0 -89
  247. package/chunk-CJEIOP3E.js +0 -0
  248. package/chunk-EHGEOAHI.js +0 -636
  249. package/chunk-FW6YHHLI.js +0 -842
  250. package/chunk-GPVRUGGY.js +0 -115
  251. package/chunk-HSVBJRF4.js +0 -339
  252. package/chunk-WJZOQR2J.js +0 -151
  253. package/chunk-ZCWRMUWT.js +0 -89
  254. package/dist-M676ULYG.js +0 -7314
  255. package/machineWsRunDispatch-WWJQ6WR3.js +0 -182
  256. package/models-PJBE6JDV.js +0 -9
  257. package/tuiSummaryLlmCaller-ALQYUUWB.js +0 -189
  258. package/{chunk-WACI5I6V.js → chunk-2THCNKE4.js} +3 -3
  259. package/{chunk-2QHFH3DR.js → chunk-47ITIXO7.js} +6 -6
  260. package/{chunk-GOO6G4PY.js → chunk-CKG3PRZ7.js} +0 -0
  261. package/{chunk-OQHIVPG4.js → chunk-SNBZZPYH.js} +3 -3
  262. package/{chunk-AETZ7AD6.js → chunk-W6RFEDN4.js} +3 -3
@@ -1,20 +1,8 @@
1
- import {
2
- applyChatCompletionDelta,
3
- applyResponsesToolEvent,
4
- createResponsesToolAccumulator,
5
- finalizeResponsesToolCalls,
6
- flushChatCompletionStream,
7
- providerHttpFailure,
8
- throwIfChatCompletionStreamFailed
9
- } from "./chunk-OQPKUBPB.js";
10
1
  import {
11
2
  DEVIN_CONNECT_URL,
12
3
  parseSseDataLineJson,
13
4
  parseSseDataLineObject
14
- } from "./chunk-CTPXME7U.js";
15
- import {
16
- readSseFrames
17
- } from "./chunk-HNUWUO75.js";
5
+ } from "./chunk-3LMUPOHG.js";
18
6
  import {
19
7
  evaluateFileWritePolicy,
20
8
  summarizeToolArguments
@@ -25,11 +13,36 @@ import {
25
13
  import {
26
14
  formatImageSummary,
27
15
  readLocalImage
28
- } from "./chunk-Y63ZSOR6.js";
16
+ } from "./chunk-AL3TYDIF.js";
17
+ import {
18
+ applyChatCompletionDelta,
19
+ applyResponsesToolEvent,
20
+ createResponsesToolAccumulator,
21
+ finalizeResponsesToolCalls,
22
+ flushChatCompletionStream,
23
+ providerHttpFailure,
24
+ throwIfChatCompletionStreamFailed
25
+ } from "./chunk-QNWG52I4.js";
26
+ import {
27
+ readSseFrames
28
+ } from "./chunk-HNUWUO75.js";
29
29
  import {
30
+ resolveToolExecutionContract
31
+ } from "./chunk-LKA64WH4.js";
32
+ import {
33
+ evaluateRequestBudget,
30
34
  hasImageInRuntimeMessages,
31
35
  stripImagePartsFromMessages
32
- } from "./chunk-W4S457LU.js";
36
+ } from "./chunk-YU4ZPWVF.js";
37
+ import {
38
+ clipToolText,
39
+ resolveHistoricalToolContentCap,
40
+ resolveToolOutputProfile
41
+ } from "./chunk-2WZXXAQD.js";
42
+ import {
43
+ resolveAgentImageInputSupport,
44
+ resolveToolImagePolicy
45
+ } from "./chunk-OBWT45ZU.js";
33
46
  import {
34
47
  applyShapingCounts,
35
48
  attachProjectionFacts,
@@ -40,7 +53,7 @@ import {
40
53
  projectUserPartForModel,
41
54
  resolveOutboundDiagnosticSink,
42
55
  writeThinkingFacts
43
- } from "./chunk-4KB6WQUL.js";
56
+ } from "./chunk-BGP2EDFG.js";
44
57
  import {
45
58
  createThinkParserState,
46
59
  createToolCallAccumulator,
@@ -66,7 +79,25 @@ import {
66
79
  hashStablePrefixContent,
67
80
  resolveResponseGuidelines,
68
81
  resolveToolGuidedSections
69
- } from "./chunk-5MQRBSMW.js";
82
+ } from "./chunk-EIAI3PPH.js";
83
+ import {
84
+ BRIDGE_STREAM_MAX_CHARS,
85
+ CLI_NOLO_WORKSPACE_TOOL_NAMES,
86
+ DEFAULT_READ_DIALOG_MAX_RESPONSE_CHARS,
87
+ NOLO_WORKSPACE_TOOL_NAMES,
88
+ buildDialogReadEnvelope,
89
+ buildNoloWorkspaceCommandArgs,
90
+ buildNoloWorkspaceOpenAiTools,
91
+ createStreamingDialogReadProjector,
92
+ noloPositiveIntegerString,
93
+ noloStringArg,
94
+ parseNoloWorkspaceToolArguments,
95
+ readTextStreamUpToChars,
96
+ resolveNoloDialogInput
97
+ } from "./chunk-C4VAR65S.js";
98
+ import {
99
+ applyToolSurfaceConstraints
100
+ } from "./chunk-SOF4OWDM.js";
70
101
  import {
71
102
  classifyUnparsableToolArgs,
72
103
  collectStablePromptPrefix,
@@ -90,7 +121,7 @@ import {
90
121
  toOpenAiCompatibleMessages,
91
122
  toResponsesTools,
92
123
  transferDowngradedToolTurnOrigin
93
- } from "./chunk-H5HF7TJA.js";
124
+ } from "./chunk-MAIITKBT.js";
94
125
  import {
95
126
  TOOL_HAD_IMAGES,
96
127
  TOOL_IMAGES,
@@ -105,37 +136,11 @@ import {
105
136
  withoutToolImages
106
137
  } from "./chunk-E2BESSAW.js";
107
138
  import {
108
- BRIDGE_STREAM_MAX_CHARS,
109
- CLI_NOLO_WORKSPACE_TOOL_NAMES,
110
- DEFAULT_READ_DIALOG_MAX_RESPONSE_CHARS,
111
- DEFAULT_SUBTASK_MAX_TOOL_CALLS,
112
- DEFAULT_SUBTASK_MAX_WALL_CLOCK_MS,
113
- NOLO_WORKSPACE_TOOL_NAMES,
114
- buildDialogReadEnvelope,
115
- buildNoloWorkspaceCommandArgs,
116
- buildNoloWorkspaceOpenAiTools,
117
- buildSubtaskBudgetExhaustedContent,
118
- createStreamingDialogReadProjector,
119
- createSubtaskBudgetTracker,
120
- noloPositiveIntegerString,
121
- noloStringArg,
122
- parseNoloWorkspaceToolArguments,
123
- readTextStreamUpToChars,
124
- resolveNoloDialogInput
125
- } from "./chunk-RZOV7OMV.js";
126
- import {
127
- applyToolSurfaceConstraints
128
- } from "./chunk-KC4XDTT3.js";
129
- import {
130
- clipToolText,
131
- planContextUsage,
132
- resolveHistoricalToolContentCap,
133
- resolveToolOutputProfile
134
- } from "./chunk-YB3YZK56.js";
139
+ isQuotaExhaustedError
140
+ } from "./chunk-TLE77UJ5.js";
135
141
  import {
136
- resolveAgentImageInputSupport,
137
- resolveToolImagePolicy
138
- } from "./chunk-YWSEPJ7C.js";
142
+ isAgentSkillDisabled
143
+ } from "./chunk-FS2XP5D7.js";
139
144
  import {
140
145
  buildDialogFallbackTitleFromUserInput
141
146
  } from "./chunk-2MBTWINN.js";
@@ -143,29 +148,34 @@ import {
143
148
  EMPTY_ASSISTANT_REPAIR_PROMPT,
144
149
  extractReferenceKeysFromMessage,
145
150
  hasAssistantVisibleOutput,
146
- normalizeUsage,
147
- readCacheCreationInputTokens,
148
- readCacheReadInputTokens,
149
151
  resolveAgentContextWindow,
150
152
  resolveEmptyAssistantFallbackMessage,
151
153
  resolveEmptyAssistantOutcome,
152
154
  resolveTruncatedReasoningTailLog
153
- } from "./chunk-V5Y3R6BT.js";
155
+ } from "./chunk-NIZ2BXFD.js";
154
156
  import {
155
157
  COMPACTION_SUMMARY_SCHEMA_VERSION,
156
158
  COMPACTION_SUMMARY_SYSTEM_PROMPT,
157
159
  buildBoundedCompactionRequest,
158
160
  buildCompactionMetricsFromPlan,
161
+ classifySummaryError,
159
162
  computeSummarySourceMetadata,
163
+ createSummaryBudget,
160
164
  formatCompactionMetricsLog,
161
165
  getMessageTokenCount,
162
166
  getModelContextWindow,
167
+ isTruncatedCompletion,
168
+ normalizeUsage,
163
169
  planCompression,
170
+ readCacheCreationInputTokens,
171
+ readCacheReadInputTokens,
164
172
  resolveCompressionTriggerRatio,
165
173
  resolveEffectiveCompactionBudgets,
174
+ runSummaryWithContract,
166
175
  sha256Hex,
176
+ summaryCompletionTokens,
167
177
  truncateToolOutputForContext
168
- } from "./chunk-7BT37FK3.js";
178
+ } from "./chunk-RDH5CQ4T.js";
169
179
  import {
170
180
  createProviderCallTimingTracker,
171
181
  finalizeProviderCallTiming,
@@ -176,25 +186,38 @@ import {
176
186
  import {
177
187
  clipCompactText
178
188
  } from "./chunk-5MMAK75Q.js";
179
- import {
180
- assertCredentialRef
181
- } from "./chunk-OQHIVPG4.js";
182
189
  import {
183
190
  asRecordOrEmpty
184
191
  } from "./chunk-KX6YBIHG.js";
192
+ import {
193
+ assertCredentialRef
194
+ } from "./chunk-SNBZZPYH.js";
195
+ import {
196
+ isLevelNotFoundError
197
+ } from "./chunk-OW765XBU.js";
185
198
  import {
186
199
  isAbortError
187
200
  } from "./chunk-7DPNBQSV.js";
188
201
  import {
189
- buildDialogSummaryLayer
190
- } from "./chunk-ZRSNONOB.js";
191
- import {
192
- buildUserResponseLanguageContext
193
- } from "./chunk-4VJAC32I.js";
202
+ WORKTREE_ISOLATION_PRELUDE
203
+ } from "./chunk-PJCIP64Z.js";
194
204
  import {
195
205
  canonicalizeToolName,
196
206
  canonicalizeToolNames
197
- } from "./chunk-ZCWRMUWT.js";
207
+ } from "./chunk-6OVNSQHN.js";
208
+ import {
209
+ dialogMessageKey,
210
+ dialogMessageRange
211
+ } from "./chunk-OJKUU3LP.js";
212
+ import {
213
+ asTrimmedNonEmptyStringArray
214
+ } from "./chunk-I42VC5KR.js";
215
+ import {
216
+ buildDialogSummaryLayer
217
+ } from "./chunk-JWXITTQB.js";
218
+ import {
219
+ buildUserResponseLanguageContext
220
+ } from "./chunk-L5I543VB.js";
198
221
  import {
199
222
  buildInteractiveCommandBlockedResult,
200
223
  buildProcessTaskResultCapsule,
@@ -209,56 +232,54 @@ import {
209
232
  formatToolOverflowMarker,
210
233
  getProcessRegistry,
211
234
  isProcessTaskTerminalStatus,
235
+ isSpilledToolOutput,
212
236
  reportedTaskStatus,
213
237
  resolveExecShellTimeoutMs,
214
238
  resolveExecutableOnPath,
215
239
  runWorkspaceCommand,
216
240
  spawnToWebStreams,
241
+ spillLargeToolOutput,
217
242
  spillToolOutput,
218
243
  toProcessTaskView,
219
244
  truncateToolOutput
220
- } from "./chunk-H4UOTUOX.js";
245
+ } from "./chunk-KEN3N2TN.js";
221
246
  import {
222
247
  estimateTokenCount
223
248
  } from "./chunk-PSCNXBYA.js";
224
249
  import {
225
250
  buildProviderAuthHeaders,
226
251
  buildProviderExecutionPlan
227
- } from "./chunk-TCZHTTUU.js";
252
+ } from "./chunk-I7WBYGBP.js";
228
253
  import {
229
254
  toErrorMessage
230
255
  } from "./chunk-HNMXYYJR.js";
256
+ import {
257
+ asTrimmedString
258
+ } from "./chunk-3EUW3WEO.js";
231
259
  import {
232
260
  isRecord
233
261
  } from "./chunk-KUG52IAP.js";
262
+ import {
263
+ PLATFORM_HOSTED_DEEPSEEK_FLASH_MODEL
264
+ } from "./chunk-CKG3PRZ7.js";
234
265
  import {
235
266
  asTrimmedLowercaseString
236
267
  } from "./chunk-QSJ3HFEF.js";
237
- import {
238
- normalizeServerOrigin
239
- } from "./chunk-CF7PE763.js";
240
- import {
241
- resolveNoloStateDir
242
- } from "./chunk-6UBGXX2W.js";
243
- import {
244
- dialogMessageKey,
245
- dialogMessageRange
246
- } from "./chunk-XJWWBO34.js";
247
- import {
248
- asTrimmedNonEmptyStringArray
249
- } from "./chunk-I42VC5KR.js";
250
268
  import {
251
269
  asOptionalPositiveFiniteNumber
252
270
  } from "./chunk-U6VC4MU4.js";
253
- import {
254
- asTrimmedString
255
- } from "./chunk-3EUW3WEO.js";
256
271
  import {
257
272
  asOptionalTrimmedString
258
273
  } from "./chunk-G3SUCT4W.js";
274
+ import {
275
+ normalizeServerOrigin
276
+ } from "./chunk-CF7PE763.js";
277
+ import {
278
+ resolveNoloStateDir
279
+ } from "./chunk-6UBGXX2W.js";
259
280
  import {
260
281
  __export
261
- } from "./chunk-4VNS5WPM.js";
282
+ } from "./chunk-U67V476Y.js";
262
283
 
263
284
  // packages/agent-runtime/actionGate.ts
264
285
  function readCommandActionGatePayload(payload) {
@@ -302,6 +323,42 @@ function readTitleParams(value) {
302
323
  return Object.fromEntries(entries);
303
324
  }
304
325
 
326
+ // packages/agent-runtime/contextRefusal.ts
327
+ var machineIdentifier = (value) => value && /^[A-Z][A-Z0-9_]{0,63}$/.test(value) ? value : void 0;
328
+ function classifyContextRefusal(args2) {
329
+ const { outcome, historyTokens, budget, droppedCount } = args2;
330
+ const largest = args2.largestMessageTokens;
331
+ const numbersKnown = Number.isFinite(historyTokens) && historyTokens >= 0 && Number.isFinite(budget) && budget >= 0 && largest !== void 0 && Number.isFinite(largest) && largest >= 0;
332
+ const base = { historyTokens, budget, droppedCount, largestMessageTokens: largest ?? -1 };
333
+ const result4 = (reason, detail) => ({ ...base, reason, detail });
334
+ if (!numbersKnown) return result4("unknown", "input-estimate-unavailable");
335
+ if (largest >= budget) return result4("single-message-too-large", "largest-message-at-or-above-budget");
336
+ const compacted = outcome?.result;
337
+ const decision = compacted?.decision;
338
+ const failure = outcome?.error ?? compacted?.failure;
339
+ if (failure || compacted?.failureReason || decision?.outcome === "failed") {
340
+ const code = machineIdentifier(failure?.code);
341
+ const errorType = failure?.errorType;
342
+ const status = failure?.status;
343
+ if (code === "PROVIDER_UNAVAILABLE" || code === "SUMMARY_PROVIDER_UNAVAILABLE") {
344
+ return result4("no-compactable-content", `code=${code}`);
345
+ }
346
+ const busy = status === 503 || status === 429 || !!code && /BUSY|RATE_LIMIT|CAPACITY|OVERLOAD|RESOURCE_EXHAUSTED/.test(code) || !!errorType && /^(?:capacity|overloaded|rate_limit)(?:_error)?$/.test(errorType);
347
+ const detail = [
348
+ status !== void 0 && Number.isInteger(status) && status >= 100 && status <= 599 ? `status=${status}` : "",
349
+ code ? `code=${code}` : "",
350
+ failure ? `category=${failure.category}` : `failure=${compacted?.failureReason ?? (decision?.outcome === "failed" ? decision.reason : "provider-error")}`
351
+ ].filter(Boolean).join(" ");
352
+ return result4(busy ? "summary-provider-busy" : "summary-failed", detail);
353
+ }
354
+ if (decision?.outcome === "skipped") {
355
+ if (decision.skipReason === "summary-empty") return result4("summary-failed", "summary-empty");
356
+ const bounded3 = decision.skipReason === "bounded-request-blocked" ? /\b(no-budget|invalid-window|empty-input|no-safe-group|group-too-large)\b/.exec(decision.detail ?? "")?.[1] : void 0;
357
+ return result4("no-compactable-content", `skip=${decision.skipReason}${bounded3 ? ` detail=${bounded3}` : ""}`);
358
+ }
359
+ return result4("unknown", decision ? "no-refusal-decision" : "compaction-not-evaluated");
360
+ }
361
+
305
362
  // node_modules/effect/dist/Pipeable.js
306
363
  var pipeArguments = (self, args2) => {
307
364
  switch (args2.length) {
@@ -13125,6 +13182,30 @@ function createLoopTiming() {
13125
13182
  return { mark, flush: flush2 };
13126
13183
  }
13127
13184
 
13185
+ // packages/agent-runtime/toolExecutionFailure.ts
13186
+ function isToolExecutionFailure(args2) {
13187
+ const metadata = args2.metadata;
13188
+ if (metadata) {
13189
+ if (metadata.error) return true;
13190
+ const exitCode = metadata.exitCode;
13191
+ if (typeof exitCode === "number" && Number.isFinite(exitCode) && exitCode !== 0) return true;
13192
+ if (typeof exitCode === "string" && exitCode.trim() !== "") {
13193
+ const parsedExitCode = Number(exitCode);
13194
+ if (Number.isFinite(parsedExitCode) && parsedExitCode !== 0) return true;
13195
+ }
13196
+ if (metadata.timedOut === true) return true;
13197
+ }
13198
+ if (typeof args2.content !== "string" || !args2.content.trim()) return false;
13199
+ try {
13200
+ const parsed = JSON.parse(args2.content);
13201
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return false;
13202
+ const record2 = parsed;
13203
+ return Boolean(record2.error) || record2.ok === false || record2.success === false || record2.status === "error";
13204
+ } catch {
13205
+ return false;
13206
+ }
13207
+ }
13208
+
13128
13209
  // packages/agent-runtime/toolCallPairing.ts
13129
13210
  function readToolCallId(message) {
13130
13211
  if (typeof message.tool_call_id === "string" && message.tool_call_id) {
@@ -13483,7 +13564,7 @@ var OBSERVATION_ONLY_METADATA_KEYS = /* @__PURE__ */ new Set([
13483
13564
  ]);
13484
13565
  function formatToolMessageContent(args2) {
13485
13566
  const sanitizedContent = sanitizeToolOutputText(args2.content ?? "");
13486
- const scrubbedContent = scrubSecrets(sanitizedContent).cleaned;
13567
+ const scrubbedContent = spillLargeToolOutput(scrubSecrets(sanitizedContent).cleaned, args2.toolName, args2.workspaceRoot);
13487
13568
  if (args2.toolName !== "globFiles" && args2.toolName !== "codeSearch" && args2.toolName !== "readFile" || !args2.metadata || Object.keys(args2.metadata).length === 0) {
13488
13569
  return scrubbedContent;
13489
13570
  }
@@ -13499,7 +13580,7 @@ function formatToolMessageContent(args2) {
13499
13580
  [tool metadata]
13500
13581
  ${JSON.stringify(visible)}`;
13501
13582
  }
13502
- function blocksToOpenAiMessages(blocks) {
13583
+ function blocksToOpenAiMessages(blocks, workspaceRoot) {
13503
13584
  const out = [];
13504
13585
  let text2 = "";
13505
13586
  let reasoning = "";
@@ -13520,6 +13601,7 @@ function blocksToOpenAiMessages(blocks) {
13520
13601
  role: "tool",
13521
13602
  content: formatToolMessageContent({
13522
13603
  toolName: tc.function.name,
13604
+ workspaceRoot,
13523
13605
  content: res?.content ?? "",
13524
13606
  ...res?.metadata ? { metadata: res.metadata } : {}
13525
13607
  }),
@@ -13586,42 +13668,6 @@ var TOOL_METADATA_KEYS = [
13586
13668
  "pasteId",
13587
13669
  "source"
13588
13670
  ];
13589
- function trimHistoryToContextBudget(history, model, contextWindowOverride) {
13590
- if (history.length === 0) return { history, droppedCount: 0 };
13591
- const { rawMessageBudget } = planContextUsage({
13592
- contextWindow: contextWindowOverride ?? getModelContextWindow(model ?? ""),
13593
- summaryTokens: 0,
13594
- // localLoop 没有 web 端的负载分档器;medium 是中性默认值,
13595
- // 不为了省几个 token 在这里复制一份分类逻辑。
13596
- recentLoad: "medium"
13597
- });
13598
- const messageTokens = (message) => {
13599
- const toolCalls = message.tool_calls;
13600
- return estimateTokenCount(contentAsText(message.content)) + (Array.isArray(toolCalls) ? estimateTokenCount(JSON.stringify(toolCalls)) : 0);
13601
- };
13602
- let used = 0;
13603
- let start = history.length;
13604
- for (let i = history.length - 1; i >= 0; i -= 1) {
13605
- const cost = messageTokens(history[i]);
13606
- if (used + cost > rawMessageBudget && start < history.length) break;
13607
- used += cost;
13608
- start = i;
13609
- }
13610
- if (start === 0) return { history, droppedCount: 0 };
13611
- return {
13612
- history: sanitizeToolCallPairing(history.slice(start)),
13613
- droppedCount: start
13614
- };
13615
- }
13616
- function contentAsText(content) {
13617
- if (typeof content === "string") return content;
13618
- if (!Array.isArray(content)) return "";
13619
- return content.map((part) => {
13620
- if (part?.type === "text") return part.text;
13621
- if (part?.type === "image_url") return part.image_url.url;
13622
- return "";
13623
- }).join("\n");
13624
- }
13625
13671
  function contentCharCount(content) {
13626
13672
  if (typeof content === "string") return content.length;
13627
13673
  if (!Array.isArray(content)) return 0;
@@ -13655,6 +13701,7 @@ function projectToolContentForProvider(args2) {
13655
13701
  const rawContent = args2.content;
13656
13702
  const content = typeof rawContent === "string" ? sanitizeToolOutputText(rawContent) : rawContent;
13657
13703
  if (typeof content !== "string") return content;
13704
+ if (isSpilledToolOutput(content)) return content;
13658
13705
  const metadataText = compactToolMetadata(args2.metadata);
13659
13706
  const embeddedMetadataIndex = metadataText ? content.indexOf("\n\n[tool metadata]\n") : -1;
13660
13707
  const contentForProjection = embeddedMetadataIndex >= 0 ? content.slice(0, embeddedMetadataIndex) : content;
@@ -13686,7 +13733,7 @@ ${metadataText}` : "";
13686
13733
  content: contentForProjection,
13687
13734
  toolName: args2.toolName
13688
13735
  });
13689
- spillNote = `; spillFile=${spill.displayPath}; totalLines=${spill.totalLines}; hint=read full output via readFile`;
13736
+ spillNote = `; spillFile=${spill.displayPath}; totalLines=${spill.totalLines}; hint=read a range, not the whole spill (readFile lines=A-B or sed -n 'A,Bp'); a full read re-injects ${spill.totalChars} chars`;
13690
13737
  } catch {
13691
13738
  }
13692
13739
  }
@@ -13896,6 +13943,29 @@ ${args2.input}` : [
13896
13943
  } : {}
13897
13944
  };
13898
13945
  }
13946
+ function estimateLocalRequestInput(messages, toolSchemaTokens = 0) {
13947
+ return messages.reduce((sum2, message) => sum2 + getMessageTokenCount(message), 0) + toolSchemaTokens;
13948
+ }
13949
+ function evaluateLocalRequestBudget(args2) {
13950
+ const estimate = estimateLocalRequestInput(args2.messages, args2.descriptor?.toolSchemaTokens ?? 0);
13951
+ const ratio = Math.max(1, args2.calibrationRatio ?? 1);
13952
+ const output = args2.descriptor?.output ?? { semantics: "unknown", fallbackReserveTokens: 8192 };
13953
+ const boundedOutput = output.semantics === "unknown" ? { ...output, fallbackReserveTokens: Math.min(
13954
+ output.fallbackReserveTokens,
13955
+ Math.min(8192, Math.max(1, Math.floor(args2.contextWindow / 4)))
13956
+ ) } : output;
13957
+ return evaluateRequestBudget({
13958
+ contextWindow: args2.contextWindow,
13959
+ messageTokens: Math.ceil(estimate * ratio),
13960
+ toolSchemaTokens: 0,
13961
+ // already included in the calibrated full-input estimate
13962
+ protocolReserveTokens: 0,
13963
+ output: boundedOutput
13964
+ });
13965
+ }
13966
+ function updateLocalRequestCalibration(previous, actual, estimated) {
13967
+ return Number.isFinite(actual) && actual > 0 && Number.isFinite(estimated) && estimated > 0 ? Math.max(1, actual / estimated) : previous;
13968
+ }
13899
13969
 
13900
13970
  // packages/agent-runtime/toolArgsTruncationPolicy.ts
13901
13971
  import { createHash } from "node:crypto";
@@ -14736,12 +14806,13 @@ function detectExecShellSynchronousWarnings(command, shell) {
14736
14806
  }
14737
14807
 
14738
14808
  // packages/agent-runtime/capabilities/execShellCapability.ts
14809
+ var WINDOWS_SHELL_NOTE = process.platform === "win32" ? ` On Windows the runtime prefers pwsh (PowerShell 7+): it is resolved first (PATH, then the WindowsApps alias), and powershell.exe 5.1 is only a fallback when pwsh is absent. Read metadata.shellKind from the result \u2014 "pwsh" supports '&&'/'||' and $PSStyle, "powershell5" does not (join with ';' or split calls). Match the syntax to the kind you actually got; never assume the version.` : "";
14739
14810
  function buildExecShellToolDefinition(toolName = "execShell") {
14740
14811
  return {
14741
14812
  type: "function",
14742
14813
  function: {
14743
14814
  name: toolName,
14744
- 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 produces a completion notification when finished; to read its output without waiting for the wake-up, read the result file for that taskId with readFile (a launchProcess taskId only yields a notice on your next real turn, never a new turn). Note that sleep commands exceeding the detach threshold will detach and trigger a completion wake-up upon exit \u2014 do not run standalone sleep commands purely to pause execution. (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.`,
14815
+ 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.${WINDOWS_SHELL_NOTE} LIFECYCLE CONTRACT: (1) A normal command waits synchronously for at most 120 seconds; if it ends inside that window the call IS finished \u2014 a result carrying exitCode means the command has already completed: parse it immediately, never read it as "still running" (no later event will remind you). (2) Only a result with metadata.detached === true AND a taskId can deliver an automatic completion turn; that taskId is an opaque handle, NOT a file path and NOT a result file \u2014 never search for, guess or readFile a path from it, and never rerun the command just to obtain its output. That terminal turn delivers the result inline as a bounded capsule; when the capsule was truncated it also carries a real full-output path in its overflow marker \u2014 read that actual path with readFile only when it is present. While waiting for that terminal notification, continue independent work on other parts of the task. A launchProcess taskId is a tracked task too, but it only yields a notice on your next real turn. (3) Text the command itself prints (LAUNCHED, STARTED, ...) 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. Do not run standalone sleep commands purely to pause execution. 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.`,
14745
14816
  parameters: {
14746
14817
  type: "object",
14747
14818
  properties: {
@@ -14759,7 +14830,7 @@ function buildExecShellToolDefinition(toolName = "execShell") {
14759
14830
  },
14760
14831
  label: {
14761
14832
  type: "string",
14762
- description: "Optional human-readable label for the resulting background task, shown in the task list and completion notices. Defaults to the command's last token, which is unhelpful for pipelines (e.g. '| tail -25' yields '-25')."
14833
+ description: "Optional human-readable label for the resulting background task, shown in task lists and completion notices. Defaults to the last token."
14763
14834
  }
14764
14835
  }
14765
14836
  }
@@ -15317,6 +15388,52 @@ import { homedir } from "node:os";
15317
15388
  import { dirname as dirname2, relative as relative4, resolve as resolve4, sep as sep2 } from "node:path";
15318
15389
  import { spawn as spawnChildProcess2 } from "node:child_process";
15319
15390
 
15391
+ // packages/agent-runtime/runResultReadProjection.ts
15392
+ import { basename } from "node:path";
15393
+ function projectRunResultRead(path, content, raw = false) {
15394
+ const unchanged = { content, projected: false };
15395
+ if (raw || !basename(path).endsWith(".result.md") || !content.startsWith("---\n")) return unchanged;
15396
+ const end3 = content.indexOf("\n---\n", 4);
15397
+ if (end3 < 0) return unchanged;
15398
+ const header = content.slice(0, end3).split("\n");
15399
+ const runLine = header.find((line) => line.startsWith("runId: "));
15400
+ const index = header.findIndex((line) => line.startsWith("acceptance: "));
15401
+ if (!runLine || index < 0) return unchanged;
15402
+ try {
15403
+ const runId = JSON.parse(runLine.slice(7));
15404
+ if (typeof runId !== "string" || basename(path) !== `${runId}.result.md`) return unchanged;
15405
+ const encoded = JSON.parse(header[index].slice(12));
15406
+ if (typeof encoded !== "string") return unchanged;
15407
+ const acceptance = JSON.parse(encoded);
15408
+ if (!isRecord(acceptance) || acceptance.schemaVersion !== 1 || acceptance.source !== "local-runtime" || !isRecord(acceptance.checks) || !Array.isArray(acceptance.checks.observations)) return unchanged;
15409
+ const { observations, ...checks } = acceptance.checks;
15410
+ let exitZero = 0;
15411
+ let exitNonzero = 0;
15412
+ let exitUnknown = 0;
15413
+ for (const observation of observations) {
15414
+ const code = isRecord(observation) ? observation.exitCode : void 0;
15415
+ if (code === 0) exitZero++;
15416
+ else if (typeof code === "number" && Number.isInteger(code)) exitNonzero++;
15417
+ else exitUnknown++;
15418
+ }
15419
+ const projected = { ...acceptance, checks: { ...checks, observationSummary: {
15420
+ count: observations.length,
15421
+ exitZero,
15422
+ exitNonzero,
15423
+ exitUnknown,
15424
+ evidence: "#/acceptance/checks/observations",
15425
+ readOriginal: "readFile with raw:true (force:true if already read)",
15426
+ limitation: "Exit counts are observations, not version-bound test or review approval."
15427
+ } } };
15428
+ const line = `acceptance: ${JSON.stringify(JSON.stringify(projected))}`;
15429
+ if (line.length >= header[index].length) return unchanged;
15430
+ header[index] = line;
15431
+ return { content: header.join("\n") + content.slice(end3), projected: true };
15432
+ } catch {
15433
+ return unchanged;
15434
+ }
15435
+ }
15436
+
15320
15437
  // packages/core/readRangeLedger.ts
15321
15438
  function filterRetainedLedgerRecords(records, retainedCap) {
15322
15439
  return records.filter((record2) => record2.chars <= retainedCap);
@@ -15351,15 +15468,27 @@ function isLineRangeCovered(request3, covered) {
15351
15468
 
15352
15469
  // packages/agent-runtime/fileTextSearch.ts
15353
15470
  import { readdir, readFile as readFile3, stat as stat3 } from "node:fs/promises";
15354
- import { basename, relative as relative2, resolve as resolve2 } from "node:path";
15471
+ import { basename as basename2, relative as relative2, resolve as resolve2 } from "node:path";
15355
15472
  import { spawn as spawnChildProcess } from "node:child_process";
15356
15473
 
15357
15474
  // packages/agent-runtime/gitignore.ts
15358
15475
  import { readFile as readFile2, stat as stat2 } from "node:fs/promises";
15359
15476
  import { resolve } from "node:path";
15477
+ function isIgnorableFsError(err) {
15478
+ if (err && typeof err === "object" && "code" in err) {
15479
+ const code = err.code;
15480
+ return code === "ENOENT" || code === "ENOTDIR";
15481
+ }
15482
+ return false;
15483
+ }
15360
15484
  async function readRootGitignorePatterns(workspaceRoot) {
15361
- const content = await readFile2(resolve(workspaceRoot, ".gitignore"), "utf8").catch(() => null);
15362
- if (content === null) return [];
15485
+ let content;
15486
+ try {
15487
+ content = await readFile2(resolve(workspaceRoot, ".gitignore"), "utf8");
15488
+ } catch (err) {
15489
+ if (isIgnorableFsError(err)) return [];
15490
+ throw err;
15491
+ }
15363
15492
  return content.split(/\r?\n/).map((line) => line.trim()).filter((line) => line && !line.startsWith("#") && !line.startsWith("!")).flatMap((line) => {
15364
15493
  const unanchored = line.replace(/^\//, "");
15365
15494
  if (unanchored.endsWith("/")) return [`${unanchored}**`];
@@ -15371,14 +15500,15 @@ async function hasRootGitignore(workspaceRoot) {
15371
15500
  try {
15372
15501
  await stat2(resolve(workspaceRoot, ".gitignore"));
15373
15502
  return true;
15374
- } catch {
15375
- return false;
15503
+ } catch (err) {
15504
+ if (isIgnorableFsError(err)) return false;
15505
+ throw err;
15376
15506
  }
15377
15507
  }
15378
15508
 
15379
15509
  // packages/agent-runtime/fileTextSearch.ts
15380
15510
  var normalizePath = (value) => value.replaceAll("\\", "/");
15381
- var pathFor = (root, file, absolute = false) => absolute ? normalizePath(file) : normalizePath(relative2(root, file) || basename(file));
15511
+ var pathFor = (root, file, absolute = false) => absolute ? normalizePath(file) : normalizePath(relative2(root, file) || basename2(file));
15382
15512
  var COMMAND_LIMIT_HEADROOM = 1;
15383
15513
  async function runCommand(binary, args2, cwd, maxResults) {
15384
15514
  const recordLimit = maxResults === void 0 ? 0 : Math.max(0, maxResults) + COMMAND_LIMIT_HEADROOM;
@@ -15404,6 +15534,17 @@ async function runCommand(binary, args2, cwd, maxResults) {
15404
15534
  }
15405
15535
  });
15406
15536
  }
15537
+ function extractGrepExcludeDir(pattern) {
15538
+ const p = pattern.replaceAll("\\", "/");
15539
+ if (!p) return null;
15540
+ if (!/\/(\*{1,2})?$/.test(p)) return null;
15541
+ const clean = p.replace(/\/(\*{1,2})?$/, "");
15542
+ if (!clean || clean.includes("/") || /[*?{}[\]]/.test(clean)) return null;
15543
+ return clean;
15544
+ }
15545
+ function isGrepExpressibleExclude(pattern) {
15546
+ return extractGrepExcludeDir(pattern) !== null;
15547
+ }
15407
15548
  function commandArgs(o, binary, target) {
15408
15549
  const args2 = binary === "rg" ? ["--line-number", "--no-heading", "--color", "never", "--null"] : ["-RIn", "--binary-files=without-match", "--null"];
15409
15550
  if (binary === "rg") args2.push("--no-require-git");
@@ -15416,8 +15557,8 @@ function commandArgs(o, binary, target) {
15416
15557
  if (o.include) args2.push("--glob", o.include);
15417
15558
  } else {
15418
15559
  for (const x of o.exclude ?? []) {
15419
- const clean = x.replace(/\/\*\*$/, "");
15420
- if (clean && !clean.includes("/")) args2.push("--exclude-dir", clean);
15560
+ const clean = extractGrepExcludeDir(x);
15561
+ if (clean) args2.push("--exclude-dir", clean);
15421
15562
  }
15422
15563
  if (o.include) args2.push("--include", o.include);
15423
15564
  }
@@ -15465,7 +15606,7 @@ function globMatch(pattern, value) {
15465
15606
  else source += c.replace(/[.+^${}()|[\]\\]/g, "\\$&");
15466
15607
  }
15467
15608
  const r = new RegExp(`^${source}$`);
15468
- return r.test(value) || r.test(basename(value));
15609
+ return r.test(value) || r.test(basename2(value));
15469
15610
  }
15470
15611
  var defaultReadTextFile = (filePath) => readFile3(filePath, "utf8").catch(() => "");
15471
15612
  async function jsSearch(o, targetPath) {
@@ -15513,6 +15654,23 @@ async function jsSearch(o, targetPath) {
15513
15654
  }
15514
15655
  return { matches: out, totalMatches: out.length, truncated: false, engine: "js" };
15515
15656
  }
15657
+ async function canGrepFulfillOptions(o) {
15658
+ if (!o.includeIgnored) {
15659
+ const gitignorePatterns = await readRootGitignorePatterns(o.root);
15660
+ if (gitignorePatterns.length > 0) return false;
15661
+ }
15662
+ if (o.include) {
15663
+ if (o.include.includes("/") || o.include.includes("\\") || o.include.includes("**") || /[{}[\]]/.test(o.include)) {
15664
+ return false;
15665
+ }
15666
+ }
15667
+ if (o.exclude && o.exclude.length > 0) {
15668
+ for (const pattern of o.exclude) {
15669
+ if (!isGrepExpressibleExclude(pattern)) return false;
15670
+ }
15671
+ }
15672
+ return true;
15673
+ }
15516
15674
  var COMMAND_ENGINES = [
15517
15675
  { engine: "ripgrep", binaryOption: "resolveRipgrepBinary", binaryName: "rg", argsKey: "rg" },
15518
15676
  { engine: "grep", binaryOption: "resolveGrepBinary", binaryName: "grep", argsKey: "grep" }
@@ -15524,6 +15682,7 @@ async function searchFileTextMatches(o) {
15524
15682
  if (targetStat.isFile()) return jsSearch(o, targetPath);
15525
15683
  const run2 = o.runCommand ?? runCommand, max4 = o.maxResults;
15526
15684
  for (const entry of COMMAND_ENGINES) {
15685
+ if (entry.engine === "grep" && !await canGrepFulfillOptions(o)) continue;
15527
15686
  const injectedResolver = o[entry.binaryOption];
15528
15687
  const binary = injectedResolver ? injectedResolver() : resolveExecutableOnPath(entry.binaryName);
15529
15688
  if (!binary) continue;
@@ -15708,13 +15867,15 @@ function runTaskLogs(args2) {
15708
15867
  const allEvents = registry.getTaskEvents(taskId, 0);
15709
15868
  const terminal = lastTerminalEvent(allEvents);
15710
15869
  const status = terminal ? reportedTaskStatus(terminal.type, terminal.exitCode) : record2 ? statusOf(record2) : "running";
15870
+ const hint = events.length === 0 && !isProcessTaskTerminalStatus(status) ? `no new events since cursor ${cursor}; the task is still running and there is nothing more to read right now. Don't re-read the same cursor immediately: later events are returned once they exist. Where the host reports completions, wait for that signal; otherwise use taskWait with an explicit budget.` : void 0;
15711
15871
  return {
15712
15872
  outcome: "ok",
15713
15873
  taskId,
15714
15874
  cursor: registry.latestTaskSeq(taskId),
15715
15875
  events,
15716
15876
  status,
15717
- terminal: isProcessTaskTerminalStatus(status)
15877
+ terminal: isProcessTaskTerminalStatus(status),
15878
+ ...hint ? { hint } : {}
15718
15879
  };
15719
15880
  }
15720
15881
  function formatTaskLogsContent(result4, options) {
@@ -15876,7 +16037,11 @@ function buildReadWorkspaceParameters() {
15876
16037
  properties: {
15877
16038
  path,
15878
16039
  lines,
15879
- force
16040
+ force,
16041
+ raw: {
16042
+ type: "boolean",
16043
+ description: "Read original bytes including full runtime acceptance observations in *.result.md. Default compacts only machine observations; report text and physical line numbers are unchanged."
16044
+ }
15880
16045
  },
15881
16046
  required: ["path"]
15882
16047
  };
@@ -16201,7 +16366,7 @@ function filterDeclaredWorkspaceToolNames(args2) {
16201
16366
  // packages/agent-runtime/workspacePathGuard.ts
16202
16367
  import { constants as fsConstants } from "node:fs";
16203
16368
  import { lstat, mkdir, open, readlink, realpath as realpath2 } from "node:fs/promises";
16204
- import { basename as basename2, dirname, isAbsolute as isAbsolute2, join as join2, relative as relative3, resolve as resolve3, sep } from "node:path";
16369
+ import { basename as basename3, dirname, isAbsolute as isAbsolute2, join as join2, relative as relative3, resolve as resolve3, sep } from "node:path";
16205
16370
  function isPathInsideRoot(root, targetPath) {
16206
16371
  const rel = relative3(resolve3(root), resolve3(targetPath));
16207
16372
  return rel === "" || rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute2(rel);
@@ -16224,7 +16389,7 @@ async function realpathAllowMissing(targetPath) {
16224
16389
  if (!isNotFoundError(error)) return null;
16225
16390
  const parent = dirname(current);
16226
16391
  if (parent === current) return null;
16227
- tail.push(basename2(current));
16392
+ tail.push(basename3(current));
16228
16393
  current = parent;
16229
16394
  }
16230
16395
  }
@@ -16418,7 +16583,7 @@ async function createMissingDirectoryChain(guard) {
16418
16583
  }
16419
16584
  try {
16420
16585
  for (const target of missing.slice().reverse()) {
16421
- const name = basename2(target);
16586
+ const name = basename3(target);
16422
16587
  if (!name || name === "." || name === "..") {
16423
16588
  throw workspacePathGuardError({
16424
16589
  ok: false,
@@ -16662,6 +16827,9 @@ function describeRejectedArgValue(value) {
16662
16827
  if (Array.isArray(value)) return "an array";
16663
16828
  return `a value of type ${typeof value}`;
16664
16829
  }
16830
+ function clipMarkerValue(text2, max4 = 60) {
16831
+ return text2.length <= max4 ? text2 : `${text2.slice(0, max4)}\u2026(${text2.length} chars)`;
16832
+ }
16665
16833
  function readIntegerArg(args2) {
16666
16834
  if (isAbsentArg(args2.value)) return {};
16667
16835
  const min4 = args2.min ?? 1;
@@ -16720,16 +16888,16 @@ function parseLinesArg(value) {
16720
16888
  }
16721
16889
  function readFileSliceArgs(args2) {
16722
16890
  const warnings = [];
16723
- const withFallback = (slice2) => {
16891
+ const withFallback = (slice2, invalidLines) => {
16724
16892
  warnings.push(
16725
16893
  `No usable line range remained; returned the first ${READ_FILE_SLICE_FALLBACK_LINES} lines instead of the whole file.`
16726
16894
  );
16727
- return { ...slice2, maxLines: READ_FILE_SLICE_FALLBACK_LINES, warnings };
16895
+ return { ...slice2, maxLines: READ_FILE_SLICE_FALLBACK_LINES, warnings, invalidLines };
16728
16896
  };
16729
- if (isAbsentArg(args2.lines)) return { warnings };
16897
+ if (isAbsentArg(args2.lines)) return { warnings, invalidLines: void 0 };
16730
16898
  const { slice, warning } = parseLinesArg(args2.lines);
16731
16899
  if (warning) warnings.push(warning);
16732
- return slice ? { ...slice, warnings } : withFallback({});
16900
+ return slice ? { ...slice, warnings, invalidLines: void 0 } : withFallback({}, describeRejectedArgValue(args2.lines));
16733
16901
  }
16734
16902
  function buildReadFileSliceHint(totalLines) {
16735
16903
  return `File has ${totalLines} lines. Use lines: ${LINES_ARG_SYNTAX}, or omit it for the whole file.`;
@@ -16998,7 +17166,7 @@ var READ_FILE_LEDGER_MAX_KEYS = 256;
16998
17166
  async function readFileTool(args2) {
16999
17167
  const parsed = parseWorkspaceToolArguments(args2.call.arguments);
17000
17168
  const requestedPath = requireWorkspaceToolPath(parsed);
17001
- const { warnings, ...sliceArgs } = readFileSliceArgs(parsed);
17169
+ const { warnings, invalidLines, ...sliceArgs } = readFileSliceArgs(parsed);
17002
17170
  const approval = await resolveLocalWorkspaceToolApproval({
17003
17171
  workspaceRoot: args2.workspaceRoot,
17004
17172
  requestedPath,
@@ -17006,7 +17174,9 @@ async function readFileTool(args2) {
17006
17174
  ...args2.confirmExternalFileAccess ? { confirmExternalFileAccess: args2.confirmExternalFileAccess } : {}
17007
17175
  });
17008
17176
  const absolutePath = approval.path;
17009
- const content = await readGuardApprovedFile(approval);
17177
+ const originalContent = await readGuardApprovedFile(approval);
17178
+ const projection = projectRunResultRead(absolutePath, originalContent, parsed.raw === true);
17179
+ const content = projection.content;
17010
17180
  const activity = extractActivity(parsed);
17011
17181
  const sliced = sliceReadFileContent({
17012
17182
  content,
@@ -17016,7 +17186,16 @@ async function readFileTool(args2) {
17016
17186
  content: sliced.content,
17017
17187
  totalLines: sliced.totalLines
17018
17188
  });
17019
- const isTruncated = sliced.truncated || truncatedRead.truncatedByCharLimit;
17189
+ const invalidLinesMarker = invalidLines !== void 0 ? `[readFile] lines=${clipMarkerValue(invalidLines)} was rejected (only ONE range: ${LINES_ARG_SYNTAX}); returned lines ${sliced.startLine}-${sliced.endLine} instead. Fix the argument before relying on this window.
17190
+ ` : "";
17191
+ const finalRead = invalidLinesMarker.length > 0 ? defensivelyTruncateReadFile({
17192
+ content: truncatedRead.content,
17193
+ totalLines: sliced.totalLines,
17194
+ limit: Math.max(1, READ_FILE_MAX_CHAR_LIMIT - invalidLinesMarker.length)
17195
+ }) : truncatedRead;
17196
+ const finalContent = invalidLinesMarker + finalRead.content;
17197
+ const truncatedByCharLimit = truncatedRead.truncatedByCharLimit || finalRead.truncatedByCharLimit;
17198
+ const isTruncated = sliced.truncated || truncatedByCharLimit;
17020
17199
  const relativePath = normalizeWorkspaceRelativePath({
17021
17200
  workspaceRoot: resolve4(args2.workspaceRoot),
17022
17201
  targetPath: absolutePath
@@ -17024,7 +17203,7 @@ async function readFileTool(args2) {
17024
17203
  const force = parsed.force === true;
17025
17204
  const ledger = args2.readLedger;
17026
17205
  const requestRange = { startLine: sliced.startLine, endLine: sliced.endLine };
17027
- const fingerprint = createHash3("sha1").update(content).digest("hex").slice(0, 20);
17206
+ const fingerprint = createHash3("sha1").update(originalContent).update(parsed.raw === true ? "\0raw" : "\0default").digest("hex").slice(0, 20);
17028
17207
  let entry = ledger?.get(relativePath);
17029
17208
  if (entry && entry.fingerprint !== fingerprint) {
17030
17209
  entry = void 0;
@@ -17052,20 +17231,25 @@ async function readFileTool(args2) {
17052
17231
  }
17053
17232
  const metadata = {
17054
17233
  path: relativePath,
17055
- bytes: Buffer.byteLength(truncatedRead.content),
17056
- totalBytes: Buffer.byteLength(content),
17234
+ bytes: Buffer.byteLength(finalContent),
17235
+ totalBytes: Buffer.byteLength(originalContent),
17236
+ ...projection.projected ? {
17237
+ projection: "run-result-observations",
17238
+ projectedTotalBytes: Buffer.byteLength(content),
17239
+ rawReadHint: "readFile raw:true reads original evidence; line numbers are unchanged."
17240
+ } : {},
17057
17241
  startLine: sliced.startLine,
17058
17242
  endLine: sliced.endLine,
17059
17243
  totalLines: sliced.totalLines,
17060
17244
  truncated: isTruncated,
17061
- ...truncatedRead.truncatedByCharLimit ? { truncatedByByteLimit: true, truncatedByCharLimit: true } : {},
17245
+ ...truncatedByCharLimit ? { truncatedByByteLimit: true, truncatedByCharLimit: true } : {},
17062
17246
  ...sliceArgs.maxLines ? { maxLines: sliceArgs.maxLines } : {},
17063
17247
  ...sliceArgs.tailLines ? { tailLines: sliceArgs.tailLines } : {},
17064
17248
  ...warnings.length ? { warnings: [...warnings, buildReadFileSliceHint(sliced.totalLines)] } : {},
17065
17249
  ...activity ? { activity } : {}
17066
17250
  };
17067
17251
  if (!isTruncated && ledger) {
17068
- const persistedChars = truncatedRead.content.length + "\n\n[tool metadata]\n".length + JSON.stringify(metadata).length;
17252
+ const persistedChars = finalContent.length + "\n\n[tool metadata]\n".length + JSON.stringify(metadata).length;
17069
17253
  const records = entry?.records ?? [];
17070
17254
  records.push({ ...requestRange, chars: persistedChars });
17071
17255
  ledger.delete(relativePath);
@@ -17079,7 +17263,7 @@ async function readFileTool(args2) {
17079
17263
  ledger.delete(oldestKey);
17080
17264
  }
17081
17265
  }
17082
- return { content: truncatedRead.content, metadata };
17266
+ return { content: finalContent, metadata };
17083
17267
  }
17084
17268
  async function ensureGuardedParentDirectory(args2) {
17085
17269
  const directoryGuard = await resolveWorkspaceSafePath({
@@ -17978,7 +18162,8 @@ function createLocalWorkspaceToolExecutors(args2) {
17978
18162
  if (!Array.isArray(paths) || paths.length === 0 || paths.some((path) => typeof path !== "string" || !path.trim())) {
17979
18163
  throw new Error("read_image: paths must contain 1 to 4 non-empty string paths (maximum 4 images)");
17980
18164
  }
17981
- const results2 = await Promise.all(paths.map(async (path) => {
18165
+ const imagePaths = paths.filter((path) => typeof path === "string");
18166
+ const results2 = await Promise.all(imagePaths.map(async (path) => {
17982
18167
  try {
17983
18168
  const absolutePath = await resolveLocalWorkspaceToolPath({
17984
18169
  workspaceRoot: args2.workspaceRoot,
@@ -18225,6 +18410,13 @@ function getAgentRuntimeToolParametersIndex() {
18225
18410
  }
18226
18411
 
18227
18412
  // packages/agent-runtime/localToolPolicy.ts
18413
+ var shellLifecycles = /* @__PURE__ */ new WeakMap();
18414
+ function registerLocalShellLifecycle(executor, lifecycle) {
18415
+ shellLifecycles.set(executor, lifecycle);
18416
+ }
18417
+ function getLocalShellLifecycle(executor) {
18418
+ return executor ? shellLifecycles.get(executor) : void 0;
18419
+ }
18228
18420
  var NEVER_LOCAL_TOOLS = /* @__PURE__ */ new Set([
18229
18421
  "deleteSpaces",
18230
18422
  "updateAgent",
@@ -18338,12 +18530,13 @@ async function runExecShellGates(args2) {
18338
18530
  return { kind: "pass", opts };
18339
18531
  }
18340
18532
  if (args2.execShellApproval) {
18341
- const result4 = await invokeCapability("execShell", args2.callArguments, {
18533
+ const execute = () => invokeCapability("execShell", args2.callArguments, {
18342
18534
  ...opts,
18343
18535
  workspaceRoot: args2.workspaceRoot,
18344
18536
  commandOutputLimit: args2.commandOutputLimit,
18345
18537
  execShellApproval: args2.execShellApproval
18346
18538
  });
18539
+ const result4 = await (args2.shellLifecycle ? args2.shellLifecycle(execute, args2.abortSignal) : execute());
18347
18540
  return { kind: "handled", result: result4 };
18348
18541
  }
18349
18542
  let parsedInput = args2.callArguments;
@@ -18387,6 +18580,7 @@ async function executeLocalToolWithPolicy(args2) {
18387
18580
  const gate = await runExecShellGates({
18388
18581
  toolName: decision.toolName,
18389
18582
  callArguments: args2.call.arguments,
18583
+ shellLifecycle: getLocalShellLifecycle(executor),
18390
18584
  execShellApproval: args2.execShellApproval,
18391
18585
  workspaceRoot: args2.workspaceRoot,
18392
18586
  commandOutputLimit: args2.commandOutputLimit,
@@ -18407,7 +18601,41 @@ async function executeLocalToolWithPolicy(args2) {
18407
18601
  );
18408
18602
  }
18409
18603
 
18604
+ // packages/agent-runtime/credentialSinkPolicy.ts
18605
+ var CREDENTIAL_REF_ALLOWED_SINKS = /* @__PURE__ */ new Set();
18606
+ function classifyCredentialRefSink(toolName, allowlist = CREDENTIAL_REF_ALLOWED_SINKS) {
18607
+ if (isCommandExecutingTool(toolName)) return "command";
18608
+ if (allowlist.has(toolName)) return "allow";
18609
+ return "confirm";
18610
+ }
18611
+ function buildCredentialSinkPermissionRequest(toolName) {
18612
+ return {
18613
+ id: `permission-credential-sink-${toolName}`,
18614
+ tool: toolName,
18615
+ action: "credential_unwrap_tool_use",
18616
+ title: "\u786E\u8BA4\u5728\u5DE5\u5177\u53C2\u6570\u4E2D\u4F7F\u7528\u51ED\u636E",
18617
+ body: `\u8BE5\u5DE5\u5177\uFF08${toolName}\uFF09\u7684\u53C2\u6570\u8981\u643A\u5E26\u51ED\u636E\uFF0C\u786E\u8BA4\u540E\u4EC5\u672C\u6B21\u8C03\u7528\u89E3\u5305\uFF1B\u660E\u6587\u4E0D\u4F1A\u5199\u5165\u5386\u53F2\u6216\u8BB0\u5F55\u3002`,
18618
+ suggestedRule: {
18619
+ scope: "once",
18620
+ pattern: { capability: "credential_use", target: "tool_arguments" }
18621
+ }
18622
+ };
18623
+ }
18624
+
18410
18625
  // packages/agent-runtime/toolCallTransaction.ts
18626
+ function buildCredentialSinkRefusal(toolName, gateId) {
18627
+ return {
18628
+ content: `\u9700\u8981\u7528\u6237\u786E\u8BA4\u624D\u80FD\u628A\u51ED\u636E\u7528\u4E8E ${toolName}\u3002\u53EF\u6539\u8D70\u53D7\u63A7\u901A\u9053\uFF1Anolo secret run --with <name> -- <cmd>\u3002`,
18629
+ metadata: {
18630
+ cancelled: true,
18631
+ actionGateResult: {
18632
+ status: "cancelled",
18633
+ ...gateId ? { gateId } : {}
18634
+ }
18635
+ }
18636
+ };
18637
+ }
18638
+ var credentialGateQueues = /* @__PURE__ */ new WeakMap();
18411
18639
  var LOCAL_TURN_ABORTED_CODE = "LOCAL_TURN_ABORTED";
18412
18640
  function buildAbortedError() {
18413
18641
  const error = new Error("local agent turn aborted by user");
@@ -18656,32 +18884,72 @@ async function executeToolCall(args2) {
18656
18884
  const rawArguments = toolCall.function.arguments;
18657
18885
  let unwrappedArguments = rawArguments;
18658
18886
  if (typeof rawArguments === "string" && rawArguments.includes("$nolo_cred:") && input.adapter.credentialBroker) {
18659
- if (isCommandExecutingTool(toolName)) {
18887
+ const sink = classifyCredentialRefSink(toolName);
18888
+ if (sink === "command") {
18660
18889
  throw new Error(
18661
18890
  `[security] \u62D2\u7EDD\u5728 ${toolName} \u547D\u4EE4\u884C\u4E2D\u76F4\u63A5\u89E3\u5305\u51ED\u8BC1\u5F15\u7528 ($nolo_cred:)\uFF0C\u4EE5\u9632\u5FA1\u63D0\u793A\u8BCD\u6CE8\u5165\u5916\u6CC4\u98CE\u9669\u3002\u8BF7\u4F7F\u7528\u53D7\u63A7\u8BA4\u8BC1\u901A\u9053\u3002`
18662
18891
  );
18663
18892
  }
18664
- unwrappedArguments = await unwrapToolArguments(rawArguments, input.adapter.credentialBroker);
18665
- }
18666
- const executePromise = input.adapter.executeTool({
18667
- id: toolCall.id,
18668
- name: toolName,
18669
- arguments: unwrappedArguments,
18670
- ...userInputText ? { userInput: userInputText } : {},
18671
- ...input.runtimeContext ? { runtimeContext: input.runtimeContext } : {},
18672
- ...args2.runToolNames ? { runToolNames: args2.runToolNames } : {},
18673
- ...input.abortSignal ? { abortSignal: input.abortSignal } : {},
18674
- // 定档信号:来源只由 turn 输入推导,不让模型/参数层参与。
18675
- // 三级判据见 plan §10——后台运行优先于子 run(后台派发的子 run 仍是
18676
- // 无人值守的,按 automation 报更保守)。
18677
- origin: input.background ? "automation" : input.runKind === "subtask" ? "agent" : "chat"
18678
- }, {
18679
- ...input.abortSignal ? { abortSignal: input.abortSignal } : {},
18680
- ...input.runtimeContext ? { runtimeContext: input.runtimeContext } : {}
18681
- });
18682
- const execStartedAtMs = Date.now();
18683
- toolResult = await runAbortableToolTask(input, executePromise, toolName);
18684
- toolExecMs = Date.now() - execStartedAtMs;
18893
+ if (sink === "confirm") {
18894
+ if (input.onActionGate) {
18895
+ const permissionRequest = buildCredentialSinkPermissionRequest(toolName);
18896
+ const gate = {
18897
+ ...permissionRequest,
18898
+ // [M1] id 带上 toolCall.id,避免同一轮同一工具两次请求 id 碰撞(对齐既有 ask 分支约定)。
18899
+ id: `${permissionRequest.id}-${toolCall.id}`,
18900
+ kind: "confirm",
18901
+ toolName,
18902
+ toolCallId: toolCall.id
18903
+ };
18904
+ const onActionGate = input.onActionGate;
18905
+ const prev = credentialGateQueues.get(input) ?? Promise.resolve();
18906
+ const next = prev.catch(() => void 0).then(
18907
+ () => runAbortableToolTask(
18908
+ input,
18909
+ onActionGate(gate),
18910
+ `${toolName} credential confirmation`
18911
+ )
18912
+ );
18913
+ credentialGateQueues.set(input, next);
18914
+ const replacement = await next.finally(() => {
18915
+ if (credentialGateQueues.get(input) === next) {
18916
+ credentialGateQueues.delete(input);
18917
+ }
18918
+ });
18919
+ if (replacement !== void 0 && isCompletedActionGateResult(replacement?.metadata?.actionGateResult)) {
18920
+ unwrappedArguments = await unwrapToolArguments(rawArguments, input.adapter.credentialBroker);
18921
+ } else {
18922
+ toolResult = buildCredentialSinkRefusal(toolName, gate.id);
18923
+ }
18924
+ } else {
18925
+ toolResult = buildCredentialSinkRefusal(toolName);
18926
+ }
18927
+ }
18928
+ if (sink === "allow") {
18929
+ unwrappedArguments = await unwrapToolArguments(rawArguments, input.adapter.credentialBroker);
18930
+ }
18931
+ }
18932
+ if (!toolResult) {
18933
+ const executePromise = input.adapter.executeTool({
18934
+ id: toolCall.id,
18935
+ name: toolName,
18936
+ arguments: unwrappedArguments,
18937
+ ...userInputText ? { userInput: userInputText } : {},
18938
+ ...input.runtimeContext ? { runtimeContext: input.runtimeContext } : {},
18939
+ ...args2.runToolNames ? { runToolNames: args2.runToolNames } : {},
18940
+ ...input.abortSignal ? { abortSignal: input.abortSignal } : {},
18941
+ // 定档信号:来源只由 turn 输入推导,不让模型/参数层参与。
18942
+ // 三级判据见 plan §10——后台运行优先于子 run(后台派发的子 run 仍是
18943
+ // 无人值守的,按 automation 报更保守)。
18944
+ origin: input.background ? "automation" : input.runKind === "subtask" ? "agent" : "chat"
18945
+ }, {
18946
+ ...input.abortSignal ? { abortSignal: input.abortSignal } : {},
18947
+ ...input.runtimeContext ? { runtimeContext: input.runtimeContext } : {}
18948
+ });
18949
+ const execStartedAtMs = Date.now();
18950
+ toolResult = await runAbortableToolTask(input, executePromise, toolName);
18951
+ toolExecMs = Date.now() - execStartedAtMs;
18952
+ }
18685
18953
  }
18686
18954
  if (tailDropNotice) {
18687
18955
  toolResult = {
@@ -18696,6 +18964,15 @@ ${toolResult.content}` : toolResult.content,
18696
18964
  toolCallId: toolCall.id,
18697
18965
  metadata: toolResult.metadata
18698
18966
  });
18967
+ if (actionGate && !input.onActionGate && actionGate.kind === "input") {
18968
+ toolResult = {
18969
+ content: "\u5F53\u524D\u8FD0\u884C\u73AF\u5883\u6CA1\u6709\u4EA4\u4E92\u8F93\u5165\u9762\u677F\uFF0C\u65E0\u6CD5\u5F39\u51FA\u51ED\u636E\u586B\u5199\u754C\u9762\uFF0C\u8BE5\u8BF7\u6C42\u5DF2\u88AB\u53D6\u6D88\u3002\u8BF7\u8BA9\u7528\u6237\u5728\u684C\u9762\u7AEF\u6216 Web \u7AEF\u754C\u9762\u63D0\u4F9B\u51ED\u636E\uFF0C\u6216\u76F4\u63A5\u5728\u5BF9\u8BDD\u4E2D\u53D1\u9001\u51ED\u636E\u5185\u5BB9\uFF1B\u51ED\u636E\u4FDD\u5B58\u540E\u7528 nolo secret run --with <NAME> -- <cmd> \u6D88\u8D39\u3002",
18970
+ metadata: {
18971
+ cancelled: true,
18972
+ actionGateResult: { gateId: actionGate.id, status: "cancelled" }
18973
+ }
18974
+ };
18975
+ }
18699
18976
  if (actionGate && input.onActionGate) {
18700
18977
  const replacement = await runAbortableToolTask(input, input.onActionGate(actionGate), `${toolName} action gate`);
18701
18978
  if (replacement) {
@@ -18789,10 +19066,15 @@ function applyPersistedTurnInput(messages, persistedInput, persistedInputReferen
18789
19066
  }
18790
19067
  function createTurnTranscript(args2) {
18791
19068
  let working = args2.initialWorking;
18792
- const raw = args2.initialWorking.slice(
19069
+ let raw = args2.initialWorking.slice(
18793
19070
  args2.prefixCount + args2.historyCount
18794
19071
  );
18795
19072
  const cap = args2.capToolMessage ?? ((msg) => msg);
19073
+ function retract(callback4) {
19074
+ const predicate = callback4;
19075
+ working = working.filter((msg) => !predicate(msg));
19076
+ raw = raw.filter((msg) => !predicate(msg));
19077
+ }
18796
19078
  return {
18797
19079
  working: () => working,
18798
19080
  replaceWorkingHistory: (nextWorkingView) => {
@@ -18811,7 +19093,13 @@ function createTurnTranscript(args2) {
18811
19093
  raw.pop();
18812
19094
  return working.pop();
18813
19095
  },
18814
- persisted: (persistedInput, persistedInputReference) => applyPersistedTurnInput(raw.map(withoutToolImages), persistedInput, persistedInputReference)
19096
+ retract,
19097
+ persisted: (persistedInput, persistedInputReference) => {
19098
+ const firstUser = raw.find(
19099
+ (message) => message.role === "user"
19100
+ );
19101
+ return firstUser?.cliMessageId ? raw.map(withoutToolImages) : applyPersistedTurnInput(raw.map(withoutToolImages), persistedInput, persistedInputReference);
19102
+ }
18815
19103
  };
18816
19104
  }
18817
19105
 
@@ -18863,10 +19151,10 @@ function createTurnUsageLedger(args2) {
18863
19151
  recordProviderCall: ({ usage, model, provider, telemetry }) => {
18864
19152
  turnUsage = mergeTurnUsage(turnUsage, usage);
18865
19153
  lastContextUsage = usage;
18866
- if (usage && Object.keys(usage).length > 0) {
19154
+ if (usage && Object.keys(usage).length > 0 || telemetry?.requestState) {
18867
19155
  providerRecords.push({
18868
- callId: resolveRecordCallId(usage),
18869
- usage,
19156
+ callId: resolveRecordCallId(usage ?? {}),
19157
+ usage: usage ?? {},
18870
19158
  model: model || args2.model || "unknown",
18871
19159
  ...provider || args2.provider ? { provider: provider || args2.provider } : {},
18872
19160
  ...telemetry && Object.keys(telemetry).length > 0 ? { telemetry } : {},
@@ -18897,7 +19185,42 @@ function createTurnUsageLedger(args2) {
18897
19185
  }
18898
19186
 
18899
19187
  // packages/agent-runtime/leafFinalHandoff.ts
18900
- 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.`;
19188
+ 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. Own investigation, implementation, related checks and failure fixes within the brief\u2019s scope and budget; leave goals and critical decisions to the parent. For required DoD checks, include the worktree, checked version (including uncommitted change scope), command, check scope, pass/fail and original-output reference; explicitly mark checks not run or evidence unavailable. Reuse valid same-version evidence; additional checks are warranted for version changes, missing/conflicting evidence or integration risk. Do not invent version metadata or treat a completion summary as independent review approval. Keep success concise without omitting required evidence.`;
19189
+
19190
+ // packages/agent-runtime/taskExecutionContract.ts
19191
+ var present = (value) => typeof value === "string" && value.trim().length > 0;
19192
+ function evaluateTaskExecutionContract(contract, outcome = {}) {
19193
+ if (!contract) return void 0;
19194
+ const result4 = (status, ...reasons) => ({ taskId: contract.taskId, status, reasons });
19195
+ if (outcome.stopped) return result4("blocked", "stopped");
19196
+ if (!present(contract.taskId) || !["owner", "implementation-leaf"].includes(contract.role) || !["active", "paused", "revoked"].includes(contract.authorization) || !["pending", "waived", "accepted", "unknown"].includes(contract.acceptance) || !["complete", "incomplete", "unknown"].includes(contract.implementation) || !["changed", "unchanged", "unknown"].includes(contract.changes)) {
19197
+ return result4("blocked", "invalid_contract");
19198
+ }
19199
+ if (contract.authorization === "revoked") return result4("blocked", "authorization_revoked");
19200
+ if (contract.authorization === "paused") return result4("blocked", "authorization_paused");
19201
+ if (outcome.executionFailed) return result4("blocked", "execution_failed");
19202
+ if (outcome.budgetExhausted) return result4("incomplete", "budget_exhausted");
19203
+ if (contract.implementation !== "complete") return result4("incomplete", "implementation_incomplete");
19204
+ if (contract.acceptance === "pending") return result4("waiting", "acceptance_pending");
19205
+ if (contract.acceptance === "unknown") return result4("blocked", "acceptance_unknown");
19206
+ if (contract.changes === "unknown") return result4("incomplete", "changes_unknown");
19207
+ if (contract.role === "implementation-leaf" || contract.changes === "unchanged") return result4("completed");
19208
+ if (!present(contract.revision)) return result4("incomplete", "revision_missing");
19209
+ const review = contract.review;
19210
+ if (!review || !present(review.evidenceRef)) return result4("incomplete", "review_missing");
19211
+ if (review.revision !== contract.revision) return result4("incomplete", "review_stale");
19212
+ if (!present(contract.authorInstanceId) || !present(review.reviewerInstanceId) || review.reviewerInstanceId === contract.authorInstanceId) return result4("blocked", "review_not_independent");
19213
+ if (review.verdict !== "APPROVE" || review.hasCriticalOrHigh !== false) return result4("blocked", "review_rejected");
19214
+ const commit = contract.commit;
19215
+ if (!commit || !present(commit.commitId) || !present(commit.evidenceRef)) return result4("incomplete", "commit_missing");
19216
+ if (commit.revision !== contract.revision) return result4("incomplete", "commit_stale");
19217
+ const merge4 = contract.localMerge;
19218
+ if (!merge4 || !present(merge4.targetBranch) || !present(merge4.mergeCommitId) || !present(merge4.evidenceRef)) {
19219
+ return result4("incomplete", "local_merge_missing");
19220
+ }
19221
+ if (merge4.sourceCommitId !== commit.commitId) return result4("incomplete", "local_merge_mismatch");
19222
+ return result4("completed");
19223
+ }
18901
19224
 
18902
19225
  // packages/agent-runtime/contextBlockScope.ts
18903
19226
  var isContextBlockScope = (value) => {
@@ -18913,6 +19236,34 @@ var normalizeContextBlockScopes = (legacyBlocks = void 0, scopes = void 0) => {
18913
19236
  return (Array.isArray(legacyBlocks) ? legacyBlocks : []).filter((block) => typeof block === "string").map((block) => block.trim()).filter((block) => block.length > 0).map((block) => ({ content: block, cacheScope: "turn" }));
18914
19237
  };
18915
19238
 
19239
+ // packages/agent-runtime/providerInputItemLimits.ts
19240
+ var PROVIDER_INPUT_ITEM_LIMITS = [
19241
+ {
19242
+ provider: "opendesign",
19243
+ endpointHost: "open-design.ai",
19244
+ // Upstream HTTP 400: "[code=context_length_exceeded] Input is too long:
19245
+ // conversation history exceeds the limit on input items (1003 > 1000 items)."
19246
+ // type=invalid_request_error, code=context_length_exceeded, param=messages.
19247
+ maxInputItems: 1e3
19248
+ }
19249
+ ];
19250
+ function resolveProviderInputItemLimit(args2) {
19251
+ const byProvider = PROVIDER_INPUT_ITEM_LIMITS.find(
19252
+ (entry) => entry.provider === args2.provider?.toLowerCase()
19253
+ );
19254
+ if (byProvider) return byProvider.maxInputItems;
19255
+ if (!args2.endpoint) return void 0;
19256
+ let host;
19257
+ try {
19258
+ host = new URL(args2.endpoint.includes("://") ? args2.endpoint : `https://${args2.endpoint}`).hostname.toLowerCase();
19259
+ } catch {
19260
+ return void 0;
19261
+ }
19262
+ return PROVIDER_INPUT_ITEM_LIMITS.find(
19263
+ (entry) => host === entry.endpointHost || host.endsWith(`.${entry.endpointHost}`)
19264
+ )?.maxInputItems;
19265
+ }
19266
+
18916
19267
  // packages/agent-runtime/localAutoCompaction.ts
18917
19268
  import { randomUUID } from "node:crypto";
18918
19269
  function toPlanCompressionMessages(history) {
@@ -18991,25 +19342,75 @@ function projectHistoryWithSummary(args2) {
18991
19342
  return [...summaryMsg, ...preservedUser, ...projected];
18992
19343
  }
18993
19344
  var COLD_RESUME_IDLE_MS = 60 * 60 * 1e3;
18994
- async function runSummaryFallback(fallback, content, options) {
19345
+ async function completeRoutedCompactionSummary(args2) {
19346
+ const budget = createSummaryBudget({ mode: "automatic", signal: args2.options.signal, deadlineMs: args2.options.deadlineMs });
18995
19347
  try {
18996
- const text2 = await fallback(content, options);
18997
- if (text2 === null) return { summary: null, error: "no summary" };
18998
- if (typeof text2 !== "string") return { summary: null, error: "non-string output" };
18999
- const trimmed = text2.trim();
19000
- return trimmed.length > 0 ? { summary: trimmed } : { summary: null, error: "empty summary" };
19001
- } catch (error) {
19002
- console.warn("[localLoop] auto-compaction summary fallback failed", {
19003
- attemptId: options.attemptId,
19004
- stage: "fallback"
19348
+ const options = { ...args2.options, signal: budget.signal, deadlineMs: budget.deadlineMs };
19349
+ const eligible = await budget.run(async () => {
19350
+ try {
19351
+ return await args2.fallback.preferPlatform?.(options) === true;
19352
+ } catch (error) {
19353
+ if (budget.signal.aborted || isAbortError(error) || error?.name === "TimeoutError") throw error;
19354
+ return false;
19355
+ }
19005
19356
  });
19006
- const message = error instanceof Error ? error.message : String(error);
19007
- return { summary: null, error: message };
19357
+ let currentResult;
19358
+ const result4 = await runSummaryWithContract({
19359
+ budget,
19360
+ eligibility: { userId: "authenticated-account", authenticated: eligible, platformAvailable: eligible, credits: eligible ? 1 : void 0 },
19361
+ platform: {
19362
+ model: PLATFORM_HOSTED_DEEPSEEK_FLASH_MODEL,
19363
+ provider: "nolo",
19364
+ attemptId: options.attemptId,
19365
+ run: async () => args2.fallback.complete ? args2.fallback.complete(args2.content, options) : { text: await args2.fallback(args2.content, options) ?? "" }
19366
+ },
19367
+ current: { attemptId: options.attemptId, run: async () => {
19368
+ currentResult = await args2.current();
19369
+ return {
19370
+ text: currentResult.content ?? "",
19371
+ finishReason: currentResult.finish_reason,
19372
+ maxOutputTokens: options.maxOutputTokens,
19373
+ completionTokens: summaryCompletionTokens(currentResult.usage)
19374
+ };
19375
+ } }
19376
+ });
19377
+ if (result4.kind === "error" || result4.kind === "cancelled") {
19378
+ throw Object.assign(new Error(result4.error.message), result4.error, {
19379
+ type: result4.error.errorType,
19380
+ name: result4.kind === "cancelled" ? "AbortError" : result4.error.category === "timeout" ? "TimeoutError" : "SummaryError"
19381
+ });
19382
+ }
19383
+ if (result4.kind === "truncated") throw {
19384
+ code: "SUMMARY_TRUNCATED",
19385
+ stage: "decode",
19386
+ finishReason: result4.completion?.finishReason,
19387
+ maxOutputTokens: result4.completion?.maxOutputTokens ?? options.maxOutputTokens,
19388
+ completionTokens: result4.completion?.completionTokens,
19389
+ outputChars: result4.completion?.text.length,
19390
+ responseStatus: result4.completion?.responseStatus,
19391
+ incompleteReason: result4.completion?.incompleteReason,
19392
+ model: result4.route === "platform" ? PLATFORM_HOSTED_DEEPSEEK_FLASH_MODEL : currentResult?.model,
19393
+ provider: result4.route === "platform" ? "nolo" : currentResult?.provider
19394
+ };
19395
+ return result4.route === "current" && currentResult ? { ...currentResult, summaryCompletion: result4.completion } : {
19396
+ content: result4.kind === "success" ? result4.text : "",
19397
+ model: PLATFORM_HOSTED_DEEPSEEK_FLASH_MODEL,
19398
+ provider: "nolo",
19399
+ summaryCompletion: result4.completion
19400
+ };
19401
+ } finally {
19402
+ budget.dispose();
19008
19403
  }
19009
19404
  }
19010
19405
  var COMPACTION_RETRY_BACKOFF_MS = 6e4;
19011
19406
  var COMPACTION_RETRY_BACKOFF_MAX_ENTRIES = 256;
19012
19407
  var compactionRetryBackoff = /* @__PURE__ */ new Map();
19408
+ var COMPACTION_RETRY_DELAYS_MS = [3e3, 8e3];
19409
+ var compactionRetryDelaysOverride;
19410
+ function compactionRetryDelayMs(retryCount) {
19411
+ const table = compactionRetryDelaysOverride ?? COMPACTION_RETRY_DELAYS_MS;
19412
+ return table[Math.min(retryCount, table.length - 1)];
19413
+ }
19013
19414
  function isCompactionBackedOff(dialogId, now) {
19014
19415
  for (const [key, until2] of compactionRetryBackoff) {
19015
19416
  if (until2 <= now) compactionRetryBackoff.delete(key);
@@ -19079,7 +19480,19 @@ async function maybeAutoCompactLocalHistory(args2) {
19079
19480
  reason = "hash-mismatch";
19080
19481
  }
19081
19482
  console.warn(
19082
- `[localLoop] invalidated dialog summary for ${dialogId} (${reason}); discarding and re-compressing`
19483
+ `[localLoop] invalidated dialog summary for ${dialogId} (${reason}); discarding and re-compressing`,
19484
+ // 诊断分项(**仅加日志,不改任何哈希/校验/重锚语义**):区分「历史真被改写」
19485
+ // 与「保存/重算口径不一致」。只记长度、条数、消息 id 与哈希前 8 位;
19486
+ // 消息正文与摘要正文一律不进日志。重算哈希只发生在失效分支内,
19487
+ // 健康路径零额外开销。
19488
+ {
19489
+ reason,
19490
+ historyLength: history.length,
19491
+ storedSourceCount: typeof invalidStored?.sourceCount === "number" ? invalidStored.sourceCount : null,
19492
+ summarizedBeforeId: typeof invalidStored?.summarizedBeforeId === "string" ? invalidStored.summarizedBeforeId : null,
19493
+ recomputedHashPrefix: hashSummarySourceSlice(history, invalidStored?.summarizedBeforeId)?.slice(0, 8) ?? null,
19494
+ storedHashPrefix: typeof invalidStored?.sourceHash === "string" && invalidStored.sourceHash ? invalidStored.sourceHash.slice(0, 8) : null
19495
+ }
19083
19496
  );
19084
19497
  stored = null;
19085
19498
  }
@@ -19088,18 +19501,34 @@ async function maybeAutoCompactLocalHistory(args2) {
19088
19501
  const legacyAnchorIndex = summarizedBeforeId ? findSummaryAnchorIndex(bridged, summarizedBeforeId) : -1;
19089
19502
  const planAnchorId = summarizedBeforeId && /^local-(0|[1-9]\d*)$/.test(summarizedBeforeId) && legacyAnchorIndex >= 0 ? bridged[legacyAnchorIndex].id : summarizedBeforeId;
19090
19503
  const contextWindow = typeof args2.contextWindow === "number" && Number.isFinite(args2.contextWindow) && args2.contextWindow > 0 ? args2.contextWindow : getModelContextWindow(args2.model ?? "");
19091
- const allMsgs = toPlanCompressionMessages(history);
19504
+ const allMsgs = toPlanCompressionMessages(prepareHistoryForNextTurn(history, args2.contextReferenceResolver));
19505
+ const maxInputItems = resolveProviderInputItemLimit({ provider: args2.provider, endpoint: args2.providerEndpoint });
19506
+ const force = args2.force === true || args2.requestOverBudget?.(
19507
+ existingSummary ? projectHistoryWithSummary({ history, summary: existingSummary, summarizedBeforeId }) : history
19508
+ ) === true;
19092
19509
  const plan = planCompression({
19093
19510
  allMsgs,
19094
19511
  summarizedBeforeId: planAnchorId,
19095
19512
  summary: existingSummary,
19096
19513
  contextWindow,
19514
+ // 失败路径补救:预算裁剪即将丢消息时由 localLoop 传入 force,交由 planCompression
19515
+ // 的既有 force 语义产出待压缩切片(仍受最小条数/安全前缀约束)。
19516
+ force,
19517
+ ...force ? { reason: "budget_rescue" } : {},
19518
+ availableHistoryTokens: args2.availableHistoryTokens,
19097
19519
  preserveLatestUserByProjection: true,
19520
+ maxInputItems,
19098
19521
  ...args2.realContextUsagePercent !== void 0 ? { realContextUsagePercent: args2.realContextUsagePercent } : {}
19099
19522
  });
19523
+ const itemDiagnostics = maxInputItems !== void 0 ? {
19524
+ itemCount: plan.diagnostics?.itemCount,
19525
+ maxInputItems,
19526
+ triggeredByItemLimit: plan.diagnostics?.triggeredByItemLimit
19527
+ } : {};
19100
19528
  const projectExisting = () => {
19101
19529
  const diag = plan.diagnostics;
19102
19530
  const diagSnapshot = diag ? {
19531
+ ...itemDiagnostics,
19103
19532
  estimatedTokens: diag.totalUsed,
19104
19533
  historyBudget: diag.historyBudget,
19105
19534
  triggerRatio: diag.triggerRatio,
@@ -19107,7 +19536,7 @@ async function maybeAutoCompactLocalHistory(args2) {
19107
19536
  } : {};
19108
19537
  const skippedDecision = {
19109
19538
  outcome: "skipped",
19110
- skipReason: diag?.emptyReason === "no-pending" ? "no-pending" : diag?.emptyReason === "below-min-compress-count" ? "below-min-compress-count" : "below-trigger",
19539
+ skipReason: diag?.emptyReason === "no-pending" ? "no-pending" : diag?.emptyReason === "below-min-compress-count" ? "below-min-compress-count" : diag?.emptyReason === "no-safe-prefix" ? "no-safe-prefix" : "below-trigger",
19111
19540
  ...diagSnapshot,
19112
19541
  ...diag ? { pendingMsgCount: diag.pendingMsgCount } : {},
19113
19542
  ...diag?.compressCount !== void 0 ? { compressCount: diag.compressCount } : {}
@@ -19121,6 +19550,13 @@ async function maybeAutoCompactLocalHistory(args2) {
19121
19550
  }),
19122
19551
  compressed: true,
19123
19552
  summaryGenerated: false,
19553
+ selectedSummary: {
19554
+ outcome: "loaded",
19555
+ hash: sha256Hex(existingSummary),
19556
+ ...summarizedBeforeId ? { anchorId: summarizedBeforeId } : {},
19557
+ ...stored?.sourceHash ? { sourceHash: stored.sourceHash } : {},
19558
+ ...stored?.sourceCount !== void 0 ? { sourceCount: stored.sourceCount } : {}
19559
+ },
19124
19560
  decision: {
19125
19561
  outcome: "compressed",
19126
19562
  trigger: "existing-summary",
@@ -19141,6 +19577,7 @@ async function maybeAutoCompactLocalHistory(args2) {
19141
19577
  outcome: "skipped",
19142
19578
  skipReason: "retry-backoff",
19143
19579
  ...diag ? {
19580
+ ...itemDiagnostics,
19144
19581
  estimatedTokens: diag.totalUsed,
19145
19582
  historyBudget: diag.historyBudget,
19146
19583
  triggerRatio: diag.triggerRatio,
@@ -19158,14 +19595,22 @@ async function maybeAutoCompactLocalHistory(args2) {
19158
19595
  });
19159
19596
  if (bounded3.status === "blocked") {
19160
19597
  const detail = `bounded compaction blocked: ${bounded3.reason}`;
19161
- console.warn(`[localLoop] ${detail}`);
19598
+ console.warn(
19599
+ `[localLoop] ${detail}`,
19600
+ ...bounded3.diagnostics ? [bounded3.diagnostics] : []
19601
+ );
19162
19602
  return {
19163
19603
  ...projectExisting(),
19164
19604
  decision: { outcome: "skipped", skipReason: "bounded-request-blocked", detail }
19165
19605
  };
19166
19606
  }
19167
19607
  emitPhase({ kind: "compaction-start" });
19168
- const compactionAttemptId = randomUUID();
19608
+ let compactionAttemptId = randomUUID();
19609
+ const maxAttempts = 3;
19610
+ let attempt = 0;
19611
+ let retryCount = 0;
19612
+ let attemptRoute = "current";
19613
+ let attemptTimingLogged = false;
19169
19614
  let compactionT0 = Date.now();
19170
19615
  let compactionInputTokens;
19171
19616
  let compactionPrevSummaryTokens;
@@ -19174,7 +19619,15 @@ async function maybeAutoCompactLocalHistory(args2) {
19174
19619
  let primaryElapsedMs;
19175
19620
  let fallbackElapsedMs;
19176
19621
  let saveElapsedMs;
19622
+ let summaryDeadlineMs = Infinity;
19623
+ let summaryModel = args2.model;
19624
+ let summaryProvider = args2.provider;
19625
+ let summaryDiagnostics = {
19626
+ maxOutputTokens: bounded3.outputTokenBudget
19627
+ };
19177
19628
  const logCompactionTiming = (outcome, fields) => {
19629
+ if (attemptTimingLogged) return;
19630
+ attemptTimingLogged = true;
19178
19631
  if (compactionInputTokens === void 0 && compactionInputSource !== void 0) {
19179
19632
  compactionInputTokens = estimateTokenCount(compactionInputSource);
19180
19633
  }
@@ -19184,10 +19637,20 @@ async function maybeAutoCompactLocalHistory(args2) {
19184
19637
  console.log(
19185
19638
  `[compaction-timing] ${JSON.stringify({
19186
19639
  attemptId: compactionAttemptId,
19187
- model: fields.model ?? args2.model ?? null,
19640
+ attempt,
19641
+ maxAttempts,
19642
+ physicalAttempts: attempt,
19643
+ route: attemptRoute,
19644
+ model: fields.model ?? summaryModel ?? null,
19645
+ provider: summaryProvider ?? null,
19188
19646
  inputTokens: compactionInputTokens ?? null,
19189
19647
  prevSummaryTokens: compactionPrevSummaryTokens ?? null,
19190
- outputChars: fields.outputChars ?? null,
19648
+ outputChars: summaryDiagnostics.outputChars ?? fields.outputChars ?? null,
19649
+ finishReason: summaryDiagnostics.finishReason ?? null,
19650
+ responseStatus: summaryDiagnostics.responseStatus ?? null,
19651
+ incompleteReason: summaryDiagnostics.incompleteReason ?? null,
19652
+ maxOutputTokens: summaryDiagnostics.maxOutputTokens ?? null,
19653
+ completionTokens: summaryDiagnostics.completionTokens ?? null,
19191
19654
  elapsedMs: Date.now() - compactionT0,
19192
19655
  primaryElapsedMs: primaryElapsedMs ?? null,
19193
19656
  fallbackElapsedMs: fallbackElapsedMs ?? null,
@@ -19197,10 +19660,11 @@ async function maybeAutoCompactLocalHistory(args2) {
19197
19660
  );
19198
19661
  };
19199
19662
  try {
19200
- if (args2.abortSignal?.aborted) {
19201
- throw Object.assign(new Error("auto-compaction aborted"), { compactionReason: "aborted" });
19202
- }
19203
- const provider = await args2.resolveProvider();
19663
+ let reportAttemptUsage2 = function(value) {
19664
+ const usage = value?.usage;
19665
+ if (usage && typeof usage === "object") args2.onAttemptUsage?.(usage);
19666
+ };
19667
+ var reportAttemptUsage = reportAttemptUsage2;
19204
19668
  if (args2.abortSignal?.aborted) {
19205
19669
  throw Object.assign(new Error("auto-compaction aborted"), { compactionReason: "aborted" });
19206
19670
  }
@@ -19209,35 +19673,144 @@ async function maybeAutoCompactLocalHistory(args2) {
19209
19673
  compactionInputSource = promptContent;
19210
19674
  compactionPrevSummarySource = existingSummary;
19211
19675
  compactionT0 = Date.now();
19212
- const timeoutMs = typeof args2.timeoutMs === "number" && Number.isFinite(args2.timeoutMs) && args2.timeoutMs > 0 ? args2.timeoutMs : void 0;
19676
+ const timeoutMs = Math.min(6e4, typeof args2.timeoutMs === "number" && Number.isFinite(args2.timeoutMs) && args2.timeoutMs > 0 ? args2.timeoutMs : 6e4);
19677
+ const requestDeadlineMs = Date.now() + timeoutMs;
19678
+ summaryDeadlineMs = requestDeadlineMs;
19213
19679
  const requestController = new AbortController();
19214
19680
  let timeoutHandle;
19215
19681
  let abortListener;
19216
19682
  if (args2.abortSignal?.aborted) requestController.abort(args2.abortSignal.reason);
19217
- const request3 = provider.complete(
19218
- [{ role: "system", content: COMPACTION_SUMMARY_SYSTEM_PROMPT }, { role: "user", content: promptContent }],
19219
- {
19220
- signal: requestController.signal,
19221
- ...timeoutMs ? { timeoutMs } : {},
19222
- // 仅日志用的 attempt id:让 compaction 埋点与 transport 诊断日志 /
19223
- // 服务端 x-request-id 可对上,用于关联请求级证据。不进 prompt/摘要/持久化。
19224
- requestId: compactionAttemptId,
19225
- // Room for factual handoff, exact commands and file operations.
19226
- max_tokens: bounded3.outputTokenBudget,
19227
- reasoning_effort: "low"
19228
- }
19229
- ).then(
19230
- (value) => ({ kind: "done", value }),
19231
- (error) => ({ kind: "provider-error", error })
19232
- );
19233
- const guards = [];
19234
- if (timeoutMs) guards.push(new Promise((resolve6) => {
19235
- timeoutHandle = setTimeout(() => {
19236
- requestController.abort();
19237
- resolve6({ kind: "timeout" });
19238
- }, timeoutMs);
19239
- }));
19240
- if (args2.abortSignal) guards.push(new Promise((resolve6) => {
19683
+ let currentOnly = false;
19684
+ const physicalRequest = async (route, operation) => {
19685
+ if (attempt >= maxAttempts) throw { code: "SUMMARY_ATTEMPT_LIMIT" };
19686
+ if (requestController.signal.aborted) throw requestController.signal.reason;
19687
+ attempt++;
19688
+ attemptRoute = route;
19689
+ attemptTimingLogged = false;
19690
+ compactionAttemptId = randomUUID();
19691
+ compactionT0 = Date.now();
19692
+ primaryElapsedMs = void 0;
19693
+ summaryDiagnostics = { maxOutputTokens: bounded3.outputTokenBudget };
19694
+ try {
19695
+ const value = await operation();
19696
+ reportAttemptUsage2(value);
19697
+ return value;
19698
+ } catch (error) {
19699
+ reportAttemptUsage2(error);
19700
+ const safe = classifySummaryError(error);
19701
+ summaryDiagnostics = {
19702
+ ...summaryDiagnostics,
19703
+ finishReason: safe.finishReason,
19704
+ responseStatus: safe.responseStatus,
19705
+ incompleteReason: safe.incompleteReason,
19706
+ completionTokens: safe.completionTokens,
19707
+ outputChars: safe.outputChars,
19708
+ maxOutputTokens: safe.maxOutputTokens ?? bounded3.outputTokenBudget
19709
+ };
19710
+ primaryElapsedMs = Date.now() - compactionT0;
19711
+ logCompactionTiming(safe.category === "cancelled" ? "aborted" : safe.category === "timeout" ? "timeout" : "provider-error", {});
19712
+ throw error;
19713
+ }
19714
+ };
19715
+ const currentRequest = async () => {
19716
+ currentOnly = true;
19717
+ const provider = await args2.resolveProvider();
19718
+ summaryModel = provider.model ?? args2.model;
19719
+ summaryProvider = args2.provider;
19720
+ if (requestController.signal.aborted) throw requestController.signal.reason ?? new DOMException("Summary cancelled", "AbortError");
19721
+ try {
19722
+ return await physicalRequest("current", () => provider.complete(
19723
+ [{ role: "system", content: COMPACTION_SUMMARY_SYSTEM_PROMPT }, { role: "user", content: promptContent }],
19724
+ {
19725
+ signal: requestController.signal,
19726
+ timeoutMs: Math.max(1, requestDeadlineMs - Date.now()),
19727
+ // 仅日志用的 attempt id:让 compaction 埋点与 transport 诊断日志 /
19728
+ // 服务端 x-request-id 可对上,用于关联请求级证据。不进 prompt/摘要/持久化。
19729
+ requestId: compactionAttemptId,
19730
+ // Room for factual handoff, exact commands and file operations.
19731
+ max_tokens: bounded3.outputTokenBudget,
19732
+ reasoning_effort: "low"
19733
+ }
19734
+ ));
19735
+ } catch (cause) {
19736
+ const safe = classifySummaryError(cause, { model: provider.model, provider: args2.provider ?? void 0, attemptId: compactionAttemptId });
19737
+ throw Object.assign(new Error(safe.message), safe, {
19738
+ type: safe.errorType,
19739
+ name: safe.category === "cancelled" ? "AbortError" : safe.category === "timeout" ? "TimeoutError" : "SummaryError"
19740
+ });
19741
+ }
19742
+ };
19743
+ const fallback = args2.summaryFallback?.preferPlatform ? args2.summaryFallback : void 0;
19744
+ let eligibility;
19745
+ const routedFallback = fallback ? Object.assign(
19746
+ async (content, options) => {
19747
+ summaryModel = PLATFORM_HOSTED_DEEPSEEK_FLASH_MODEL;
19748
+ summaryProvider = "nolo";
19749
+ return physicalRequest("platform", () => fallback(content, { ...options, attemptId: compactionAttemptId }));
19750
+ },
19751
+ {
19752
+ preferPlatform: (options) => eligibility ??= fallback.preferPlatform(options),
19753
+ ...fallback.complete ? { complete: async (content, options) => {
19754
+ summaryModel = PLATFORM_HOSTED_DEEPSEEK_FLASH_MODEL;
19755
+ summaryProvider = "nolo";
19756
+ return physicalRequest("platform", () => fallback.complete(content, { ...options, attemptId: compactionAttemptId }));
19757
+ } } : {}
19758
+ }
19759
+ ) : void 0;
19760
+ const requestOnce = () => routedFallback && !currentOnly ? completeRoutedCompactionSummary({
19761
+ fallback: routedFallback,
19762
+ content: promptContent,
19763
+ options: {
19764
+ signal: requestController.signal,
19765
+ deadlineMs: requestDeadlineMs,
19766
+ attemptId: compactionAttemptId,
19767
+ maxOutputTokens: bounded3.outputTokenBudget
19768
+ },
19769
+ current: currentRequest
19770
+ }) : currentRequest();
19771
+ const request3 = (async () => {
19772
+ for (; ; ) {
19773
+ try {
19774
+ return await requestOnce();
19775
+ } catch (error) {
19776
+ const safe = classifySummaryError(error);
19777
+ const terminal = isQuotaExhaustedError(safe.status ?? 0, safe.code, safe.errorType) || safe.category === "cancelled" || safe.category === "timeout" || ["PROVIDER_UNAVAILABLE", "SUMMARY_TRUNCATED", "INSUFFICIENT_BALANCE"].includes(safe.code ?? "") || safe.errorType === "insufficient_balance_error";
19778
+ const busy = !terminal && (safe.category === "http" && (safe.status === 503 || safe.status === 429) || /^(capacity|overload(?:ed)?|rate[_-]?limit(?:ed)?)(?:[_-](?:error|exceeded))?$/i.test(safe.errorType ?? "") || ["PLATFORM_LLM_BUSY", "RATE_LIMIT", "CAPACITY", "OVERLOAD", "RESOURCE_EXHAUSTED"].includes(safe.code ?? ""));
19779
+ const delayMs = compactionRetryDelayMs(retryCount);
19780
+ if (!busy || attempt >= maxAttempts || requestController.signal.aborted || requestDeadlineMs - Date.now() < delayMs + 5e3) throw error;
19781
+ primaryElapsedMs = Date.now() - compactionT0;
19782
+ logCompactionTiming("provider-error", {});
19783
+ emitPhase({ kind: "compaction-retry", attempt: attempt + 1, maxAttempts });
19784
+ await new Promise((resolve6, reject) => {
19785
+ const signal = requestController.signal;
19786
+ const cancel = () => {
19787
+ clearTimeout(timer);
19788
+ signal.removeEventListener("abort", cancel);
19789
+ reject(signal.reason);
19790
+ };
19791
+ const timer = setTimeout(() => {
19792
+ signal.removeEventListener("abort", cancel);
19793
+ resolve6();
19794
+ }, delayMs);
19795
+ signal.addEventListener("abort", cancel, { once: true });
19796
+ if (signal.aborted) cancel();
19797
+ });
19798
+ if (requestController.signal.aborted || requestDeadlineMs - Date.now() < 5e3) throw error;
19799
+ retryCount++;
19800
+ }
19801
+ }
19802
+ })().then(
19803
+ (value) => ({ kind: "done", value }),
19804
+ (error) => ({ kind: "provider-error", error })
19805
+ );
19806
+ const guards = [];
19807
+ if (timeoutMs) guards.push(new Promise((resolve6) => {
19808
+ timeoutHandle = setTimeout(() => {
19809
+ requestController.abort(new DOMException("Summary deadline exceeded", "TimeoutError"));
19810
+ resolve6({ kind: "timeout" });
19811
+ }, timeoutMs);
19812
+ }));
19813
+ if (args2.abortSignal) guards.push(new Promise((resolve6) => {
19241
19814
  abortListener = () => {
19242
19815
  requestController.abort(args2.abortSignal?.reason);
19243
19816
  resolve6({ kind: "aborted" });
@@ -19250,71 +19823,37 @@ async function maybeAutoCompactLocalHistory(args2) {
19250
19823
  if (timeoutHandle) clearTimeout(timeoutHandle);
19251
19824
  if (abortListener) args2.abortSignal?.removeEventListener("abort", abortListener);
19252
19825
  const result4 = outcome.kind === "done" ? outcome.value : void 0;
19253
- let newSummary = result4?.finish_reason !== "length" && typeof result4?.content === "string" ? result4.content.trim() : "";
19254
- const primaryFailure = outcome.kind === "aborted" || args2.abortSignal?.aborted ? "aborted" : newSummary ? null : outcome.kind === "done" ? "provider-error" : outcome.kind;
19255
- if (primaryFailure === "aborted") {
19256
- throw Object.assign(new Error("auto-compaction aborted"), {
19257
- compactionReason: "aborted"
19258
- });
19259
- }
19260
- const summaryFallback = args2.summaryFallback;
19261
- let fallbackFailureDetail;
19262
- if (primaryFailure && summaryFallback) {
19263
- const fallbackT0 = Date.now();
19264
- const fallbackResult = await runSummaryFallback(
19265
- summaryFallback,
19266
- promptContent,
19267
- {
19268
- signal: args2.abortSignal,
19269
- deadlineMs: Date.now() + 3e4,
19270
- attemptId: compactionAttemptId,
19271
- maxOutputTokens: bounded3.outputTokenBudget
19272
- }
19273
- );
19274
- fallbackElapsedMs = Date.now() - fallbackT0;
19275
- if (args2.abortSignal?.aborted) {
19276
- throw Object.assign(new Error("auto-compaction aborted"), {
19277
- compactionReason: "aborted"
19278
- });
19279
- }
19280
- if (fallbackResult.summary) {
19281
- newSummary = fallbackResult.summary;
19282
- } else {
19283
- fallbackFailureDetail = fallbackResult.error ?? "no summary";
19284
- }
19826
+ if (result4?.model) summaryModel = result4.model;
19827
+ if (result4?.provider) summaryProvider = result4.provider;
19828
+ if (outcome.kind === "aborted") throw Object.assign(new Error("Summary cancelled"), { name: "AbortError", compactionReason: "aborted" });
19829
+ if (outcome.kind === "timeout") throw Object.assign(new Error("Summary deadline exceeded"), { compactionReason: "timeout" });
19830
+ if (outcome.kind === "provider-error") throw outcome.error;
19831
+ const completion = result4?.summaryCompletion;
19832
+ const diagnostic = classifySummaryError({
19833
+ finishReason: completion?.finishReason ?? result4?.finish_reason,
19834
+ responseStatus: completion?.responseStatus,
19835
+ incompleteReason: completion?.incompleteReason,
19836
+ maxOutputTokens: completion?.maxOutputTokens ?? bounded3.outputTokenBudget,
19837
+ completionTokens: completion?.completionTokens ?? summaryCompletionTokens(result4?.usage),
19838
+ outputChars: completion?.text.length ?? result4?.content?.length
19839
+ });
19840
+ summaryDiagnostics = {
19841
+ finishReason: diagnostic.finishReason,
19842
+ responseStatus: diagnostic.responseStatus,
19843
+ incompleteReason: diagnostic.incompleteReason,
19844
+ maxOutputTokens: diagnostic.maxOutputTokens,
19845
+ completionTokens: diagnostic.completionTokens,
19846
+ outputChars: diagnostic.outputChars
19847
+ };
19848
+ if (isTruncatedCompletion({ ...completion, finishReason: completion?.finishReason ?? result4?.finish_reason })) {
19849
+ throw { code: "SUMMARY_TRUNCATED", stage: "decode", ...summaryDiagnostics };
19285
19850
  }
19851
+ const newSummary = result4?.content?.trim() ?? "";
19286
19852
  if (!newSummary) {
19287
- const failureNow = args2.now?.() ?? Date.now();
19288
- recordCompactionFailure(dialogId, failureNow);
19289
- const primaryDetail = outcome.kind === "provider-error" ? outcome.error instanceof Error ? outcome.error.message : String(outcome.error) : outcome.kind === "done" ? "summary model returned empty content" : `auto-compaction timed out after ${timeoutMs ?? primaryElapsedMs}ms`;
19290
- const primaryDiagnostic = `primary failed (${primaryElapsedMs ?? 0}ms): ${primaryDetail}`;
19291
- const fallbackDiagnostic = fallbackElapsedMs !== void 0 ? `; fallback failed (${fallbackElapsedMs}ms): ${fallbackFailureDetail ?? "no summary"}` : "";
19292
- const combinedDetail = `${primaryDiagnostic}${fallbackDiagnostic}`;
19293
- if (outcome.kind === "done") {
19294
- console.warn(
19295
- "[localLoop] auto-compaction produced empty summary; keeping prior projection"
19296
- );
19297
- logCompactionTiming("empty", {});
19298
- const failureReason = "provider-error";
19299
- emitPhase({ kind: "compaction-failed", reason: failureReason });
19300
- const failureMessage = combinedDetail.slice(0, 200);
19301
- return {
19302
- ...projectExisting(),
19303
- failureMessage,
19304
- failureReason,
19305
- decision: {
19306
- outcome: "failed",
19307
- reason: failureReason,
19308
- detail: failureMessage
19309
- }
19310
- };
19311
- }
19312
- const reason = primaryFailure ?? "provider-error";
19313
- throw Object.assign(new Error(combinedDetail), {
19314
- compactionReason: reason,
19315
- primaryElapsedMs,
19316
- fallbackElapsedMs
19317
- });
19853
+ recordCompactionFailure(dialogId, args2.now?.() ?? Date.now());
19854
+ logCompactionTiming("empty", { model: result4?.model });
19855
+ emitPhase({ kind: "compaction-end" });
19856
+ return { ...projectExisting(), failureMessage: "Summary empty", failureReason: "provider-error", decision: { outcome: "failed", reason: "provider-error", detail: "Summary empty", ...itemDiagnostics } };
19318
19857
  }
19319
19858
  const metricsReason = invalidSummary ? "invalid_summary" : "context_budget";
19320
19859
  const metrics = buildCompactionMetricsFromPlan({
@@ -19325,36 +19864,51 @@ async function maybeAutoCompactLocalHistory(args2) {
19325
19864
  msgsToKeep: [...msgsToCompress.slice(bounded3.coveredMessages.length), ...plan.msgsToKeep]
19326
19865
  },
19327
19866
  newSummary,
19328
- summaryUsage: result4?.usage
19867
+ summaryUsage: result4?.usage,
19868
+ summaryInput: {
19869
+ inputTokens: bounded3.inputTokens,
19870
+ capTokens: bounded3.capTokens,
19871
+ expandedMessages: bounded3.expandedMessages,
19872
+ omittedMessages: bounded3.omittedMessages
19873
+ },
19874
+ planCounts: {
19875
+ pendingMessageCount: plan.diagnostics?.pendingMsgCount,
19876
+ plannedCompressCount: plan.diagnostics?.compressCount
19877
+ }
19329
19878
  });
19330
19879
  const projectedHistory = projectHistoryWithSummary({
19331
19880
  history,
19332
19881
  summary: newSummary,
19333
19882
  summarizedBeforeId: bounded3.summarizedBeforeId
19334
19883
  });
19335
- const projectedMessages = toPlanCompressionMessages(projectedHistory);
19884
+ const projectedMessages = toPlanCompressionMessages(prepareHistoryForNextTurn(projectedHistory, args2.contextReferenceResolver));
19336
19885
  const projectedTokens = projectedMessages.reduce((sum2, message) => sum2 + getMessageTokenCount(message), 0);
19337
19886
  const projectedRetained = projectedMessages.slice(1);
19338
19887
  metrics.retainedTokens = projectedRetained.reduce((sum2, message) => sum2 + getMessageTokenCount(message), 0);
19339
19888
  metrics.retainedCount = projectedRetained.length;
19340
- const { effectiveHistoryBudget } = resolveEffectiveCompactionBudgets({
19889
+ const { effectiveRawMessageBudget } = resolveEffectiveCompactionBudgets({
19341
19890
  contextWindow,
19342
19891
  triggerRatio: plan.diagnostics.triggerRatio,
19343
- historyBudget: plan.diagnostics.historyBudget,
19344
- rawMessageBudget: plan.diagnostics.historyBudget,
19345
- summaryTokens: 0
19892
+ historyBudget: contextWindow,
19893
+ rawMessageBudget: contextWindow,
19894
+ summaryTokens: 0,
19895
+ newSummaryTokenReserve: estimateTokenCount(newSummary)
19346
19896
  });
19347
- const withinSafetyBudget = projectedTokens <= effectiveHistoryBudget;
19897
+ const effectiveHistoryBudget = effectiveRawMessageBudget + estimateTokenCount(newSummary);
19898
+ const projectedItemCount = projectedHistory.length + 1;
19899
+ const withinSafetyBudget = projectedTokens <= effectiveHistoryBudget && (maxInputItems === void 0 || projectedItemCount <= Math.floor(maxInputItems * 0.6));
19348
19900
  if (!withinSafetyBudget) {
19349
19901
  console.warn("[localLoop] compaction projection remains above safety budget", {
19350
19902
  projectedTokens,
19351
- effectiveHistoryBudget
19903
+ effectiveHistoryBudget,
19904
+ ...maxInputItems !== void 0 ? { projectedItemCount, maxInputItems } : {}
19352
19905
  });
19353
19906
  }
19354
19907
  const oldProjection = projectExisting();
19355
- const beforeProjectedTokens = toPlanCompressionMessages(oldProjection.history).reduce((sum2, message) => sum2 + getMessageTokenCount(message), 0);
19356
- const historyBudget = plan.diagnostics.historyBudget;
19357
- if (projectedTokens >= beforeProjectedTokens || projectedTokens >= historyBudget) {
19908
+ const beforeProjectedTokens = toPlanCompressionMessages(prepareHistoryForNextTurn(oldProjection.history, args2.contextReferenceResolver)).reduce((sum2, message) => sum2 + getMessageTokenCount(message), 0);
19909
+ const historyBudget = Math.max(0, contextWindow - Math.min(8192, Math.max(1, Math.floor(contextWindow / 4))));
19910
+ const candidateOverBudget = args2.requestOverBudget ? args2.requestOverBudget(projectedHistory) : projectedTokens > historyBudget;
19911
+ if (projectedTokens >= beforeProjectedTokens || candidateOverBudget) {
19358
19912
  const failureMessage = `compaction candidate rejected: ${projectedTokens >= beforeProjectedTokens ? "no token reduction" : "above history budget"} (before=${beforeProjectedTokens}, after=${projectedTokens}, budget=${historyBudget})`;
19359
19913
  console.warn(`[localLoop] ${failureMessage}`);
19360
19914
  recordCompactionFailure(dialogId, args2.now?.() ?? Date.now());
@@ -19364,13 +19918,20 @@ async function maybeAutoCompactLocalHistory(args2) {
19364
19918
  ...oldProjection,
19365
19919
  failureMessage,
19366
19920
  ...result4?.usage ? { usage: result4.usage } : {},
19367
- decision: { outcome: "skipped", skipReason: "unsafe-projection", detail: failureMessage }
19921
+ decision: {
19922
+ outcome: "skipped",
19923
+ skipReason: "unsafe-projection",
19924
+ detail: failureMessage,
19925
+ ...itemDiagnostics,
19926
+ ...maxInputItems !== void 0 ? { projectedItemCount } : {}
19927
+ }
19368
19928
  };
19369
19929
  }
19370
19930
  const sourceMeta = await computeSummarySourceMetadata(
19371
19931
  toPlanCompressionMessages(history),
19372
19932
  bounded3.summarizedBeforeId
19373
19933
  );
19934
+ if (Date.now() >= summaryDeadlineMs) throw Object.assign(new Error("Summary deadline exceeded"), { compactionReason: "timeout" });
19374
19935
  if (args2.abortSignal?.aborted) {
19375
19936
  throw Object.assign(new Error("auto-compaction aborted"), { compactionReason: "aborted" });
19376
19937
  }
@@ -19400,6 +19961,13 @@ async function maybeAutoCompactLocalHistory(args2) {
19400
19961
  history: projectedHistory,
19401
19962
  compressed: true,
19402
19963
  summaryGenerated: true,
19964
+ selectedSummary: {
19965
+ outcome: "generated",
19966
+ hash: sha256Hex(newSummary),
19967
+ anchorId: bounded3.summarizedBeforeId,
19968
+ ...sourceMeta?.sourceHash ? { sourceHash: sourceMeta.sourceHash } : {},
19969
+ ...sourceMeta?.sourceCount !== void 0 ? { sourceCount: sourceMeta.sourceCount } : {}
19970
+ },
19403
19971
  reason: metricsReason,
19404
19972
  beforeTokens: beforeProjectedTokens,
19405
19973
  afterTokens: projectedTokens,
@@ -19408,7 +19976,9 @@ async function maybeAutoCompactLocalHistory(args2) {
19408
19976
  metrics,
19409
19977
  decision: {
19410
19978
  outcome: "compressed",
19411
- trigger: invalidSummary ? "invalid-summary" : plan.diagnostics?.triggeredByRealUsage ? "real-usage" : "estimate",
19979
+ trigger: invalidSummary ? "invalid-summary" : plan.diagnostics?.triggeredByItemLimit ? "item-limit" : plan.diagnostics?.triggeredByRealUsage ? "real-usage" : "estimate",
19980
+ ...itemDiagnostics,
19981
+ ...maxInputItems !== void 0 ? { projectedItemCount } : {},
19412
19982
  ...plan.diagnostics ? {
19413
19983
  estimatedTokens: plan.diagnostics.totalUsed,
19414
19984
  historyBudget: plan.diagnostics.historyBudget,
@@ -19418,18 +19988,39 @@ async function maybeAutoCompactLocalHistory(args2) {
19418
19988
  }
19419
19989
  };
19420
19990
  } catch (error) {
19421
- console.warn("[localLoop] auto-compaction failed:", error);
19422
- const failureMessage = error instanceof Error && error.message ? error.message.slice(0, 200) : String(error).slice(0, 200);
19423
- const failureReason = error instanceof Error && "compactionReason" in error ? error.compactionReason : "provider-error";
19991
+ const safe = classifySummaryError(error, { model: summaryModel ?? void 0, provider: summaryProvider ?? void 0, attemptId: compactionAttemptId });
19992
+ summaryModel = safe.model ?? summaryModel;
19993
+ summaryProvider = safe.provider ?? summaryProvider;
19994
+ const failureReason = args2.abortSignal?.aborted || safe.category === "cancelled" || error?.compactionReason === "aborted" ? "aborted" : safe.category === "timeout" || error?.compactionReason === "timeout" ? "timeout" : "provider-error";
19995
+ const failureMessage = [safe.message, safe.code, safe.errorType, safe.balanceSource].filter(Boolean).join(" ");
19996
+ if (failureReason !== "aborted") console.warn("[localLoop] auto-compaction failed:", safe);
19997
+ summaryDiagnostics = {
19998
+ ...summaryDiagnostics,
19999
+ ...safe.finishReason === void 0 ? {} : { finishReason: safe.finishReason },
20000
+ ...safe.responseStatus === void 0 ? {} : { responseStatus: safe.responseStatus },
20001
+ ...safe.incompleteReason === void 0 ? {} : { incompleteReason: safe.incompleteReason },
20002
+ ...safe.maxOutputTokens === void 0 ? {} : { maxOutputTokens: safe.maxOutputTokens },
20003
+ ...safe.completionTokens === void 0 ? {} : { completionTokens: safe.completionTokens },
20004
+ ...safe.outputChars === void 0 ? {} : { outputChars: safe.outputChars }
20005
+ };
19424
20006
  logCompactionTiming(failureReason, {});
19425
20007
  const failureNow = args2.now?.() ?? Date.now();
19426
20008
  if (failureReason !== "aborted") recordCompactionFailure(dialogId, failureNow);
19427
- emitPhase({ kind: "compaction-failed", reason: failureReason });
20009
+ if (failureReason === "aborted") emitPhase({ kind: "compaction-end" });
20010
+ else emitPhase({ kind: "compaction-failed", reason: failureReason });
19428
20011
  return {
19429
20012
  ...projectExisting(),
19430
20013
  failureMessage,
19431
20014
  failureReason,
19432
- decision: { outcome: "failed", reason: failureReason, detail: failureMessage }
20015
+ failure: safe,
20016
+ decision: {
20017
+ outcome: "failed",
20018
+ reason: failureReason,
20019
+ detail: failureMessage,
20020
+ ...itemDiagnostics,
20021
+ estimatedTokens: plan.diagnostics?.totalUsed,
20022
+ historyBudget: plan.diagnostics?.historyBudget
20023
+ }
19433
20024
  };
19434
20025
  }
19435
20026
  }
@@ -19440,13 +20031,14 @@ function emitCompactionPhaseEvent(boundary, phase, scope3) {
19440
20031
  emitLoopEvent(boundary, { kind: "turn-phase", phase: "compacting", atMs, compactionScope: scope3 });
19441
20032
  emitLoopEvent(boundary, { kind: "compaction-start", atMs, scope: scope3 });
19442
20033
  } else if (phase.kind === "waiting-provider") emitLoopEvent(boundary, { kind: "turn-phase", phase: "waiting-provider", atMs, compactionScope: scope3 });
20034
+ else if (phase.kind === "compaction-retry") emitLoopEvent(boundary, { kind: "compaction-retry", atMs, scope: scope3, attempt: phase.attempt, maxAttempts: phase.maxAttempts });
19443
20035
  else if (phase.kind === "compaction-end") emitLoopEvent(boundary, { kind: "compaction-end", atMs, scope: scope3 });
19444
20036
  else emitLoopEvent(boundary, { kind: "compaction-failed", atMs, scope: scope3, reason: phase.reason });
19445
20037
  }
19446
20038
  function emitCompactionObservationEvent(boundary, compacted) {
19447
20039
  const decision = compacted.decision;
19448
20040
  if (decision?.outcome === "skipped") {
19449
- const isProtectionGap = decision.skipReason === "bounded-request-blocked" || decision.skipReason === "unsafe-projection" || decision.skipReason === "adapter-missing-summary-methods" || decision.skipReason === "load-summary-failed";
20041
+ const isProtectionGap = decision.skipReason === "bounded-request-blocked" || decision.skipReason === "unsafe-projection" || decision.skipReason === "no-safe-prefix" || decision.skipReason === "adapter-missing-summary-methods" || decision.skipReason === "load-summary-failed";
19450
20042
  const approachingBudget = decision.skipReason === "below-trigger" && typeof decision.estimatedTokens === "number" && typeof decision.historyBudget === "number" && decision.historyBudget > 0 && decision.estimatedTokens >= decision.historyBudget * 0.5;
19451
20043
  if (!isProtectionGap && !approachingBudget) return;
19452
20044
  }
@@ -19454,7 +20046,7 @@ function emitCompactionObservationEvent(boundary, compacted) {
19454
20046
  kind: "compaction",
19455
20047
  atMs: Date.now(),
19456
20048
  ...compacted.reason ? { reason: compacted.reason } : {},
19457
- ...compacted.withinSafetyBudget === false ? { detail: "Estimated projected history remains above the compaction safety budget" } : {},
20049
+ ...compacted.withinSafetyBudget === false ? { detail: "Projected history remains above the token or input-item safety budget" } : {},
19458
20050
  summaryGenerated: compacted.summaryGenerated,
19459
20051
  compressed: compacted.compressed,
19460
20052
  ...compacted.failureMessage ? { failed: true, detail: compacted.failureMessage } : {},
@@ -19464,6 +20056,12 @@ function emitCompactionObservationEvent(boundary, compacted) {
19464
20056
  ...decision && "historyBudget" in decision && decision.historyBudget !== void 0 ? { historyBudget: decision.historyBudget } : {},
19465
20057
  ...decision && "realUsageRatio" in decision && decision.realUsageRatio !== void 0 ? { realUsageRatio: decision.realUsageRatio } : {},
19466
20058
  ...decision && "triggerRatio" in decision && decision.triggerRatio !== void 0 ? { triggerRatio: decision.triggerRatio } : {},
20059
+ ...decision?.maxInputItems !== void 0 ? {
20060
+ itemCount: decision.itemCount,
20061
+ maxInputItems: decision.maxInputItems,
20062
+ triggeredByItemLimit: decision.triggeredByItemLimit,
20063
+ ...decision.projectedItemCount !== void 0 ? { projectedItemCount: decision.projectedItemCount } : {}
20064
+ } : {},
19467
20065
  ...compacted.beforeTokens !== void 0 ? { beforeTokens: compacted.beforeTokens } : {},
19468
20066
  ...compacted.afterTokens !== void 0 ? { afterTokens: compacted.afterTokens } : {},
19469
20067
  ...compacted.savedTokens !== void 0 ? { savedTokens: compacted.savedTokens } : {}
@@ -19482,7 +20080,7 @@ function createTurnCompactionController(args2) {
19482
20080
  }
19483
20081
  };
19484
20082
  const compactionDialogId = args2.continueDialogId ?? ephemeralDialogId;
19485
- const runInitial = async (history) => {
20083
+ const runCompaction = async (history, force) => {
19486
20084
  let realContextUsagePercent;
19487
20085
  if (args2.continueDialogId && typeof args2.adapter.loadLastContextUsage === "function") {
19488
20086
  try {
@@ -19505,15 +20103,21 @@ function createTurnCompactionController(args2) {
19505
20103
  adapter: args2.adapter,
19506
20104
  dialogId: args2.continueDialogId,
19507
20105
  history,
20106
+ contextReferenceResolver: args2.contextReferenceResolver,
19508
20107
  model: args2.model,
20108
+ provider: args2.provider,
20109
+ providerEndpoint: args2.providerEndpoint,
19509
20110
  resolveProvider: args2.resolveProvider,
19510
20111
  contextWindow: args2.contextWindow,
19511
20112
  realContextUsagePercent,
20113
+ force,
19512
20114
  abortSignal: args2.abortSignal,
19513
20115
  timeoutMs: args2.timeoutMs,
19514
20116
  summaryFallback: args2.summaryFallback,
20117
+ onAttemptUsage: args2.onUsage,
19515
20118
  onPhase: (phase) => emitPhase(phase, "initial")
19516
20119
  });
20120
+ args2.onSelectedSummary?.(compacted.selectedSummary ?? { outcome: "absent" });
19517
20121
  emitObservation(compacted);
19518
20122
  return { history: compacted.history, usage: compacted.usage };
19519
20123
  } catch (error) {
@@ -19521,11 +20125,25 @@ function createTurnCompactionController(args2) {
19521
20125
  return { history };
19522
20126
  }
19523
20127
  };
19524
- const maybeCompactInLoop = async () => {
20128
+ const runInitial = async (history) => runCompaction(history, false);
20129
+ const forceCompact = async (history) => {
20130
+ const result4 = await runCompaction(history, true);
20131
+ return { history: result4.history, applied: result4.history !== history, usage: result4.usage };
20132
+ };
20133
+ let lastCompactionOutcome;
20134
+ const prepareRequest = async (requestOverBudget, initial = false, availableHistoryTokens) => {
20135
+ lastCompactionOutcome = void 0;
19525
20136
  const contextUsage = args2.getContextUsage();
19526
- const inputTokens = contextUsage ? normalizeUsage(contextUsage).input_tokens : 0;
20137
+ let inputTokens = contextUsage ? normalizeUsage(contextUsage).input_tokens : 0;
20138
+ if (initial && args2.continueDialogId && args2.adapter.loadLastContextUsage) {
20139
+ try {
20140
+ inputTokens = (await args2.adapter.loadLastContextUsage(args2.continueDialogId))?.inputTokens ?? 0;
20141
+ } catch (error) {
20142
+ console.warn("[localLoop] loadLastContextUsage failed:", error);
20143
+ }
20144
+ }
19527
20145
  const ratio = inputTokens > 0 ? Math.min(1, inputTokens / args2.contextWindow) : void 0;
19528
- if (ratio !== void 0 && ratio < args2.compressionTriggerRatio * 0.85) {
20146
+ if (!requestOverBudget && ratio !== void 0 && ratio < args2.compressionTriggerRatio * 0.85 && resolveProviderInputItemLimit({ provider: args2.provider, endpoint: args2.providerEndpoint }) === void 0) {
19529
20147
  return;
19530
20148
  }
19531
20149
  try {
@@ -19538,18 +20156,22 @@ function createTurnCompactionController(args2) {
19538
20156
  // 锚点与 sourceHash 都必须按持久化形态计算——否则下轮从 store
19539
20157
  // 重载后重算 hash 必不匹配,摘要被判无效、白付一次摘要调用。
19540
20158
  history: args2.getCanonicalCompactionHistory(),
20159
+ requestOverBudget,
20160
+ availableHistoryTokens: availableHistoryTokens?.(),
20161
+ contextReferenceResolver: args2.contextReferenceResolver,
19541
20162
  model: args2.model,
20163
+ provider: args2.provider,
20164
+ providerEndpoint: args2.providerEndpoint,
19542
20165
  resolveProvider: args2.resolveProvider,
19543
20166
  contextWindow: args2.contextWindow,
19544
20167
  ...ratio !== void 0 ? { realContextUsagePercent: ratio } : {},
19545
20168
  abortSignal: args2.abortSignal,
19546
20169
  timeoutMs: args2.timeoutMs,
19547
20170
  summaryFallback: args2.summaryFallback,
19548
- onPhase: (phase) => emitPhase(phase, "in-loop")
20171
+ onAttemptUsage: args2.onUsage,
20172
+ onPhase: (phase) => emitPhase(phase, initial ? "initial" : "in-loop")
19549
20173
  });
19550
- if (compacted.usage) {
19551
- args2.onUsage(compacted.usage);
19552
- }
20174
+ lastCompactionOutcome = { result: compacted };
19553
20175
  if (compacted.compressed) {
19554
20176
  const rebuilt = composeProviderMessages({
19555
20177
  prefix: args2.getWorkingPrefix(),
@@ -19564,16 +20186,65 @@ function createTurnCompactionController(args2) {
19564
20186
  }
19565
20187
  args2.replaceWorkingView(rebuilt.messages);
19566
20188
  }
20189
+ if (compacted.compressed || initial) {
20190
+ args2.onSelectedSummary?.(compacted.selectedSummary ?? { outcome: initial ? "absent" : "unknown" });
20191
+ }
19567
20192
  emitObservation(compacted);
19568
20193
  } catch (error) {
20194
+ lastCompactionOutcome = { ...lastCompactionOutcome, error: classifySummaryError(error) };
19569
20195
  console.warn("[localLoop] in-loop auto-compaction failed:", error);
19570
20196
  }
19571
20197
  };
19572
- return { runInitial, maybeCompactInLoop };
20198
+ return { getLastCompactionOutcome: () => lastCompactionOutcome, runInitial, maybeCompactInLoop: () => prepareRequest(), forceCompact, prepareRequest };
19573
20199
  }
19574
20200
 
19575
20201
  // packages/agent-runtime/providerCallTelemetry.ts
19576
20202
  import { createHash as createHash4 } from "node:crypto";
20203
+ var isCount = (v) => Number.isSafeInteger(v) && v >= 0;
20204
+ var isDigest = (v) => typeof v === "string" && /^[a-f0-9]{64}$/.test(v);
20205
+ var isMessageId = (v) => typeof v === "string" && /^(?:[0-9]{13}-[0-9]{3,6}|[0-9A-HJKMNP-TV-Z]{26}|local-[0-9]{1,12})$/.test(v);
20206
+ var isRecord2 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
20207
+ function projectContextProvenance(value) {
20208
+ if (!isRecord2(value) || value.schemaVersion !== 1) return void 0;
20209
+ const transports = ["unknown", "local-cli", "direct-openai-compatible", "codex-oauth", "codex-responses", "owner-oauth-proxy", "server-proxy"];
20210
+ const result4 = { schemaVersion: 1, transport: transports.includes(value.transport) ? value.transport : "unknown" };
20211
+ if (Array.isArray(value.phases)) {
20212
+ result4.phases = [];
20213
+ for (const item of value.phases.slice(0, 8)) {
20214
+ if (!isRecord2(item) || !["history-loaded", "summary-projected", "budget-trim", "final-request"].includes(item.phase)) continue;
20215
+ const phase = { phase: item.phase };
20216
+ if (["local", "remote", "partial", "none", "unknown"].includes(item.source)) phase.source = item.source;
20217
+ for (const key of ["count", "droppedCount"]) if (isCount(item[key])) phase[key] = item[key];
20218
+ for (const key of ["hash", "idsHash"]) if (isDigest(item[key])) phase[key] = item[key];
20219
+ for (const key of ["firstId", "lastId"]) if (isMessageId(item[key])) phase[key] = item[key];
20220
+ result4.phases.push(phase);
20221
+ }
20222
+ }
20223
+ if (isRecord2(value.summary)) {
20224
+ const input = value.summary;
20225
+ const outcomes = ["absent", "loaded", "generated", "invalid", "skipped", "failed", "unknown"];
20226
+ const summary = { outcome: outcomes.includes(input.outcome) ? input.outcome : "unknown" };
20227
+ for (const key of ["summaryId", "hash", "sourceHash"]) if (isDigest(input[key])) summary[key] = input[key];
20228
+ if (isMessageId(input.anchorId)) summary.anchorId = input.anchorId;
20229
+ if (isCount(input.sourceCount)) summary.sourceCount = input.sourceCount;
20230
+ result4.summary = summary;
20231
+ }
20232
+ return result4;
20233
+ }
20234
+ function contextPhase(phase, messages, options = {}) {
20235
+ const digest = (value) => createHash4("sha256").update(JSON.stringify(value) ?? "null").digest("hex");
20236
+ const ids = messages.map((m) => isRecord2(m) && isMessageId(m.cliMessageId) ? m.cliMessageId : null);
20237
+ return {
20238
+ phase,
20239
+ count: messages.length,
20240
+ hash: digest(messages),
20241
+ idsHash: digest(ids),
20242
+ ...ids[0] ? { firstId: ids[0] } : {},
20243
+ ...ids.at(-1) ? { lastId: ids.at(-1) } : {},
20244
+ ...options.source ? { source: options.source } : {},
20245
+ ...isCount(options.droppedCount) ? { droppedCount: options.droppedCount } : {}
20246
+ };
20247
+ }
19577
20248
  var shortHash = (value) => createHash4("sha1").update(value).digest("hex").slice(0, 12);
19578
20249
  var hashMessage = (message) => {
19579
20250
  let text2;
@@ -19613,6 +20284,7 @@ function createDivergeTracker(max4 = DIVERGE_TRACKER_MAX_DIALOGS) {
19613
20284
  var processDivergeTracker = createDivergeTracker();
19614
20285
  function buildRequestTelemetry(args2) {
19615
20286
  try {
20287
+ const provenance = projectContextProvenance(args2.provenance);
19616
20288
  const hashes = args2.messages.map(hashMessage);
19617
20289
  let toolResultChars = 0;
19618
20290
  let toolResultCount = 0;
@@ -19628,6 +20300,7 @@ function buildRequestTelemetry(args2) {
19628
20300
  const diverge = (args2.tracker ?? processDivergeTracker).observe(args2.dialogKey, hashes);
19629
20301
  return {
19630
20302
  ...args2.toolsHash ? { toolsHash: args2.toolsHash } : {},
20303
+ ...provenance ? { provenance } : {},
19631
20304
  msgCount: args2.messages.length,
19632
20305
  contentChars,
19633
20306
  toolResultChars,
@@ -19647,13 +20320,23 @@ function hashToolDefinitions(tools) {
19647
20320
  }
19648
20321
 
19649
20322
  // packages/agent-runtime/localLoop.ts
20323
+ var LocalContextCoverageError = class extends Error {
20324
+ constructor(droppedCount, compactionUsage, diagnostics) {
20325
+ super("Context protection: history cannot fit without losing messages not represented in the sending context. This pending provider request was not sent; canonical history and pending input are retained. Restore summary access/provider availability and retry this same input, or select a larger context window. An invalid summary must be rebuilt before retrying.");
20326
+ this.droppedCount = droppedCount;
20327
+ this.compactionUsage = compactionUsage;
20328
+ this.diagnostics = diagnostics;
20329
+ this.name = "LocalContextCoverageError";
20330
+ }
20331
+ code = "LOCAL_CONTEXT_COVERAGE_REQUIRED";
20332
+ };
19650
20333
  var LOCAL_AGENT_CONFIG_MISSING_CODE = "LOCAL_AGENT_CONFIG_MISSING";
19651
20334
  var LLM_REQUEST_TIMEOUT = "LLM_REQUEST_TIMEOUT";
19652
20335
  var DEFAULT_LLM_REQUEST_TIMEOUT_MS = 10 * 60 * 1e3;
19653
20336
  var DEFAULT_COMPACTION_TIMEOUT_MS = 60 * 1e3;
19654
20337
  var DEFAULT_ROUND_BUDGET_WARNING_THRESHOLD = 25;
19655
20338
  function buildRoundBudgetWarningPrompt(threshold) {
19656
- return `[\u7CFB\u7EDF\u63D0\u793A] \u5F53\u524D\u5355\u6B21\u4EFB\u52A1\u7684\u8FDE\u7EED\u5DE5\u5177\u8C03\u7528\u5DF2\u8FBE ${threshold} \u8F6E\uFF0C\u8BF7\u8FDB\u884C\u8FDB\u5C55\u4E0E\u6210\u672C\u68C0\u67E5\uFF1A\u82E5\u5DF2\u6388\u6743\u76EE\u6807\u672A\u5B8C\u6210\u4E14\u6301\u7EED\u6709\u8FDB\u5C55\uFF0C\u5E94\u7EE7\u7EED\u63A8\u8FDB\uFF0C\u4E0D\u5F97\u4EC5\u56E0\u8F6E\u6570\u4EA4\u63A5\uFF1B\u4EC5\u5728\u76EE\u6807\u5DF2\u5B8C\u6210\u3001\u9047\u5230\u771F\u5B9E\u963B\u585E\u6216\u89E6\u53CA\u786C\u9884\u7B97\u65F6\u624D\u6536\u5C3E\uFF1B\u82E5\u8FDB\u5C55\u505C\u6EDE\uFF0C\u8BF7\u53CA\u65F6\u66F4\u6362\u7B56\u7565\u3002`;
20339
+ return `[\u7CFB\u7EDF\u63D0\u793A] \u5F53\u524D\u5355\u6B21\u4EFB\u52A1\u7684\u8FDE\u7EED\u5DE5\u5177\u8C03\u7528\u5DF2\u8FBE ${threshold} \u8F6E\uFF0C\u8BF7\u8FDB\u884C\u8FDB\u5C55\u4E0E\u6210\u672C\u68C0\u67E5\uFF1A\u82E5\u5DF2\u6388\u6743\u76EE\u6807\u672A\u5B8C\u6210\u4E14\u6301\u7EED\u6709\u8FDB\u5C55\uFF0C\u5E94\u7EE7\u7EED\u63A8\u8FDB\uFF0C\u4E0D\u5F97\u4EC5\u56E0\u8F6E\u6570\u4EA4\u63A5\uFF1B\u4EC5\u5728\u76EE\u6807\u5DF2\u5B8C\u6210\u3001\u9047\u5230\u771F\u5B9E\u963B\u585E\u6216\u7528\u6237\u660E\u786E\u505C\u6B62\u65F6\u624D\u6536\u5C3E\uFF1B\u82E5\u8FDB\u5C55\u505C\u6EDE\uFF0C\u8BF7\u53CA\u65F6\u66F4\u6362\u7B56\u7565\u3002`;
19657
20340
  }
19658
20341
  var ROUND_BUDGET_WARNING_PROMPT = buildRoundBudgetWarningPrompt(
19659
20342
  DEFAULT_ROUND_BUDGET_WARNING_THRESHOLD
@@ -19683,23 +20366,24 @@ var CONCURRENT_READONLY_TOOLS = /* @__PURE__ */ new Set([
19683
20366
  ]);
19684
20367
  function partitionToolCallsIntoBatches(toolCalls) {
19685
20368
  const batches = [];
19686
- let currentReadBatch = [];
19687
- for (let i = 0; i < toolCalls.length; i += 1) {
19688
- const toolCall = toolCalls[i];
19689
- const isReadOnly = CONCURRENT_READONLY_TOOLS.has(toolCall.function.name);
19690
- if (isReadOnly) {
19691
- currentReadBatch.push({ index: i, toolCall });
19692
- } else {
19693
- if (currentReadBatch.length > 0) {
19694
- batches.push({ isConcurrent: true, calls: currentReadBatch });
19695
- currentReadBatch = [];
19696
- }
19697
- batches.push({ isConcurrent: false, calls: [{ index: i, toolCall }] });
20369
+ let currentBatch = [];
20370
+ const flush2 = () => {
20371
+ if (currentBatch.length) batches.push({ isConcurrent: true, calls: currentBatch });
20372
+ currentBatch = [];
20373
+ };
20374
+ for (let index = 0; index < toolCalls.length; index += 1) {
20375
+ const toolCall = toolCalls[index];
20376
+ const contract = resolveToolExecutionContract(toolCall.function.name);
20377
+ const isReadOnly = contract.interaction === "none" && contract.concurrency === "safe";
20378
+ const legacyRead = contract.concurrency === "exclusive" && contract.interaction === "unknown" && CONCURRENT_READONLY_TOOLS.has(toolCall.function.name);
20379
+ if (toolCall.function.name === "startAgentRun" || !isReadOnly && !legacyRead) {
20380
+ flush2();
20381
+ batches.push({ isConcurrent: false, calls: [{ index, toolCall }] });
20382
+ continue;
19698
20383
  }
20384
+ currentBatch.push({ index, toolCall });
19699
20385
  }
19700
- if (currentReadBatch.length > 0) {
19701
- batches.push({ isConcurrent: true, calls: currentReadBatch });
19702
- }
20386
+ flush2();
19703
20387
  return batches;
19704
20388
  }
19705
20389
  function resolveLlmRequestTimeoutMs(input) {
@@ -19708,7 +20392,7 @@ function resolveLlmRequestTimeoutMs(input) {
19708
20392
  }
19709
20393
  function resolveCompactionTimeoutMs(input) {
19710
20394
  const raw = input.compactionTimeoutMs;
19711
- return typeof raw === "number" && Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_COMPACTION_TIMEOUT_MS;
20395
+ return typeof raw === "number" && Number.isFinite(raw) && raw > 0 ? Math.min(raw, DEFAULT_COMPACTION_TIMEOUT_MS) : DEFAULT_COMPACTION_TIMEOUT_MS;
19712
20396
  }
19713
20397
  function readHostCompactionSummaryFallback(adapter) {
19714
20398
  const value = adapter.compactionSummaryFallback;
@@ -19833,6 +20517,10 @@ function attachUsageRecordsToError(error, usageRecords) {
19833
20517
  }
19834
20518
  async function persistFailedLocalTurn(args2) {
19835
20519
  const errorMessage = toErrorMessage(args2.error);
20520
+ const businessCompletion = evaluateTaskExecutionContract(args2.input.taskExecutionContract, {
20521
+ stopped: args2.input.abortSignal?.aborted || args2.error instanceof Error && args2.error.code === LOCAL_TURN_ABORTED_CODE,
20522
+ executionFailed: true
20523
+ });
19836
20524
  try {
19837
20525
  const saved = await args2.adapter.saveTurn({
19838
20526
  agentKey: args2.agentKey,
@@ -19843,6 +20531,7 @@ async function persistFailedLocalTurn(args2) {
19843
20531
  toolCallCount: args2.toolCallCount ?? 0,
19844
20532
  error: true,
19845
20533
  errorMessage,
20534
+ ...businessCompletion ? { businessCompletion } : {},
19846
20535
  ...args2.usage ? { usage: args2.usage } : {}
19847
20536
  },
19848
20537
  ...args2.accountingUsage ? { accountingUsage: args2.accountingUsage } : {},
@@ -19876,6 +20565,18 @@ async function loadAdapterDialogHistory(adapter, dialogId, options) {
19876
20565
  }
19877
20566
  return { messages: await adapter.loadDialogHistory(dialogId) };
19878
20567
  }
20568
+ function combineRollbackError(primary, rollbackError) {
20569
+ const primaryError = primary instanceof Error ? primary : new Error(String(primary));
20570
+ const rollbackErr = rollbackError instanceof Error ? rollbackError : new Error(String(rollbackError));
20571
+ const combined = new AggregateError(
20572
+ [primaryError, rollbackErr],
20573
+ `${primaryError.message} (additionally, injection rollback failed: ${rollbackErr.message})`
20574
+ );
20575
+ if (primary && typeof primary === "object") {
20576
+ Object.assign(combined, primary);
20577
+ }
20578
+ return combined;
20579
+ }
19879
20580
  async function runLocalAgentTurn(input) {
19880
20581
  const legacyCallbacks = {
19881
20582
  onLoopEvent: input.onLoopEvent,
@@ -19889,17 +20590,6 @@ async function runLocalAgentTurn(input) {
19889
20590
  observationBoundary.attachCallbacks(legacyCallbacks);
19890
20591
  }
19891
20592
  const loopTiming = createLoopTiming();
19892
- const subtaskBudgetClock = input.subtaskBudgetNow ?? (() => Date.now());
19893
- const subtaskBudgetLimits = {
19894
- maxToolCalls: input.subtaskMaxToolCalls ?? input.subtaskBudgetLimits?.maxToolCalls ?? DEFAULT_SUBTASK_MAX_TOOL_CALLS,
19895
- maxWallClockMs: input.subtaskMaxWallClockMs ?? input.subtaskBudgetLimits?.maxWallClockMs ?? DEFAULT_SUBTASK_MAX_WALL_CLOCK_MS
19896
- };
19897
- const subtaskBudgetTracker = input.runKind === "subtask" ? createSubtaskBudgetTracker({
19898
- limits: subtaskBudgetLimits,
19899
- now: subtaskBudgetClock,
19900
- // 墙钟从 run 开始计(含 provider 初始化与首轮前的准备)。
19901
- startedAtMs: subtaskBudgetClock()
19902
- }) : null;
19903
20593
  try {
19904
20594
  loopTiming.mark("turnStart", 0);
19905
20595
  let agentConfig = await input.adapter.loadAgentConfig(input.agentRef);
@@ -19987,7 +20677,8 @@ async function runLocalAgentTurn(input) {
19987
20677
  if (input.strictContinueHistory && (loaded.source === "none" || loaded.source === "partial")) {
19988
20678
  throw new Error(`Strict continuation history unavailable: ${loaded.source}`);
19989
20679
  }
19990
- history = loaded.messages;
20680
+ const currentIds = new Set(input.userMessages?.map((message) => message.cliMessageId));
20681
+ history = loaded.messages.filter((message) => !currentIds.has(message.cliMessageId));
19991
20682
  dialogHistorySource = loaded.source;
19992
20683
  }
19993
20684
  } catch (error) {
@@ -20073,6 +20764,9 @@ async function runLocalAgentTurn(input) {
20073
20764
  // 平台风控纪律:无条件系统层块,沿用 session 占位约定。
20074
20765
  { content: PLATFORM_RISK_CONTROL_RULE, cacheScope: "session" },
20075
20766
  { content: buildConsultationModeBlock(), cacheScope: "session" },
20767
+ // Git 任务隔离:系统层固定前置,四条装配线同序(请教模式之后、身份信息之前;
20768
+ // skill 配置 disabled 也注入)。
20769
+ { content: WORKTREE_ISOLATION_PRELUDE, cacheScope: "session" },
20076
20770
  { content: identityBlock, cacheScope: "session" },
20077
20771
  ...guidanceScopes,
20078
20772
  // 回复行为准则:local 线(CLI 非交互 / 桌面 / TUI)的唯一注入边界。
@@ -20092,19 +20786,29 @@ async function runLocalAgentTurn(input) {
20092
20786
  let resolvedSummaryProvider;
20093
20787
  const resolveSummaryProviderOnce = async () => {
20094
20788
  if (!resolvedSummaryProvider) {
20095
- resolvedSummaryProvider = await input.adapter.resolveProvider(agentConfig, {
20789
+ const provider = await input.adapter.resolveProvider(agentConfig, {
20790
+ textOnly: true,
20096
20791
  toolNames: [],
20097
20792
  prompt: ""
20098
20793
  });
20794
+ if (provider.textOnly !== true) {
20795
+ throw new Error("auto-compaction requires an isolated text-only provider");
20796
+ }
20797
+ resolvedSummaryProvider = provider;
20099
20798
  }
20100
20799
  return resolvedSummaryProvider;
20101
20800
  };
20102
20801
  const contextWindow = resolveAgentContextWindow(agentConfig);
20103
20802
  const compressionTriggerRatio = resolveCompressionTriggerRatio(contextWindow);
20803
+ let selectedSummary = { outcome: "unknown" };
20804
+ let summaryProjectedPhase = contextPhase("summary-projected", history);
20805
+ const historyLoadedPhase = contextPhase("history-loaded", history, { source: dialogHistorySource ?? "unknown" });
20104
20806
  const compaction = createTurnCompactionController({
20105
20807
  adapter: input.adapter,
20106
20808
  continueDialogId: input.continueDialogId,
20107
20809
  model: agentConfig.model,
20810
+ provider: agentConfig.provider,
20811
+ providerEndpoint: agentConfig.customProviderUrl,
20108
20812
  resolveProvider: resolveSummaryProviderOnce,
20109
20813
  contextWindow,
20110
20814
  compressionTriggerRatio,
@@ -20125,19 +20829,18 @@ async function runLocalAgentTurn(input) {
20125
20829
  )
20126
20830
  ],
20127
20831
  getWorkingPrefix: () => transcript.working().slice(0, prefixCount),
20128
- replaceWorkingView: (next) => transcript.replaceWorkingHistory(next),
20832
+ onSelectedSummary: (summary) => {
20833
+ selectedSummary = summary;
20834
+ },
20835
+ replaceWorkingView: (next) => {
20836
+ transcript.replaceWorkingHistory(next);
20837
+ summaryProjectedPhase = contextPhase("summary-projected", next);
20838
+ },
20129
20839
  contextReferenceResolver: input.adapter.host === "cli" ? input.contextReferenceResolver : void 0,
20130
20840
  onUsage: (usage) => usageLedger.addCompaction(usage)
20131
20841
  });
20132
- const initialCompaction = await compaction.runInitial(history);
20133
- history = initialCompaction.history;
20134
- const initialCompactionUsage = initialCompaction.usage;
20135
- loopTiming.mark("maybeAutoCompactLocalHistory", 0);
20136
- const trimmedHistory = trimHistoryToContextBudget(history, agentConfig.model, contextWindow);
20137
- if (trimmedHistory.droppedCount > 0) {
20138
- history = trimmedHistory.history;
20139
- }
20140
- loopTiming.mark("trimHistoryToContextBudget", 0);
20842
+ throwIfAborted(input);
20843
+ let budgetTrimPhase = contextPhase("budget-trim", history, { droppedCount: 0 });
20141
20844
  const builtMessages = buildMessages({
20142
20845
  prompt: agentConfig.prompt,
20143
20846
  contextBlockScopes: mergedContextBlockScopes,
@@ -20169,6 +20872,14 @@ async function runLocalAgentTurn(input) {
20169
20872
  ...msg,
20170
20873
  content: truncateToolOutputForContext(msg.content, contextWindow)
20171
20874
  } : msg;
20875
+ if (input.userMessages?.length) {
20876
+ const currentInputStart = builtMessages.prefixCount + history.length;
20877
+ const sanitizedUsers = await Promise.all(input.userMessages.map(async (message) => ({
20878
+ ...message,
20879
+ content: await sanitizeInbound(message.content)
20880
+ })));
20881
+ messages.splice(currentInputStart, messages.length - currentInputStart, ...sanitizedUsers);
20882
+ }
20172
20883
  const acceptToolImages = createToolImageLedger();
20173
20884
  const transcript = createTurnTranscript({
20174
20885
  initialWorking: messages,
@@ -20178,7 +20889,9 @@ async function runLocalAgentTurn(input) {
20178
20889
  });
20179
20890
  const userInputText = extractUserInputText(input.input);
20180
20891
  let toolCallCount = 0;
20892
+ let requestCalibrationRatio = 1;
20181
20893
  let result4 = { content: "" };
20894
+ let stopReason;
20182
20895
  const usageLedger = createTurnUsageLedger({
20183
20896
  model: agentConfig.model,
20184
20897
  provider: agentConfig.provider,
@@ -20187,77 +20900,126 @@ async function runLocalAgentTurn(input) {
20187
20900
  stablePrefixEstimatedTokens: builtMessages.stablePrefixEstimatedTokens
20188
20901
  } : {}
20189
20902
  });
20190
- if (initialCompactionUsage) usageLedger.addCompaction(initialCompactionUsage);
20191
20903
  let loopError;
20904
+ let consumedInjectedMessages = false;
20192
20905
  let round = 0;
20193
- let budgetExhaustion = null;
20194
- const executedToolNameLog = [];
20195
- const markBudgetExhausted = () => {
20196
- if (!subtaskBudgetTracker || budgetExhaustion) return;
20197
- const exhaustion = subtaskBudgetTracker.exhaustion();
20198
- if (!exhaustion) return;
20199
- budgetExhaustion = exhaustion;
20200
- emitLoopEvent(observationBoundary, {
20201
- kind: "budget-exhausted",
20202
- limit: exhaustion.limit,
20203
- atMs: exhaustion.atMs,
20204
- maxToolCalls: exhaustion.limits.maxToolCalls,
20205
- maxWallClockMs: exhaustion.limits.maxWallClockMs,
20206
- toolCallsExecuted: exhaustion.toolCallsExecuted,
20207
- elapsedMs: exhaustion.elapsedMs,
20208
- round
20209
- });
20210
- };
20211
20906
  let emptyAssistantRepairPending = false;
20212
20907
  let emptyAssistantRepairUsed = false;
20213
20908
  let reasoningEmptyRepairCount = 0;
20214
20909
  let skipFinalAppend = false;
20215
20910
  let partialContent = "";
20216
20911
  const progressGuard = createLocalLoopProgressGuard(input.progressGuardConfig);
20217
- const budgetAdmissionGate = () => {
20218
- if (!subtaskBudgetTracker) return true;
20219
- if (!subtaskBudgetTracker.isExhausted()) return true;
20220
- markBudgetExhausted();
20221
- return false;
20912
+ let pendingInjectionCommits = [];
20913
+ let inFlightTransactions = [];
20914
+ let rollbackInjectionCommits = async () => {
20222
20915
  };
20223
20916
  try {
20224
20917
  const provider = await resolveProviderOnce();
20225
- const applyPendingInjections = () => {
20918
+ const applyPendingInjections = async () => {
20226
20919
  if (!input.drainInjections) return false;
20227
- let pending = [];
20228
- try {
20229
- pending = input.drainInjections() ?? [];
20230
- } catch (error) {
20231
- console.warn("[localLoop] drainInjections failed:", error);
20232
- return false;
20233
- }
20920
+ const pending = await input.drainInjections() ?? [];
20234
20921
  let injected = false;
20235
- for (const text2 of pending) {
20236
- if (typeof text2 !== "string" || !text2.trim()) continue;
20237
- const safeText = scrubSecrets(text2).cleaned;
20238
- transcript.push({ role: "user", content: safeText });
20922
+ for (const entry of pending) {
20923
+ if (typeof entry === "string") {
20924
+ if (!entry.trim()) continue;
20925
+ transcript.push({ role: "user", content: scrubSecrets(entry).cleaned });
20926
+ } else if (entry && typeof entry === "object" && "messages" in entry) {
20927
+ for (const message of entry.messages) {
20928
+ const content = typeof message.content === "string" ? scrubSecrets(message.content).cleaned : Array.isArray(message.content) ? message.content.map((part) => part?.type === "text" && typeof part.text === "string" ? { ...part, text: scrubSecrets(part.text).cleaned } : part) : message.content;
20929
+ transcript.push({
20930
+ role: "user",
20931
+ content,
20932
+ ...message.cliMessageId ? { cliMessageId: message.cliMessageId } : {}
20933
+ });
20934
+ }
20935
+ pendingInjectionCommits.push({ commit: entry.commit, rollback: entry.rollback, messages: entry.messages });
20936
+ } else if (entry && typeof entry === "object" && "content" in entry) {
20937
+ const content = typeof entry.content === "string" ? scrubSecrets(entry.content).cleaned : Array.isArray(entry.content) ? entry.content.map((part) => part?.type === "text" && typeof part.text === "string" ? { ...part, text: scrubSecrets(part.text).cleaned } : part) : entry.content;
20938
+ transcript.push({
20939
+ role: "user",
20940
+ content,
20941
+ ...entry.cliMessageId ? { cliMessageId: entry.cliMessageId } : {}
20942
+ });
20943
+ } else {
20944
+ continue;
20945
+ }
20946
+ consumedInjectedMessages = true;
20239
20947
  injected = true;
20240
20948
  }
20241
20949
  return injected;
20242
20950
  };
20951
+ rollbackInjectionCommits = async (transactions) => {
20952
+ const errors = [];
20953
+ for (const transaction of transactions.slice().reverse()) {
20954
+ try {
20955
+ await transaction.rollback();
20956
+ } catch (rollbackError) {
20957
+ errors.push(rollbackError);
20958
+ }
20959
+ const ids = new Set(
20960
+ transaction.messages.map((message) => message.cliMessageId).filter((id) => Boolean(id))
20961
+ );
20962
+ if (ids.size > 0) {
20963
+ transcript.retract((msg) => {
20964
+ const id = msg.cliMessageId;
20965
+ return typeof id === "string" && ids.has(id);
20966
+ });
20967
+ }
20968
+ }
20969
+ if (errors.length > 0) {
20970
+ throw new Error(
20971
+ `injection rollback failed: ${errors.map((error) => error instanceof Error ? error.message : String(error)).join("; ")}`
20972
+ );
20973
+ }
20974
+ };
20243
20975
  const newDialogTelemetryKey = `new:${crypto.randomUUID()}`;
20244
20976
  let roundBudgetWarningInjected = false;
20245
- let subtaskToolBudgetWarningInjected = false;
20246
20977
  const roundWarningThreshold = input.roundBudgetWarningThreshold ?? DEFAULT_ROUND_BUDGET_WARNING_THRESHOLD;
20247
20978
  while (true) {
20248
20979
  partialContent = "";
20249
20980
  throwIfAborted(input);
20250
20981
  loopTiming.mark("roundStart", round);
20251
- applyPendingInjections();
20982
+ await applyPendingInjections();
20252
20983
  if (roundWarningThreshold > 0 && round >= roundWarningThreshold && !roundBudgetWarningInjected) {
20253
20984
  const prompt = buildRoundBudgetWarningPrompt(roundWarningThreshold);
20254
20985
  transcript.push({ role: "user", content: prompt });
20255
20986
  roundBudgetWarningInjected = true;
20256
20987
  }
20257
- if (subtaskBudgetTracker && !subtaskToolBudgetWarningInjected && subtaskBudgetTracker.toolCallsExecuted > 0 && subtaskBudgetTracker.remainingToolCalls() > 0 && subtaskBudgetTracker.remainingToolCalls() <= Math.max(1, Math.ceil(subtaskBudgetTracker.limits.maxToolCalls * 0.1))) {
20258
- transcript.push({ role: "user", content: `\u5B50\u4EFB\u52A1\u5DE5\u5177\u9884\u7B97\u5373\u5C06\u7528\u5C3D\uFF08\u5269\u4F59 ${subtaskBudgetTracker.remainingToolCalls()} \u6B21\uFF09\u3002\u8BF7\u4F18\u5148\u6536\u5C3E\u5E76\u7ED9\u51FA\u53EF\u8BFB\u7684\u9636\u6BB5\u7ED3\u679C\u3002` });
20259
- subtaskToolBudgetWarningInjected = true;
20260
- }
20988
+ const projectRequest = (source) => {
20989
+ const prepared = prepareMessagesForProviderCall(source);
20990
+ const base = filterImagePartsFromMessages(prepared.messages, supportsImages);
20991
+ return emptyAssistantRepairPending ? [...base, { role: "user", content: EMPTY_ASSISTANT_REPAIR_PROMPT }] : base;
20992
+ };
20993
+ const admissionProvider = await resolveProviderOnce();
20994
+ throwIfAborted(input);
20995
+ const requestOverBudget = (messages2) => !evaluateLocalRequestBudget({
20996
+ messages: messages2,
20997
+ contextWindow,
20998
+ descriptor: admissionProvider.requestBudget,
20999
+ calibrationRatio: requestCalibrationRatio
21000
+ }).fitsEstimate;
21001
+ const availableHistoryTokens = () => {
21002
+ const decision = evaluateLocalRequestBudget({
21003
+ messages: [],
21004
+ contextWindow,
21005
+ descriptor: admissionProvider.requestBudget,
21006
+ calibrationRatio: requestCalibrationRatio
21007
+ });
21008
+ const prefixTokens = estimateLocalRequestInput(projectRequest(transcript.working().slice(0, prefixCount)));
21009
+ return Math.max(0, Math.floor(decision.availableInputTokens / requestCalibrationRatio) - prefixTokens - (admissionProvider.requestBudget?.toolSchemaTokens ?? 0));
21010
+ };
21011
+ const historyTokensBeforeCompaction = Math.ceil(estimateLocalRequestInput(
21012
+ projectRequest(transcript.working()).slice(prefixCount)
21013
+ ) * requestCalibrationRatio);
21014
+ await compaction.prepareRequest((source) => {
21015
+ const projected = composeProviderMessages({
21016
+ prefix: transcript.working().slice(0, prefixCount),
21017
+ history: source,
21018
+ contextReferenceResolver: input.adapter.host === "cli" ? input.contextReferenceResolver : void 0
21019
+ });
21020
+ return requestOverBudget(projectRequest(projected.messages));
21021
+ }, round === 0, availableHistoryTokens);
21022
+ throwIfAborted(input);
20261
21023
  const preparedMessages = prepareMessagesForProviderCall(transcript.working());
20262
21024
  if (!supportsImages && hasImageInRuntimeMessages(preparedMessages.messages) && !imageDowngradeEmitted) {
20263
21025
  emitLoopEvent(observationBoundary, {
@@ -20267,11 +21029,27 @@ async function runLocalAgentTurn(input) {
20267
21029
  });
20268
21030
  imageDowngradeEmitted = true;
20269
21031
  }
20270
- const baseRequestMessages = filterImagePartsFromMessages(
20271
- preparedMessages.messages,
20272
- supportsImages
20273
- );
20274
- const requestMessages = emptyAssistantRepairPending ? [...baseRequestMessages, { role: "user", content: EMPTY_ASSISTANT_REPAIR_PROMPT }] : baseRequestMessages;
21032
+ const requestMessages = projectRequest(transcript.working());
21033
+ if (requestOverBudget(requestMessages)) {
21034
+ const budget = evaluateLocalRequestBudget({
21035
+ messages: requestMessages,
21036
+ contextWindow,
21037
+ descriptor: admissionProvider.requestBudget,
21038
+ calibrationRatio: requestCalibrationRatio
21039
+ });
21040
+ const largestMessageTokens = requestMessages.reduce((largest, message) => Math.max(
21041
+ largest,
21042
+ Math.ceil(estimateLocalRequestInput([message]) * requestCalibrationRatio)
21043
+ ), 0);
21044
+ throw new LocalContextCoverageError(0, void 0, classifyContextRefusal({
21045
+ outcome: compaction.getLastCompactionOutcome(),
21046
+ historyTokens: historyTokensBeforeCompaction,
21047
+ budget: budget.availableInputTokens,
21048
+ droppedCount: 0,
21049
+ largestMessageTokens
21050
+ }));
21051
+ }
21052
+ budgetTrimPhase = contextPhase("budget-trim", requestMessages, { droppedCount: 0 });
20275
21053
  const contextMetrics = {
20276
21054
  ...preparedMessages.metrics,
20277
21055
  messageCount: requestMessages.length,
@@ -20299,63 +21077,109 @@ async function runLocalAgentTurn(input) {
20299
21077
  // 无 dialogId 的新对话用本 turn 唯一键,避免不同新对话共用一个槽误报漂移。
20300
21078
  dialogKey: input.continueDialogId || newDialogTelemetryKey,
20301
21079
  messages: requestMessages,
21080
+ provenance: {
21081
+ schemaVersion: 1,
21082
+ transport: provider.transport ?? "unknown",
21083
+ phases: [historyLoadedPhase, summaryProjectedPhase, budgetTrimPhase, contextPhase("final-request", requestMessages)],
21084
+ summary: selectedSummary
21085
+ },
20302
21086
  toolsHash: safeToolNamesHash(agentTools)
20303
21087
  });
20304
- if (!budgetAdmissionGate()) {
20305
- break;
20306
- }
20307
21088
  resetToolImageDelivery(requestMessages.flatMap(toolImages));
20308
- result4 = await runCompleteWithTimeout({
20309
- provider,
20310
- messages: requestMessages,
20311
- options: {
21089
+ if (pendingInjectionCommits.length > 0) {
21090
+ inFlightTransactions = pendingInjectionCommits.splice(0);
21091
+ try {
21092
+ for (const transaction of inFlightTransactions) await transaction.commit();
21093
+ if (input.abortSignal?.aborted) throw buildAbortedError();
21094
+ } catch (error) {
21095
+ const toRollback = inFlightTransactions;
21096
+ inFlightTransactions = [];
21097
+ try {
21098
+ await rollbackInjectionCommits(toRollback);
21099
+ } catch (rbErr) {
21100
+ throw combineRollbackError(error, rbErr);
21101
+ }
21102
+ throw error;
21103
+ }
21104
+ }
21105
+ let providerInvoked = false;
21106
+ try {
21107
+ result4 = await runCompleteWithTimeout({
21108
+ provider: { complete: (messages2, options) => {
21109
+ providerInvoked = true;
21110
+ inFlightTransactions = [];
21111
+ return provider.complete(messages2, options);
21112
+ } },
21113
+ messages: requestMessages,
21114
+ options: {
21115
+ timeoutMs: resolveLlmRequestTimeoutMs(input),
21116
+ // 用户取消信号穿进 provider options:providerStreamRetry 据此在
21117
+ // 取消后跳过重试,provider 分支(如 antigravity/openai-compatible)
21118
+ // 也可把 fetch 绑定到同一信号,实现真正的传输层取消。
21119
+ ...input.abortSignal ? { signal: input.abortSignal } : {},
21120
+ // 计费归因:续聊轮次带上 dialogId,platform proxy 才能把 token 记录
21121
+ // 归到具体对话而不是 chat-proxy 兜底桶。新对话首轮 id 尚未分配。
21122
+ ...input.continueDialogId ? { dialogId: input.continueDialogId } : {},
21123
+ ...shouldStreamDeltas ? { onTextDelta: (chunk) => {
21124
+ llmStreamActivity += 1;
21125
+ partialContent += chunk;
21126
+ observationBoundary.emit({
21127
+ kind: "text-delta",
21128
+ chunk,
21129
+ round,
21130
+ atMs: Date.now()
21131
+ });
21132
+ } } : {},
21133
+ ...shouldStreamReasoning ? { onReasoningDelta: (chunk) => {
21134
+ llmStreamActivity += 1;
21135
+ observationBoundary.emit({
21136
+ kind: "reasoning-delta",
21137
+ chunk,
21138
+ round,
21139
+ atMs: Date.now()
21140
+ });
21141
+ } } : {},
21142
+ ...shouldPassToolEvents ? { onToolEvent: (event) => {
21143
+ llmStreamActivity += 1;
21144
+ observationBoundary.emit({
21145
+ kind: "tool-event",
21146
+ event,
21147
+ round,
21148
+ atMs: Date.now()
21149
+ });
21150
+ } } : {},
21151
+ ...shouldPassToolEvents ? { toolEventRound: round } : {}
21152
+ },
20312
21153
  timeoutMs: resolveLlmRequestTimeoutMs(input),
20313
- // 用户取消信号穿进 provider options:providerStreamRetry 据此在
20314
- // 取消后跳过重试,provider 分支(如 antigravity/openai-compatible)
20315
- // 也可把 fetch 绑定到同一信号,实现真正的传输层取消。
20316
- ...input.abortSignal ? { signal: input.abortSignal } : {},
20317
- // 计费归因:续聊轮次带上 dialogId,platform proxy 才能把 token 记录
20318
- // 归到具体对话而不是 chat-proxy 兜底桶。新对话首轮 id 尚未分配。
20319
- ...input.continueDialogId ? { dialogId: input.continueDialogId } : {},
20320
- ...shouldStreamDeltas ? { onTextDelta: (chunk) => {
20321
- llmStreamActivity += 1;
20322
- partialContent += chunk;
20323
- observationBoundary.emit({
20324
- kind: "text-delta",
20325
- chunk,
20326
- round,
20327
- atMs: Date.now()
20328
- });
20329
- } } : {},
20330
- ...shouldStreamReasoning ? { onReasoningDelta: (chunk) => {
20331
- llmStreamActivity += 1;
20332
- observationBoundary.emit({
20333
- kind: "reasoning-delta",
20334
- chunk,
20335
- round,
20336
- atMs: Date.now()
20337
- });
20338
- } } : {},
20339
- ...shouldPassToolEvents ? { onToolEvent: (event) => {
20340
- llmStreamActivity += 1;
20341
- observationBoundary.emit({
20342
- kind: "tool-event",
20343
- event,
20344
- round,
20345
- atMs: Date.now()
20346
- });
20347
- } } : {},
20348
- ...shouldPassToolEvents ? { toolEventRound: round } : {}
20349
- },
20350
- timeoutMs: resolveLlmRequestTimeoutMs(input),
20351
- activityVersion: () => llmStreamActivity,
20352
- round,
20353
- input,
20354
- boundary: observationBoundary,
20355
- context: contextMetrics,
20356
- providerName: agentConfig.provider,
20357
- model: provider.model
20358
- });
21154
+ activityVersion: () => llmStreamActivity,
21155
+ round,
21156
+ input,
21157
+ boundary: observationBoundary,
21158
+ context: contextMetrics,
21159
+ providerName: agentConfig.provider,
21160
+ model: provider.model
21161
+ });
21162
+ } catch (error) {
21163
+ if (!providerInvoked && inFlightTransactions.length > 0) {
21164
+ const toRollback = inFlightTransactions;
21165
+ inFlightTransactions = [];
21166
+ try {
21167
+ await rollbackInjectionCommits(toRollback);
21168
+ } catch (rbErr) {
21169
+ error = combineRollbackError(error, rbErr);
21170
+ }
21171
+ }
21172
+ if (providerInvoked) usageLedger.recordProviderCall({
21173
+ model: agentConfig.model,
21174
+ telemetry: {
21175
+ ...requestTelemetry,
21176
+ round,
21177
+ endTs: Date.now(),
21178
+ requestState: input.abortSignal?.aborted ? "cancelled" : "failed"
21179
+ }
21180
+ });
21181
+ throw error;
21182
+ }
20359
21183
  acceptToolImages.discard(requestMessages.flatMap(toolImages).filter((image) => toolImageDelivery(image) === "impossible"));
20360
21184
  acceptToolImages.commit(
20361
21185
  requestMessages.flatMap(toolImages).filter((image) => toolImageDelivery(image) === "delivered"),
@@ -20365,13 +21189,25 @@ async function runLocalAgentTurn(input) {
20365
21189
  const pending = toolImages(message).filter((image) => supportsImages && !toolImageDelivery(image));
20366
21190
  return pending.length ? { ...message, [TOOL_IMAGES]: pending } : stripToolImages(message);
20367
21191
  }));
21192
+ const actualInput = normalizeUsage(result4.usage).input_tokens;
21193
+ const estimatedInput = estimateLocalRequestInput(
21194
+ requestMessages,
21195
+ admissionProvider.requestBudget?.toolSchemaTokens ?? 0
21196
+ );
21197
+ requestCalibrationRatio = updateLocalRequestCalibration(requestCalibrationRatio, actualInput, estimatedInput);
20368
21198
  loopTiming.mark("llmCall", round);
20369
21199
  usageLedger.recordProviderCall({
20370
21200
  usage: result4.usage,
20371
21201
  model: result4.model,
20372
21202
  provider: result4.provider,
20373
- telemetry: finalizeCallTelemetry(requestTelemetry, result4, round, callStartedAt)
21203
+ telemetry: { ...finalizeCallTelemetry(requestTelemetry, result4, round, callStartedAt), requestState: "completed" }
20374
21204
  });
21205
+ if (!result4.content && result4.output?.some((block) => block.type === "text") && !result4.output.some((block) => block.type === "toolCall")) {
21206
+ result4 = {
21207
+ ...result4,
21208
+ content: result4.output.filter((block) => block.type === "text").map((block) => block.text).join("")
21209
+ };
21210
+ }
20375
21211
  const assistantGuardVerdict = progressGuard.observeAssistantResponse(result4);
20376
21212
  if (assistantGuardVerdict.action === "stall") {
20377
21213
  emitLoopEvent(observationBoundary, {
@@ -20389,11 +21225,11 @@ async function runLocalAgentTurn(input) {
20389
21225
  };
20390
21226
  break;
20391
21227
  }
20392
- if (result4.runtimeProviderFailure) {
21228
+ if (result4.error || result4.runtimeProviderFailure) {
20393
21229
  result4 = {
20394
21230
  ...result4,
20395
21231
  error: true,
20396
- errorMessage: result4.runtimeProviderFailure.message
21232
+ errorMessage: result4.runtimeProviderFailure?.message ?? result4.errorMessage
20397
21233
  };
20398
21234
  break;
20399
21235
  }
@@ -20456,13 +21292,14 @@ ${reasoningTailLog}
20456
21292
  ...result4.reasoning_content ? { reasoning_content: result4.reasoning_content } : {}
20457
21293
  };
20458
21294
  transcript.push(assistantMessage);
20459
- if (applyPendingInjections()) {
21295
+ if (await applyPendingInjections()) {
20460
21296
  loopTiming.mark("roundEnd", round);
20461
21297
  round += 1;
20462
21298
  continue;
20463
21299
  }
20464
21300
  transcript.popLast();
20465
21301
  }
21302
+ stopReason = "done";
20466
21303
  break;
20467
21304
  }
20468
21305
  const outputBlocks = result4.output ?? [];
@@ -20481,9 +21318,11 @@ ${reasoningTailLog}
20481
21318
  }
20482
21319
  }
20483
21320
  if (hasInlineExecutedTools) {
20484
- for (const blockMsg of blocksToOpenAiMessages(outputBlocks)) {
21321
+ for (const blockMsg of blocksToOpenAiMessages(outputBlocks, input.adapter.workspaceRoot)) {
20485
21322
  transcript.pushTool(blockMsg);
20486
21323
  }
21324
+ const inlineResults = outputBlocks.flatMap((block) => block.type === "toolCall" && block.result ? [block.result] : []);
21325
+ stopReason = inlineResults.some(isToolExecutionFailure) ? "error" : inlineResults.some(({ metadata }) => metadata?.uiAskChoice === true && metadata.resolved !== true) ? "pending" : "done";
20487
21326
  skipFinalAppend = true;
20488
21327
  break;
20489
21328
  }
@@ -20500,15 +21339,10 @@ ${reasoningTailLog}
20500
21339
  const executedToolResults = [];
20501
21340
  loopTiming.mark("toolLoopStart", round);
20502
21341
  const slotResults = new Array(toolCalls.length);
20503
- const runSingleTool = async ({ index, toolCall }) => {
20504
- throwIfAborted(input);
20505
- if (!budgetAdmissionGate()) {
20506
- skippedToolCalls.push(toolCall);
20507
- return;
20508
- }
20509
- subtaskBudgetTracker?.recordToolCallExecuted();
21342
+ const runSingleTool = async ({ index, toolCall }, executionInput = input, isClosed = () => false) => {
21343
+ throwIfAborted(executionInput);
20510
21344
  const { toolResult, toolExecMs } = await executeToolCall({
20511
- input,
21345
+ input: executionInput,
20512
21346
  toolCall,
20513
21347
  round,
20514
21348
  boundary: observationBoundary,
@@ -20521,12 +21355,12 @@ ${reasoningTailLog}
20521
21355
  ...result4.finish_reason ? { finishReason: result4.finish_reason } : {}
20522
21356
  }
20523
21357
  });
21358
+ if (isClosed()) return;
20524
21359
  slotResults[index] = {
20525
21360
  toolName: toolCall.function.name,
20526
21361
  toolResult,
20527
21362
  toolExecMs
20528
21363
  };
20529
- executedToolNameLog.push(toolCall.function.name);
20530
21364
  };
20531
21365
  const commitSlot = (i) => {
20532
21366
  const toolCall = toolCalls[i];
@@ -20545,7 +21379,8 @@ ${reasoningTailLog}
20545
21379
  content: formatToolMessageContent({
20546
21380
  toolName,
20547
21381
  content: toolResult.content,
20548
- metadata: observedMetadata
21382
+ metadata: observedMetadata,
21383
+ workspaceRoot: input.adapter.workspaceRoot
20549
21384
  }),
20550
21385
  tool_call_id: toolCall.id,
20551
21386
  toolName,
@@ -20557,14 +21392,36 @@ ${reasoningTailLog}
20557
21392
  } } : {}
20558
21393
  });
20559
21394
  };
20560
- const skippedToolCalls = [];
20561
21395
  const batches = partitionToolCallsIntoBatches(toolCalls);
20562
21396
  for (const batch of batches) {
20563
21397
  throwIfAborted(input);
20564
21398
  let batchError;
20565
21399
  try {
20566
21400
  if (batch.isConcurrent && batch.calls.length > 1) {
20567
- await Promise.all(batch.calls.map(runSingleTool));
21401
+ const controller = new AbortController();
21402
+ const batchInput = {
21403
+ ...input,
21404
+ abortSignal: input.abortSignal ? AbortSignal.any([input.abortSignal, controller.signal]) : controller.signal
21405
+ };
21406
+ let closed = false;
21407
+ let failed = false;
21408
+ let firstError;
21409
+ const tasks = batch.calls.map((call) => runAbortableToolTask(
21410
+ batchInput,
21411
+ runSingleTool(call, batchInput, () => closed),
21412
+ call.toolCall.function.name
21413
+ ).catch((error) => {
21414
+ if (!failed) {
21415
+ failed = true;
21416
+ firstError = error;
21417
+ closed = true;
21418
+ controller.abort();
21419
+ }
21420
+ throw error;
21421
+ }));
21422
+ await Promise.allSettled(tasks);
21423
+ closed = true;
21424
+ if (failed) throw firstError;
20568
21425
  } else {
20569
21426
  for (const call of batch.calls) {
20570
21427
  await runSingleTool(call);
@@ -20581,15 +21438,20 @@ ${reasoningTailLog}
20581
21438
  }
20582
21439
  if (batchError) throw batchError;
20583
21440
  }
20584
- for (const skipped of skippedToolCalls) {
20585
- transcript.pushTool({
20586
- role: "tool",
20587
- content: "[\u5B50\u4EFB\u52A1\u9884\u7B97\u4E0A\u9650\u5DF2\u89E6\u53D1] \u8BE5\u5DE5\u5177\u8C03\u7528\u672A\u6267\u884C\uFF1A\u5DF2\u8FBE\u5B50\u4EFB\u52A1\u9884\u7B97\u4E0A\u9650\uFF0C\u672C\u8F6E\u4E0D\u518D\u53D1\u8D77\u65B0\u7684\u5DE5\u5177\u8C03\u7528\u3002",
20588
- tool_call_id: skipped.id,
20589
- toolName: skipped.function.name
20590
- });
21441
+ const handoff = executedToolResults.map(({ content }) => {
21442
+ try {
21443
+ return typeof content === "string" ? JSON.parse(content) : null;
21444
+ } catch {
21445
+ return null;
21446
+ }
21447
+ }).find((value) => value?.handoff && typeof value.content === "string" && value.content.trim());
21448
+ if (handoff) {
21449
+ result4 = { ...result4, content: handoff.content };
21450
+ stopReason = "handoff";
21451
+ break;
20591
21452
  }
20592
- if (budgetExhaustion) {
21453
+ if (executedToolResults.some(({ metadata }) => metadata?.uiAskChoice === true && metadata.resolved !== true)) {
21454
+ stopReason = "pending";
20593
21455
  break;
20594
21456
  }
20595
21457
  const toolExecutionGuardVerdict = progressGuard.observeToolExecution(
@@ -20612,117 +21474,28 @@ ${reasoningTailLog}
20612
21474
  };
20613
21475
  break;
20614
21476
  }
20615
- await compaction.maybeCompactInLoop();
20616
- if (!budgetAdmissionGate()) {
20617
- break;
20618
- }
20619
21477
  loopTiming.mark("roundEnd", round);
20620
21478
  round += 1;
20621
21479
  }
20622
21480
  } catch (error) {
20623
- loopError = error;
20624
- }
20625
- const finalBudgetExhaustion = budgetExhaustion;
20626
- let budgetWrapupFailure;
20627
- let budgetWrapupSucceeded = false;
20628
- if (!loopError && finalBudgetExhaustion && subtaskBudgetTracker && finalBudgetExhaustion.limit === "max-tool-calls" && !input.abortSignal?.aborted && subtaskBudgetTracker.elapsedMs() < subtaskBudgetTracker.limits.maxWallClockMs) {
20629
- const remainingMs = subtaskBudgetTracker.limits.maxWallClockMs - subtaskBudgetTracker.elapsedMs();
20630
- const timeoutMs = Math.min(3e4, remainingMs);
20631
- const controller = new AbortController();
20632
- let rejectCancelled = () => {
20633
- };
20634
- const cancelled = new Promise((_, reject) => {
20635
- rejectCancelled = reject;
20636
- });
20637
- const onAbort = () => {
20638
- controller.abort();
20639
- rejectCancelled(buildAbortedError());
20640
- };
20641
- input.abortSignal?.addEventListener("abort", onAbort, { once: true });
20642
- if (input.abortSignal?.aborted) onAbort();
20643
- let timer;
20644
- let wrapRequestStarted = false;
20645
- let wrapUsageCaptured = false;
20646
- let wrapOutcomeReceived = false;
21481
+ const toRollback = [...pendingInjectionCommits.splice(0), ...inFlightTransactions];
21482
+ inFlightTransactions = [];
21483
+ let rollbackErr;
20647
21484
  try {
20648
- const deadline = new Promise((_, reject) => {
20649
- timer = setTimeout(() => {
20650
- controller.abort();
20651
- reject(new Error("budget wrap-up timed out"));
20652
- }, timeoutMs);
20653
- });
20654
- const work = async () => {
20655
- const wrapProvider = await input.adapter.resolveProvider(agentConfig, { toolNames: [], prompt: "", textOnly: true });
20656
- if (!wrapProvider.textOnly) throw new Error(`host ${input.adapter.host} does not support isolated text-only provider`);
20657
- if (controller.signal.aborted || input.abortSignal?.aborted || subtaskBudgetTracker.elapsedMs() >= subtaskBudgetTracker.limits.maxWallClockMs) throw new Error("budget wrap-up cancelled or deadline reached");
20658
- const wrapMessages = [
20659
- ...filterImagePartsFromMessages(prepareMessagesForProviderCall(transcript.working()).messages, supportsImages),
20660
- { role: "user", content: "\u5DE5\u5177\u9884\u7B97\u5DF2\u8017\u5C3D\u3002\u4E0D\u8981\u8C03\u7528\u5DE5\u5177\uFF1B\u4EC5\u57FA\u4E8E\u5DF2\u5B8C\u6210\u5185\u5BB9\uFF0C\u7B80\u8FF0\u7ED3\u679C\u3001\u8BC1\u636E\u4E0E\u672A\u5B8C\u6210\u4E8B\u9879\u3002" }
20661
- ];
20662
- const startedAt = Date.now();
20663
- const telemetry2 = buildRequestTelemetry({ dialogKey: input.continueDialogId || `budget:${crypto.randomUUID()}`, messages: wrapMessages, toolsHash: safeToolNamesHash([]) });
20664
- wrapRequestStarted = true;
20665
- const summary2 = await runCompleteWithTimeout({
20666
- provider: wrapProvider,
20667
- messages: wrapMessages,
20668
- options: { max_tokens: 1024, noRetries: true, timeoutMs, signal: controller.signal, ...input.continueDialogId ? { dialogId: input.continueDialogId } : {} },
20669
- timeoutMs,
20670
- round,
20671
- input,
20672
- boundary: observationBoundary,
20673
- providerName: agentConfig.provider,
20674
- model: wrapProvider.model
20675
- });
20676
- return { summary: summary2, telemetry: finalizeCallTelemetry(telemetry2, summary2, round, startedAt) };
20677
- };
20678
- const { summary, telemetry } = await Promise.race([work(), deadline, cancelled]);
20679
- wrapOutcomeReceived = true;
20680
- usageLedger.recordProviderCall({ usage: summary.usage, model: summary.model, provider: summary.provider, telemetry });
20681
- wrapUsageCaptured = Boolean(summary.usage);
20682
- if (summary.tool_calls?.length || summary.output?.some((block) => block.type === "toolCall")) throw new Error("text-only provider returned a tool call");
20683
- if (summary.error || typeof summary.content !== "string" || !summary.content.trim()) throw new Error("text-only provider returned no usable summary");
20684
- if (input.abortSignal?.aborted) throw buildAbortedError();
20685
- result4 = { ...result4, content: summary.content.trim().slice(0, 12e3) };
20686
- budgetWrapupSucceeded = true;
20687
- } catch (error) {
20688
- if (input.abortSignal?.aborted) {
20689
- const aborted = buildAbortedError();
20690
- if (wrapRequestStarted && !wrapUsageCaptured) {
20691
- aborted.message += "\uFF08\u65E0\u5DE5\u5177\u603B\u7ED3\u8BF7\u6C42\u53EF\u80FD\u5DF2\u8BA1\u8D39\uFF0C\u672A\u8FD4\u56DE\u7684\u7528\u91CF\u672A\u77E5\uFF09";
20692
- aborted.usageUnavailable = true;
20693
- }
20694
- loopError = aborted;
20695
- } else {
20696
- const detail = error instanceof Error ? error.message : "unknown error";
20697
- budgetWrapupFailure = scrubSecrets(detail).cleaned.slice(0, 300);
20698
- if (wrapRequestStarted && !wrapOutcomeReceived) budgetWrapupFailure += "\uFF1B\u8BF7\u6C42\u53EF\u80FD\u5DF2\u8BA1\u8D39\uFF0C\u672A\u8FD4\u56DE\u7684\u7528\u91CF\u672A\u77E5";
20699
- }
20700
- } finally {
20701
- if (timer) clearTimeout(timer);
20702
- controller.abort();
20703
- input.abortSignal?.removeEventListener("abort", onAbort);
21485
+ await rollbackInjectionCommits(toRollback);
21486
+ } catch (rbErr) {
21487
+ rollbackErr = rbErr;
20704
21488
  }
21489
+ loopError = rollbackErr ? combineRollbackError(error, rollbackErr) : error;
20705
21490
  }
20706
21491
  if (!loopError && input.abortSignal?.aborted) loopError = buildAbortedError();
20707
- if (budgetExhaustion) {
20708
- if (!budgetWrapupSucceeded && !budgetWrapupFailure) {
20709
- budgetWrapupFailure = input.abortSignal?.aborted ? "\u7528\u6237\u5DF2\u53D6\u6D88" : "\u5899\u949F\u9884\u7B97\u5DF2\u8017\u5C3D\uFF0C\u672A\u53D1\u8D77\u603B\u7ED3\u8BF7\u6C42";
20710
- }
20711
- const previousContent = typeof result4?.content === "string" ? result4.content.trim() : "";
20712
- result4 = {
20713
- ...result4,
20714
- content: [
20715
- previousContent,
20716
- ...budgetWrapupFailure ? [`\u65E0\u5DE5\u5177\u603B\u7ED3\u672A\u5B8C\u6210\uFF08${budgetWrapupFailure}\uFF09\uFF1B\u4EE5\u4E0B\u662F\u4FDD\u7559\u7684\u5DF2\u6709\u5185\u5BB9\u548C\u9884\u7B97\u72B6\u6001\u3002`] : [],
20717
- buildSubtaskBudgetExhaustedContent({
20718
- exhaustion: budgetExhaustion,
20719
- roundsCompleted: round,
20720
- recentToolNames: executedToolNameLog
20721
- })
20722
- ].filter((part) => part.length > 0).join("\n\n")
20723
- };
20724
- }
20725
21492
  if (loopError) {
21493
+ if (loopError instanceof LocalContextCoverageError && round === 0 && !consumedInjectedMessages) {
21494
+ if (input.continueDialogId) attachDialogIdToError(loopError, input.continueDialogId);
21495
+ attachUsageRecordsToError(loopError, usageLedger.records());
21496
+ await loopTiming.flush();
21497
+ throw loopError;
21498
+ }
20726
21499
  const turnMessages2 = transcript.persisted(
20727
21500
  sanitizedPersistedInput,
20728
21501
  input.persistedInputReference
@@ -20748,6 +21521,16 @@ ${reasoningTailLog}
20748
21521
  throw loopError;
20749
21522
  }
20750
21523
  result4 = result4;
21524
+ result4 = {
21525
+ ...result4,
21526
+ stopReason: input.abortSignal?.aborted ? "aborted" : result4.error || result4.runtimeProviderFailure || result4.emptyAssistantFallbackReason ? "error" : stopReason
21527
+ };
21528
+ delete result4.businessCompletion;
21529
+ const businessCompletion = evaluateTaskExecutionContract(input.taskExecutionContract, {
21530
+ stopped: input.abortSignal?.aborted,
21531
+ executionFailed: result4.error === true || !!result4.runtimeProviderFailure || !!result4.emptyAssistantFallbackReason
21532
+ });
21533
+ if (businessCompletion) result4.businessCompletion = businessCompletion;
20751
21534
  if (!skipFinalAppend) {
20752
21535
  const truncatedReasoningTailLog = resolveTruncatedReasoningTailLog(
20753
21536
  result4.emptyAssistantFallbackReason,
@@ -20834,6 +21617,231 @@ function buildResponsesRequestBody(body, model, options) {
20834
21617
  };
20835
21618
  }
20836
21619
 
21620
+ // packages/agent-runtime/responsesProtocolLeakSanitizer.ts
21621
+ var LEAKED_PROTOCOL_NOTICE = "\n\n[\u63D0\u793A\uFF1A\u5DF2\u62E6\u622A\u6A21\u578B\u751F\u6210\u7684\u672A\u6267\u884C\u5DE5\u5177\u534F\u8BAE\u7247\u6BB5]";
21622
+ var INLINE_MARKER_REGEXES = [
21623
+ /\u3011\u3010\u3002\s*assistant\s+to\s*=\s*functions\./i,
21624
+ /\u3011\u3010\u3002\s*functions\./i,
21625
+ /<\|(?:im_start|start_header_id)\|>\s*assistant\s+to\s*=\s*functions\./i,
21626
+ /<\|(?:im_start|start_header_id)\|>\s*functions\./i
21627
+ ];
21628
+ var INLINE_MARKER_PREFIXES = [
21629
+ "\u3011\u3010\u3002assistant to=functions.",
21630
+ "\u3011\u3010\u3002functions.",
21631
+ "<|im_start|>assistant to=functions.",
21632
+ "<|im_start|>functions.",
21633
+ "<|start_header_id|>assistant to=functions.",
21634
+ "<|start_header_id|>functions."
21635
+ ];
21636
+ var TAB_D_PREFIX = " Dfunctions.".toLowerCase();
21637
+ function findLeakedMarker(text2, wasAtLineStart) {
21638
+ let earliestIndex = -1;
21639
+ let earliestLength = 0;
21640
+ for (const regex of INLINE_MARKER_REGEXES) {
21641
+ const match7 = regex.exec(text2);
21642
+ if (match7 && match7.index !== void 0) {
21643
+ if (earliestIndex === -1 || match7.index < earliestIndex) {
21644
+ earliestIndex = match7.index;
21645
+ earliestLength = match7[0].length;
21646
+ }
21647
+ }
21648
+ }
21649
+ const lineMarkerRegex = /(?:^|[\r\n])[ \t]*\tDfunctions\./gi;
21650
+ let m;
21651
+ while ((m = lineMarkerRegex.exec(text2)) !== null) {
21652
+ const isNewline = m[0].startsWith("\n") || m[0].startsWith("\r");
21653
+ const actualIndex = m.index + (isNewline ? 1 : 0);
21654
+ if (actualIndex === 0 && !wasAtLineStart) {
21655
+ continue;
21656
+ }
21657
+ if (earliestIndex === -1 || actualIndex < earliestIndex) {
21658
+ earliestIndex = actualIndex;
21659
+ earliestLength = m[0].length - (isNewline ? 1 : 0);
21660
+ }
21661
+ break;
21662
+ }
21663
+ return earliestIndex >= 0 ? { index: earliestIndex, length: earliestLength } : null;
21664
+ }
21665
+ function calculateHoldLength(buffer3, wasAtLineStart) {
21666
+ let hold = 0;
21667
+ const lower = buffer3.toLowerCase();
21668
+ for (const prefix of INLINE_MARKER_PREFIXES) {
21669
+ const pLower = prefix.toLowerCase();
21670
+ const limit = Math.min(lower.length, pLower.length);
21671
+ for (let len = limit; len > 0; len--) {
21672
+ if (pLower.startsWith(lower.slice(-len))) {
21673
+ if (len > hold) hold = len;
21674
+ break;
21675
+ }
21676
+ }
21677
+ }
21678
+ const trailingLineMatch = /(?:^|[\r\n])[ \t]*$/.exec(buffer3);
21679
+ if (trailingLineMatch) {
21680
+ const isNewline = trailingLineMatch[0].startsWith("\n") || trailingLineMatch[0].startsWith("\r");
21681
+ const len = trailingLineMatch[0].length - (isNewline ? 1 : 0);
21682
+ if (len > 0 && (isNewline || wasAtLineStart)) {
21683
+ if (len > hold) hold = len;
21684
+ }
21685
+ }
21686
+ const trailingTabMatch = /(?:^|[\r\n])[ \t]*(\t[a-zA-Z0-9_\.]*)$/i.exec(lower);
21687
+ if (trailingTabMatch) {
21688
+ const candidate = trailingTabMatch[1];
21689
+ if (TAB_D_PREFIX.startsWith(candidate)) {
21690
+ const isNewline = trailingTabMatch[0].startsWith("\n") || trailingTabMatch[0].startsWith("\r");
21691
+ const len = trailingTabMatch[0].length - (isNewline ? 1 : 0);
21692
+ if (isNewline || wasAtLineStart) {
21693
+ if (len > hold) hold = len;
21694
+ }
21695
+ }
21696
+ }
21697
+ const trailingFenceMatch = /(?:^|[\r\n])[ ]{0,3}(?:`{1,2}|~{1,2})$/.exec(buffer3);
21698
+ if (trailingFenceMatch) {
21699
+ const isNewline = trailingFenceMatch[0].startsWith("\n") || trailingFenceMatch[0].startsWith("\r");
21700
+ const len = trailingFenceMatch[0].length - (isNewline ? 1 : 0);
21701
+ if (isNewline || wasAtLineStart) {
21702
+ if (len > hold) hold = len;
21703
+ }
21704
+ }
21705
+ return hold;
21706
+ }
21707
+ function createResponsesProtocolLeakSanitizer(onTextDelta) {
21708
+ let safeContent = "";
21709
+ let buffer3 = "";
21710
+ let inCodeFence = false;
21711
+ let fenceChar = "";
21712
+ let fenceLength = 0;
21713
+ let intercepted = false;
21714
+ let wasAtLineStart = true;
21715
+ const emit = (text2) => {
21716
+ safeContent += text2;
21717
+ onTextDelta?.(text2);
21718
+ if (text2.length > 0) {
21719
+ const last = text2[text2.length - 1];
21720
+ wasAtLineStart = last === "\n" || last === "\r";
21721
+ }
21722
+ };
21723
+ const processBuffer = (isFinal) => {
21724
+ if (intercepted) return;
21725
+ while (buffer3.length > 0) {
21726
+ if (!inCodeFence) {
21727
+ let fenceIndex = -1;
21728
+ let fenceMarkerStr = "";
21729
+ let lineStartOffset = 0;
21730
+ const fenceRegex = /(?:^|[\r\n])[ ]{0,3}(`{3,}|~{3,})[^\r\n]*(?:\r?\n|$)/g;
21731
+ let m;
21732
+ while ((m = fenceRegex.exec(buffer3)) !== null) {
21733
+ const isNewlinePreceded = m[0].startsWith("\n") || m[0].startsWith("\r");
21734
+ const actualStart = m.index + (isNewlinePreceded ? 1 : 0);
21735
+ if (actualStart === 0 && !wasAtLineStart) {
21736
+ continue;
21737
+ }
21738
+ fenceIndex = actualStart;
21739
+ fenceMarkerStr = m[1];
21740
+ lineStartOffset = m[0].length - (isNewlinePreceded ? 1 : 0);
21741
+ break;
21742
+ }
21743
+ const markerMatch = findLeakedMarker(buffer3, wasAtLineStart);
21744
+ const markerIndex = markerMatch ? markerMatch.index : -1;
21745
+ if (markerIndex >= 0 && (fenceIndex === -1 || markerIndex < fenceIndex)) {
21746
+ const normalPart = buffer3.slice(0, markerIndex);
21747
+ emit(normalPart);
21748
+ emit(LEAKED_PROTOCOL_NOTICE);
21749
+ intercepted = true;
21750
+ buffer3 = "";
21751
+ return;
21752
+ }
21753
+ if (fenceIndex >= 0) {
21754
+ if (!isFinal && !buffer3.slice(fenceIndex).includes("\n") && !buffer3.slice(fenceIndex).includes("\r")) {
21755
+ const beforeFence2 = buffer3.slice(0, fenceIndex);
21756
+ emit(beforeFence2);
21757
+ buffer3 = buffer3.slice(fenceIndex);
21758
+ return;
21759
+ }
21760
+ const beforeFence = buffer3.slice(0, fenceIndex);
21761
+ emit(beforeFence);
21762
+ inCodeFence = true;
21763
+ fenceChar = fenceMarkerStr[0];
21764
+ fenceLength = fenceMarkerStr.length;
21765
+ emit(buffer3.slice(fenceIndex, fenceIndex + lineStartOffset));
21766
+ buffer3 = buffer3.slice(fenceIndex + lineStartOffset);
21767
+ continue;
21768
+ }
21769
+ if (isFinal) {
21770
+ emit(buffer3);
21771
+ buffer3 = "";
21772
+ return;
21773
+ }
21774
+ const hold = calculateHoldLength(buffer3, wasAtLineStart);
21775
+ if (hold === 0) {
21776
+ emit(buffer3);
21777
+ buffer3 = "";
21778
+ } else if (hold < buffer3.length) {
21779
+ emit(buffer3.slice(0, buffer3.length - hold));
21780
+ buffer3 = buffer3.slice(buffer3.length - hold);
21781
+ }
21782
+ return;
21783
+ } else {
21784
+ const closeRegex = new RegExp(`(?:^|[\\r\\n])[ ]{0,3}${fenceChar}{${fenceLength},}[ ]*(?:\\r?\\n|$)`, "g");
21785
+ const closeMatch = closeRegex.exec(buffer3);
21786
+ if (closeMatch) {
21787
+ const isNewlinePreceded = closeMatch[0].startsWith("\n") || closeMatch[0].startsWith("\r");
21788
+ const actualStart = closeMatch.index + (isNewlinePreceded ? 1 : 0);
21789
+ if (actualStart === 0 && !wasAtLineStart) {
21790
+ emit(buffer3.slice(0, closeMatch.index + 1));
21791
+ buffer3 = buffer3.slice(closeMatch.index + 1);
21792
+ continue;
21793
+ }
21794
+ const closeEnd = closeMatch.index + closeMatch[0].length;
21795
+ emit(buffer3.slice(0, closeEnd));
21796
+ buffer3 = buffer3.slice(closeEnd);
21797
+ inCodeFence = false;
21798
+ fenceChar = "";
21799
+ fenceLength = 0;
21800
+ continue;
21801
+ }
21802
+ if (isFinal) {
21803
+ emit(buffer3);
21804
+ buffer3 = "";
21805
+ return;
21806
+ }
21807
+ const trailingCloseMatch = new RegExp(`(?:^|[\\r\\n])[ ]{0,3}${fenceChar}{1,${fenceLength}}$`).exec(buffer3);
21808
+ const hold = trailingCloseMatch ? trailingCloseMatch[0].length : 0;
21809
+ if (hold === 0) {
21810
+ emit(buffer3);
21811
+ buffer3 = "";
21812
+ } else if (hold < buffer3.length) {
21813
+ emit(buffer3.slice(0, buffer3.length - hold));
21814
+ buffer3 = buffer3.slice(buffer3.length - hold);
21815
+ }
21816
+ return;
21817
+ }
21818
+ }
21819
+ };
21820
+ return {
21821
+ push(chunk) {
21822
+ if (intercepted) return;
21823
+ if (chunk.length === 0) {
21824
+ emit("");
21825
+ return;
21826
+ }
21827
+ buffer3 += chunk;
21828
+ processBuffer(false);
21829
+ },
21830
+ finalize() {
21831
+ processBuffer(true);
21832
+ return {
21833
+ content: safeContent,
21834
+ intercepted
21835
+ };
21836
+ }
21837
+ };
21838
+ }
21839
+ function sanitizeResponsesProtocolLeakText(text2) {
21840
+ const sanitizer = createResponsesProtocolLeakSanitizer();
21841
+ sanitizer.push(text2);
21842
+ return sanitizer.finalize();
21843
+ }
21844
+
20837
21845
  // packages/agent-runtime/responsesSseStream.ts
20838
21846
  function toResponsesStreamFailure(raw, fallbackPrefix = "Responses") {
20839
21847
  if (!raw || typeof raw !== "object") return void 0;
@@ -20870,6 +21878,8 @@ function hasNumericTokenCount(u) {
20870
21878
  function createResponsesStreamCollector(callbacks) {
20871
21879
  let content = "";
20872
21880
  let reasoning = "";
21881
+ const textSanitizer = createResponsesProtocolLeakSanitizer(callbacks?.onTextDelta);
21882
+ let sawNonEmptyOutputTextDelta = false;
20873
21883
  const toolCallsAcc = createResponsesToolAccumulator();
20874
21884
  let usage;
20875
21885
  let failure;
@@ -20921,8 +21931,8 @@ function createResponsesStreamCollector(callbacks) {
20921
21931
  return;
20922
21932
  }
20923
21933
  if (type === "response.output_text.delta" && typeof ev.delta === "string") {
20924
- content += ev.delta;
20925
- callbacks?.onTextDelta?.(ev.delta);
21934
+ if (ev.delta.length > 0) sawNonEmptyOutputTextDelta = true;
21935
+ textSanitizer.push(ev.delta);
20926
21936
  return;
20927
21937
  }
20928
21938
  if ((type === "response.reasoning_text.delta" || type === "response.reasoning.delta") && typeof ev.delta === "string") {
@@ -20932,11 +21942,11 @@ function createResponsesStreamCollector(callbacks) {
20932
21942
  }
20933
21943
  if (type === "response.output_item.added" || type === "response.output_item.done") {
20934
21944
  const item = ev.item;
20935
- if (item?.type === "message" && !content) {
21945
+ if (item?.type === "message" && !sawNonEmptyOutputTextDelta) {
20936
21946
  const itemText = extractOutputTextFromItem(item);
20937
21947
  if (itemText) {
20938
- content = itemText;
20939
- callbacks?.onTextDelta?.(itemText);
21948
+ sawNonEmptyOutputTextDelta = true;
21949
+ textSanitizer.push(itemText);
20940
21950
  }
20941
21951
  }
20942
21952
  applyResponsesToolEvent(toolCallsAcc, ev);
@@ -21035,8 +22045,16 @@ function createResponsesStreamCollector(callbacks) {
21035
22045
  if (authoritativeCompletedTools && failure) {
21036
22046
  finalToolCalls = [];
21037
22047
  }
22048
+ const sanitized = textSanitizer.finalize();
22049
+ content = sanitized.content;
22050
+ let intercepted = sanitized.intercepted;
21038
22051
  if (!content && completedResponse) {
21039
- content = extractTextFromResponseOutput(completedResponse);
22052
+ const extracted = extractTextFromResponseOutput(completedResponse);
22053
+ if (extracted) {
22054
+ const sanitizedExtracted = sanitizeResponsesProtocolLeakText(extracted);
22055
+ content = sanitizedExtracted.content;
22056
+ if (sanitizedExtracted.intercepted) intercepted = true;
22057
+ }
21040
22058
  }
21041
22059
  if (!reasoning && completedResponse) {
21042
22060
  reasoning = extractReasoningFromResponseOutput(completedResponse);
@@ -21051,6 +22069,7 @@ function createResponsesStreamCollector(callbacks) {
21051
22069
  ...streamComplete ? { finish_reason: finalToolCalls.length > 0 ? "tool_calls" : "stop" } : {},
21052
22070
  ...streamComplete ? { stream_complete: true } : {},
21053
22071
  ...streamTruncated ? { stream_truncated: true } : {},
22072
+ ...intercepted ? { leaked_protocol_intercepted: true } : {},
21054
22073
  ...completedResponse ? { completed_response: completedResponse } : {},
21055
22074
  ...completedResponse && typeof completedResponse.id === "string" ? { response_id: completedResponse.id } : {},
21056
22075
  ...failure ? { failure } : {}
@@ -21432,6 +22451,7 @@ async function executeOpenAiCompatibleChatCompletion(args2) {
21432
22451
  ...streamed2.usage ? { usage: streamed2.usage } : {},
21433
22452
  ...streamed2.stream_complete ? { stream_complete: true } : {},
21434
22453
  ...streamed2.finish_reason ? { finish_reason: streamed2.finish_reason } : {},
22454
+ ...streamed2.leaked_protocol_intercepted ? { leaked_protocol_intercepted: true } : {},
21435
22455
  trace: args2.messages
21436
22456
  });
21437
22457
  }
@@ -21479,6 +22499,16 @@ async function executeOpenAiCompatibleChatCompletion(args2) {
21479
22499
  throw error;
21480
22500
  }
21481
22501
  }
22502
+ function describeOpenAiCompatibleRequestBudget(args2) {
22503
+ const request3 = buildOpenAiCompatibleChatCompletionRequest({ ...args2, messages: [] });
22504
+ const body = JSON.parse(request3.init.body);
22505
+ const cap = body.max_completion_tokens ?? body.max_output_tokens ?? body.max_tokens;
22506
+ return {
22507
+ toolSchemaTokens: estimateTokenCount(JSON.stringify(body.tools ?? [])),
22508
+ // Explicit wire allowance is conservatively reserved. Omitted provider defaults are unknown.
22509
+ output: typeof cap === "number" && Number.isFinite(cap) && cap > 0 ? { semantics: "shared-window", finalWireCapTokens: cap } : { semantics: "unknown", fallbackReserveTokens: 8192 }
22510
+ };
22511
+ }
21482
22512
 
21483
22513
  // packages/agent-runtime/openAiCompatibleProviderConfig.ts
21484
22514
  async function resolveOpenAiCompatibleProviderConfig(args2) {
@@ -22622,7 +23652,8 @@ function createHybridRecordStore(deps) {
22622
23652
  const normalizedLocalRecord = normalizeHybridRecord(dbKey, localRecord);
22623
23653
  if (normalizedLocalRecord && options?.remote !== true) return normalizedLocalRecord;
22624
23654
  if (options?.remote === false) return null;
22625
- const fallbackDeadlineMs = requestTimeoutMs > 0 ? Date.now() + requestTimeoutMs : null;
23655
+ const effectiveTimeoutMs = typeof options?.timeoutMs === "number" && Number.isFinite(options.timeoutMs) && options.timeoutMs > 0 ? options.timeoutMs : requestTimeoutMs;
23656
+ const fallbackDeadlineMs = effectiveTimeoutMs > 0 ? Date.now() + effectiveTimeoutMs : null;
22626
23657
  for (const server of resolveHybridServers(
22627
23658
  deps.defaultServer,
22628
23659
  options?.preferredServerOrigin,
@@ -22673,6 +23704,267 @@ function createHybridRecordStore(deps) {
22673
23704
  var DEFAULT_SANDBOX_TMPFS_SIZE_BYTES = 128 * 1024 * 1024;
22674
23705
  var BWRAP_PROBE_TTL_MS = 5 * 6e4;
22675
23706
 
23707
+ // node_modules/async-mutex/index.mjs
23708
+ var E_TIMEOUT = new Error("timeout while waiting for mutex to become available");
23709
+ var E_ALREADY_LOCKED = new Error("mutex already locked");
23710
+ var E_CANCELED = new Error("request for lock canceled");
23711
+ var __awaiter$2 = function(thisArg, _arguments, P, generator) {
23712
+ function adopt(value) {
23713
+ return value instanceof P ? value : new P(function(resolve6) {
23714
+ resolve6(value);
23715
+ });
23716
+ }
23717
+ return new (P || (P = Promise))(function(resolve6, reject) {
23718
+ function fulfilled(value) {
23719
+ try {
23720
+ step(generator.next(value));
23721
+ } catch (e) {
23722
+ reject(e);
23723
+ }
23724
+ }
23725
+ function rejected(value) {
23726
+ try {
23727
+ step(generator["throw"](value));
23728
+ } catch (e) {
23729
+ reject(e);
23730
+ }
23731
+ }
23732
+ function step(result4) {
23733
+ result4.done ? resolve6(result4.value) : adopt(result4.value).then(fulfilled, rejected);
23734
+ }
23735
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
23736
+ });
23737
+ };
23738
+ var Semaphore = class {
23739
+ constructor(_value, _cancelError = E_CANCELED) {
23740
+ this._value = _value;
23741
+ this._cancelError = _cancelError;
23742
+ this._queue = [];
23743
+ this._weightedWaiters = [];
23744
+ }
23745
+ acquire(weight = 1, priority = 0) {
23746
+ if (weight <= 0)
23747
+ throw new Error(`invalid weight ${weight}: must be positive`);
23748
+ return new Promise((resolve6, reject) => {
23749
+ const task = { resolve: resolve6, reject, weight, priority };
23750
+ const i = findIndexFromEnd(this._queue, (other) => priority <= other.priority);
23751
+ if (i === -1 && weight <= this._value) {
23752
+ this._dispatchItem(task);
23753
+ } else {
23754
+ this._queue.splice(i + 1, 0, task);
23755
+ }
23756
+ });
23757
+ }
23758
+ runExclusive(callback_1) {
23759
+ return __awaiter$2(this, arguments, void 0, function* (callback4, weight = 1, priority = 0) {
23760
+ const [value, release3] = yield this.acquire(weight, priority);
23761
+ try {
23762
+ return yield callback4(value);
23763
+ } finally {
23764
+ release3();
23765
+ }
23766
+ });
23767
+ }
23768
+ waitForUnlock(weight = 1, priority = 0) {
23769
+ if (weight <= 0)
23770
+ throw new Error(`invalid weight ${weight}: must be positive`);
23771
+ if (this._couldLockImmediately(weight, priority)) {
23772
+ return Promise.resolve();
23773
+ } else {
23774
+ return new Promise((resolve6) => {
23775
+ if (!this._weightedWaiters[weight - 1])
23776
+ this._weightedWaiters[weight - 1] = [];
23777
+ insertSorted(this._weightedWaiters[weight - 1], { resolve: resolve6, priority });
23778
+ });
23779
+ }
23780
+ }
23781
+ isLocked() {
23782
+ return this._value <= 0;
23783
+ }
23784
+ getValue() {
23785
+ return this._value;
23786
+ }
23787
+ setValue(value) {
23788
+ this._value = value;
23789
+ this._dispatchQueue();
23790
+ }
23791
+ release(weight = 1) {
23792
+ if (weight <= 0)
23793
+ throw new Error(`invalid weight ${weight}: must be positive`);
23794
+ this._value += weight;
23795
+ this._dispatchQueue();
23796
+ }
23797
+ cancel() {
23798
+ this._queue.forEach((entry) => entry.reject(this._cancelError));
23799
+ this._queue = [];
23800
+ }
23801
+ _dispatchQueue() {
23802
+ this._drainUnlockWaiters();
23803
+ while (this._queue.length > 0 && this._queue[0].weight <= this._value) {
23804
+ this._dispatchItem(this._queue.shift());
23805
+ this._drainUnlockWaiters();
23806
+ }
23807
+ }
23808
+ _dispatchItem(item) {
23809
+ const previousValue = this._value;
23810
+ this._value -= item.weight;
23811
+ item.resolve([previousValue, this._newReleaser(item.weight)]);
23812
+ }
23813
+ _newReleaser(weight) {
23814
+ let called = false;
23815
+ return () => {
23816
+ if (called)
23817
+ return;
23818
+ called = true;
23819
+ this.release(weight);
23820
+ };
23821
+ }
23822
+ _drainUnlockWaiters() {
23823
+ if (this._queue.length === 0) {
23824
+ for (let weight = this._value; weight > 0; weight--) {
23825
+ const waiters = this._weightedWaiters[weight - 1];
23826
+ if (!waiters)
23827
+ continue;
23828
+ waiters.forEach((waiter) => waiter.resolve());
23829
+ this._weightedWaiters[weight - 1] = [];
23830
+ }
23831
+ } else {
23832
+ const queuedPriority = this._queue[0].priority;
23833
+ for (let weight = this._value; weight > 0; weight--) {
23834
+ const waiters = this._weightedWaiters[weight - 1];
23835
+ if (!waiters)
23836
+ continue;
23837
+ const i = waiters.findIndex((waiter) => waiter.priority <= queuedPriority);
23838
+ (i === -1 ? waiters : waiters.splice(0, i)).forEach((waiter) => waiter.resolve());
23839
+ }
23840
+ }
23841
+ }
23842
+ _couldLockImmediately(weight, priority) {
23843
+ return (this._queue.length === 0 || this._queue[0].priority < priority) && weight <= this._value;
23844
+ }
23845
+ };
23846
+ function insertSorted(a, v) {
23847
+ const i = findIndexFromEnd(a, (other) => v.priority <= other.priority);
23848
+ a.splice(i + 1, 0, v);
23849
+ }
23850
+ function findIndexFromEnd(a, predicate) {
23851
+ for (let i = a.length - 1; i >= 0; i--) {
23852
+ if (predicate(a[i])) {
23853
+ return i;
23854
+ }
23855
+ }
23856
+ return -1;
23857
+ }
23858
+ var __awaiter$1 = function(thisArg, _arguments, P, generator) {
23859
+ function adopt(value) {
23860
+ return value instanceof P ? value : new P(function(resolve6) {
23861
+ resolve6(value);
23862
+ });
23863
+ }
23864
+ return new (P || (P = Promise))(function(resolve6, reject) {
23865
+ function fulfilled(value) {
23866
+ try {
23867
+ step(generator.next(value));
23868
+ } catch (e) {
23869
+ reject(e);
23870
+ }
23871
+ }
23872
+ function rejected(value) {
23873
+ try {
23874
+ step(generator["throw"](value));
23875
+ } catch (e) {
23876
+ reject(e);
23877
+ }
23878
+ }
23879
+ function step(result4) {
23880
+ result4.done ? resolve6(result4.value) : adopt(result4.value).then(fulfilled, rejected);
23881
+ }
23882
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
23883
+ });
23884
+ };
23885
+ var Mutex = class {
23886
+ constructor(cancelError) {
23887
+ this._semaphore = new Semaphore(1, cancelError);
23888
+ }
23889
+ acquire() {
23890
+ return __awaiter$1(this, arguments, void 0, function* (priority = 0) {
23891
+ const [, releaser] = yield this._semaphore.acquire(1, priority);
23892
+ return releaser;
23893
+ });
23894
+ }
23895
+ runExclusive(callback4, priority = 0) {
23896
+ return this._semaphore.runExclusive(() => callback4(), 1, priority);
23897
+ }
23898
+ isLocked() {
23899
+ return this._semaphore.isLocked();
23900
+ }
23901
+ waitForUnlock(priority = 0) {
23902
+ return this._semaphore.waitForUnlock(1, priority);
23903
+ }
23904
+ release() {
23905
+ if (this._semaphore.isLocked())
23906
+ this._semaphore.release();
23907
+ }
23908
+ cancel() {
23909
+ return this._semaphore.cancel();
23910
+ }
23911
+ };
23912
+
23913
+ // packages/database-engine/dialogOwner.ts
23914
+ var dialogOwnerKey = (dialogId) => `dialogown-${dialogId}`;
23915
+ var locks = /* @__PURE__ */ new Map();
23916
+ async function registerDialogOwner(args2) {
23917
+ const emit = (event) => {
23918
+ try {
23919
+ (args2.log ?? ((event2) => console.warn(event2)))(event);
23920
+ } catch {
23921
+ }
23922
+ };
23923
+ const key = dialogOwnerKey(args2.dialogId);
23924
+ let mutex = locks.get(key);
23925
+ if (!mutex) {
23926
+ mutex = new Mutex();
23927
+ locks.set(key, mutex);
23928
+ }
23929
+ try {
23930
+ await mutex.runExclusive(async () => {
23931
+ let existing;
23932
+ try {
23933
+ existing = await args2.store.get(key);
23934
+ } catch (error) {
23935
+ if (!isLevelNotFoundError(error)) throw error;
23936
+ existing = null;
23937
+ }
23938
+ if (existing != null) {
23939
+ if (existing.ownerId !== args2.ownerId) emit({
23940
+ event: "dialog_owner_conflict",
23941
+ dialogId: args2.dialogId,
23942
+ existingOwnerId: existing.ownerId,
23943
+ attemptedOwnerId: args2.ownerId,
23944
+ source: args2.source
23945
+ });
23946
+ return;
23947
+ }
23948
+ await args2.store.put(key, {
23949
+ dialogId: args2.dialogId,
23950
+ ownerId: args2.ownerId,
23951
+ ...args2.spaceId !== void 0 ? { spaceId: args2.spaceId } : {},
23952
+ source: args2.source,
23953
+ registeredAt: (args2.now ?? (() => (/* @__PURE__ */ new Date()).toISOString()))()
23954
+ });
23955
+ });
23956
+ } catch (error) {
23957
+ emit({
23958
+ event: "dialog_owner_registration_failed",
23959
+ dialogId: args2.dialogId,
23960
+ source: args2.source,
23961
+ error: error instanceof Error ? error.message : String(error)
23962
+ });
23963
+ } finally {
23964
+ if (!mutex.isLocked() && locks.get(key) === mutex) locks.delete(key);
23965
+ }
23966
+ }
23967
+
22676
23968
  // packages/cli/client/localDialogRecords.ts
22677
23969
  var localDialogMessageRecordToRuntimeMessage = dialogMessageRecordToAgentRuntimeMessage;
22678
23970
  function buildLocalDialogWritePlan(args2) {
@@ -22689,6 +23981,138 @@ function buildLocalDialogWritePlan(args2) {
22689
23981
  ...args2.titleOverride ? { titleOverride: args2.titleOverride } : {}
22690
23982
  });
22691
23983
  }
23984
+ async function saveCliPendingUserMessages(args2) {
23985
+ const dialogKey = `dialog-${args2.userId}-${args2.dialogId}`;
23986
+ const existing = await args2.store.read(dialogKey, { remote: false });
23987
+ const now = (/* @__PURE__ */ new Date()).toISOString();
23988
+ const ops = [];
23989
+ if (!existing) ops.push({ type: "put", key: dialogKey, value: {
23990
+ id: args2.dialogId,
23991
+ dbKey: dialogKey,
23992
+ type: "dialog",
23993
+ userId: args2.userId,
23994
+ primaryAgentKey: args2.agentKey,
23995
+ cybots: [args2.agentKey],
23996
+ createdAt: now,
23997
+ updatedAt: now
23998
+ } });
23999
+ for (const request3 of args2.requests) {
24000
+ if (!request3.messageId) throw new Error("Pending user message requires a stable messageId");
24001
+ const key = `dialog-${args2.dialogId}-msg-${request3.messageId}`;
24002
+ if (await args2.store.read(key, { remote: false })) continue;
24003
+ ops.push({ type: "put", key, value: {
24004
+ id: request3.messageId,
24005
+ dbKey: key,
24006
+ type: "message",
24007
+ dialogId: args2.dialogId,
24008
+ userId: args2.userId,
24009
+ role: "user",
24010
+ content: request3.text,
24011
+ createdAt: now,
24012
+ metadata: {
24013
+ queueState: "pending",
24014
+ source: request3.source ?? "tui",
24015
+ imagePaths: request3.imagePaths,
24016
+ mediaPaths: request3.mediaPaths
24017
+ }
24018
+ } });
24019
+ }
24020
+ await registerDialogOwner({
24021
+ store: { get: (key) => args2.store.read(key, { remote: false }), put: (key, value) => args2.store.batch([{ type: "put", key, value }]) },
24022
+ dialogId: args2.dialogId,
24023
+ ownerId: args2.userId,
24024
+ source: "cli-pending-user"
24025
+ });
24026
+ if (ops.length) await args2.store.batch(ops);
24027
+ }
24028
+ async function readCliPendingUserMessages(store, dialogId) {
24029
+ const { readDialogMessageRecords } = await import("./cliLocalAgentRecordReader-MHHD4T7R.js");
24030
+ const rows = await readDialogMessageRecords({ store, dialogId });
24031
+ const uncertain = rows.filter(({ record: record2 }) => record2.metadata?.cliBatchPhase === "requested" && !record2.metadata?.cliBatchResolution);
24032
+ const recovery = uncertain.length ? {
24033
+ status: "unknown",
24034
+ messageIds: uncertain.map((row) => row.messageId),
24035
+ // Attribute each unknown batch to its own members via the tail receipt.
24036
+ // `acceptedUserMessageIds` is written by `markCliUserBatchPhase`; older
24037
+ // records carry the same field from the legacy admission path. Records with
24038
+ // neither fall back to every accepted row (never attribute a completed
24039
+ // batch's members to an unknown one when the receipt is available).
24040
+ batches: uncertain.map((row) => {
24041
+ const receiptIds = row.record.metadata?.acceptedUserMessageIds;
24042
+ const memberIds = Array.isArray(receiptIds) && receiptIds.length ? receiptIds : rows.filter((message) => message.record.metadata?.queueState === "accepted").map((message) => message.messageId);
24043
+ const memberSet = new Set(memberIds);
24044
+ return { batchId: row.messageId, messages: rows.filter((message) => memberSet.has(message.messageId)).map((message) => ({ messageId: message.messageId, text: String(message.record.content ?? "") })) };
24045
+ }),
24046
+ message: `Batch provider outcome unknown for ${uncertain.map((row) => row.messageId).join(", ")}; pending history retained; resubmission paused to avoid duplicate billing`
24047
+ } : { status: "ready", messageIds: [] };
24048
+ const pending = rows.filter(({ record: record2 }) => record2.role === "user" && record2.metadata?.queueState === "pending").map(({ record: record2, messageId }) => ({
24049
+ messageId,
24050
+ source: record2.metadata?.source,
24051
+ text: String(record2.content ?? ""),
24052
+ event: { kind: "user", text: String(record2.content ?? "") },
24053
+ imagePaths: record2.metadata?.imagePaths,
24054
+ mediaPaths: record2.metadata?.mediaPaths
24055
+ }));
24056
+ return { pending, recovery };
24057
+ }
24058
+ async function markCliUserBatchPhase(store, dialogId, messageId, phase, messageIds, preparedMessages) {
24059
+ const key = `dialog-${dialogId}-msg-${messageId}`;
24060
+ const record2 = await store.read(key, { remote: false });
24061
+ if (!record2) throw new Error("Missing durable batch receipt");
24062
+ const current = record2.metadata?.cliBatchPhase;
24063
+ if (phase === "requested" && (current === "requested" || current === "completed")) {
24064
+ throw new Error(`Batch ${messageId} already requested; resubmission blocked`);
24065
+ }
24066
+ const ops = [];
24067
+ for (const id of messageIds) {
24068
+ const messageKey = `dialog-${dialogId}-msg-${id}`;
24069
+ const message = await store.read(messageKey, { remote: false });
24070
+ if (!message) throw new Error(`Missing batch user message ${id}`);
24071
+ const prepared = preparedMessages?.find((item) => item.cliMessageId === id);
24072
+ ops.push({ type: "put", key: messageKey, value: {
24073
+ ...message,
24074
+ ...prepared ? { content: prepared.content } : {},
24075
+ metadata: {
24076
+ ...message.metadata,
24077
+ queueState: "accepted",
24078
+ ...id === messageId ? { cliBatchPhase: phase, acceptedUserMessageIds: [...messageIds] } : {}
24079
+ }
24080
+ } });
24081
+ }
24082
+ await store.batch(ops);
24083
+ }
24084
+ async function rollbackCliUserBatchPhase(store, dialogId, batchId, messageIds) {
24085
+ const ops = [];
24086
+ for (const id of messageIds) {
24087
+ const key = `dialog-${dialogId}-msg-${id}`;
24088
+ const record2 = await store.read(key, { remote: false });
24089
+ if (!record2) continue;
24090
+ const metadata = { ...record2.metadata };
24091
+ delete metadata.cliBatchPhase;
24092
+ metadata.queueState = "pending";
24093
+ ops.push({ type: "put", key, value: { ...record2, metadata } });
24094
+ }
24095
+ if (ops.length) await store.batch(ops);
24096
+ }
24097
+ async function buildCliBatchCompletionOp(store, dialogId, batchId) {
24098
+ const key = `dialog-${dialogId}-msg-${batchId}`;
24099
+ const record2 = await store.read(key, { remote: false });
24100
+ if (!record2) throw new Error(`Missing batch user message ${batchId}`);
24101
+ return { type: "put", key, value: {
24102
+ ...record2,
24103
+ metadata: { ...record2.metadata, cliBatchPhase: "completed" }
24104
+ } };
24105
+ }
24106
+ async function resolveCliUnknownBatchWithoutResend(store, dialogId, batchId) {
24107
+ const key = `dialog-${dialogId}-msg-${batchId}`;
24108
+ const record2 = await store.read(key, { remote: false });
24109
+ if (!record2 || record2.metadata?.cliBatchPhase !== "requested") throw new Error(`Unknown requested batch not found: ${batchId}`);
24110
+ if (record2.metadata.cliBatchResolution) return;
24111
+ await store.batch([{ type: "put", key, value: { ...record2, metadata: {
24112
+ ...record2.metadata,
24113
+ cliBatchResolution: { decision: "continue-without-resend", decidedBy: "user", decidedAt: (/* @__PURE__ */ new Date()).toISOString() }
24114
+ } } }]);
24115
+ }
22692
24116
 
22693
24117
  // packages/agent-runtime/noloWorkspaceTools.node.ts
22694
24118
  async function readNoloProcessStream(readable) {
@@ -22804,7 +24228,7 @@ function buildLoadSkillExecutor(args2) {
22804
24228
  const parsed = parseNoloWorkspaceToolArguments(call.arguments);
22805
24229
  const referenceKeys = ["source", "slug", "projectBinding", "relativePath", "dbKey", "id", "skillId"];
22806
24230
  if (referenceKeys.some((key) => Object.hasOwn(parsed, key))) {
22807
- return loadPreciseSkill(args2.cwd, parsed);
24231
+ return loadPreciseSkill(args2.cwd, parsed, args2.agentSkillSource, args2.resolveAgentSkillSource);
22808
24232
  }
22809
24233
  const name = noloStringArg(parsed.name ?? parsed.skillName ?? parsed.skill);
22810
24234
  if (!name) {
@@ -22813,12 +24237,14 @@ function buildLoadSkillExecutor(args2) {
22813
24237
  metadata: { loadSkill: true, resolved: false }
22814
24238
  };
22815
24239
  }
22816
- const { resolveSkillByName } = await import("./skillDiscovery-XKHROSH2.js");
24240
+ const { resolveSkillByName } = await import("./skillDiscovery-B5V23AXP.js");
22817
24241
  const resolved = resolveSkillByName(args2.cwd, name);
22818
24242
  if (!resolved) {
22819
- const { resolveBuiltinSkillByName } = await import("./builtinSkillRegistry-2QETTQPM.js");
24243
+ const { resolveBuiltinSkillByName } = await import("./builtinSkillRegistry-2ECA42FC.js");
22820
24244
  const builtin = resolveBuiltinSkillByName(name);
22821
24245
  if (builtin) {
24246
+ const denied = await rejectDisabledBuiltin(args2.agentSkillSource, builtin.slug, name, args2.resolveAgentSkillSource);
24247
+ if (denied) return denied;
22822
24248
  return {
22823
24249
  content: `Skill "${name}" loaded inline. Follow its instructions.
22824
24250
 
@@ -22858,7 +24284,25 @@ ${body}`,
22858
24284
  }
22859
24285
  };
22860
24286
  }
22861
- async function loadPreciseSkill(cwd, parsed) {
24287
+ async function rejectDisabledBuiltin(source, slug, requestedName, resolveSource) {
24288
+ if (resolveSource) {
24289
+ try {
24290
+ source = await resolveSource();
24291
+ } catch {
24292
+ source = null;
24293
+ }
24294
+ if (!source) return {
24295
+ content: `Skill "${requestedName}" cannot be loaded: current agent policy is unavailable.`,
24296
+ metadata: { loadSkill: true, resolved: false, builtin: true, policyUnavailable: true, slug, name: requestedName, requestedName }
24297
+ };
24298
+ }
24299
+ if (!isAgentSkillDisabled(source, slug)) return null;
24300
+ return {
24301
+ content: `Skill "${requestedName}" \u5DF2\u88AB\u7981\u7528\uFF1A\u8BE5\u80FD\u529B\u5728\u6B64 agent \u4E0A\u4E0D\u53EF\u7528\uFF0C\u65E0\u6CD5\u52A0\u8F7D\u3002`,
24302
+ metadata: { loadSkill: true, resolved: false, builtin: true, disabled: true, slug, name: requestedName, requestedName }
24303
+ };
24304
+ }
24305
+ async function loadPreciseSkill(cwd, parsed, agentSkillSource, resolveAgentSkillSource) {
22862
24306
  const source = parsed.source;
22863
24307
  const has2 = (key) => Object.hasOwn(parsed, key);
22864
24308
  const reject = (reason) => ({
@@ -22875,9 +24319,11 @@ async function loadPreciseSkill(cwd, parsed) {
22875
24319
  if (!nonEmpty(parsed.slug) || has2("projectBinding") || has2("relativePath")) {
22876
24320
  return reject("builtin requires only an exact slug.");
22877
24321
  }
22878
- const { listBuiltinSkills } = await import("./builtinSkillRegistry-2QETTQPM.js");
24322
+ const { listBuiltinSkills } = await import("./builtinSkillRegistry-2ECA42FC.js");
22879
24323
  const builtin = listBuiltinSkills().find((entry) => entry.slug === parsed.slug);
22880
24324
  if (!builtin) return reject("builtin slug not found.");
24325
+ const denied = await rejectDisabledBuiltin(agentSkillSource, builtin.slug, builtin.slug, resolveAgentSkillSource);
24326
+ if (denied) return { ...denied, metadata: { ...denied.metadata, source } };
22881
24327
  return {
22882
24328
  content: `Skill "${builtin.slug}" loaded inline. Follow its instructions.
22883
24329
 
@@ -22899,7 +24345,7 @@ ${builtin.content}`,
22899
24345
  }
22900
24346
  try {
22901
24347
  const { realpathSync, statSync, readFileSync } = await import("node:fs");
22902
- const { resolve: resolve6, relative: relative6, isAbsolute: isAbsolute5, basename: basename3, sep: sep3 } = await import("node:path");
24348
+ const { resolve: resolve6, relative: relative6, isAbsolute: isAbsolute5, basename: basename4, sep: sep3 } = await import("node:path");
22903
24349
  const inside = (root, target) => {
22904
24350
  const rel = relative6(root, target);
22905
24351
  return rel !== "" && rel !== ".." && !rel.startsWith(`..${sep3}`) && !isAbsolute5(rel);
@@ -22908,7 +24354,7 @@ ${builtin.content}`,
22908
24354
  if (!isAbsolute5(parsed.projectBinding) || realpathSync(parsed.projectBinding) !== realCwd) {
22909
24355
  return reject("projectBinding does not match the authorized cwd.");
22910
24356
  }
22911
- if (isAbsolute5(parsed.relativePath) || basename3(parsed.relativePath) !== "SKILL.md") {
24357
+ if (isAbsolute5(parsed.relativePath) || basename4(parsed.relativePath) !== "SKILL.md") {
22912
24358
  return reject("relativePath must reference a project SKILL.md.");
22913
24359
  }
22914
24360
  const skillsRoot = resolve6(realCwd, ".agents", "skills");
@@ -22916,7 +24362,7 @@ ${builtin.content}`,
22916
24362
  if (!inside(skillsRoot, candidate)) return reject("path is outside cwd/.agents/skills/.");
22917
24363
  const realRoot = realpathSync(skillsRoot);
22918
24364
  const realPath = realpathSync(candidate);
22919
- if (realRoot !== skillsRoot || !inside(realRoot, realPath) || basename3(realPath) !== "SKILL.md") {
24365
+ if (realRoot !== skillsRoot || !inside(realRoot, realPath) || basename4(realPath) !== "SKILL.md") {
22920
24366
  return reject("skill path escapes cwd/.agents/skills/ or is not SKILL.md.");
22921
24367
  }
22922
24368
  if (!statSync(realPath).isFile()) return reject("SKILL.md is not a file.");
@@ -22939,7 +24385,7 @@ ${body}`,
22939
24385
  }
22940
24386
  }
22941
24387
  async function formatUnknownSkillMessage(cwd, requestedName) {
22942
- const { discoverSkills } = await import("./skillDiscovery-XKHROSH2.js");
24388
+ const { discoverSkills } = await import("./skillDiscovery-B5V23AXP.js");
22943
24389
  let available = [];
22944
24390
  try {
22945
24391
  available = discoverSkills(cwd).map((s) => s.name);
@@ -23604,12 +25050,16 @@ export {
23604
25050
  createLocalWorkspaceToolExecutors,
23605
25051
  buildToolParametersIndex,
23606
25052
  getAgentRuntimeToolParametersIndex,
25053
+ registerLocalShellLifecycle,
25054
+ getLocalShellLifecycle,
23607
25055
  normalizeLocalToolName,
23608
25056
  isCommandExecutingTool,
23609
25057
  resolveLocalToolPolicy,
23610
25058
  runExecShellGates,
23611
25059
  executeLocalToolWithPolicy,
23612
25060
  LOCAL_TURN_ABORTED_CODE,
25061
+ evaluateTaskExecutionContract,
25062
+ projectContextProvenance,
23613
25063
  hashToolDefinitions,
23614
25064
  LOCAL_AGENT_CONFIG_MISSING_CODE,
23615
25065
  runLocalAgentTurn,
@@ -23621,6 +25071,7 @@ export {
23621
25071
  parseOpenAiCompatibleChatCompletionResponse,
23622
25072
  readOpenAiCompatibleSseCompletion,
23623
25073
  executeOpenAiCompatibleChatCompletion,
25074
+ describeOpenAiCompatibleRequestBudget,
23624
25075
  resolveOpenAiCompatibleProviderConfig,
23625
25076
  dialogMessageRecordToAgentRuntimeMessage,
23626
25077
  buildAgentRuntimeDialogWritePlan,
@@ -23628,8 +25079,15 @@ export {
23628
25079
  parseSyncServersEnv,
23629
25080
  DEFAULT_HYBRID_READ_TIMEOUT_MS,
23630
25081
  createHybridRecordStore,
25082
+ registerDialogOwner,
23631
25083
  localDialogMessageRecordToRuntimeMessage,
23632
25084
  buildLocalDialogWritePlan,
25085
+ saveCliPendingUserMessages,
25086
+ readCliPendingUserMessages,
25087
+ markCliUserBatchPhase,
25088
+ rollbackCliUserBatchPhase,
25089
+ buildCliBatchCompletionOp,
25090
+ resolveCliUnknownBatchWithoutResend,
23633
25091
  buildNoloWorkspaceCliToolExecutors,
23634
25092
  buildLoadSkillExecutor,
23635
25093
  isStableSessionIdentityEnabled,