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
@@ -14,8 +14,8 @@ import { t } from './i18n.js';
14
14
  import { registerAction } from './ui-keybinds.js';
15
15
  import { isGuest, hasPermission, showGuestToast } from './ui-sync.js';
16
16
  import { createAssistantHosts, describeBrainFallback } from './assistant/asst-hosts.js';
17
- import { createAssistantSession, getAssistantSession, listAssistantSessions } from './assistant/asst-api.js';
18
- import { providerShortLabel, readStoredBrain } from './assistant/asst-state.js';
17
+ import { createAssistantSession, getAssistantSession, listAssistantSessions, removeAssistantSession } from './assistant/asst-api.js';
18
+ import { providerShortLabel, readStoredBrain, tabStorageKey } from './assistant/asst-state.js';
19
19
 
20
20
  /** Guests need the terminal permission for the Assistant, in either host. */
21
21
  export function assistantBlocked() {
@@ -30,7 +30,7 @@ function brainFallbackText(fallback) {
30
30
  }
31
31
 
32
32
  const hosts = createAssistantHosts({
33
- api: { list: listAssistantSessions, get: getAssistantSession, create: createAssistantSession },
33
+ api: { list: listAssistantSessions, get: getAssistantSession, create: createAssistantSession, remove: removeAssistantSession },
34
34
  isBlocked: assistantBlocked,
35
35
  toast: (text) => showGuestToast(text),
36
36
  storedBrain: () => readStoredBrain(storage),
@@ -48,6 +48,11 @@ export const noteAssistantFocus = (sessionId, hostId) => hosts.noteFocus(session
48
48
 
49
49
  let _initialized = false;
50
50
 
51
+ /** A removed session's per-tab UI state (composer draft, dock, route mode): written by its tab as it closes, then dropped. */
52
+ function forgetTabState(sessionId) {
53
+ try { storage.removeItem(tabStorageKey(sessionId)); } catch { /* storage unavailable */ }
54
+ }
55
+
51
56
  /** Bus events + keybinds for both hosts. Idempotent: initNavbar and initTerminal both call it. */
52
57
  export function initAssistant() {
53
58
  if (_initialized) return;
@@ -60,8 +65,24 @@ export function initAssistant() {
60
65
  floating: data?.floating,
61
66
  prompt: data?.prompt,
62
67
  }));
63
- on('assistant:resume', (data) => {
64
- if (data?.sessionId) hosts.open({ resume: data.sessionId, host: data?.host || null, floating: data?.floating });
68
+ // `replaces`: the session the user left by picking this one, when nobody chatted in it: it goes.
69
+ on('assistant:resume', async (data) => {
70
+ if (!data?.sessionId) return;
71
+ const res = await hosts.open({ resume: data.sessionId, host: data?.host || null, floating: data?.floating });
72
+ if (res && data.replaces && data.replaces !== data.sessionId) hosts.discard(data.replaces);
73
+ });
74
+ // Sessions menu → Remove. `done(result)` tells the menu that asked how it went.
75
+ on('assistant:remove', (data) => {
76
+ if (!data?.sessionId) return;
77
+ hosts.remove(data.sessionId, { host: data.host || null, fresh: data.fresh === true, brain: data.brain || null })
78
+ .then((result) => {
79
+ if (result?.ok) forgetTabState(data.sessionId);
80
+ try { data.done?.(result); } catch { /* the menu is gone */ }
81
+ });
82
+ });
83
+ // A session the server no longer has: its tab closes (a fresh one in its place when it was showing unused).
84
+ on('assistant:gone', (data) => {
85
+ if (data?.sessionId) hosts.gone(data.sessionId, { host: data.host || null, fresh: data.fresh === true, brain: data.brain || null }).finally(() => forgetTabState(data.sessionId));
65
86
  });
66
87
  on('assistant:toggle', () => hosts.toggle());
67
88
  // Notification clicks (toasts and OS banners) — see ui-notifications _getNotifMeta.
@@ -71,6 +92,7 @@ export function initAssistant() {
71
92
  on('sync:assistant:session-ended', (msg) => {
72
93
  const id = msg?.sessionId || msg?.session?.id || msg?.id;
73
94
  if (id) hosts.ended(id);
95
+ if (id && msg?.destroyed) forgetTabState(id); // removed for good: its tab's UI state goes too
74
96
  });
75
97
  registerAction('launch-assistant', () => hosts.openOrFocus());
76
98
  registerAction('toggle-assistant', () => hosts.toggle());
@@ -9,6 +9,7 @@ import { storage } from './storage.js';
9
9
  import { initI18n, isReady as i18nReady, t } from './i18n.js';
10
10
  import { fetchStyleGuideProjects, fetchStyleGuideSummary } from './api.js';
11
11
  import { isGuest, hasPermission, showGuestToast } from './ui-sync.js';
12
+ import { registerLaunchPrefetch } from './launch-prefetch.js';
12
13
  import {
13
14
  getNativeLoopRouterClaimToken,
14
15
  getNativeLoopRouterWindowId,
@@ -90,6 +91,9 @@ let _prevLoopActive = false;
90
91
 
91
92
  // Cleanup: stored references for document-level listeners so closePanel() can remove them
92
93
  let _docListeners = [];
94
+ // What the last read found (templates, folders, the active loop, agents): a later open paints from it at once
95
+ // and reads it again behind it.
96
+ let _keptData = null;
93
97
 
94
98
  // Detail/editor state
95
99
  let _metaDirty = false;
@@ -283,9 +287,9 @@ function renderMcpProfileChips(presets, selected) {
283
287
  return renderLaunchSelect('mcp', options, selected);
284
288
  }
285
289
  return entries.map(([id, p]) => `
286
- <button class="as-launch-mcp-profile${id === selected ? ' active' : ''}" data-mcp-profile="${id}">
287
- <span class="as-launch-mcp-profile-name">${p.label || id}</span>
288
- <span class="as-launch-mcp-profile-tools">${p.tools} tools</span>
290
+ <button class="as-launch-mcp-profile${id === selected ? ' active' : ''}" data-mcp-profile="${_escLaunch(id)}">
291
+ <span class="as-launch-mcp-profile-name">${_escLaunch(p.label || id)}</span>
292
+ <span class="as-launch-mcp-profile-tools">${_escLaunch(p.tools)} tools</span>
289
293
  </button>
290
294
  `).join('');
291
295
  }
@@ -1187,9 +1191,9 @@ function renderModelChips(profileId, selectedModel) {
1187
1191
  return renderLaunchSelect('model', models, selectedModel, { groupByDesc: profileId === 'opencode' });
1188
1192
  }
1189
1193
  return models.map(m => `
1190
- <button class="as-launch-model${modelMatchesSelector(m, selectedModel) ? ' active' : ''}${m.tier ? ` as-launch-model--${m.tier}` : ''}" data-model="${modelSelectorValue(m)}">
1191
- <span class="as-launch-model-name">${m.label}</span>
1192
- <span class="as-launch-model-desc">${m.desc}</span>
1194
+ <button class="as-launch-model${modelMatchesSelector(m, selectedModel) ? ' active' : ''}${m.tier ? ` as-launch-model--${m.tier}` : ''}" data-model="${_escLaunch(modelSelectorValue(m))}">
1195
+ <span class="as-launch-model-name">${_escLaunch(m.label)}</span>
1196
+ <span class="as-launch-model-desc">${_escLaunch(m.desc)}</span>
1193
1197
  </button>
1194
1198
  `).join('');
1195
1199
  }
@@ -1299,7 +1303,10 @@ function wireLaunchSelect(root, type, onChange) {
1299
1303
  });
1300
1304
  });
1301
1305
 
1302
- document.addEventListener('click', e => { if (!sel.contains(e.target)) close(); });
1306
+ // Outside click closes it; removed with the panel (closePanel) like the studio's other document listeners.
1307
+ const onOutside = e => { if (!sel.contains(e.target)) close(); };
1308
+ document.addEventListener('click', onOutside);
1309
+ _docListeners.push(['click', onOutside]);
1303
1310
  });
1304
1311
  }
1305
1312
 
@@ -1721,6 +1728,8 @@ function buildTemplateCommand(template) {
1721
1728
  // ═══════════════════════════════════════════
1722
1729
 
1723
1730
  export function initAutomationStudio() {
1731
+ // Launch: the studio's lists are read once the page has painted (launch-prefetch.js), so the first open paints at once.
1732
+ registerLaunchPrefetch('automation-studio', prefetchAutomationStudio, { order: 72 });
1724
1733
  on('automations:open', (opts) => openPanel(opts || {}));
1725
1734
  on('automations:open-wizard', () => { openPanel(); });
1726
1735
  on('automations:import', () => { openPanel().then(() => triggerImport()); });
@@ -1759,15 +1768,44 @@ async function openPanel(opts = {}) {
1759
1768
  _panel.style.top = Math.max(48, (window.innerHeight - 520) / 2) + 'px';
1760
1769
 
1761
1770
  wirePanel();
1762
- await loadData();
1771
+ const kept = _keptData;
1772
+ if (kept) {
1773
+ // The studio as last read paints at once; it is read again behind it and the sidebar follows in place.
1774
+ _templates = kept.templates; _folders = kept.folders; _activeLoop = kept.activeLoop; _agents = kept.agents;
1775
+ updateSidebarFooter();
1776
+ } else {
1777
+ await loadData();
1778
+ }
1763
1779
  if (opts.templateId) {
1764
1780
  const tpl = _templates.find(t => t.id === opts.templateId);
1765
1781
  if (tpl) switchToDetail(tpl); else renderView();
1766
1782
  } else {
1767
1783
  renderView();
1768
1784
  }
1769
- requestAnimationFrame(() => { _backdrop.classList.add('open'); _panel.classList.add('open'); });
1785
+ requestAnimationFrame(() => { _backdrop?.classList.add('open'); _panel?.classList.add('open'); });
1770
1786
  startPolling();
1787
+ if (kept) refreshDataInPlace();
1788
+ }
1789
+
1790
+ // The data read again behind a studio painted from the kept one: the sidebar list is redrawn when the templates
1791
+ // or folders changed (an open template, the wizard and the editors keep what the user is doing).
1792
+ async function refreshDataInPlace() {
1793
+ const before = JSON.stringify([_templates, _folders]);
1794
+ await loadData();
1795
+ if (!_panel) return;
1796
+ if (JSON.stringify([_templates, _folders]) === before) return;
1797
+ const list = $('as-template-list');
1798
+ const scroll = list ? list.scrollTop : 0;
1799
+ renderSidebar();
1800
+ const count = $('as-total-count');
1801
+ if (count) count.textContent = `${PRESET_TEMPLATES.length + _templates.length}`;
1802
+ if (list && list.isConnected) list.scrollTop = scroll;
1803
+ }
1804
+
1805
+ /** Launch prefetch: the studio's data read once, so the first open paints at once (nothing is drawn here). */
1806
+ export function prefetchAutomationStudio() {
1807
+ if (_keptData || _panel || (isGuest() && !hasPermission('automations'))) return Promise.resolve();
1808
+ return loadData().catch(() => {});
1771
1809
  }
1772
1810
 
1773
1811
  function closePanel() {
@@ -2326,11 +2364,15 @@ async function promptCreateFolder() {
2326
2364
  // ═══════════════════════════════════════════
2327
2365
 
2328
2366
  async function loadData() {
2329
- try { _templates = await fetchLoopTemplates(); } catch { _templates = []; }
2330
- try { _folders = await fetchLoopFolders(); } catch { _folders = []; }
2331
- try { _activeLoop = await fetchActiveLoop(); } catch { _activeLoop = null; }
2367
+ // One round trip for the three lists (they were asked one after another), then the agents.
2368
+ const [templates, folders, active] = await Promise.allSettled([fetchLoopTemplates(), fetchLoopFolders(), fetchActiveLoop()]);
2369
+ _templates = templates.status === 'fulfilled' ? templates.value : [];
2370
+ _folders = folders.status === 'fulfilled' ? folders.value : [];
2371
+ _activeLoop = active.status === 'fulfilled' ? active.value : null;
2332
2372
  await refreshAgents();
2333
- updateSidebarFooter();
2373
+ if (_panel) updateSidebarFooter();
2374
+ // Kept only when the templates answered (a failed read must not paint an empty studio next time).
2375
+ if (templates.status === 'fulfilled') _keptData = { templates: _templates, folders: _folders, activeLoop: _activeLoop, agents: _agents };
2334
2376
  }
2335
2377
 
2336
2378
  function startPolling() {
@@ -3754,16 +3796,14 @@ async function refreshAgents() {
3754
3796
  try {
3755
3797
  const data = await fetchAgents();
3756
3798
  _agents = data?.agents || [];
3757
- // Update local output buffers with any text we haven't captured via WebSocket
3758
- for (const a of _agents) {
3759
- if (!_agentOutputs[a.id] && a.textLength > 0) {
3760
- // Fetch full details to get textOutput
3761
- try {
3762
- const full = await fetchAgent(a.id);
3763
- if (full?.textOutput) _agentOutputs[a.id] = full.textOutput;
3764
- } catch {}
3765
- }
3766
- }
3799
+ // Update local output buffers with any text we haven't captured via WebSocket (together, not one by one)
3800
+ await Promise.all(_agents.filter(a => !_agentOutputs[a.id] && a.textLength > 0).map(async (a) => {
3801
+ // Fetch full details to get textOutput
3802
+ try {
3803
+ const full = await fetchAgent(a.id);
3804
+ if (full?.textOutput) _agentOutputs[a.id] = full.textOutput;
3805
+ } catch {}
3806
+ }));
3767
3807
  } catch {}
3768
3808
  }
3769
3809
 
@@ -4742,9 +4782,9 @@ function renderSchedulesMain() {
4742
4782
  ? (qtModels.length > LAUNCH_CHIP_MAX
4743
4783
  ? renderLaunchSelect('qt-model', qtModels, qtCurrentModel, { groupByDesc: qtProfile === 'opencode' })
4744
4784
  : qtModels.map(m => `
4745
- <button class="as-launch-model${modelMatchesSelector(m, qtCurrentModel) ? ' active' : ''}${m.tier ? ` as-launch-model--${m.tier}` : ''}" data-action="qt-model" data-model="${modelSelectorValue(m)}">
4746
- <span class="as-launch-model-name">${m.label}</span>
4747
- <span class="as-launch-model-desc">${m.desc}</span>
4785
+ <button class="as-launch-model${modelMatchesSelector(m, qtCurrentModel) ? ' active' : ''}${m.tier ? ` as-launch-model--${m.tier}` : ''}" data-action="qt-model" data-model="${_escLaunch(modelSelectorValue(m))}">
4786
+ <span class="as-launch-model-name">${_escLaunch(m.label)}</span>
4787
+ <span class="as-launch-model-desc">${_escLaunch(m.desc)}</span>
4748
4788
  </button>
4749
4789
  `).join(''))
4750
4790
  : `<span class="as-launch-no-models">${esc(dynamicModelEmptyText(qtProfile))}</span>`;
@@ -4942,7 +4982,7 @@ function renderScheduleEditorMain() {
4942
4982
  </div>`).join('');
4943
4983
 
4944
4984
  const templateOptions = allTemplates.map(t =>
4945
- `<option value="${t.id}"${(s?.templateId === t.id) ? ' selected' : ''}>${esc(t.name)} (${t.category})</option>`
4985
+ `<option value="${esc(t.id)}"${(s?.templateId === t.id) ? ' selected' : ''}>${esc(t.name)} (${esc(t.category)})</option>`
4946
4986
  ).join('');
4947
4987
 
4948
4988
  // Reorder: Mon(1)–Sat(6) then Sun(0) so 2-column grid pairs weekdays nicely
@@ -8,6 +8,7 @@ import { state, emit, on } from './state.js';
8
8
  import { KEYS } from './constants.js';
9
9
  import { storage } from './storage.js';
10
10
  import { catColor } from './colors.js';
11
+ import { escapeHtml } from './utils.js';
11
12
 
12
13
  const $ = (id) => document.getElementById(id);
13
14
 
@@ -120,7 +121,7 @@ export function renderBookmarks() {
120
121
  item.innerHTML = `
121
122
  <div class="dropdown-item-dot" style="background:${color}"></div>
122
123
  <div class="dropdown-item-text">
123
- <div class="dropdown-item-title">${b.title}</div>
124
+ <div class="dropdown-item-title">${escapeHtml(String(b.title ?? ''))}</div>
124
125
  <div class="dropdown-item-sub">${b.category}</div>
125
126
  </div>
126
127
  <button class="icon-btn icon-btn--danger" style="width:22px;height:22px;font-size:13px;border:none" data-tooltip="Remove">&times;</button>
@@ -24,6 +24,7 @@ import { requestGeneratedSessionTitle } from './session-title.js';
24
24
  import { wireFileLinks } from './ui-file-links.js';
25
25
  import { WORKER_END_CLASS, backToAssistant, buildWorkerEndCard, endWorkerRunOnClose, forgetWorkerEnd, trackWorkerEnd, updateWorkerEndBusy } from './ui-worker-end.js';
26
26
  import { isDispatchedRun, isEndOfRunAbort } from './worker-end.js';
27
+ import { runAwaitsAnswer } from './sidepanel-session-status.js';
27
28
  // ── cp/ feature modules (SDK-engine CLI parity: diffs, agents, bash, perms, statusline) ──
28
29
  import { setCpCtx } from './cp/cp-ctx.js';
29
30
  import { buildDiffCard, buildDiffPreviewEl, finalizeDiffCard } from './cp/cp-diff.js';
@@ -46,7 +47,8 @@ import { historyHasResultSupport, historyResultsNotice, snapshotWantsResults, fu
46
47
  import { CP_STYLES } from './cp/cp-styles.js';
47
48
  import { TEMPORARY_CAPABILITY, keepsNothing, chooseTemporary, applyTemporary, temporaryRefusal, endTemporary, leaveTemporary, tabAfterTemporary, persistableTabs, temporaryBlocks, reattaches } from './cp/cp-temporary.js';
48
49
  import { paintTemporary } from './cp/cp-temporary-view.js';
49
- import { renderMarkdown, scrubStoredHtml } from './cp/cp-markdown.js';
50
+ import { renderMarkdown } from './cp/cp-markdown.js';
51
+ import { sanitizeStoredHtml } from './assistant/asst-sanitize.js';
50
52
  import { escapeHtml, classToken } from './cp/cp-markdown.js';
51
53
  import { rehydrateStoredTranscript } from './cp/cp-rehydrate.js';
52
54
  import { compactHolds, compactQueue, isCompactCommand, newConversation, nextQueued, ownsQueued, queuedRefusal, restoredQueue, QUEUED_REFUSAL_LINES } from './cp/cp-compose.js';
@@ -178,7 +180,7 @@ let _cliBanner = null;
178
180
  let _marked = null;
179
181
  (async () => {
180
182
  try {
181
- const m = await import('https://cdn.jsdelivr.net/npm/marked@14/lib/marked.esm.js').catch(() => null);
183
+ const m = await import('/vendor/marked/marked-14.1.4.esm.js').catch(() => null);
182
184
  if (m) {
183
185
  _marked = m.marked || m.default;
184
186
  if (_marked?.setOptions) _marked.setOptions({ breaks: true, gfm: true });
@@ -2338,6 +2340,11 @@ function injectStyles() {
2338
2340
  .cp-session-pill.cp-pill-running .term-minimized-pill-icon { color: rgba(212, 162, 127, 1); }
2339
2341
  .cp-session-pill:hover .term-minimized-pill-icon { color: rgba(212, 162, 127, 0.85); }
2340
2342
  .cp-session-pill .term-minimized-pill-icon svg { width: 12px; height: 12px; }
2343
+ /* A session that waits on you (a permission, a plan, an MCP server's request, a question): the
2344
+ warn hairline and mark of the Assistant's and a detached window's pills, in place of the
2345
+ working look (paintPillState). */
2346
+ .cp-session-pill.cp-pill-attention { border-color: color-mix(in srgb, var(--accent-orange, #FFB74D) 42%, transparent); }
2347
+ .cp-session-pill.cp-pill-attention .term-minimized-pill-icon { color: var(--accent-orange, #FFB74D); }
2341
2348
  .cp-session-pill.cp-pill-enter {
2342
2349
  animation: cp-pill-pop-in 0.25s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
2343
2350
  }
@@ -2909,6 +2916,7 @@ function newTabState(sessionId = null, label = 'New chat', id = null) {
2909
2916
  mcpServers: [], // from system/init + mcp_status events
2910
2917
  automationRunId: null,
2911
2918
  automationActive: false,
2919
+ automationAwaits: false, // the run's turn ended asking the user (runAwaitsAnswer): its pill needs you
2912
2920
  automationOwnerId: null,
2913
2921
  releasedRunId: null, // the ended run this tab showed before the user continued it by hand
2914
2922
  };
@@ -3201,6 +3209,9 @@ function _createTrayPill(tab) {
3201
3209
  if (idx >= 0) closeTab(idx);
3202
3210
  });
3203
3211
  pill.style.display = 'none'; // Hidden by default; renderPills() controls visibility
3212
+ // A pill made after the tab started working (a tray that was not there yet) starts out working too,
3213
+ // or needing you.
3214
+ paintPillState(pill, tab);
3204
3215
  tray.appendChild(pill);
3205
3216
  return pill;
3206
3217
  }
@@ -3220,8 +3231,10 @@ function _renderPillsNow() {
3220
3231
  const panelOpen = _visible;
3221
3232
  for (let i = 0; i < _tabs.length; i++) {
3222
3233
  const tab = _tabs[i];
3223
- if (!tab.pillEl) tab.pillEl = _createTrayPill(tab);
3234
+ if (!tab.pillEl?.isConnected) tab.pillEl = _createTrayPill(tab);
3224
3235
  if (!tab.pillEl) continue;
3236
+ // Every pass says it again: a re-render never leaves a working tab's pill idle.
3237
+ paintPillState(tab.pillEl, tab);
3225
3238
  const isActive = i === activeIdx;
3226
3239
  const shouldShow = !isActive || !panelOpen;
3227
3240
  const wasHidden = tab.pillEl.style.display === 'none';
@@ -3247,15 +3260,52 @@ function updatePillLabel(tab) {
3247
3260
  if (lbl) lbl.textContent = tab.label;
3248
3261
  }
3249
3262
 
3263
+ /**
3264
+ * A tab works while its turn runs, and also once the turn ended while background work it started
3265
+ * still runs (agents, shells, workflows: the CLI's own live list, non-ambient, the statusline's count):
3266
+ * the session reports back when they end, so its pill keeps moving, like their cards in the transcript.
3267
+ */
3268
+ function tabWorking(tab) {
3269
+ return !!tab?.running || (Array.isArray(tab?.backgroundWork) && tab.backgroundWork.length > 0);
3270
+ }
3271
+
3272
+ /**
3273
+ * A tab needs you while its session waits on your answer: a permission, a plan approval or an MCP
3274
+ * server's request (the card on screen, and the ones queued behind it), or a question; and, for a
3275
+ * dispatched worker, while its turn ended asking you something (the run's outcome: automationAwaits).
3276
+ */
3277
+ function tabNeedsYou(tab) {
3278
+ return !!(tab?._activePerm || tab?._permQueue?.length || tab?.pendingAskRequestId || tab?.automationAwaits);
3279
+ }
3280
+
3281
+ /** What a tab's pill says: it needs you > it works > it rests (read for a detached window's pill too). */
3282
+ function paintPillState(pill, tab) {
3283
+ const needsYou = tabNeedsYou(tab);
3284
+ pill.classList.toggle('cp-pill-attention', needsYou);
3285
+ pill.classList.toggle('cp-pill-running', !needsYou && tabWorking(tab));
3286
+ }
3287
+
3288
+ // Whether each tab was saved needing you: a change is written at once, for the placeholder a reload
3289
+ // shows and the pill of a detached window that has not loaded yet (ui-sidepanel-tray.js).
3290
+ const _needsYouSaved = new WeakMap();
3250
3291
  function updatePillRunning(tab) {
3251
- if (tab.pillEl) tab.pillEl.classList.toggle('cp-pill-running', tab.running);
3292
+ if (tab.pillEl) paintPillState(tab.pillEl, tab);
3293
+ const needsYou = tabNeedsYou(tab);
3294
+ if ((_needsYouSaved.get(tab) || false) === needsYou) return;
3295
+ _needsYouSaved.set(tab, needsYou);
3296
+ saveTabs();
3252
3297
  }
3253
3298
 
3254
3299
  function saveTabs() {
3255
3300
  try {
3256
3301
  // A temporary tab is never written: not the tab, its label, its queue or its session id.
3302
+ // `backgroundWork`: how many background agents and shells it still ran (tabWorking). A tab whose turn
3303
+ // ended while they run is working too: after a reload its placeholder says so and the panel loads to ask
3304
+ // (ui-sidepanel-tray.js). Never restored: the reattach's live list says what still runs. `needsYou`:
3305
+ // a permission or a question waited on the user (tabNeedsYou): its placeholder says so and the panel
3306
+ // loads to ask, and the reattach brings the card back.
3257
3307
  const kept = persistableTabs(_tabs, _activeTabIdx);
3258
- const localTabs = kept.tabs.map(t => ({ id: t.id, sessionId: t.sessionId, label: t.label, titleState: t.titleState === 'generating' ? 'default' : (t.titleState || 'default'), sessionCost: t.sessionCost || 0, running: t.running || false, project: t.project || '', model: t.model || '', effort: t.effort || 'off', planMode: t.planMode || false, ...savedMode(t), toolPolicy: t.toolPolicy || 'full', accountId: t.accountId || '', session: normalizeSession(t.session), transcriptAt: t.transcriptAt || null, planFilePath: t.planFilePath || '', conversation: t.conversation || '', queue: t.queue || [], queuePaused: t.queuePaused || false, automationRunId: t.automationRunId || null, automationActive: !!t.automationActive, automationOwnerId: t.automationOwnerId || null, releasedRunId: t.releasedRunId || null }));
3308
+ const localTabs = kept.tabs.map(t => ({ id: t.id, sessionId: t.sessionId, label: t.label, titleState: t.titleState === 'generating' ? 'default' : (t.titleState || 'default'), sessionCost: t.sessionCost || 0, running: t.running || false, backgroundWork: Array.isArray(t.backgroundWork) ? t.backgroundWork.length : 0, needsYou: tabNeedsYou(t), project: t.project || '', model: t.model || '', effort: t.effort || 'off', planMode: t.planMode || false, ...savedMode(t), toolPolicy: t.toolPolicy || 'full', accountId: t.accountId || '', session: normalizeSession(t.session), transcriptAt: t.transcriptAt || null, planFilePath: t.planFilePath || '', conversation: t.conversation || '', queue: t.queue || [], queuePaused: t.queuePaused || false, automationRunId: t.automationRunId || null, automationActive: !!t.automationActive, automationOwnerId: t.automationOwnerId || null, releasedRunId: t.releasedRunId || null }));
3259
3309
  const localRunIds = new Set(localTabs.map((tab) => tab.automationRunId).filter(Boolean));
3260
3310
  let foreignAutomationTabs = [];
3261
3311
  try {
@@ -4550,9 +4600,9 @@ function flushAllSessionSnapshots() {
4550
4600
  function renderStoredSession(snapshot, $msgs, tab) {
4551
4601
  const norm = _normalizeSnapshotEntry(snapshot);
4552
4602
  if (!norm || !$msgs) return false;
4553
- // A snapshot comes back from storage and may predate the sanitiser: nothing
4554
- // that can run or load code is put back (cp/cp-markdown.js).
4555
- $msgs.replaceChildren(scrubStoredHtml(norm.html));
4603
+ // A snapshot comes back from storage and may predate the sanitiser: it is rebuilt
4604
+ // from the stored-transcript allowlist every panel uses (asst-sanitize.js).
4605
+ $msgs.replaceChildren(sanitizeStoredHtml(norm.html));
4556
4606
  // Restored DOM has no event handlers. Pending prompts (permission, plan, ask
4557
4607
  // cards) are stale and go, answered ones stay inert; the read-only actions
4558
4608
  // are wired again from what the markup saved: rewind (prompt uuid), the
@@ -5282,10 +5332,14 @@ function _setBackgroundWork(tab, tasks) {
5282
5332
  // reloaded while it ran) gets one, so /tasks can show and stop it.
5283
5333
  try { adoptLiveTasks(tab.tasks, tasks); } catch {}
5284
5334
  try { reconcileTasks(tab.tasks, tasks); } catch {}
5335
+ const before = Array.isArray(tab.backgroundWork) ? tab.backgroundWork.length : 0;
5285
5336
  tab.backgroundWork = (Array.isArray(tasks) ? tasks : [])
5286
5337
  .filter(t => t && !t.ambient)
5287
5338
  .map(t => ({ id: t.task_id, type: t.task_type || '', description: t.description || '' }));
5288
5339
  if (tab === activeTab()) { try { renderStatusline(tab); } catch {} }
5340
+ updatePillRunning(tab);
5341
+ // Saved as it changes: a reload with only this left running loads the panel to ask (saveTabs).
5342
+ if (tab.backgroundWork.length !== before) saveTabs();
5289
5343
  }
5290
5344
 
5291
5345
  function _processTabMsg(tab, msg) {
@@ -5343,6 +5397,7 @@ function _processTabMsg(tab, msg) {
5343
5397
  if (tab._pendingPlan?.requestId === msg.request_id) tab._pendingPlan = null;
5344
5398
  if (tab._activePerm) { tab._activePerm = false; _showNextPerm(tab); }
5345
5399
  else _flushMsgBuffer(tab);
5400
+ updatePillRunning(tab);
5346
5401
  break;
5347
5402
  }
5348
5403
  case 'reattach_result': {
@@ -6885,6 +6940,7 @@ function sendAskAnswer(tab, questions, answers, annotations = null) {
6885
6940
  tab.pendingAskRequestId = null;
6886
6941
  tab.pendingAskToolUseId = null;
6887
6942
  tab.pendingAskBufferedAnswer = null;
6943
+ updatePillRunning(tab); // answered: the pill no longer needs you
6888
6944
  // Flush BEFORE clearing askRenderedViaControl — buffered assistant events
6889
6945
  // must see the flag so buildAskFromToolUse returns a hidden placeholder
6890
6946
  // instead of rendering a duplicate card.
@@ -7400,6 +7456,7 @@ function handleControlRequest(tab, msg) {
7400
7456
  if (toolName === 'AskUserQuestion') {
7401
7457
  // AskUserQuestion: save the request_id — answer is sent via control_response when user picks an option
7402
7458
  tab.pendingAskRequestId = requestId;
7459
+ updatePillRunning(tab); // the pill needs you
7403
7460
  // If tool_use block hasn't rendered the ask card yet, render from control_request input
7404
7461
  // Also check askRenderedViaControl to prevent duplicate cards from denial control_requests
7405
7462
  if (!tab.pendingAskToolUseId && !tab.askRenderedViaControl) {
@@ -8501,6 +8558,7 @@ function finishTab(tab, skipNotif) {
8501
8558
  if (tab.sessionId && tab._snapshotDirty) flushSessionSnapshotSave(tab);
8502
8559
  hideThinking(tab); setRunning(tab, false); tab._wasRunning = false; tab.currentMsgEl = null; tab.currentMsgId = null; tab.pendingAskToolUseId = null; tab.pendingAskRequestId = null; tab.pendingAskBufferedAnswer = null; tab.askRenderedViaControl = false; tab.sendStartedAt = null; tab._planContentCaptured = false; if (tab._stream?.mdTimer) clearTimeout(tab._stream.mdTimer); tab._stream = null;
8503
8560
  tab.currentActivity = null;
8561
+ updatePillRunning(tab); // (its question went with the turn)
8504
8562
  if (tab === activeTab()) { try { renderStatusline(tab); } catch {} }
8505
8563
  if (tab.compacting) { tab.compacting = false; if (tab === activeTab()) _setCompactingUI(false); }
8506
8564
  tab._compactSentAt = 0; // a Compact the user started ends here too (cp-compose.js)
@@ -10347,6 +10405,14 @@ function applyClaudeVisibility(visible) {
10347
10405
 
10348
10406
  export function isClaudePanelOpen() { return _visible; }
10349
10407
 
10408
+ /**
10409
+ * Boot the panel without showing it (ui-sidepanel-tray.js, when a tab saved while it worked comes back
10410
+ * after a reload): its tabs reconnect and reattach, so their pills say what the sessions do now.
10411
+ */
10412
+ export async function bootClaudePanel() {
10413
+ await _ensureClaudePanelReady();
10414
+ }
10415
+
10350
10416
  export function endHostedClaudeSessions() {
10351
10417
  while (_tabs.length) closeTab(_tabs.length - 1);
10352
10418
  }
@@ -10396,6 +10462,8 @@ export async function attachClaudeAutomation(run, { focus = !!run?.focus, isCanc
10396
10462
  tab.project = run.cwd || tab.project;
10397
10463
  tab.model = run.model || tab.model;
10398
10464
  tab.effort = run.effort || tab.effort;
10465
+ // A warm worker whose turn ended on a question needs you, like one with a card up (setRunning repaints).
10466
+ tab.automationAwaits = runAwaitsAnswer(run);
10399
10467
  setRunning(tab, running);
10400
10468
  updatePillLabel(tab);
10401
10469
  tab._automationIteration = run.currentIteration;
@@ -6,6 +6,7 @@
6
6
  export {
7
7
  toggleCodexPanel,
8
8
  isCodexPanelOpen,
9
+ bootCodexPanel,
9
10
  attachCodexAutomation,
10
11
  detachCodexAutomation,
11
12
  handleCodexAutomationEvent,
@@ -12,7 +12,7 @@ import { updateMemory, deleteMemory, fetchMemory } from './api.js';
12
12
  import { KEYS } from './constants.js';
13
13
  import { storage } from './storage.js';
14
14
  import { catColor } from './colors.js';
15
- import { truncate, formatMemoryContent, exportMemoryAsMarkdown, rafThrottle } from './utils.js';
15
+ import { truncate, formatMemoryContent, exportMemoryAsMarkdown, rafThrottle, escapeHtml } from './utils.js';
16
16
  import { t, getLocale } from './i18n.js';
17
17
  import { savePanelLayout, restorePanelLayout } from './ui-panels.js';
18
18
  import { sendSync as _sendSync, hasPermission, isGuest } from './ui-sync.js';
@@ -111,7 +111,7 @@ function showTagDeleteModal(tagName, onConfirm) {
111
111
  overlay.innerHTML = `
112
112
  <div class="tag-delete-modal">
113
113
  <div class="tag-delete-modal-title">Remove tag</div>
114
- <div class="tag-delete-modal-tag">${tagName}</div>
114
+ <div class="tag-delete-modal-tag">${escapeHtml(tagName)}</div>
115
115
  <div class="tag-delete-modal-actions">
116
116
  <button class="action-btn action-btn--ghost tag-modal-cancel">Cancel</button>
117
117
  <button class="action-btn action-btn--danger tag-modal-confirm">Delete</button>
@@ -363,7 +363,7 @@ function openCategoryChangeModal(card, node) {
363
363
  <div class="cat-opt-dot" style="color:${catColor(cat)}; background:${catColor(cat)}"></div>
364
364
  <div class="cat-opt-info">
365
365
  <span class="cat-opt-name">${cat}</span>
366
- ${desc ? `<span class="cat-opt-desc">${desc}</span>` : ''}
366
+ ${desc ? `<span class="cat-opt-desc">${escapeHtml(desc)}</span>` : ''}
367
367
  </div>
368
368
  </div>`;
369
369
  }
@@ -375,7 +375,7 @@ function openCategoryChangeModal(card, node) {
375
375
  <div class="cat-opt-dot" style="color:${catColor(cat)}; background:${catColor(cat)}"></div>
376
376
  <div class="cat-opt-info">
377
377
  <span class="cat-opt-name">${cat}</span>
378
- ${desc ? `<span class="cat-opt-desc">${desc}</span>` : ''}
378
+ ${desc ? `<span class="cat-opt-desc">${escapeHtml(desc)}</span>` : ''}
379
379
  </div>
380
380
  ${isActive ? '<svg class="cat-opt-check" viewBox="0 0 24 24"><path d="M20 6L9 17l-5-5"/></svg>' : ''}
381
381
  </div>`;
@@ -863,7 +863,7 @@ export function openMemoryCard(node, savedPosition) {
863
863
  if (!value) return;
864
864
  const row = document.createElement('div');
865
865
  row.className = 'detail-meta-row';
866
- row.innerHTML = `<span style="color:rgba(255,255,255,0.3)">${label}</span><span>${value}</span>`;
866
+ row.innerHTML = `<span style="color:rgba(255,255,255,0.3)">${label}</span><span>${escapeHtml(String(value))}</span>`;
867
867
  meta.appendChild(row);
868
868
  };
869
869
  addMeta('Project', p.project);
@@ -893,7 +893,7 @@ export function openMemoryCard(node, savedPosition) {
893
893
  const filesContent = q(card, 'files-content');
894
894
  if (p.related_files && p.related_files.length) {
895
895
  filesContent.innerHTML = p.related_files.map(f =>
896
- `<div style="font-family:'JetBrains Mono','SF Mono',Consolas,monospace;font-size:12px;color:rgba(255,255,255,0.45);padding:2px 0">${f}</div>`
896
+ `<div style="font-family:'JetBrains Mono','SF Mono',Consolas,monospace;font-size:12px;color:rgba(255,255,255,0.45);padding:2px 0">${escapeHtml(String(f))}</div>`
897
897
  ).join('');
898
898
  filesDiv.style.display = '';
899
899
  } else {
@@ -65,6 +65,7 @@ const handlers = createFileLinkHandlers({
65
65
  say: fileLinkText,
66
66
  notify: showFileLinkNotice,
67
67
  follow: (anchor) => {
68
+ if (anchor.protocol !== 'http:' && anchor.protocol !== 'https:') return; // an address of this app, never a script link
68
69
  if (anchor.target === '_blank') window.open(anchor.href, '_blank', 'noopener,noreferrer');
69
70
  else window.location.assign(anchor.href);
70
71
  },
@@ -7,6 +7,7 @@
7
7
  import { emit, on } from './state.js';
8
8
  import { fetchImages, toggleImageFavorite, deleteImage } from './api.js';
9
9
  import { addImageToWhiteboard } from './ui-whiteboard.js';
10
+ import { escapeHtml } from './utils.js';
10
11
 
11
12
  const $ = (id) => document.getElementById(id);
12
13
 
@@ -153,9 +154,9 @@ function buildPanelHTML() {
153
154
 
154
155
  function buildCard(img) {
155
156
  return `
156
- <div class="ig-card" data-filename="${img.filename}">
157
+ <div class="ig-card" data-filename="${escapeHtml(img.filename).replace(/"/g, '&quot;')}">
157
158
  <div class="ig-thumb-wrap">
158
- <img class="ig-thumb" src="${imageUrl(img.filename)}" loading="lazy" alt="${img.filename}">
159
+ <img class="ig-thumb" src="${imageUrl(img.filename)}" loading="lazy" alt="${escapeHtml(img.filename).replace(/"/g, '&quot;')}">
159
160
  <div class="ig-card-actions">
160
161
  <button class="ig-card-btn ig-card-fav${img.favorite ? ' active' : ''}" data-action="fav" data-tooltip="${img.favorite ? 'Unfavorite' : 'Favorite'}">${img.favorite ? ICON_HEART_FILL : ICON_HEART}</button>
161
162
  <button class="ig-card-btn ig-card-wb" data-action="whiteboard" data-tooltip="Add to Whiteboard">${ICON_WHITEBOARD}</button>
@@ -164,7 +165,7 @@ function buildCard(img) {
164
165
  </div>
165
166
  </div>
166
167
  <div class="ig-card-info">
167
- <span class="ig-card-name">${img.filename.length > 30 ? img.filename.slice(0, 27) + '...' : img.filename}</span>
168
+ <span class="ig-card-name">${escapeHtml(img.filename.length > 30 ? img.filename.slice(0, 27) + '...' : img.filename)}</span>
168
169
  <span class="ig-card-meta">${formatSize(img.size)} · ${formatDate(img.modifiedAt)}</span>
169
170
  </div>
170
171
  </div>`;
@@ -185,9 +186,9 @@ function openLightbox(filename) {
185
186
  lb.innerHTML = `
186
187
  <div class="ig-lb-overlay" id="ig-lb-overlay"></div>
187
188
  <div class="ig-lb-content">
188
- <img class="ig-lb-img" src="${imageUrl(filename)}" alt="${filename}">
189
+ <img class="ig-lb-img" src="${imageUrl(filename)}" alt="${escapeHtml(filename).replace(/"/g, '&quot;')}">
189
190
  <div class="ig-lb-bar">
190
- <span class="ig-lb-name">${filename}</span>
191
+ <span class="ig-lb-name">${escapeHtml(filename)}</span>
191
192
  ${img ? `<span class="ig-lb-size">${formatSize(img.size)}</span>` : ''}
192
193
  <div class="ig-lb-actions">
193
194
  <button class="ig-btn" id="ig-lb-fav" data-tooltip="Toggle favorite">${img?.favorite ? ICON_HEART_FILL : ICON_HEART}</button>
@@ -37,6 +37,7 @@ const _tunnel = createTunnelCard({
37
37
  let _keyMode = 'auto'; // 'auto' | 'custom'
38
38
  let _revealedKey = null; // shown only right after generation
39
39
  let _permissions = { terminal: false, whiteboard: false, memories: true, skills: false, cards: true, browser: false };
40
+ let _dataLoaded = false; // the status was read once (the badge's read at start-up counts)
40
41
 
41
42
  // ─── Dropdown open/close ────────────────
42
43
 
@@ -47,7 +48,12 @@ function openDropdown() {
47
48
  dd.style.display = '';
48
49
  $('invite-btn')?.classList.add('active');
49
50
  emit('panel:close-all-dropdowns-except', 'invite');
50
- loadAndRender();
51
+ // The status as last read (the badge's read at start-up, an earlier open) paints at once once the tunnel's
52
+ // state is known too (an earlier open asked it); the read below redraws only what changed, and never under a
53
+ // field being typed in.
54
+ const early = _dataLoaded && _tunnelKind !== null;
55
+ if (early) render();
56
+ loadAndRender({ inPlace: early });
51
57
  }
52
58
 
53
59
  function closeDropdown() {
@@ -65,7 +71,10 @@ function toggleDropdown() {
65
71
 
66
72
  // ─── Data fetching ──────────────────────
67
73
 
68
- async function loadAndRender() {
74
+ const inviteSnapshot = () => { try { return JSON.stringify([_data, _permissions, _tunnel.view()]); } catch { return ''; } };
75
+
76
+ async function loadAndRender({ inPlace = false } = {}) {
77
+ const before = inPlace ? inviteSnapshot() : null;
69
78
  try {
70
79
  const [inviteRes, , permRes] = await Promise.all([
71
80
  fetchInviteStatus(),
@@ -73,10 +82,16 @@ async function loadAndRender() {
73
82
  fetchInvitePermissions(),
74
83
  ]);
75
84
  _data = inviteRes;
85
+ _dataLoaded = true;
76
86
  if (permRes?.permissions) _permissions = permRes.permissions;
77
87
  } catch (err) {
78
88
  console.warn('[invite] Failed to load status:', err);
79
89
  }
90
+ if (inPlace) {
91
+ if (inviteSnapshot() === before) return;
92
+ const typing = document.activeElement?.closest?.('#invite-dropdown') && /^(INPUT|TEXTAREA)$/.test(document.activeElement.tagName);
93
+ if (typing) return; // the next open shows it
94
+ }
80
95
  render();
81
96
  }
82
97
 
@@ -524,6 +539,7 @@ export function initInvite() {
524
539
  // Load initial badge count
525
540
  fetchInviteStatus().then((res) => {
526
541
  _data = res;
542
+ _dataLoaded = true;
527
543
  updateBadge();
528
544
  }).catch(() => {});
529
545
  }