synabun 2.0.0 → 2.0.5

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 (334) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/NOTICE +5 -0
  3. package/README.md +1 -1
  4. package/THIRD-PARTY-LICENSES.md +46 -0
  5. package/benchmarks/fixtures/browser/README.md +1 -1
  6. package/benchmarks/fixtures/desktop/README.md +1 -1
  7. package/docs/whatsapp.md +2 -2
  8. package/hooks/claude-code/pre-compact.mjs +11 -0
  9. package/hooks/claude-code/prompt-submit.mjs +60 -184
  10. package/hooks/claude-code/session-start.mjs +73 -16
  11. package/hooks/claude-code/shared.mjs +20 -0
  12. package/hooks/claude-code/stop.mjs +5 -5
  13. package/lib/start-launcher.js +88 -1
  14. package/mcp-server/package-lock.json +2 -2
  15. package/mcp-server/package.json +1 -3
  16. package/neural-interface/i18n/en.json +1500 -153
  17. package/neural-interface/i18n/pt-BR.json +1498 -152
  18. package/neural-interface/lib/assistant-api.js +62 -6
  19. package/neural-interface/lib/assistant-brains/claude.js +4 -0
  20. package/neural-interface/lib/assistant-catalog.js +179 -27
  21. package/neural-interface/lib/assistant-config.js +31 -2
  22. package/neural-interface/lib/assistant-dispatch.js +62 -13
  23. package/neural-interface/lib/assistant-hidden-models.js +393 -66
  24. package/neural-interface/lib/assistant-memory.js +24 -10
  25. package/neural-interface/lib/assistant-persona.js +6 -2
  26. package/neural-interface/lib/assistant-pricing.js +21 -6
  27. package/neural-interface/lib/assistant-runtime.js +181 -12
  28. package/neural-interface/lib/assistant-task-prompt.js +13 -3
  29. package/neural-interface/lib/assistant-test-density.js +142 -0
  30. package/neural-interface/lib/assistant-user-context-budgets.js +123 -0
  31. package/neural-interface/lib/assistant-user-context.js +908 -0
  32. package/neural-interface/lib/assistant-user-knowledge.js +328 -0
  33. package/neural-interface/lib/assistant-user-learn.js +404 -0
  34. package/neural-interface/lib/assistant-user-threads.js +226 -0
  35. package/neural-interface/lib/assistant-user-writer.js +181 -0
  36. package/neural-interface/lib/browser-background-tab.js +134 -0
  37. package/neural-interface/lib/browser-screenshot.js +678 -61
  38. package/neural-interface/lib/claude-agent-bridge.js +33 -6
  39. package/neural-interface/lib/claude-model-catalog.js +30 -2
  40. package/neural-interface/lib/claude-temporary.js +46 -7
  41. package/neural-interface/lib/cloudflared/install.js +533 -0
  42. package/neural-interface/lib/cloudflared/manager.js +545 -0
  43. package/neural-interface/lib/cloudflared/pins.js +115 -0
  44. package/neural-interface/lib/cloudflared/pins.json +63 -0
  45. package/neural-interface/lib/cloudflared/tunnel.js +265 -0
  46. package/neural-interface/lib/editor-completion-endpoint.js +264 -0
  47. package/neural-interface/lib/editor-completion-opencode.js +436 -0
  48. package/neural-interface/lib/editor-completion.js +714 -0
  49. package/neural-interface/lib/editor-config.js +149 -0
  50. package/neural-interface/lib/editor-files-api.js +268 -0
  51. package/neural-interface/lib/editor-git-api.js +134 -0
  52. package/neural-interface/lib/file-explorer-api.js +1039 -0
  53. package/neural-interface/lib/file-explorer-system.js +355 -0
  54. package/neural-interface/lib/file-explorer-trash.ps1 +85 -0
  55. package/neural-interface/lib/file-explorer-watch.js +239 -0
  56. package/neural-interface/lib/git-diff-api.js +103 -0
  57. package/neural-interface/lib/git-read.js +202 -0
  58. package/neural-interface/lib/greeting/api.js +89 -0
  59. package/neural-interface/lib/greeting/budgets.js +90 -0
  60. package/neural-interface/lib/greeting/engine.js +461 -0
  61. package/neural-interface/lib/greeting/facts.js +320 -0
  62. package/neural-interface/lib/greeting/sources.js +252 -0
  63. package/neural-interface/lib/greeting/writer.js +139 -0
  64. package/neural-interface/lib/http-guards.js +189 -13
  65. package/neural-interface/lib/machine-names.js +88 -0
  66. package/neural-interface/lib/memory-api.js +31 -7
  67. package/neural-interface/lib/native-loop-providers.js +10 -5
  68. package/neural-interface/lib/plugin-installer.js +4 -2
  69. package/neural-interface/lib/project-storage.js +5 -7
  70. package/neural-interface/lib/request-gate.js +589 -0
  71. package/neural-interface/lib/ui-blob-api.js +108 -0
  72. package/neural-interface/lib/ui-blob-store.js +68 -0
  73. package/neural-interface/package-lock.json +701 -2
  74. package/neural-interface/package.json +13 -2
  75. package/neural-interface/public/install-app.html +9 -6
  76. package/neural-interface/public/offline.html +3 -2
  77. package/neural-interface/public/onboarding.html +12 -1
  78. package/neural-interface/public/shared/agent-runtime-options.js +22 -6
  79. package/neural-interface/public/shared/assistant/asst-automation.js +286 -0
  80. package/neural-interface/public/shared/assistant/asst-brain-picker.js +312 -62
  81. package/neural-interface/public/shared/assistant/asst-composer.js +11 -1
  82. package/neural-interface/public/shared/assistant/asst-mascot.js +18 -58
  83. package/neural-interface/public/shared/assistant/asst-menu.js +12 -8
  84. package/neural-interface/public/shared/assistant/asst-panel.js +150 -28
  85. package/neural-interface/public/shared/assistant/asst-render.js +162 -19
  86. package/neural-interface/public/shared/assistant/asst-route-models.js +108 -0
  87. package/neural-interface/public/shared/assistant/asst-route.js +23 -329
  88. package/neural-interface/public/shared/assistant/asst-routes-editor.js +495 -0
  89. package/neural-interface/public/shared/assistant/asst-sanitize.js +8 -1
  90. package/neural-interface/public/shared/assistant/asst-sidepanel.js +31 -9
  91. package/neural-interface/public/shared/assistant/asst-state.js +185 -1
  92. package/neural-interface/public/shared/assistant/asst-styles.js +158 -4
  93. package/neural-interface/public/shared/blob-store.js +111 -22
  94. package/neural-interface/public/shared/cdx/cdx-panel.js +32 -11
  95. package/neural-interface/public/shared/cdx/cdx-render.js +25 -89
  96. package/neural-interface/public/shared/cdx/cdx-styles.js +36 -0
  97. package/neural-interface/public/shared/cdx/cdx-tabs.js +87 -4
  98. package/neural-interface/public/shared/constants.js +6 -1
  99. package/neural-interface/public/shared/cp/cp-rehydrate.js +2 -1
  100. package/neural-interface/public/shared/cp/cp-styles.js +15 -0
  101. package/neural-interface/public/shared/editor/editor-ai-logic.js +409 -0
  102. package/neural-interface/public/shared/editor/editor-ai.js +619 -0
  103. package/neural-interface/public/shared/editor/editor-commands.js +212 -0
  104. package/neural-interface/public/shared/editor/editor-compare.js +103 -0
  105. package/neural-interface/public/shared/editor/editor-completion-list.js +172 -0
  106. package/neural-interface/public/shared/editor/editor-doc.js +257 -0
  107. package/neural-interface/public/shared/editor/editor-git-logic.js +131 -0
  108. package/neural-interface/public/shared/editor/editor-git.js +95 -0
  109. package/neural-interface/public/shared/editor/editor-languages.js +156 -0
  110. package/neural-interface/public/shared/editor/editor-lint.js +229 -0
  111. package/neural-interface/public/shared/editor/editor-loader.js +51 -0
  112. package/neural-interface/public/shared/editor/editor-markdown.js +90 -0
  113. package/neural-interface/public/shared/editor/editor-menu.js +233 -0
  114. package/neural-interface/public/shared/editor/editor-minimap.js +371 -0
  115. package/neural-interface/public/shared/editor/editor-palette-logic.js +192 -0
  116. package/neural-interface/public/shared/editor/editor-palette.js +125 -0
  117. package/neural-interface/public/shared/editor/editor-panel-logic.js +288 -0
  118. package/neural-interface/public/shared/editor/editor-panel.js +2099 -0
  119. package/neural-interface/public/shared/editor/editor-prefs.js +224 -0
  120. package/neural-interface/public/shared/editor/editor-safety.js +500 -0
  121. package/neural-interface/public/shared/editor/editor-search.js +352 -0
  122. package/neural-interface/public/shared/editor/editor-statusbar.js +144 -0
  123. package/neural-interface/public/shared/editor/editor-tabs-logic.js +331 -0
  124. package/neural-interface/public/shared/editor/editor-tabs.js +233 -0
  125. package/neural-interface/public/shared/editor/editor-theme.js +244 -0
  126. package/neural-interface/public/shared/editor/editor-view.js +332 -0
  127. package/neural-interface/public/shared/explorer/fx-api.js +216 -0
  128. package/neural-interface/public/shared/explorer/fx-links.js +204 -0
  129. package/neural-interface/public/shared/explorer/fx-logic.js +537 -0
  130. package/neural-interface/public/shared/explorer/fx-main.js +865 -0
  131. package/neural-interface/public/shared/explorer/fx-names.js +87 -0
  132. package/neural-interface/public/shared/explorer/fx-ops.js +370 -0
  133. package/neural-interface/public/shared/explorer/fx-popups.js +490 -0
  134. package/neural-interface/public/shared/explorer/fx-tree.js +790 -0
  135. package/neural-interface/public/shared/file-links.js +339 -0
  136. package/neural-interface/public/shared/greeting/greeting-card.js +178 -0
  137. package/neural-interface/public/shared/greeting/greeting-view.js +148 -0
  138. package/neural-interface/public/shared/html-shell.js +160 -165
  139. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +4 -0
  140. package/neural-interface/public/shared/ocp-v2/ocp-v2-greeting.js +47 -0
  141. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +99 -17
  142. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +7 -1
  143. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +5 -0
  144. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +40 -0
  145. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +3 -2
  146. package/neural-interface/public/shared/pwa-install-page.js +29 -1
  147. package/neural-interface/public/shared/pwa-install.css +3 -3
  148. package/neural-interface/public/shared/pwa-install.js +100 -1
  149. package/neural-interface/public/shared/settings/settings-code-editor-pane.js +412 -0
  150. package/neural-interface/public/shared/settings/settings-code-editor.js +328 -0
  151. package/neural-interface/public/shared/settings/settings-greeting.js +133 -0
  152. package/neural-interface/public/shared/settings/settings-ia.js +18 -4
  153. package/neural-interface/public/shared/settings/settings-user-context.js +154 -0
  154. package/neural-interface/public/shared/start-bridge.js +238 -19
  155. package/neural-interface/public/shared/styles.css +1414 -721
  156. package/neural-interface/public/shared/synabun-mascot.js +20 -56
  157. package/neural-interface/public/shared/tunnel-view.js +167 -0
  158. package/neural-interface/public/shared/ui-claude-panel.js +157 -16
  159. package/neural-interface/public/shared/ui-detail.js +15 -0
  160. package/neural-interface/public/shared/ui-explorer.js +22 -0
  161. package/neural-interface/public/shared/ui-file-explorer.js +481 -2496
  162. package/neural-interface/public/shared/ui-file-links.js +103 -0
  163. package/neural-interface/public/shared/ui-help.js +26 -0
  164. package/neural-interface/public/shared/ui-invite.js +126 -116
  165. package/neural-interface/public/shared/ui-keybinds.js +4 -0
  166. package/neural-interface/public/shared/ui-loading.js +159 -15
  167. package/neural-interface/public/shared/ui-menubar.js +12 -30
  168. package/neural-interface/public/shared/ui-native-loop-router-state.js +11 -0
  169. package/neural-interface/public/shared/ui-native-loop-router.js +15 -2
  170. package/neural-interface/public/shared/ui-navbar.js +45 -21
  171. package/neural-interface/public/shared/ui-settings.js +455 -488
  172. package/neural-interface/public/shared/ui-sidepanel-runtime.js +2 -1
  173. package/neural-interface/public/shared/ui-sync.js +16 -0
  174. package/neural-interface/public/shared/ui-terminal.js +23 -5
  175. package/neural-interface/public/shared/ui-tunnel.js +130 -0
  176. package/neural-interface/public/shared/ui-tutorial-steps.js +6 -2
  177. package/neural-interface/public/shared/ui-whiteboard.js +12 -0
  178. package/neural-interface/public/shared/ui-worker-end.js +245 -0
  179. package/neural-interface/public/shared/ui-workspaces.js +41 -27
  180. package/neural-interface/public/shared/worker-end.js +251 -0
  181. package/neural-interface/public/sw.js +2 -2
  182. package/neural-interface/public/vendor/codemirror/chunks/apl-CSDKT72Z.js +1 -0
  183. package/neural-interface/public/vendor/codemirror/chunks/asciiarmor-JYYPU5BM.js +1 -0
  184. package/neural-interface/public/vendor/codemirror/chunks/asn1-NRWCZDA6.js +1 -0
  185. package/neural-interface/public/vendor/codemirror/chunks/asterisk-5OU6D4ZQ.js +1 -0
  186. package/neural-interface/public/vendor/codemirror/chunks/brainfuck-ID4N4SYC.js +1 -0
  187. package/neural-interface/public/vendor/codemirror/chunks/chunk-4RZWGCS2.js +1 -0
  188. package/neural-interface/public/vendor/codemirror/chunks/chunk-ACTJM3JR.js +11 -0
  189. package/neural-interface/public/vendor/codemirror/chunks/chunk-S6BMAXLK.js +1 -0
  190. package/neural-interface/public/vendor/codemirror/chunks/chunk-TO2RDOHQ.js +3 -0
  191. package/neural-interface/public/vendor/codemirror/chunks/chunk-US2GMZZQ.js +13 -0
  192. package/neural-interface/public/vendor/codemirror/chunks/chunk-WYYO5ZAJ.js +1 -0
  193. package/neural-interface/public/vendor/codemirror/chunks/chunk-XALZTDJC.js +1 -0
  194. package/neural-interface/public/vendor/codemirror/chunks/chunk-YRNUTRDV.js +1 -0
  195. package/neural-interface/public/vendor/codemirror/chunks/chunk-Z3QOKH62.js +1 -0
  196. package/neural-interface/public/vendor/codemirror/chunks/clike-OWQBSOGL.js +1 -0
  197. package/neural-interface/public/vendor/codemirror/chunks/clojure-KV75UQWC.js +1 -0
  198. package/neural-interface/public/vendor/codemirror/chunks/cmake-GTEP5Y3C.js +1 -0
  199. package/neural-interface/public/vendor/codemirror/chunks/cobol-WCZ7QEGJ.js +1 -0
  200. package/neural-interface/public/vendor/codemirror/chunks/coffeescript-TGLBOT6B.js +1 -0
  201. package/neural-interface/public/vendor/codemirror/chunks/commonlisp-WVVI432V.js +1 -0
  202. package/neural-interface/public/vendor/codemirror/chunks/crystal-S6R6HLKQ.js +1 -0
  203. package/neural-interface/public/vendor/codemirror/chunks/css-PDA2RINP.js +1 -0
  204. package/neural-interface/public/vendor/codemirror/chunks/cypher-HL5QPL4C.js +1 -0
  205. package/neural-interface/public/vendor/codemirror/chunks/d-HHJH2HAJ.js +1 -0
  206. package/neural-interface/public/vendor/codemirror/chunks/diff-MN3A35BY.js +1 -0
  207. package/neural-interface/public/vendor/codemirror/chunks/dist-25SQLBVS.js +3 -0
  208. package/neural-interface/public/vendor/codemirror/chunks/dist-27ZGGJJS.js +1 -0
  209. package/neural-interface/public/vendor/codemirror/chunks/dist-4E52OJIF.js +1 -0
  210. package/neural-interface/public/vendor/codemirror/chunks/dist-6FT73XDC.js +2 -0
  211. package/neural-interface/public/vendor/codemirror/chunks/dist-BE6YGRMW.js +1 -0
  212. package/neural-interface/public/vendor/codemirror/chunks/dist-CJSGKW2O.js +1 -0
  213. package/neural-interface/public/vendor/codemirror/chunks/dist-CRPH2WLI.js +1 -0
  214. package/neural-interface/public/vendor/codemirror/chunks/dist-CTG7JEQN.js +1 -0
  215. package/neural-interface/public/vendor/codemirror/chunks/dist-I3FEVELX.js +1 -0
  216. package/neural-interface/public/vendor/codemirror/chunks/dist-IM5BBSWZ.js +1 -0
  217. package/neural-interface/public/vendor/codemirror/chunks/dist-KERUKTIF.js +1 -0
  218. package/neural-interface/public/vendor/codemirror/chunks/dist-LXTMWZAM.js +1 -0
  219. package/neural-interface/public/vendor/codemirror/chunks/dist-MABASSET.js +1 -0
  220. package/neural-interface/public/vendor/codemirror/chunks/dist-MJVBUZLL.js +3 -0
  221. package/neural-interface/public/vendor/codemirror/chunks/dist-MNB3WZVU.js +1 -0
  222. package/neural-interface/public/vendor/codemirror/chunks/dist-QYWXOE3U.js +7 -0
  223. package/neural-interface/public/vendor/codemirror/chunks/dist-TEBOFWLZ.js +1 -0
  224. package/neural-interface/public/vendor/codemirror/chunks/dist-UNXFROM3.js +1 -0
  225. package/neural-interface/public/vendor/codemirror/chunks/dist-XL2SLQO6.js +1 -0
  226. package/neural-interface/public/vendor/codemirror/chunks/dist-XO42Y3AP.js +6 -0
  227. package/neural-interface/public/vendor/codemirror/chunks/dist-Y2WWDWJK.js +1 -0
  228. package/neural-interface/public/vendor/codemirror/chunks/dist-YFEZKWMU.js +1 -0
  229. package/neural-interface/public/vendor/codemirror/chunks/dist-ZY7SWQO6.js +1 -0
  230. package/neural-interface/public/vendor/codemirror/chunks/dockerfile-3Y4R4NCH.js +1 -0
  231. package/neural-interface/public/vendor/codemirror/chunks/dtd-5343GJ4P.js +1 -0
  232. package/neural-interface/public/vendor/codemirror/chunks/dylan-FI2CXSW2.js +1 -0
  233. package/neural-interface/public/vendor/codemirror/chunks/ebnf-GNQ77YWA.js +1 -0
  234. package/neural-interface/public/vendor/codemirror/chunks/ecl-MRPUAE2L.js +1 -0
  235. package/neural-interface/public/vendor/codemirror/chunks/eiffel-DZROES27.js +1 -0
  236. package/neural-interface/public/vendor/codemirror/chunks/elm-4KICRFKQ.js +1 -0
  237. package/neural-interface/public/vendor/codemirror/chunks/erlang-3IHYC263.js +1 -0
  238. package/neural-interface/public/vendor/codemirror/chunks/factor-C6D3E5UK.js +1 -0
  239. package/neural-interface/public/vendor/codemirror/chunks/fcl-VYTWUJMI.js +1 -0
  240. package/neural-interface/public/vendor/codemirror/chunks/forth-ZBO2EH5M.js +1 -0
  241. package/neural-interface/public/vendor/codemirror/chunks/fortran-5NDCRWIK.js +1 -0
  242. package/neural-interface/public/vendor/codemirror/chunks/gas-SKRMOXFP.js +1 -0
  243. package/neural-interface/public/vendor/codemirror/chunks/gherkin-L45YXZLW.js +1 -0
  244. package/neural-interface/public/vendor/codemirror/chunks/groovy-GLPTLN44.js +1 -0
  245. package/neural-interface/public/vendor/codemirror/chunks/haskell-E7NF7P4Y.js +1 -0
  246. package/neural-interface/public/vendor/codemirror/chunks/haxe-K2NVDOB6.js +1 -0
  247. package/neural-interface/public/vendor/codemirror/chunks/http-Q6DRQIJB.js +1 -0
  248. package/neural-interface/public/vendor/codemirror/chunks/idl-MZYNJVLN.js +1 -0
  249. package/neural-interface/public/vendor/codemirror/chunks/javascript-6YRYK2HR.js +1 -0
  250. package/neural-interface/public/vendor/codemirror/chunks/julia-HXEL2T5G.js +1 -0
  251. package/neural-interface/public/vendor/codemirror/chunks/livescript-MV7CI2JZ.js +1 -0
  252. package/neural-interface/public/vendor/codemirror/chunks/lua-E36NHOOD.js +1 -0
  253. package/neural-interface/public/vendor/codemirror/chunks/mathematica-3XOZZCMC.js +1 -0
  254. package/neural-interface/public/vendor/codemirror/chunks/mbox-ODYGFKS7.js +1 -0
  255. package/neural-interface/public/vendor/codemirror/chunks/mirc-WB43AWF5.js +1 -0
  256. package/neural-interface/public/vendor/codemirror/chunks/mllike-ZT6ET7ZH.js +1 -0
  257. package/neural-interface/public/vendor/codemirror/chunks/modelica-LLMZPFGA.js +1 -0
  258. package/neural-interface/public/vendor/codemirror/chunks/mscgen-2OPNNYFH.js +1 -0
  259. package/neural-interface/public/vendor/codemirror/chunks/mumps-6P3HWJH6.js +1 -0
  260. package/neural-interface/public/vendor/codemirror/chunks/nginx-TCGKQTRW.js +1 -0
  261. package/neural-interface/public/vendor/codemirror/chunks/nsis-ATIGIXTJ.js +1 -0
  262. package/neural-interface/public/vendor/codemirror/chunks/ntriples-5PLBI5OV.js +1 -0
  263. package/neural-interface/public/vendor/codemirror/chunks/octave-QF2S3NL6.js +1 -0
  264. package/neural-interface/public/vendor/codemirror/chunks/oz-JFIVFFH2.js +1 -0
  265. package/neural-interface/public/vendor/codemirror/chunks/pascal-JG6SW76V.js +1 -0
  266. package/neural-interface/public/vendor/codemirror/chunks/perl-4B3IWVVO.js +1 -0
  267. package/neural-interface/public/vendor/codemirror/chunks/pig-ABEBH4MI.js +1 -0
  268. package/neural-interface/public/vendor/codemirror/chunks/powershell-H32XE7TL.js +1 -0
  269. package/neural-interface/public/vendor/codemirror/chunks/properties-NN644MMA.js +1 -0
  270. package/neural-interface/public/vendor/codemirror/chunks/protobuf-YKWQK2J3.js +1 -0
  271. package/neural-interface/public/vendor/codemirror/chunks/pug-RIYXEKFW.js +1 -0
  272. package/neural-interface/public/vendor/codemirror/chunks/puppet-JZQ5WPEV.js +1 -0
  273. package/neural-interface/public/vendor/codemirror/chunks/python-EBCGGHAC.js +1 -0
  274. package/neural-interface/public/vendor/codemirror/chunks/q-5UMH2YQY.js +1 -0
  275. package/neural-interface/public/vendor/codemirror/chunks/r-7XMGG453.js +1 -0
  276. package/neural-interface/public/vendor/codemirror/chunks/rpm-IEV7TSLR.js +1 -0
  277. package/neural-interface/public/vendor/codemirror/chunks/ruby-T4SPFPNM.js +1 -0
  278. package/neural-interface/public/vendor/codemirror/chunks/sas-GEDX67ET.js +1 -0
  279. package/neural-interface/public/vendor/codemirror/chunks/scheme-FTLOWKPC.js +1 -0
  280. package/neural-interface/public/vendor/codemirror/chunks/shell-HUFRKFSJ.js +1 -0
  281. package/neural-interface/public/vendor/codemirror/chunks/sieve-P5QUDVYO.js +1 -0
  282. package/neural-interface/public/vendor/codemirror/chunks/smalltalk-2SORJNQI.js +1 -0
  283. package/neural-interface/public/vendor/codemirror/chunks/solr-M22TGAR5.js +1 -0
  284. package/neural-interface/public/vendor/codemirror/chunks/sparql-4M3KL752.js +1 -0
  285. package/neural-interface/public/vendor/codemirror/chunks/spreadsheet-3K23LA4N.js +1 -0
  286. package/neural-interface/public/vendor/codemirror/chunks/sql-VHRFMUBI.js +1 -0
  287. package/neural-interface/public/vendor/codemirror/chunks/stex-QZDAQUY2.js +1 -0
  288. package/neural-interface/public/vendor/codemirror/chunks/stylus-7ZDKAKBD.js +1 -0
  289. package/neural-interface/public/vendor/codemirror/chunks/swift-WCLCLGMR.js +1 -0
  290. package/neural-interface/public/vendor/codemirror/chunks/tcl-JLMU6WOK.js +1 -0
  291. package/neural-interface/public/vendor/codemirror/chunks/textile-4CHDKZ4T.js +1 -0
  292. package/neural-interface/public/vendor/codemirror/chunks/tiddlywiki-DKMCZZQ4.js +1 -0
  293. package/neural-interface/public/vendor/codemirror/chunks/tiki-F4JIG36Q.js +1 -0
  294. package/neural-interface/public/vendor/codemirror/chunks/toml-BHMHTIM2.js +1 -0
  295. package/neural-interface/public/vendor/codemirror/chunks/troff-YQ2KV5NT.js +1 -0
  296. package/neural-interface/public/vendor/codemirror/chunks/ttcn-ZHRCVFJY.js +1 -0
  297. package/neural-interface/public/vendor/codemirror/chunks/ttcn-cfg-KJOVTY4X.js +1 -0
  298. package/neural-interface/public/vendor/codemirror/chunks/turtle-535P5N6I.js +1 -0
  299. package/neural-interface/public/vendor/codemirror/chunks/vb-76UMQHOT.js +1 -0
  300. package/neural-interface/public/vendor/codemirror/chunks/vbscript-CGNDTK6Z.js +1 -0
  301. package/neural-interface/public/vendor/codemirror/chunks/velocity-VSJDO5T5.js +1 -0
  302. package/neural-interface/public/vendor/codemirror/chunks/verilog-4T6ETXIP.js +1 -0
  303. package/neural-interface/public/vendor/codemirror/chunks/vhdl-T7FNAXUN.js +1 -0
  304. package/neural-interface/public/vendor/codemirror/chunks/webidl-P6MJSJ6J.js +1 -0
  305. package/neural-interface/public/vendor/codemirror/chunks/xquery-UOVILRGN.js +1 -0
  306. package/neural-interface/public/vendor/codemirror/chunks/yacas-CV2APROL.js +1 -0
  307. package/neural-interface/public/vendor/codemirror/chunks/z80-3KFNVM4M.js +1 -0
  308. package/neural-interface/public/vendor/codemirror/codemirror-6.43.14-75f05e67.min.js +2 -0
  309. package/neural-interface/public/vendor/codemirror/manifest.json +60 -0
  310. package/neural-interface/scripts/build-vendor.mjs +78 -2
  311. package/neural-interface/scripts/vendor-codemirror-entry.js +121 -0
  312. package/neural-interface/server.js +873 -1028
  313. package/neural-interface/templates/rulesets/README.md +1 -1
  314. package/package.json +2 -8
  315. package/setup.js +44 -7
  316. package/benchmarks/browser-live-results.json +0 -79
  317. package/benchmarks/browser-v2-results.json +0 -258
  318. package/neural-interface/lib/codex-capabilities.d.ts +0 -41
  319. package/neural-interface/public/category-logos/claude.svg +0 -1
  320. package/neural-interface/public/category-logos/gemini-color.svg +0 -1
  321. package/neural-interface/public/category-logos/openai.svg +0 -1
  322. package/neural-interface/public/category-logos/synabun.png +0 -0
  323. package/neural-interface/public/claude.svg +0 -1
  324. package/neural-interface/public/gallery.svg +0 -6
  325. package/neural-interface/public/leonardoai.svg +0 -1
  326. package/neural-interface/public/mcp.svg +0 -4
  327. package/neural-interface/public/opencode-logo-dark-square.svg +0 -1
  328. package/neural-interface/public/pin-svgrepo-com.svg +0 -25
  329. package/neural-interface/public/sessionicon.svg +0 -8
  330. package/neural-interface/public/toolbar-album.svg +0 -8
  331. package/neural-interface/public/toolbar-question.svg +0 -6
  332. package/neural-interface/public/toolbar-sessions.svg +0 -6
  333. package/neural-interface/scripts/codex-protocol-fixture.mjs +0 -83
  334. package/skills/synabun/synabunicon.png +0 -0
@@ -15,6 +15,7 @@ import { createClaudeBrain } from './assistant-brains/claude.js';
15
15
  import { createCodexBrain } from './assistant-brains/codex.js';
16
16
  import { createOpenCodeBrain } from './assistant-brains/opencode.js';
17
17
  import { buildAssistantPersona, formatMailbox, reconcileRouteItems } from './assistant-persona.js';
18
+ import { normalizeTestDensity, testDensityStamp } from './assistant-test-density.js';
18
19
  import { codexUsageCostUsd } from './assistant-budget.js';
19
20
  import { CLAUDE_ESTIMATE_SOURCE, createClaudeMeter, createCodexMeter, createOpenCodeMeter, totalTokens, withTotal, zeroTokens } from './assistant-usage.js';
20
21
  import { claudeTokensCostUsd } from './assistant-pricing.js';
@@ -192,6 +193,11 @@ export function createAssistantRuntime({
192
193
  // Clarification (assistant-clarify.js): human prompts, the brain's own
193
194
  // question cards and the clarify cards' answers reach it from here.
194
195
  clarifier = null,
196
+ // The user-context engine (assistant-user-context.js): what the Assistant knows about the
197
+ // user, nudges from recent conversations, the learning loop and the per-turn trace.
198
+ userContext = null,
199
+ // lib/greeting/ engine (or null): the note about the greeting a conversation opened with, and when the person was last here.
200
+ greeting = null,
195
201
  // Remote sessions (lib/remote-policy.js): the level registry shared with the
196
202
  // dispatcher and the WhatsApp bridge, and the registered project paths a
197
203
  // remote session's workers must run in.
@@ -340,6 +346,10 @@ export function createAssistantRuntime({
340
346
  // Closing (endSessionWork): the close under way (a promise), and the generation it started.
341
347
  gen: 0, work: new Map(), workActive: null, workTimer: null, workPumpQueued: false, routeGens: new Map(),
342
348
  closing: null, closedGen: 0,
349
+ // User context (assistant-user-context.js): what the live brain was built for (a layer
350
+ // switched since restarts an idle brain), the human turns sent and not ended yet (oldest
351
+ // first), and the last reply (context for what the next message teaches).
352
+ brainUserContext: null, ucTurns: [], lastReply: '',
343
353
  };
344
354
  }
345
355
  // ── remote sessions (lib/remote-policy.js) ─────────────────────────────────
@@ -727,6 +737,12 @@ export function createAssistantRuntime({
727
737
  */
728
738
  function brainNeedsRestart(live) {
729
739
  if (!live?.brain) return false;
740
+ // Its persona was built with (or without) a user-context layer that was switched since.
741
+ if (userContext && live.brainUserContext !== null) {
742
+ let key = null;
743
+ try { key = userContext.brainKey(live.record); } catch { key = null; }
744
+ if (key !== null && key !== live.brainUserContext) return true;
745
+ }
730
746
  const policy = remotePolicyFor(live);
731
747
  // A Codex / OpenCode brain built before the session became remote is not read-only yet.
732
748
  if (live.brainProvider !== 'claude-code') return !!policy && live.brainRemoteReadOnly !== true;
@@ -747,6 +763,8 @@ export function createAssistantRuntime({
747
763
  const effectiveMode = policy ? clampRouteMode(policy, mode || defaultRouteMode(), { now: now() }) : (mode || defaultRouteMode());
748
764
  return { mode, effectiveMode, defaultMode: defaultRouteMode(), askBelow };
749
765
  }
766
+ /** The session's Test density: its own value, else the default level (assistant-test-density.js). */
767
+ function testDensityOf(record) { return normalizeTestDensity(record?.testDensity); }
750
768
  function computerSupported() { try { return !!desktop?.isSupported?.(); } catch { return false; } }
751
769
  /**
752
770
  * Effective computer toggle: explicit per-session value, else the service default (ON after setup).
@@ -825,6 +843,8 @@ export function createAssistantRuntime({
825
843
  function publicRecord(record, { withLive = true, live = null, persisted = false } = {}) {
826
844
  const out = { ...record };
827
845
  if (!persisted) delete out.usageState;
846
+ // The user-context state (frozen digest, delivery ledgers) is the server's own.
847
+ if (!persisted) delete out.userContext;
828
848
  if (withLive && live) {
829
849
  out.running = live.running;
830
850
  out.attached = live.sockets.size;
@@ -840,7 +860,10 @@ export function createAssistantRuntime({
840
860
  // A remote session's computer use is not a per-conversation choice: no explicit value, and why it is what it is.
841
861
  out.computerUseExplicit = policy ? null : (record.computerUse === true || record.computerUse === false ? record.computerUse : null);
842
862
  out.computerRemote = computer ? { state: computer.state, reason: computer.reason, level: computer.level, approved: live.computerTurn?.approved === true } : null;
843
- out.features = { routing: !!router, computer: computerSupported() };
863
+ out.testDensity = testDensityOf(record);
864
+ out.testDensityExplicit = normalizeTestDensity(record.testDensity, null);
865
+ // `rename`: this server keeps a rename on the record (userRenamed), so a panel writes it here.
866
+ out.features = { routing: !!router, computer: computerSupported(), testDensity: true, rename: true };
844
867
  out.budget = sessionMoney(record.id);
845
868
  out.remoteLevel = policy ? effectiveLevel(policy, { untrusted: live.turnUntrusted === true, now: now() }) : null;
846
869
  }
@@ -1187,6 +1210,8 @@ export function createAssistantRuntime({
1187
1210
  // The Claude CLI's own cap stopped the brain: its process cannot run another turn.
1188
1211
  if (event.subtype === 'error_max_budget_usd' && live.brainProvider === 'claude-code') live.brainCapHit = true;
1189
1212
  if (live.planTurn) live.planText = planTurnText(event);
1213
+ // The reply the user's next message answers (context for the learning loop; a subagent's result is not it).
1214
+ if (typeof event.result === 'string' && event.result.trim() && !event.parent_tool_use_id) live.lastReply = clip(event.result, 600);
1190
1215
  touch(live);
1191
1216
  }
1192
1217
  if (TRANSCRIPT_EVENT_TYPES.has(event.type) && !(event.type === 'system' && event.subtype !== 'init')) appendTranscript(id, { at: iso(now()), packet });
@@ -1292,8 +1317,32 @@ export function createAssistantRuntime({
1292
1317
  }
1293
1318
  }
1294
1319
  sendToSockets(live, packet);
1320
+ // After the packet: the turn's trace moves under the reply the panel has just closed.
1321
+ if ((packet.type === 'done' || packet.type === 'aborted' || packet.type === 'error') && !live.running) finishUserContextTurn(live, packet.type);
1295
1322
  if (plan) offerPlanCard(live, plan, planGen);
1296
1323
  }
1324
+ /** The oldest human turn still open ended: its trace moves under the reply and learning starts, off the reply path. */
1325
+ function finishUserContextTurn(live, outcome) {
1326
+ const handle = live.ucTurns.shift();
1327
+ if (!handle || !userContext) return;
1328
+ try { userContext.endTurn(live.record, handle, { outcome }); } catch (error) { log('assistant:user-context', error?.message || String(error)); }
1329
+ }
1330
+ /** One turn's user-context trace: to the panel and into the journal, a full snapshot each time (the panel upserts by traceId). */
1331
+ function emitAutomation(live, event) {
1332
+ if (!event || sessions.get(live.record.id) !== live) return;
1333
+ const packet = { type: 'event', event };
1334
+ sendToSockets(live, packet);
1335
+ appendTranscript(live.record.id, { at: iso(now()), packet });
1336
+ }
1337
+ /** The cards of a session left unanswered right now: the runtime's plan card, route cards, clarify cards. */
1338
+ function openCards(live) {
1339
+ const out = [];
1340
+ const title = (card) => clip(card?.request?.title || card?.request?.summary || card?.request?.route?.summary || card?.request?.questions?.[0]?.question || '', 80);
1341
+ if (live.planCard?.requestId) out.push({ id: String(live.planCard.requestId), kind: 'plan', title: clip(String(live.planCard.plan || '').split('\n')[0], 80) });
1342
+ try { for (const card of router?.pendingCards?.(live.record.id) || []) if (card?.request_id) out.push({ id: String(card.request_id), kind: 'route', title: title(card) }); } catch {}
1343
+ try { for (const card of clarifier?.pendingCards?.(live.record.id) || []) if (card?.request_id) out.push({ id: String(card.request_id), kind: 'question', title: title(card) }); } catch {}
1344
+ return out;
1345
+ }
1297
1346
  /**
1298
1347
  * Codex and OpenCode end a plan turn with the plan instead of asking for
1299
1348
  * approval the way Claude's ExitPlanMode does: the runtime asks, with the same
@@ -1703,6 +1752,8 @@ export function createAssistantRuntime({
1703
1752
  try { if (catalogValue?.models) sheet = selectSheetModels(catalogValue, { brain, preferences: routingCfg.preferences || {}, max: 8 }); } catch {}
1704
1753
  let setupState = null;
1705
1754
  try { setupState = desktop?.setupState?.() || null; } catch {}
1755
+ // This prompt states the level: a "[SynaBun Tests]" line follows only a later change.
1756
+ live.testDensityStamp = testDensityStamp(testDensityOf(live.record));
1706
1757
  return buildAssistantPersona({
1707
1758
  assistantSessionId: live.record.id,
1708
1759
  brain,
@@ -1721,6 +1772,7 @@ export function createAssistantRuntime({
1721
1772
  // A remote session: what remoteComputerUse says for it (a turn with untrusted content is asked either way).
1722
1773
  computer: { available: computerSupported(), enabled: computerEffective(live.record), setupState, remote: computerDecision(live, { untrusted: false }) },
1723
1774
  taskClasses: TASK_CLASS_META,
1775
+ testDensity: testDensityOf(live.record),
1724
1776
  clarify: !!clarifier,
1725
1777
  channel: live.record.channel || live.record.remote?.channel || null,
1726
1778
  });
@@ -1742,9 +1794,18 @@ export function createAssistantRuntime({
1742
1794
  if (remote) live.record.brain = clampedBrain(live, live.record.brain);
1743
1795
  const brain = live.record.brain;
1744
1796
  const sink = { send: (packet) => onBrainPacket(live, packet) };
1745
- const persona = await personaFor(live);
1797
+ const basePersona = await personaFor(live);
1746
1798
  if (stale()) return null;
1747
1799
  if (live.brain) return live.brain;
1800
+ // What SynaBun has learned about the user rides after the persona, in the same system-level
1801
+ // slot (Claude systemPromptAppend, Codex developerInstructions, OpenCode agent prompt), frozen
1802
+ // per provider conversation so the prompt cache holds.
1803
+ let userSuffix = { text: '', key: null, nudges: false };
1804
+ if (userContext) {
1805
+ try { userSuffix = userContext.personaSuffix(live.record) || userSuffix; }
1806
+ catch (error) { log('assistant:user-context', error?.message || String(error)); }
1807
+ }
1808
+ const persona = userSuffix.text ? `${basePersona}\n\n${userSuffix.text}` : basePersona;
1748
1809
  live.personaText = persona;
1749
1810
  // Computer use: one unforgeable grant per brain (the MCP `computer` group is
1750
1811
  // advertised only to callers presenting it). Revoked when the brain goes.
@@ -1790,6 +1851,8 @@ export function createAssistantRuntime({
1790
1851
  // A Codex / OpenCode brain of a remote session is read-only for good: the
1791
1852
  // read-only sandbox (Codex), the plan agent (OpenCode), no auto-accept.
1792
1853
  remoteReadOnly: !!remote && remoteBrainLimited(brain.provider),
1854
+ // The engine keeps this conversation's thread record: pre-compact.mjs asks for no conversation memory.
1855
+ userContextThreads: brain.provider === 'claude-code' && userSuffix.nudges === true,
1793
1856
  };
1794
1857
  const gate = ensureGate(live);
1795
1858
  // A remote session's hook / plugin refuses a call it could not check (fail closed).
@@ -1805,11 +1868,14 @@ export function createAssistantRuntime({
1805
1868
  const memoryHooks = brain.provider === 'claude-code' && memory?.claudeHooks ? memory.claudeHooks({
1806
1869
  session: live.record.id, project: live.record.project || 'global',
1807
1870
  getGeneration: () => live.record.memoryGeneration || 1,
1808
- bumpGeneration: () => { live.record.memoryGeneration = (live.record.memoryGeneration || 1) + 1; touch(live); },
1871
+ bumpGeneration: () => { live.record.memoryGeneration = (live.record.memoryGeneration || 1) + 1; try { userContext?.onCompaction(live.record); } catch {} touch(live); },
1809
1872
  markRemembered: () => { live.rememberedThisTurn = true; live.obligations.clear(); },
1810
1873
  hasPendingObligations: () => live.obligations.size > 0 && !live.rememberedThisTurn,
1811
1874
  onRecall: (recalled) => { if (recalled?.results?.length || recalled?.alreadyPresent) sendToSockets(live, { type: 'event', event: { type: 'synabun.memories', memories: recalled.results || [], alreadyPresent: !!recalled.alreadyPresent } }); },
1812
1875
  describeTurn: (text) => describeTurn(live, text),
1876
+ // User context: its categories stay out of Related Memories, and its per-turn block follows them.
1877
+ divert: () => userContext?.divertCategories() || null,
1878
+ turnContext: ({ recalled, meta }) => (userContext && meta?.uc ? userContext.turnBlock(live.record, meta.uc, recalled).text : ''),
1813
1879
  }) : null;
1814
1880
  // Claude: memory, the route gate, the browser policy (every session: plan mode and subagents too), a remote session's hook.
1815
1881
  const claude = brain.provider === 'claude-code';
@@ -1821,6 +1887,7 @@ export function createAssistantRuntime({
1821
1887
  live.brainSkipAllowed = remote ? deps.remoteLevel === 'autonomous' : null;
1822
1888
  live.brainComputerMode = computerMode;
1823
1889
  live.brainRemoteReadOnly = deps.remoteReadOnly === true;
1890
+ live.brainUserContext = userSuffix.key;
1824
1891
  live.record.status = 'idle';
1825
1892
  touch(live);
1826
1893
  try { await created.start?.(); } catch (error) {
@@ -1858,6 +1925,9 @@ export function createAssistantRuntime({
1858
1925
  live.running = false;
1859
1926
  closePlanCard(live, 'brain_disposed');
1860
1927
  live.planTurn = false;
1928
+ live.brainUserContext = null;
1929
+ // A human turn the brain took with it never ends on its own: its trace and its learning close here.
1930
+ for (const handle of live.ucTurns.splice(0)) { try { userContext?.endTurn(live.record, handle, { outcome: 'aborted' }); } catch {} }
1861
1931
  // A remote session's computer use ends with its brain: the turn's approval, its open request, the live grant.
1862
1932
  endComputerTurn(live, reason === 'closed' ? 'session_closed' : 'brain_disposed');
1863
1933
  settleNativeQuestions(live, 'brain_disposed');
@@ -1990,7 +2060,7 @@ export function createAssistantRuntime({
1990
2060
  // `offerPlan`: a system turn that revises a plan still ends with a plan card.
1991
2061
  // `work`: the scheduler entry this system turn is; `request`: { id, reply } of a
1992
2062
  // prompt a socket sent (refuse); `turnGen`: the generation runQuery entered in.
1993
- async function runTurn(live, { text, images = [], cwd = null, model = null, effort = null, permissionMode = null, planMode = null, system = false, taskId = null, turnModel = null, turnEffort = null, gateOpen = false, gateCarry = false, origin = null, authority = null, untrusted = false, offerPlan = false, work = null, request = null, turnGen = live.gen, computerRoutes = null } = {}) {
2063
+ async function runTurn(live, { text, images = [], cwd = null, model = null, effort = null, permissionMode = null, planMode = null, system = false, taskId = null, turnModel = null, turnEffort = null, gateOpen = false, gateCarry = false, origin = null, authority = null, untrusted = false, offerPlan = false, work = null, request = null, turnGen = live.gen, computerRoutes = null, typed = null, greetingId = null } = {}) {
1994
2064
  const prompt = String(text || '').trim();
1995
2065
  if (!prompt && !(Array.isArray(images) && images.length)) return refuse(live, 'EMPTY_PROMPT', 'No prompt provided', { packet: { type: 'error', message: 'No prompt provided' }, request });
1996
2066
  // The scheduler's turn goes once a Stop, the session closing or a newer
@@ -2049,6 +2119,8 @@ export function createAssistantRuntime({
2049
2119
  return refuse(live, 'ASSISTANT_BUSY', BUSY_MESSAGE, { packet: { type: 'error', message: BUSY_MESSAGE, code: 'ASSISTANT_BUSY' }, request });
2050
2120
  }
2051
2121
  let finalText = prompt;
2122
+ // A Codex / OpenCode turn's recall result, for the user-context block below.
2123
+ let turnRecalled = null;
2052
2124
  if (!system) {
2053
2125
  live.lastPrompt = prompt;
2054
2126
  // A prompt from elsewhere (WhatsApp, the API) is news to the panel: it goes live too.
@@ -2057,7 +2129,8 @@ export function createAssistantRuntime({
2057
2129
  if (origin && origin !== 'ui') sendToSockets(live, promptPacket);
2058
2130
  if (live.brainProvider !== 'claude-code' && memory?.recallForPrompt) {
2059
2131
  let recalled = null;
2060
- try { recalled = await memory.recallForPrompt({ prompt, project: live.record.project, session: live.record.id, generation: live.record.memoryGeneration || 1 }); } catch {}
2132
+ try { recalled = await memory.recallForPrompt({ prompt, project: live.record.project, session: live.record.id, generation: live.record.memoryGeneration || 1, divert: userContext?.divertCategories() || null }); } catch {}
2133
+ turnRecalled = recalled;
2061
2134
  // Closed while it recalled: the session that reopened hears nothing of this prompt, and its record keeps what came since.
2062
2135
  if (cancelled()) return dropTurn(live, request);
2063
2136
  try {
@@ -2087,6 +2160,16 @@ export function createAssistantRuntime({
2087
2160
  }
2088
2161
  } catch {}
2089
2162
  }
2163
+ // Test density changed since the brain last saw it (the same place, after the router's line).
2164
+ // Its prompt states the level it starts with (personaFor), so only a change is a line.
2165
+ try {
2166
+ const tests = testDensityStamp(testDensityOf(live.record));
2167
+ if (live.testDensityStamp === undefined) live.testDensityStamp = tests;
2168
+ else if (live.testDensityStamp !== tests) {
2169
+ finalText = finalText.endsWith(prompt) ? `${finalText.slice(0, finalText.length - prompt.length)}${tests}\n\n${prompt}` : `${tests}\n\n${finalText}`;
2170
+ live.testDensityStamp = tests;
2171
+ }
2172
+ } catch {}
2090
2173
  // A new request cycle. Open clarify questions make this message the
2091
2174
  // user's reply (assistant-clarify.js); otherwise the last brief retires.
2092
2175
  live.promptCycle += 1;
@@ -2149,7 +2232,8 @@ export function createAssistantRuntime({
2149
2232
  live.record.lastActiveAt = iso(now());
2150
2233
  live.turnStartedMs = now();
2151
2234
  live.record.turns = (Number(live.record.turns) || 0) + 1;
2152
- if (isDefaultSessionTitle(live.record.title)) {
2235
+ // A name the user gave (updateSession) is never replaced, whatever it says.
2236
+ if (!live.record.userRenamed && isDefaultSessionTitle(live.record.title)) {
2153
2237
  if (!system) { live.record.title = clip(prompt.split('\n')[0], 60); broadcast('assistant:session-updated', live); }
2154
2238
  }
2155
2239
  touch(live);
@@ -2157,12 +2241,48 @@ export function createAssistantRuntime({
2157
2241
  // the record, and routed/continuation turns override it for one turn only.
2158
2242
  const effectiveModel = turnModel || live.record.brain.model || null;
2159
2243
  const effectiveEffort = brainEffort(live.record.brain.provider, effectiveModel, turnEffort || live.record.brain.effort || null);
2244
+ // User context (assistant-user-context.js): a human turn's trace and learning start here.
2245
+ // A Codex / OpenCode turn gets its per-turn block now (its recall result is known); a Claude
2246
+ // turn gets it in the in-process UserPromptSubmit hook, which finds the handle in turnMeta.
2247
+ let ucTurn = null;
2248
+ // Greeting (lib/greeting/): a human turn is the person being here, and the greeting the panel
2249
+ // showed when this conversation opened is told to the brain once, as background (never for a
2250
+ // slash command). The user-context block carries it when that engine runs; else it goes in
2251
+ // front of the user's words by itself.
2252
+ let greeted = null;
2253
+ if (!system && greeting) {
2254
+ try { greeting.noteActivity({ project: live.record.project || null, cwd: live.record.brain.cwd || null, surface: 'assistant', words: typed || prompt }); } catch { /* the turn goes on */ }
2255
+ if (greetingId) {
2256
+ try {
2257
+ const shown = greeting.peek(greetingId);
2258
+ const note = greeting.consumeNote(greetingId, { skip: prompt.trimStart().startsWith('/') });
2259
+ if (note && shown) greeted = { id: shown.id, text: shown.text, note, ids: (shown.basis || []).map((row) => row.id).filter(Boolean), source: shown.source, model: shown.model, costUsd: shown.costUsd };
2260
+ } catch { greeted = null; }
2261
+ }
2262
+ }
2263
+ if (!system && userContext) {
2264
+ try {
2265
+ ucTurn = userContext.beginTurn(live.record, {
2266
+ prompt, typed, origin: origin || null, untrusted: untrusted === true, generation: live.record.memoryGeneration || 1,
2267
+ previousReply: live.lastReply || '', cwd: live.record.brain.cwd || null, taskId, greeting: greeted,
2268
+ emit: (event) => emitAutomation(live, event), persist: () => touch(live), openCards: () => openCards(live),
2269
+ });
2270
+ if (live.brainProvider !== 'claude-code') {
2271
+ const block = userContext.turnBlock(live.record, ucTurn, turnRecalled).text;
2272
+ // After the memories and the router / test lines, right before the user's words.
2273
+ if (block) finalText = finalText.endsWith(prompt) ? `${finalText.slice(0, finalText.length - prompt.length)}${block}\n\n${prompt}` : `${block}\n\n${finalText}`;
2274
+ }
2275
+ live.ucTurns.push(ucTurn);
2276
+ } catch (error) { ucTurn = null; log('assistant:user-context', error?.message || String(error)); }
2277
+ }
2278
+ // No user-context engine to carry it: the note goes right before the user's words.
2279
+ if (greeted && !ucTurn) finalText = finalText.endsWith(prompt) ? `${finalText.slice(0, finalText.length - prompt.length)}${greeted.note}\n\n${prompt}` : `${greeted.note}\n\n${finalText}`;
2160
2280
  // The Claude brain recalls in its UserPromptSubmit hook, which sees only the
2161
2281
  // final text: record who wrote it (mailbox / router = system) and the user's
2162
2282
  // own words, so a router-stamped first turn recalls on the prompt itself.
2163
2283
  if (live.brainProvider === 'claude-code' && finalText.trim()) {
2164
2284
  live.turnMeta.delete(finalText.trim());
2165
- live.turnMeta.set(finalText.trim(), { system: !!system, prompt: system ? null : prompt });
2285
+ live.turnMeta.set(finalText.trim(), { system: !!system, prompt: system ? null : prompt, ...(ucTurn ? { uc: ucTurn } : {}) });
2166
2286
  while (live.turnMeta.size > 8) live.turnMeta.delete(live.turnMeta.keys().next().value);
2167
2287
  }
2168
2288
  // Every turn starts unrouted; a continuation or a route_decided "here" turn starts open.
@@ -2181,6 +2301,12 @@ export function createAssistantRuntime({
2181
2301
  scheduleUsage(live.record.id);
2182
2302
  live.planTurn = false;
2183
2303
  live.record.status = 'idle';
2304
+ // The prompt never reached the brain: nothing is traced or learned from it.
2305
+ if (ucTurn) {
2306
+ const at = live.ucTurns.indexOf(ucTurn);
2307
+ if (at >= 0) live.ucTurns.splice(at, 1);
2308
+ try { userContext?.cancelTurn(live.record, ucTurn); } catch {}
2309
+ }
2184
2310
  const message = error?.message || String(error);
2185
2311
  sendToSockets(live, { type: 'error', message });
2186
2312
  sendToSockets(live, { type: 'done', code: 1 });
@@ -2281,6 +2407,8 @@ export function createAssistantRuntime({
2281
2407
  if (!run?.assistantSessionId) return;
2282
2408
  const live = sessions.get(run.assistantSessionId);
2283
2409
  if (!live) return;
2410
+ // The conversation's thread record: its runs, their outcomes and follow-ups.
2411
+ try { userContext?.onDispatch(live.record, payload); } catch {}
2284
2412
  if (!live.record.dispatchRunIds?.includes(run.runId)) { live.record.dispatchRunIds = [...(live.record.dispatchRunIds || []), run.runId].slice(-100); touch(live); }
2285
2413
  sendToSockets(live, { type: 'assistant:dispatch', reason: payload.reason, run });
2286
2414
  scheduleUsage(live.record.id);
@@ -2316,6 +2444,7 @@ export function createAssistantRuntime({
2316
2444
  if (!live) return;
2317
2445
  const removed = new Set((payload.runIds || []).map(String));
2318
2446
  if (!removed.size) return;
2447
+ try { userContext?.onDispatch(live.record, { type: 'assistant:run-removed', runIds: [...removed] }); } catch {}
2319
2448
  for (const runId of removed) {
2320
2449
  live.obligations.delete(runId);
2321
2450
  live.nudgedRuns.delete(runId);
@@ -2395,6 +2524,10 @@ export function createAssistantRuntime({
2395
2524
  if (mode && !normalized) { const error = new Error(`Unknown route mode ${mode}`); error.code = 'ROUTE_MODE_INVALID'; error.status = 400; throw error; }
2396
2525
  live.record.routing = { ...(live.record.routing || {}), mode: normalized };
2397
2526
  }
2527
+ /** null, '' or an unknown value clears the session's own level: it reads as the default. */
2528
+ function applyTestDensity(live, level) {
2529
+ live.record.testDensity = normalizeTestDensity(level, null);
2530
+ }
2398
2531
  function applyComputerUse(live, enabled) {
2399
2532
  // A remote session's computer use is not a per-conversation choice: the owner's switch and
2400
2533
  // the level decide (remoteComputerUse). A PATCH, the socket and updateSession change nothing.
@@ -2406,9 +2539,17 @@ export function createAssistantRuntime({
2406
2539
  async function updateSession(id, patch = {}) {
2407
2540
  const live = sessions.get(id);
2408
2541
  if (!live) return null;
2409
- if (patch.title) live.record.title = clip(patch.title, 60);
2542
+ // A rename (tab, float header, sidepanel ✎) is the session's one name: every list reads it
2543
+ // here, and `userRenamed` tells a host to reopen the session under it. Calling it "SynaBun"
2544
+ // again gives the name up: the next prompt titles it, as for a session never renamed. The
2545
+ // mark stays, as `false`: a host holding the old name with a saved tab must not write it back
2546
+ // (no mark at all means a rename the record never heard of, which a host does write).
2547
+ const renamed = typeof patch.title === 'string' ? clip(patch.title, 60) : '';
2548
+ if (renamed && renamed !== DEFAULT_SESSION_TITLE) { live.record.title = renamed; live.record.userRenamed = true; }
2549
+ else if (renamed && live.record.userRenamed) { live.record.title = DEFAULT_SESSION_TITLE; live.record.userRenamed = false; }
2410
2550
  if (patch.routing && typeof patch.routing === 'object' && 'mode' in patch.routing) applyRouteMode(live, patch.routing.mode);
2411
2551
  if ('computerUse' in patch) applyComputerUse(live, patch.computerUse);
2552
+ if ('testDensity' in patch) applyTestDensity(live, patch.testDensity);
2412
2553
  if (patch.brain && typeof patch.brain === 'object') {
2413
2554
  // A provider switch that names no model starts from the new provider's
2414
2555
  // default, never from the old provider's model id.
@@ -2424,11 +2565,19 @@ export function createAssistantRuntime({
2424
2565
  // A usage limit is the account's: another provider or account starts without the notice.
2425
2566
  if (next.provider !== live.record.brain.provider || next.accountId !== live.record.brain.accountId) setLimit(live, null);
2426
2567
  live.record.brain = next;
2427
- live.record.project = detectProject(next.cwd) || live.record.project || 'global';
2568
+ // The panel's brain names its working folder `project` and never sends `cwd`. A project picked
2569
+ // there (the toolbar's Working project, the empty state's picker) moves the folder the way a
2570
+ // prompt's cwd does (runTurn): a brain that is running keeps its conversation and gets the folder
2571
+ // with its next turn; one not started yet starts there, with it as its Default project.
2572
+ const folder = !patch.brain.cwd && typeof patch.brain.project === 'string' ? patch.brain.project.trim() : '';
2573
+ if (folder && folder !== next.cwd) live.record.brain = { ...next, cwd: folder };
2574
+ live.record.project = detectProject(live.record.brain.cwd) || live.record.project || 'global';
2428
2575
  if (providerChanged) {
2429
2576
  await disposeBrain(live, 'switched');
2430
2577
  live.record.providerSessionId = null;
2431
2578
  live.record.providerThreadId = null;
2579
+ // Another conversation: its user-context digest is built (and frozen) anew.
2580
+ try { userContext?.conversationReplaced(live.record); } catch {}
2432
2581
  if (live.record.usageState) delete live.record.usageState[next.provider === 'claude-code' ? 'claude' : next.provider];
2433
2582
  if (live.record.usageState) delete live.record.usageState.codexThreadId;
2434
2583
  live.record.status = 'idle';
@@ -2472,6 +2621,8 @@ export function createAssistantRuntime({
2472
2621
  live.record.status = 'ended';
2473
2622
  touch(live);
2474
2623
  persist({ force: true });
2624
+ // The conversation's thread memory is written now (not awaited: an embedding and a write).
2625
+ try { userContext?.sessionClosed(id); } catch {}
2475
2626
  broadcast('assistant:session-ended', live);
2476
2627
  } finally { closed(); }
2477
2628
  return view(live);
@@ -2489,6 +2640,7 @@ export function createAssistantRuntime({
2489
2640
  usageTimers.delete(id);
2490
2641
  usageSentAt.delete(id);
2491
2642
  metered('drop', () => usage?.dropSession?.(id));
2643
+ try { userContext?.sessionDestroyed(id); } catch {}
2492
2644
  try { remotePolicy?.forgetSession?.(id); } catch {}
2493
2645
  try { rmSync(resolve(dataDir, 'assistant', id), { recursive: true, force: true }); } catch {}
2494
2646
  persist({ force: true });
@@ -2772,7 +2924,7 @@ export function createAssistantRuntime({
2772
2924
  // `request_id`: the panel's id for this prompt. A refusal reaches this socket
2773
2925
  // alone and carries it back, so this panel (only) puts the text back.
2774
2926
  const request = { id: msg.request_id ? String(msg.request_id) : null, reply };
2775
- const result = await runQuery(live, { text: msg.prompt, images: msg.images || [], cwd: msg.cwd || null, model: msg.model || null, effort: msg.effort || null, permissionMode: msg.permissionMode || null, planMode: typeof msg.planMode === 'boolean' ? msg.planMode : null, origin: 'ui', request });
2927
+ const result = await runQuery(live, { text: msg.prompt, images: msg.images || [], cwd: msg.cwd || null, model: msg.model || null, effort: msg.effort || null, permissionMode: msg.permissionMode || null, planMode: typeof msg.planMode === 'boolean' ? msg.planMode : null, origin: 'ui', request, typed: typeof msg.typed === 'string' ? msg.typed : null, greetingId: typeof msg.greetingId === 'string' ? msg.greetingId : null });
2776
2928
  // Refused before it ran: the panel, "working" since it sent, shows what runs now.
2777
2929
  if (result && !result.ok && result.code !== 'TURN_FAILED') reply({ type: 'reattach_result', ok: true, sessionId: live.record.id, running: isBusy(live.record.id) });
2778
2930
  return;
@@ -2814,6 +2966,12 @@ export function createAssistantRuntime({
2814
2966
  broadcast('assistant:session-updated', live);
2815
2967
  sendToSockets(live, { type: 'assistant:session', session: view(live), reason: 'routing' });
2816
2968
  return;
2969
+ case 'set_test_density':
2970
+ applyTestDensity(live, msg.level);
2971
+ touch(live);
2972
+ broadcast('assistant:session-updated', live);
2973
+ sendToSockets(live, { type: 'assistant:session', session: view(live), reason: 'tests' });
2974
+ return;
2817
2975
  case 'set_computer_use':
2818
2976
  applyComputerUse(live, msg.enabled);
2819
2977
  touch(live);
@@ -2837,12 +2995,14 @@ export function createAssistantRuntime({
2837
2995
  return;
2838
2996
  case 'compact':
2839
2997
  live.record.memoryGeneration = (live.record.memoryGeneration || 1) + 1;
2998
+ try { userContext?.onCompaction(live.record); } catch {}
2840
2999
  touch(live);
2841
3000
  // Session work: it starts once a running turn ends, and nothing else starts until it settles.
2842
3001
  queueWork(live, { kind: 'compact', reply });
2843
3002
  return;
2844
3003
  case 'clear':
2845
3004
  live.record.memoryGeneration = (live.record.memoryGeneration || 1) + 1;
3005
+ try { userContext?.onCompaction(live.record); } catch {}
2846
3006
  touch(live);
2847
3007
  return;
2848
3008
  case 'heartbeat':
@@ -2869,6 +3029,8 @@ export function createAssistantRuntime({
2869
3029
  live.record.providerSessionId = msg.providerSessionId;
2870
3030
  // Another provider session: the meter's baseline was the old one's.
2871
3031
  if (moved) resetMeterIdentity(live, msg.providerSessionId);
3032
+ // …and so was the frozen user-context digest.
3033
+ if (moved) { try { userContext?.conversationReplaced(live.record); } catch {} }
2872
3034
  touch(live);
2873
3035
  await live.brain?.resume?.(msg.providerSessionId);
2874
3036
  }
@@ -2887,6 +3049,12 @@ export function createAssistantRuntime({
2887
3049
  }
2888
3050
  }
2889
3051
 
3052
+ /** The dispatcher's reading of a session's Test density (wired in server.js); null for an unknown session. */
3053
+ function testDensityFor(id) {
3054
+ const live = sessions.get(id);
3055
+ return live ? testDensityOf(live.record) : null;
3056
+ }
3057
+
2890
3058
  // ── router sinks (wired lazily in server.js; see assistant-router.js) ──────
2891
3059
  function routerSession(id) {
2892
3060
  const live = sessions.get(id);
@@ -3303,6 +3471,7 @@ export function createAssistantRuntime({
3303
3471
  live.record.status = 'idle';
3304
3472
  }
3305
3473
  persist({ force: true });
3474
+ try { userContext?.shutdown(); } catch {}
3306
3475
  // Rows whose append failed get one more try before the process goes.
3307
3476
  metered('flush', () => usage?.flush?.());
3308
3477
  }
@@ -3326,12 +3495,12 @@ export function createAssistantRuntime({
3326
3495
  handleWebSocket, listSessions, getSession, createSession, updateSession, closeSession, destroySession,
3327
3496
  resolveTerminal, accountInUse, ownsIsolatedServe, noteDispatch, subscribe, shutdown,
3328
3497
  observeBroadcast: onPermissionBroadcast,
3329
- routerSession, routerSend, routerEvent, routerMailbox, routerContinue, routerSinks, routerRouted, routerRouteFailed, routerComputerApproved, gateCheck, isPlanning, isPlanReadOnly,
3498
+ testDensityFor, routerSession, routerSend, routerEvent, routerMailbox, routerContinue, routerSinks, routerRouted, routerRouteFailed, routerComputerApproved, gateCheck, isPlanning, isPlanReadOnly,
3330
3499
  clarifySession, clarifyEvent, clarifyGate, clarifySinks,
3331
3500
  getComputerUse, getComputerTurn, supersedeComputerApproval, abortTurn, notifySession,
3332
3501
  brainSpend, brainCapUsd, onBudgetChanged, currentTask, usageView,
3333
3502
  // Programmatic surface (the WhatsApp bridge) and remote sessions.
3334
3503
  observe, submit, stopTurn, answerControl, answerDispatchControl, pendingControls, isBusy, sessionPolicy, sessionChannel,
3335
- _internals: { sessions, ensureBrain, disposeBrain, runQuery, enqueueMailbox, deliverMailbox, settings, ensureGate, onBrainPacket, onTurnFinished, remoteHooks, packetObservers },
3504
+ _internals: { sessions, ensureBrain, disposeBrain, runQuery, enqueueMailbox, deliverMailbox, settings, ensureGate, onBrainPacket, onTurnFinished, remoteHooks, packetObservers, userContext },
3336
3505
  };
3337
3506
  }
@@ -10,6 +10,7 @@
10
10
  // parses. Pure module: no I/O, no server imports.
11
11
 
12
12
  import { browserPolicyText } from './browser-tool-policy.js';
13
+ import { testingBlock, testingFollowUpLine } from './assistant-test-density.js';
13
14
 
14
15
  export const RESULT_HEADING = '## Result';
15
16
  /** What a worker without a browser says to get one (the brain dispatches it again with uses_browser). */
@@ -291,7 +292,9 @@ export function briefBlock(brief) {
291
292
  * collectMedia ('image' | 'video' for Image / Video creation runs; 'image' for design),
292
293
  * playbook ({ name, text } for design runs: assistant-playbooks.js),
293
294
  * styleGuide ({ summary, designPath, tokenFiles, proposals } when the project has a saved Style Guide
294
- * that lets this class of run in: lib/style-guide/store.js loadStyleGuideForRun) }
295
+ * that lets this class of run in: lib/style-guide/store.js loadStyleGuideForRun),
296
+ * testDensity (the session's Test density when the dispatcher composes the prompt; the TESTING block,
297
+ * for code / complex / quick runs and runs with no class: assistant-test-density.js) }
295
298
  */
296
299
  export function buildTaskPrompt(state = {}) {
297
300
  const task = clean(state.task);
@@ -335,12 +338,18 @@ export function buildTaskPrompt(state = {}) {
335
338
  if (brief) lines.push('', brief);
336
339
  const context = clean(state.context);
337
340
  if (context) lines.push('', 'CONTEXT (from the assistant):', context);
341
+ // After TASK and CONTEXT, which it overrides: the only place SynaBun tells a worker how much to test.
342
+ const testing = testingBlock(state.testDensity, state.taskClass);
343
+ if (testing) lines.push('', testing);
338
344
  lines.push('', resultContractText({ requireJson: !!state.outputSchema, media: !!(mediaBlock(state) || designBlock(state)) }));
339
345
  return lines.filter((line) => line !== null && line !== undefined).join('\n').replace(/\n{3,}/g, '\n\n');
340
346
  }
341
347
 
342
- /** Follow-up turn wrapper (agent_send / user steer) for the same run. */
343
- export function buildFollowUpPrompt(text, state = {}, { turn = null, maxTurns = null, origin = 'assistant' } = {}) {
348
+ /**
349
+ * Follow-up turn wrapper (agent_send / user steer) for the same run.
350
+ * `testDensity`: the level, only when it changed since the run's last TESTING rules.
351
+ */
352
+ export function buildFollowUpPrompt(text, state = {}, { turn = null, maxTurns = null, origin = 'assistant', testDensity = null } = {}) {
344
353
  const project = clean(state.project) || 'global';
345
354
  const runId = clean(state.runId) || 'unknown-run';
346
355
  const counter = turn && maxTurns ? ` ${turn}/${maxTurns}` : turn ? ` ${turn}` : '';
@@ -349,6 +358,7 @@ export function buildFollowUpPrompt(text, state = {}, { turn = null, maxTurns =
349
358
  `${who}${counter} (same run ${runId}, project "${project}"):`,
350
359
  clean(text),
351
360
  '',
361
+ ...(testDensity ? [testingFollowUpLine(testDensity), ''] : []),
352
362
  `Apply the same MEMORY and OUTPUT CONTRACT rules; end with the ${RESULT_HEADING} block.`,
353
363
  ].join('\n');
354
364
  }