synabun 2.0.9 → 2.0.10

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 (252) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +1 -1
  3. package/THIRD-PARTY-LICENSES.md +46 -4
  4. package/launcher.mjs +9 -3
  5. package/lib/start-launcher.js +6 -1
  6. package/mcp-server/package-lock.json +2 -2
  7. package/mcp-server/package.json +1 -1
  8. package/neural-interface/i18n/en.json +218 -7
  9. package/neural-interface/i18n/pt-BR.json +220 -9
  10. package/neural-interface/lib/account-usage.js +5 -3
  11. package/neural-interface/lib/app-controls-api.js +161 -0
  12. package/neural-interface/lib/assistant-api.js +21 -7
  13. package/neural-interface/lib/assistant-brains/opencode.js +13 -1
  14. package/neural-interface/lib/assistant-brains/opencode2-browser-policy/index.js +44 -0
  15. package/neural-interface/lib/assistant-brains/opencode2-route-gate/index.js +57 -0
  16. package/neural-interface/lib/assistant-catalog.js +17 -2
  17. package/neural-interface/lib/assistant-dispatch.js +3 -0
  18. package/neural-interface/lib/assistant-envelope.js +19 -1
  19. package/neural-interface/lib/assistant-runtime.js +127 -8
  20. package/neural-interface/lib/claude-model-catalog.js +64 -14
  21. package/neural-interface/lib/codex-settings-schema.js +109 -47
  22. package/neural-interface/lib/editor-completion-opencode.js +132 -35
  23. package/neural-interface/lib/exec-loop-command.js +8 -1
  24. package/neural-interface/lib/launch-cache.js +293 -0
  25. package/neural-interface/lib/login-start.js +524 -0
  26. package/neural-interface/lib/native-loop-providers.js +18 -3
  27. package/neural-interface/lib/native-loop-runtime.js +19 -0
  28. package/neural-interface/lib/opencode-db-schema.js +75 -0
  29. package/neural-interface/lib/opencode-history.js +16 -1
  30. package/neural-interface/lib/opencode-protocol.js +173 -0
  31. package/neural-interface/lib/opencode-provider-view.js +81 -0
  32. package/neural-interface/lib/opencode-runtime-config.js +10 -3
  33. package/neural-interface/lib/opencode-serve-records.js +606 -0
  34. package/neural-interface/lib/opencode-v2-client.js +33 -6
  35. package/neural-interface/lib/opencode-v2-ws-requests.js +19 -5
  36. package/neural-interface/lib/opencode-worker-activity.js +229 -0
  37. package/neural-interface/lib/opencode2-client.js +760 -0
  38. package/neural-interface/lib/opencode2-proxy.js +116 -0
  39. package/neural-interface/lib/opencode2-translate.js +1008 -0
  40. package/neural-interface/lib/process-identity.js +361 -0
  41. package/neural-interface/lib/session-cache-builder.js +26 -5
  42. package/neural-interface/lib/session-title-generator.js +18 -0
  43. package/neural-interface/lib/style-guide/api.js +4 -3
  44. package/neural-interface/lib/untrusted-file-headers.js +27 -0
  45. package/neural-interface/package-lock.json +1343 -45
  46. package/neural-interface/package.json +5 -1
  47. package/neural-interface/public/index.html +2 -2
  48. package/neural-interface/public/shared/agent-runtime-options.js +55 -11
  49. package/neural-interface/public/shared/assistant/asst-agents-dock.js +172 -17
  50. package/neural-interface/public/shared/assistant/asst-api.js +11 -3
  51. package/neural-interface/public/shared/assistant/asst-brain-picker.js +195 -80
  52. package/neural-interface/public/shared/assistant/asst-composer.js +1 -1
  53. package/neural-interface/public/shared/assistant/asst-hosts.js +49 -2
  54. package/neural-interface/public/shared/assistant/asst-menu.js +77 -6
  55. package/neural-interface/public/shared/assistant/asst-panel.js +197 -33
  56. package/neural-interface/public/shared/assistant/asst-rack-styles.js +28 -10
  57. package/neural-interface/public/shared/assistant/asst-redact.js +186 -0
  58. package/neural-interface/public/shared/assistant/asst-render.js +382 -36
  59. package/neural-interface/public/shared/assistant/asst-run-stage.js +231 -0
  60. package/neural-interface/public/shared/assistant/asst-sanitize.js +193 -3
  61. package/neural-interface/public/shared/assistant/asst-scene-styles.js +273 -0
  62. package/neural-interface/public/shared/assistant/asst-scenes.js +528 -0
  63. package/neural-interface/public/shared/assistant/asst-sidepanel-styles.js +3 -15
  64. package/neural-interface/public/shared/assistant/asst-state.js +14 -0
  65. package/neural-interface/public/shared/assistant/asst-styles.js +43 -0
  66. package/neural-interface/public/shared/assistant/asst-tool-kinds.js +13 -1
  67. package/neural-interface/public/shared/cc-dropdown.js +480 -0
  68. package/neural-interface/public/shared/cdx/cdx-panel.js +13 -1
  69. package/neural-interface/public/shared/cdx/cdx-render.js +15 -3
  70. package/neural-interface/public/shared/cdx/cdx-styles.js +4 -0
  71. package/neural-interface/public/shared/cdx/cdx-tabs.js +149 -35
  72. package/neural-interface/public/shared/cp/cp-markdown.js +4 -57
  73. package/neural-interface/public/shared/data-cache.js +223 -0
  74. package/neural-interface/public/shared/desktop-bridge.js +11 -0
  75. package/neural-interface/public/shared/desktop-titlebar-view.js +78 -1
  76. package/neural-interface/public/shared/desktop-titlebar.js +214 -28
  77. package/neural-interface/public/shared/desktop-update-view.js +15 -5
  78. package/neural-interface/public/shared/file-links.js +73 -8
  79. package/neural-interface/public/shared/html-shell.js +74 -80
  80. package/neural-interface/public/shared/launch-prefetch.js +59 -0
  81. package/neural-interface/public/shared/ocp-v2/ocp-v2-caps.js +3 -2
  82. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +7 -2
  83. package/neural-interface/public/shared/ocp-v2/ocp-v2-modelpicker.js +54 -17
  84. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +93 -2
  85. package/neural-interface/public/shared/ocp-v2/ocp-v2-projectbar.js +3 -2
  86. package/neural-interface/public/shared/ocp-v2/ocp-v2-providers.js +11 -9
  87. package/neural-interface/public/shared/ocp-v2/ocp-v2-rehydrate.js +15 -4
  88. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +1 -1
  89. package/neural-interface/public/shared/ocp-v2/ocp-v2-send-logic.js +401 -0
  90. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +16 -9
  91. package/neural-interface/public/shared/ocp-v2/ocp-v2-sessions-logic.js +60 -0
  92. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +48 -19
  93. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +195 -19
  94. package/neural-interface/public/shared/opencode-providers.js +39 -0
  95. package/neural-interface/public/shared/schedules-view.js +318 -0
  96. package/neural-interface/public/shared/settings/settings-app-pane.js +335 -0
  97. package/neural-interface/public/shared/settings/settings-app.js +243 -0
  98. package/neural-interface/public/shared/settings/settings-data.js +88 -0
  99. package/neural-interface/public/shared/settings/settings-ia.js +17 -0
  100. package/neural-interface/public/shared/sidepanel-session-status.js +49 -0
  101. package/neural-interface/public/shared/styles.css +540 -371
  102. package/neural-interface/public/shared/ui-assistant.js +27 -5
  103. package/neural-interface/public/shared/ui-automation-studio.js +67 -27
  104. package/neural-interface/public/shared/ui-bookmarks.js +2 -1
  105. package/neural-interface/public/shared/ui-claude-panel.js +76 -8
  106. package/neural-interface/public/shared/ui-codex-panel.js +1 -0
  107. package/neural-interface/public/shared/ui-detail.js +6 -6
  108. package/neural-interface/public/shared/ui-file-links.js +1 -0
  109. package/neural-interface/public/shared/ui-image-gallery.js +6 -5
  110. package/neural-interface/public/shared/ui-invite.js +18 -2
  111. package/neural-interface/public/shared/ui-link.js +9 -9
  112. package/neural-interface/public/shared/ui-menubar.js +232 -33
  113. package/neural-interface/public/shared/ui-native-loop-router.js +2 -0
  114. package/neural-interface/public/shared/ui-opencode-panel-v2.js +1 -0
  115. package/neural-interface/public/shared/ui-schedules-studio.js +2099 -869
  116. package/neural-interface/public/shared/ui-sessions.js +15 -4
  117. package/neural-interface/public/shared/ui-settings.js +87 -36
  118. package/neural-interface/public/shared/ui-sidebar.js +4 -4
  119. package/neural-interface/public/shared/ui-sidepanel-session-entry.js +13 -0
  120. package/neural-interface/public/shared/ui-sidepanel-sessions.js +55 -4
  121. package/neural-interface/public/shared/ui-sidepanel-tray.js +84 -8
  122. package/neural-interface/public/shared/ui-skills.js +70 -12
  123. package/neural-interface/public/shared/ui-sync.js +7 -0
  124. package/neural-interface/public/shared/ui-terminal.js +36 -20
  125. package/neural-interface/public/shared/ui-trash.js +8 -1
  126. package/neural-interface/public/shared/ui-tutorial-steps.js +7 -3
  127. package/neural-interface/public/shared/ui-tutorial.js +15 -5
  128. package/neural-interface/public/shared/ui-update.js +83 -14
  129. package/neural-interface/public/shared/ui-whiteboard.js +2 -1
  130. package/neural-interface/public/variant/2d/main.js +4 -1
  131. package/neural-interface/public/variant/3d/main.js +3 -0
  132. package/neural-interface/public/vendor/highlight/LICENSE +29 -0
  133. package/neural-interface/public/vendor/highlight/github-dark-dimmed-11.12.0.min.css +9 -0
  134. package/neural-interface/public/vendor/highlight/highlight-11.12.0.min.js +1262 -0
  135. package/neural-interface/public/vendor/highlight/manifest.json +5 -0
  136. package/neural-interface/public/vendor/html2canvas/LICENSE +22 -0
  137. package/neural-interface/public/vendor/html2canvas/html2canvas-1.4.1.min.js +20 -0
  138. package/neural-interface/public/vendor/html2canvas/manifest.json +4 -0
  139. package/neural-interface/public/vendor/marked/LICENSE.md +44 -0
  140. package/neural-interface/public/vendor/marked/manifest.json +4 -0
  141. package/neural-interface/public/vendor/marked/marked-14.1.4.esm.js +2502 -0
  142. package/neural-interface/public/vendor/mermaid/LICENSE +21 -0
  143. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/abnfDiagram-PAR5IXJI.mjs +1 -0
  144. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/architecture-7GRP2DOG-M7G3SWCO.mjs +1 -0
  145. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/architectureDiagram-2PNWDENW.mjs +36 -0
  146. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/blockDiagram-PUWGUBOA.mjs +129 -0
  147. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/c4Diagram-3NLNHZP5.mjs +38 -0
  148. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-2NMSUPJH.mjs +1 -0
  149. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-32MCUW2P.mjs +206 -0
  150. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-3NPNU5GP.mjs +1 -0
  151. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-3SMGFRGC.mjs +1 -0
  152. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-4CAJECRU.mjs +14 -0
  153. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-4OADUZ5Q.mjs +10 -0
  154. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-5IMINLNL.mjs +1 -0
  155. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-5IQCK5RY.mjs +3 -0
  156. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-5MVFYWFU.mjs +1 -0
  157. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-AQ6EADP3.mjs +1 -0
  158. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-AZZRMDJM.mjs +15 -0
  159. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-CLS4B6BI.mjs +168 -0
  160. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-DZP67EKU.mjs +70 -0
  161. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-EICP7K5X.mjs +1 -0
  162. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-GDY2UAB4.mjs +1 -0
  163. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-H7VHZCWX.mjs +1 -0
  164. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-IJBDOHL6.mjs +1 -0
  165. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-JFFVMJIA.mjs +1 -0
  166. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-JQRUD6KW.mjs +1 -0
  167. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-JZGDI5JK.mjs +1 -0
  168. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-KD6SPIPN.mjs +2 -0
  169. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-NF366YI6.mjs +231 -0
  170. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-NUDPKBEF.mjs +32 -0
  171. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-OJINMHTS.mjs +1 -0
  172. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-Q6NGPIAH.mjs +2 -0
  173. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-QJSWEUOL.mjs +132 -0
  174. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-R7JDMUKV.mjs +1 -0
  175. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-RCXAM2GK.mjs +1 -0
  176. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-RT4MAWR3.mjs +1 -0
  177. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-SFMINX4W.mjs +1 -0
  178. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-SLDQ463B.mjs +88 -0
  179. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-STOV2HOB.mjs +1 -0
  180. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-T5OCTHI4.mjs +1 -0
  181. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-TCEHX7R3.mjs +1 -0
  182. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-VQC2EELJ.mjs +1 -0
  183. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-VRZKL3EH.mjs +1 -0
  184. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-VTHDQ7BV.mjs +1 -0
  185. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-VY2OSTJR.mjs +2 -0
  186. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-WUBWJARI.mjs +1 -0
  187. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-YBMQ5JGJ.mjs +2 -0
  188. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-YDMLL4PJ.mjs +1 -0
  189. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-Z6V44ARX.mjs +62 -0
  190. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZJJ7VQJP.mjs +339 -0
  191. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZNLK7HRR.mjs +1 -0
  192. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZQN7UHIL.mjs +1 -0
  193. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZS6VBONO.mjs +174 -0
  194. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZUNWM646.mjs +1 -0
  195. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/classDiagram-FGAMPOII.mjs +1 -0
  196. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/classDiagram-v2-AI5RJEK4.mjs +1 -0
  197. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/cose-bilkent-Q72C2OJ3.mjs +1 -0
  198. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/cynefin-OW5HDTMX-6OCTWOGG.mjs +1 -0
  199. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/cynefinDiagram-2OOWUY4M.mjs +62 -0
  200. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/dagre-MPVFI544.mjs +4 -0
  201. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-AE4NWXFW.mjs +24 -0
  202. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-BUJID5UG.mjs +41 -0
  203. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-C44CZ5NI.mjs +24 -0
  204. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-EYOLEXXS.mjs +30 -0
  205. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-P7B6PWOJ.mjs +3 -0
  206. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/ebnfDiagram-FTPZ3PV6.mjs +1 -0
  207. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/erDiagram-H3ERR26V.mjs +99 -0
  208. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/eventmodeling-NTZA5JFV-WKSWQHFS.mjs +1 -0
  209. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/flowDiagram-YHGXBVSY.mjs +1 -0
  210. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/ganttDiagram-RWFTB7ED.mjs +292 -0
  211. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/gitGraph-4MIJSDKK-JH2HKYUO.mjs +1 -0
  212. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/gitGraphDiagram-MAR6GKOP.mjs +106 -0
  213. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/info-A6RAGUB7-3TX3OFWW.mjs +1 -0
  214. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/infoDiagram-LV547AFH.mjs +2 -0
  215. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/ishikawaDiagram-PUJQGNDS.mjs +70 -0
  216. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/journeyDiagram-EW5QJT4W.mjs +139 -0
  217. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/kanban-definition-JSQERAZB.mjs +89 -0
  218. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/katex-CZ4GXH2S.mjs +257 -0
  219. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/mindmap-definition-VBJCLQLM.mjs +96 -0
  220. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/packet-AYTQ26CC-MKII6P3Y.mjs +1 -0
  221. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/pegDiagram-7WILOQTJ.mjs +1 -0
  222. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/pie-WAS4IAKB-6B4OPMJ5.mjs +1 -0
  223. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/pieDiagram-EFA7CBSB.mjs +39 -0
  224. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/quadrantDiagram-WARVYA7P.mjs +7 -0
  225. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/radar-RG4KPBEZ-UPTR3XUL.mjs +1 -0
  226. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroad-74A4TZTK-MEFUPFMS.mjs +1 -0
  227. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroad-abnf-HS5TGJTU-R5YG2S5D.mjs +1 -0
  228. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroad-ebnf-LZEXJU2U-G5QEX2L6.mjs +1 -0
  229. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroad-peg-WCYAUIDC-H5I2ZRY2.mjs +1 -0
  230. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroadDiagram-5NIQVNLQ.mjs +1 -0
  231. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/requirementDiagram-SXREGOKZ.mjs +84 -0
  232. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/sankeyDiagram-VV74M2VP.mjs +40 -0
  233. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/sequenceDiagram-52ZFIFDL.mjs +162 -0
  234. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/sizeCapture-FGA5AGYP.mjs +1 -0
  235. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/stateDiagram-3QAKPIVI.mjs +1 -0
  236. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/stateDiagram-v2-HN2P4RDZ.mjs +1 -0
  237. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/swimlanes-QLVP42QD.mjs +1 -0
  238. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/swimlanesDiagram-JKPDBFCN.mjs +8 -0
  239. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/timeline-definition-GS45XAXW.mjs +120 -0
  240. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/treeView-Q6P3EWNA-ZRJGANNQ.mjs +1 -0
  241. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/treemap-WGGIJYW6-K64VN5AS.mjs +1 -0
  242. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/vennDiagram-QIQU2ZRJ.mjs +34 -0
  243. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/wardley-WFR3VGLG-D67OWCCH.mjs +1 -0
  244. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/wardleyDiagram-U6YNM66N.mjs +78 -0
  245. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/xychartDiagram-XJAZEDNM.mjs +7 -0
  246. package/neural-interface/public/vendor/mermaid/manifest.json +4 -0
  247. package/neural-interface/public/vendor/mermaid/mermaid-11.17.2.esm.min.mjs +14 -0
  248. package/neural-interface/scripts/build-vendor.mjs +84 -3
  249. package/neural-interface/server.js +1099 -286
  250. package/npm-shrinkwrap.json +2 -2
  251. package/package.json +1 -1
  252. package/skills/synabun/modules/schedule.md +1 -1
@@ -17,25 +17,41 @@
17
17
  // out. mascot() names the character on screen for the panel's director
18
18
  // (asst-mascot.js), which may hold its pose; stills are painted SVGs, never
19
19
  // rigs.
20
-
21
- import { assistantEventKey, fmtCost, fmtElapsed, isRemovableRun, isRunIdle, isTerminalRunStatus, modelShortName, providerShortLabel, routeTargetLabel, runTone, summarizeMailbox } from './asst-state.js';
20
+ // A dispatched run's card opens a stage while its worker works
21
+ // (asst-run-stage.js): the run's own mascot and scene, independent of the
22
+ // Assistant's turn, so a worker that outlives the turn keeps moving. Run
23
+ // cards are not journaled: a reload restores them from the runs the agents
24
+ // tray lists (restoreRuns), next to the call that dispatched them.
25
+ // The Assistant's own work on a direct route (an approved "do it here" route
26
+ // of a task class: research, browser, quick…) uses the same scenes: its
27
+ // racks' stages show the class's scene under the mascot, and the scene the
28
+ // bubble's call implies (a browser tool the browser, a web search research),
29
+ // for the turn the route runs in (noteDirectRoute, paintRackScene). A
30
+ // conversation opened again learns its route from the journal like any other
31
+ // event (no rack of the history moves: none is live) and lends it to the
32
+ // resumed work once its turn runs (lendRouteScene).
33
+
34
+ import { assistantEventKey, fmtCost, fmtElapsed, isRemovableRun, isTerminalRunStatus, modelShortName, providerShortLabel, routeTargetLabel, runTone, summarizeMailbox } from './asst-state.js';
22
35
  import { createRouteLine } from './asst-route.js';
23
36
  import { closeMenu, isMenuOpen } from './asst-menu.js';
24
37
  import { computerToolName, describeComputerAction, frameUrls, markerPercent, openFrameLightbox, parseComputerResult, swapImage } from './asst-computer.js';
25
38
  import * as mascotLib from '../synabun-mascot.js';
26
39
  import { isReducedMotion, subscribe } from '../synabun-ticker.js';
27
- import { KIND_SPECS, callOutcome, commandOf, editDiff, editPaths, fmtDuration, groupSubject, kindOf, maskEcho, mcpServerOf, middleEllipsis, planState, plural, prettyServer, receiptSentence, redactInput, resultDetail, sayCall, shouldFoldNarration, stationAggregate, stationKey, targetKey, toolWords, verbText } from './asst-tool-kinds.js';
40
+ import { KIND_SPECS, callOutcome, commandOf, editDiff, editPaths, fmtDuration, groupSubject, joinCut, kindOf, maskEcho, mcpServerOf, middleEllipsis, planState, plural, prettyServer, receiptSentence, redactInput, resultDetail, sayCall, shouldFoldNarration, stationAggregate, stationKey, targetKey, toolWords, verbText } from './asst-tool-kinds.js';
28
41
  import { injectRackStyles } from './asst-rack-styles.js';
42
+ import { injectSceneStyles } from './asst-scene-styles.js';
43
+ import { activityScene, compactActivity, isLivePhase, runChip, sceneOfClass, sceneSvg, stagePhase } from './asst-scenes.js';
44
+ import { STAGE_TIMING, createWorkerStage } from './asst-run-stage.js';
29
45
  import { formatRunTokens } from './asst-usage.js';
30
46
  import { sanitizeHtmlString, sanitizeInto } from './asst-sanitize.js';
31
47
  import { automationView } from './asst-automation.js';
32
48
 
33
- // ── Markdown (lazy marked, same CDN build as the sidepanels) ───────────────
49
+ // ── Markdown (lazy marked, the vendored build the sidepanels use too) ───────────────
34
50
  let _marked = null;
35
51
  let _markedLoading = null;
36
52
  function loadMarked() {
37
53
  if (_marked || _markedLoading) return _markedLoading;
38
- _markedLoading = import('https://cdn.jsdelivr.net/npm/marked@14/lib/marked.esm.js')
54
+ _markedLoading = import('/vendor/marked/marked-14.1.4.esm.js')
39
55
  .then((m) => {
40
56
  _marked = m?.marked || m?.default || null;
41
57
  if (_marked?.setOptions) _marked.setOptions({ breaks: true, gfm: true });
@@ -126,10 +142,6 @@ function memoryTitle(memory) {
126
142
 
127
143
  // ── Run cards (shared with the agents dock) ────────────────────────────────
128
144
 
129
- function runWaitingForRoute(run) {
130
- return run?.state === 'awaiting_route' || run?.turnState === 'awaiting_route' || run?.route?.status === 'pending';
131
- }
132
-
133
145
  /**
134
146
  * buildRunCard(run, { t, providerIcon, providerColor, onFocus, onStop, onRead, onEscalate, compact, result, index })
135
147
  * Returns a card element; call updateRunCard(el, run, opts) to refresh it in place.
@@ -191,20 +203,13 @@ export function updateRunCard(card, run, opts = {}) {
191
203
  const title = card.querySelector('.asst-run-title');
192
204
  const label = run.title || run.task || run.runId || '';
193
205
  if (title) { title.textContent = label; title.title = label; }
194
- const waitingRoute = runWaitingForRoute(run);
195
206
  const pill = card.querySelector('.asst-pill');
196
207
  if (pill) {
197
- const tone = waitingRoute ? 'action' : runTone(run);
198
- pill.dataset.tone = tone;
199
- (pill.querySelector('.asst-pill-label') || pill).textContent = waitingRoute ? t('assistant.run.awaitingRoute', 'waiting for your model choice')
200
- : run.turnState === 'awaiting_permission' ? t('assistant.run.needsApproval', 'needs approval')
201
- : run.turnState === 'needs_input' ? t('assistant.run.needsInput', 'needs input')
202
- : run.queued && run.status !== 'running' ? t('assistant.run.queued', 'queued')
203
- // A warm worker between turns reports what its last turn ended with, not "running".
204
- : isRunIdle(run) ? (run.outcome || run.lastResult?.status
205
- ? t('assistant.run.idleWith', '{outcome} · idle', { outcome: run.outcome || run.lastResult.status })
206
- : t('assistant.run.idle', 'idle'))
207
- : (run.status || tone);
208
+ // What it waits on (a route card, your answer: the stage's own words) > its approval > queued > what a warm
209
+ // worker's last turn ended with > its status (asst-scenes.js runChip).
210
+ const chip = runChip(run, t);
211
+ pill.dataset.tone = chip.tone;
212
+ (pill.querySelector('.asst-pill-label') || pill).textContent = chip.label;
208
213
  }
209
214
  const meta = card.querySelector('.asst-run-meta');
210
215
  if (meta) {
@@ -433,9 +438,13 @@ const SUGGESTION_KINDS = { computer: 'computer', web: 'web', fix: 'edit', schedu
433
438
  export function createRenderer(transcriptEl, hooks = {}) {
434
439
  loadMarked();
435
440
  injectRackStyles();
441
+ injectSceneStyles();
436
442
  const $msgs = transcriptEl;
437
443
  const scopes = new Map(); // parent_tool_use_id → { container, stream, key }
438
444
  const runCards = new Map(); // runId → card element
445
+ const stages = new Map(); // runId → its card's worker stage (asst-run-stage.js)
446
+ const runActivity = new Map(); // runId → what its worker does now, compact ({ kind, name, step, at }), while the run lasts
447
+ let unsubStageTick = null;
439
448
  const routeLines = new Map(); // routeId → line handle
440
449
  const autoRows = new Map(); // traceId → the turn's automation trace row
441
450
  let working = null;
@@ -451,6 +460,16 @@ export function createRenderer(transcriptEl, hooks = {}) {
451
460
  let turn = { calls: 0, last: null, errored: false, answerRow: null }; // what the turn did: the mascot's verdict, the announced answer
452
461
  let grace = null; // the stage on screen when the result arrived: it waits for the turn's verdict (finishTurn)
453
462
  let waiting = null; // a card waits on the user: { id: the tool_use id it holds or null, since } (setWaiting)
463
+ // An approved direct route (synabun.route auto / decided, target.kind 'direct', a class with a scene) lends its
464
+ // class's scene to the racks of the turn it runs in (noteDirectRoute): { routeId, taskClass, turn, rackAt } with
465
+ // `turn` the turnSeq it belongs to, null until a turn takes it (a pick made while no turn runs, or a route the
466
+ // journal's last turn was still running), and `rackAt` the last rack made before it (its racks come after).
467
+ // A route the journal records (`journal`) belongs to the journal's last turn only: the turn the reattach finds
468
+ // running takes it; with none running, that turn is over (it ended with no result) and nothing later takes it.
469
+ let directRoute = null;
470
+ let lastDirect = null; // the last one approved since the user's prompt: a "do it here" continuation turn takes it again
471
+ let turnSeq = 0; // turns this renderer has seen start (showWorking)
472
+ let reattachRunning = null; // what the last reattach said: a turn runs (true), none (false), not asked yet (null)
454
473
 
455
474
  // ── Activity racks ──
456
475
  // Every burst of agent work (tool calls, thinking, short narration) is one
@@ -463,6 +482,27 @@ export function createRenderer(transcriptEl, hooks = {}) {
463
482
  let rackSeq = 0;
464
483
  let stepSeq = 0;
465
484
  let unsubTick = null;
485
+ // A staged rack's bubble is cut to its stage's width (subjectBudget, fitSubject): when that width
486
+ // changes, the bubble is cut again once it has held still (one pass when a drag ends, never one per
487
+ // frame of it), instead of on the next tick. Heights are not watched.
488
+ const RECUT_SETTLE_MS = 120;
489
+ const stageWidths = new WeakMap(); // stage element → { rack, width }
490
+ const toRecut = new Set();
491
+ let recutTimer = 0;
492
+ const stageResizes = typeof ResizeObserver === 'function' ? new ResizeObserver((entries) => {
493
+ for (const entry of entries) {
494
+ const seen = stageWidths.get(entry.target);
495
+ if (!seen) continue;
496
+ const width = Math.round(entry.contentRect.width);
497
+ if (seen.width === null) { seen.width = width; continue; } // the first report: the width it was cut at
498
+ if (width === seen.width) continue;
499
+ seen.width = width;
500
+ toRecut.add(seen.rack);
501
+ }
502
+ if (!toRecut.size) return;
503
+ clearTimeout(recutTimer);
504
+ recutTimer = setTimeout(recutBubbles, RECUT_SETTLE_MS);
505
+ }) : null;
466
506
  let muted = false; // inside handleEvent(…, { silent: true }): no motion, no announcements
467
507
  let replayDraw = false; // inside handleEvent(…, { replay: true }): a reattach's missed packets (drawn like the journal)
468
508
  let clockAt = null; // the journal `at` of the entry being replayed
@@ -1256,6 +1296,25 @@ export function createRenderer(transcriptEl, hooks = {}) {
1256
1296
  }
1257
1297
 
1258
1298
  // ── Racks ──
1299
+ /**
1300
+ * The class whose scene a new rack of this turn shows: an approved direct route this turn runs, else ''. Live, or
1301
+ * a reattach's replay of the running turn; never the journal (no turn runs while it is drawn).
1302
+ */
1303
+ function directSceneClass() {
1304
+ return (!muted || replayDraw) && turnActive && directRoute && directRoute.turn === turnSeq ? directRoute.taskClass : '';
1305
+ }
1306
+
1307
+ /**
1308
+ * A route a turn takes when it starts (a conversation opened again mid-turn, a pick made while no turn ran): the
1309
+ * open racks made after it are that turn's work, and show its scene from now on. Settled racks never move.
1310
+ */
1311
+ function lendRouteScene(route) {
1312
+ for (const rack of racks) {
1313
+ if (rack.nested || rack.closed || rack.sceneClass || !(rack.id > (route.rackAt ?? Infinity))) continue;
1314
+ rack.sceneClass = route.taskClass;
1315
+ }
1316
+ }
1317
+
1259
1318
  function createRack(scope) {
1260
1319
  const id = ++rackSeq;
1261
1320
  const nested = scope.container !== $msgs;
@@ -1265,7 +1324,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
1265
1324
  row.setAttribute('role', 'group');
1266
1325
  const ledgerId = `asst-rack-${id}-ledger`;
1267
1326
  row.innerHTML = `<div class="asst-rack-stage">
1268
- <div class="asst-rack-face" aria-hidden="true"></div>
1327
+ <div class="asst-rack-set"><div class="asst-rack-face" aria-hidden="true"></div><div class="asst-rack-scene" aria-hidden="true"></div></div>
1269
1328
  <div class="asst-rack-caption"></div>
1270
1329
  <div class="asst-bubble" aria-live="off" hidden><span class="asst-bubble-verb"></span><span class="asst-bubble-subject" role="button" tabindex="-1" aria-expanded="false"><span class="asst-bubble-text"></span><span class="asst-bubble-caret" aria-hidden="true"></span></span><span class="asst-bubble-meta"></span><span class="asst-bubble-err" hidden></span></div>
1271
1330
  <div class="asst-rack-sweep" aria-hidden="true"></div>
@@ -1278,7 +1337,10 @@ export function createRenderer(transcriptEl, hooks = {}) {
1278
1337
  const rack = {
1279
1338
  id, row, scope, nested, closed: false, live: false, staged: false, open: false, view: 'kind', everAwake: false,
1280
1339
  stations: new Map(), steps: [], timeline: [], said: [], firstAt: 0,
1281
- stage: { el: q('.asst-rack-stage'), face: q('.asst-rack-face'), caption: q('.asst-rack-caption'), bubble: q('.asst-bubble'), verb: q('.asst-bubble-verb'), subject: q('.asst-bubble-subject'), text: q('.asst-bubble-text'), meta: q('.asst-bubble-meta'), err: q('.asst-bubble-err') },
1340
+ // The direct route's class whose scene the stage shows: set here, or once when a reopened conversation's route
1341
+ // resumes (lendRouteScene); a rack never loses its scene.
1342
+ sceneClass: nested ? '' : directSceneClass(), sceneId: '', sceneAt: -Infinity,
1343
+ stage: { el: q('.asst-rack-stage'), set: q('.asst-rack-set'), face: q('.asst-rack-face'), scene: q('.asst-rack-scene'), caption: q('.asst-rack-caption'), bubble: q('.asst-bubble'), verb: q('.asst-bubble-verb'), subject: q('.asst-bubble-subject'), text: q('.asst-bubble-text'), meta: q('.asst-bubble-meta'), err: q('.asst-bubble-err') },
1282
1344
  receipt: { btn: q('.asst-rack-receipt'), face: q('.asst-rack-receipt-face'), text: q('.asst-rack-receipt-text'), failed: q('.asst-rack-receipt-failed'), clock: q('.asst-rack-receipt-clock'), faceBuilt: false, pose: '', svg: null },
1283
1345
  ledger: q('.asst-rack-ledger'), statusEl: q('.asst-rack-status'), alertEl: q('.asst-rack-alert'), timelineEl: null, timelineSig: '',
1284
1346
  rig: null, target: null, held: false, holdUntil: 0, holdTimer: 0, pose: null, awake: null, awakeKey: null,
@@ -1454,6 +1516,8 @@ export function createRenderer(transcriptEl, hooks = {}) {
1454
1516
  renderRackLabel(rack, sentence);
1455
1517
  if (live) { if (rack.staged) paintStage(rack, now); scheduleStage(rack, now); needTicker(); }
1456
1518
  else if (was) settle(rack);
1519
+ // The scene moves while the rack is live, as its sweep does: never while a card holds it, nor once it settled.
1520
+ rack.stage.scene.firstElementChild?.toggleAttribute('data-live', live && !rack.waiting);
1457
1521
  if (rack.open && rack.view === 'time') renderTimeline(rack);
1458
1522
  if (working?.isConnected) syncWorkingFace();
1459
1523
  }
@@ -1506,6 +1570,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
1506
1570
  clearTimeout(rack.clearTimer);
1507
1571
  rack.clearTimer = 0;
1508
1572
  rack.row.classList.add('is-staged');
1573
+ watchStageWidth(rack, true);
1509
1574
  if (!rack.rig) {
1510
1575
  try { rack.rig = mascotLib.createMascot(rack.stage.face, { width: 72, height: 36, rangeX: 10, rangeY: 7, pose: 'idle' }); } catch { rack.rig = null; }
1511
1576
  rack.pose = null;
@@ -1544,6 +1609,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
1544
1609
  /** A folded stage keeps nothing: its rig goes, its texts empty (a settled rack is the same live or replayed). */
1545
1610
  function clearStage(rack) {
1546
1611
  if (rack.staged) return;
1612
+ watchStageWidth(rack, false);
1547
1613
  const had = !!rack.rig;
1548
1614
  rack.rig?.destroy?.();
1549
1615
  rack.rig = null;
@@ -1553,6 +1619,10 @@ export function createRenderer(transcriptEl, hooks = {}) {
1553
1619
  if (had) mascotChanged();
1554
1620
  const s = rack.stage;
1555
1621
  s.face.textContent = '';
1622
+ s.scene.textContent = '';
1623
+ delete s.el.dataset.scene;
1624
+ rack.sceneId = '';
1625
+ rack.sceneAt = -Infinity;
1556
1626
  s.caption.textContent = '';
1557
1627
  s.bubble.hidden = true;
1558
1628
  delete s.bubble.dataset.state;
@@ -1638,11 +1708,32 @@ export function createRenderer(transcriptEl, hooks = {}) {
1638
1708
  if (!rack.waiting && shown && shown.rack === rack && now - rack.bubble.since < hold) rack.bubble.pendingAt = rack.bubble.since + hold;
1639
1709
  else { rack.bubble.step = target; rack.bubble.since = now; }
1640
1710
  }
1711
+ paintRackScene(rack, now); // before the bubble: the set it sits beside takes the scene's width
1641
1712
  poseStage(rack);
1642
1713
  paintBubble(rack, now, { swap: true });
1643
1714
  paintCaption(rack);
1644
1715
  }
1645
1716
 
1717
+ /**
1718
+ * A direct route's rack: the scene under the mascot is the route class's
1719
+ * (asst-scenes.js), or the one the bubble's call implies (a browser tool the
1720
+ * browser, a computer tool the computer, a web search or a page read
1721
+ * research); a scene a call picked holds STAGE_TIMING.sceneDwellMs, as on a
1722
+ * run's stage (tickRacks tries again). Nothing for a rack without a route.
1723
+ */
1724
+ function paintRackScene(rack, now) {
1725
+ if (!rack.sceneClass || !rack.staged) return;
1726
+ const step = rack.bubble.step;
1727
+ const call = step?.type === 'call' ? { kind: step.kind, name: step.name } : null;
1728
+ const want = activityScene(call, rack.sceneClass) || sceneOfClass(rack.sceneClass);
1729
+ if (want === rack.sceneId) return;
1730
+ if (rack.sceneId && now - rack.sceneAt < STAGE_TIMING.sceneDwellMs) return;
1731
+ rack.sceneId = want;
1732
+ rack.sceneAt = now;
1733
+ rack.stage.scene.innerHTML = sceneSvg(want, { live: rack.live && !rack.waiting });
1734
+ rack.stage.el.dataset.scene = want;
1735
+ }
1736
+
1646
1737
  function paintCaption(rack) {
1647
1738
  const step = rack.bubble.step;
1648
1739
  const say = step ? sayOf(step) : null;
@@ -1670,10 +1761,11 @@ export function createRenderer(transcriptEl, hooks = {}) {
1670
1761
  const full = grouped || say.text;
1671
1762
  const literal = !grouped && say.full ? say.full : full; // the command as written: the title and the opened bubble
1672
1763
  const open = rack.bubble.open;
1673
- // Narrow stages cut from the head, so the file name (the tail) survives.
1764
+ // Narrow stages cut from the head, so the file name (the tail) survives. A small budget keeps a
1765
+ // shorter tail (24 chars of it would not fit beside the verb).
1674
1766
  const cut = subjectCut(rack);
1675
- const budget = subjectBudget(rack, verb, cut);
1676
- const shownText = open ? literal : cut === 'head' ? headEllipsis(full, budget) : middleEllipsis(full, budget, 24);
1767
+ const cutTo = (n) => (open ? literal : cut === 'head' ? headEllipsis(full, n) : middleEllipsis(full, n, Math.min(24, Math.max(8, n - 6))));
1768
+ const shownText = cutTo(subjectBudget(rack, verb, cut));
1677
1769
  const key = `${step.id}|${verb}|${shownText}|${say.muted ? 1 : 0}`;
1678
1770
  s.bubble.hidden = !full && !verb;
1679
1771
  s.bubble.dataset.state = held ? 'waiting' : running ? 'running' : step.state;
@@ -1684,8 +1776,12 @@ export function createRenderer(transcriptEl, hooks = {}) {
1684
1776
  const write = () => {
1685
1777
  s.verb.textContent = verb;
1686
1778
  s.subject.className = `asst-bubble-subject${say.mono ? ' is-mono' : ''}${say.muted ? ' is-muted' : ''}`;
1687
- writeSubject(s.text, grouped || open ? null : say.segments, shownText);
1688
- if (literal !== shownText || open) s.subject.title = literal; else s.subject.removeAttribute('title');
1779
+ const segments = grouped || open ? null : say.segments;
1780
+ writeSubject(s.text, segments, shownText);
1781
+ // The budget is an estimate (a long token wraps whole to the next line): what the two-line
1782
+ // clamp would hide is cut here instead, so the subject is never cut a second time.
1783
+ const fitted = open ? shownText : fitSubject(s, shownText, cutTo, segments);
1784
+ if (literal !== fitted || open) s.subject.title = literal; else s.subject.removeAttribute('title');
1689
1785
  };
1690
1786
  if (swap && changedStep && !calm() && typeof s.subject.animate === 'function' && s.text.textContent) {
1691
1787
  s.subject.getAnimations?.().forEach(a => a.cancel());
@@ -1717,7 +1813,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
1717
1813
  const s = rack.stage;
1718
1814
  const width = s.el.clientWidth;
1719
1815
  if (!width) return 96;
1720
- const face = s.face.getBoundingClientRect();
1816
+ const face = (s.set || s.face).getBoundingClientRect(); // the mascot's column (with a scene under it, as wide as the scene)
1721
1817
  const bubble = s.bubble.hidden ? null : s.bubble.getBoundingClientRect();
1722
1818
  const beside = !bubble || bubble.left >= face.right - 1;
1723
1819
  const verbPx = Math.max(s.verb.getBoundingClientRect().width, verb.length * 7);
@@ -1733,14 +1829,17 @@ export function createRenderer(transcriptEl, hooks = {}) {
1733
1829
  try { return getComputedStyle(rack.stage.subject).getPropertyValue('--rk-cut').trim() === 'head' ? 'head' : 'middle'; } catch { return 'middle'; }
1734
1830
  }
1735
1831
 
1736
- /** "…ender-events.browser.mjs": the last `max` chars, from a token boundary when one is near. */
1832
+ /**
1833
+ * "…ender-events.browser.mjs": the last `max` chars, from a token boundary when one is near. One "…":
1834
+ * a token the subject already shortened at the start of that tail goes with the cut ("……/x.js").
1835
+ */
1737
1836
  function headEllipsis(text, max) {
1738
1837
  const s = String(text ?? '');
1739
1838
  if (s.length <= max) return s;
1740
1839
  let tail = s.slice(s.length - Math.max(8, max - 1));
1741
1840
  const cut = tail.search(/[\s/]/);
1742
1841
  if (cut > 0 && cut < tail.length * 0.4) tail = tail.slice(tail[cut] === '/' ? cut : cut + 1);
1743
- return `…${tail}`;
1842
+ return joinCut('', tail);
1744
1843
  }
1745
1844
 
1746
1845
  function writeSubject(node, segments, text) {
@@ -1750,6 +1849,18 @@ export function createRenderer(transcriptEl, hooks = {}) {
1750
1849
  } else node.textContent = text;
1751
1850
  }
1752
1851
 
1852
+ /** The bubble's subject cut shorter (`cutTo`, six steps at most) until its clamp hides none of it; the text it shows. */
1853
+ function fitSubject(s, text, cutTo, segments) {
1854
+ let shown = text;
1855
+ let budget = shown.length;
1856
+ for (let i = 0; i < 6 && budget > 12 && s.subject.scrollHeight > s.subject.clientHeight + 1; i += 1) {
1857
+ budget = Math.floor(budget * 0.85);
1858
+ shown = cutTo(budget);
1859
+ writeSubject(s.text, segments, shown);
1860
+ }
1861
+ return shown;
1862
+ }
1863
+
1753
1864
  /** How many calls of the rack act on the bubble call's target (itself included): a re-run or re-read reads ×N. */
1754
1865
  function repeatCount(step) {
1755
1866
  if (step.type !== 'call') return 1;
@@ -2462,6 +2573,30 @@ export function createRenderer(transcriptEl, hooks = {}) {
2462
2573
  unsubTick = null;
2463
2574
  }
2464
2575
 
2576
+ /** The bubbles whose stage changed width: cut and fit again (the same text may fit differently now). */
2577
+ function recutBubbles() {
2578
+ recutTimer = 0;
2579
+ for (const rack of toRecut) {
2580
+ if (!rack.staged || !rack.bubble.step || !rack.row.isConnected) continue;
2581
+ rack.bubble.key = '';
2582
+ paintBubble(rack, Date.now(), { swap: false });
2583
+ }
2584
+ toRecut.clear();
2585
+ }
2586
+
2587
+ function watchStageWidth(rack, on) {
2588
+ if (!stageResizes) return;
2589
+ const stage = rack.stage.el;
2590
+ if (on && !stageWidths.has(stage)) {
2591
+ stageWidths.set(stage, { rack, width: null });
2592
+ stageResizes.observe(stage);
2593
+ } else if (!on && stageWidths.has(stage)) {
2594
+ stageWidths.delete(stage);
2595
+ stageResizes.unobserve(stage);
2596
+ toRecut.delete(rack);
2597
+ }
2598
+ }
2599
+
2465
2600
  /** Once a second while a rack is live: its clocks (and any wake-up the timers have not caught). */
2466
2601
  function tickRacks() {
2467
2602
  let any = false;
@@ -2470,7 +2605,8 @@ export function createRenderer(transcriptEl, hooks = {}) {
2470
2605
  if (!rack.live) continue;
2471
2606
  any = true;
2472
2607
  const now = Date.now();
2473
- if (rack.staged && rack.bubble.step) paintBubble(rack, now, { swap: false }); // clocks, and the cut after a resize
2608
+ if (rack.staged && rack.sceneClass) paintRackScene(rack, now); // a scene swap the dwell held back
2609
+ if (rack.staged && rack.bubble.step) paintBubble(rack, now, { swap: false }); // its clocks (a resize re-cuts it: recutBubbles)
2474
2610
  for (const st of rack.stations.values()) { const time = stationTime(st, true, rackNow(rack)); if (st.time.textContent !== time) st.time.textContent = time; }
2475
2611
  for (const step of rack.steps) if (step.state === 'running' && step.type === 'call' && step.progress >= 1) refreshStep(step);
2476
2612
  }
@@ -2478,6 +2614,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
2478
2614
  }
2479
2615
 
2480
2616
  function dropRack(rack) {
2617
+ watchStageWidth(rack, false);
2481
2618
  clearTimeout(rack.stageTimer);
2482
2619
  clearTimeout(rack.clearTimer);
2483
2620
  clearTimeout(rack.sr.timer);
@@ -2545,6 +2682,8 @@ export function createRenderer(transcriptEl, hooks = {}) {
2545
2682
 
2546
2683
  function appendUser(text, { images = [], files = [], memories = [], origin = null, imageCount = 0 } = {}) {
2547
2684
  turnFrames = [];
2685
+ directRoute = null; // a new prompt is a new task: its own route decides
2686
+ lastDirect = null;
2548
2687
  rootScope.kept = null;
2549
2688
  lastErrorText = '';
2550
2689
  const row = el('div', 'asst-msg msg-user');
@@ -2604,7 +2743,14 @@ export function createRenderer(transcriptEl, hooks = {}) {
2604
2743
  working.querySelector('.asst-working-label').textContent = label || t('assistant.rack.verb.think.now', 'Thinking');
2605
2744
  hideEmpty({ park: true });
2606
2745
  $msgs.appendChild(working);
2607
- if (!turnActive) turn = { calls: 0, last: null, errored: false, answerRow: null };
2746
+ if (!turnActive) {
2747
+ turn = { calls: 0, last: null, errored: false, answerRow: null };
2748
+ turnSeq += 1;
2749
+ if (directRoute && directRoute.turn == null) { // a route picked while no turn ran, or one the journal left running, is this turn's
2750
+ directRoute.turn = turnSeq;
2751
+ lendRouteScene(directRoute);
2752
+ }
2753
+ }
2608
2754
  turnActive = true;
2609
2755
  if (racks.size) syncRacks();
2610
2756
  syncWorkingFace();
@@ -2690,28 +2836,167 @@ export function createRenderer(transcriptEl, hooks = {}) {
2690
2836
  };
2691
2837
  }
2692
2838
 
2693
- function upsertRunCard(run, { result = null } = {}) {
2694
- if (!run?.runId) return null;
2839
+ /** What the renderer knows of a run (titles for the stage bubble, the route line's live state). */
2840
+ function noteRun(run) {
2695
2841
  const prev = runInfo.get(run.runId);
2696
2842
  const status = run.status || prev?.state;
2697
2843
  const tone = status ? runTone({ ...run, status }) : '';
2698
2844
  const routeId = run.route?.routeId || prev?.routeId || '';
2699
2845
  runInfo.set(run.runId, { ...prev, title: run.title || run.task || prev?.title, state: status, provider: run.provider, routeId, working: tone === 'running' || tone === 'queued' });
2846
+ if (isTerminalRunStatus(status)) runActivity.delete(run.runId); // the run is over: so is what it was doing
2700
2847
  if (routeId) syncRouteLive(routeId);
2848
+ }
2849
+
2850
+ function upsertRunCard(run, { result = null } = {}) {
2851
+ if (!run?.runId) return null;
2852
+ noteRun(run);
2701
2853
  const cardOpts = runCardOpts(result);
2702
2854
  let card = runCards.get(run.runId);
2703
2855
  if (card && card.isConnected) {
2704
2856
  updateRunCard(card, run, cardOpts);
2857
+ syncStage(run.runId, card, run);
2705
2858
  for (const rack of racks) if (rack.live) renderRack(rack); // "Waiting on <title>" learns the title
2706
2859
  return card;
2707
2860
  }
2861
+ dropStage(run.runId); // a card that left the transcript took its stage with it
2708
2862
  card = buildRunCard(run, cardOpts);
2709
2863
  runCards.set(run.runId, card);
2710
2864
  const { wrap } = assistantRow(rootScope);
2711
2865
  wrap.appendChild(card);
2866
+ syncStage(run.runId, card, run);
2867
+ return card;
2868
+ }
2869
+
2870
+ /**
2871
+ * A run update from anywhere (the agents tray's sources: the sync bus, its
2872
+ * fetch): the card and its stage update in place, the route line learns
2873
+ * whether its run works. Never adds a card.
2874
+ */
2875
+ function syncRun(run) {
2876
+ if (!run?.runId) return null;
2877
+ noteRun(run);
2878
+ const card = runCards.get(run.runId);
2879
+ if (!card?.isConnected) return null;
2880
+ updateRunCard(card, run, runCardOpts()); // fresh options: the run's own lastResult, never a result the card was built with
2881
+ syncStage(run.runId, card, run);
2712
2882
  return card;
2713
2883
  }
2714
2884
 
2885
+ // ── Worker stages (asst-run-stage.js) ──
2886
+ /** The card's stage: made when the run first works, then updated in place for as long as the card lives. */
2887
+ function syncStage(runId, card, run) {
2888
+ let stage = stages.get(runId);
2889
+ if (!stage) {
2890
+ if (!isLivePhase(stagePhase(run))) return;
2891
+ stage = createWorkerStage(card, { t, calm: () => reducedMotion() });
2892
+ stages.set(runId, stage);
2893
+ stage.setActivity(runActivity.get(runId) || null);
2894
+ }
2895
+ stage.update(run);
2896
+ if (stage.live()) needStageTicker();
2897
+ }
2898
+
2899
+ function dropStage(runId) {
2900
+ const stage = stages.get(runId);
2901
+ if (!stage) return;
2902
+ stages.delete(runId);
2903
+ try { stage.destroy(); } catch { /* gone */ }
2904
+ }
2905
+
2906
+ function needStageTicker() {
2907
+ if (unsubStageTick) return;
2908
+ try { unsubStageTick = subscribe(tickStages, { fps: 1 }); } catch { unsubStageTick = null; }
2909
+ }
2910
+
2911
+ function stopStageTicker() {
2912
+ if (!unsubStageTick) return;
2913
+ try { unsubStageTick(); } catch { /* already gone */ }
2914
+ unsubStageTick = null;
2915
+ }
2916
+
2917
+ /** Once a second while a stage is open: its beat, and the card's elapsed time (the run's one clock: the details line). */
2918
+ function tickStages() {
2919
+ let any = false;
2920
+ const now = Date.now();
2921
+ for (const [runId, stage] of [...stages]) {
2922
+ const card = runCards.get(runId);
2923
+ if (!card?.isConnected) { dropStage(runId); continue; }
2924
+ if (!stage.live()) continue;
2925
+ any = true;
2926
+ stage.tick(now);
2927
+ if (card._usageRun) tickRunCard(card, card._usageRun);
2928
+ }
2929
+ if (!any) stopStageTicker();
2930
+ }
2931
+
2932
+ /** What a run's worker does now (its provider events: Claude and Codex workers). Kept compact, and only while the run lasts. */
2933
+ function setRunActivity(runId, activity) {
2934
+ if (!runId) return;
2935
+ const next = isTerminalRunStatus(runInfo.get(runId)?.state) ? null : compactActivity(activity, t);
2936
+ if (next) runActivity.set(runId, next); else runActivity.delete(runId);
2937
+ while (runActivity.size > 200) runActivity.delete(runActivity.keys().next().value);
2938
+ stages.get(runId)?.setActivity(next);
2939
+ }
2940
+
2941
+ /** The row a restored card sits after: its dispatch call's rack, else its route line; null when neither is drawn. */
2942
+ function dispatchAnchor(run) {
2943
+ const routeId = run.route?.routeId || '';
2944
+ let byRoute = null;
2945
+ for (const step of stepsById.values()) {
2946
+ if (step.type !== 'call' || step.kind !== 'runs' || step.rack.nested || !/agent_dispatch$/i.test(String(step.name))) continue;
2947
+ if (step.result && step.result.includes(run.runId)) return step.rack.row;
2948
+ if (!byRoute && routeId && (step.input?.route_id === routeId || step.input?.routeId === routeId)) byRoute = step.rack.row;
2949
+ }
2950
+ if (byRoute?.isConnected) return byRoute;
2951
+ const line = routeId ? routeLines.get(routeId) : null;
2952
+ return line?.el?.isConnected ? line.el : null;
2953
+ }
2954
+
2955
+ const startMs = (run) => new Date(run.startedAt || run.createdAt || 0).getTime() || 0;
2956
+
2957
+ /**
2958
+ * After a reload (run cards are not journaled): each run's card comes back
2959
+ * next to the call that dispatched it (runs of one call in the order they
2960
+ * started), live when its worker still works. A finished run with nothing
2961
+ * in the transcript to sit by is left to the tray; one still working is
2962
+ * added at the end. A card already drawn is only updated.
2963
+ */
2964
+ function restoreRuns(list) {
2965
+ const placed = new Map(); // anchor row → the last card row put after it
2966
+ const runs = (Array.isArray(list) ? list : []).filter(run => run?.runId).sort((a, b) => startMs(a) - startMs(b));
2967
+ for (const run of runs) {
2968
+ if (runCards.get(run.runId)?.isConnected) { syncRun(run); continue; }
2969
+ const anchor = dispatchAnchor(run);
2970
+ if (!anchor && isTerminalRunStatus(run.status)) { noteRun(run); continue; }
2971
+ const row = upsertRunCard(run)?.closest('.asst-msg');
2972
+ if (!row || !anchor?.isConnected || anchor === row) continue;
2973
+ const after = placed.get(anchor);
2974
+ (after?.isConnected ? after : anchor).after(row);
2975
+ placed.set(anchor, row);
2976
+ }
2977
+ }
2978
+
2979
+ /** Bring a run's card into view and hand it focus (the agents tray's mini scenes and lanes). */
2980
+ function revealRun(runId) {
2981
+ const card = runCards.get(runId);
2982
+ if (!card?.isConnected) return false;
2983
+ const top = card.getBoundingClientRect().top - $msgs.getBoundingClientRect().top + $msgs.scrollTop - 16;
2984
+ try { $msgs.scrollTo({ top: Math.max(0, top), behavior: calm() ? 'auto' : 'smooth' }); } catch { $msgs.scrollTop = Math.max(0, top); }
2985
+ card.classList.remove('is-revealed');
2986
+ void card.offsetWidth; // restart the highlight
2987
+ card.classList.add('is-revealed');
2988
+ clearTimeout(card._revealTimer);
2989
+ card._revealTimer = setTimeout(() => card.classList.remove('is-revealed'), 1600);
2990
+ if (!card.hasAttribute('tabindex')) card.tabIndex = -1;
2991
+ try { card.focus({ preventScroll: true }); } catch { /* not focusable */ }
2992
+ return true;
2993
+ }
2994
+
2995
+ function dropStages() {
2996
+ for (const runId of [...stages.keys()]) dropStage(runId);
2997
+ stopStageTicker();
2998
+ }
2999
+
2715
3000
  function patchRunUsage(runId, tokens, fidelity) {
2716
3001
  const card = runCards.get(runId);
2717
3002
  if (!card?.isConnected || !card._usageRun) return;
@@ -2749,6 +3034,43 @@ export function createRenderer(transcriptEl, hooks = {}) {
2749
3034
  }
2750
3035
 
2751
3036
  // ── Route lines ──
3037
+ /**
3038
+ * A decided route says what the Assistant's own racks show. A direct one
3039
+ * ("do it here") whose class has a scene lends it to the racks of the turn
3040
+ * it runs in: this turn, or the next one when it was picked while no turn
3041
+ * ran (a late card answer). A dispatch or chat route takes it back, and so
3042
+ * does the end of its card (declined, expired, cancelled). The scene goes
3043
+ * with its turn (finishTurn; in a journal or a replay, the turn's `result`) and with a new prompt (appendUser).
3044
+ */
3045
+ function noteDirectRoute(route, phase) {
3046
+ const id = String(route?.routeId || '');
3047
+ if (phase !== 'auto' && phase !== 'decided') {
3048
+ if (directRoute && directRoute.routeId === id && (phase === 'declined' || phase === 'expired' || phase === 'cancelled')) directRoute = null;
3049
+ return;
3050
+ }
3051
+ const target = route?.target && typeof route.target === 'object' ? route.target : null;
3052
+ const cls = sceneOfClass(route?.taskClass);
3053
+ const direct = !!target && String(target.kind || 'direct') === 'direct' && cls !== 'generic';
3054
+ // Drawn from the journal (not a live event, not a reattach's replay of the running turn): its turn is the journal's last.
3055
+ const journal = muted && !replayDraw;
3056
+ // That turn is known to be over (the reattach found nothing running, e.g. the journal was drawn again after it):
3057
+ // nothing takes its route.
3058
+ const over = journal && !turnActive && reattachRunning === false;
3059
+ directRoute = direct && !over ? { routeId: id, taskClass: cls, turn: turnActive ? turnSeq : null, rackAt: rackSeq, journal } : null;
3060
+ lastDirect = direct ? { routeId: id, taskClass: cls } : null;
3061
+ }
3062
+
3063
+ /**
3064
+ * What a reattach said (asst-panel.js reattach_result). With no turn running, a route the journal left without its
3065
+ * turn's result belonged to a turn that is over: a later turn (one the CLI starts on its own, a mailbox turn) is
3066
+ * something else and never takes its scene. With one running, that turn is the journal's last: it takes the route
3067
+ * when it starts (showWorking).
3068
+ */
3069
+ function reattached(running) {
3070
+ reattachRunning = !!running;
3071
+ if (!running && directRoute?.journal && directRoute.turn == null) directRoute = null;
3072
+ }
3073
+
2752
3074
  function routeLineOpts(phase) {
2753
3075
  return { phase, t: hooks.t, providerIcon: hooks.providerIcon, brainLabel: hooks.brainLabel, onChangeRoutes: hooks.onChangeRoutes };
2754
3076
  }
@@ -3212,6 +3534,8 @@ export function createRenderer(transcriptEl, hooks = {}) {
3212
3534
  if (!parentId && !muted) grace = newestStage();
3213
3535
  if (grace) grace.holdUntil = Infinity;
3214
3536
  if (!parentId) endBursts(rootScope);
3537
+ // A journal's or a replay's turn ended here (live, finishTurn ends it): so did the route it ran.
3538
+ if (!parentId && muted) directRoute = null;
3215
3539
  hooks.onResult?.(ev);
3216
3540
  if (errorText && errorText !== lastErrorText) appendError(errorText);
3217
3541
  if (!parentId) { lastErrorText = ''; rootScope.kept = null; releaseGrace(); }
@@ -3270,11 +3594,16 @@ export function createRenderer(transcriptEl, hooks = {}) {
3270
3594
  return true;
3271
3595
  case 'synabun.route': {
3272
3596
  const phase = ev.phase || 'decided';
3597
+ noteDirectRoute(ev.route, phase); // the journal's too: a conversation opened again keeps the route of its last turn
3273
3598
  if (hooks.onRouteEvent?.(ev)) return true; // an open card represents it
3274
3599
  if (phase === 'card' && silent) return true; // replayed card: the pending control re-sends it
3275
3600
  upsertRouteLine(ev.route || {}, phase);
3276
3601
  return true;
3277
3602
  }
3603
+ case 'synabun.continuation':
3604
+ // A "do it here" pick on another model runs as a turn of its own: the route it approved goes on there.
3605
+ if (lastDirect) directRoute = { ...lastDirect, turn: turnActive ? turnSeq : null, rackAt: rackSeq };
3606
+ return true;
3278
3607
  case 'synabun.clarify':
3279
3608
  // The card itself arrives as a control_request; its outcome is a line (the panel's card shows it live).
3280
3609
  if (ev.phase !== 'card') appendStatus(clarifyLine(ev), 'info');
@@ -3321,6 +3650,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
3321
3650
  closeStreamThinks();
3322
3651
  hideWorking();
3323
3652
  turnActive = false;
3653
+ if (directRoute && directRoute.turn === turnSeq) directRoute = null; // its turn is over
3324
3654
  const stage = grace || newestStage();
3325
3655
  grace = null;
3326
3656
  const hold = holdMs > 0 && !calm();
@@ -3402,6 +3732,8 @@ export function createRenderer(transcriptEl, hooks = {}) {
3402
3732
  if (rootScope.stream?.mdTimer) clearTimeout(rootScope.stream.mdTimer);
3403
3733
  if (rootScope.stream?.thinkTimer) clearTimeout(rootScope.stream.thinkTimer);
3404
3734
  scopes.clear();
3735
+ dropStages();
3736
+ runActivity.clear();
3405
3737
  runCards.clear();
3406
3738
  routeLines.clear();
3407
3739
  autoRows.clear();
@@ -3417,6 +3749,8 @@ export function createRenderer(transcriptEl, hooks = {}) {
3417
3749
  dropHero();
3418
3750
  grace = null;
3419
3751
  turn = { calls: 0, last: null, errored: false, answerRow: null };
3752
+ directRoute = null;
3753
+ lastDirect = null;
3420
3754
  mcpWarned.clear();
3421
3755
  seen.clear();
3422
3756
  snapshot = null;
@@ -3430,7 +3764,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
3430
3764
  return {
3431
3765
  el: $msgs,
3432
3766
  clear,
3433
- destroy: () => { hideWorking(); dropHero(); dropAll(); },
3767
+ destroy: () => { hideWorking(); dropHero(); dropAll(); dropStages(); },
3434
3768
  appendUser,
3435
3769
  appendAssistantMarkdown,
3436
3770
  appendStatus,
@@ -3441,6 +3775,16 @@ export function createRenderer(transcriptEl, hooks = {}) {
3441
3775
  appendMemorySaved,
3442
3776
  appendMailbox,
3443
3777
  upsertRunCard,
3778
+ /** A run update that never adds a card (the tray's sources): the card, its stage and the route line follow it. */
3779
+ syncRun,
3780
+ /** After a reload: these runs' cards again, next to the calls that dispatched them (see restoreRuns). */
3781
+ restoreRuns,
3782
+ /** What a run's worker does now: its stage's scene and step follow it. */
3783
+ setRunActivity,
3784
+ /** Scroll a run's card into view and focus it. */
3785
+ revealRun,
3786
+ /** For tests and devtools: a run card's stage ({ open, phase, scene, final, pose, step, label }) or null. */
3787
+ stageOf: (runId) => stages.get(runId)?.state() || null,
3444
3788
  patchRunUsage,
3445
3789
  upsertRouteLine,
3446
3790
  registerRouteLine,
@@ -3453,6 +3797,8 @@ export function createRenderer(transcriptEl, hooks = {}) {
3453
3797
  claimEvent,
3454
3798
  /** A reattach's buffered packets minus what is on screen (see planReplay). */
3455
3799
  planReplay,
3800
+ /** What the reattach said (a turn runs or not): a route the journal left open goes only to that turn (see reattached). */
3801
+ reattached,
3456
3802
  /** Forget what was drawn (a brain switch starts a new id space). */
3457
3803
  forgetSeen: () => { seen.clear(); snapshot = null; },
3458
3804
  /** A card waits on the user: `toolUseId` names the call it holds (the newest live stage when unknown); false clears it. */