@markusylisiurunen/tau 0.3.79 → 0.3.80

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 (240) hide show
  1. package/dist/core/agent/agent_runtime.js +68 -76
  2. package/dist/core/agent/agent_runtime.js.map +1 -1
  3. package/dist/core/auth/auth_manager.js +3 -7
  4. package/dist/core/auth/auth_manager.js.map +1 -1
  5. package/dist/core/auth/auth_messages.js +1 -1
  6. package/dist/core/auth/auth_messages.js.map +1 -1
  7. package/dist/core/auth/auth_storage.js +71 -7
  8. package/dist/core/auth/auth_storage.js.map +1 -1
  9. package/dist/core/auth/cli.js +8 -7
  10. package/dist/core/auth/cli.js.map +1 -1
  11. package/dist/core/auth/credential_store.js +88 -186
  12. package/dist/core/auth/credential_store.js.map +1 -1
  13. package/dist/core/auth/index.js +1 -1
  14. package/dist/core/auth/index.js.map +1 -1
  15. package/dist/core/auth/providers/openai_codex.js +51 -229
  16. package/dist/core/auth/providers/openai_codex.js.map +1 -1
  17. package/dist/core/cli.js +1 -2
  18. package/dist/core/cli.js.map +1 -1
  19. package/dist/core/config/content_loader.js +27 -130
  20. package/dist/core/config/content_loader.js.map +1 -1
  21. package/dist/core/config/index.js.map +1 -1
  22. package/dist/core/config/paths.js +0 -1
  23. package/dist/core/config/paths.js.map +1 -1
  24. package/dist/core/config/runtime.js +3 -2
  25. package/dist/core/config/runtime.js.map +1 -1
  26. package/dist/core/config/runtime_config_snapshot.js +23 -9
  27. package/dist/core/config/runtime_config_snapshot.js.map +1 -1
  28. package/dist/core/config/schema.js +13 -69
  29. package/dist/core/config/schema.js.map +1 -1
  30. package/dist/core/config/skills_loader.js +6 -2
  31. package/dist/core/config/skills_loader.js.map +1 -1
  32. package/dist/core/debug.js +1 -16
  33. package/dist/core/debug.js.map +1 -1
  34. package/dist/core/index.js +2 -2
  35. package/dist/core/index.js.map +1 -1
  36. package/dist/core/models/catalog.js +2 -239
  37. package/dist/core/models/catalog.js.map +1 -1
  38. package/dist/core/personas.js +0 -14
  39. package/dist/core/personas.js.map +1 -1
  40. package/dist/core/runtime/agent_model.js +0 -3
  41. package/dist/core/runtime/agent_model.js.map +1 -1
  42. package/dist/core/runtime/chat_runtime.js +16 -13
  43. package/dist/core/runtime/chat_runtime.js.map +1 -1
  44. package/dist/core/runtime/runtime_bootstrap.js +12 -59
  45. package/dist/core/runtime/runtime_bootstrap.js.map +1 -1
  46. package/dist/core/session/auto_compaction_archive.js +1 -1
  47. package/dist/core/session/auto_compaction_archive.js.map +1 -1
  48. package/dist/core/session/compaction.js +79 -506
  49. package/dist/core/session/compaction.js.map +1 -1
  50. package/dist/core/static/tau_docs/config-reference.md +12 -37
  51. package/dist/core/static/tau_docs/configuration.md +3 -16
  52. package/dist/core/static/tau_docs/credentials.md +20 -50
  53. package/dist/core/static/tau_docs/index.md +2 -1
  54. package/dist/core/static/tau_docs/manifest.json +1 -0
  55. package/dist/core/static/tau_docs/models.md +8 -159
  56. package/dist/core/static/tau_docs/openrouter.md +165 -0
  57. package/dist/core/static/tau_docs/ownership-and-scope.md +3 -3
  58. package/dist/core/static/tau_docs/personas.md +9 -41
  59. package/dist/core/static/tau_docs/prompts-and-project-context.md +10 -44
  60. package/dist/core/static/tau_docs/remote-sessions.md +6 -7
  61. package/dist/core/static/tau_docs/security.md +4 -5
  62. package/dist/core/static/tau_docs/session-protocol-methods.md +0 -1
  63. package/dist/core/static/tau_docs/session-protocol.md +11 -9
  64. package/dist/core/static/tau_docs/sessions.md +8 -6
  65. package/dist/core/static/tau_docs/skills.md +8 -32
  66. package/dist/core/static/tau_docs/subagents.md +2 -2
  67. package/dist/core/static/tau_docs/telegram.md +2 -2
  68. package/dist/core/static/tau_docs/tools.md +2 -1
  69. package/dist/core/static/tau_docs/troubleshooting.md +11 -12
  70. package/dist/core/static/tau_docs/tui.md +4 -2
  71. package/dist/core/subagents/agent_supervisor.js +0 -1
  72. package/dist/core/subagents/agent_supervisor.js.map +1 -1
  73. package/dist/core/telegram/adapter.js +1 -1
  74. package/dist/core/telegram/adapter.js.map +1 -1
  75. package/dist/core/tool/cli.js +10 -2
  76. package/dist/core/tool/cli.js.map +1 -1
  77. package/dist/core/tool/openrouter.js +291 -0
  78. package/dist/core/tool/openrouter.js.map +1 -0
  79. package/dist/core/tool/openrouter_decisions.js +97 -0
  80. package/dist/core/tool/openrouter_decisions.js.map +1 -0
  81. package/dist/core/tool/openrouter_media.js +180 -0
  82. package/dist/core/tool/openrouter_media.js.map +1 -0
  83. package/dist/core/tools/tau_docs.js +1 -0
  84. package/dist/core/tools/tau_docs.js.map +1 -1
  85. package/dist/core/utils/model_stream.js +47 -34
  86. package/dist/core/utils/model_stream.js.map +1 -1
  87. package/dist/core/utils/user_metadata.js +2 -27
  88. package/dist/core/utils/user_metadata.js.map +1 -1
  89. package/dist/core/version.js +2 -2
  90. package/dist/core/version.js.map +1 -1
  91. package/dist/diff_tool/app/dist/assets/{ada-C5qYipkI.js → ada-X3Y2mlq2.js} +1 -1
  92. package/dist/diff_tool/app/dist/assets/{ahk-CA9gdOdF.js → ahk-DwADkxyd.js} +1 -1
  93. package/dist/diff_tool/app/dist/assets/{angular-html-CnO8qept.js → angular-html-BbLgbIhq.js} +1 -1
  94. package/dist/diff_tool/app/dist/assets/{angular-ts-DPXUio9Z.js → angular-ts-DXk17TRM.js} +1 -1
  95. package/dist/diff_tool/app/dist/assets/{apl-X8YsymsQ.js → apl-BsvksCXR.js} +1 -1
  96. package/dist/diff_tool/app/dist/assets/{astro-BQ9k8AQ0.js → astro-DH775E4V.js} +1 -1
  97. package/dist/diff_tool/app/dist/assets/beancount-C2IN4Vk8.js +1 -0
  98. package/dist/diff_tool/app/dist/assets/{blade-DZ1LYIzB.js → blade-Ck99Rm59.js} +1 -1
  99. package/dist/diff_tool/app/dist/assets/{c-VLTwNh7z.js → c-DJpHqu5J.js} +1 -1
  100. package/dist/diff_tool/app/dist/assets/{chapel-5s-sGN8D.js → chapel-Owff0Csy.js} +1 -1
  101. package/dist/diff_tool/app/dist/assets/{cobol-C9RzwrVu.js → cobol-Cv2gZuhO.js} +1 -1
  102. package/dist/diff_tool/app/dist/assets/{coffee-BHvjZ343.js → coffee-Bji0YCf_.js} +1 -1
  103. package/dist/diff_tool/app/dist/assets/coq-CGaSfhwR.js +1 -0
  104. package/dist/diff_tool/app/dist/assets/{cpp-CBTVXbBp.js → cpp-DTjA_yFF.js} +1 -1
  105. package/dist/diff_tool/app/dist/assets/{crystal-DQJua4y4.js → crystal-Dd00NOeb.js} +1 -1
  106. package/dist/diff_tool/app/dist/assets/csharp-dmMxLVLW.js +1 -0
  107. package/dist/diff_tool/app/dist/assets/css-D44TN1V6.js +1 -0
  108. package/dist/diff_tool/app/dist/assets/dart-CHZY9Xhz.js +1 -0
  109. package/dist/diff_tool/app/dist/assets/dotenv-CuH5my32.js +1 -0
  110. package/dist/diff_tool/app/dist/assets/{edge-BmdmXGH4.js → edge-CVmGSi2m.js} +1 -1
  111. package/dist/diff_tool/app/dist/assets/{elixir-oiOLs98L.js → elixir-B8Mq7no2.js} +1 -1
  112. package/dist/diff_tool/app/dist/assets/{elm-BgYuiU4E.js → elm-CXS0I3Z2.js} +1 -1
  113. package/dist/diff_tool/app/dist/assets/emacs-lisp-DR2vtWaj.js +1 -0
  114. package/dist/diff_tool/app/dist/assets/{erb-CvFrXpyY.js → erb-B9oDKaUo.js} +1 -1
  115. package/dist/diff_tool/app/dist/assets/{git-rebase-D3Zo1BLH.js → git-rebase-DT-YFebH.js} +1 -1
  116. package/dist/diff_tool/app/dist/assets/{glimmer-js-B4-8E3lJ.js → glimmer-js-s0DXlNjq.js} +1 -1
  117. package/dist/diff_tool/app/dist/assets/{glimmer-ts-CtKkD0t_.js → glimmer-ts-QgEB5S2z.js} +1 -1
  118. package/dist/diff_tool/app/dist/assets/{glsl-BAqforx_.js → glsl-NA0icwbY.js} +1 -1
  119. package/dist/diff_tool/app/dist/assets/gn-CAXYAgV-.js +1 -0
  120. package/dist/diff_tool/app/dist/assets/{graphql-DuE1RMyL.js → graphql-0_dOlfV0.js} +1 -1
  121. package/dist/diff_tool/app/dist/assets/{hack-CHBcXjz1.js → hack-D-mr7l1l.js} +1 -1
  122. package/dist/diff_tool/app/dist/assets/{haml-DAs2z3tD.js → haml-B0-oQmz7.js} +1 -1
  123. package/dist/diff_tool/app/dist/assets/handlebars-Yq9bXpWN.js +1 -0
  124. package/dist/diff_tool/app/dist/assets/{html-D5co0BLD.js → html-B8bMmdRK.js} +1 -1
  125. package/dist/diff_tool/app/dist/assets/{html-derivative-BZaWEmAT.js → html-derivative-BpxSlQb_.js} +1 -1
  126. package/dist/diff_tool/app/dist/assets/{http-Cii5MjJE.js → http-hS_53mxt.js} +1 -1
  127. package/dist/diff_tool/app/dist/assets/{hurl-Bur-XdJO.js → hurl-B2ST1kwl.js} +1 -1
  128. package/dist/diff_tool/app/dist/assets/index-CBT_sNIz.js +340 -0
  129. package/dist/diff_tool/app/dist/assets/{java-DN39wI4w.js → java-D4mj0lZJ.js} +1 -1
  130. package/dist/diff_tool/app/dist/assets/{javascript-VUReF1H4.js → javascript-wC7rrKiV.js} +1 -1
  131. package/dist/diff_tool/app/dist/assets/{jinja-BcrfV1JB.js → jinja-DbQ3o87F.js} +1 -1
  132. package/dist/diff_tool/app/dist/assets/{jison-Bez5Mtmy.js → jison-CrE9FdvP.js} +1 -1
  133. package/dist/diff_tool/app/dist/assets/{json-ChV8m0Le.js → json-xjicDyWN.js} +1 -1
  134. package/dist/diff_tool/app/dist/assets/{jsx-D0SXNtM0.js → jsx-m28lXojQ.js} +1 -1
  135. package/dist/diff_tool/app/dist/assets/{julia-DvhpcBwG.js → julia-19xkOmop.js} +1 -1
  136. package/dist/diff_tool/app/dist/assets/{just-BGwe8S6x.js → just-Cf7OorTr.js} +1 -1
  137. package/dist/diff_tool/app/dist/assets/latex-DDb7GQOy.js +1 -0
  138. package/dist/diff_tool/app/dist/assets/{liquid-UdnqDjKz.js → liquid-DHOuQ0rf.js} +1 -1
  139. package/dist/diff_tool/app/dist/assets/{lua-CjDHeHEr.js → lua-BGyIvonw.js} +1 -1
  140. package/dist/diff_tool/app/dist/assets/luau-VGWVLoqX.js +1 -0
  141. package/dist/diff_tool/app/dist/assets/marko-DB5OqI1C.js +1 -0
  142. package/dist/diff_tool/app/dist/assets/{mdc-BOLj6Oyq.js → mdc-DTyNMupO.js} +1 -1
  143. package/dist/diff_tool/app/dist/assets/monokai-BscAjiQZ.js +1 -0
  144. package/dist/diff_tool/app/dist/assets/moonbit-D9W784E_.js +1 -0
  145. package/dist/diff_tool/app/dist/assets/nextflow-CSgcYZcx.js +1 -0
  146. package/dist/diff_tool/app/dist/assets/{nextflow-groovy-DJMQeKeT.js → nextflow-groovy-D7IRt6Pp.js} +1 -1
  147. package/dist/diff_tool/app/dist/assets/{nginx-5r5xPc2T.js → nginx-DfF0fMYD.js} +1 -1
  148. package/dist/diff_tool/app/dist/assets/{nim-9exKQYMz.js → nim-BbwsXuZD.js} +1 -1
  149. package/dist/diff_tool/app/dist/assets/nushell--0fT3PI1.js +1 -0
  150. package/dist/diff_tool/app/dist/assets/one-dark-pro-C6YyR8OD.js +1 -0
  151. package/dist/diff_tool/app/dist/assets/{org-B2fpAlbt.js → org-BkOBBl1f.js} +1 -1
  152. package/dist/diff_tool/app/dist/assets/{perl-DV2fcwyY.js → perl-BTXTOl4K.js} +1 -1
  153. package/dist/diff_tool/app/dist/assets/php-DIMVQWiA.js +1 -0
  154. package/dist/diff_tool/app/dist/assets/{powershell-CCSX75yM.js → powershell-QtSnmXcu.js} +1 -1
  155. package/dist/diff_tool/app/dist/assets/{pug-DuKcQxFj.js → pug-B_nrb4dJ.js} +1 -1
  156. package/dist/diff_tool/app/dist/assets/{qml-C16b4SYx.js → qml-1PXi8G8Z.js} +1 -1
  157. package/dist/diff_tool/app/dist/assets/{r-B1ZldnJf.js → r-CJrAkc2Z.js} +1 -1
  158. package/dist/diff_tool/app/dist/assets/{razor-TYmslaAB.js → razor-DgPUftNd.js} +1 -1
  159. package/dist/diff_tool/app/dist/assets/red-CYlAq2b0.js +1 -0
  160. package/dist/diff_tool/app/dist/assets/{regexp-dWCffmDD.js → regexp-C2hS8XvL.js} +1 -1
  161. package/dist/diff_tool/app/dist/assets/{rst-DEkLr2YR.js → rst-CDouhKnc.js} +1 -1
  162. package/dist/diff_tool/app/dist/assets/{ruby-BJnJzr4u.js → ruby-CAjKW1uO.js} +1 -1
  163. package/dist/diff_tool/app/dist/assets/rust-DpiIf5JO.js +1 -0
  164. package/dist/diff_tool/app/dist/assets/{sas-DeRsDbUE.js → sas-qAHkHLc1.js} +1 -1
  165. package/dist/diff_tool/app/dist/assets/{scss-SGV_CwKm.js → scss-Bfx2Ydid.js} +1 -1
  166. package/dist/diff_tool/app/dist/assets/{shellscript-BI7khmmR.js → shellscript-Bu76R8y2.js} +1 -1
  167. package/dist/diff_tool/app/dist/assets/{shellsession-Nupw_Vvw.js → shellsession-BU8BL0oL.js} +1 -1
  168. package/dist/diff_tool/app/dist/assets/solarized-dark-b97czJOk.js +1 -0
  169. package/dist/diff_tool/app/dist/assets/solarized-light-DFPxpIYQ.js +1 -0
  170. package/dist/diff_tool/app/dist/assets/{soy-B96cBzxv.js → soy-FRl8ozP-.js} +1 -1
  171. package/dist/diff_tool/app/dist/assets/sql-BacQgsEs.js +1 -0
  172. package/dist/diff_tool/app/dist/assets/{stata-Dnq48cE6.js → stata-B9kkG-ET.js} +1 -1
  173. package/dist/diff_tool/app/dist/assets/{surrealql-D6lw4TiD.js → surrealql-qvETr7iY.js} +1 -1
  174. package/dist/diff_tool/app/dist/assets/{svelte-ChVoq9tB.js → svelte-B0z1MgHc.js} +1 -1
  175. package/dist/diff_tool/app/dist/assets/{templ-Dh1EXuHD.js → templ-DTnPshKb.js} +1 -1
  176. package/dist/diff_tool/app/dist/assets/{tex-KTYusNFe.js → tex-Cosab9_H.js} +1 -1
  177. package/dist/diff_tool/app/dist/assets/{ts-tags-DC2RU1_R.js → ts-tags-DNYvLFUH.js} +1 -1
  178. package/dist/diff_tool/app/dist/assets/{tsx-DtIM39Jy.js → tsx-Dstq6j1a.js} +1 -1
  179. package/dist/diff_tool/app/dist/assets/twig-BzqZdwaX.js +1 -0
  180. package/dist/diff_tool/app/dist/assets/{typescript-BGAE5iuw.js → typescript-YOQ_oE25.js} +1 -1
  181. package/dist/diff_tool/app/dist/assets/typespec-BGhR2SqG.js +1 -0
  182. package/dist/diff_tool/app/dist/assets/{typst-nWoGZ22y.js → typst-DTffwfpc.js} +1 -1
  183. package/dist/diff_tool/app/dist/assets/{vue-iyHf-l4z.js → vue-D4OMj_C5.js} +1 -1
  184. package/dist/diff_tool/app/dist/assets/{vue-html-prNdIjLu.js → vue-html-kT4WrHGK.js} +1 -1
  185. package/dist/diff_tool/app/dist/assets/{vue-vine-KSSlnVeg.js → vue-vine-CxeapeMO.js} +1 -1
  186. package/dist/diff_tool/app/dist/assets/{xml-DdCC83Xd.js → xml-DWVpuurg.js} +1 -1
  187. package/dist/diff_tool/app/dist/assets/{xsl-DSqZxsez.js → xsl-C0ciup5a.js} +1 -1
  188. package/dist/diff_tool/app/dist/assets/{yaml-CZcBUWvf.js → yaml-98Fvyfpc.js} +1 -1
  189. package/dist/diff_tool/app/dist/index.html +1 -1
  190. package/dist/execution/execution_environment.js.map +1 -1
  191. package/dist/execution/fly_sprite_execution_environment.js +5 -10
  192. package/dist/execution/fly_sprite_execution_environment.js.map +1 -1
  193. package/dist/execution/tool_backend_execution_environment.js +0 -2
  194. package/dist/execution/tool_backend_execution_environment.js.map +1 -1
  195. package/dist/host/execution_runtime.js +5 -4
  196. package/dist/host/execution_runtime.js.map +1 -1
  197. package/dist/host/hosted_ephemeral_agent_session.js +0 -2
  198. package/dist/host/hosted_ephemeral_agent_session.js.map +1 -1
  199. package/dist/host/local_session_host.js +15 -31
  200. package/dist/host/local_session_host.js.map +1 -1
  201. package/dist/main.js +14 -29
  202. package/dist/main.js.map +1 -1
  203. package/dist/protocol/session_protocol.d.ts +3 -6
  204. package/dist/protocol/session_protocol.js +3 -6
  205. package/dist/protocol/session_protocol.js.map +1 -1
  206. package/dist/sdk/local_client.js +3 -3
  207. package/dist/sdk/local_client.js.map +1 -1
  208. package/dist/store/session_snapshot_migrations.js +28 -1
  209. package/dist/store/session_snapshot_migrations.js.map +1 -1
  210. package/dist/tui/session_chat_app.js +2 -4
  211. package/dist/tui/session_chat_app.js.map +1 -1
  212. package/dist/tui/session_chat_controller.js +10 -1
  213. package/dist/tui/session_chat_controller.js.map +1 -1
  214. package/package.json +8 -8
  215. package/dist/diff_tool/app/dist/assets/beancount-D-usSTwE.js +0 -1
  216. package/dist/diff_tool/app/dist/assets/coq-DuBRPxYg.js +0 -1
  217. package/dist/diff_tool/app/dist/assets/csharp-oqKa8noW.js +0 -1
  218. package/dist/diff_tool/app/dist/assets/css-BQflXFb6.js +0 -1
  219. package/dist/diff_tool/app/dist/assets/dart-CnvKMtbv.js +0 -1
  220. package/dist/diff_tool/app/dist/assets/dotenv-_5a1GRtc.js +0 -1
  221. package/dist/diff_tool/app/dist/assets/emacs-lisp-c_oH4hRZ.js +0 -1
  222. package/dist/diff_tool/app/dist/assets/gn-ilITqXS6.js +0 -1
  223. package/dist/diff_tool/app/dist/assets/handlebars-CfAla6QK.js +0 -1
  224. package/dist/diff_tool/app/dist/assets/index-CZUi_I-p.js +0 -340
  225. package/dist/diff_tool/app/dist/assets/latex-pWSag83P.js +0 -1
  226. package/dist/diff_tool/app/dist/assets/luau-C2pBCSgt.js +0 -1
  227. package/dist/diff_tool/app/dist/assets/marko-yLX6iW68.js +0 -1
  228. package/dist/diff_tool/app/dist/assets/monokai-B_bV9PQ9.js +0 -1
  229. package/dist/diff_tool/app/dist/assets/moonbit-DgjMoLWr.js +0 -1
  230. package/dist/diff_tool/app/dist/assets/nextflow-BJtWHP5T.js +0 -1
  231. package/dist/diff_tool/app/dist/assets/nushell-DHYfO66s.js +0 -1
  232. package/dist/diff_tool/app/dist/assets/one-dark-pro-CLwyXe_n.js +0 -1
  233. package/dist/diff_tool/app/dist/assets/php-D-AthX_E.js +0 -1
  234. package/dist/diff_tool/app/dist/assets/red-DX1BQOs8.js +0 -1
  235. package/dist/diff_tool/app/dist/assets/rust-Cfkwpbl8.js +0 -1
  236. package/dist/diff_tool/app/dist/assets/solarized-dark-UdsmMDCL.js +0 -1
  237. package/dist/diff_tool/app/dist/assets/solarized-light-C1IUL_tW.js +0 -1
  238. package/dist/diff_tool/app/dist/assets/sql-CsIdKfaO.js +0 -1
  239. package/dist/diff_tool/app/dist/assets/twig-C6D_TA3n.js +0 -1
  240. package/dist/diff_tool/app/dist/assets/typespec-BRdr0IET.js +0 -1
@@ -1,465 +1,97 @@
1
- import { Buffer } from "node:buffer";
2
1
  import { isSystemCompactionContinuation } from "../../protocol/system_message.js";
3
- import { buildCompactionUserMessage, formatHistoryForCompaction, truncateToolRecoveryResults, } from "../utils/compact.js";
2
+ import { buildCompactionUserMessage } from "../utils/compact.js";
4
3
  import { extractAssistantText } from "../utils/messages.js";
5
- import { bytesToTokens, estimateMessageTokens } from "../utils/token.js";
6
- import { truncateForTokens } from "../utils/truncate.js";
7
- import { formatTauUserText, getSummaryCompactionMetadataFromMessage, hasAutoCompactionContinuationMetadata, hasToolRecoveryMetadata, stripTauUserMetadata, } from "../utils/user_metadata.js";
8
- const PRESERVED_USER_MESSAGE_MAX_TOKENS = 20_000;
9
- const RETAINED_TOOL_RESULT_MAX_TOKENS = 8_192;
10
- const PRESERVED_USER_MESSAGE_IDS_OPEN_TAG = "<preserved-user-message-ids>";
11
- const PRESERVED_USER_MESSAGE_IDS_CLOSE_TAG = "</preserved-user-message-ids>";
12
- export const COMPACTION_SUMMARIZATION_SYSTEM_PROMPT = "You are a context compaction assistant. Your output will replace the conversation history for another assistant. Do not continue the conversation. Do not answer any conversation questions. Only output a structured handoff summary followed by the required preserved user message id block.";
13
- const COMPACTION_SUMMARIZATION_PROMPT = `The messages above are a conversation to compact. Create an information-dense context checkpoint summary that will replace the full conversation history. Another assistant should be able to continue the session from this summary as if the original conversation were still available.
4
+ import { estimateMessageTokens } from "../utils/token.js";
5
+ import { formatTauUserText, getSummaryCompactionMetadataFromMessage, hasAutoCompactionContinuationMetadata, hasToolRecoveryMetadata, } from "../utils/user_metadata.js";
6
+ import { AUTO_COMPACTION_ARCHIVE_DOCUMENTATION, } from "./auto_compaction_archive.js";
7
+ const COMPACTION_SUMMARIZATION_PROMPT = `You are pausing your current work to compact your own conversation and free space in your context window. The conversation you can see now will be replaced with the compaction summary you write. After that, you will continue as the same agent in the same session, working on the same task, but without the full earlier conversation available.
14
8
 
15
- If a [System prompt] block is present, use it as context for interpreting the conversation. Do not summarize it as part of the conversation history.
9
+ Write a compaction summary of the conversation: what the user asked for, what has been done and learned, and what remains to be done. It should also give you the handoff you will need to resume that work as smoothly as possible. Your next continuation should be able to understand what the user wants, where the work stands, and what to do next from the compaction summary alone. Think through what you would otherwise forget: important constraints, evidence, decisions and their rationale, unresolved questions, and unfinished work. Preserve what will let you continue without reconstructing the conversation, repeating completed work, or asking the user to explain it again. Recent messages may also remain, but treat them as additional detail rather than something your compaction summary depends on. Some overlap is expected.
16
10
 
17
- Use this structure as a strong default, not a rigid form. Preserve the same continuity-critical concerns, but reorganize, combine, omit, or add sections when another structure would produce a clearer handoff. Project-specific sections and concise prose are allowed. The <preserved-user-message-ids> block remains required and must appear exactly once at the end.
11
+ Preserve continuity-critical information in compact form:
12
+ - The current objective, still-relevant original requests, and user constraints, preferences, and corrections.
13
+ - Confirmed progress, current work, blockers, and the next concrete actions.
14
+ - Decisions and their useful rationale, including rejected approaches when they matter for continuation.
15
+ - Evidence needed to resume: exact paths, identifiers, commands, important errors, and verification status.
16
+ - Uncertainties, unverified assumptions, pending validation, and the difference between attempted work and confirmed outcomes.
17
+ - For unfinished tool work, the request being pursued, results already received, and what remains to interpret or do. Do not repeat completed tool calls just because the earlier exchange is no longer visible.
18
18
 
19
- ## Goal
20
- [What the user is currently trying to accomplish. If the goal changed, briefly note the shift.]
19
+ Incorporate still-relevant information from any previous compaction summary. Remove information that is clearly obsolete or superseded. Collapse tangents and repetition unless they affect the work. When a detail may matter later, preserve it concisely rather than omitting it solely for brevity.
21
20
 
22
- ## Constraints & Preferences
23
- - [Constraints, preferences, or requirements from the user]
24
- - [Use "(none)" when nothing explicit exists]
21
+ Choose the structure that best supports your continuation. Goal, Constraints, Progress, Decisions, Next actions, and Critical context can be useful headings, but are not a required form. Record actionable conclusions and rationale, not a transcript of internal thinking. The base system instructions remain available separately; do not spend the compaction summary reproducing them.
25
22
 
26
- ## Progress
27
- ### Done
28
- - [x] [Completed tasks and confirmed outcomes]
23
+ Do not include large verbatim chunks from user messages, pasted documents, or tool output. Summarize the relevant requirements and findings.
29
24
 
30
- ### In Progress
31
- - [ ] [Current work in progress]
25
+ Output only the compaction summary.
32
26
 
33
- ### Blocked
34
- - [Open blockers, or "(none)"]
35
-
36
- ## Key Decisions
37
- - **[Decision]**: [Brief rationale]
38
-
39
- ## Next Steps
40
- 1. [Ordered next action]
41
-
42
- ## Critical Context
43
- - [Concrete details needed to resume: file paths, function names, commands, errors, test/build status, assumptions]
44
-
45
- <preserved-user-message-ids>
46
- [JSON array of history entry id strings for user messages to copy verbatim into the compaction summary. Use [] when no candidate should be copied verbatim.]
47
- </preserved-user-message-ids>
48
-
49
- Rules:
50
- - Preserve as much continuity-critical context as possible while removing tokens.
51
- - Optimize for continuing the work, not retrospectively describing the conversation.
52
- - The Goal section must preserve the user's current objective and any still-relevant original request, even if it appears early in the conversation.
53
- - Keep each section information-dense and focused; do not omit useful details solely for brevity.
54
- - When unsure whether a detail may matter later, preserve it in compact form.
55
- - Preserve the current state of the work: files touched, commands run, test/build status, known failures, unverified assumptions, and pending validation.
56
- - Distinguish attempted work from confirmed outcomes.
57
- - If goals evolved over time, capture the current goal and briefly note the change.
58
- - Collapse tangents, retries, and pleasantries unless they materially affect decisions, blockers, or next steps.
59
- - Select preserved user message ids only from <user-message-candidates>. The candidates with source "conversation" are identified inline in <conversation> by matching [User id="..."] markers. Select user messages whose exact wording is likely needed to continue, such as standing goals, constraints, corrections, explicit instructions, and recent actionable requests. Omit resolved, repetitive, superseded, or conversational messages. Keep the selected messages under roughly 20,000 tokens total.
60
- - Preserve exact file paths, function names, commands, and error messages.
61
- - Treat the suggested headings as a checklist, not a form to fill mechanically. Add project-specific sections, combine or omit empty subsections, and use prose or tables when they communicate the handoff more clearly.`;
62
- const COMPACTION_UPDATE_SUMMARIZATION_PROMPT = `The messages above are new conversation messages to incorporate into the existing summary in <previous-summary> tags. The updated summary will replace the prior summary plus these new messages as the session's continuity context.
63
-
64
- If a [System prompt] block is present, use it as context for interpreting the new conversation messages. Do not summarize it as part of the conversation history.
65
-
66
- Update the existing structured summary with these rules:
67
- - Preserve all still-relevant information from the previous summary.
68
- - Add new progress, decisions, and context from the new messages.
69
- - Move items from In Progress to Done when completed.
70
- - Update Next Steps based on the current state.
71
- - Remove only information that is clearly obsolete, superseded, or irrelevant to continuing the session.
72
-
73
- Use the previous summary's structure when it remains clear, but reorganize it when the conversation has changed enough that another structure would produce a better handoff. Preserve the same continuity-critical concerns. You may combine, omit, or add sections, use project-specific headings, and mix concise prose with lists. The <preserved-user-message-ids> block remains required and must appear exactly once at the end.
74
-
75
- ## Goal
76
- [Preserve and extend goals as needed. If the goal shifted, reflect the latest goal and note the change briefly.]
77
-
78
- ## Constraints & Preferences
79
- - [Preserve and extend constraints]
80
-
81
- ## Progress
82
- ### Done
83
- - [x] [Previously done and newly completed]
84
-
85
- ### In Progress
86
- - [ ] [Current work]
87
-
88
- ### Blocked
89
- - [Current blockers, or "(none)"]
90
-
91
- ## Key Decisions
92
- - **[Decision]**: [Brief rationale]
93
-
94
- ## Next Steps
95
- 1. [Updated ordered actions]
96
-
97
- ## Critical Context
98
- - [Concrete details needed to resume: file paths, function names, commands, errors, test/build status, assumptions]
99
-
100
- <preserved-user-message-ids>
101
- [JSON array of history entry id strings for user messages to copy verbatim into the compaction summary. Use [] when no candidate should be copied verbatim.]
102
- </preserved-user-message-ids>
103
-
104
- Rules:
105
- - Preserve as much continuity-critical context as possible while removing tokens.
106
- - Optimize for continuing the work, not retrospectively describing the conversation.
107
- - The Goal section must preserve the user's current objective and any still-relevant original request, even if it appears early in the conversation.
108
- - Keep each section information-dense and focused; do not omit useful details solely for brevity.
109
- - When unsure whether a detail may matter later, preserve it in compact form.
110
- - Preserve the current state of the work: files touched, commands run, test/build status, known failures, unverified assumptions, and pending validation.
111
- - Distinguish attempted work from confirmed outcomes.
112
- - If goals evolved over time, capture the current goal and briefly note the change.
113
- - Collapse tangents, retries, and pleasantries unless they materially affect decisions, blockers, or next steps.
114
- - Select preserved user message ids only from <user-message-candidates>. The candidates with source "conversation" are identified inline in <conversation> by matching [User id="..."] markers. Select user messages whose exact wording is likely needed to continue, such as standing goals, constraints, corrections, explicit instructions, and recent actionable requests. Omit resolved, repetitive, superseded, or conversational messages. Keep the selected messages under roughly 20,000 tokens total.
115
- - Preserve exact file paths, function names, commands, and error messages.
116
- - Treat the suggested headings as a checklist, not a form to fill mechanically. Add project-specific sections, combine or omit empty subsections, and use prose or tables when they communicate the handoff more clearly.`;
117
- export function prepareSessionCompaction(entries, options) {
118
- const latestCompaction = findLatestCompactionEntry(entries);
119
- const entriesToSummarize = entries
120
- .slice(latestCompaction.index + 1)
121
- .filter((entry) => !isCompactionContinuation(entry.message));
122
- const messagesToSummarize = entriesToSummarize.map((entry) => entry.message);
123
- const historyEntryIds = buildHistoryEntryIdMap(entriesToSummarize);
124
- const formattedConversation = formatHistoryForCompaction(messagesToSummarize, {
125
- historyEntryIds,
126
- });
127
- if (!formattedConversation) {
27
+ Do not answer the latest request, perform more work, or call tools.`;
28
+ export function prepareSessionCompaction(entries) {
29
+ const latestCompactionIndex = findLatestCompactionIndex(entries);
30
+ const activeEntries = entries.filter((entry) => !isCompactionContinuation(entry.message));
31
+ if (!entries
32
+ .slice(latestCompactionIndex + 1)
33
+ .some((entry) => !isCompactionContinuation(entry.message))) {
128
34
  return undefined;
129
35
  }
130
- const formattedHistory = formatHistoryForCompaction(messagesToSummarize, {
131
- systemPrompt: options.systemPrompt,
132
- historyEntryIds,
133
- });
134
36
  return {
135
- previousSummary: latestCompaction.summary,
136
- messagesToSummarize,
137
- formattedHistory,
138
- userMessageCandidates: collectUserMessageCandidates(entriesToSummarize, latestCompaction.preservedUserMessages),
37
+ messagesToSummarize: activeEntries.map((entry) => entry.message),
139
38
  };
140
39
  }
141
- export function buildSessionCompactionPrompt(args) {
142
- const { preparation, guidance } = args;
143
- let summaryPrompt = `<conversation>\n${preparation.formattedHistory}\n</conversation>\n\n`;
144
- if (preparation.previousSummary?.trim()) {
145
- summaryPrompt += `<previous-summary>\n${preparation.previousSummary.trim()}\n</previous-summary>\n\n`;
146
- }
147
- if (preparation.userMessageCandidates.length > 0) {
148
- summaryPrompt += `<user-message-candidates>\n${formatUserMessageCandidates(preparation.userMessageCandidates)}\n</user-message-candidates>\n\n`;
149
- }
150
- summaryPrompt += preparation.previousSummary
151
- ? COMPACTION_UPDATE_SUMMARIZATION_PROMPT
40
+ export function buildSessionCompactionPrompt(args = {}) {
41
+ const guidance = args.guidance?.trim();
42
+ return guidance
43
+ ? `${COMPACTION_SUMMARIZATION_PROMPT}\n\nAdditional compaction summary focus: ${guidance}`
152
44
  : COMPACTION_SUMMARIZATION_PROMPT;
153
- const guidanceBlock = guidance?.trim();
154
- if (guidanceBlock) {
155
- summaryPrompt += `\n\nAdditional focus: ${guidanceBlock}`;
156
- }
157
- return summaryPrompt;
158
45
  }
159
46
  export function buildSessionCompactionMessage(args) {
160
47
  const lastAssistantMessage = args.mode === "with-last-assistant"
161
48
  ? extractLastAssistantMessage(args.messagesToSummarize)
162
49
  : undefined;
163
- const summary = buildCompactionSummary({
164
- summary: args.summary,
165
- preservedUserMessages: args.preservedUserMessages,
166
- });
167
50
  return {
168
51
  compactionMessage: buildCompactionUserMessage({
169
- summary,
52
+ summary: args.summary,
170
53
  lastAssistantMessage,
171
54
  }),
172
55
  includedLastAssistant: Boolean(lastAssistantMessage),
173
56
  };
174
57
  }
175
- export function buildCompactionSummary(args) {
176
- const summary = args.summary.trim();
177
- if (args.preservedUserMessages.length === 0) {
178
- return summary;
179
- }
180
- const preserved = args.preservedUserMessages
181
- .map((message) => `<user-message id="${escapeXmlAttribute(message.id)}">\n${message.text}\n</user-message>`)
182
- .join("\n\n");
183
- return `${summary}\n\n## Preserved User Messages\nUse these original user messages as verbatim continuity anchors. Treat them as preserved user intent selected from the compacted history by history entry id.\n\n<preserved-user-messages>\n${preserved}\n</preserved-user-messages>`;
184
- }
185
- export function parseCompactionSummaryResponse(args) {
186
- const { summary, selectedIds } = extractSelectedUserMessageIds(args.response, {
187
- requireSelectionBlock: args.userMessageCandidates.length > 0,
188
- });
189
- if (!summary) {
190
- throw new Error("compaction summary response did not include a summary");
191
- }
192
- const candidatesById = new Map(args.userMessageCandidates.map((message) => [message.id, message]));
193
- const seenIds = new Set();
194
- const selectedCandidates = [];
195
- for (const id of selectedIds) {
196
- const candidate = candidatesById.get(id);
197
- if (!candidate) {
198
- continue;
199
- }
200
- if (seenIds.has(id)) {
201
- throw new Error(`compaction summary selected duplicate preserved user message id '${id}'`);
202
- }
203
- seenIds.add(id);
204
- selectedCandidates.push(candidate);
205
- }
206
- return {
207
- summary,
208
- preservedUserMessages: fitPreservedUserMessages(selectedCandidates),
209
- };
210
- }
211
- function extractSelectedUserMessageIds(response, options) {
212
- const text = response.trim();
213
- const start = text.indexOf(PRESERVED_USER_MESSAGE_IDS_OPEN_TAG);
214
- if (start < 0) {
215
- if (options.requireSelectionBlock) {
216
- throw new Error("compaction summary response did not include preserved user message ids");
217
- }
218
- return { summary: text, selectedIds: [] };
219
- }
220
- const contentStart = start + PRESERVED_USER_MESSAGE_IDS_OPEN_TAG.length;
221
- const end = text.indexOf(PRESERVED_USER_MESSAGE_IDS_CLOSE_TAG, contentStart);
222
- if (end < 0) {
223
- throw new Error("compaction summary response did not close preserved user message ids");
224
- }
225
- if (text.indexOf(PRESERVED_USER_MESSAGE_IDS_OPEN_TAG, contentStart) >= 0) {
226
- throw new Error("compaction summary response included multiple preserved user message id blocks");
227
- }
228
- const before = text.slice(0, start).trimEnd();
229
- const after = text.slice(end + PRESERVED_USER_MESSAGE_IDS_CLOSE_TAG.length).trimStart();
230
- const summary = [before, after].filter(Boolean).join("\n\n").trim();
231
- let parsed;
232
- try {
233
- parsed = JSON.parse(text.slice(contentStart, end).trim());
234
- }
235
- catch (error) {
236
- throw new Error(`invalid preserved user message id selection: ${error.message}`);
237
- }
238
- if (!Array.isArray(parsed) || parsed.some((id) => typeof id !== "string")) {
239
- throw new Error("invalid preserved user message id selection: expected a JSON string array");
240
- }
241
- return { summary, selectedIds: parsed };
242
- }
243
- function fitPreservedUserMessages(candidates) {
244
- const tokenCounts = candidates.map((candidate) => estimateTextTokens(candidate.text));
245
- const totalTokens = tokenCounts.reduce((total, tokens) => total + tokens, 0);
246
- if (totalTokens <= PRESERVED_USER_MESSAGE_MAX_TOKENS) {
247
- return candidates.map((candidate) => ({ id: candidate.id, text: candidate.text }));
248
- }
249
- const targets = tokenCounts.map((tokens) => Math.max(1, Math.floor((tokens / totalTokens) * PRESERVED_USER_MESSAGE_MAX_TOKENS)));
250
- let targetTotal = targets.reduce((total, tokens) => total + tokens, 0);
251
- while (targetTotal > PRESERVED_USER_MESSAGE_MAX_TOKENS) {
252
- let largestIndex = 0;
253
- for (let index = 1; index < targets.length; index += 1) {
254
- if (targets[index] > targets[largestIndex]) {
255
- largestIndex = index;
256
- }
257
- }
258
- if (targets[largestIndex] <= 1) {
259
- break;
260
- }
261
- targets[largestIndex] = targets[largestIndex] - 1;
262
- targetTotal -= 1;
263
- }
264
- return candidates.map((candidate, index) => {
265
- const maxTokens = targets[index];
266
- return {
267
- id: candidate.id,
268
- text: truncateForTokens(candidate.text, { maxTokens, strategy: "middle" }).content,
269
- };
270
- });
271
- }
272
- function buildHistoryEntryIdMap(entries) {
273
- return new Map(entries.map((entry) => [entry.message, entry.id]));
274
- }
275
- function isAutoCompactionArchiveEntry(entry) {
276
- const message = entry.message;
277
- return !(message.role === "assistant" &&
278
- (message.stopReason === "error" || message.stopReason === "aborted"));
279
- }
280
- function collectUserMessageCandidates(entries, previousMessages) {
281
- const candidates = new Map();
282
- for (const message of previousMessages) {
283
- candidates.set(message.id, {
284
- id: message.id,
285
- text: message.text,
286
- source: "previous-preserved",
287
- });
288
- }
289
- for (const entry of entries) {
290
- const text = extractPreservableUserText(entry.message);
291
- if (!text) {
292
- continue;
293
- }
294
- candidates.set(entry.id, { id: entry.id, text, source: "conversation" });
295
- }
296
- return [...candidates.values()];
297
- }
298
- function formatUserMessageCandidates(candidates) {
299
- const records = candidates.map((message) => {
300
- if (message.source === "conversation") {
301
- return { id: message.id, source: message.source };
302
- }
303
- return { id: message.id, source: message.source, text: message.text };
304
- });
305
- return JSON.stringify(records, null, 2).replaceAll("<", "\\u003c");
306
- }
307
- function extractPreservableUserText(message) {
308
- if (message.role !== "user") {
309
- return undefined;
310
- }
311
- if (getSummaryCompactionMetadataFromMessage(message) ||
312
- isCompactionContinuation(message) ||
313
- hasToolRecoveryMetadata(message)) {
58
+ export function prepareAutoCompaction(entries, settings) {
59
+ const preparation = prepareSessionCompaction(entries);
60
+ if (!preparation) {
314
61
  return undefined;
315
62
  }
316
- const text = extractUserText(message);
317
- return text.trim() ? text : undefined;
318
- }
319
- function extractUserText(message) {
320
- if (typeof message.content === "string") {
321
- return stripTauUserMetadata(message.content);
322
- }
323
- const parts = [];
324
- for (const block of message.content) {
325
- if (typeof block === "string") {
326
- parts.push(block);
327
- }
328
- else if (block.type === "text") {
329
- parts.push(block.text ?? "");
330
- }
331
- }
332
- return stripTauUserMetadata(parts.join("\n"));
333
- }
334
- function escapeXmlAttribute(value) {
335
- return value
336
- .replaceAll("&", "&amp;")
337
- .replaceAll('"', "&quot;")
338
- .replaceAll("<", "&lt;")
339
- .replaceAll(">", "&gt;");
340
- }
341
- function estimateTextTokens(text) {
342
- return Math.max(1, bytesToTokens(Buffer.byteLength(text, "utf8")));
343
- }
344
- function boundRetainedEntry(entry) {
345
- const message = structuredClone(entry.message);
346
- if (message.role === "user" && hasToolRecoveryMetadata(message)) {
347
- if (typeof message.content === "string") {
348
- message.content = truncateToolRecoveryResults(message.content, RETAINED_TOOL_RESULT_MAX_TOKENS);
349
- }
350
- else {
351
- for (const block of message.content) {
352
- if (block.type === "text") {
353
- block.text = truncateToolRecoveryResults(block.text, RETAINED_TOOL_RESULT_MAX_TOKENS);
354
- }
355
- }
356
- }
357
- return { id: entry.id, message };
358
- }
359
- if (message.role !== "toolResult") {
360
- return { id: entry.id, message };
361
- }
362
- const toolResult = message;
363
- const text = toolResult.content
364
- .filter((content) => content.type === "text")
365
- .map((content) => content.text)
366
- .join("\n");
367
- const truncated = truncateForTokens(text, {
368
- maxTokens: RETAINED_TOOL_RESULT_MAX_TOKENS,
369
- strategy: "middle",
370
- });
371
- if (!truncated.truncated) {
372
- return { id: entry.id, message };
373
- }
374
- const content = [];
375
- let replacedText = false;
376
- for (const block of toolResult.content) {
377
- if (block.type !== "text") {
378
- content.push(block);
379
- continue;
380
- }
381
- if (!replacedText) {
382
- content.push({ ...block, text: truncated.content });
383
- replacedText = true;
384
- }
385
- }
386
- return {
387
- id: entry.id,
388
- message: { ...toolResult, content },
389
- };
390
- }
391
- export function prepareAutoCompaction(entries, settings) {
392
- const latestCompaction = findLatestCompactionEntry(entries);
63
+ const latestCompactionIndex = findLatestCompactionIndex(entries);
393
64
  const cut = selectAutoCompactionCut(entries, {
394
- startIndex: latestCompaction.index + 1,
65
+ startIndex: latestCompactionIndex + 1,
395
66
  keepRecentTokens: settings.keepRecentTokens,
396
67
  });
397
68
  if (!cut) {
398
69
  return undefined;
399
70
  }
400
- const entriesToSummarize = entries
401
- .slice(latestCompaction.index + 1, cut.startIndex)
402
- .filter((entry) => !isCompactionContinuation(entry.message));
403
- const messagesToSummarize = entriesToSummarize.map((entry) => entry.message);
404
- const historyEntryIds = buildHistoryEntryIdMap(entriesToSummarize.filter(isAutoCompactionArchiveEntry));
405
- const formattedConversation = formatHistoryForCompaction(messagesToSummarize, {
406
- historyEntryIds,
407
- });
408
- if (!formattedConversation) {
409
- return undefined;
410
- }
411
- const formattedHistory = formatHistoryForCompaction(messagesToSummarize, {
412
- systemPrompt: settings.systemPrompt,
413
- historyEntryIds,
414
- });
415
- const retainedEntries = entries
416
- .slice(cut.startIndex)
417
- .filter((entry) => !isCompactionContinuation(entry.message))
418
- .map(boundRetainedEntry);
419
- if (retainedEntries.length === 0) {
420
- return undefined;
421
- }
422
71
  return {
423
- previousSummary: latestCompaction.summary,
424
- retainedEntries,
72
+ ...preparation,
73
+ retainedEntries: entries
74
+ .slice(cut.startIndex)
75
+ .filter((entry) => !isCompactionContinuation(entry.message))
76
+ .map((entry) => structuredClone(entry)),
425
77
  cutType: cut.cutType,
426
- formattedHistory,
427
- userMessageCandidates: collectUserMessageCandidates(entriesToSummarize, latestCompaction.preservedUserMessages),
428
78
  };
429
79
  }
430
- const AUTO_COMPACTION_ARCHIVE_REFERENCE_GUIDANCE = `When best-effort archiving succeeds, the continuing assistant receives exact paths to a temporary text transcript and JSON snapshot of the pre-compaction context. Conversation records above that show an archive entry id can be recovered directly from the matching JSON file. When no entry id is available, the files also support discovery from distinctive evidence.
431
-
432
- Keep the summary independently useful. State continuity-critical goals, constraints, decisions, current state, blockers, and next steps directly. When exact or bulky details would be wasteful to reproduce, you may mention the relevant id as an auto-compaction archive entry id so the continuing assistant knows to use the supplied files, not the separate history tool. This is useful for long tool output, diagnostic logs, exact errors, payloads, and large code excerpts. Use such references sparingly and only for ids shown in the conversation.
433
-
434
- Good pattern: "The key failure is a missing RuntimeConfig field; the complete compiler output is in auto-compaction archive entry 'ARCHIVE_ENTRY_ID'."
435
- Bad pattern: "See archive entry 'ARCHIVE_ENTRY_ID' for what happened."`;
436
- export function buildAutoCompactionPrompt(preparation) {
437
- const retainedContextGuidance = preparation.cutType === "split-turn"
438
- ? `The retained context will begin in the middle of the latest assistant/tool turn. Add a "## Current Turn Handoff" section that clearly captures:
439
- - the original user request for this turn
440
- - work completed before the retained suffix
441
- - tool state and findings at the cut boundary
442
- - what the first retained message is continuing and what remains unresolved
443
-
444
- Place the section wherever it makes the handoff clearest. Preserve earlier session context elsewhere in the summary without duplicating the retained suffix.`
445
- : "The retained context will include recent messages. Ensure the summary complements that retained context without duplicating unnecessary detail.";
446
- const boundedRetainedContextGuidance = "Individual textual tool results and tool-recovery payloads in the retained context may be middle-truncated above the retention limit. Do not describe the retained context as exact or verbatim. When the pre-compaction archive is available, the continuing assistant can recover omitted output through a targeted lookup.";
80
+ export function buildAutoCompactionPrompt() {
447
81
  return buildSessionCompactionPrompt({
448
- preparation,
449
- guidance: `${retainedContextGuidance}\n\n${boundedRetainedContextGuidance}\n\n${AUTO_COMPACTION_ARCHIVE_REFERENCE_GUIDANCE}`,
82
+ guidance: "After this compaction summary, some recent complete messages may remain, or there may be no retained messages. Do not rely on a particular retention boundary. After compaction, you will have access to a transcript archive of the conversation before this compaction: a readable .txt transcript, a structured .json snapshot, and an archive guide. The text transcript may truncate large tool results; the JSON snapshot retains full archived results but excludes assistant thinking. You can refer to this transcript archive for exact wording or bulky evidence instead of copying it into the summary. Include targeted instructions to revisit a relevant user message or other evidence when useful, explaining what to look for and why it matters. Use distinctive details such as the topic of a user request, a file path, a tool name, or an error. The continuation supplies the actual paths, so do not invent paths or entry IDs. Keep the essential context in the summary; the transcript archive is an additional way to recover details, not a substitute for a useful summary. This transcript archive is separate from the history tool's collection.",
450
83
  });
451
84
  }
85
+ export const COMPACTION_CONTINUATION_GUIDANCE = `Your earlier conversation was compacted to keep this same session within the context limit, not to start a new task.
86
+ The compaction summary supplies continuity. Any retained recent messages supply additional detail and may overlap with it. Compaction does not mean that work completed or that the user request changed.
87
+ Resume from the compaction summary and any retained messages without repeating completed work. Recover missing evidence when it matters rather than guessing or asking the user to repeat information.`;
452
88
  export function buildAutoCompactionContinuationMessage(args) {
453
- const lines = [
454
- "The conversation context before this point has been compacted.",
455
- "Earlier context is summarized in the compaction message above. Recent messages are retained after that summary, but large textual tool results and tool-recovery payloads may be middle-truncated.",
456
- "Continue from the summary and retained context without asking the user to repeat information.",
457
- ];
89
+ const lines = [COMPACTION_CONTINUATION_GUIDANCE];
458
90
  if (args.cutType === "split-turn") {
459
- lines.push("The retained messages begin in the middle of the latest assistant/tool turn. The summary contains the original request and earlier tool work from that turn.");
91
+ lines.push("The retained suffix may begin partway through a user turn, or be empty. Use the compaction summary to understand earlier requests, prior tool work, and unfinished actions that are no longer visible.");
460
92
  }
461
93
  if (args.archive) {
462
- lines.push("The summary and retained context should normally be sufficient. Temporary pre-compaction archive files are also available:", `- archive guide: ${args.archive.documentationPath}`, `- this compaction's text transcript: ${args.archive.textPath}`, `- this compaction's full JSON: ${args.archive.jsonPath}`, "Before continuing, ensure the archive guide's full contents are present in the current model context. If they are not already visible in full, read the guide now with an execution-environment file tool. This is required even when no archive lookup is currently planned.", "For details removed from this session by automatic compaction, use these execution-environment files rather than the separate history tool, whose collection may be stale, remotely replicated, truncated, or unavailable.", "The guide describes the archive format and adaptable, bounded lookup examples, including how to inspect earlier numbered pairs when needed.");
94
+ lines.push("The summary and retained context should normally be sufficient. The transcript archive from before this compaction is also available:", `- archive guide: ${args.archive.documentationPath}`, `- transcript archive (.txt): ${args.archive.textPath}`, `- transcript archive (.json): ${args.archive.jsonPath}`, "The archive guide is included below and remains available at the path above.", "For details removed from this session by automatic compaction, use this transcript archive rather than the separate history tool, whose collection may be stale, remotely replicated, truncated, or unavailable.", "The guide describes the archive format and adaptable, bounded lookup examples, including how to inspect earlier numbered pairs when needed.", "<transcript-archive-guide>", AUTO_COMPACTION_ARCHIVE_DOCUMENTATION, "</transcript-archive-guide>");
463
95
  }
464
96
  const hiddenSystemMessages = [...(args.systemMessages ?? []), lines.join("\n")];
465
97
  return {
@@ -481,109 +113,50 @@ export function selectAutoCompactionCut(entries, args) {
481
113
  if (entries.length === 0 || args.startIndex >= entries.length) {
482
114
  return undefined;
483
115
  }
484
- const turnStarts = collectTurnStarts(entries, args.startIndex);
485
- const latestTurnStart = turnStarts.at(-1) ?? findOngoingTurnStart(entries, args.startIndex);
486
- if (latestTurnStart === undefined) {
487
- return undefined;
488
- }
489
- const latestTurnTokens = estimateEntriesTokens(entries.slice(latestTurnStart));
490
- if (latestTurnTokens <= args.keepRecentTokens) {
491
- if (turnStarts.length === 0) {
492
- return undefined;
116
+ let retainedTokens = 0;
117
+ let startIndex = entries.length;
118
+ for (let index = entries.length - 1; index >= args.startIndex; index -= 1) {
119
+ if (isCompactionContinuation(entries[index].message)) {
120
+ continue;
493
121
  }
494
- let firstKeptTurn = latestTurnStart;
495
- let totalTokens = latestTurnTokens;
496
- for (let i = turnStarts.length - 2; i >= 0; i -= 1) {
497
- const turnStart = turnStarts[i];
498
- const nextTurnStart = turnStarts[i + 1];
499
- const turnTokens = estimateEntriesTokens(entries.slice(turnStart, nextTurnStart));
500
- if (totalTokens + turnTokens > args.keepRecentTokens) {
122
+ let groupStart = index;
123
+ if (entries[index].message.role === "toolResult") {
124
+ while (groupStart > args.startIndex) {
125
+ groupStart -= 1;
126
+ const message = entries[groupStart].message;
127
+ if (message.role === "assistant") {
128
+ break;
129
+ }
130
+ if (message.role !== "toolResult" && !isCompactionContinuation(message)) {
131
+ break;
132
+ }
133
+ }
134
+ if (entries[groupStart].message.role !== "assistant") {
501
135
  break;
502
136
  }
503
- firstKeptTurn = turnStart;
504
- totalTokens += turnTokens;
505
137
  }
506
- if (firstKeptTurn <= args.startIndex) {
507
- return undefined;
138
+ const groupTokens = estimateEntriesTokens(entries.slice(groupStart, index + 1));
139
+ if (retainedTokens + groupTokens > args.keepRecentTokens) {
140
+ break;
508
141
  }
509
- return { startIndex: firstKeptTurn, cutType: "turn-boundary" };
510
- }
511
- const splitStart = selectLatestTurnSplitStart(entries, {
512
- turnStart: latestTurnStart,
513
- keepRecentTokens: args.keepRecentTokens,
514
- });
515
- if (splitStart === undefined) {
516
- return latestTurnStart > args.startIndex
517
- ? { startIndex: latestTurnStart, cutType: "turn-boundary" }
518
- : undefined;
142
+ retainedTokens += groupTokens;
143
+ startIndex = groupStart;
144
+ index = groupStart;
519
145
  }
520
- if (splitStart <= args.startIndex) {
146
+ if (startIndex <= args.startIndex) {
521
147
  return undefined;
522
148
  }
523
- return { startIndex: splitStart, cutType: "split-turn" };
149
+ const message = entries[startIndex]?.message;
150
+ const cutType = message?.role === "user" && !hasToolRecoveryMetadata(message) ? "turn-boundary" : "split-turn";
151
+ return { startIndex, cutType };
524
152
  }
525
- function findLatestCompactionEntry(entries) {
153
+ function findLatestCompactionIndex(entries) {
526
154
  for (let index = entries.length - 1; index >= 0; index -= 1) {
527
- const metadata = getSummaryCompactionMetadataFromMessage(entries[index].message);
528
- if (metadata) {
529
- const preservedUserMessages = metadata.preservedUserMessages.map((message) => ({
530
- id: message.id,
531
- text: message.text,
532
- }));
533
- return {
534
- index,
535
- summary: metadata.summary,
536
- preservedUserMessages,
537
- };
538
- }
539
- }
540
- return { index: -1, preservedUserMessages: [] };
541
- }
542
- function collectTurnStarts(entries, startIndex) {
543
- const starts = [];
544
- for (let index = Math.max(0, startIndex); index < entries.length; index += 1) {
545
- const message = entries[index].message;
546
- if (message.role !== "user") {
547
- continue;
548
- }
549
- if (getSummaryCompactionMetadataFromMessage(message) ||
550
- isCompactionContinuation(message) ||
551
- hasToolRecoveryMetadata(message)) {
552
- continue;
553
- }
554
- starts.push(index);
555
- }
556
- return starts;
557
- }
558
- function findOngoingTurnStart(entries, startIndex) {
559
- for (let index = Math.max(0, startIndex); index < entries.length; index += 1) {
560
- if (!isCompactionContinuation(entries[index].message)) {
155
+ if (getSummaryCompactionMetadataFromMessage(entries[index].message)) {
561
156
  return index;
562
157
  }
563
158
  }
564
- return undefined;
565
- }
566
- function selectLatestTurnSplitStart(entries, args) {
567
- let retainedTokens = 0;
568
- let latestAssistantBoundary;
569
- let crossedBudget = false;
570
- for (let index = entries.length - 1; index >= args.turnStart; index -= 1) {
571
- const entry = entries[index];
572
- if (isCompactionContinuation(entry.message)) {
573
- continue;
574
- }
575
- retainedTokens += estimateMessageTokens(entry.message);
576
- if (entry.message.role === "assistant") {
577
- latestAssistantBoundary = index;
578
- }
579
- if (retainedTokens > args.keepRecentTokens) {
580
- crossedBudget = true;
581
- }
582
- if (crossedBudget && latestAssistantBoundary !== undefined) {
583
- return latestAssistantBoundary;
584
- }
585
- }
586
- return latestAssistantBoundary;
159
+ return -1;
587
160
  }
588
161
  function estimateEntriesTokens(entries) {
589
162
  return entries.reduce((total, entry) => isCompactionContinuation(entry.message)