synabun 2.0.0-beta → 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 (374) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/NOTICE +5 -0
  3. package/README.md +1 -1
  4. package/THIRD-PARTY-LICENSES.md +51 -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/launcher.mjs +40 -0
  14. package/lib/claude-hooks.js +61 -8
  15. package/lib/client-config-repair.js +4 -3
  16. package/lib/data-home-migration.js +4 -0
  17. package/lib/desktop-pwa.js +206 -0
  18. package/lib/external-tools.js +221 -0
  19. package/lib/first-launch-protection.js +5 -0
  20. package/lib/packaged-runtime.js +110 -0
  21. package/lib/start-launcher.js +1396 -0
  22. package/mcp-server/package-lock.json +2 -2
  23. package/mcp-server/package.json +1 -3
  24. package/neural-interface/i18n/en.json +1540 -154
  25. package/neural-interface/i18n/pt-BR.json +1538 -153
  26. package/neural-interface/lib/assistant-api.js +62 -6
  27. package/neural-interface/lib/assistant-brains/claude.js +4 -0
  28. package/neural-interface/lib/assistant-catalog.js +179 -27
  29. package/neural-interface/lib/assistant-config.js +31 -2
  30. package/neural-interface/lib/assistant-dispatch.js +62 -13
  31. package/neural-interface/lib/assistant-envelope.js +9 -6
  32. package/neural-interface/lib/assistant-hidden-models.js +393 -66
  33. package/neural-interface/lib/assistant-memory.js +24 -10
  34. package/neural-interface/lib/assistant-persona.js +6 -2
  35. package/neural-interface/lib/assistant-pricing.js +21 -6
  36. package/neural-interface/lib/assistant-runtime.js +181 -12
  37. package/neural-interface/lib/assistant-task-prompt.js +13 -3
  38. package/neural-interface/lib/assistant-test-density.js +142 -0
  39. package/neural-interface/lib/assistant-user-context-budgets.js +123 -0
  40. package/neural-interface/lib/assistant-user-context.js +908 -0
  41. package/neural-interface/lib/assistant-user-knowledge.js +328 -0
  42. package/neural-interface/lib/assistant-user-learn.js +404 -0
  43. package/neural-interface/lib/assistant-user-threads.js +226 -0
  44. package/neural-interface/lib/assistant-user-writer.js +181 -0
  45. package/neural-interface/lib/augmented-path.js +23 -3
  46. package/neural-interface/lib/browser-background-tab.js +134 -0
  47. package/neural-interface/lib/browser-language.js +83 -0
  48. package/neural-interface/lib/browser-screenshot.js +678 -61
  49. package/neural-interface/lib/claude-agent-bridge.js +87 -153
  50. package/neural-interface/lib/claude-executable.js +241 -0
  51. package/neural-interface/lib/claude-model-catalog.js +82 -93
  52. package/neural-interface/lib/claude-temporary.js +46 -7
  53. package/neural-interface/lib/cloudflared/install.js +533 -0
  54. package/neural-interface/lib/cloudflared/manager.js +545 -0
  55. package/neural-interface/lib/cloudflared/pins.js +115 -0
  56. package/neural-interface/lib/cloudflared/pins.json +63 -0
  57. package/neural-interface/lib/cloudflared/tunnel.js +265 -0
  58. package/neural-interface/lib/editor-completion-endpoint.js +264 -0
  59. package/neural-interface/lib/editor-completion-opencode.js +436 -0
  60. package/neural-interface/lib/editor-completion.js +714 -0
  61. package/neural-interface/lib/editor-config.js +149 -0
  62. package/neural-interface/lib/editor-files-api.js +268 -0
  63. package/neural-interface/lib/editor-git-api.js +134 -0
  64. package/neural-interface/lib/file-explorer-api.js +1039 -0
  65. package/neural-interface/lib/file-explorer-system.js +355 -0
  66. package/neural-interface/lib/file-explorer-trash.ps1 +85 -0
  67. package/neural-interface/lib/file-explorer-watch.js +239 -0
  68. package/neural-interface/lib/git-diff-api.js +103 -0
  69. package/neural-interface/lib/git-read.js +202 -0
  70. package/neural-interface/lib/greeting/api.js +89 -0
  71. package/neural-interface/lib/greeting/budgets.js +90 -0
  72. package/neural-interface/lib/greeting/engine.js +461 -0
  73. package/neural-interface/lib/greeting/facts.js +320 -0
  74. package/neural-interface/lib/greeting/sources.js +252 -0
  75. package/neural-interface/lib/greeting/writer.js +139 -0
  76. package/neural-interface/lib/http-guards.js +189 -13
  77. package/neural-interface/lib/machine-names.js +88 -0
  78. package/neural-interface/lib/memory-api.js +31 -7
  79. package/neural-interface/lib/native-binary-runtime.js +69 -337
  80. package/neural-interface/lib/native-loop-providers.js +40 -49
  81. package/neural-interface/lib/offline-page.js +54 -0
  82. package/neural-interface/lib/plugin-installer.js +4 -2
  83. package/neural-interface/lib/project-storage.js +5 -7
  84. package/neural-interface/lib/request-gate.js +589 -0
  85. package/neural-interface/lib/session-title-generator.js +5 -0
  86. package/neural-interface/lib/synabun-update-plan.js +19 -0
  87. package/neural-interface/lib/ui-blob-api.js +108 -0
  88. package/neural-interface/lib/ui-blob-store.js +68 -0
  89. package/neural-interface/package-lock.json +701 -253
  90. package/neural-interface/package.json +13 -2
  91. package/neural-interface/public/install-app.html +34 -0
  92. package/neural-interface/public/manifest.json +2 -0
  93. package/neural-interface/public/offline.html +402 -66
  94. package/neural-interface/public/onboarding.html +264 -147
  95. package/neural-interface/public/shared/agent-runtime-options.js +22 -6
  96. package/neural-interface/public/shared/assistant/asst-automation.js +286 -0
  97. package/neural-interface/public/shared/assistant/asst-brain-picker.js +312 -62
  98. package/neural-interface/public/shared/assistant/asst-composer.js +11 -1
  99. package/neural-interface/public/shared/assistant/asst-mascot.js +26 -64
  100. package/neural-interface/public/shared/assistant/asst-menu.js +12 -8
  101. package/neural-interface/public/shared/assistant/asst-panel.js +150 -28
  102. package/neural-interface/public/shared/assistant/asst-rack-styles.js +0 -3
  103. package/neural-interface/public/shared/assistant/asst-render.js +219 -67
  104. package/neural-interface/public/shared/assistant/asst-route-models.js +108 -0
  105. package/neural-interface/public/shared/assistant/asst-route.js +23 -329
  106. package/neural-interface/public/shared/assistant/asst-routes-editor.js +495 -0
  107. package/neural-interface/public/shared/assistant/asst-sanitize.js +8 -1
  108. package/neural-interface/public/shared/assistant/asst-sidepanel.js +31 -9
  109. package/neural-interface/public/shared/assistant/asst-state.js +185 -1
  110. package/neural-interface/public/shared/assistant/asst-styles.js +162 -6
  111. package/neural-interface/public/shared/blob-store.js +111 -22
  112. package/neural-interface/public/shared/cdx/cdx-panel.js +32 -11
  113. package/neural-interface/public/shared/cdx/cdx-render.js +25 -89
  114. package/neural-interface/public/shared/cdx/cdx-styles.js +36 -0
  115. package/neural-interface/public/shared/cdx/cdx-tabs.js +87 -4
  116. package/neural-interface/public/shared/command-runner-model.js +30 -0
  117. package/neural-interface/public/shared/constants.js +6 -1
  118. package/neural-interface/public/shared/cp/cp-rehydrate.js +2 -1
  119. package/neural-interface/public/shared/cp/cp-styles.js +15 -0
  120. package/neural-interface/public/shared/editor/editor-ai-logic.js +409 -0
  121. package/neural-interface/public/shared/editor/editor-ai.js +619 -0
  122. package/neural-interface/public/shared/editor/editor-commands.js +212 -0
  123. package/neural-interface/public/shared/editor/editor-compare.js +103 -0
  124. package/neural-interface/public/shared/editor/editor-completion-list.js +172 -0
  125. package/neural-interface/public/shared/editor/editor-doc.js +257 -0
  126. package/neural-interface/public/shared/editor/editor-git-logic.js +131 -0
  127. package/neural-interface/public/shared/editor/editor-git.js +95 -0
  128. package/neural-interface/public/shared/editor/editor-languages.js +156 -0
  129. package/neural-interface/public/shared/editor/editor-lint.js +229 -0
  130. package/neural-interface/public/shared/editor/editor-loader.js +51 -0
  131. package/neural-interface/public/shared/editor/editor-markdown.js +90 -0
  132. package/neural-interface/public/shared/editor/editor-menu.js +233 -0
  133. package/neural-interface/public/shared/editor/editor-minimap.js +371 -0
  134. package/neural-interface/public/shared/editor/editor-palette-logic.js +192 -0
  135. package/neural-interface/public/shared/editor/editor-palette.js +125 -0
  136. package/neural-interface/public/shared/editor/editor-panel-logic.js +288 -0
  137. package/neural-interface/public/shared/editor/editor-panel.js +2099 -0
  138. package/neural-interface/public/shared/editor/editor-prefs.js +224 -0
  139. package/neural-interface/public/shared/editor/editor-safety.js +500 -0
  140. package/neural-interface/public/shared/editor/editor-search.js +352 -0
  141. package/neural-interface/public/shared/editor/editor-statusbar.js +144 -0
  142. package/neural-interface/public/shared/editor/editor-tabs-logic.js +331 -0
  143. package/neural-interface/public/shared/editor/editor-tabs.js +233 -0
  144. package/neural-interface/public/shared/editor/editor-theme.js +244 -0
  145. package/neural-interface/public/shared/editor/editor-view.js +332 -0
  146. package/neural-interface/public/shared/explorer/fx-api.js +216 -0
  147. package/neural-interface/public/shared/explorer/fx-links.js +204 -0
  148. package/neural-interface/public/shared/explorer/fx-logic.js +537 -0
  149. package/neural-interface/public/shared/explorer/fx-main.js +865 -0
  150. package/neural-interface/public/shared/explorer/fx-names.js +87 -0
  151. package/neural-interface/public/shared/explorer/fx-ops.js +370 -0
  152. package/neural-interface/public/shared/explorer/fx-popups.js +490 -0
  153. package/neural-interface/public/shared/explorer/fx-tree.js +790 -0
  154. package/neural-interface/public/shared/file-links.js +339 -0
  155. package/neural-interface/public/shared/greeting/greeting-card.js +178 -0
  156. package/neural-interface/public/shared/greeting/greeting-view.js +148 -0
  157. package/neural-interface/public/shared/html-shell.js +169 -170
  158. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +4 -0
  159. package/neural-interface/public/shared/ocp-v2/ocp-v2-greeting.js +47 -0
  160. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +99 -17
  161. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +7 -1
  162. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +5 -0
  163. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +40 -0
  164. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +3 -2
  165. package/neural-interface/public/shared/pwa-install-page.js +62 -0
  166. package/neural-interface/public/shared/pwa-install.css +12 -0
  167. package/neural-interface/public/shared/pwa-install.js +127 -0
  168. package/neural-interface/public/shared/settings/settings-code-editor-pane.js +412 -0
  169. package/neural-interface/public/shared/settings/settings-code-editor.js +328 -0
  170. package/neural-interface/public/shared/settings/settings-greeting.js +133 -0
  171. package/neural-interface/public/shared/settings/settings-ia.js +18 -4
  172. package/neural-interface/public/shared/settings/settings-rules.js +160 -0
  173. package/neural-interface/public/shared/settings/settings-user-context.js +154 -0
  174. package/neural-interface/public/shared/start-bridge.js +802 -0
  175. package/neural-interface/public/shared/styleguide/sg-colors.js +5 -2
  176. package/neural-interface/public/shared/styleguide/sg-import-export.js +4 -3
  177. package/neural-interface/public/shared/styleguide/sg-kit.js +4 -1
  178. package/neural-interface/public/shared/styleguide/sg-panel.js +9 -1
  179. package/neural-interface/public/shared/styleguide/sg-typography.js +1 -0
  180. package/neural-interface/public/shared/styles.css +2219 -1117
  181. package/neural-interface/public/shared/synabun-mascot.js +20 -56
  182. package/neural-interface/public/shared/term-ready.js +307 -0
  183. package/neural-interface/public/shared/tunnel-view.js +167 -0
  184. package/neural-interface/public/shared/ui-claude-panel.js +160 -19
  185. package/neural-interface/public/shared/ui-command-runner.js +251 -468
  186. package/neural-interface/public/shared/ui-detail.js +15 -0
  187. package/neural-interface/public/shared/ui-explorer.js +22 -0
  188. package/neural-interface/public/shared/ui-file-explorer.js +481 -2496
  189. package/neural-interface/public/shared/ui-file-links.js +103 -0
  190. package/neural-interface/public/shared/ui-help.js +26 -0
  191. package/neural-interface/public/shared/ui-invite.js +126 -116
  192. package/neural-interface/public/shared/ui-judgments.js +1 -1
  193. package/neural-interface/public/shared/ui-keybinds.js +4 -0
  194. package/neural-interface/public/shared/ui-loading.js +367 -12
  195. package/neural-interface/public/shared/ui-menubar.js +21 -55
  196. package/neural-interface/public/shared/ui-native-loop-router-state.js +11 -0
  197. package/neural-interface/public/shared/ui-native-loop-router.js +15 -2
  198. package/neural-interface/public/shared/ui-navbar.js +45 -21
  199. package/neural-interface/public/shared/ui-settings.js +491 -547
  200. package/neural-interface/public/shared/ui-sidepanel-runtime.js +2 -1
  201. package/neural-interface/public/shared/ui-sync.js +16 -0
  202. package/neural-interface/public/shared/ui-terminal.js +153 -149
  203. package/neural-interface/public/shared/ui-tunnel.js +130 -0
  204. package/neural-interface/public/shared/ui-tutorial-steps.js +6 -2
  205. package/neural-interface/public/shared/ui-whiteboard.js +12 -0
  206. package/neural-interface/public/shared/ui-worker-end.js +245 -0
  207. package/neural-interface/public/shared/ui-workspaces.js +41 -27
  208. package/neural-interface/public/shared/worker-end.js +251 -0
  209. package/neural-interface/public/sw.js +3 -1
  210. package/neural-interface/public/variant/2d/main.js +2 -1
  211. package/neural-interface/public/variant/3d/main.js +2 -1
  212. package/neural-interface/public/vendor/codemirror/chunks/apl-CSDKT72Z.js +1 -0
  213. package/neural-interface/public/vendor/codemirror/chunks/asciiarmor-JYYPU5BM.js +1 -0
  214. package/neural-interface/public/vendor/codemirror/chunks/asn1-NRWCZDA6.js +1 -0
  215. package/neural-interface/public/vendor/codemirror/chunks/asterisk-5OU6D4ZQ.js +1 -0
  216. package/neural-interface/public/vendor/codemirror/chunks/brainfuck-ID4N4SYC.js +1 -0
  217. package/neural-interface/public/vendor/codemirror/chunks/chunk-4RZWGCS2.js +1 -0
  218. package/neural-interface/public/vendor/codemirror/chunks/chunk-ACTJM3JR.js +11 -0
  219. package/neural-interface/public/vendor/codemirror/chunks/chunk-S6BMAXLK.js +1 -0
  220. package/neural-interface/public/vendor/codemirror/chunks/chunk-TO2RDOHQ.js +3 -0
  221. package/neural-interface/public/vendor/codemirror/chunks/chunk-US2GMZZQ.js +13 -0
  222. package/neural-interface/public/vendor/codemirror/chunks/chunk-WYYO5ZAJ.js +1 -0
  223. package/neural-interface/public/vendor/codemirror/chunks/chunk-XALZTDJC.js +1 -0
  224. package/neural-interface/public/vendor/codemirror/chunks/chunk-YRNUTRDV.js +1 -0
  225. package/neural-interface/public/vendor/codemirror/chunks/chunk-Z3QOKH62.js +1 -0
  226. package/neural-interface/public/vendor/codemirror/chunks/clike-OWQBSOGL.js +1 -0
  227. package/neural-interface/public/vendor/codemirror/chunks/clojure-KV75UQWC.js +1 -0
  228. package/neural-interface/public/vendor/codemirror/chunks/cmake-GTEP5Y3C.js +1 -0
  229. package/neural-interface/public/vendor/codemirror/chunks/cobol-WCZ7QEGJ.js +1 -0
  230. package/neural-interface/public/vendor/codemirror/chunks/coffeescript-TGLBOT6B.js +1 -0
  231. package/neural-interface/public/vendor/codemirror/chunks/commonlisp-WVVI432V.js +1 -0
  232. package/neural-interface/public/vendor/codemirror/chunks/crystal-S6R6HLKQ.js +1 -0
  233. package/neural-interface/public/vendor/codemirror/chunks/css-PDA2RINP.js +1 -0
  234. package/neural-interface/public/vendor/codemirror/chunks/cypher-HL5QPL4C.js +1 -0
  235. package/neural-interface/public/vendor/codemirror/chunks/d-HHJH2HAJ.js +1 -0
  236. package/neural-interface/public/vendor/codemirror/chunks/diff-MN3A35BY.js +1 -0
  237. package/neural-interface/public/vendor/codemirror/chunks/dist-25SQLBVS.js +3 -0
  238. package/neural-interface/public/vendor/codemirror/chunks/dist-27ZGGJJS.js +1 -0
  239. package/neural-interface/public/vendor/codemirror/chunks/dist-4E52OJIF.js +1 -0
  240. package/neural-interface/public/vendor/codemirror/chunks/dist-6FT73XDC.js +2 -0
  241. package/neural-interface/public/vendor/codemirror/chunks/dist-BE6YGRMW.js +1 -0
  242. package/neural-interface/public/vendor/codemirror/chunks/dist-CJSGKW2O.js +1 -0
  243. package/neural-interface/public/vendor/codemirror/chunks/dist-CRPH2WLI.js +1 -0
  244. package/neural-interface/public/vendor/codemirror/chunks/dist-CTG7JEQN.js +1 -0
  245. package/neural-interface/public/vendor/codemirror/chunks/dist-I3FEVELX.js +1 -0
  246. package/neural-interface/public/vendor/codemirror/chunks/dist-IM5BBSWZ.js +1 -0
  247. package/neural-interface/public/vendor/codemirror/chunks/dist-KERUKTIF.js +1 -0
  248. package/neural-interface/public/vendor/codemirror/chunks/dist-LXTMWZAM.js +1 -0
  249. package/neural-interface/public/vendor/codemirror/chunks/dist-MABASSET.js +1 -0
  250. package/neural-interface/public/vendor/codemirror/chunks/dist-MJVBUZLL.js +3 -0
  251. package/neural-interface/public/vendor/codemirror/chunks/dist-MNB3WZVU.js +1 -0
  252. package/neural-interface/public/vendor/codemirror/chunks/dist-QYWXOE3U.js +7 -0
  253. package/neural-interface/public/vendor/codemirror/chunks/dist-TEBOFWLZ.js +1 -0
  254. package/neural-interface/public/vendor/codemirror/chunks/dist-UNXFROM3.js +1 -0
  255. package/neural-interface/public/vendor/codemirror/chunks/dist-XL2SLQO6.js +1 -0
  256. package/neural-interface/public/vendor/codemirror/chunks/dist-XO42Y3AP.js +6 -0
  257. package/neural-interface/public/vendor/codemirror/chunks/dist-Y2WWDWJK.js +1 -0
  258. package/neural-interface/public/vendor/codemirror/chunks/dist-YFEZKWMU.js +1 -0
  259. package/neural-interface/public/vendor/codemirror/chunks/dist-ZY7SWQO6.js +1 -0
  260. package/neural-interface/public/vendor/codemirror/chunks/dockerfile-3Y4R4NCH.js +1 -0
  261. package/neural-interface/public/vendor/codemirror/chunks/dtd-5343GJ4P.js +1 -0
  262. package/neural-interface/public/vendor/codemirror/chunks/dylan-FI2CXSW2.js +1 -0
  263. package/neural-interface/public/vendor/codemirror/chunks/ebnf-GNQ77YWA.js +1 -0
  264. package/neural-interface/public/vendor/codemirror/chunks/ecl-MRPUAE2L.js +1 -0
  265. package/neural-interface/public/vendor/codemirror/chunks/eiffel-DZROES27.js +1 -0
  266. package/neural-interface/public/vendor/codemirror/chunks/elm-4KICRFKQ.js +1 -0
  267. package/neural-interface/public/vendor/codemirror/chunks/erlang-3IHYC263.js +1 -0
  268. package/neural-interface/public/vendor/codemirror/chunks/factor-C6D3E5UK.js +1 -0
  269. package/neural-interface/public/vendor/codemirror/chunks/fcl-VYTWUJMI.js +1 -0
  270. package/neural-interface/public/vendor/codemirror/chunks/forth-ZBO2EH5M.js +1 -0
  271. package/neural-interface/public/vendor/codemirror/chunks/fortran-5NDCRWIK.js +1 -0
  272. package/neural-interface/public/vendor/codemirror/chunks/gas-SKRMOXFP.js +1 -0
  273. package/neural-interface/public/vendor/codemirror/chunks/gherkin-L45YXZLW.js +1 -0
  274. package/neural-interface/public/vendor/codemirror/chunks/groovy-GLPTLN44.js +1 -0
  275. package/neural-interface/public/vendor/codemirror/chunks/haskell-E7NF7P4Y.js +1 -0
  276. package/neural-interface/public/vendor/codemirror/chunks/haxe-K2NVDOB6.js +1 -0
  277. package/neural-interface/public/vendor/codemirror/chunks/http-Q6DRQIJB.js +1 -0
  278. package/neural-interface/public/vendor/codemirror/chunks/idl-MZYNJVLN.js +1 -0
  279. package/neural-interface/public/vendor/codemirror/chunks/javascript-6YRYK2HR.js +1 -0
  280. package/neural-interface/public/vendor/codemirror/chunks/julia-HXEL2T5G.js +1 -0
  281. package/neural-interface/public/vendor/codemirror/chunks/livescript-MV7CI2JZ.js +1 -0
  282. package/neural-interface/public/vendor/codemirror/chunks/lua-E36NHOOD.js +1 -0
  283. package/neural-interface/public/vendor/codemirror/chunks/mathematica-3XOZZCMC.js +1 -0
  284. package/neural-interface/public/vendor/codemirror/chunks/mbox-ODYGFKS7.js +1 -0
  285. package/neural-interface/public/vendor/codemirror/chunks/mirc-WB43AWF5.js +1 -0
  286. package/neural-interface/public/vendor/codemirror/chunks/mllike-ZT6ET7ZH.js +1 -0
  287. package/neural-interface/public/vendor/codemirror/chunks/modelica-LLMZPFGA.js +1 -0
  288. package/neural-interface/public/vendor/codemirror/chunks/mscgen-2OPNNYFH.js +1 -0
  289. package/neural-interface/public/vendor/codemirror/chunks/mumps-6P3HWJH6.js +1 -0
  290. package/neural-interface/public/vendor/codemirror/chunks/nginx-TCGKQTRW.js +1 -0
  291. package/neural-interface/public/vendor/codemirror/chunks/nsis-ATIGIXTJ.js +1 -0
  292. package/neural-interface/public/vendor/codemirror/chunks/ntriples-5PLBI5OV.js +1 -0
  293. package/neural-interface/public/vendor/codemirror/chunks/octave-QF2S3NL6.js +1 -0
  294. package/neural-interface/public/vendor/codemirror/chunks/oz-JFIVFFH2.js +1 -0
  295. package/neural-interface/public/vendor/codemirror/chunks/pascal-JG6SW76V.js +1 -0
  296. package/neural-interface/public/vendor/codemirror/chunks/perl-4B3IWVVO.js +1 -0
  297. package/neural-interface/public/vendor/codemirror/chunks/pig-ABEBH4MI.js +1 -0
  298. package/neural-interface/public/vendor/codemirror/chunks/powershell-H32XE7TL.js +1 -0
  299. package/neural-interface/public/vendor/codemirror/chunks/properties-NN644MMA.js +1 -0
  300. package/neural-interface/public/vendor/codemirror/chunks/protobuf-YKWQK2J3.js +1 -0
  301. package/neural-interface/public/vendor/codemirror/chunks/pug-RIYXEKFW.js +1 -0
  302. package/neural-interface/public/vendor/codemirror/chunks/puppet-JZQ5WPEV.js +1 -0
  303. package/neural-interface/public/vendor/codemirror/chunks/python-EBCGGHAC.js +1 -0
  304. package/neural-interface/public/vendor/codemirror/chunks/q-5UMH2YQY.js +1 -0
  305. package/neural-interface/public/vendor/codemirror/chunks/r-7XMGG453.js +1 -0
  306. package/neural-interface/public/vendor/codemirror/chunks/rpm-IEV7TSLR.js +1 -0
  307. package/neural-interface/public/vendor/codemirror/chunks/ruby-T4SPFPNM.js +1 -0
  308. package/neural-interface/public/vendor/codemirror/chunks/sas-GEDX67ET.js +1 -0
  309. package/neural-interface/public/vendor/codemirror/chunks/scheme-FTLOWKPC.js +1 -0
  310. package/neural-interface/public/vendor/codemirror/chunks/shell-HUFRKFSJ.js +1 -0
  311. package/neural-interface/public/vendor/codemirror/chunks/sieve-P5QUDVYO.js +1 -0
  312. package/neural-interface/public/vendor/codemirror/chunks/smalltalk-2SORJNQI.js +1 -0
  313. package/neural-interface/public/vendor/codemirror/chunks/solr-M22TGAR5.js +1 -0
  314. package/neural-interface/public/vendor/codemirror/chunks/sparql-4M3KL752.js +1 -0
  315. package/neural-interface/public/vendor/codemirror/chunks/spreadsheet-3K23LA4N.js +1 -0
  316. package/neural-interface/public/vendor/codemirror/chunks/sql-VHRFMUBI.js +1 -0
  317. package/neural-interface/public/vendor/codemirror/chunks/stex-QZDAQUY2.js +1 -0
  318. package/neural-interface/public/vendor/codemirror/chunks/stylus-7ZDKAKBD.js +1 -0
  319. package/neural-interface/public/vendor/codemirror/chunks/swift-WCLCLGMR.js +1 -0
  320. package/neural-interface/public/vendor/codemirror/chunks/tcl-JLMU6WOK.js +1 -0
  321. package/neural-interface/public/vendor/codemirror/chunks/textile-4CHDKZ4T.js +1 -0
  322. package/neural-interface/public/vendor/codemirror/chunks/tiddlywiki-DKMCZZQ4.js +1 -0
  323. package/neural-interface/public/vendor/codemirror/chunks/tiki-F4JIG36Q.js +1 -0
  324. package/neural-interface/public/vendor/codemirror/chunks/toml-BHMHTIM2.js +1 -0
  325. package/neural-interface/public/vendor/codemirror/chunks/troff-YQ2KV5NT.js +1 -0
  326. package/neural-interface/public/vendor/codemirror/chunks/ttcn-ZHRCVFJY.js +1 -0
  327. package/neural-interface/public/vendor/codemirror/chunks/ttcn-cfg-KJOVTY4X.js +1 -0
  328. package/neural-interface/public/vendor/codemirror/chunks/turtle-535P5N6I.js +1 -0
  329. package/neural-interface/public/vendor/codemirror/chunks/vb-76UMQHOT.js +1 -0
  330. package/neural-interface/public/vendor/codemirror/chunks/vbscript-CGNDTK6Z.js +1 -0
  331. package/neural-interface/public/vendor/codemirror/chunks/velocity-VSJDO5T5.js +1 -0
  332. package/neural-interface/public/vendor/codemirror/chunks/verilog-4T6ETXIP.js +1 -0
  333. package/neural-interface/public/vendor/codemirror/chunks/vhdl-T7FNAXUN.js +1 -0
  334. package/neural-interface/public/vendor/codemirror/chunks/webidl-P6MJSJ6J.js +1 -0
  335. package/neural-interface/public/vendor/codemirror/chunks/xquery-UOVILRGN.js +1 -0
  336. package/neural-interface/public/vendor/codemirror/chunks/yacas-CV2APROL.js +1 -0
  337. package/neural-interface/public/vendor/codemirror/chunks/z80-3KFNVM4M.js +1 -0
  338. package/neural-interface/public/vendor/codemirror/codemirror-6.43.14-75f05e67.min.js +2 -0
  339. package/neural-interface/public/vendor/codemirror/manifest.json +60 -0
  340. package/neural-interface/scripts/build-vendor.mjs +78 -2
  341. package/neural-interface/scripts/rebuild-pty.js +15 -12
  342. package/neural-interface/scripts/vendor-codemirror-entry.js +121 -0
  343. package/neural-interface/server.js +1092 -1212
  344. package/neural-interface/templates/rulesets/README.md +1 -1
  345. package/package.json +13 -7
  346. package/postinstall.js +11 -81
  347. package/setup.js +111 -6
  348. package/benchmarks/browser-live-results.json +0 -79
  349. package/benchmarks/browser-v2-results.json +0 -258
  350. package/docs/browser-v2.md +0 -109
  351. package/docs/hook-integrity.md +0 -89
  352. package/docs/judgments.md +0 -182
  353. package/docs/style-guide.md +0 -246
  354. package/neural-interface/lib/codex-capabilities.d.ts +0 -41
  355. package/neural-interface/public/category-logos/claude.svg +0 -1
  356. package/neural-interface/public/category-logos/gemini-color.svg +0 -1
  357. package/neural-interface/public/category-logos/openai.svg +0 -1
  358. package/neural-interface/public/category-logos/synabun.png +0 -0
  359. package/neural-interface/public/claude.svg +0 -1
  360. package/neural-interface/public/gallery.svg +0 -6
  361. package/neural-interface/public/leonardoai.svg +0 -1
  362. package/neural-interface/public/mcp.svg +0 -4
  363. package/neural-interface/public/opencode-logo-dark-square.svg +0 -1
  364. package/neural-interface/public/pin-svgrepo-com.svg +0 -25
  365. package/neural-interface/public/sessionicon.svg +0 -8
  366. package/neural-interface/public/toolbar-album.svg +0 -8
  367. package/neural-interface/public/toolbar-question.svg +0 -6
  368. package/neural-interface/public/toolbar-sessions.svg +0 -6
  369. package/neural-interface/scripts/codex-protocol-fixture.mjs +0 -83
  370. package/scripts/audit-claude-hook-usage.mjs +0 -12
  371. package/scripts/audit-memory-duplicates.mjs +0 -44
  372. package/scripts/measure-claude-hook-overhead.mjs +0 -77
  373. package/scripts/recover-user-config.mjs +0 -254
  374. package/skills/synabun/synabunicon.png +0 -0
@@ -0,0 +1,320 @@
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun — Greeting: the facts (pure)
3
+ // ═══════════════════════════════════════════
4
+ //
5
+ // What a greeting may say, decided without a model: the records of one project
6
+ // (sources.js reads them) become a short list of facts, a fingerprint that
7
+ // changes only when there is something new to say, the decision whether this
8
+ // opening is a return worth greeting, the deterministic line used when the
9
+ // writer is not, and the rows the "Based on" disclosure shows.
10
+ //
11
+ // A fact is said whole or left out: a name may be shortened with an ellipsis,
12
+ // a statement never is. Pure: no I/O, no clock of its own.
13
+
14
+ import { createHash } from 'node:crypto';
15
+ import { FACTS, RETURN, TEXT } from './budgets.js';
16
+
17
+ const collapse = (value) => String(value ?? '').replace(/\s+/g, ' ').trim();
18
+ const ms = (value) => { const at = typeof value === 'number' ? value : Date.parse(value ?? ''); return Number.isFinite(at) ? at : null; };
19
+ const plural = (n, one, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;
20
+
21
+ /** A name shortened at a word, with an ellipsis (names only: titles, a quoted ask). */
22
+ export function clipLabel(value, max = TEXT.titleChars) {
23
+ const text = collapse(value);
24
+ if (text.length <= max) return text;
25
+ const cut = text.slice(0, max - 1);
26
+ const space = cut.lastIndexOf(' ');
27
+ return `${(space > max * 0.6 ? cut.slice(0, space) : cut).replace(/[\s,;:.(-]+$/, '')}…`;
28
+ }
29
+
30
+ /** The sentences of a text (terminal punctuation kept). */
31
+ export function splitSentences(value) {
32
+ const text = collapse(value);
33
+ if (!text) return [];
34
+ return text.split(/(?<=[.!?…]["”’')\]]?)\s+/u).map((part) => part.trim()).filter(Boolean);
35
+ }
36
+
37
+ /** A statement said whole or not at all: the text when it fits, else its leading whole sentences, else ''. */
38
+ export function wholeOrNothing(value, max) {
39
+ const text = collapse(value);
40
+ if (text.length <= max) return text;
41
+ let out = '';
42
+ for (const sentence of splitSentences(text)) {
43
+ const next = out ? `${out} ${sentence}` : sentence;
44
+ if (next.length > max) break;
45
+ out = next;
46
+ }
47
+ // One run-on "sentence" with no end in sight is not a whole statement.
48
+ return /[.!?…]["”’')\]]?$/u.test(out) ? out : '';
49
+ }
50
+
51
+ /** "40 minutes" · "3 hours" · "2 days" · "3 weeks" (English; the panels format their own). */
52
+ export function awayText(gapMs) {
53
+ const minutes = Math.max(0, Math.round(Number(gapMs) / 60_000));
54
+ if (!Number.isFinite(minutes)) return '';
55
+ if (minutes < 60) return plural(Math.max(1, minutes), 'minute');
56
+ const hours = Math.round(minutes / 60);
57
+ if (hours < 36) return plural(hours, 'hour');
58
+ const days = Math.round(hours / 24);
59
+ if (days < 21) return plural(days, 'day');
60
+ if (days < 75) return plural(Math.round(days / 7), 'week');
61
+ return plural(Math.round(days / 30), 'month');
62
+ }
63
+
64
+ const PROBLEMS = new Set(['failed', 'blocked', 'needs_input', 'interrupted']);
65
+ const STATUS_WORDS = Object.freeze({ done: 'finished', completed: 'finished', failed: 'failed', blocked: 'is blocked', needs_input: 'needs your input', interrupted: 'was interrupted', stopped: 'was stopped', running: 'is still running' });
66
+
67
+ /** A dispatched run's outcome in one word: what its worker reported, else how the run ended. */
68
+ export function runStatus(run = {}) {
69
+ if (run.active === true) return 'running';
70
+ const reported = String(run.outcome || '').toLowerCase();
71
+ if (reported === 'done' || PROBLEMS.has(reported)) return reported;
72
+ const state = String(run.state || '').toLowerCase();
73
+ if (state === 'completed') return 'done';
74
+ if (state === 'failed' || state === 'interrupted' || state === 'stopped') return state;
75
+ return reported || state || 'done';
76
+ }
77
+
78
+ /** An open item as the record holds it; one cut short upstream loses its unfinished tail. */
79
+ function wholeItem(value) {
80
+ let text = collapse(value);
81
+ if (!text.endsWith('…')) return { text, cut: false };
82
+ // "blocked: Title (a note that was cut…" → "blocked: Title"
83
+ const paren = text.lastIndexOf(' (');
84
+ if (paren > 0 && !text.slice(paren).includes(')')) text = text.slice(0, paren).trim();
85
+ return { text, cut: text.endsWith('…') };
86
+ }
87
+
88
+ /**
89
+ * The facts of one project.
90
+ * raw: { project, label, now, since, threads[], sessions[], memories[], runs[], schedules[], elsewhere[], git, owner }
91
+ * threads [{ memoryId, sessionId, title, endedAt, asks[], open[] }] newest first (assistant-threads)
92
+ * sessions [{ id, sessionId, title, summary, open[], at, agent, source }] session summaries and captures
93
+ * memories [{ id, title, excerpt, category, importance, at }] significant, from the last working period
94
+ * runs [{ runId, title, state, outcome, active, finishedAt, provider }] this project, since the person left
95
+ * schedules [{ id, name, result, at }] runs that did not complete
96
+ * elsewhere [{ project, label, runs, schedules }] failures in other projects (counts only)
97
+ * git { branch, dirty } | null
98
+ * owner { notes[], noteIds[], words[] }
99
+ * `redact` removes credentials from any text before it is used.
100
+ */
101
+ export function buildFacts(raw = {}, { redact = (text) => text } = {}) {
102
+ const clean = (value) => { try { return collapse(redact(collapse(value))); } catch { return ''; } };
103
+ const now = ms(raw.now) ?? Date.now();
104
+ const since = raw.since == null ? null : ms(raw.since);
105
+
106
+ const candidates = [];
107
+ for (const thread of Array.isArray(raw.threads) ? raw.threads : []) {
108
+ const at = ms(thread?.endedAt);
109
+ const title = clean(thread?.title);
110
+ if (at == null || !title) continue;
111
+ const asks = (Array.isArray(thread.asks) ? thread.asks : []).map(clean).filter(Boolean);
112
+ candidates.push({
113
+ kind: 'thread', agent: 'assistant', at, title: clipLabel(title), asks: asks.slice(-2),
114
+ ids: [thread.memoryId].filter(Boolean).map(String), key: `thread:${thread.sessionId || thread.memoryId || title}`,
115
+ open: (Array.isArray(thread.open) ? thread.open : []).map((item) => wholeItem(clean(item))).filter((item) => item.text),
116
+ });
117
+ }
118
+ for (const session of Array.isArray(raw.sessions) ? raw.sessions : []) {
119
+ const at = ms(session?.at);
120
+ const title = clean(session?.title);
121
+ if (at == null || !title) continue;
122
+ candidates.push({
123
+ kind: 'session', agent: session.agent || null, at, title: clipLabel(title), source: session.source || 'summary',
124
+ detail: wholeOrNothing(clean(session.summary), FACTS.excerptChars), quoted: session.source === 'capture',
125
+ ids: [session.id].filter(Boolean).map(String), key: `session:${session.sessionId || session.id || title}`,
126
+ // What a session summary lists as open: each said whole (its leading sentences) or left out.
127
+ open: (Array.isArray(session.open) ? session.open : []).map((item) => ({ text: wholeOrNothing(clean(item), FACTS.excerptChars), cut: false })).filter((item) => item.text),
128
+ });
129
+ }
130
+ candidates.sort((a, b) => b.at - a.at);
131
+ const last = candidates[0] || null;
132
+
133
+ const open = [];
134
+ const seenOpen = new Set();
135
+ for (const candidate of candidates) {
136
+ for (const item of candidate.open) {
137
+ const key = item.text.toLowerCase();
138
+ if (seenOpen.has(key) || open.length >= FACTS.openItems) continue;
139
+ seenOpen.add(key);
140
+ open.push({ text: item.text, cut: item.cut, ids: candidate.ids });
141
+ }
142
+ }
143
+
144
+ const memories = (Array.isArray(raw.memories) ? raw.memories : []).map((memory) => {
145
+ const title = clean(memory?.title);
146
+ if (!memory?.id || !title) return null;
147
+ return { id: String(memory.id), title: clipLabel(title), excerpt: wholeOrNothing(clean(memory.excerpt), FACTS.excerptChars), category: memory.category || null, at: ms(memory.at) };
148
+ }).filter(Boolean).slice(0, FACTS.memories);
149
+
150
+ const rank = (status) => (PROBLEMS.has(status) ? 0 : status === 'running' ? 1 : status === 'stopped' ? 3 : 2);
151
+ const allRuns = (Array.isArray(raw.runs) ? raw.runs : []).map((run) => {
152
+ const title = clean(run?.title);
153
+ if (!run?.runId || !title) return null;
154
+ return { runId: String(run.runId), title: clipLabel(title), status: runStatus(run), provider: run.provider || null, at: ms(run.finishedAt) };
155
+ }).filter(Boolean).sort((a, b) => rank(a.status) - rank(b.status) || (b.at ?? 0) - (a.at ?? 0));
156
+ const runCounts = {
157
+ total: allRuns.length,
158
+ done: allRuns.filter((run) => run.status === 'done').length,
159
+ problems: allRuns.filter((run) => PROBLEMS.has(run.status)).length,
160
+ running: allRuns.filter((run) => run.status === 'running').length,
161
+ stopped: allRuns.filter((run) => run.status === 'stopped').length,
162
+ };
163
+
164
+ const schedules = (Array.isArray(raw.schedules) ? raw.schedules : []).map((schedule) => {
165
+ const name = clean(schedule?.name);
166
+ if (!name) return null;
167
+ return { id: String(schedule.id || name), name: clipLabel(name), result: clipLabel(clean(schedule.result) || 'did not complete', 80), at: ms(schedule.at) };
168
+ }).filter(Boolean).slice(0, FACTS.schedules);
169
+
170
+ const elsewhere = (Array.isArray(raw.elsewhere) ? raw.elsewhere : []).map((item) => {
171
+ const label = clean(item?.label || item?.project);
172
+ const runs = Math.max(0, Math.round(Number(item?.runs) || 0));
173
+ const failedSchedules = Math.max(0, Math.round(Number(item?.schedules) || 0));
174
+ if (!label || runs + failedSchedules === 0) return null;
175
+ return { project: String(item.project || label), label, runs, schedules: failedSchedules };
176
+ }).filter(Boolean).slice(0, FACTS.elsewhere);
177
+
178
+ // `known` false: the branch was read but `git status` gave no answer in time (nothing is claimed about changes).
179
+ const branch = clean(raw.git?.branch);
180
+ const git = branch ? { branch, dirty: Math.max(0, Math.round(Number(raw.git.dirty) || 0)), known: raw.git.dirty != null } : null;
181
+
182
+ const owner = {
183
+ notes: (Array.isArray(raw.owner?.notes) ? raw.owner.notes : []).map(clean).filter(Boolean).slice(0, FACTS.ownerNotes),
184
+ noteIds: (Array.isArray(raw.owner?.noteIds) ? raw.owner.noteIds : []).map(String).slice(0, FACTS.ownerNotes),
185
+ words: (Array.isArray(raw.owner?.words) ? raw.owner.words : []).map((text) => clipLabel(clean(text), TEXT.quoteChars)).filter(Boolean).slice(-FACTS.ownerWords),
186
+ };
187
+
188
+ const runs = allRuns.slice(0, FACTS.runs);
189
+ const substantive = !!last || open.length > 0 || allRuns.length > 0 || schedules.length > 0 || elsewhere.length > 0;
190
+ return {
191
+ project: String(raw.project || 'global'), label: clean(raw.label) || String(raw.project || 'global'),
192
+ now, since, lastAt: last?.at ?? null,
193
+ last, open, memories, runs, runCounts, schedules, elsewhere, git, owner, substantive,
194
+ };
195
+ }
196
+
197
+ /**
198
+ * Changes only when there is something new to say: the last work, what is open, runs and their
199
+ * outcomes, schedules that did not complete, failures elsewhere. The working tree is not news
200
+ * (gitKey): a commit made in a terminal must not greet a person twice.
201
+ */
202
+ export function fingerprint(facts) {
203
+ if (!facts) return '';
204
+ const parts = {
205
+ last: facts.last ? `${facts.last.key}@${Math.floor(facts.last.at / 60_000)}` : null,
206
+ open: facts.open.map((item) => item.text),
207
+ runs: facts.runs.map((run) => `${run.runId}:${run.status}`),
208
+ counts: facts.runCounts,
209
+ schedules: facts.schedules.map((schedule) => `${schedule.id}:${schedule.at ?? ''}:${schedule.result}`),
210
+ elsewhere: facts.elsewhere.map((item) => `${item.project}:${item.runs}:${item.schedules}`),
211
+ };
212
+ return createHash('sha1').update(JSON.stringify(parts)).digest('hex').slice(0, 16);
213
+ }
214
+
215
+ /**
216
+ * The working tree a prepared sentence may have described: branch and whether anything is
217
+ * uncommitted. null when unknown (no repository, or `git status` did not answer in time).
218
+ */
219
+ export function gitKey(facts) {
220
+ if (!facts?.git) return 'none';
221
+ if (!facts.git.known) return null;
222
+ return `${facts.git.branch}:${facts.git.dirty > 0 ? 'dirty' : 'clean'}`;
223
+ }
224
+
225
+ /**
226
+ * Is this opening a return worth a greeting?
227
+ * → { greet, reason }: 'off' | 'first-session' | 'recent' | 'nothing-to-say' | 'nothing-new' | 'return' | 'forced'
228
+ */
229
+ export function decide({ enabled = true, facts = null, now = Date.now(), since = null, shownFingerprint = null, forced = false, gapMs = RETURN.gapMs } = {}) {
230
+ if (!enabled) return { greet: false, reason: 'off' };
231
+ if (!facts || !facts.substantive) return { greet: false, reason: since == null ? 'first-session' : 'nothing-to-say' };
232
+ if (forced) return { greet: true, reason: 'forced' };
233
+ if (since == null) return { greet: false, reason: 'first-session' };
234
+ if (now - since < gapMs) return { greet: false, reason: 'recent' };
235
+ if (shownFingerprint && shownFingerprint === fingerprint(facts)) return { greet: false, reason: 'nothing-new' };
236
+ return { greet: true, reason: 'return' };
237
+ }
238
+
239
+ /** "blocked: Title (note)" → "Still blocked: Title (note)."; anything else → "Still open: …". */
240
+ function openSentence(text) {
241
+ const item = String(text).replace(/[.]+$/, '');
242
+ const status = /^(blocked|failed|interrupted|needs input):\s*(.+)$/i.exec(item);
243
+ return status ? `Still ${status[1].toLowerCase()}: ${status[2]}.` : `Still open: ${item}.`;
244
+ }
245
+
246
+ function lastSentence(last) {
247
+ if (!last) return '';
248
+ if (last.kind === 'thread') return `Last conversation here: “${last.title}”.`;
249
+ if (last.quoted) return `Last asked here: “${last.title}”.`;
250
+ return `Last session here: ${last.title.replace(/[.!?…]+$/, '')}.`;
251
+ }
252
+
253
+ /**
254
+ * The line used when the writer is not (no Claude login, a timeout, an unusable reply, a preparation
255
+ * that is out of date): at most two short sentences from the same facts, in English. What needs the
256
+ * person comes first; a sentence that does not fit is left out, never cut.
257
+ */
258
+ export function fallbackLine(facts, { maxChars = TEXT.maxChars, maxSentences = TEXT.maxSentences } = {}) {
259
+ if (!facts?.substantive) return '';
260
+ const candidates = []; // { order, priority, text }
261
+ const problems = facts.runs.filter((run) => PROBLEMS.has(run.status));
262
+ const news = [];
263
+ if (problems.length === 1) news.push(`“${problems[0].title}” ${STATUS_WORDS[problems[0].status]}`);
264
+ else if (facts.runCounts.problems > 1) news.push(`${plural(facts.runCounts.problems, 'run')} need a look`);
265
+ for (const schedule of facts.schedules.slice(0, 1)) news.push(`the schedule “${schedule.name}” did not complete`);
266
+ if (facts.schedules.length > 1) news.push(`${plural(facts.schedules.length - 1, 'other schedule')} did not complete`);
267
+ if (news.length) candidates.push({ order: 2, priority: 0, text: `While you were away: ${news.join('; ')}.` });
268
+
269
+ const open = facts.open.filter((item) => !item.cut);
270
+ if (open.length) candidates.push({ order: 1, priority: 1, text: openSentence(open[0].text) });
271
+ if (facts.last) candidates.push({ order: 0, priority: 3, text: lastSentence(facts.last) });
272
+
273
+ if (!news.length && facts.runCounts.done + facts.runCounts.running > 0) {
274
+ const bits = [];
275
+ if (facts.runCounts.done) bits.push(`${plural(facts.runCounts.done, 'run')} finished`);
276
+ if (facts.runCounts.running) bits.push(`${plural(facts.runCounts.running, 'run')} still going`);
277
+ // What happened while they were away is the news; where they left off is the context for it.
278
+ candidates.push({ order: 2, priority: 2, text: `While you were away: ${bits.join(', ')}.` });
279
+ }
280
+ if (facts.elsewhere.length) {
281
+ const item = facts.elsewhere[0];
282
+ const what = [item.runs ? plural(item.runs, 'failed run') : null, item.schedules ? plural(item.schedules, 'schedule that did not complete', 'schedules that did not complete') : null].filter(Boolean).join(' and ');
283
+ candidates.push({ order: 3, priority: 4, text: `Elsewhere: ${what} in ${item.label}.` });
284
+ }
285
+ if (facts.git?.known && facts.git.dirty > 0) candidates.push({ order: 4, priority: 5, text: `${plural(facts.git.dirty, 'uncommitted file')} on ${facts.git.branch}.` });
286
+
287
+ const picked = [];
288
+ for (const candidate of [...candidates].sort((a, b) => a.priority - b.priority)) {
289
+ if (picked.length >= maxSentences) break;
290
+ const length = [...picked, candidate].reduce((total, item) => total + item.text.length + 1, -1);
291
+ if (length > maxChars) continue;
292
+ picked.push(candidate);
293
+ }
294
+ return picked.sort((a, b) => a.order - b.order).map((item) => item.text).join(' ');
295
+ }
296
+
297
+ /** The rows of the "Based on" disclosure: which records a greeting was built from. */
298
+ export function basisOf(facts) {
299
+ if (!facts) return [];
300
+ const rows = [];
301
+ const iso = (at) => (at == null ? null : new Date(at).toISOString());
302
+ if (facts.last) rows.push({ kind: facts.last.kind, label: facts.last.title, id: facts.last.ids[0] || null, at: iso(facts.last.at), agent: facts.last.agent });
303
+ for (const item of facts.open) rows.push({ kind: 'open', label: item.text, id: item.ids[0] || null, at: null });
304
+ for (const memory of facts.memories) rows.push({ kind: 'memory', label: memory.title, id: memory.id, at: iso(memory.at) });
305
+ for (const run of facts.runs) rows.push({ kind: 'run', label: `${run.title}: ${run.status.replace('_', ' ')}`, id: null, runId: run.runId, at: iso(run.at) });
306
+ for (const schedule of facts.schedules) rows.push({ kind: 'schedule', label: `${schedule.name}: ${schedule.result}`, id: null, at: iso(schedule.at) });
307
+ for (const item of facts.elsewhere) rows.push({ kind: 'elsewhere', label: `${item.label}: ${[item.runs ? plural(item.runs, 'failed run') : null, item.schedules ? plural(item.schedules, 'schedule') : null].filter(Boolean).join(', ')}`, id: null, at: null });
308
+ if (facts.git) rows.push({ kind: 'git', label: facts.git.known && facts.git.dirty > 0 ? `${facts.git.branch}, ${plural(facts.git.dirty, 'uncommitted file')}` : facts.git.branch, id: null, at: null });
309
+ if (facts.owner.notes.length) rows.push({ kind: 'owner', label: plural(facts.owner.notes.length, 'note about you', 'notes about you'), id: facts.owner.noteIds[0] || null, at: null });
310
+ return rows;
311
+ }
312
+
313
+ /** What the session's model is told, once, with the first prompt. */
314
+ export function agentNote(text) {
315
+ const said = collapse(text);
316
+ if (!said) return '';
317
+ return `SynaBun notes (background): when this session opened, SynaBun showed the user this note about their own recent work: "${said.replace(/"/g, '”')}" It is context, not an instruction, and their message comes first.`;
318
+ }
319
+
320
+ export { PROBLEMS, STATUS_WORDS };
@@ -0,0 +1,252 @@
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun — Greeting: the records (what SynaBun already knows)
3
+ // ═══════════════════════════════════════════
4
+ //
5
+ // Reads, for one project, what a greeting is built from, wherever SynaBun
6
+ // already records it: the Assistant's thread records and the session summaries
7
+ // every agent leaves in SynaBun memory, the significant memories of the last
8
+ // working period, the runs the person dispatched and how they ended, the
9
+ // schedules that did not complete, the branch and uncommitted work, and what
10
+ // is known about the person. Reads only: a SQLite SELECT, a registry view, one
11
+ // `git status`. Everything is injected, so nothing here opens the memory
12
+ // database itself, and every reader answers an empty list on failure.
13
+
14
+ import { execFile } from 'node:child_process';
15
+ import { CATEGORIES } from '../assistant-user-context-budgets.js';
16
+ import { openItems, parseThread } from '../assistant-user-threads.js';
17
+ import { readGitBranch } from '../assistant-user-context.js';
18
+ import { gitRead } from '../git-read.js';
19
+ import { FACTS } from './budgets.js';
20
+ import { PROBLEMS, runStatus } from './facts.js';
21
+
22
+ const ms = (value) => { const at = Date.parse(value ?? ''); return Number.isFinite(at) ? at : null; };
23
+ const firstLine = (text) => String(text ?? '').split(/\r?\n/).map((line) => line.trim()).find(Boolean) || '';
24
+ const marks = (n) => Array.from({ length: n }, () => '?').join(',');
25
+ const same = (a, b) => String(a ?? '').toLowerCase() === String(b ?? '').toLowerCase();
26
+
27
+ /**
28
+ * A session summary memory ("## Session: …" with a "### Summary" block) → { title, summary, open[], sessionId } | null.
29
+ * `open`: the bullets under an "Open" / "Still owed" / "Follow-ups" / "Next steps" line, when the summary has one.
30
+ */
31
+ export function parseSessionSummary(content) {
32
+ const text = String(content ?? '');
33
+ const head = /^#{1,3}\s*Session\s*:\s*(.+)$/im.exec(text);
34
+ if (!head) return null;
35
+ const title = head[1].replace(/\s*\(\d{4}-\d{2}-\d{2}\)\s*$/, '').replace(/[*_`]+/g, '').trim();
36
+ if (!title || /^\{.*\}$/.test(title)) return null;
37
+ const block = /^#{2,4}\s*Summary\s*$\n([\s\S]*?)(?=^\s*#{2,4}\s|\s*$(?![\s\S]))/im.exec(text);
38
+ const summary = block ? block[1].replace(/\s+/g, ' ').replace(/[*_`]+/g, '').trim() : '';
39
+ const session = /\*\*Session ID\*\*\s*:\s*([A-Za-z0-9_-]{6,})/.exec(text);
40
+ const owed = /^(?:#{2,4}\s*)?(?:open(?:\s+(?:at|items?)\b[^\n:]*)?|still\s+(?:open|owed)[^\n:]*|follow[- ]?ups?|next\s+steps?|pending)\s*:?\s*$\n((?:[ \t]*[-*][ \t]+.+(?:\n|$))+)/im.exec(text);
41
+ const open = owed ? owed[1].split('\n').map((line) => line.replace(/^[ \t]*[-*][ \t]+/, '').replace(/\s+/g, ' ').replace(/[*_`]+/g, '').trim()).filter(Boolean).slice(0, 4) : [];
42
+ return { title, summary, open, sessionId: session ? session[1] : null };
43
+ }
44
+
45
+ /**
46
+ * `git status --porcelain` → the number of changed and untracked paths, or null (not a repository, too slow).
47
+ * Run as lib/git-read.js runs a read: nothing the project's own git configuration names is started for it.
48
+ */
49
+ export function gitDirtyCount(cwd, { timeoutMs = 1500, run = execFile } = {}) {
50
+ return new Promise((done) => {
51
+ if (!cwd) return done(null);
52
+ gitRead(cwd, ['status', '--porcelain']).then((read) => {
53
+ run('git', read.args, { cwd, env: read.env, timeout: timeoutMs, maxBuffer: 4 * 1024 * 1024, windowsHide: true }, (error, stdout) => {
54
+ if (error) return done(null);
55
+ done(String(stdout || '').split('\n').filter((line) => line.trim()).length);
56
+ });
57
+ }).catch(() => done(null));
58
+ });
59
+ }
60
+
61
+ /**
62
+ * @param {object} deps
63
+ * @param {() => object} deps.getDb node:sqlite handle of the memory database
64
+ * @param {() => object[]} [deps.listRuns] the dispatcher's run views (assistant-dispatch.js list())
65
+ * @param {() => object[]} [deps.listSchedules] loop schedules ({ id, name, enabled, lastRun, lastRunResult })
66
+ * @param {() => {path:string,label:string}[]} [deps.projects] the project registry
67
+ * @param {(cwd:string) => string} [deps.detectProject] a directory's memory project
68
+ * @param {() => string[]} [deps.sessionCategories] where session summaries are filed ("conversations" and its children)
69
+ * @param {(project:string) => {id:string,statement:string}[]} [deps.knowledge] what is known about the person, ranked
70
+ * @param {() => object[]} [deps.workingThreads] the user-context engine's working threads (newer than their memory)
71
+ * @param {(cwd:string) => Promise<number|null>} [deps.gitDirty]
72
+ */
73
+ export function createGreetingSources({
74
+ getDb, listRuns = () => [], listSchedules = () => [], projects = () => [], detectProject = () => 'global',
75
+ sessionCategories = () => ['conversations'], knowledge = () => [], workingThreads = () => [],
76
+ gitDirty = gitDirtyCount, readBranch = readGitBranch, log = () => {},
77
+ } = {}) {
78
+ const safe = (fn, fallback) => { try { const value = fn(); return value == null ? fallback : value; } catch (error) { log('greeting:source-error', error?.message || String(error)); return fallback; } };
79
+ const normalize = (label) => String(label || '').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
80
+
81
+ /** The registry entry of a project key: { key, label, path } (label and path null when unregistered). */
82
+ function projectInfo(project) {
83
+ const key = String(project || 'global');
84
+ for (const entry of safe(() => projects(), [])) {
85
+ if (!entry?.path) continue;
86
+ const detected = safe(() => detectProject(entry.path), null);
87
+ if (same(detected, key) || same(normalize(entry.label), key)) return { key, label: String(entry.label || key), path: String(entry.path) };
88
+ }
89
+ return { key, label: key, path: null };
90
+ }
91
+ function projectOf(cwd) {
92
+ const key = cwd ? safe(() => detectProject(String(cwd)), 'global') : 'global';
93
+ return String(key || 'global');
94
+ }
95
+
96
+ function threadRows(project) {
97
+ const out = [];
98
+ const seen = new Set();
99
+ // A working thread is newer than its own memory (which is written 45 s after a turn).
100
+ for (const thread of safe(() => workingThreads(), [])) {
101
+ if (!thread?.sessionId || !(Number(thread.prompts) > 0) || !same(thread.project || 'global', project)) continue;
102
+ seen.add(String(thread.sessionId));
103
+ out.push({
104
+ memoryId: thread.memoryId || null, sessionId: String(thread.sessionId), title: thread.title || 'Untitled', endedAt: thread.lastAt || thread.startedAt,
105
+ asks: (thread.asks || []).map((ask) => ask?.text || '').filter(Boolean),
106
+ open: safe(() => openItems(thread).map((item) => item.text), []),
107
+ });
108
+ }
109
+ const rows = safe(() => getDb().prepare(`SELECT id, content, project, updated_at FROM memories
110
+ WHERE trashed_at IS NULL AND category = ? AND lower(project) = lower(?) ORDER BY updated_at DESC LIMIT ?`).all(CATEGORIES.threads, project, FACTS.threads + 2), []);
111
+ for (const row of rows) {
112
+ const parsed = safe(() => parseThread(row.content, { updatedAt: row.updated_at }), null);
113
+ if (!parsed || !(parsed.prompts > 0) || (parsed.sessionId && seen.has(parsed.sessionId))) continue;
114
+ out.push({ memoryId: String(row.id), sessionId: parsed.sessionId, title: parsed.title, endedAt: parsed.endedAt || row.updated_at, asks: parsed.asks, open: parsed.open });
115
+ }
116
+ return out.sort((a, b) => (ms(b.endedAt) ?? 0) - (ms(a.endedAt) ?? 0)).slice(0, FACTS.threads);
117
+ }
118
+
119
+ function summaryRows(project) {
120
+ const categories = [...new Set(safe(() => sessionCategories(), ['conversations']).map(String).filter((name) => name && name !== CATEGORIES.threads))];
121
+ if (!categories.length) return [];
122
+ const rows = safe(() => getDb().prepare(`SELECT id, content, created_at FROM memories
123
+ WHERE trashed_at IS NULL AND category IN (${marks(categories.length)}) AND lower(project) = lower(?) ORDER BY created_at DESC LIMIT ?`).all(...categories, project, FACTS.sessions * 3), []);
124
+ const out = [];
125
+ for (const row of rows) {
126
+ const parsed = parseSessionSummary(row.content);
127
+ if (!parsed) continue;
128
+ out.push({ id: String(row.id), sessionId: parsed.sessionId, title: parsed.title, summary: parsed.summary, open: parsed.open, at: row.created_at, agent: null, source: 'summary' });
129
+ if (out.length >= FACTS.sessions) break;
130
+ }
131
+ return out;
132
+ }
133
+
134
+ /** Significant memories written around the last working period (not the knowledge or thread records themselves). */
135
+ function memoryRows(project, { from, to, exclude = [] }) {
136
+ if (from == null) return [];
137
+ const skip = [...new Set([CATEGORIES.threads, CATEGORIES.knowledge, CATEGORIES.knowledgeParent, ...exclude])];
138
+ const rows = safe(() => getDb().prepare(`SELECT id, content, category, importance, created_at FROM memories
139
+ WHERE trashed_at IS NULL AND lower(project) = lower(?) AND importance >= ? AND created_at >= ? AND created_at <= ?
140
+ AND category NOT IN (${marks(skip.length)}) ORDER BY importance DESC, created_at DESC LIMIT ?`)
141
+ .all(project, FACTS.memoryImportance, new Date(from).toISOString(), new Date(to).toISOString(), ...skip, FACTS.memories), []);
142
+ return rows.map((row) => ({ id: String(row.id), title: firstLine(row.content).replace(/^#+\s*/, ''), excerpt: String(row.content || '').replace(/^#+\s*/gm, ''), category: row.category, importance: row.importance, at: row.created_at }));
143
+ }
144
+
145
+ function runRows(project, { since, now }) {
146
+ const floor = Math.max(since ?? 0, now - FACTS.newsWindowMs);
147
+ const here = [];
148
+ for (const run of safe(() => listRuns(), [])) {
149
+ if (!run?.runId || run.removed || !same(run.project || 'global', project)) continue;
150
+ const finished = ms(run.finishedAt || run.completedAt);
151
+ if (run.active === true) { if ((ms(run.createdAt) ?? 0) >= floor) here.push(run); continue; }
152
+ if (finished != null && finished > floor && finished <= now) here.push(run);
153
+ }
154
+ return here.map((run) => ({ runId: run.runId, title: run.title || run.task || 'Untitled run', state: run.state, outcome: run.outcome, active: run.active === true, finishedAt: run.finishedAt || run.completedAt || null, provider: run.provider || null }));
155
+ }
156
+
157
+ /** Failures in other projects since the person was last anywhere: counts per project, never a detail. */
158
+ function elsewhereRows(project, { globalSince, now, told = new Set() }) {
159
+ const floor = Math.max(globalSince ?? 0, now - FACTS.newsWindowMs);
160
+ const byProject = new Map();
161
+ const keys = [];
162
+ for (const run of safe(() => listRuns(), [])) {
163
+ if (!run?.runId || run.removed || run.active === true || same(run.project || 'global', project)) continue;
164
+ const finished = ms(run.finishedAt || run.completedAt);
165
+ if (finished == null || finished <= floor || finished > now) continue;
166
+ const status = runStatus(run);
167
+ if (status !== 'failed' && status !== 'interrupted' && !PROBLEMS.has(status)) continue;
168
+ const key = `run:${run.runId}`;
169
+ if (told.has(key)) continue;
170
+ const name = String(run.project || 'global');
171
+ const entry = byProject.get(name.toLowerCase()) || { project: name, label: projectInfo(name).label, runs: 0, schedules: 0 };
172
+ entry.runs += 1;
173
+ byProject.set(name.toLowerCase(), entry);
174
+ keys.push(key);
175
+ }
176
+ return { rows: [...byProject.values()].sort((a, b) => b.runs - a.runs), keys };
177
+ }
178
+
179
+ /** Enabled schedules whose last run, since the person was last anywhere, did not complete. */
180
+ function scheduleRows({ globalSince, now, told = new Set() }) {
181
+ const floor = Math.max(globalSince ?? 0, now - FACTS.newsWindowMs);
182
+ const rows = [];
183
+ for (const schedule of safe(() => listSchedules(), [])) {
184
+ if (!schedule?.id || schedule.enabled === false) continue;
185
+ const at = ms(schedule.lastRun);
186
+ const result = String(schedule.lastRunResult ?? '').trim();
187
+ if (at == null || at <= floor || at > now || !result || result === 'completed' || result === 'launched') continue;
188
+ const key = `schedule:${schedule.id}:${schedule.lastRun}`;
189
+ if (told.has(key)) continue;
190
+ rows.push({ id: String(schedule.id), name: schedule.name || String(schedule.id), result: firstLine(result), at: schedule.lastRun, key });
191
+ }
192
+ return rows.sort((a, b) => (ms(b.at) ?? 0) - (ms(a.at) ?? 0));
193
+ }
194
+
195
+ /**
196
+ * Everything facts.js needs for one project.
197
+ * `since`: the person's last message in the project (ms) or null; `globalSince`: anywhere.
198
+ * `activity`: the engine's own record of their last prompts per surface ([{ surface, at, words }]).
199
+ * → the raw input of buildFacts, plus `toldKeys` (the unscoped news in it, said once).
200
+ */
201
+ async function collect(project, { since = null, globalSince = null, now = Date.now(), activity = [], told = new Set(), gitTimeoutMs = 1500 } = {}) {
202
+ const info = projectInfo(project);
203
+ const threads = threadRows(info.key);
204
+ const sessions = summaryRows(info.key);
205
+ for (const entry of Array.isArray(activity) ? activity : []) {
206
+ // The person's own last words on a surface that leaves no thread record of its own.
207
+ if (!entry?.words || entry.surface === 'assistant' || ms(entry.at) == null) continue;
208
+ sessions.push({ id: null, sessionId: `${entry.surface}:${entry.at}`, title: entry.words, summary: '', at: entry.at, agent: entry.agent || null, source: 'capture' });
209
+ }
210
+ const lastAt = Math.max(...[...threads.map((thread) => ms(thread.endedAt)), ...sessions.map((session) => ms(session.at))].filter((at) => at != null), 0) || null;
211
+ const anchor = since ?? lastAt;
212
+ const memories = anchor == null ? [] : memoryRows(info.key, { from: anchor - FACTS.memoryWindowMs, to: now, exclude: safe(() => sessionCategories(), []) });
213
+ const runs = anchor == null ? [] : runRows(info.key, { since: anchor, now });
214
+ const schedules = scheduleRows({ globalSince: globalSince ?? anchor, now, told });
215
+ const elsewhere = elsewhereRows(info.key, { globalSince: globalSince ?? anchor, now, told });
216
+ let git = null;
217
+ if (info.path) {
218
+ const branch = safe(() => readBranch(info.path), null);
219
+ // dirty null: `git status` gave no answer in time, so nothing is claimed about changes.
220
+ if (branch) git = { branch, dirty: await Promise.resolve(gitDirty(info.path, { timeoutMs: gitTimeoutMs })).catch(() => null) };
221
+ }
222
+ const known = safe(() => knowledge(info.key), []).filter((row) => row?.statement).slice(0, FACTS.ownerNotes);
223
+ const words = threads[0]?.asks?.length ? threads[0].asks.slice(-FACTS.ownerWords) : sessions.filter((session) => session.source === 'capture').map((session) => session.title).slice(0, FACTS.ownerWords);
224
+ return {
225
+ project: info.key, label: info.label, now, since: since ?? null,
226
+ threads, sessions, memories, runs,
227
+ schedules: schedules.map(({ key: _key, ...rest }) => rest),
228
+ elsewhere: elsewhere.rows,
229
+ git,
230
+ owner: { notes: known.map((row) => row.statement), noteIds: known.map((row) => String(row.id)), words },
231
+ toldKeys: [...schedules.map((row) => row.key), ...elsewhere.keys],
232
+ lastAt,
233
+ };
234
+ }
235
+
236
+ /** When the person last worked in a project, from the records alone (an engine with no activity of its own yet). */
237
+ function lastWorkAt(project) {
238
+ const info = projectInfo(project);
239
+ const times = [...threadRows(info.key).map((thread) => ms(thread.endedAt)), ...summaryRows(info.key).map((session) => ms(session.at))].filter((at) => at != null);
240
+ return times.length ? Math.max(...times) : null;
241
+ }
242
+
243
+ /** The projects with the newest thread records and session summaries (boot: what to prepare first). */
244
+ function recentProjects({ limit = 6 } = {}) {
245
+ const categories = [...new Set([CATEGORIES.threads, ...safe(() => sessionCategories(), ['conversations'])])];
246
+ const rows = safe(() => getDb().prepare(`SELECT lower(project) AS project, MAX(updated_at) AS at FROM memories
247
+ WHERE trashed_at IS NULL AND category IN (${marks(categories.length)}) GROUP BY lower(project) ORDER BY at DESC LIMIT ?`).all(...categories, limit), []);
248
+ return rows.map((row) => String(row.project || 'global')).filter(Boolean);
249
+ }
250
+
251
+ return { projectInfo, projectOf, collect, lastWorkAt, recentProjects };
252
+ }