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
@@ -0,0 +1,1008 @@
1
+ // ─────────────────────────────────────────────────────────────────────────────
2
+ // OpenCode 2 → the OpenCode 1 shapes the rest of SynaBun reads.
3
+ //
4
+ // SynaBun was written against OpenCode 1's API (sessions with `{ info, parts }`
5
+ // messages, `message.updated` / `message.part.updated` / `message.part.delta`
6
+ // events, `permission.asked` / `question.asked`, tools called `bash` and
7
+ // `task`). OpenCode 2 is a different API: everything under /api/*, sessions
8
+ // whose assistant messages carry a `content` list, a stream of step / text /
9
+ // reasoning / tool events, permissions with `action` / `resources`, questions as
10
+ // forms, the shell tool called `shell` and the sub-agent tool `subagent`.
11
+ //
12
+ // lib/opencode2-client.js speaks OpenCode 2 and hands everything it gets back
13
+ // through this module, so the OpenCode panel, the Assistant's OpenCode brain,
14
+ // workers, loops, the usage meter and the worker activity relay keep reading
15
+ // one set of shapes. Pure functions plus one stateful event translator (a
16
+ // stream's parts are assembled from its deltas); no I/O.
17
+ // ─────────────────────────────────────────────────────────────────────────────
18
+
19
+ const str = (v) => (typeof v === 'string' ? v : v == null ? '' : String(v));
20
+ const num = (v, fallback = 0) => (Number.isFinite(Number(v)) ? Number(v) : fallback);
21
+ const obj = (v) => (v && typeof v === 'object' && !Array.isArray(v) ? v : {});
22
+
23
+ // ── Names ───────────────────────────────────────────────────────────────────
24
+
25
+ // OpenCode 2 renamed two tools (and their permission actions); every other
26
+ // tool and action keeps its OpenCode 1 name.
27
+ const TOOL_TO_V1 = Object.freeze({ shell: 'bash', subagent: 'task' });
28
+ const TOOL_TO_V2 = Object.freeze({ bash: 'shell', task: 'subagent' });
29
+
30
+ /** A tool or permission name of OpenCode 2 as OpenCode 1 called it. */
31
+ export function v1ToolName(name) {
32
+ const value = str(name);
33
+ return TOOL_TO_V1[value] || value;
34
+ }
35
+
36
+ /** A tool or permission name of OpenCode 1 as OpenCode 2 calls it. */
37
+ export function v2ToolName(name) {
38
+ const value = str(name);
39
+ return TOOL_TO_V2[value] || value;
40
+ }
41
+
42
+ // A part id the panel can use anywhere (an attribute, a map key): the message
43
+ // id, the kind of part and its key. The same part gets the same id from the
44
+ // live stream and from a transcript read later, so a reload never doubles it.
45
+ const idSafe = (v) => str(v).replace(/[^A-Za-z0-9_-]/g, '-');
46
+ export function partId(messageID, kind, key = 0) {
47
+ return `prt_${idSafe(str(messageID).replace(/^msg_/, ''))}_${kind}_${idSafe(key)}`;
48
+ }
49
+
50
+ // ── Models, rules, errors ───────────────────────────────────────────────────
51
+
52
+ /** `{ providerID, modelID }` (or "provider/model") + a variant → OpenCode 2's `{ id, providerID, variant? }`. */
53
+ export function modelRefToV2(model, variant = null) {
54
+ let providerID = '';
55
+ let modelID = '';
56
+ if (typeof model === 'string') {
57
+ const text = model.trim();
58
+ const slash = text.indexOf('/');
59
+ if (slash > 0 && slash < text.length - 1) { providerID = text.slice(0, slash); modelID = text.slice(slash + 1); }
60
+ } else if (model && typeof model === 'object') {
61
+ providerID = str(model.providerID).trim();
62
+ modelID = str(model.modelID || model.id).trim();
63
+ }
64
+ if (!providerID || !modelID) return null;
65
+ const ref = { id: modelID, providerID };
66
+ const v = str(variant || (model && typeof model === 'object' ? model.variant : '')).trim();
67
+ if (v) ref.variant = v;
68
+ return ref;
69
+ }
70
+
71
+ /** OpenCode 2's `{ id, providerID, variant? }` → OpenCode 1's `{ providerID, modelID, variant? }`. */
72
+ export function modelRefToV1(ref) {
73
+ if (!ref || typeof ref !== 'object' || !ref.id || !ref.providerID) return null;
74
+ const out = { providerID: str(ref.providerID), modelID: str(ref.id) };
75
+ if (ref.variant) out.variant = str(ref.variant);
76
+ return out;
77
+ }
78
+
79
+ /** OpenCode 2 rules `{ action, resource, effect }` → OpenCode 1 `{ permission, pattern, action }`. */
80
+ export function rulesToV1(rules) {
81
+ if (!Array.isArray(rules)) return undefined;
82
+ return rules
83
+ .filter((rule) => rule && typeof rule === 'object')
84
+ .map((rule) => ({ permission: v1ToolName(rule.action), pattern: str(rule.resource || '*'), action: str(rule.effect) }));
85
+ }
86
+
87
+ /**
88
+ * OpenCode 1 rules (`[{ permission, pattern, action }]`, or the config's
89
+ * `{ bash: 'ask', edit: { '*.md': 'allow' } }` map) → OpenCode 2 rules.
90
+ */
91
+ export function rulesToV2(rules) {
92
+ if (rules == null) return undefined;
93
+ const out = [];
94
+ const push = (permission, pattern, action) => {
95
+ const effect = str(action);
96
+ if (!['allow', 'deny', 'ask'].includes(effect)) return;
97
+ out.push({ action: v2ToolName(permission || '*'), resource: str(pattern || '*'), effect });
98
+ };
99
+ if (Array.isArray(rules)) {
100
+ for (const rule of rules) {
101
+ if (!rule || typeof rule !== 'object') continue;
102
+ if ('effect' in rule) push(rule.action, rule.resource, rule.effect);
103
+ else push(rule.permission, rule.pattern, rule.action);
104
+ }
105
+ } else if (typeof rules === 'object') {
106
+ for (const [permission, value] of Object.entries(rules)) {
107
+ if (typeof value === 'string') push(permission, '*', value);
108
+ else if (value && typeof value === 'object') for (const [pattern, action] of Object.entries(value)) push(permission, pattern, action);
109
+ }
110
+ }
111
+ return out;
112
+ }
113
+
114
+ /**
115
+ * An OpenCode 2 error (`{ type, message, status?, response? }`, or a tagged
116
+ * `{ _tag, message }`) as OpenCode 1's named error `{ name, data }`, which the
117
+ * panel, the brain and the loops describe (ocp-v2-events.js describeError).
118
+ */
119
+ export function errorToV1(error) {
120
+ if (!error) return { name: 'UnknownError', data: { message: 'OpenCode failed' } };
121
+ if (typeof error === 'string') return { name: 'UnknownError', data: { message: error } };
122
+ const type = str(error.type || error._tag || error.name).toLowerCase();
123
+ const message = str(error.message || error.data?.message) || 'OpenCode failed';
124
+ const status = Number(error.status);
125
+ if (/abort|interrupt/.test(type)) return { name: 'MessageAbortedError', data: { message } };
126
+ if (/auth|credential|unauthori[sz]ed|api.?key/.test(type) || status === 401) {
127
+ return { name: 'ProviderAuthError', data: { providerID: str(error.providerID), message } };
128
+ }
129
+ if (/context|overflow/.test(type)) return { name: 'ContextOverflowError', data: { message, ...(error.response?.body ? { responseBody: str(error.response.body) } : {}) } };
130
+ if (/length|max.?tokens/.test(type)) return { name: 'MessageOutputLengthError', data: { message } };
131
+ if (/content.?filter/.test(type)) return { name: 'ContentFilterError', data: { message } };
132
+ if (/busy/.test(type)) return { name: 'SessionBusyError', data: { message } };
133
+ if (Number.isFinite(status) && status > 0) {
134
+ return {
135
+ name: 'APIError',
136
+ data: {
137
+ message,
138
+ statusCode: status,
139
+ isRetryable: status === 429 || status >= 500,
140
+ ...(error.response?.body ? { responseBody: str(error.response.body) } : {}),
141
+ },
142
+ };
143
+ }
144
+ return { name: 'UnknownError', data: { message, ...(type ? { type } : {}) } };
145
+ }
146
+
147
+ const ZERO_TOKENS = Object.freeze({ input: 0, output: 0, reasoning: 0, cache: Object.freeze({ read: 0, write: 0 }) });
148
+
149
+ /** OpenCode 2 token usage → OpenCode 1's (the same fields; a missing one is 0). */
150
+ export function tokensToV1(tokens) {
151
+ const t = obj(tokens);
152
+ const cache = obj(t.cache);
153
+ return { input: num(t.input), output: num(t.output), reasoning: num(t.reasoning), cache: { read: num(cache.read), write: num(cache.write) } };
154
+ }
155
+
156
+ // ── Sessions ────────────────────────────────────────────────────────────────
157
+
158
+ /** OpenCode 2 Session.Info → OpenCode 1 Session. `extra` fills what OpenCode 2 does not keep (slug, version). */
159
+ export function sessionToV1(info, extra = {}) {
160
+ const s = obj(info);
161
+ const time = obj(s.time);
162
+ const out = {
163
+ id: str(s.id),
164
+ slug: str(s.slug || extra.slug),
165
+ projectID: str(s.projectID),
166
+ directory: str(s.location?.directory || s.directory || extra.directory),
167
+ title: str(s.title || extra.title),
168
+ version: str(extra.version || s.version),
169
+ cost: num(s.cost),
170
+ tokens: tokensToV1(s.tokens),
171
+ time: { created: num(time.created), updated: num(time.updated || time.created) },
172
+ };
173
+ if (s.parentID) out.parentID = str(s.parentID);
174
+ if (s.agent) out.agent = str(s.agent);
175
+ if (s.model?.id && s.model?.providerID) out.model = { id: str(s.model.id), providerID: str(s.model.providerID), ...(s.model.variant ? { variant: str(s.model.variant) } : {}) };
176
+ if (s.metadata && typeof s.metadata === 'object') out.metadata = s.metadata;
177
+ if (time.archived) out.time.archived = num(time.archived);
178
+ if (extra.compacting) out.time.compacting = num(extra.compacting);
179
+ const permission = rulesToV1(s.permissions);
180
+ if (permission) out.permission = permission;
181
+ if (s.revert?.messageID) {
182
+ out.revert = { messageID: str(s.revert.messageID) };
183
+ if (s.revert.partID) out.revert.partID = str(s.revert.partID);
184
+ if (s.revert.snapshot) out.revert.snapshot = str(s.revert.snapshot);
185
+ }
186
+ return out;
187
+ }
188
+
189
+ // ── Parts ───────────────────────────────────────────────────────────────────
190
+
191
+ /** A tool's text content as OpenCode 1's `output` string. */
192
+ export function toolContentText(content) {
193
+ return (Array.isArray(content) ? content : [])
194
+ .filter((item) => item?.type === 'text' && typeof item.text === 'string')
195
+ .map((item) => item.text)
196
+ .join('\n');
197
+ }
198
+
199
+ /** A tool's file content as OpenCode 1 file parts (a tool's attachments). */
200
+ function toolAttachments(content, base) {
201
+ const files = (Array.isArray(content) ? content : []).filter((item) => item?.type === 'file' && item.uri);
202
+ return files.map((item, index) => ({
203
+ id: partId(base.messageID, 'att', `${base.callID}-${index}`),
204
+ sessionID: base.sessionID,
205
+ messageID: base.messageID,
206
+ type: 'file',
207
+ mime: str(item.mime) || 'application/octet-stream',
208
+ ...(item.name ? { filename: str(item.name) } : {}),
209
+ url: str(item.uri),
210
+ }));
211
+ }
212
+
213
+ /** The line a tool card shows: the call's own description, else what it acts on. */
214
+ function toolTitle(name, input) {
215
+ const i = obj(input);
216
+ for (const key of ['description', 'command', 'filePath', 'path', 'pattern', 'query', 'url']) {
217
+ if (typeof i[key] === 'string' && i[key].trim()) return i[key].trim().slice(0, 200);
218
+ }
219
+ return v1ToolName(name);
220
+ }
221
+
222
+ /**
223
+ * An OpenCode 2 tool state → OpenCode 1's (`pending` / `running` / `completed`
224
+ * / `error`). `times` are the call's own times (created, ran, completed).
225
+ */
226
+ export function toolStateToV1(state, { name = '', times = {}, base = null } = {}) {
227
+ const s = obj(state);
228
+ const start = num(times.ran || times.created);
229
+ const end = num(times.completed || times.ran || times.created);
230
+ const input = obj(s.input);
231
+ switch (s.status) {
232
+ case 'streaming':
233
+ return { status: 'pending', input: {}, raw: typeof s.input === 'string' ? s.input : '' };
234
+ case 'running':
235
+ return { status: 'running', input, ...(s.metadata ? { metadata: obj(s.metadata) } : {}), time: { start } };
236
+ case 'completed': {
237
+ const out = {
238
+ status: 'completed',
239
+ input,
240
+ output: toolContentText(s.content),
241
+ title: toolTitle(name, input),
242
+ metadata: obj(s.metadata),
243
+ time: { start, end },
244
+ };
245
+ const attachments = base ? toolAttachments(s.content, base) : [];
246
+ if (attachments.length) out.attachments = attachments;
247
+ return out;
248
+ }
249
+ case 'error':
250
+ return {
251
+ status: 'error',
252
+ input,
253
+ error: str(s.error?.message || s.error) || 'Tool failed',
254
+ ...(s.metadata ? { metadata: obj(s.metadata) } : {}),
255
+ time: { start, end },
256
+ };
257
+ default:
258
+ return { status: 'pending', input, raw: '' };
259
+ }
260
+ }
261
+
262
+ function toolPart(item, base) {
263
+ const callID = str(item.id);
264
+ const b = { ...base, callID };
265
+ return {
266
+ id: partId(base.messageID, 'tool', callID),
267
+ sessionID: base.sessionID,
268
+ messageID: base.messageID,
269
+ type: 'tool',
270
+ callID,
271
+ tool: v1ToolName(item.name),
272
+ state: toolStateToV1(item.state, { name: item.name, times: obj(item.time), base: b }),
273
+ };
274
+ }
275
+
276
+ /** A user's prompt attachments (stored as base64 data) → OpenCode 1 file parts. */
277
+ function userFileParts(files, base) {
278
+ return (Array.isArray(files) ? files : []).map((file, index) => {
279
+ const mime = str(file?.mime) || 'application/octet-stream';
280
+ const url = file?.source?.type === 'uri' && file.source.uri
281
+ ? str(file.source.uri)
282
+ : (typeof file?.data === 'string' ? `data:${mime};base64,${file.data}` : '');
283
+ return {
284
+ id: partId(base.messageID, 'file', index),
285
+ sessionID: base.sessionID,
286
+ messageID: base.messageID,
287
+ type: 'file',
288
+ mime,
289
+ ...(file?.name ? { filename: str(file.name) } : {}),
290
+ url,
291
+ };
292
+ }).filter((part) => part.url);
293
+ }
294
+
295
+ // ── Messages ────────────────────────────────────────────────────────────────
296
+
297
+ function assistantInfo(item, { sessionID, directory, parentID }) {
298
+ const time = obj(item.time);
299
+ const model = obj(item.model);
300
+ const info = {
301
+ id: str(item.id),
302
+ sessionID,
303
+ role: 'assistant',
304
+ time: { created: num(time.created) },
305
+ parentID: str(parentID),
306
+ modelID: str(model.id),
307
+ providerID: str(model.providerID),
308
+ mode: str(item.agent),
309
+ agent: str(item.agent),
310
+ path: { cwd: str(directory), root: str(directory) },
311
+ cost: num(item.cost),
312
+ tokens: tokensToV1(item.tokens),
313
+ };
314
+ if (model.variant) info.variant = str(model.variant);
315
+ if (time.completed) info.time.completed = num(time.completed);
316
+ if (item.finish) info.finish = str(item.finish);
317
+ if (item.error) info.error = errorToV1(item.error);
318
+ return info;
319
+ }
320
+
321
+ /** The parts of an OpenCode 2 assistant message, in their order, with OpenCode 1 ids. */
322
+ function assistantParts(item, base) {
323
+ const parts = [];
324
+ const time = obj(item.time);
325
+ parts.push({ id: partId(base.messageID, 'step', 'start'), sessionID: base.sessionID, messageID: base.messageID, type: 'step-start' });
326
+ const ordinals = { text: 0, reasoning: 0 };
327
+ for (const content of Array.isArray(item.content) ? item.content : []) {
328
+ if (!content || typeof content !== 'object') continue;
329
+ if (content.type === 'text' || content.type === 'reasoning') {
330
+ const ordinal = ordinals[content.type]++;
331
+ const ctime = obj(content.time);
332
+ const part = {
333
+ id: partId(base.messageID, content.type, ordinal),
334
+ sessionID: base.sessionID,
335
+ messageID: base.messageID,
336
+ type: content.type,
337
+ text: str(content.text),
338
+ };
339
+ const start = num(ctime.created || time.created);
340
+ const end = num(ctime.completed || time.completed);
341
+ part.time = end ? { start, end } : { start };
342
+ parts.push(part);
343
+ } else if (content.type === 'tool') {
344
+ parts.push(toolPart(content, base));
345
+ }
346
+ }
347
+ if (time.completed) {
348
+ parts.push({
349
+ id: partId(base.messageID, 'step', 'finish'),
350
+ sessionID: base.sessionID,
351
+ messageID: base.messageID,
352
+ type: 'step-finish',
353
+ reason: str(item.finish || 'stop'),
354
+ cost: num(item.cost),
355
+ tokens: tokensToV1(item.tokens),
356
+ });
357
+ }
358
+ return parts;
359
+ }
360
+
361
+ /**
362
+ * An OpenCode 2 transcript (any order; `session.message.list`) → OpenCode 1's
363
+ * `[{ info, parts }]`, oldest first. A user message has no model or agent of its
364
+ * own in OpenCode 2: it takes what the session last switched to, else the model
365
+ * of the reply that follows it. Rows SynaBun has no use for (idle markers,
366
+ * switches, system notes) are left out.
367
+ */
368
+ export function messagesToV1(items, { sessionID, directory = '' } = {}) {
369
+ const list = (Array.isArray(items) ? items : []).filter((item) => item && typeof item === 'object' && item.id);
370
+ list.sort((a, b) => num(a.time?.created) - num(b.time?.created) || str(a.id).localeCompare(str(b.id)));
371
+ const rows = [];
372
+ let agent = '';
373
+ let model = null;
374
+ let lastUser = '';
375
+ const pendingUsers = [];
376
+ for (const item of list) {
377
+ const base = { sessionID: str(sessionID), messageID: str(item.id) };
378
+ switch (item.type) {
379
+ case 'agent-switched':
380
+ agent = str(item.agent);
381
+ break;
382
+ case 'model-switched':
383
+ model = modelRefToV1(item.model);
384
+ break;
385
+ // A synthetic message is context OpenCode 2 (its plan-mode reminders) or
386
+ // SynaBun (the greeting's note) added for the model; OpenCode 1 kept such
387
+ // text as a synthetic part the transcript never draws: it is left out.
388
+ case 'user': {
389
+ const info = {
390
+ id: base.messageID,
391
+ sessionID: base.sessionID,
392
+ role: 'user',
393
+ time: { created: num(item.time?.created) },
394
+ agent: agent || 'build',
395
+ model: model ? { ...model } : { providerID: '', modelID: '' },
396
+ };
397
+ const parts = [{
398
+ id: partId(base.messageID, 'text', 0),
399
+ sessionID: base.sessionID,
400
+ messageID: base.messageID,
401
+ type: 'text',
402
+ text: str(item.text),
403
+ }];
404
+ parts.push(...userFileParts(item.files, base));
405
+ for (const [index, attached] of (Array.isArray(item.agents) ? item.agents : []).entries()) {
406
+ if (attached?.name) parts.push({ id: partId(base.messageID, 'agent', index), sessionID: base.sessionID, messageID: base.messageID, type: 'agent', name: str(attached.name) });
407
+ }
408
+ const row = { info, parts };
409
+ rows.push(row);
410
+ if (!model) pendingUsers.push(row);
411
+ lastUser = base.messageID;
412
+ break;
413
+ }
414
+ case 'assistant': {
415
+ const info = assistantInfo(item, { sessionID: base.sessionID, directory, parentID: lastUser });
416
+ const ref = modelRefToV1(item.model);
417
+ if (ref) {
418
+ for (const row of pendingUsers.splice(0)) row.info.model = { ...ref };
419
+ if (!agent && item.agent) agent = str(item.agent);
420
+ }
421
+ rows.push({ info, parts: assistantParts(item, base) });
422
+ break;
423
+ }
424
+ case 'compaction': {
425
+ if (item.status !== 'completed' || !item.summary) break;
426
+ const time = num(item.time?.created);
427
+ const ref = modelRefToV1(item.model) || model || { providerID: '', modelID: '' };
428
+ const info = {
429
+ id: base.messageID,
430
+ sessionID: base.sessionID,
431
+ role: 'assistant',
432
+ time: { created: time, completed: time },
433
+ parentID: lastUser,
434
+ modelID: ref.modelID,
435
+ providerID: ref.providerID,
436
+ mode: 'compaction',
437
+ agent: 'compaction',
438
+ path: { cwd: str(directory), root: str(directory) },
439
+ summary: true,
440
+ cost: num(item.cost),
441
+ tokens: tokensToV1(item.tokens),
442
+ finish: 'stop',
443
+ };
444
+ rows.push({ info, parts: [{ id: partId(base.messageID, 'text', 0), sessionID: base.sessionID, messageID: base.messageID, type: 'text', text: str(item.summary), time: { start: time, end: time } }] });
445
+ break;
446
+ }
447
+ case 'shell': {
448
+ // `!command` from the panel: OpenCode 1 showed it as a bash call.
449
+ const time = obj(item.time);
450
+ const info = {
451
+ id: base.messageID,
452
+ sessionID: base.sessionID,
453
+ role: 'assistant',
454
+ time: { created: num(time.created), ...(time.completed ? { completed: num(time.completed) } : {}) },
455
+ parentID: lastUser,
456
+ modelID: model?.modelID || '',
457
+ providerID: model?.providerID || '',
458
+ mode: agent || 'build',
459
+ agent: agent || 'build',
460
+ path: { cwd: str(directory), root: str(directory) },
461
+ cost: 0,
462
+ tokens: tokensToV1(null),
463
+ };
464
+ const done = item.status !== 'running';
465
+ const state = done
466
+ ? { status: 'completed', input: { command: str(item.command) }, output: str(item.output?.output), title: str(item.command), metadata: { exit: item.exit ?? null, status: item.status }, time: { start: num(time.created), end: num(time.completed || time.created) } }
467
+ : { status: 'running', input: { command: str(item.command) }, time: { start: num(time.created) } };
468
+ rows.push({ info, parts: [{ id: partId(base.messageID, 'tool', item.shellID || 'shell'), sessionID: base.sessionID, messageID: base.messageID, type: 'tool', callID: str(item.shellID || item.id), tool: 'bash', state }] });
469
+ break;
470
+ }
471
+ default:
472
+ break;
473
+ }
474
+ }
475
+ return rows;
476
+ }
477
+
478
+ // ── Permissions and questions ───────────────────────────────────────────────
479
+
480
+ /** OpenCode 2 Permission.Request → OpenCode 1's permission request (the `permission.asked` payload). */
481
+ export function permissionToV1(request) {
482
+ const r = obj(request);
483
+ const out = {
484
+ id: str(r.id),
485
+ sessionID: str(r.sessionID),
486
+ permission: v1ToolName(r.action),
487
+ patterns: Array.isArray(r.resources) ? r.resources.map(str) : [],
488
+ metadata: { ...obj(r.metadata), ...(r.message ? { message: str(r.message) } : {}) },
489
+ always: Array.isArray(r.save) ? r.save.map(str) : [],
490
+ };
491
+ if (r.source?.type === 'tool' && r.source.id) out.tool = { messageID: str(r.source.messageID), callID: str(r.source.id) };
492
+ return out;
493
+ }
494
+
495
+ const QUESTION_FIELD_TYPES = new Set(['string', 'multiselect', 'boolean']);
496
+
497
+ /**
498
+ * An OpenCode 2 form → OpenCode 1's question request, or null for a form that
499
+ * is not a question (an integration's sign-in form has its own flow). The
500
+ * question tool makes one field per question (`q0`, `q1`…): its title is the
501
+ * header, its description the question.
502
+ */
503
+ export function formToQuestionV1(form) {
504
+ const f = obj(form);
505
+ const meta = obj(f.metadata);
506
+ const fields = (Array.isArray(f.fields) ? f.fields : []).filter((field) => field && QUESTION_FIELD_TYPES.has(field.type) && !field.hidden);
507
+ if (!f.id || !fields.length) return null;
508
+ if (meta.kind && meta.kind !== 'question') return null;
509
+ const questions = fields.map((field) => {
510
+ const options = field.type === 'boolean'
511
+ ? [{ label: 'Yes', description: '' }, { label: 'No', description: '' }]
512
+ : (Array.isArray(field.options) ? field.options : []).map((option) => ({ label: str(option?.label || option?.value), description: str(option?.description) }));
513
+ const question = {
514
+ question: str(field.description || field.title || f.title),
515
+ header: str(field.title || f.title).slice(0, 30),
516
+ options,
517
+ };
518
+ if (field.type === 'multiselect') question.multiple = true;
519
+ if (field.custom === true || (field.type === 'string' && !options.length)) question.custom = true;
520
+ return question;
521
+ });
522
+ const out = { id: str(f.id), sessionID: str(f.sessionID), questions };
523
+ if (meta.tool?.id) out.tool = { messageID: str(meta.tool.messageID), callID: str(meta.tool.id) };
524
+ return out;
525
+ }
526
+
527
+ /** The keys of a form's question fields, in order (what formToQuestionV1 made questions of). */
528
+ export function questionKeys(form) {
529
+ return (Array.isArray(form?.fields) ? form.fields : [])
530
+ .filter((field) => field && QUESTION_FIELD_TYPES.has(field.type) && !field.hidden)
531
+ .map((field) => ({ key: str(field.key), type: field.type, options: Array.isArray(field.options) ? field.options : [] }));
532
+ }
533
+
534
+ /**
535
+ * OpenCode 1 answers (`[[label, …], …]`, one list per question) → an OpenCode
536
+ * 2 form answer `{ key: value }`: a label is sent as its option's value, a
537
+ * multiselect as a list, a yes / no question as a boolean.
538
+ */
539
+ export function answersToForm(form, answers) {
540
+ const keys = questionKeys(form);
541
+ const list = Array.isArray(answers) ? answers : [];
542
+ const answer = {};
543
+ keys.forEach((field, index) => {
544
+ const given = (Array.isArray(list[index]) ? list[index] : (list[index] == null ? [] : [list[index]])).map(str).filter((v) => v !== '');
545
+ const valueOf = (label) => {
546
+ const option = field.options.find((o) => str(o?.label) === label || str(o?.value) === label);
547
+ return option ? str(option.value ?? option.label) : label;
548
+ };
549
+ if (field.type === 'multiselect') answer[field.key] = given.map(valueOf);
550
+ else if (field.type === 'boolean') { if (given.length) answer[field.key] = /^(yes|true|y|1)$/i.test(given[0]); }
551
+ else if (given.length) answer[field.key] = valueOf(given[0]);
552
+ });
553
+ return answer;
554
+ }
555
+
556
+ /** A form answer → OpenCode 1 answers (one list per question), for `question.replied`. */
557
+ export function formAnswerToV1(form, answer) {
558
+ const a = obj(answer);
559
+ return questionKeys(form).map((field) => {
560
+ const value = a[field.key];
561
+ if (Array.isArray(value)) return value.map(str);
562
+ if (value == null) return [];
563
+ if (typeof value === 'boolean') return [value ? 'Yes' : 'No'];
564
+ return [str(value)];
565
+ });
566
+ }
567
+
568
+ // ── The event translator ────────────────────────────────────────────────────
569
+
570
+ /**
571
+ * Translates one OpenCode 2 event stream into OpenCode 1 events. Stateful: it
572
+ * keeps each assistant message and its parts (text and reasoning grow from
573
+ * deltas, a tool call from its input to its result), the user prompts waiting
574
+ * in a session's inbox, the sessions it has seen and which session a pending
575
+ * permission or form belongs to (lib/opencode2-client.js answers them by id).
576
+ *
577
+ * `translate(event)` → `[{ directory, payload: { id, type, properties } }]`,
578
+ * the shape OpenCode 1's `/global/event` streamed. `version` is the serve's
579
+ * (OpenCode 1 sessions carry the version that made them).
580
+ */
581
+ export function createEventTranslator({ version = '', directory: defaultDirectory = '' } = {}) {
582
+ const sessions = new Map(); // sessionID → { info (v1), directory, lastUser, compacting }
583
+ const messages = new Map(); // assistant messageID → { info, parts: Map(kind:key → part), sessionID }
584
+ const inbox = new Map(); // inboxID → { sessionID, item }
585
+ const permissions = new Map(); // requestID → sessionID
586
+ const forms = new Map(); // formID → form
587
+ const MAX_MESSAGES = 400;
588
+ const MAX_INBOX = 200;
589
+
590
+ const sessionState = (sessionID) => {
591
+ let state = sessions.get(sessionID);
592
+ if (!state) {
593
+ state = { info: null, directory: '', lastUser: '', compacting: 0 };
594
+ sessions.set(sessionID, state);
595
+ }
596
+ return state;
597
+ };
598
+ const directoryOf = (event, sessionID) => str(event?.location?.directory)
599
+ || (sessionID ? sessions.get(sessionID)?.directory : '')
600
+ || defaultDirectory;
601
+
602
+ const trim = (map, max) => {
603
+ while (map.size > max) map.delete(map.keys().next().value);
604
+ };
605
+
606
+ /** Remember a session as OpenCode 2 answered it (list / get / create), so its events carry the whole Session. */
607
+ function noteSession(info, extra = {}) {
608
+ if (!info?.id) return null;
609
+ const state = sessionState(str(info.id));
610
+ const prior = state.info || {};
611
+ const v1 = sessionToV1(info, { version, slug: prior.slug, ...extra });
612
+ if (!v1.title && prior.title) v1.title = prior.title;
613
+ if (!v1.slug && prior.slug) v1.slug = prior.slug;
614
+ state.info = v1;
615
+ state.directory = v1.directory || state.directory;
616
+ return v1;
617
+ }
618
+
619
+ const sessionInfo = (sessionID, patch = null) => {
620
+ const state = sessionState(sessionID);
621
+ const base = state.info || { id: sessionID, slug: '', projectID: '', directory: state.directory, title: '', version, time: { created: 0, updated: 0 } };
622
+ const next = patch ? { ...base, ...patch, time: { ...base.time, ...(patch.time || {}) } } : { ...base };
623
+ if (state.compacting) next.time = { ...next.time, compacting: state.compacting };
624
+ else if (next.time?.compacting) { next.time = { ...next.time }; delete next.time.compacting; }
625
+ state.info = next;
626
+ return next;
627
+ };
628
+
629
+ const messageState = (sessionID, messageID) => {
630
+ let state = messages.get(messageID);
631
+ if (!state) {
632
+ state = { sessionID, info: null, parts: new Map() };
633
+ messages.set(messageID, state);
634
+ trim(messages, MAX_MESSAGES);
635
+ }
636
+ return state;
637
+ };
638
+
639
+ function translate(event) {
640
+ if (!event || typeof event !== 'object' || typeof event.type !== 'string') return [];
641
+ const data = obj(event.data);
642
+ const sessionID = str(data.sessionID);
643
+ const at = num(event.created, Date.now());
644
+ const out = [];
645
+ const emit = (type, properties) => {
646
+ out.push({
647
+ directory: directoryOf(event, sessionID || properties?.sessionID),
648
+ payload: { id: `${str(event.id) || 'evt'}${out.length ? `-${out.length}` : ''}`, type, properties },
649
+ });
650
+ };
651
+ const assistant = (messageID) => {
652
+ const state = messageState(sessionID, messageID);
653
+ if (!state.info) {
654
+ const session = sessionState(sessionID);
655
+ state.info = assistantInfo({ id: messageID, time: { created: at } }, { sessionID, directory: directoryOf(event, sessionID), parentID: session.lastUser });
656
+ }
657
+ return state;
658
+ };
659
+ const updateMessage = (state) => emit('message.updated', { sessionID, info: { ...state.info, time: { ...state.info.time }, tokens: tokensToV1(state.info.tokens) } });
660
+ const updatePart = (part) => emit('message.part.updated', { sessionID, part: { ...part, ...(part.state ? { state: { ...part.state } } : {}) }, time: at });
661
+ const streamPart = (kind, d, patch) => {
662
+ const messageID = str(d.assistantMessageID);
663
+ const state = assistant(messageID);
664
+ const key = `${kind}:${num(d.ordinal)}`;
665
+ let part = state.parts.get(key);
666
+ if (!part) {
667
+ part = { id: partId(messageID, kind, num(d.ordinal)), sessionID, messageID, type: kind, text: '', time: { start: at } };
668
+ state.parts.set(key, part);
669
+ }
670
+ Object.assign(part, patch);
671
+ return part;
672
+ };
673
+ const toolOf = (d) => {
674
+ const messageID = str(d.assistantMessageID);
675
+ const state = assistant(messageID);
676
+ const callID = str(d.id);
677
+ const key = `tool:${callID}`;
678
+ let part = state.parts.get(key);
679
+ if (!part) {
680
+ part = { id: partId(messageID, 'tool', callID), sessionID, messageID, type: 'tool', callID, tool: '', state: { status: 'pending', input: {}, raw: '' }, created: at };
681
+ state.parts.set(key, part);
682
+ }
683
+ return part;
684
+ };
685
+ const publicTool = (part) => {
686
+ const { created, ...rest } = part;
687
+ return rest;
688
+ };
689
+ const turnEnd = (error = null) => {
690
+ if (error) emit('session.error', { sessionID, error });
691
+ emit('session.status', { sessionID, status: { type: 'idle' } });
692
+ emit('session.idle', { sessionID });
693
+ };
694
+
695
+ switch (event.type) {
696
+ case 'server.connected':
697
+ emit('server.connected', {});
698
+ break;
699
+
700
+ // ── Sessions ──
701
+ case 'session.created': {
702
+ const state = sessionState(sessionID);
703
+ state.directory = str(data.location?.directory) || state.directory;
704
+ const info = noteSession({
705
+ id: sessionID,
706
+ projectID: data.projectID,
707
+ parentID: data.parentID,
708
+ title: data.title,
709
+ agent: data.agent,
710
+ model: data.model,
711
+ metadata: data.metadata,
712
+ permissions: data.permissions,
713
+ location: data.location,
714
+ time: { created: at, updated: at },
715
+ }, { slug: str(data.slug), version: str(data.version) || version });
716
+ emit('session.created', { sessionID, info });
717
+ break;
718
+ }
719
+ case 'session.renamed':
720
+ emit('session.updated', { sessionID, info: sessionInfo(sessionID, { title: str(data.title), time: { updated: at } }) });
721
+ break;
722
+ case 'session.metadata.updated':
723
+ emit('session.updated', { sessionID, info: sessionInfo(sessionID, { metadata: obj(data.metadata), time: { updated: at } }) });
724
+ break;
725
+ case 'session.permissions':
726
+ emit('session.updated', { sessionID, info: sessionInfo(sessionID, { permission: rulesToV1(data.permissions) || [], time: { updated: at } }) });
727
+ break;
728
+ case 'session.agent.selected':
729
+ emit('session.updated', { sessionID, info: sessionInfo(sessionID, { agent: str(data.agent), time: { updated: at } }) });
730
+ break;
731
+ case 'session.model.selected':
732
+ if (data.model?.id) emit('session.updated', { sessionID, info: sessionInfo(sessionID, { model: { id: str(data.model.id), providerID: str(data.model.providerID), ...(data.model.variant ? { variant: str(data.model.variant) } : {}) }, time: { updated: at } }) });
733
+ break;
734
+ case 'session.moved': {
735
+ const state = sessionState(sessionID);
736
+ state.directory = str(data.location?.directory) || state.directory;
737
+ emit('session.updated', { sessionID, info: sessionInfo(sessionID, { directory: state.directory, time: { updated: at } }) });
738
+ break;
739
+ }
740
+ case 'session.usage.updated':
741
+ emit('session.updated', { sessionID, info: sessionInfo(sessionID, { cost: num(data.cost), tokens: tokensToV1(data.tokens) }) });
742
+ break;
743
+ case 'session.revert.staged':
744
+ emit('session.updated', { sessionID, info: sessionInfo(sessionID, { revert: data.revert?.messageID ? { messageID: str(data.revert.messageID), ...(data.revert.partID ? { partID: str(data.revert.partID) } : {}), ...(data.revert.snapshot ? { snapshot: str(data.revert.snapshot) } : {}) } : undefined, time: { updated: at } }) });
745
+ break;
746
+ case 'session.revert.cleared':
747
+ case 'session.revert.committed': {
748
+ const info = sessionInfo(sessionID, { time: { updated: at } });
749
+ delete info.revert;
750
+ emit('session.updated', { sessionID, info });
751
+ break;
752
+ }
753
+ case 'session.deleted': {
754
+ const info = sessionInfo(sessionID);
755
+ sessions.delete(sessionID);
756
+ emit('session.deleted', { sessionID, info });
757
+ break;
758
+ }
759
+ case 'session.forked':
760
+ break;
761
+
762
+ // ── Prompts ──
763
+ case 'session.inbox.enqueued':
764
+ if (data.inboxID && data.item) {
765
+ inbox.set(str(data.inboxID), { sessionID, item: data.item });
766
+ trim(inbox, MAX_INBOX);
767
+ }
768
+ break;
769
+ case 'session.inbox.cancelled':
770
+ inbox.delete(str(data.inboxID));
771
+ break;
772
+ case 'session.inbox.delivered': {
773
+ const entry = inbox.get(str(data.inboxID));
774
+ inbox.delete(str(data.inboxID));
775
+ const item = entry?.item;
776
+ // Only a person's prompt is a user message; a synthetic item is context
777
+ // for the model (see messagesToV1), a compaction or a move is not a message.
778
+ if (!item || item.type !== 'user') break;
779
+ const messageID = str(data.inboxID);
780
+ const session = sessionState(sessionID);
781
+ session.lastUser = messageID;
782
+ const info = session.info || {};
783
+ const model = info.model ? { providerID: str(info.model.providerID), modelID: str(info.model.id), ...(info.model.variant ? { variant: str(info.model.variant) } : {}) } : { providerID: '', modelID: '' };
784
+ emit('message.updated', { sessionID, info: { id: messageID, sessionID, role: 'user', time: { created: at }, agent: str(info.agent) || 'build', model } });
785
+ const payload = obj(item.payload);
786
+ updatePart({ id: partId(messageID, 'text', 0), sessionID, messageID, type: 'text', text: str(payload.text) });
787
+ (Array.isArray(payload.files) ? payload.files : []).forEach((file, index) => {
788
+ // A prompt's attachment arrives as the uri it was sent with.
789
+ if (file?.uri) updatePart({ id: partId(messageID, 'file', index), sessionID, messageID, type: 'file', mime: str(file.mime) || 'application/octet-stream', ...(file.name ? { filename: str(file.name) } : {}), url: str(file.uri) });
790
+ });
791
+ break;
792
+ }
793
+
794
+ // ── A turn ──
795
+ case 'session.execution.started':
796
+ emit('session.status', { sessionID, status: { type: 'busy' } });
797
+ break;
798
+ case 'session.retry.scheduled':
799
+ emit('session.status', { sessionID, status: { type: 'retry', attempt: num(data.attempt), message: str(data.error?.message) || 'Retrying', next: num(data.at) } });
800
+ break;
801
+ case 'session.execution.succeeded':
802
+ turnEnd(null);
803
+ break;
804
+ case 'session.execution.failed':
805
+ turnEnd(errorToV1(data.error));
806
+ break;
807
+ case 'session.execution.interrupted':
808
+ // OpenCode 1 ended a stopped turn with MessageAbortedError: the panel
809
+ // shows "Stopped", the loops and the brain read it as an abort.
810
+ turnEnd({ name: 'MessageAbortedError', data: { message: `The turn was stopped (${str(data.reason) || 'user'}).` } });
811
+ break;
812
+
813
+ case 'session.step.started': {
814
+ const messageID = str(data.assistantMessageID);
815
+ const state = assistant(messageID);
816
+ const model = obj(data.model);
817
+ state.info = {
818
+ ...state.info,
819
+ time: { created: num(data.started, at) },
820
+ modelID: str(model.id),
821
+ providerID: str(model.providerID),
822
+ mode: str(data.agent),
823
+ agent: str(data.agent),
824
+ ...(model.variant ? { variant: str(model.variant) } : {}),
825
+ };
826
+ updateMessage(state);
827
+ updatePart({ id: partId(messageID, 'step', 'start'), sessionID, messageID, type: 'step-start', ...(data.snapshot ? { snapshot: str(data.snapshot) } : {}) });
828
+ break;
829
+ }
830
+ case 'session.step.ended': {
831
+ const messageID = str(data.assistantMessageID);
832
+ const state = assistant(messageID);
833
+ state.info = { ...state.info, time: { ...state.info.time, completed: at }, finish: str(data.finish), cost: num(data.cost), tokens: tokensToV1(data.tokens) };
834
+ updatePart({ id: partId(messageID, 'step', 'finish'), sessionID, messageID, type: 'step-finish', reason: str(data.finish || 'stop'), cost: num(data.cost), tokens: tokensToV1(data.tokens), ...(data.snapshot ? { snapshot: str(data.snapshot) } : {}) });
835
+ updateMessage(state);
836
+ break;
837
+ }
838
+ case 'session.step.failed': {
839
+ const messageID = str(data.assistantMessageID);
840
+ const state = assistant(messageID);
841
+ state.info = {
842
+ ...state.info,
843
+ time: { ...state.info.time, completed: at },
844
+ finish: 'error',
845
+ error: errorToV1(data.error),
846
+ ...(data.cost != null ? { cost: num(data.cost) } : {}),
847
+ ...(data.tokens ? { tokens: tokensToV1(data.tokens) } : {}),
848
+ };
849
+ updateMessage(state);
850
+ break;
851
+ }
852
+
853
+ case 'session.text.started':
854
+ case 'session.reasoning.started': {
855
+ const kind = event.type === 'session.text.started' ? 'text' : 'reasoning';
856
+ updatePart(streamPart(kind, data, {}));
857
+ break;
858
+ }
859
+ case 'session.text.delta':
860
+ case 'session.reasoning.delta': {
861
+ const kind = event.type === 'session.text.delta' ? 'text' : 'reasoning';
862
+ const part = streamPart(kind, data, {});
863
+ const delta = str(data.delta);
864
+ if (!delta) break;
865
+ part.text += delta;
866
+ emit('message.part.delta', { sessionID, messageID: part.messageID, partID: part.id, field: 'text', delta });
867
+ break;
868
+ }
869
+ case 'session.text.ended':
870
+ case 'session.reasoning.ended': {
871
+ const kind = event.type === 'session.text.ended' ? 'text' : 'reasoning';
872
+ const part = streamPart(kind, data, {});
873
+ part.text = str(data.text);
874
+ part.time = { start: num(part.time?.start, at), end: at };
875
+ updatePart(part);
876
+ break;
877
+ }
878
+
879
+ case 'session.tool.input.started': {
880
+ const part = toolOf(data);
881
+ part.tool = v1ToolName(data.name);
882
+ part.state = { status: 'pending', input: {}, raw: '' };
883
+ updatePart(publicTool(part));
884
+ break;
885
+ }
886
+ case 'session.tool.input.delta': {
887
+ const part = toolOf(data);
888
+ if (part.state.status === 'pending') part.state = { ...part.state, raw: str(part.state.raw) + str(data.delta) };
889
+ break;
890
+ }
891
+ case 'session.tool.input.ended': {
892
+ const part = toolOf(data);
893
+ let input = {};
894
+ try { input = obj(JSON.parse(str(data.text))); } catch { /* the call's own event carries the parsed input */ }
895
+ if (part.state.status === 'pending') part.state = { status: 'pending', input, raw: str(data.text) };
896
+ updatePart(publicTool(part));
897
+ break;
898
+ }
899
+ case 'session.tool.called': {
900
+ const part = toolOf(data);
901
+ part.state = { status: 'running', input: obj(data.input), time: { start: at } };
902
+ updatePart(publicTool(part));
903
+ break;
904
+ }
905
+ case 'session.tool.progress': {
906
+ const part = toolOf(data);
907
+ const start = num(part.state.time?.start, at);
908
+ part.state = { ...part.state, status: 'running', metadata: { ...obj(part.state.metadata), ...obj(data.metadata) }, time: { start } };
909
+ updatePart(publicTool(part));
910
+ break;
911
+ }
912
+ case 'session.tool.success':
913
+ case 'session.tool.failed': {
914
+ const part = toolOf(data);
915
+ const start = num(part.state.time?.start, part.created || at);
916
+ const state = event.type === 'session.tool.success'
917
+ ? { status: 'completed', input: obj(part.state.input), content: data.content, metadata: data.metadata }
918
+ : { status: 'error', input: obj(part.state.input), error: data.error, metadata: data.metadata };
919
+ part.state = toolStateToV1(state, { name: part.tool, times: { ran: start, completed: at }, base: { sessionID, messageID: part.messageID, callID: part.callID } });
920
+ updatePart(publicTool(part));
921
+ break;
922
+ }
923
+
924
+ // ── Compaction ──
925
+ case 'session.compaction.started':
926
+ sessionState(sessionID).compacting = at;
927
+ emit('session.updated', { sessionID, info: sessionInfo(sessionID) });
928
+ break;
929
+ case 'session.compaction.ended':
930
+ case 'session.compaction.failed': {
931
+ sessionState(sessionID).compacting = 0;
932
+ if (event.type === 'session.compaction.failed') emit('session.error', { sessionID, error: errorToV1(data.error) });
933
+ emit('session.updated', { sessionID, info: sessionInfo(sessionID) });
934
+ emit('session.compacted', { sessionID });
935
+ break;
936
+ }
937
+
938
+ // ── Asks ──
939
+ case 'permission.asked': {
940
+ const request = permissionToV1(data);
941
+ if (!request.id) break;
942
+ permissions.set(request.id, request.sessionID);
943
+ trim(permissions, MAX_INBOX);
944
+ emit('permission.asked', request);
945
+ break;
946
+ }
947
+ case 'permission.replied':
948
+ permissions.delete(str(data.requestID));
949
+ emit('permission.replied', { sessionID, requestID: str(data.requestID), reply: str(data.reply) });
950
+ break;
951
+ case 'form.created': {
952
+ const form = data.form;
953
+ const question = formToQuestionV1(form);
954
+ if (!question) break;
955
+ forms.set(question.id, form);
956
+ trim(forms, MAX_INBOX);
957
+ emit('question.asked', question);
958
+ break;
959
+ }
960
+ case 'form.replied': {
961
+ const form = forms.get(str(data.id));
962
+ forms.delete(str(data.id));
963
+ if (!form) break;
964
+ emit('question.replied', { sessionID: str(data.sessionID), requestID: str(data.id), answers: formAnswerToV1(form, data.answer) });
965
+ break;
966
+ }
967
+ case 'form.cancelled': {
968
+ const known = forms.has(str(data.id));
969
+ forms.delete(str(data.id));
970
+ if (known) emit('question.rejected', { sessionID: str(data.sessionID), requestID: str(data.id) });
971
+ break;
972
+ }
973
+
974
+ // ── The rest of the workspace ──
975
+ case 'vcs.branch.updated':
976
+ emit('vcs.branch.updated', { branch: str(data.branch) });
977
+ break;
978
+ case 'mcp.status.changed':
979
+ case 'mcp.resources.changed':
980
+ emit('mcp.tools.changed', { server: str(data.server) });
981
+ break;
982
+ case 'installation.update-available':
983
+ case 'installation.updated':
984
+ emit(event.type, { version: str(data.version) });
985
+ break;
986
+ case 'filesystem.changed':
987
+ emit('file.watcher.updated', { file: str(data.file), event: str(data.event) });
988
+ break;
989
+ default:
990
+ break;
991
+ }
992
+ return out;
993
+ }
994
+
995
+ return {
996
+ translate,
997
+ noteSession,
998
+ /** The session a pending permission request belongs to (OpenCode 2 answers it under the session). */
999
+ permissionSession: (requestID) => permissions.get(str(requestID)) || null,
1000
+ /** A pending question's form (its field keys turn OpenCode 1 answers into a form answer). */
1001
+ form: (formID) => forms.get(str(formID)) || null,
1002
+ noteForm: (form) => { if (form?.id) { forms.set(str(form.id), form); trim(forms, MAX_INBOX); } },
1003
+ notePermission: (requestID, sessionID) => { if (requestID && sessionID) { permissions.set(str(requestID), str(sessionID)); trim(permissions, MAX_INBOX); } },
1004
+ sessionDirectory: (sessionID) => sessions.get(str(sessionID))?.directory || '',
1005
+ };
1006
+ }
1007
+
1008
+ export const OPENCODE2_TRANSLATE_INTERNALS = Object.freeze({ ZERO_TOKENS });