@maudecode/talaria-web 1.15.0 → 1.17.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (386) hide show
  1. package/_release.json +4 -4
  2. package/dist/api/automation-raw.js +6 -3
  3. package/dist/api/automation-raw.js.map +1 -1
  4. package/dist/api/automation-router.d.ts +158 -16
  5. package/dist/api/automation-router.js +12 -6
  6. package/dist/api/automation-router.js.map +1 -1
  7. package/dist/api/chat-router.d.ts +193 -6
  8. package/dist/api/chat-router.js +39 -16
  9. package/dist/api/chat-router.js.map +1 -1
  10. package/dist/api/git-router.d.ts +12 -1
  11. package/dist/api/git-router.js +60 -38
  12. package/dist/api/git-router.js.map +1 -1
  13. package/dist/api/raw-routes.d.ts +6 -0
  14. package/dist/api/raw-routes.js +35 -58
  15. package/dist/api/raw-routes.js.map +1 -1
  16. package/dist/api/router.d.ts +4 -2
  17. package/dist/api/router.js +8 -8
  18. package/dist/api/router.js.map +1 -1
  19. package/dist/api/sessions-router.d.ts +974 -42
  20. package/dist/api/sessions-router.js +87 -25
  21. package/dist/api/sessions-router.js.map +1 -1
  22. package/dist/api/settings-router.d.ts +254 -11
  23. package/dist/api/settings-router.js +68 -18
  24. package/dist/api/settings-router.js.map +1 -1
  25. package/dist/api/sse-routes.d.ts +11 -3
  26. package/dist/api/sse-routes.js +145 -65
  27. package/dist/api/sse-routes.js.map +1 -1
  28. package/dist/api/tools-raw.js +6 -2
  29. package/dist/api/tools-raw.js.map +1 -1
  30. package/dist/api/tools-router.d.ts +86 -9
  31. package/dist/api/tools-router.js +19 -15
  32. package/dist/api/tools-router.js.map +1 -1
  33. package/dist/app.d.ts +0 -8397
  34. package/dist/app.js +7 -2
  35. package/dist/app.js.map +1 -1
  36. package/dist/auth/gate.js +11 -4
  37. package/dist/auth/gate.js.map +1 -1
  38. package/dist/auth/oidc.d.ts +6 -2
  39. package/dist/auth/oidc.js +16 -11
  40. package/dist/auth/oidc.js.map +1 -1
  41. package/dist/auth/store.d.ts +14 -2
  42. package/dist/auth/store.js +26 -1
  43. package/dist/auth/store.js.map +1 -1
  44. package/dist/cli/dotenv.d.ts +1 -1
  45. package/dist/cli/dotenv.js +36 -3
  46. package/dist/cli/dotenv.js.map +1 -1
  47. package/dist/config/agent-config.d.ts +2 -0
  48. package/dist/config/agent-config.js +9 -0
  49. package/dist/config/agent-config.js.map +1 -1
  50. package/dist/http/addresses.d.ts +6 -0
  51. package/dist/http/addresses.js +26 -0
  52. package/dist/http/addresses.js.map +1 -1
  53. package/dist/http/context.d.ts +6 -3
  54. package/dist/http/context.js +10 -4
  55. package/dist/http/context.js.map +1 -1
  56. package/dist/links.d.ts +26 -0
  57. package/dist/links.js +60 -0
  58. package/dist/links.js.map +1 -0
  59. package/dist/onboarding.js +30 -7
  60. package/dist/onboarding.js.map +1 -1
  61. package/dist/profiles/profiles.js +1 -1
  62. package/dist/profiles/profiles.js.map +1 -1
  63. package/dist/providers/catalog.d.ts +167 -5
  64. package/dist/providers/catalog.js +662 -135
  65. package/dist/providers/catalog.js.map +1 -1
  66. package/dist/providers/tables.d.ts +3 -1
  67. package/dist/providers/tables.js +9 -4
  68. package/dist/providers/tables.js.map +1 -1
  69. package/dist/redact.d.ts +1 -0
  70. package/dist/redact.js +518 -15
  71. package/dist/redact.js.map +1 -1
  72. package/dist/runtime.js +79 -37
  73. package/dist/runtime.js.map +1 -1
  74. package/dist/sessions/anchor.d.ts +24 -3
  75. package/dist/sessions/anchor.js +94 -44
  76. package/dist/sessions/anchor.js.map +1 -1
  77. package/dist/sessions/background-tasks.d.ts +4 -1
  78. package/dist/sessions/background-tasks.js +7 -4
  79. package/dist/sessions/background-tasks.js.map +1 -1
  80. package/dist/sessions/claude-code.d.ts +19 -0
  81. package/dist/sessions/claude-code.js +250 -0
  82. package/dist/sessions/claude-code.js.map +1 -0
  83. package/dist/sessions/completions.d.ts +13 -1
  84. package/dist/sessions/completions.js +64 -12
  85. package/dist/sessions/completions.js.map +1 -1
  86. package/dist/sessions/compress.d.ts +14 -1
  87. package/dist/sessions/compress.js +52 -11
  88. package/dist/sessions/compress.js.map +1 -1
  89. package/dist/sessions/journal.d.ts +8 -0
  90. package/dist/sessions/journal.js +44 -10
  91. package/dist/sessions/journal.js.map +1 -1
  92. package/dist/sessions/list.d.ts +13 -5
  93. package/dist/sessions/list.js +56 -26
  94. package/dist/sessions/list.js.map +1 -1
  95. package/dist/sessions/merge.d.ts +122 -9
  96. package/dist/sessions/merge.js +544 -33
  97. package/dist/sessions/merge.js.map +1 -1
  98. package/dist/sessions/pending.d.ts +5 -0
  99. package/dist/sessions/pending.js +30 -10
  100. package/dist/sessions/pending.js.map +1 -1
  101. package/dist/sessions/service.d.ts +100 -4
  102. package/dist/sessions/service.js +567 -102
  103. package/dist/sessions/service.js.map +1 -1
  104. package/dist/sessions/session.d.ts +16 -1
  105. package/dist/sessions/session.js +29 -2
  106. package/dist/sessions/session.js.map +1 -1
  107. package/dist/sessions/shares.js +11 -2
  108. package/dist/sessions/shares.js.map +1 -1
  109. package/dist/sessions/state-db.d.ts +26 -4
  110. package/dist/sessions/state-db.js +126 -8
  111. package/dist/sessions/state-db.js.map +1 -1
  112. package/dist/sessions/store.js +6 -1
  113. package/dist/sessions/store.js.map +1 -1
  114. package/dist/sessions/streams.d.ts +10 -0
  115. package/dist/sessions/streams.js +14 -3
  116. package/dist/sessions/streams.js.map +1 -1
  117. package/dist/sessions/todo.d.ts +6 -1
  118. package/dist/sessions/todo.js +11 -4
  119. package/dist/sessions/todo.js.map +1 -1
  120. package/dist/sessions/tool-display.d.ts +12 -1
  121. package/dist/sessions/tool-display.js +76 -1
  122. package/dist/sessions/tool-display.js.map +1 -1
  123. package/dist/sessions/turn.d.ts +49 -10
  124. package/dist/sessions/turn.js +459 -185
  125. package/dist/sessions/turn.js.map +1 -1
  126. package/dist/settings.d.ts +20 -2
  127. package/dist/settings.js +49 -2
  128. package/dist/settings.js.map +1 -1
  129. package/dist/sidecar/fake.d.ts +2 -2
  130. package/dist/sidecar/fake.js +7 -2
  131. package/dist/sidecar/fake.js.map +1 -1
  132. package/dist/spa.js +2 -1
  133. package/dist/spa.js.map +1 -1
  134. package/dist/test/harness.js +2 -0
  135. package/dist/test/harness.js.map +1 -1
  136. package/dist/tools/commands.d.ts +15 -0
  137. package/dist/tools/commands.js +52 -0
  138. package/dist/tools/commands.js.map +1 -0
  139. package/dist/tools/crons.d.ts +4 -1
  140. package/dist/tools/crons.js +58 -11
  141. package/dist/tools/crons.js.map +1 -1
  142. package/dist/tools/insights.d.ts +8 -2
  143. package/dist/tools/insights.js +18 -2
  144. package/dist/tools/insights.js.map +1 -1
  145. package/dist/tools/kanban.d.ts +41 -1
  146. package/dist/tools/kanban.js +113 -2
  147. package/dist/tools/kanban.js.map +1 -1
  148. package/dist/tools/mcp.d.ts +2 -1
  149. package/dist/tools/mcp.js +16 -6
  150. package/dist/tools/mcp.js.map +1 -1
  151. package/dist/tools/terminal.d.ts +1 -0
  152. package/dist/tools/terminal.js +1 -1
  153. package/dist/tools/terminal.js.map +1 -1
  154. package/dist/tools/update-notifications.d.ts +1 -1
  155. package/dist/tools/update-notifications.js +1 -1
  156. package/dist/tools/update-notifications.js.map +1 -1
  157. package/dist/tools/updates.d.ts +15 -3
  158. package/dist/tools/updates.js +207 -41
  159. package/dist/tools/updates.js.map +1 -1
  160. package/dist/workspace/fs.d.ts +7 -3
  161. package/dist/workspace/fs.js +5 -1
  162. package/dist/workspace/fs.js.map +1 -1
  163. package/dist/workspace/git.d.ts +57 -10
  164. package/dist/workspace/git.js +302 -195
  165. package/dist/workspace/git.js.map +1 -1
  166. package/dist/workspace/media-refs.d.ts +13 -0
  167. package/dist/workspace/media-refs.js +165 -0
  168. package/dist/workspace/media-refs.js.map +1 -0
  169. package/dist/workspace/media.d.ts +19 -0
  170. package/dist/workspace/media.js +63 -2
  171. package/dist/workspace/media.js.map +1 -1
  172. package/dist/workspace/preview.d.ts +28 -0
  173. package/dist/workspace/preview.js +110 -0
  174. package/dist/workspace/preview.js.map +1 -0
  175. package/dist/workspace/rollback.d.ts +5 -4
  176. package/dist/workspace/rollback.js +12 -9
  177. package/dist/workspace/rollback.js.map +1 -1
  178. package/dist/workspace/upload.d.ts +2 -0
  179. package/dist/workspace/upload.js +4 -1
  180. package/dist/workspace/upload.js.map +1 -1
  181. package/dist/workspace/workspaces.d.ts +7 -1
  182. package/dist/workspace/workspaces.js +30 -21
  183. package/dist/workspace/workspaces.js.map +1 -1
  184. package/dist/workspace/worktrees.d.ts +2 -4
  185. package/dist/workspace/worktrees.js +28 -33
  186. package/dist/workspace/worktrees.js.map +1 -1
  187. package/package.json +4 -2
  188. package/sidecar/scripts/replay_sidecar.py +4 -2
  189. package/sidecar/talaria_sidecar/__init__.py +1 -1
  190. package/sidecar/talaria_sidecar/__main__.py +7 -1
  191. package/sidecar/talaria_sidecar/home.py +98 -2
  192. package/sidecar/talaria_sidecar/methods/__init__.py +2 -2
  193. package/sidecar/talaria_sidecar/methods/aux.py +19 -3
  194. package/sidecar/talaria_sidecar/methods/chat.py +551 -61
  195. package/sidecar/talaria_sidecar/methods/commands.py +61 -33
  196. package/sidecar/talaria_sidecar/methods/config.py +14 -10
  197. package/sidecar/talaria_sidecar/methods/cron.py +10 -6
  198. package/sidecar/talaria_sidecar/methods/kanban.py +115 -15
  199. package/sidecar/talaria_sidecar/methods/oauth.py +439 -0
  200. package/sidecar/talaria_sidecar/methods/process.py +60 -1
  201. package/sidecar/talaria_sidecar/methods/profiles.py +12 -5
  202. package/sidecar/talaria_sidecar/methods/providers.py +161 -1
  203. package/sidecar/talaria_sidecar/methods/runtime.py +5 -0
  204. package/sidecar/talaria_sidecar/methods/text.py +18 -21
  205. package/sidecar/talaria_sidecar/methods/usage.py +264 -5
  206. package/static/dist/FILES.txt +126 -121
  207. package/static/dist/assets/AppShell-fb2td9zi.js +1 -0
  208. package/static/dist/assets/ChatPage-cj96kiy9.js +25 -0
  209. package/static/dist/assets/Dialog-kxcg7ard.js +1 -0
  210. package/static/dist/assets/ExtensionRoute-jf7h0754.js +1 -0
  211. package/static/dist/assets/HubRoute-gmanpd5y.js +2 -0
  212. package/static/dist/assets/{Markdown-dy8t4dy7.js → Markdown-mdwlvj3w.js} +125 -125
  213. package/static/dist/assets/RightPanel-ftvrjlcp.js +1 -0
  214. package/static/dist/assets/Select-bilaypej.js +1 -0
  215. package/static/dist/assets/SessionListPanel-og8n36qs.js +1 -0
  216. package/static/dist/assets/SettingsLayout-jonjefmj.js +1 -0
  217. package/static/dist/assets/States-nmejrwrm.js +1 -0
  218. package/static/dist/assets/Toaster-nnl8zkt9.js +1 -0
  219. package/static/dist/assets/_app-gdidzd94.js +1 -0
  220. package/static/dist/assets/_app.ext._extensionId-opyh9y0v.js +1 -0
  221. package/static/dist/assets/_app.index-hbarzbjx.js +1 -0
  222. package/static/dist/assets/_app.insights-luywlarz.js +1 -0
  223. package/static/dist/assets/_app.kanban-kincnbr8.js +1 -0
  224. package/static/dist/assets/_app.logs-oi2gr3gs.js +1 -0
  225. package/static/dist/assets/_app.memory-b6jfivt6.js +1 -0
  226. package/static/dist/assets/_app.profiles-lpvixmqp.js +1 -0
  227. package/static/dist/assets/{_app.session._sessionId-gyoh5ylx.js → _app.session._sessionId-bfd4h3id.js} +1 -1
  228. package/static/dist/assets/_app.settings-vowx3iq0.js +1 -0
  229. package/static/dist/assets/_app.settings._section-cnntg1tu.js +3 -0
  230. package/static/dist/assets/_app.skills-fb57lpau.js +1 -0
  231. package/static/dist/assets/_app.tasks-eszpx1q7.js +2 -0
  232. package/static/dist/assets/_app.todos-nr05ac6w.js +1 -0
  233. package/static/dist/assets/_app.workspaces-d2luomu6.js +1 -0
  234. package/static/dist/assets/{abnfDiagram-VCTEODGH-cjqletra.js → abnfDiagram-VCTEODGH-b2x1lqot.js} +1 -1
  235. package/static/dist/assets/architecture-7GRP2DOG-hd0o7bde.js +1 -0
  236. package/static/dist/assets/architectureDiagram-5GKGNRK7-h38o5l7b.js +36 -0
  237. package/static/dist/assets/{back-e4m6nsnm.js → back-bemaer09.js} +1 -1
  238. package/static/dist/assets/{blockDiagram-I7D4REHJ-b195z021.js → blockDiagram-I7D4REHJ-bqquxv2u.js} +1 -1
  239. package/static/dist/assets/{c4Diagram-7LVT6UL2-olu212k1.js → c4Diagram-7LVT6UL2-f3fb8w6z.js} +1 -1
  240. package/static/dist/assets/cancel-drd0g6d9.js +1 -0
  241. package/static/dist/assets/channel-cpdgf867.js +1 -0
  242. package/static/dist/assets/{chunk-4HAMMTFA-b8o1nufs.js → chunk-4HAMMTFA-bzfyyzp3.js} +1 -1
  243. package/static/dist/assets/{chunk-75Z2AOVW-l1seddto.js → chunk-75Z2AOVW-ly99rfx5.js} +1 -1
  244. package/static/dist/assets/{chunk-DU6HZSFF-lo5zg15v.js → chunk-DU6HZSFF-bg6ecfqf.js} +3 -3
  245. package/static/dist/assets/{chunk-F27PBJKO-dsdzrp16.js → chunk-F27PBJKO-e8k7mmqw.js} +1 -1
  246. package/static/dist/assets/{chunk-GMAD6QVW-f3fxqq8b.js → chunk-GMAD6QVW-cmalnoaj.js} +1 -1
  247. package/static/dist/assets/{chunk-GVQU2GXP-h8egxtf6.js → chunk-GVQU2GXP-gmif5o01.js} +1 -1
  248. package/static/dist/assets/{chunk-IMKFNOWR-iolwd8s0.js → chunk-IMKFNOWR-f1nwwh6u.js} +1 -1
  249. package/static/dist/assets/{chunk-L3NEJ4N5-b88bbtwi.js → chunk-L3NEJ4N5-oktfh5v7.js} +1 -1
  250. package/static/dist/assets/{chunk-OSK3NFVY-e3e7nr6s.js → chunk-OSK3NFVY-b1sdx96z.js} +1 -1
  251. package/static/dist/assets/{chunk-P2QGCYS3-mdc46rt1.js → chunk-P2QGCYS3-fhu4nzxt.js} +1 -1
  252. package/static/dist/assets/{chunk-POPQ4Y6H-kqlllrrm.js → chunk-POPQ4Y6H-em31rnef.js} +1 -1
  253. package/static/dist/assets/{chunk-PWAF6VOD-he585k2x.js → chunk-PWAF6VOD-bxhhyftw.js} +1 -1
  254. package/static/dist/assets/{chunk-SHT3W25Y-gh94y9a4.js → chunk-SHT3W25Y-fmrvwcav.js} +3 -3
  255. package/static/dist/assets/{chunk-SVP7TREG-euxp9y7e.js → chunk-SVP7TREG-lymukl2j.js} +1 -1
  256. package/static/dist/assets/{chunk-TICWLB2K-c8cxes1q.js → chunk-TICWLB2K-lmza6k23.js} +1 -1
  257. package/static/dist/assets/classDiagram-ZZMXUADV-ko09sguk.js +1 -0
  258. package/static/dist/assets/classDiagram-v2-VYDZK3BY-ko09sguk.js +1 -0
  259. package/static/dist/assets/{copied-kq0u3ht7.js → copied-ima3pysb.js} +1 -1
  260. package/static/dist/assets/copy-fw5b1pc6.js +1 -0
  261. package/static/dist/assets/{cynefin-OW5HDTMX-lxd44ux4.js → cynefin-OW5HDTMX-czlhbyhs.js} +1 -1
  262. package/static/dist/assets/{cynefinDiagram-5FMLGOSQ-njpiwat8.js → cynefinDiagram-5FMLGOSQ-gq7mvswb.js} +1 -1
  263. package/static/dist/assets/{dagre-GXQ25YYZ-nvgy6yju.js → dagre-GXQ25YYZ-cqdbf7dr.js} +4 -4
  264. package/static/dist/assets/{diagram-S7CK7UJ4-g98nyv72.js → diagram-S7CK7UJ4-o6arxa58.js} +3 -3
  265. package/static/dist/assets/{diagram-UQ7AKVKN-ovgm6lvv.js → diagram-UQ7AKVKN-nhnd68tz.js} +1 -1
  266. package/static/dist/assets/{diagram-VSXAHHWV-pb2bn2e4.js → diagram-VSXAHHWV-dk2sclzn.js} +1 -1
  267. package/static/dist/assets/{diagram-VX7I27RA-6j4mvfqo.js → diagram-VX7I27RA-n3k5nyx3.js} +1 -1
  268. package/static/dist/assets/{diagram-Z3DM3KII-dqmb982r.js → diagram-Z3DM3KII-m6q9cwdh.js} +1 -1
  269. package/static/dist/assets/{ebnfDiagram-PWID7BFC-ozbpzb6d.js → ebnfDiagram-PWID7BFC-lsa5641p.js} +1 -1
  270. package/static/dist/assets/endpoints-d33siokx.js +64 -0
  271. package/static/dist/assets/{erDiagram-RLTQ6QDP-hl7m8vua.js → erDiagram-RLTQ6QDP-fo2ckbe6.js} +1 -1
  272. package/static/dist/assets/eventmodeling-NTZA5JFV-emctlo64.js +1 -0
  273. package/static/dist/assets/flowDiagram-HODETNUW-bfi3moqa.js +1 -0
  274. package/static/dist/assets/{ganttDiagram-EL5Y4UJY-ompixqmh.js → ganttDiagram-EL5Y4UJY-nzqz6x9g.js} +1 -1
  275. package/static/dist/assets/{gitGraph-4MIJSDKK-b6vxwp9q.js → gitGraph-4MIJSDKK-gukvcxe6.js} +1 -1
  276. package/static/dist/assets/{gitGraphDiagram-WWUBYQGX-k98f57on.js → gitGraphDiagram-WWUBYQGX-lqhdp8f0.js} +1 -1
  277. package/static/dist/assets/{highlighted-body-KPVGNVTW-cd3ff6ge.js → highlighted-body-KPVGNVTW-igw6cp39.js} +1 -1
  278. package/static/dist/assets/index-cxq58xb1.js +11 -0
  279. package/static/dist/assets/index-hgtx08n5.css +2 -0
  280. package/static/dist/assets/{info-A6RAGUB7-hjcwjami.js → info-A6RAGUB7-gvu7a93l.js} +1 -1
  281. package/static/dist/assets/infoDiagram-27XIBGKW-m8t8g6w1.js +2 -0
  282. package/static/dist/assets/{ishikawaDiagram-5VMMS53U-bxak4mwu.js → ishikawaDiagram-5VMMS53U-l07vkbgj.js} +1 -1
  283. package/static/dist/assets/{journeyDiagram-3NMN7TZE-khmihclr.js → journeyDiagram-3NMN7TZE-hybkgj8l.js} +1 -1
  284. package/static/dist/assets/{kanban-definition-UXKFOSKX-h1r32wdx.js → kanban-definition-UXKFOSKX-by13uvtn.js} +1 -1
  285. package/static/dist/assets/{lazyRouteComponent-b2eg0fws.js → lazyRouteComponent-af0lvgjx.js} +1 -1
  286. package/static/dist/assets/{line-friyjof3.js → line-osxq6fq7.js} +1 -1
  287. package/static/dist/assets/link-jyxicdhg.js +1 -0
  288. package/static/dist/assets/login-ec21t39x.js +1 -0
  289. package/static/dist/assets/mermaid-HWGCJPDP-gfl8z3ep.js +1 -0
  290. package/static/dist/assets/{mermaid-parser.core-f1gvpwrt.js → mermaid-parser.core-i91oxmou.js} +3 -3
  291. package/static/dist/assets/{mindmap-definition-YA3MSWOX-mt2oxwue.js → mindmap-definition-YA3MSWOX-m82mx8ww.js} +1 -1
  292. package/static/dist/assets/onboarding-btnltvyz.js +1 -0
  293. package/static/dist/assets/onboarding_model_label-dp7xffoh.js +1 -0
  294. package/static/dist/assets/{packet-AYTQ26CC-jik4ixlf.js → packet-AYTQ26CC-nbtgge2o.js} +1 -1
  295. package/static/dist/assets/{pegDiagram-XKGWAZYB-erxlv6cc.js → pegDiagram-XKGWAZYB-f1zxzga1.js} +1 -1
  296. package/static/dist/assets/{pie-WAS4IAKB-mnkoldfl.js → pie-WAS4IAKB-of8vuhxs.js} +1 -1
  297. package/static/dist/assets/{pieDiagram-E7YTZNPT-i7lwrada.js → pieDiagram-E7YTZNPT-o7y8ftio.js} +3 -3
  298. package/static/dist/assets/{quadrantDiagram-AXDQQJYC-fr8ejdoy.js → quadrantDiagram-AXDQQJYC-iqiwia1t.js} +1 -1
  299. package/static/dist/assets/{radar-RG4KPBEZ-otarjte2.js → radar-RG4KPBEZ-delnsy2v.js} +1 -1
  300. package/static/dist/assets/{railroad-74A4TZTK-lw362oto.js → railroad-74A4TZTK-nspnyq9u.js} +1 -1
  301. package/static/dist/assets/railroad-abnf-HS5TGJTU-nst1qxyn.js +1 -0
  302. package/static/dist/assets/railroad-ebnf-LZEXJU2U-ci37x8qy.js +1 -0
  303. package/static/dist/assets/railroad-peg-WCYAUIDC-aimkk07z.js +1 -0
  304. package/static/dist/assets/{railroadDiagram-O6MQD6OU-esyn75pw.js → railroadDiagram-O6MQD6OU-z41tqx5z.js} +1 -1
  305. package/static/dist/assets/refresh-b4uams1i.js +1 -0
  306. package/static/dist/assets/refresh-cw-nt1dtsx4.js +1 -0
  307. package/static/dist/assets/{requirementDiagram-BXWQKSXE-cyhi5u29.js → requirementDiagram-BXWQKSXE-bdvif194.js} +1 -1
  308. package/static/dist/assets/{sankeyDiagram-P5KCCOFB-jljpbo0h.js → sankeyDiagram-P5KCCOFB-h73e0rol.js} +1 -1
  309. package/static/dist/assets/{saved-do49si3v.js → saved-kg52csy7.js} +1 -1
  310. package/static/dist/assets/{sequenceDiagram-WJ2MYXX4-fz0dhlu7.js → sequenceDiagram-WJ2MYXX4-iv95mb1z.js} +1 -1
  311. package/static/dist/assets/{settings_section_extensions_title-b7lojn0o.js → settings_section_extensions_title-ej0iapxe.js} +1 -1
  312. package/static/dist/assets/share._token-f97e94r0.js +2 -0
  313. package/static/dist/assets/sliders-horizontal-fj18it47.js +1 -0
  314. package/static/dist/assets/sse-fwpqtzbg.js +1 -0
  315. package/static/dist/assets/{stateDiagram-D77RDMKH-kdnithdx.js → stateDiagram-D77RDMKH-k417ixmf.js} +1 -1
  316. package/static/dist/assets/stateDiagram-v2-MP3YSRHH-hwn4apct.js +1 -0
  317. package/static/dist/assets/{swimlanes-42K2YHIH-esd6gmz3.js → swimlanes-42K2YHIH-esola70w.js} +1 -1
  318. package/static/dist/assets/swimlanesDiagram-VR7AAH4N-d1h97oyg.js +8 -0
  319. package/static/dist/assets/{timeline-definition-24CTP7MA-g0qygccv.js → timeline-definition-24CTP7MA-getrz2y8.js} +1 -1
  320. package/static/dist/assets/todoStore-eginf9h4.js +1 -0
  321. package/static/dist/assets/{treeView-Q6P3EWNA-ndchzkuu.js → treeView-Q6P3EWNA-hc7ap1q7.js} +1 -1
  322. package/static/dist/assets/{treemap-WGGIJYW6-hwjxj0zz.js → treemap-WGGIJYW6-b7k1syy5.js} +1 -1
  323. package/static/dist/assets/tts-lxdxnmjd.js +1 -0
  324. package/static/dist/assets/{untitled-mbr6tuhu.js → untitled-ovpfthg4.js} +1 -1
  325. package/static/dist/assets/useForm-bcblv1rx.js +1 -0
  326. package/static/dist/assets/useLocale-fi5jb8ma.js +1 -0
  327. package/static/dist/assets/{useParams-lo9u33d1.js → useParams-lhg566ew.js} +1 -1
  328. package/static/dist/assets/{useSearch-lq1mr090.js → useSearch-lbrh6ejn.js} +1 -1
  329. package/static/dist/assets/{vennDiagram-4TSXK5OY-gubovd1r.js → vennDiagram-4TSXK5OY-n6pcy5qb.js} +1 -1
  330. package/static/dist/assets/{wardley-WFR3VGLG-ir3om65j.js → wardley-WFR3VGLG-lpa18vz1.js} +1 -1
  331. package/static/dist/assets/{wardleyDiagram-VM6X3IG4-ce0or1ie.js → wardleyDiagram-VM6X3IG4-kpclwwg7.js} +2 -2
  332. package/static/dist/assets/{xychartDiagram-S5SC5T6Z-dexylcjg.js → xychartDiagram-S5SC5T6Z-paa2n8y0.js} +1 -1
  333. package/static/dist/index.html +1 -1
  334. package/static/dist/sw.js +2 -2
  335. package/static/dist/assets/AppShell-orat1s43.js +0 -1
  336. package/static/dist/assets/ChatPage-ojhka3hy.js +0 -22
  337. package/static/dist/assets/HubRoute-gxgw1t33.js +0 -2
  338. package/static/dist/assets/RightPanel-c66ani1s.js +0 -1
  339. package/static/dist/assets/Select-fswf66up.js +0 -1
  340. package/static/dist/assets/SessionListPanel-goh5mtt8.js +0 -1
  341. package/static/dist/assets/SettingsLayout-n5d4wq4m.js +0 -1
  342. package/static/dist/assets/States-kxyu50at.js +0 -1
  343. package/static/dist/assets/Toaster-6zmbcqgg.js +0 -1
  344. package/static/dist/assets/_app-fowyruiu.js +0 -1
  345. package/static/dist/assets/_app.ext._extensionId-nvjyrwhy.js +0 -1
  346. package/static/dist/assets/_app.index-c9zv4y7w.js +0 -1
  347. package/static/dist/assets/_app.insights-jf12nofo.js +0 -1
  348. package/static/dist/assets/_app.kanban-lri5a4yu.js +0 -1
  349. package/static/dist/assets/_app.logs-md1p0zqo.js +0 -1
  350. package/static/dist/assets/_app.memory-bzvf2e28.js +0 -1
  351. package/static/dist/assets/_app.profiles-jpg7zmbn.js +0 -1
  352. package/static/dist/assets/_app.settings-mpuwlx4j.js +0 -1
  353. package/static/dist/assets/_app.settings._section-knz8rwoj.js +0 -1
  354. package/static/dist/assets/_app.skills-fwu1dkir.js +0 -1
  355. package/static/dist/assets/_app.tasks-h9gphtl3.js +0 -2
  356. package/static/dist/assets/_app.todos-hd1dnvrh.js +0 -1
  357. package/static/dist/assets/_app.workspaces-l8toyn5f.js +0 -1
  358. package/static/dist/assets/architecture-7GRP2DOG-cavnusaw.js +0 -1
  359. package/static/dist/assets/architectureDiagram-5GKGNRK7-k5a7v36n.js +0 -36
  360. package/static/dist/assets/channel-gozy3dp3.js +0 -1
  361. package/static/dist/assets/classDiagram-ZZMXUADV-n0vs2f2q.js +0 -1
  362. package/static/dist/assets/classDiagram-v2-VYDZK3BY-n0vs2f2q.js +0 -1
  363. package/static/dist/assets/endpoints-k8agnro8.js +0 -9
  364. package/static/dist/assets/eventmodeling-NTZA5JFV-j1y2ekec.js +0 -1
  365. package/static/dist/assets/flowDiagram-HODETNUW-g2cvcbm5.js +0 -1
  366. package/static/dist/assets/index-nfy0ezs6.css +0 -2
  367. package/static/dist/assets/index-nqed3oum.js +0 -11
  368. package/static/dist/assets/infoDiagram-27XIBGKW-fm06rj36.js +0 -2
  369. package/static/dist/assets/link-m90ahx3n.js +0 -1
  370. package/static/dist/assets/login-bw7m2fzo.js +0 -1
  371. package/static/dist/assets/matchContext-f8oy3lo7.js +0 -1
  372. package/static/dist/assets/mermaid-HWGCJPDP-njwcl62n.js +0 -1
  373. package/static/dist/assets/onboarding-ret37j1e.js +0 -1
  374. package/static/dist/assets/railroad-abnf-HS5TGJTU-m741owar.js +0 -1
  375. package/static/dist/assets/railroad-ebnf-LZEXJU2U-noa4fbia.js +0 -1
  376. package/static/dist/assets/railroad-peg-WCYAUIDC-o4bpw4nb.js +0 -1
  377. package/static/dist/assets/refresh-cw-k66keho6.js +0 -1
  378. package/static/dist/assets/search-j5ki6a9o.js +0 -1
  379. package/static/dist/assets/share._token-kg8w647b.js +0 -2
  380. package/static/dist/assets/sliders-horizontal-c4wya4dy.js +0 -1
  381. package/static/dist/assets/stateDiagram-v2-MP3YSRHH-k2wg2645.js +0 -1
  382. package/static/dist/assets/swimlanesDiagram-VR7AAH4N-n5kvjtgr.js +0 -8
  383. package/static/dist/assets/tabBuild-bjmyvmn5.js +0 -56
  384. package/static/dist/assets/todoStore-kjk5l0sy.js +0 -1
  385. package/static/dist/assets/useForm-ocxu460f.js +0 -1
  386. package/static/dist/assets/useLocale-fzy5jhg6.js +0 -1
@@ -3,9 +3,11 @@
3
3
  * `_merge_display_messages_after_agent_result`, `_message_identity`,
4
4
  * `_extract_tool_calls_from_messages`, `_build_partial_message`).
5
5
  */
6
- import { buildActiveTurnToken } from '../redact.js';
6
+ import { buildActiveTurnToken, completedToolIndex } from '../redact.js';
7
+ import { RunJournal } from './journal.js';
7
8
  import { str } from '../util.js';
8
9
  import { stripAttachedFilesMarker } from './session.js';
10
+ import { decidedEditDiff, toolEditDiff, toolName } from './tool-display.js';
9
11
  export const WORKSPACE_PREFIX_RE = /^\s*\[Workspace::v1:\s*(?:\\.|[^\]\\])+\]\s*/;
10
12
  const LEGACY_WORKSPACE_PREFIX_RE = /^\s*\[Workspace:[^\]]+\]\s*/;
11
13
  const TOOL_RESULT_SNIPPET_MAX = 4000;
@@ -55,7 +57,7 @@ export function messageText(content) {
55
57
  }
56
58
  return str(content);
57
59
  }
58
- export function messageIdentity(msg) {
60
+ export function messageIdentity(msg, textLimit = 500) {
59
61
  if (!isDict(msg))
60
62
  return null;
61
63
  const role = str(msg.role);
@@ -75,7 +77,7 @@ export function messageIdentity(msg) {
75
77
  return null;
76
78
  }
77
79
  const toolCalls = Array.isArray(msg.tool_calls) ? msg.tool_calls : [];
78
- return JSON.stringify([role, text.split(/\s+/).join(' ').trim().slice(0, 500), str(msg.tool_call_id), JSON.stringify(sortKeysDeep(toolCalls))]);
80
+ return JSON.stringify([role, text.split(/\s+/).join(' ').trim().slice(0, textLimit), str(msg.tool_call_id), JSON.stringify(sortKeysDeep(toolCalls))]);
79
81
  }
80
82
  function sortKeysDeep(value) {
81
83
  if (Array.isArray(value))
@@ -96,13 +98,43 @@ export function messagesHavePrefix(messages, prefix) {
96
98
  return false;
97
99
  return true;
98
100
  }
99
- export function isContextCompressionMarker(msg) {
101
+ const PRESERVED_TASK_LIST_PREFIX = '[your active task list was preserved across context compression]';
102
+ const hasPrefix = (text, prefix) => text.slice(0, prefix.length).toLowerCase() === prefix;
103
+ /**
104
+ * TAL-305: the one rule for the marker rows the Agent writes around context compaction. A flagged row, or a user or
105
+ * assistant row starting `[context compaction`, is a compaction summary; a user row starting with the preserved-task-list
106
+ * line is that list. Both ignore case and leading whitespace; prose that merely mentions compaction is no marker.
107
+ */
108
+ export function markerKind(msg) {
100
109
  if (!isDict(msg))
101
- return false;
110
+ return null;
102
111
  if (msg._context_compression_marker || msg._compression_marker || msg._compaction_marker)
103
- return true;
104
- const text = messageText(msg.content);
105
- return str(msg.role) === 'user' && text.startsWith('[CONTEXT COMPACTION]');
112
+ return 'context_compaction';
113
+ const role = str(msg.role);
114
+ const text = messageText(msg.content).trimStart();
115
+ if (role === 'user' && hasPrefix(text, PRESERVED_TASK_LIST_PREFIX))
116
+ return 'preserved_task_list';
117
+ if ((role === 'user' || role === 'assistant') && hasPrefix(text, '[context compaction'))
118
+ return 'context_compaction';
119
+ return null;
120
+ }
121
+ export function isContextCompressionMarker(msg) {
122
+ return markerKind(msg) === 'context_compaction';
123
+ }
124
+ /**
125
+ * TAL-305: stamps `_marker_kind` on marker rows, and `_marker_body` (the list without its marker line) on a preserved
126
+ * task list, so clients render a marker card instead of a message. Returns copies; stored rows are untouched.
127
+ */
128
+ export function withMarkerKinds(messages) {
129
+ return messages.map((m) => {
130
+ const kind = markerKind(m);
131
+ if (!kind || !isDict(m))
132
+ return m;
133
+ if (kind === 'context_compaction')
134
+ return { ...m, _marker_kind: kind };
135
+ const body = messageText(m.content).trim().slice(PRESERVED_TASK_LIST_PREFIX.length).trim();
136
+ return { ...m, _marker_kind: kind, _marker_body: body };
137
+ });
106
138
  }
107
139
  /** A user prompt as the user typed it: without the workspace prefix and the attached-files line the server adds. */
108
140
  const userPromptText = (text) => stripWorkspacePrefix(stripAttachedFilesMarker(text), true);
@@ -196,6 +228,17 @@ export function injectMaxIterationSummaryFallback(messages, finalResponse) {
196
228
  return messages;
197
229
  return [...messages, { role: 'assistant', content: finalResponse, _max_iteration_summary_fallback: true }];
198
230
  }
231
+ /**
232
+ * TAL-537: the user row the Agent appends to ask for that summary is its own prompt, neither shown nor sent back. It is
233
+ * the exhausted turn's last user row; any other row with the same text (one the user typed) stays.
234
+ */
235
+ export function withoutMaxIterationSummaryRequest(messages, request, msgText) {
236
+ const at = messages.findLastIndex((m) => m.role === 'user');
237
+ const row = messages[at];
238
+ if (!request || row?.content !== request || looksLikeCurrentUserTurn(row, msgText))
239
+ return messages;
240
+ return messages.filter((_, i) => i !== at);
241
+ }
199
242
  export function mergeDisplayMessagesAfterAgentResult(previousDisplay, previousContext, resultMessages, msgText, opts = {}) {
200
243
  const display = previousDisplay.filter((m) => !isContextCompressionMarker(m));
201
244
  const seenPartial = new Set();
@@ -314,10 +357,134 @@ function presentError(v) {
314
357
  return v.length > 0;
315
358
  return isDict(v) ? Object.keys(v).length > 0 : v === true;
316
359
  }
360
+ const RESULT_TEXT_KEYS = ['result', 'results', 'preview', 'content', 'text', 'message', 'summary', 'data', 'items'];
361
+ const RESULT_VIEW_MAX_DEPTH = 8;
362
+ const tryJson = (text) => { try {
363
+ return JSON.parse(text);
364
+ }
365
+ catch {
366
+ return undefined;
367
+ } };
368
+ const prettyJson = (value) => { try {
369
+ return JSON.stringify(value, null, 2);
370
+ }
371
+ catch {
372
+ return str(value);
373
+ } };
374
+ /** Line breaks and tabs a JSON-encoded result left escaped (`\n`, `\\n`), as the characters they stand for. */
375
+ const unescapeResultText = (text) => text.replace(/\r\n/g, '\n').replace(/\\{1,2}r\\{1,2}n|\\{1,2}n/g, '\n').replace(/\\{1,2}t/g, '\t');
376
+ /** The JSON a result string holds: as written, else with its escaped quotes undone; nested JSON strings unwrap three levels. */
377
+ function resultJson(text) {
378
+ const trimmed = text.trim();
379
+ let value = tryJson(trimmed);
380
+ if (value === undefined && trimmed.includes('\\"'))
381
+ value = tryJson(trimmed.replaceAll('\\"', '"'));
382
+ for (let i = 0; i < 3 && typeof value === 'string'; i += 1) {
383
+ const inner = tryJson(value.trim());
384
+ if (inner === undefined)
385
+ break;
386
+ value = inner;
387
+ }
388
+ return value;
389
+ }
390
+ /** A section's text: a string unescaped, a scalar as written, a structure as pretty JSON; `undefined` when blank. */
391
+ function sectionText(value) {
392
+ const text = typeof value === 'string' ? unescapeResultText(value) : typeof value === 'number' || typeof value === 'boolean' ? String(value) : value === null || value === undefined ? '' : prettyJson(value);
393
+ return text.trim() || undefined;
394
+ }
395
+ /** An exit code: an integer, or a string of one; anything else is dropped. */
396
+ function exitCodeOf(value) {
397
+ const n = typeof value === 'number' ? Math.trunc(value) : typeof value === 'string' && /^\s*[+-]?\d+\s*$/.test(value) ? Number(value) : NaN;
398
+ return Number.isSafeInteger(n) ? n : undefined;
399
+ }
400
+ /** A terminal result's sections; the exit code only when it is non-zero or nothing else would show. */
401
+ function terminalView(data) {
402
+ const view = {};
403
+ const stdout = sectionText(data.output) ?? sectionText(data.stdout);
404
+ const stderr = sectionText(data.stderr);
405
+ const error = sectionText(data.error);
406
+ if (stdout)
407
+ view.stdout = stdout;
408
+ if (stderr)
409
+ view.stderr = stderr;
410
+ if (error)
411
+ view.error = error;
412
+ const exit = exitCodeOf(data.exit_code ?? data.exitCode);
413
+ if (exit !== undefined && (exit !== 0 || !Object.keys(view).length))
414
+ view.exit_code = exit;
415
+ return view;
416
+ }
417
+ /** Every item of a list is a text part (`text`, `content` or `message`): their texts, one per line. */
418
+ function textParts(items) {
419
+ const texts = items.map((item) => (isDict(item) ? sectionText(item.text) ?? sectionText(item.content) ?? sectionText(item.message) : undefined));
420
+ return texts.length && texts.every(Boolean) ? texts.join('\n') : undefined;
421
+ }
422
+ const isEmptyView = (view) => Object.keys(view).length === 0;
423
+ function resultView(value, depth) {
424
+ if (typeof value === 'string') {
425
+ const parsed = depth < RESULT_VIEW_MAX_DEPTH ? resultJson(value) : undefined;
426
+ if (parsed !== undefined && typeof parsed !== 'string') {
427
+ const view = resultView(parsed, depth + 1);
428
+ if (!isEmptyView(view))
429
+ return view;
430
+ }
431
+ // Top-level text that is not JSON shows exactly as written; strings inside JSON are unescaped.
432
+ const text = depth === 0 && parsed === undefined ? value.trim() : sectionText(typeof parsed === 'string' ? parsed : value);
433
+ return text ? { text } : {};
434
+ }
435
+ if (Array.isArray(value)) {
436
+ const text = textParts(value) ?? sectionText(value);
437
+ return text ? { text } : {};
438
+ }
439
+ if (!isDict(value)) {
440
+ const text = sectionText(value);
441
+ return text ? { text } : {};
442
+ }
443
+ if (depth >= RESULT_VIEW_MAX_DEPTH)
444
+ return { text: prettyJson(value) };
445
+ // A process result: its output streams, error and exit code.
446
+ if (sectionText(value.output) ?? sectionText(value.stdout) ?? sectionText(value.stderr))
447
+ return terminalView(value);
448
+ for (const key of RESULT_TEXT_KEYS) {
449
+ const view = resultView(value[key], depth + 1);
450
+ if (!isEmptyView(view))
451
+ return view;
452
+ }
453
+ const terminal = terminalView(value);
454
+ if (!isEmptyView(terminal))
455
+ return terminal;
456
+ return Object.keys(value).length ? { text: prettyJson(value) } : {};
457
+ }
458
+ /**
459
+ * The server's one result view rule (TAL-315), ported from the iOS formatter so every client renders the same sections:
460
+ * a JSON result (nested JSON strings unwrapped, escaped line breaks undone) with `output`/`stdout`/`stderr` maps to its
461
+ * terminal sections; any other object shows the first readable `result`, `results`, `preview`, `content`, `text`,
462
+ * `message`, `summary`, `data` or `items`, else its `error` and exit code, else pretty JSON. A list of text parts joins
463
+ * by line; a scalar shows as text, and a result that is not JSON exactly as written. Each field is capped like the snippet
464
+ * (`limit`; TAL-331's whole-result read passes `Infinity`).
465
+ */
466
+ export function toolResultView(raw, limit = TOOL_RESULT_SNIPPET_MAX) {
467
+ // Content parts read as their text; a list with no text (`[{ title }]`) is the result itself.
468
+ const flat = Array.isArray(raw) ? messageText(raw) : raw;
469
+ const view = resultView(Array.isArray(raw) && !str(flat).trim() ? raw : flat, 0);
470
+ return Object.fromEntries(Object.entries(view).map(([key, field]) => [key, typeof field === 'string' ? toolResultSnippet(field, limit) : field]));
471
+ }
472
+ /** A view the server already decided (a built scene row, or one stored with its scene): its known fields, capped. */
473
+ export function decidedResultView(value) {
474
+ const view = {};
475
+ for (const key of ['text', 'stdout', 'stderr', 'error']) {
476
+ const field = value[key];
477
+ if (typeof field === 'string' && field)
478
+ view[key] = toolResultSnippet(field);
479
+ }
480
+ if (typeof value.exit_code === 'number' && Number.isSafeInteger(value.exit_code))
481
+ view.exit_code = value.exit_code;
482
+ return view;
483
+ }
317
484
  /**
318
485
  * The server's one tool outcome rule (TAL-313), for a live `raw_result` and a persisted tool-role content alike: a result
319
486
  * is an error when it parses to an object with a non-empty `error`, a non-zero numeric `exit_code` / `exitCode`, or
320
- * `success: false`. `result_text` is the result's display snippet.
487
+ * `success: false`. `result_text` is the result's display snippet; `result_view` its display sections (TAL-315).
321
488
  */
322
489
  export function toolOutcome(raw) {
323
490
  const value = Array.isArray(raw) ? messageText(raw) : raw;
@@ -332,7 +499,7 @@ export function toolOutcome(raw) {
332
499
  }
333
500
  const exit = isDict(data) ? data.exit_code ?? data.exitCode : undefined;
334
501
  const isError = isDict(data) && (presentError(data.error) || (typeof exit === 'number' && exit !== 0) || data.success === false);
335
- return { is_error: isError, result_text: toolResultSnippet(value) };
502
+ return { is_error: isError, result_text: toolResultSnippet(value), result_view: toolResultView(raw) };
336
503
  }
337
504
  const finiteOrNull = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : null);
338
505
  function nearestAssistantIdx(messages, msgIdx) {
@@ -444,7 +611,8 @@ export function extractToolCallsFromMessages(messages, liveToolCalls = [], recor
444
611
  * a call only the session-level list recorded joins the row at its `assistant_msg_idx`, a failed or cancelled turn's
445
612
  * partial snapshot shows the live calls it kept, and every call carries `done`
446
613
  * (answered, or outside the running turn), `is_error` (`toolOutcome` of its result, else the recorded value), `duration`
447
- * (recorded live, else `null`) and `result` (the result snippet, else `null`). Returns copies; stored rows are untouched.
614
+ * (recorded live, else `null`), `result` (the result snippet, else `null`) and `result_view` (its display sections, TAL-315,
615
+ * else `null`). Returns copies; stored rows are untouched.
448
616
  */
449
617
  export function withToolCallOutcomes(messages, sessionToolCalls, activeTurnId) {
450
618
  // Every identifier a stored call may carry (`ToolCallSchema`).
@@ -515,25 +683,70 @@ export function withToolCallOutcomes(messages, sessionToolCalls, activeTurnId) {
515
683
  if (!calls.length && !records.length)
516
684
  return m;
517
685
  const running = Boolean(activeTurnId) && m._turn_id === activeTurnId;
518
- const resolve = (id, rec, answered) => {
686
+ const resolve = (id, name, rec, answered) => {
519
687
  const reply = id ? replies.get(key(index, id)) : undefined;
520
688
  const outcome = reply ? { ...toolOutcome(reply.content), flagged: reply.is_error } : null;
689
+ // TAL-448: a file edit's change, from its full reply, else the one its live record decided.
690
+ const editDiff = reply ? toolEditDiff(name, reply.content) : decidedEditDiff(rec?.edit_diff);
521
691
  return {
692
+ ...(editDiff ? { edit_diff: editDiff } : {}),
522
693
  done: Boolean(reply) || answered || !running, is_error: outcome ? outcome.flagged || outcome.is_error : rec?.is_error === true,
523
694
  duration: finiteOrNull(rec?.duration), result: outcome ? outcome.result_text : typeof rec?.snippet === 'string' ? rec.snippet : null,
695
+ result_view: outcome ? outcome.result_view : isDict(rec?.result_view) ? decidedResultView(rec.result_view) : typeof rec?.snippet === 'string' ? toolResultView(rec.snippet) : null,
524
696
  };
525
697
  };
526
698
  const projected = [
527
- ...calls.map((call) => (isDict(call) ? { ...call, ...resolve(callId(call), recorded.get(callId(call)), false) } : call)),
699
+ ...calls.map((call) => (isDict(call) ? { ...call, ...resolve(callId(call), toolName(call), recorded.get(callId(call)), false) } : call)),
528
700
  ...records.map((tc) => ({
529
701
  ...(str(tc.tid) ? { id: str(tc.tid) } : {}), type: 'function', function: { name: str(tc.name), arguments: JSON.stringify(tc.args ?? {}) },
530
702
  // A session-level entry was answered; a live call says whether it completed.
531
- ...resolve(str(tc.tid), tc, typeof tc.done === 'boolean' ? tc.done : true),
703
+ ...resolve(str(tc.tid), tc.name, tc, typeof tc.done === 'boolean' ? tc.done : true),
532
704
  })),
533
705
  ];
534
706
  return { ...m, tool_calls: projected };
535
707
  });
536
708
  }
709
+ /** The prompt text naming every attached file by path after it (TAL-276), so an attachment-only turn still asks something. */
710
+ export function attachedFilesPrompt(text, attachments) {
711
+ const named = attachments.map((att) => (isDict(att) ? str(att.path).trim() : '')).filter(Boolean).map(escapeWorkspacePrefixPath);
712
+ return named.length ? `${text}\n\n[Attached files: ${named.join(', ')}]` : text;
713
+ }
714
+ const TOOL_IMAGE_PART_TYPES = new Set(['image', 'image_url', 'input_image']);
715
+ /**
716
+ * TAL-544, Python `_compact_image_parts_for_persistence`: a settled turn's tool-result images (browser and vision
717
+ * screenshots) become `[screenshot]` text, so saves, loads and later turns stop carrying the base64. A result is a `tool`
718
+ * row or an Anthropic-style `tool_result` block in a user row; the user's own attachments stay.
719
+ */
720
+ export function withoutToolImages(messages) {
721
+ const isImage = (p) => isDict(p) && TOOL_IMAGE_PART_TYPES.has(p.type);
722
+ const hasImage = (parts) => Array.isArray(parts) && parts.some(isImage);
723
+ const compact = (parts) => parts.map((p) => (isImage(p) ? { type: 'text', text: '[screenshot]' } : p));
724
+ const isImageResult = (p) => isDict(p) && p.type === 'tool_result' && hasImage(p.content);
725
+ const out = messages.map((m) => {
726
+ const content = m.content;
727
+ if (m.role === 'tool' && hasImage(content))
728
+ return { ...m, content: compact(content) };
729
+ if (m.role === 'user' && Array.isArray(content) && content.some(isImageResult)) {
730
+ return { ...m, content: content.map((p) => (isImageResult(p) ? { ...p, content: compact(p.content) } : p)) };
731
+ }
732
+ return m;
733
+ });
734
+ // The same array when nothing changed, so a caller can tell whether its rows were rewritten.
735
+ return out.some((m, i) => m !== messages[i]) ? out : messages;
736
+ }
737
+ /** A settled model context without an assistant row repeated back to back, and without tool-result images. */
738
+ export function dedupeContext(messages) {
739
+ const out = [];
740
+ let lastKey = null;
741
+ for (const m of withoutToolImages(messages)) {
742
+ const key = messageIdentity(m);
743
+ if (key !== null && key === lastKey && m.role === 'assistant')
744
+ continue;
745
+ out.push(m);
746
+ lastKey = key;
747
+ }
748
+ return out;
749
+ }
537
750
  /** Python `_build_partial_message`. */
538
751
  export function buildPartialMessage(contentText, reasoningText, toolCalls, now = Date.now() / 1000) {
539
752
  let stripped = '';
@@ -553,21 +766,196 @@ export function buildPartialMessage(contentText, reasoningText, toolCalls, now =
553
766
  msg._partial_tool_calls = [...toolCalls];
554
767
  return msg;
555
768
  }
556
- export function stripXmlToolCalls(text) {
557
- return text.replace(/<function_calls>[\s\S]*?<\/function_calls>/gi, '').replace(/<tool_call>[\s\S]*?<\/tool_call>/gi, '').trimEnd();
769
+ /**
770
+ * TAL-536: a dead run's journaled output as partial rows, in order: each row is a stretch of reasoning and prose with the
771
+ * tool calls that followed it, the shape a Stop's snapshot keeps. A run that ended in `done` gets its answer as a settled
772
+ * row: its last prose row, else the one the `done` frame's session carries (an answer that never streamed, like the
773
+ * tool-limit summary). `answered` says the run finished with one.
774
+ */
775
+ export function journalOutputRows(events, turnId) {
776
+ const rows = [];
777
+ const calls = [];
778
+ let text = '';
779
+ let reasoning = '';
780
+ let tools = [];
781
+ let at = 0;
782
+ const flush = () => {
783
+ const row = buildPartialMessage(text, reasoning, tools, at);
784
+ if (row)
785
+ rows.push({ ...row, _turn_id: turnId });
786
+ text = '';
787
+ reasoning = '';
788
+ tools = [];
789
+ };
790
+ for (const e of events) {
791
+ const p = isDict(e.payload) ? e.payload : {};
792
+ if ((e.event === 'token' || e.event === 'reasoning') && tools.length)
793
+ flush();
794
+ if (e.event === 'token')
795
+ text += str(p.text);
796
+ else if (e.event === 'reasoning')
797
+ reasoning += str(p.text);
798
+ else if (e.event === 'tool') {
799
+ const call = { name: p.name, args: p.args ?? {}, tid: str(p.id), done: false };
800
+ calls.push(call);
801
+ tools.push(call);
802
+ }
803
+ else if (e.event === 'tool_complete') {
804
+ // The record is shared with the row that holds it, so a completion after its row closed still lands.
805
+ const call = calls[completedToolIndex(calls, str(p.id), p.name)];
806
+ if (call)
807
+ Object.assign(call, { done: true, snippet: p.preview, is_error: p.is_error === true, duration: p.duration ?? null, ...(isDict(p.result_view) ? { result_view: p.result_view } : {}), ...(isDict(p.edit_diff) ? { edit_diff: p.edit_diff } : {}) });
808
+ }
809
+ else
810
+ continue;
811
+ at = e.created_at || at;
812
+ }
813
+ flush();
814
+ const done = RunJournal.selectAuthoritativeTerminalEvent(events);
815
+ if (done?.event !== 'done')
816
+ return { rows, answered: false };
817
+ // The last row answers only with visible prose and no calls after it (a reasoning-only segment is no answer).
818
+ const last = rows[rows.length - 1];
819
+ if (last && !last._partial_tool_calls && str(last.content)) {
820
+ delete last._partial;
821
+ return { rows, answered: true };
822
+ }
823
+ const settled = isDict(done.payload) && isDict(done.payload.session) && Array.isArray(done.payload.session.messages) ? done.payload.session.messages : [];
824
+ const answer = settled.findLast((m) => isDict(m) && m.role === 'assistant' && m._turn_id === turnId);
825
+ // Its visible prose, from string or structured content, as the scene's final answer reads it.
826
+ const content = answer && !answer._error && !(Array.isArray(answer.tool_calls) && answer.tool_calls.length) ? splitDisplayText(messageText(answer.content))[0] : '';
827
+ if (!content)
828
+ return { rows, answered: false };
829
+ rows.push({ role: 'assistant', content, timestamp: Math.trunc(done.created_at), _turn_id: turnId });
830
+ return { rows, answered: true };
831
+ }
832
+ const DSML = '(?:\\s*|\\s*DSML\\s*[||]\\s*)?';
833
+ /** Markdown code (fenced blocks, an unclosed one running to the end, and inline spans): markup written there is literal. */
834
+ function codeRanges(text) {
835
+ const code = /^ {0,3}(`{3,}|~{3,})[^\n]*(?:\n[\s\S]*?(?:\n {0,3}\1[^\n]*(?=\n|$)|(?![\s\S]))|(?![\s\S]))|(`+)(?!`)(?:[^\n]|\n(?!\n))*?(?<!`)\2(?!`)/gm;
836
+ return [...text.matchAll(code)].map((m) => [m.index, m.index + m[0].length]);
837
+ }
838
+ const inCode = (code, at) => code.some(([from, to]) => at >= from && at < to);
839
+ /** `re` matches removed, except the ones that start inside Markdown code. */
840
+ function removeOutsideCode(text, re) {
841
+ const code = codeRanges(text);
842
+ return text.replace(re, (match, ...rest) => (inCode(code, rest.find((x) => typeof x === 'number') ?? 0) ? match : ''));
558
843
  }
559
- /** Split inline `<think>` blocks out of assistant content (Python `_split_thinking_from_content`, reduced). */
560
- export function splitThinkingFromContent(content, existingReasoning = '') {
561
- const parts = [];
562
- const cleaned = content.replace(/<think(?:ing)?\b[^>]*>([\s\S]*?)<\/think(?:ing)?>/gi, (_m, inner) => { parts.push(inner.trim()); return ''; });
563
- const reasoning = [existingReasoning.trim(), ...parts].filter(Boolean).join('\n\n');
564
- return [cleaned.replace(/^\s+/, '').trimEnd(), reasoning];
844
+ /** Remove provider tool-call XML (`<function_calls>…`, DSML variants, `<tool_call>`), closed or cut off, that leaks into text. */
845
+ export function stripToolCallXml(text) {
846
+ const lo = text.toLowerCase();
847
+ if (!lo.includes('function_calls') && !lo.includes('dsml') && !lo.includes('<tool_call'))
848
+ return text;
849
+ return [
850
+ new RegExp(`<${DSML}function_calls>[\\s\\S]*?<\\/${DSML}function_calls>`, 'gi'),
851
+ new RegExp(`<${DSML}function_calls(?:>|$)[\\s\\S]*$`, 'i'),
852
+ /<tool_call>[\s\S]*?<\/tool_call>/gi,
853
+ /<tool_call>[\s\S]*$/i,
854
+ /<\s*|\s*DSML\s*[||]\s*/gi,
855
+ ].reduce(removeOutsideCode, text).replace(/^\s+/, '').trimEnd();
856
+ }
857
+ /** Inline thinking openers: `<think>`/`<thinking>` in any case and with attributes (as the Agent's own split), and the channel forms. */
858
+ const THINK_OPEN = /<think(?:ing)?(?:\s[^>]*)?>|<\|channel\|>thought|<\|turn\|>thinking/gi;
859
+ function thinkClose(open) {
860
+ if (open.toLowerCase().startsWith('<|channel|>'))
861
+ return /<channel\|>/gi;
862
+ if (open.toLowerCase().startsWith('<|turn|>'))
863
+ return /<turn\|>/gi;
864
+ return /<\/think(?:ing)?\s*>/gi;
865
+ }
866
+ /**
867
+ * Split assistant text into its visible prose and its inline thinking: every `<think>`, `<thinking>`,
868
+ * `<|channel|>thought` and `<|turn|>thinking` block anywhere in the text, an unterminated one running to the end.
869
+ * A tag written inside Markdown code is prose. Tool-call XML leaves both halves; both come back trimmed.
870
+ */
871
+ export function splitDisplayText(text) {
872
+ const code = codeRanges(text);
873
+ let content = '';
874
+ const reasoning = [];
875
+ let cursor = 0;
876
+ while (cursor < text.length) {
877
+ THINK_OPEN.lastIndex = cursor;
878
+ let open = THINK_OPEN.exec(text);
879
+ while (open && inCode(code, open.index))
880
+ open = THINK_OPEN.exec(text);
881
+ if (!open) {
882
+ content += text.slice(cursor);
883
+ break;
884
+ }
885
+ content += text.slice(cursor, open.index);
886
+ const body = open.index + open[0].length;
887
+ const closer = thinkClose(open[0]);
888
+ closer.lastIndex = body;
889
+ const close = closer.exec(text);
890
+ reasoning.push(text.slice(body, close?.index));
891
+ if (!close)
892
+ break;
893
+ cursor = close.index + close[0].length;
894
+ }
895
+ return [stripToolCallXml(content).replace(/^\s+/, '').trimEnd(), joinReasoning(reasoning)];
896
+ }
897
+ /**
898
+ * Reasoning parts as one display string: tool-call XML removed, blank-line joined, each part once. A part that is
899
+ * whole paragraphs of another (a settled row's `reasoning` that already took in its `reasoning_content`) is dropped.
900
+ */
901
+ export function joinReasoning(parts) {
902
+ const clean = parts.map((part) => stripToolCallXml(part).trim()).filter(Boolean);
903
+ const within = (outer, inner) => `\n\n${outer}\n\n`.includes(`\n\n${inner}\n\n`);
904
+ return clean.filter((p, i) => !clean.some((q, j) => j !== i && (q === p ? j < i : within(q, p)))).join('\n\n');
905
+ }
906
+ /** A message's reasoning fields as one string: `reasoning_content`, `reasoning` (text or a list of text parts), `thinking`. */
907
+ export function reasoningFieldsText(m) {
908
+ const listed = Array.isArray(m.reasoning) ? m.reasoning.map((part) => (isDict(part) ? reasoningBlockText(part) : str(part))) : [str(m.reasoning)];
909
+ return joinReasoning([str(m.reasoning_content), ...listed, str(m.thinking)]);
910
+ }
911
+ /**
912
+ * The one display shape of an assistant row: `content` without inline thinking or tool-call XML (typed `thinking` /
913
+ * `reasoning` parts leave a list), and every piece of reasoning in one `reasoning` string. `reasoning_content` and
914
+ * `thinking` are not sent. Idempotent; other rows pass through. A copy: stored rows and model history are untouched.
915
+ */
916
+ export function normalizeAssistantDisplay(message) {
917
+ if (!isDict(message) || message.role !== 'assistant')
918
+ return message;
919
+ const m = { ...message };
920
+ delete m.reasoning_content;
921
+ delete m.thinking;
922
+ delete m.reasoning;
923
+ const parts = [reasoningFieldsText(message)];
924
+ if (typeof m.content === 'string') {
925
+ const [content, inline] = splitDisplayText(m.content);
926
+ // Only text something was taken out of is rewritten: plain prose keeps its exact whitespace (an indented code block).
927
+ if (content !== m.content.trim())
928
+ m.content = content;
929
+ parts.push(inline);
930
+ }
931
+ else if (Array.isArray(m.content)) {
932
+ m.content = m.content.flatMap((part) => {
933
+ if (!isDict(part))
934
+ return [part];
935
+ if (isReasoningBlock(part)) {
936
+ parts.push(reasoningBlockText(part));
937
+ return [];
938
+ }
939
+ if (typeof part.text !== 'string')
940
+ return [part];
941
+ const [text, inline] = splitDisplayText(part.text);
942
+ parts.push(inline);
943
+ return text === part.text.trim() ? [part] : text ? [{ ...part, text }] : [];
944
+ });
945
+ }
946
+ const reasoning = joinReasoning(parts);
947
+ return (reasoning ? { ...m, reasoning } : m);
565
948
  }
566
949
  /**
567
950
  * Python `merge_session_messages_append_only`, bounded to its load-bearing rules: state.db rows never delete a local
568
951
  * row; rows that replay the sidecar (same role, timestamp, and content) are skipped; rows at or before the sidecar's
569
952
  * newest timestamp are already represented locally; a truncation watermark hides rows the user cut (0 blocks every
570
- * replay). Rows past the sidecar tail (a conversation continued from the CLI) are appended in state.db order.
953
+ * replay): rows at or before it, and, for a truncation cut, rows after it until the sidecar advances past it (TAL-504).
954
+ * A compression watermark only covers the compressed rows. Rows past the sidecar tail (a conversation continued from the
955
+ * CLI) are appended in state.db order.
956
+ * TAL-493: once a boundary or settled turn recorded the highest state.db id it read (`stateDbSeenId`) and the rows carry
957
+ * ids, identity replaces the timestamp rules: rows up to that id are covered, and every newer row not already in the
958
+ * sidecar is appended, even one a writer stamped before the newest local row or the watermark but committed later.
571
959
  * ponytail: the Python identity memo (api_content sidecars, message ids, workspace-prefix normalisation) is not ported;
572
960
  * add it if a mixed WebUI/CLI transcript shows duplicated turns.
573
961
  */
@@ -578,6 +966,31 @@ export function mergeSessionMessagesAppendOnly(sidecar, state, opts = {}) {
578
966
  return sidecar;
579
967
  const ts = (m) => { const n = Number(m.timestamp); return Number.isFinite(n) ? n : null; };
580
968
  const key = (m) => `${String(m.role)}\0${String(ts(m) ?? '')}\0${typeof m.content === 'string' ? m.content : JSON.stringify(m.content ?? null)}`;
969
+ const seenId = opts.stateDbSeenId;
970
+ if (typeof seenId === 'number' && Number.isFinite(seenId) && state.some((m) => stateDbRowId(m) !== null)) {
971
+ // Each local row stands for at most one new state.db row: a persisted copy by its id, any other by its key.
972
+ const localIds = new Set(sidecar.map(stateDbRowId));
973
+ const localKeys = new Map();
974
+ for (const m of sidecar)
975
+ if (stateDbRowId(m) === null)
976
+ localKeys.set(key(m), (localKeys.get(key(m)) ?? 0) + 1);
977
+ const merged = [...sidecar];
978
+ for (const m of state) {
979
+ const id = stateDbRowId(m);
980
+ if (id !== null && localIds.has(id))
981
+ continue;
982
+ // In state.db order, so a covered row matches its own local copy before a newer duplicate can.
983
+ const left = localKeys.get(key(m)) ?? 0;
984
+ if (left > 0) {
985
+ localKeys.set(key(m), left - 1);
986
+ continue;
987
+ }
988
+ if (id === null || id <= seenId)
989
+ continue;
990
+ merged.push(m);
991
+ }
992
+ return merged;
993
+ }
581
994
  if (!sidecar.length) {
582
995
  if (!hasWatermark) {
583
996
  const seen = new Set();
@@ -586,7 +999,7 @@ export function mergeSessionMessagesAppendOnly(sidecar, state, opts = {}) {
586
999
  }
587
1000
  if (watermark === 0)
588
1001
  return [];
589
- return state.filter((m) => { const t = ts(m); return t !== null && t > watermark; });
1002
+ return state.filter((m) => { const t = ts(m); return t !== null && (opts.compressedWatermark === true ? t > watermark : t <= watermark); });
590
1003
  }
591
1004
  const seen = new Set(sidecar.map(key));
592
1005
  let maxSidecar = null;
@@ -595,6 +1008,8 @@ export function mergeSessionMessagesAppendOnly(sidecar, state, opts = {}) {
595
1008
  if (t !== null && (maxSidecar === null || t > maxSidecar))
596
1009
  maxSidecar = t;
597
1010
  }
1011
+ // Until a new turn lands in the sidecar, every state.db row past the watermark is the deleted suffix.
1012
+ const advanced = !hasWatermark || opts.compressedWatermark === true || (maxSidecar !== null && maxSidecar > watermark);
598
1013
  const merged = [...sidecar];
599
1014
  for (const m of state) {
600
1015
  const k = key(m);
@@ -603,13 +1018,28 @@ export function mergeSessionMessagesAppendOnly(sidecar, state, opts = {}) {
603
1018
  const t = ts(m);
604
1019
  if (maxSidecar !== null && t !== null && t <= maxSidecar)
605
1020
  continue;
606
- if (hasWatermark && t !== null && t <= watermark)
1021
+ if (hasWatermark && t !== null && (t <= watermark || !advanced))
607
1022
  continue;
608
1023
  seen.add(k);
609
1024
  merged.push(m);
610
1025
  }
611
1026
  return merged;
612
1027
  }
1028
+ /** The Agent's `messages.id` a state.db row was read with, or null for a database without the column. */
1029
+ function stateDbRowId(m) {
1030
+ const id = m._state_db_row_id;
1031
+ return typeof id === 'number' && Number.isFinite(id) ? id : null;
1032
+ }
1033
+ /** TAL-493: the `state_db_seen_id` after reading `rows`: their highest id, or null when they carry none. */
1034
+ export function stateDbSeenId(rows) {
1035
+ let max = null;
1036
+ for (const m of rows) {
1037
+ const id = stateDbRowId(m);
1038
+ if (id !== null && (max === null || id > max))
1039
+ max = id;
1040
+ }
1041
+ return max;
1042
+ }
613
1043
  /**
614
1044
  * TAL-316: the merged transcript without the running turn's output, which the run journal replays from its start. The turn
615
1045
  * begins at its checkpointed prompt, else at its first stamped row or the first state.db row appended past the sidecar
@@ -644,6 +1074,46 @@ export function attachmentObjects(items) {
644
1074
  export function withAttachmentObjects(messages) {
645
1075
  return messages.map((m) => (isDict(m) && Array.isArray(m.attachments) && m.attachments.some((a) => typeof a === 'string') ? { ...m, attachments: attachmentObjects(m.attachments) } : m));
646
1076
  }
1077
+ /** A scene's prose rows with `display_text` and `media` where `project` rewrites their text; the same array when none does. */
1078
+ export function withSceneRowMedia(rows, project) {
1079
+ if (!Array.isArray(rows))
1080
+ return rows;
1081
+ let changed = false;
1082
+ const out = rows.map((row) => {
1083
+ if (!isDict(row) || row.role !== 'prose' || typeof row.text !== 'string')
1084
+ return row;
1085
+ const projected = project(row.text);
1086
+ if (!projected)
1087
+ return row;
1088
+ changed = true;
1089
+ return { ...row, display_text: projected.text, media: projected.media };
1090
+ });
1091
+ return changed ? out : rows;
1092
+ }
1093
+ /**
1094
+ * TAL-186: assistant rows with `_display_content` and `_media`, and their scenes with `final_answer_display` /
1095
+ * `final_answer_media` and prose rows' `display_text` / `media`, wherever `project` rewrites media references in the
1096
+ * text. Runs after scene hydration and before body excerpts, which are cut from the display text. Returns copies.
1097
+ */
1098
+ export function withDisplayMedia(messages, project) {
1099
+ return messages.map((m) => {
1100
+ if (!isDict(m) || m.role !== 'assistant')
1101
+ return m;
1102
+ let out = m;
1103
+ const projected = project(messageText(m.content));
1104
+ if (projected)
1105
+ out = { ...out, _display_content: projected.text, _media: projected.media };
1106
+ const scene = m._anchor_activity_scene;
1107
+ if (isDict(scene)) {
1108
+ const final = typeof scene.final_answer === 'string' ? project(scene.final_answer) : null;
1109
+ const rows = withSceneRowMedia(scene.activity_rows, project);
1110
+ if (final || rows !== scene.activity_rows) {
1111
+ out = { ...out, _anchor_activity_scene: { ...scene, activity_rows: rows, ...(final ? { final_answer_display: final.text, final_answer_media: final.media } : {}) } };
1112
+ }
1113
+ }
1114
+ return out;
1115
+ });
1116
+ }
647
1117
  /** TAL-456: a settled user or assistant body longer than this ships a collapsed excerpt for clients to render. */
648
1118
  export const BODY_EXCERPT_LIMIT = 3000;
649
1119
  const graphemes = new Intl.Segmenter(undefined, { granularity: 'grapheme' });
@@ -663,7 +1133,7 @@ function bodyExcerpt(text) {
663
1133
  return head.slice(0, cut).trimEnd();
664
1134
  }
665
1135
  /**
666
- * TAL-456: stamps `_display_excerpt` and `_display_truncated` on settled user and assistant rows whose text is longer than
1136
+ * TAL-456: stamps `_display_excerpt` and `_display_truncated` on settled user and assistant rows whose display text is longer than
667
1137
  * `BODY_EXCERPT_LIMIT`, and `final_answer_excerpt` on a settled scene whose final answer is, so clients render a bounded
668
1138
  * excerpt instead of laying out the whole body. Full text stays for copy and edit; the running turn's rows are left alone.
669
1139
  * Runs after scene hydration. Returns copies; stored rows are untouched.
@@ -675,12 +1145,13 @@ export function withBodyExcerpts(messages, activeTurnId) {
675
1145
  if (activeTurnId && m._turn_id === activeTurnId)
676
1146
  return m;
677
1147
  let out = m;
678
- const text = messageText(m.content);
1148
+ const text = typeof m._display_content === 'string' ? m._display_content : messageText(m.content);
679
1149
  if (text.length > BODY_EXCERPT_LIMIT)
680
1150
  out = { ...out, _display_excerpt: bodyExcerpt(text), _display_truncated: true };
681
1151
  const scene = m._anchor_activity_scene;
682
- if (isDict(scene) && typeof scene.final_answer === 'string' && scene.final_answer.length > BODY_EXCERPT_LIMIT) {
683
- out = { ...out, _anchor_activity_scene: { ...scene, final_answer_excerpt: bodyExcerpt(scene.final_answer) } };
1152
+ const finalAnswer = isDict(scene) ? (typeof scene.final_answer_display === 'string' ? scene.final_answer_display : scene.final_answer) : null;
1153
+ if (isDict(scene) && typeof finalAnswer === 'string' && finalAnswer.length > BODY_EXCERPT_LIMIT) {
1154
+ out = { ...out, _anchor_activity_scene: { ...scene, final_answer_excerpt: bodyExcerpt(finalAnswer) } };
684
1155
  }
685
1156
  return out;
686
1157
  });
@@ -758,7 +1229,8 @@ export function stoppedTurnContext(previousContext, checkpoint, prompt, msgText,
758
1229
  // A tool result is a `tool` row or an Anthropic-style user row of `tool_result` blocks. A steer the Agent applied is
759
1230
  // neither a prompt nor a result: the boundary goes after it, and it never makes the turn look unanswered.
760
1231
  const toolResult = (m) => m?.role === 'tool' || (m?.role === 'user' && Array.isArray(m.content) && m.content.some((part) => isDict(part) && part.type === 'tool_result'));
761
- const tail = () => sanitizeMessagesForApi(rows.filter((m) => agentSteerText(m) === null)).at(-1);
1232
+ // The next turn's own history projection, so an answer the Agent replays from `api_content` counts.
1233
+ const tail = () => sanitizeMessagesForApi(rows.filter((m) => agentSteerText(m) === null), { preserveApiContent: true }).at(-1);
762
1234
  if (toolResult(tail()))
763
1235
  rows.push({ role: 'assistant', content: unsettled || 'Operation interrupted.' });
764
1236
  else if (unsettled)
@@ -783,6 +1255,10 @@ export function agentSteerText(m) {
783
1255
  return inner.trim();
784
1256
  return m.display_kind === 'steer' ? content.trim() : null;
785
1257
  }
1258
+ /** The Agent's `api_content` replay sidecar: the exact bytes a user/assistant row was sent as, else undefined. */
1259
+ function apiContent(msg) {
1260
+ return (msg.role === 'user' || msg.role === 'assistant') && typeof msg.api_content === 'string' && msg.api_content ? msg.api_content : undefined;
1261
+ }
786
1262
  /** Python `_is_reasoning_only_assistant_message`: a display-only Thinking card with no visible reply. */
787
1263
  function isReasoningOnlyAssistant(msg) {
788
1264
  if (msg.role !== 'assistant' || (Array.isArray(msg.tool_calls) && msg.tool_calls.length))
@@ -807,8 +1283,10 @@ function stripOobBlocks(content) {
807
1283
  * reasoning-only assistants), orphaned tool rows and unanswered tool calls, keeps only API-safe keys, strips consumed
808
1284
  * out-of-band blocks, and keeps a cancelled (`_recovered`) user prompt only where it separates two assistant turns —
809
1285
  * otherwise the neighbours fuse cleanly or the prompt is stale, and replaying it would answer it again.
1286
+ * `preserveApiContent` (Python `_sanitize_messages_for_agent`) keeps the Agent's `api_content` replay sidecar on
1287
+ * user/assistant rows for turn history; compression and other projections strip it.
810
1288
  */
811
- export function sanitizeMessagesForApi(messages) {
1289
+ export function sanitizeMessagesForApi(messages, { preserveApiContent = false } = {}) {
812
1290
  // Calls are OpenAI `tool_calls` or Anthropic-style `tool_use` content blocks; results name them by `tool_call_id`,
813
1291
  // `tool_use_id`, or a user row's `tool_result` blocks.
814
1292
  const toolUseIds = (msg) => (Array.isArray(msg.content) ? msg.content.flatMap((part) => (isDict(part) && part.type === 'tool_use' && str(part.id) ? [str(part.id)] : [])) : []);
@@ -828,7 +1306,9 @@ export function sanitizeMessagesForApi(messages) {
828
1306
  for (const msg of messages) {
829
1307
  if (!msg || typeof msg !== 'object')
830
1308
  continue;
831
- if (isReasoningOnlyAssistant(msg))
1309
+ // A reasoning-only clean stop the Agent answered from `api_content` is a reply, not a Thinking card.
1310
+ const replay = preserveApiContent ? apiContent(msg) : undefined;
1311
+ if (isReasoningOnlyAssistant(msg) && !replay)
832
1312
  continue;
833
1313
  // The Agent already received a steer mid-turn; its persisted row is display-only.
834
1314
  if (msg._error || isDict(msg._steer))
@@ -844,6 +1324,8 @@ export function sanitizeMessagesForApi(messages) {
844
1324
  const sanitized = Object.fromEntries(Object.entries(msg).filter(([k]) => API_SAFE_MSG_KEYS.has(k)));
845
1325
  if (Array.isArray(sanitized.tool_calls) && !sanitized.tool_calls.length)
846
1326
  Reflect.deleteProperty(sanitized, 'tool_calls');
1327
+ if (replay)
1328
+ sanitized.api_content = replay;
847
1329
  if (recovered)
848
1330
  sanitized._recovered = true;
849
1331
  if ('content' in sanitized)
@@ -866,7 +1348,7 @@ export function sanitizeMessagesForApi(messages) {
866
1348
  continue;
867
1349
  msg = { ...msg, content: parts };
868
1350
  // Reasoning left behind by a dropped call is not an answer either.
869
- if (isReasoningOnlyAssistant(msg))
1351
+ if (isReasoningOnlyAssistant(msg) && !apiContent(msg))
870
1352
  continue;
871
1353
  }
872
1354
  if (msg.role === 'assistant' && Array.isArray(msg.tool_calls) && msg.tool_calls.length) {
@@ -895,4 +1377,33 @@ export function sanitizeMessagesForApi(messages) {
895
1377
  }
896
1378
  return final;
897
1379
  }
1380
+ const isNativeImagePart = (part) => isDict(part) && (part.type === 'image_url' || 'image_url' in part);
1381
+ /** True when any row's content carries a native `image_url` part. */
1382
+ export function hasNativeImages(messages) {
1383
+ return messages.some((msg) => Array.isArray(msg.content) && msg.content.some(isNativeImagePart));
1384
+ }
1385
+ /**
1386
+ * Python `_strip_native_image_parts_from_content` (TAL-545): the history for a turn whose image mode resolved to text.
1387
+ * Native `image_url` parts are dropped and the text kept; a lone text part collapses to its string, as a text-only
1388
+ * provider rejects a replayed image before the Agent's own strip-and-retry guard can recover.
1389
+ */
1390
+ export function withoutNativeImages(messages) {
1391
+ return messages.map((msg) => {
1392
+ if (!Array.isArray(msg.content) || !msg.content.some(isNativeImagePart))
1393
+ return msg;
1394
+ const parts = msg.content.filter((part) => isDict(part) && !isNativeImagePart(part));
1395
+ const content = !parts.length ? '' : parts.length === 1 && parts[0].type === 'text' ? str(parts[0].text) : parts;
1396
+ return { ...msg, content };
1397
+ });
1398
+ }
1399
+ /**
1400
+ * TAL-545: the image-free history is one request's projection, never the session's. The Agent hands the history it got
1401
+ * back ahead of its own rows, so each row it returned exactly as sent (`sent[i]`, stripped from `original[i]`) is swapped
1402
+ * back for the original; a row the Agent rewrote stays its own.
1403
+ */
1404
+ export function withNativeImagesRestored(rows, sent, original) {
1405
+ if (sent === original)
1406
+ return rows;
1407
+ return rows.map((row, i) => (i < sent.length && sent[i] !== original[i] && JSON.stringify(row) === JSON.stringify(sent[i]) ? original[i] : row));
1408
+ }
898
1409
  //# sourceMappingURL=merge.js.map