synabun 2.0.0 → 2.0.7

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 (507) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/NOTICE +5 -0
  3. package/README.md +2 -1
  4. package/THIRD-PARTY-LICENSES.md +59 -0
  5. package/benchmarks/fixtures/browser/README.md +1 -1
  6. package/benchmarks/fixtures/desktop/README.md +1 -1
  7. package/docs/whatsapp.md +153 -18
  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 +26 -3
  12. package/hooks/claude-code/stop.mjs +5 -5
  13. package/lib/child-path.js +303 -0
  14. package/lib/claude-hooks.js +2 -1
  15. package/lib/data-home-migration.js +2 -0
  16. package/lib/desktop-app.js +265 -0
  17. package/lib/external-tools.js +7 -3
  18. package/lib/log-redact.js +102 -0
  19. package/lib/packaged-runtime.js +138 -11
  20. package/lib/start-launcher.js +88 -1
  21. package/mcp-server/dist/http.d.ts +3 -1
  22. package/mcp-server/dist/http.d.ts.map +1 -1
  23. package/mcp-server/dist/http.js +15 -4
  24. package/mcp-server/dist/http.js.map +1 -1
  25. package/mcp-server/dist/index.d.ts.map +1 -1
  26. package/mcp-server/dist/index.js +9 -6
  27. package/mcp-server/dist/index.js.map +1 -1
  28. package/mcp-server/dist/services/budget-judgments.d.ts +108 -0
  29. package/mcp-server/dist/services/budget-judgments.d.ts.map +1 -0
  30. package/mcp-server/dist/services/budget-judgments.js +153 -0
  31. package/mcp-server/dist/services/budget-judgments.js.map +1 -0
  32. package/mcp-server/dist/services/codex-config-heal.d.ts +5 -1
  33. package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
  34. package/mcp-server/dist/services/codex-config-heal.js +41 -4
  35. package/mcp-server/dist/services/codex-config-heal.js.map +1 -1
  36. package/mcp-server/dist/services/identity.d.ts +11 -0
  37. package/mcp-server/dist/services/identity.d.ts.map +1 -1
  38. package/mcp-server/dist/services/identity.js +24 -0
  39. package/mcp-server/dist/services/identity.js.map +1 -1
  40. package/mcp-server/dist/services/neural-interface.d.ts +13 -0
  41. package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
  42. package/mcp-server/dist/services/neural-interface.js +34 -2
  43. package/mcp-server/dist/services/neural-interface.js.map +1 -1
  44. package/mcp-server/dist/services/typesafe-config.d.ts +1 -1
  45. package/mcp-server/dist/services/typesafe-config.d.ts.map +1 -1
  46. package/mcp-server/dist/services/typesafe-config.js +4 -1
  47. package/mcp-server/dist/services/typesafe-config.js.map +1 -1
  48. package/mcp-server/dist/tools/agents.d.ts +10 -7
  49. package/mcp-server/dist/tools/agents.d.ts.map +1 -1
  50. package/mcp-server/dist/tools/agents.js +34 -13
  51. package/mcp-server/dist/tools/agents.js.map +1 -1
  52. package/mcp-server/dist/tools/whatsapp-send-file.d.ts +17 -0
  53. package/mcp-server/dist/tools/whatsapp-send-file.d.ts.map +1 -0
  54. package/mcp-server/dist/tools/whatsapp-send-file.js +44 -0
  55. package/mcp-server/dist/tools/whatsapp-send-file.js.map +1 -0
  56. package/mcp-server/dist/tui.js +2 -1
  57. package/mcp-server/dist/tui.js.map +1 -1
  58. package/mcp-server/dist/version.d.ts +5 -0
  59. package/mcp-server/dist/version.d.ts.map +1 -0
  60. package/mcp-server/dist/version.js +21 -0
  61. package/mcp-server/dist/version.js.map +1 -0
  62. package/mcp-server/package-lock.json +2 -2
  63. package/mcp-server/package.json +1 -3
  64. package/mcp-server/run.mjs +15 -9
  65. package/mcp-server/src/http.ts +18 -3
  66. package/mcp-server/src/index.ts +9 -6
  67. package/mcp-server/src/services/budget-judgments.ts +183 -0
  68. package/mcp-server/src/services/codex-config-heal.ts +35 -4
  69. package/mcp-server/src/services/identity.ts +27 -0
  70. package/mcp-server/src/services/neural-interface.ts +34 -2
  71. package/mcp-server/src/services/typesafe-config.ts +4 -1
  72. package/mcp-server/src/tools/agents.ts +33 -14
  73. package/mcp-server/src/tools/whatsapp-send-file.ts +51 -0
  74. package/mcp-server/src/tui.ts +2 -1
  75. package/mcp-server/src/version.ts +22 -0
  76. package/neural-interface/i18n/en.json +2126 -209
  77. package/neural-interface/i18n/pt-BR.json +2123 -207
  78. package/neural-interface/lib/account-usage.js +163 -0
  79. package/neural-interface/lib/assistant-api.js +316 -57
  80. package/neural-interface/lib/assistant-brains/claude.js +19 -2
  81. package/neural-interface/lib/assistant-brains/codex.js +3 -0
  82. package/neural-interface/lib/assistant-brains/opencode.js +17 -4
  83. package/neural-interface/lib/assistant-budget-planner.js +390 -0
  84. package/neural-interface/lib/assistant-budget-split.js +408 -0
  85. package/neural-interface/lib/assistant-budget.js +94 -95
  86. package/neural-interface/lib/assistant-catalog.js +179 -27
  87. package/neural-interface/lib/assistant-config.js +91 -14
  88. package/neural-interface/lib/assistant-dispatch.js +494 -138
  89. package/neural-interface/lib/assistant-hidden-models.js +393 -66
  90. package/neural-interface/lib/assistant-jev.js +26 -3
  91. package/neural-interface/lib/assistant-memory.js +24 -10
  92. package/neural-interface/lib/assistant-persona.js +73 -12
  93. package/neural-interface/lib/assistant-plan-permissions.js +8 -1
  94. package/neural-interface/lib/assistant-pricing.js +21 -6
  95. package/neural-interface/lib/assistant-route-accounts.js +14 -0
  96. package/neural-interface/lib/assistant-route-gate.js +19 -3
  97. package/neural-interface/lib/assistant-router.js +373 -42
  98. package/neural-interface/lib/assistant-runtime.js +488 -59
  99. package/neural-interface/lib/assistant-task-prompt.js +13 -3
  100. package/neural-interface/lib/assistant-test-density.js +142 -0
  101. package/neural-interface/lib/assistant-user-context-budgets.js +123 -0
  102. package/neural-interface/lib/assistant-user-context.js +908 -0
  103. package/neural-interface/lib/assistant-user-knowledge.js +328 -0
  104. package/neural-interface/lib/assistant-user-learn.js +404 -0
  105. package/neural-interface/lib/assistant-user-threads.js +226 -0
  106. package/neural-interface/lib/assistant-user-writer.js +183 -0
  107. package/neural-interface/lib/augmented-path.js +317 -53
  108. package/neural-interface/lib/browser-background-tab.js +134 -0
  109. package/neural-interface/lib/browser-screenshot.js +678 -61
  110. package/neural-interface/lib/claude-accounts.js +342 -31
  111. package/neural-interface/lib/claude-agent-bridge.js +123 -9
  112. package/neural-interface/lib/claude-executable.js +6 -1
  113. package/neural-interface/lib/claude-model-catalog.js +33 -5
  114. package/neural-interface/lib/claude-panel-auth.js +221 -0
  115. package/neural-interface/lib/claude-profile-api-key.js +62 -0
  116. package/neural-interface/lib/claude-temporary.js +46 -7
  117. package/neural-interface/lib/cli-install-api.js +137 -0
  118. package/neural-interface/lib/cli-install.js +797 -0
  119. package/neural-interface/lib/cli-path.js +130 -0
  120. package/neural-interface/lib/cli-resolver.js +744 -0
  121. package/neural-interface/lib/cli-update-planner.js +143 -7
  122. package/neural-interface/lib/cloudflared/install.js +533 -0
  123. package/neural-interface/lib/cloudflared/manager.js +545 -0
  124. package/neural-interface/lib/cloudflared/pins.js +115 -0
  125. package/neural-interface/lib/cloudflared/pins.json +63 -0
  126. package/neural-interface/lib/cloudflared/tunnel.js +265 -0
  127. package/neural-interface/lib/codex-account-auth.js +178 -0
  128. package/neural-interface/lib/codex-accounts.js +489 -0
  129. package/neural-interface/lib/codex-capabilities.js +2 -1
  130. package/neural-interface/lib/codex-compat-rpc.js +8 -2
  131. package/neural-interface/lib/codex-mcp-approvals.js +5 -1
  132. package/neural-interface/lib/codex-panel-auth.js +62 -0
  133. package/neural-interface/lib/codex-session-settings.js +1 -1
  134. package/neural-interface/lib/codex-settings-schema.js +120 -0
  135. package/neural-interface/lib/desktop/config.js +3 -0
  136. package/neural-interface/lib/editor-completion-endpoint.js +264 -0
  137. package/neural-interface/lib/editor-completion-opencode.js +436 -0
  138. package/neural-interface/lib/editor-completion.js +714 -0
  139. package/neural-interface/lib/editor-config.js +149 -0
  140. package/neural-interface/lib/editor-files-api.js +268 -0
  141. package/neural-interface/lib/editor-git-api.js +134 -0
  142. package/neural-interface/lib/file-explorer-api.js +1039 -0
  143. package/neural-interface/lib/file-explorer-system.js +355 -0
  144. package/neural-interface/lib/file-explorer-trash.ps1 +85 -0
  145. package/neural-interface/lib/file-explorer-watch.js +239 -0
  146. package/neural-interface/lib/git-bash/bzip2.js +339 -0
  147. package/neural-interface/lib/git-bash/claude-settings.js +73 -0
  148. package/neural-interface/lib/git-bash/extract-worker.js +34 -0
  149. package/neural-interface/lib/git-bash/install.js +529 -0
  150. package/neural-interface/lib/git-bash/licenses/COPYING +360 -0
  151. package/neural-interface/lib/git-bash/licenses/LGPL-2.1 +511 -0
  152. package/neural-interface/lib/git-bash/manager.js +268 -0
  153. package/neural-interface/lib/git-bash/pins.js +206 -0
  154. package/neural-interface/lib/git-bash/pins.json +34 -0
  155. package/neural-interface/lib/git-bash/resolver.js +194 -0
  156. package/neural-interface/lib/git-diff-api.js +103 -0
  157. package/neural-interface/lib/git-read.js +234 -0
  158. package/neural-interface/lib/greeting/api.js +89 -0
  159. package/neural-interface/lib/greeting/budgets.js +90 -0
  160. package/neural-interface/lib/greeting/engine.js +461 -0
  161. package/neural-interface/lib/greeting/facts.js +320 -0
  162. package/neural-interface/lib/greeting/sources.js +252 -0
  163. package/neural-interface/lib/greeting/writer.js +139 -0
  164. package/neural-interface/lib/http-guards.js +189 -13
  165. package/neural-interface/lib/install-details.js +145 -0
  166. package/neural-interface/lib/machine-names.js +88 -0
  167. package/neural-interface/lib/mcp-installer.js +14 -2
  168. package/neural-interface/lib/memory-api.js +31 -7
  169. package/neural-interface/lib/native-loop-providers.js +26 -8
  170. package/neural-interface/lib/native-loop-runtime.js +9 -4
  171. package/neural-interface/lib/os-path.js +319 -0
  172. package/neural-interface/lib/plugin-installer.js +4 -2
  173. package/neural-interface/lib/project-storage.js +5 -7
  174. package/neural-interface/lib/provider-panel-api-key-auth.js +92 -0
  175. package/neural-interface/lib/provider-panel-terminal-auth.js +95 -0
  176. package/neural-interface/lib/pty-host/core.js +2 -1
  177. package/neural-interface/lib/pty-host/manager.js +27 -2
  178. package/neural-interface/lib/remote-policy.js +20 -11
  179. package/neural-interface/lib/request-gate.js +589 -0
  180. package/neural-interface/lib/session-title-generator.js +4 -3
  181. package/neural-interface/lib/system-about.js +340 -0
  182. package/neural-interface/lib/system-activity.js +66 -0
  183. package/neural-interface/lib/terminal-env.js +11 -3
  184. package/neural-interface/lib/terminal-path.js +55 -0
  185. package/neural-interface/lib/ui-blob-api.js +108 -0
  186. package/neural-interface/lib/ui-blob-store.js +68 -0
  187. package/neural-interface/lib/virtual-socket.js +12 -1
  188. package/neural-interface/lib/whatsapp/baileys-adapter.js +165 -45
  189. package/neural-interface/lib/whatsapp/bridge.js +78 -34
  190. package/neural-interface/lib/whatsapp/commands.js +13 -14
  191. package/neural-interface/lib/whatsapp/config.js +134 -20
  192. package/neural-interface/lib/whatsapp/fake-baileys.js +99 -14
  193. package/neural-interface/lib/whatsapp/host-core.js +168 -35
  194. package/neural-interface/lib/whatsapp/inbound.js +5 -3
  195. package/neural-interface/lib/whatsapp/local-file.js +524 -0
  196. package/neural-interface/lib/whatsapp/manager.js +92 -32
  197. package/neural-interface/lib/whatsapp/paths.js +11 -1
  198. package/neural-interface/lib/whatsapp/protocol.js +65 -5
  199. package/neural-interface/lib/whatsapp/redact.js +63 -0
  200. package/neural-interface/lib/whatsapp/service.js +343 -91
  201. package/neural-interface/lib/whatsapp/transcription.js +463 -0
  202. package/neural-interface/package-lock.json +701 -2
  203. package/neural-interface/package.json +13 -2
  204. package/neural-interface/public/index.html +2 -0
  205. package/neural-interface/public/index2d.html +2 -0
  206. package/neural-interface/public/install-app.html +9 -6
  207. package/neural-interface/public/offline.html +19 -3
  208. package/neural-interface/public/onboarding.html +107 -2
  209. package/neural-interface/public/shared/account-i18n.js +7 -0
  210. package/neural-interface/public/shared/account-menu.js +539 -0
  211. package/neural-interface/public/shared/account-usage.js +19 -0
  212. package/neural-interface/public/shared/account-view.js +335 -0
  213. package/neural-interface/public/shared/agent-runtime-options.js +50 -14
  214. package/neural-interface/public/shared/assistant/asst-accounts.js +182 -184
  215. package/neural-interface/public/shared/assistant/asst-api.js +32 -2
  216. package/neural-interface/public/shared/assistant/asst-automation.js +286 -0
  217. package/neural-interface/public/shared/assistant/asst-brain-picker.js +430 -81
  218. package/neural-interface/public/shared/assistant/asst-budget.js +259 -303
  219. package/neural-interface/public/shared/assistant/asst-composer.js +11 -1
  220. package/neural-interface/public/shared/assistant/asst-mascot.js +140 -61
  221. package/neural-interface/public/shared/assistant/asst-menu.js +27 -11
  222. package/neural-interface/public/shared/assistant/asst-panel.js +198 -42
  223. package/neural-interface/public/shared/assistant/asst-render.js +197 -23
  224. package/neural-interface/public/shared/assistant/asst-route-models.js +108 -0
  225. package/neural-interface/public/shared/assistant/asst-route.js +95 -343
  226. package/neural-interface/public/shared/assistant/asst-routes-editor.js +534 -0
  227. package/neural-interface/public/shared/assistant/asst-sanitize.js +8 -1
  228. package/neural-interface/public/shared/assistant/asst-sidepanel.js +31 -9
  229. package/neural-interface/public/shared/assistant/asst-slash.js +1 -1
  230. package/neural-interface/public/shared/assistant/asst-state.js +186 -2
  231. package/neural-interface/public/shared/assistant/asst-styles.js +247 -11
  232. package/neural-interface/public/shared/blob-store.js +111 -22
  233. package/neural-interface/public/shared/cdx/cdx-commands.js +4 -1
  234. package/neural-interface/public/shared/cdx/cdx-features.js +22 -14
  235. package/neural-interface/public/shared/cdx/cdx-icons.js +2 -0
  236. package/neural-interface/public/shared/cdx/cdx-panel.js +80 -118
  237. package/neural-interface/public/shared/cdx/cdx-render.js +25 -89
  238. package/neural-interface/public/shared/cdx/cdx-settings.js +231 -0
  239. package/neural-interface/public/shared/cdx/cdx-styles.js +164 -107
  240. package/neural-interface/public/shared/cdx/cdx-tabs.js +348 -668
  241. package/neural-interface/public/shared/cdx/cdx-ws.js +2 -0
  242. package/neural-interface/public/shared/claude-account-view.js +3 -0
  243. package/neural-interface/public/shared/cli-install-view.js +392 -0
  244. package/neural-interface/public/shared/cli-status.js +282 -22
  245. package/neural-interface/public/shared/constants.js +6 -1
  246. package/neural-interface/public/shared/cp/cp-events.js +2 -1
  247. package/neural-interface/public/shared/cp/cp-rehydrate.js +2 -1
  248. package/neural-interface/public/shared/cp/cp-styles.js +61 -0
  249. package/neural-interface/public/shared/desktop-bridge.js +39 -0
  250. package/neural-interface/public/shared/desktop-titlebar-page.js +59 -0
  251. package/neural-interface/public/shared/desktop-titlebar-view.js +122 -0
  252. package/neural-interface/public/shared/desktop-titlebar.js +153 -0
  253. package/neural-interface/public/shared/desktop-update-view.js +462 -0
  254. package/neural-interface/public/shared/editor/editor-ai-logic.js +409 -0
  255. package/neural-interface/public/shared/editor/editor-ai.js +619 -0
  256. package/neural-interface/public/shared/editor/editor-commands.js +212 -0
  257. package/neural-interface/public/shared/editor/editor-compare.js +103 -0
  258. package/neural-interface/public/shared/editor/editor-completion-list.js +172 -0
  259. package/neural-interface/public/shared/editor/editor-doc.js +257 -0
  260. package/neural-interface/public/shared/editor/editor-git-logic.js +131 -0
  261. package/neural-interface/public/shared/editor/editor-git.js +95 -0
  262. package/neural-interface/public/shared/editor/editor-languages.js +156 -0
  263. package/neural-interface/public/shared/editor/editor-lint.js +229 -0
  264. package/neural-interface/public/shared/editor/editor-loader.js +51 -0
  265. package/neural-interface/public/shared/editor/editor-markdown.js +90 -0
  266. package/neural-interface/public/shared/editor/editor-menu.js +233 -0
  267. package/neural-interface/public/shared/editor/editor-minimap.js +371 -0
  268. package/neural-interface/public/shared/editor/editor-palette-logic.js +192 -0
  269. package/neural-interface/public/shared/editor/editor-palette.js +125 -0
  270. package/neural-interface/public/shared/editor/editor-panel-logic.js +288 -0
  271. package/neural-interface/public/shared/editor/editor-panel.js +2099 -0
  272. package/neural-interface/public/shared/editor/editor-prefs.js +224 -0
  273. package/neural-interface/public/shared/editor/editor-safety.js +500 -0
  274. package/neural-interface/public/shared/editor/editor-search.js +352 -0
  275. package/neural-interface/public/shared/editor/editor-statusbar.js +144 -0
  276. package/neural-interface/public/shared/editor/editor-tabs-logic.js +331 -0
  277. package/neural-interface/public/shared/editor/editor-tabs.js +233 -0
  278. package/neural-interface/public/shared/editor/editor-theme.js +244 -0
  279. package/neural-interface/public/shared/editor/editor-view.js +332 -0
  280. package/neural-interface/public/shared/explorer/fx-api.js +216 -0
  281. package/neural-interface/public/shared/explorer/fx-links.js +204 -0
  282. package/neural-interface/public/shared/explorer/fx-logic.js +537 -0
  283. package/neural-interface/public/shared/explorer/fx-main.js +865 -0
  284. package/neural-interface/public/shared/explorer/fx-names.js +87 -0
  285. package/neural-interface/public/shared/explorer/fx-ops.js +370 -0
  286. package/neural-interface/public/shared/explorer/fx-popups.js +490 -0
  287. package/neural-interface/public/shared/explorer/fx-tree.js +790 -0
  288. package/neural-interface/public/shared/file-links.js +339 -0
  289. package/neural-interface/public/shared/git-bash-view.js +145 -0
  290. package/neural-interface/public/shared/greeting/greeting-card.js +178 -0
  291. package/neural-interface/public/shared/greeting/greeting-view.js +148 -0
  292. package/neural-interface/public/shared/html-shell.js +161 -165
  293. package/neural-interface/public/shared/i18n.js +19 -1
  294. package/neural-interface/public/shared/install-details-view.js +86 -0
  295. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +4 -0
  296. package/neural-interface/public/shared/ocp-v2/ocp-v2-greeting.js +47 -0
  297. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +131 -17
  298. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +7 -1
  299. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +22 -0
  300. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +158 -0
  301. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +3 -2
  302. package/neural-interface/public/shared/pwa-install-page.js +32 -2
  303. package/neural-interface/public/shared/pwa-install.css +3 -3
  304. package/neural-interface/public/shared/pwa-install.js +100 -1
  305. package/neural-interface/public/shared/settings/settings-about-pane.js +271 -0
  306. package/neural-interface/public/shared/settings/settings-about.js +254 -0
  307. package/neural-interface/public/shared/settings/settings-code-editor-pane.js +412 -0
  308. package/neural-interface/public/shared/settings/settings-code-editor.js +328 -0
  309. package/neural-interface/public/shared/settings/settings-greeting.js +133 -0
  310. package/neural-interface/public/shared/settings/settings-ia.js +33 -4
  311. package/neural-interface/public/shared/settings/settings-user-context.js +154 -0
  312. package/neural-interface/public/shared/showcase-privacy.js +489 -0
  313. package/neural-interface/public/shared/start-bridge.js +238 -19
  314. package/neural-interface/public/shared/storage.js +30 -3
  315. package/neural-interface/public/shared/styles.css +1710 -770
  316. package/neural-interface/public/shared/synabun-mascot.js +68 -58
  317. package/neural-interface/public/shared/tunnel-view.js +167 -0
  318. package/neural-interface/public/shared/ui-claude-panel.js +369 -229
  319. package/neural-interface/public/shared/ui-cli-banner.js +355 -0
  320. package/neural-interface/public/shared/ui-detail.js +15 -0
  321. package/neural-interface/public/shared/ui-explorer.js +22 -0
  322. package/neural-interface/public/shared/ui-file-explorer.js +481 -2496
  323. package/neural-interface/public/shared/ui-file-links.js +103 -0
  324. package/neural-interface/public/shared/ui-git-bash.js +124 -0
  325. package/neural-interface/public/shared/ui-help.js +26 -0
  326. package/neural-interface/public/shared/ui-invite.js +126 -116
  327. package/neural-interface/public/shared/ui-judgments.js +10 -1
  328. package/neural-interface/public/shared/ui-keybinds.js +4 -0
  329. package/neural-interface/public/shared/ui-loading.js +187 -19
  330. package/neural-interface/public/shared/ui-menubar.js +25 -33
  331. package/neural-interface/public/shared/ui-native-loop-router-state.js +11 -0
  332. package/neural-interface/public/shared/ui-native-loop-router.js +15 -2
  333. package/neural-interface/public/shared/ui-navbar.js +48 -22
  334. package/neural-interface/public/shared/ui-provider-auth.js +104 -0
  335. package/neural-interface/public/shared/ui-provider-terminal-auth.js +223 -0
  336. package/neural-interface/public/shared/ui-sessions.js +9 -3
  337. package/neural-interface/public/shared/ui-settings.js +509 -490
  338. package/neural-interface/public/shared/ui-sidepanel-runtime.js +2 -1
  339. package/neural-interface/public/shared/ui-sidepanel-sessions.js +3 -1
  340. package/neural-interface/public/shared/ui-sync.js +22 -0
  341. package/neural-interface/public/shared/ui-terminal.js +40 -5
  342. package/neural-interface/public/shared/ui-tool-updates.js +5 -0
  343. package/neural-interface/public/shared/ui-tunnel.js +130 -0
  344. package/neural-interface/public/shared/ui-tutorial-steps.js +6 -2
  345. package/neural-interface/public/shared/ui-update.js +594 -5
  346. package/neural-interface/public/shared/ui-whatsapp.js +21 -6
  347. package/neural-interface/public/shared/ui-whiteboard.js +12 -0
  348. package/neural-interface/public/shared/ui-worker-end.js +245 -0
  349. package/neural-interface/public/shared/ui-workspaces.js +41 -27
  350. package/neural-interface/public/shared/whatsapp/wa-view.js +51 -8
  351. package/neural-interface/public/shared/worker-end.js +253 -0
  352. package/neural-interface/public/sw.js +2 -2
  353. package/neural-interface/public/variant/2d/main.js +10 -1
  354. package/neural-interface/public/variant/3d/main.js +10 -1
  355. package/neural-interface/public/vendor/codemirror/chunks/apl-CSDKT72Z.js +1 -0
  356. package/neural-interface/public/vendor/codemirror/chunks/asciiarmor-JYYPU5BM.js +1 -0
  357. package/neural-interface/public/vendor/codemirror/chunks/asn1-NRWCZDA6.js +1 -0
  358. package/neural-interface/public/vendor/codemirror/chunks/asterisk-5OU6D4ZQ.js +1 -0
  359. package/neural-interface/public/vendor/codemirror/chunks/brainfuck-ID4N4SYC.js +1 -0
  360. package/neural-interface/public/vendor/codemirror/chunks/chunk-4RZWGCS2.js +1 -0
  361. package/neural-interface/public/vendor/codemirror/chunks/chunk-ACTJM3JR.js +11 -0
  362. package/neural-interface/public/vendor/codemirror/chunks/chunk-S6BMAXLK.js +1 -0
  363. package/neural-interface/public/vendor/codemirror/chunks/chunk-TO2RDOHQ.js +3 -0
  364. package/neural-interface/public/vendor/codemirror/chunks/chunk-US2GMZZQ.js +13 -0
  365. package/neural-interface/public/vendor/codemirror/chunks/chunk-WYYO5ZAJ.js +1 -0
  366. package/neural-interface/public/vendor/codemirror/chunks/chunk-XALZTDJC.js +1 -0
  367. package/neural-interface/public/vendor/codemirror/chunks/chunk-YRNUTRDV.js +1 -0
  368. package/neural-interface/public/vendor/codemirror/chunks/chunk-Z3QOKH62.js +1 -0
  369. package/neural-interface/public/vendor/codemirror/chunks/clike-OWQBSOGL.js +1 -0
  370. package/neural-interface/public/vendor/codemirror/chunks/clojure-KV75UQWC.js +1 -0
  371. package/neural-interface/public/vendor/codemirror/chunks/cmake-GTEP5Y3C.js +1 -0
  372. package/neural-interface/public/vendor/codemirror/chunks/cobol-WCZ7QEGJ.js +1 -0
  373. package/neural-interface/public/vendor/codemirror/chunks/coffeescript-TGLBOT6B.js +1 -0
  374. package/neural-interface/public/vendor/codemirror/chunks/commonlisp-WVVI432V.js +1 -0
  375. package/neural-interface/public/vendor/codemirror/chunks/crystal-S6R6HLKQ.js +1 -0
  376. package/neural-interface/public/vendor/codemirror/chunks/css-PDA2RINP.js +1 -0
  377. package/neural-interface/public/vendor/codemirror/chunks/cypher-HL5QPL4C.js +1 -0
  378. package/neural-interface/public/vendor/codemirror/chunks/d-HHJH2HAJ.js +1 -0
  379. package/neural-interface/public/vendor/codemirror/chunks/diff-MN3A35BY.js +1 -0
  380. package/neural-interface/public/vendor/codemirror/chunks/dist-25SQLBVS.js +3 -0
  381. package/neural-interface/public/vendor/codemirror/chunks/dist-27ZGGJJS.js +1 -0
  382. package/neural-interface/public/vendor/codemirror/chunks/dist-4E52OJIF.js +1 -0
  383. package/neural-interface/public/vendor/codemirror/chunks/dist-6FT73XDC.js +2 -0
  384. package/neural-interface/public/vendor/codemirror/chunks/dist-BE6YGRMW.js +1 -0
  385. package/neural-interface/public/vendor/codemirror/chunks/dist-CJSGKW2O.js +1 -0
  386. package/neural-interface/public/vendor/codemirror/chunks/dist-CRPH2WLI.js +1 -0
  387. package/neural-interface/public/vendor/codemirror/chunks/dist-CTG7JEQN.js +1 -0
  388. package/neural-interface/public/vendor/codemirror/chunks/dist-I3FEVELX.js +1 -0
  389. package/neural-interface/public/vendor/codemirror/chunks/dist-IM5BBSWZ.js +1 -0
  390. package/neural-interface/public/vendor/codemirror/chunks/dist-KERUKTIF.js +1 -0
  391. package/neural-interface/public/vendor/codemirror/chunks/dist-LXTMWZAM.js +1 -0
  392. package/neural-interface/public/vendor/codemirror/chunks/dist-MABASSET.js +1 -0
  393. package/neural-interface/public/vendor/codemirror/chunks/dist-MJVBUZLL.js +3 -0
  394. package/neural-interface/public/vendor/codemirror/chunks/dist-MNB3WZVU.js +1 -0
  395. package/neural-interface/public/vendor/codemirror/chunks/dist-QYWXOE3U.js +7 -0
  396. package/neural-interface/public/vendor/codemirror/chunks/dist-TEBOFWLZ.js +1 -0
  397. package/neural-interface/public/vendor/codemirror/chunks/dist-UNXFROM3.js +1 -0
  398. package/neural-interface/public/vendor/codemirror/chunks/dist-XL2SLQO6.js +1 -0
  399. package/neural-interface/public/vendor/codemirror/chunks/dist-XO42Y3AP.js +6 -0
  400. package/neural-interface/public/vendor/codemirror/chunks/dist-Y2WWDWJK.js +1 -0
  401. package/neural-interface/public/vendor/codemirror/chunks/dist-YFEZKWMU.js +1 -0
  402. package/neural-interface/public/vendor/codemirror/chunks/dist-ZY7SWQO6.js +1 -0
  403. package/neural-interface/public/vendor/codemirror/chunks/dockerfile-3Y4R4NCH.js +1 -0
  404. package/neural-interface/public/vendor/codemirror/chunks/dtd-5343GJ4P.js +1 -0
  405. package/neural-interface/public/vendor/codemirror/chunks/dylan-FI2CXSW2.js +1 -0
  406. package/neural-interface/public/vendor/codemirror/chunks/ebnf-GNQ77YWA.js +1 -0
  407. package/neural-interface/public/vendor/codemirror/chunks/ecl-MRPUAE2L.js +1 -0
  408. package/neural-interface/public/vendor/codemirror/chunks/eiffel-DZROES27.js +1 -0
  409. package/neural-interface/public/vendor/codemirror/chunks/elm-4KICRFKQ.js +1 -0
  410. package/neural-interface/public/vendor/codemirror/chunks/erlang-3IHYC263.js +1 -0
  411. package/neural-interface/public/vendor/codemirror/chunks/factor-C6D3E5UK.js +1 -0
  412. package/neural-interface/public/vendor/codemirror/chunks/fcl-VYTWUJMI.js +1 -0
  413. package/neural-interface/public/vendor/codemirror/chunks/forth-ZBO2EH5M.js +1 -0
  414. package/neural-interface/public/vendor/codemirror/chunks/fortran-5NDCRWIK.js +1 -0
  415. package/neural-interface/public/vendor/codemirror/chunks/gas-SKRMOXFP.js +1 -0
  416. package/neural-interface/public/vendor/codemirror/chunks/gherkin-L45YXZLW.js +1 -0
  417. package/neural-interface/public/vendor/codemirror/chunks/groovy-GLPTLN44.js +1 -0
  418. package/neural-interface/public/vendor/codemirror/chunks/haskell-E7NF7P4Y.js +1 -0
  419. package/neural-interface/public/vendor/codemirror/chunks/haxe-K2NVDOB6.js +1 -0
  420. package/neural-interface/public/vendor/codemirror/chunks/http-Q6DRQIJB.js +1 -0
  421. package/neural-interface/public/vendor/codemirror/chunks/idl-MZYNJVLN.js +1 -0
  422. package/neural-interface/public/vendor/codemirror/chunks/javascript-6YRYK2HR.js +1 -0
  423. package/neural-interface/public/vendor/codemirror/chunks/julia-HXEL2T5G.js +1 -0
  424. package/neural-interface/public/vendor/codemirror/chunks/livescript-MV7CI2JZ.js +1 -0
  425. package/neural-interface/public/vendor/codemirror/chunks/lua-E36NHOOD.js +1 -0
  426. package/neural-interface/public/vendor/codemirror/chunks/mathematica-3XOZZCMC.js +1 -0
  427. package/neural-interface/public/vendor/codemirror/chunks/mbox-ODYGFKS7.js +1 -0
  428. package/neural-interface/public/vendor/codemirror/chunks/mirc-WB43AWF5.js +1 -0
  429. package/neural-interface/public/vendor/codemirror/chunks/mllike-ZT6ET7ZH.js +1 -0
  430. package/neural-interface/public/vendor/codemirror/chunks/modelica-LLMZPFGA.js +1 -0
  431. package/neural-interface/public/vendor/codemirror/chunks/mscgen-2OPNNYFH.js +1 -0
  432. package/neural-interface/public/vendor/codemirror/chunks/mumps-6P3HWJH6.js +1 -0
  433. package/neural-interface/public/vendor/codemirror/chunks/nginx-TCGKQTRW.js +1 -0
  434. package/neural-interface/public/vendor/codemirror/chunks/nsis-ATIGIXTJ.js +1 -0
  435. package/neural-interface/public/vendor/codemirror/chunks/ntriples-5PLBI5OV.js +1 -0
  436. package/neural-interface/public/vendor/codemirror/chunks/octave-QF2S3NL6.js +1 -0
  437. package/neural-interface/public/vendor/codemirror/chunks/oz-JFIVFFH2.js +1 -0
  438. package/neural-interface/public/vendor/codemirror/chunks/pascal-JG6SW76V.js +1 -0
  439. package/neural-interface/public/vendor/codemirror/chunks/perl-4B3IWVVO.js +1 -0
  440. package/neural-interface/public/vendor/codemirror/chunks/pig-ABEBH4MI.js +1 -0
  441. package/neural-interface/public/vendor/codemirror/chunks/powershell-H32XE7TL.js +1 -0
  442. package/neural-interface/public/vendor/codemirror/chunks/properties-NN644MMA.js +1 -0
  443. package/neural-interface/public/vendor/codemirror/chunks/protobuf-YKWQK2J3.js +1 -0
  444. package/neural-interface/public/vendor/codemirror/chunks/pug-RIYXEKFW.js +1 -0
  445. package/neural-interface/public/vendor/codemirror/chunks/puppet-JZQ5WPEV.js +1 -0
  446. package/neural-interface/public/vendor/codemirror/chunks/python-EBCGGHAC.js +1 -0
  447. package/neural-interface/public/vendor/codemirror/chunks/q-5UMH2YQY.js +1 -0
  448. package/neural-interface/public/vendor/codemirror/chunks/r-7XMGG453.js +1 -0
  449. package/neural-interface/public/vendor/codemirror/chunks/rpm-IEV7TSLR.js +1 -0
  450. package/neural-interface/public/vendor/codemirror/chunks/ruby-T4SPFPNM.js +1 -0
  451. package/neural-interface/public/vendor/codemirror/chunks/sas-GEDX67ET.js +1 -0
  452. package/neural-interface/public/vendor/codemirror/chunks/scheme-FTLOWKPC.js +1 -0
  453. package/neural-interface/public/vendor/codemirror/chunks/shell-HUFRKFSJ.js +1 -0
  454. package/neural-interface/public/vendor/codemirror/chunks/sieve-P5QUDVYO.js +1 -0
  455. package/neural-interface/public/vendor/codemirror/chunks/smalltalk-2SORJNQI.js +1 -0
  456. package/neural-interface/public/vendor/codemirror/chunks/solr-M22TGAR5.js +1 -0
  457. package/neural-interface/public/vendor/codemirror/chunks/sparql-4M3KL752.js +1 -0
  458. package/neural-interface/public/vendor/codemirror/chunks/spreadsheet-3K23LA4N.js +1 -0
  459. package/neural-interface/public/vendor/codemirror/chunks/sql-VHRFMUBI.js +1 -0
  460. package/neural-interface/public/vendor/codemirror/chunks/stex-QZDAQUY2.js +1 -0
  461. package/neural-interface/public/vendor/codemirror/chunks/stylus-7ZDKAKBD.js +1 -0
  462. package/neural-interface/public/vendor/codemirror/chunks/swift-WCLCLGMR.js +1 -0
  463. package/neural-interface/public/vendor/codemirror/chunks/tcl-JLMU6WOK.js +1 -0
  464. package/neural-interface/public/vendor/codemirror/chunks/textile-4CHDKZ4T.js +1 -0
  465. package/neural-interface/public/vendor/codemirror/chunks/tiddlywiki-DKMCZZQ4.js +1 -0
  466. package/neural-interface/public/vendor/codemirror/chunks/tiki-F4JIG36Q.js +1 -0
  467. package/neural-interface/public/vendor/codemirror/chunks/toml-BHMHTIM2.js +1 -0
  468. package/neural-interface/public/vendor/codemirror/chunks/troff-YQ2KV5NT.js +1 -0
  469. package/neural-interface/public/vendor/codemirror/chunks/ttcn-ZHRCVFJY.js +1 -0
  470. package/neural-interface/public/vendor/codemirror/chunks/ttcn-cfg-KJOVTY4X.js +1 -0
  471. package/neural-interface/public/vendor/codemirror/chunks/turtle-535P5N6I.js +1 -0
  472. package/neural-interface/public/vendor/codemirror/chunks/vb-76UMQHOT.js +1 -0
  473. package/neural-interface/public/vendor/codemirror/chunks/vbscript-CGNDTK6Z.js +1 -0
  474. package/neural-interface/public/vendor/codemirror/chunks/velocity-VSJDO5T5.js +1 -0
  475. package/neural-interface/public/vendor/codemirror/chunks/verilog-4T6ETXIP.js +1 -0
  476. package/neural-interface/public/vendor/codemirror/chunks/vhdl-T7FNAXUN.js +1 -0
  477. package/neural-interface/public/vendor/codemirror/chunks/webidl-P6MJSJ6J.js +1 -0
  478. package/neural-interface/public/vendor/codemirror/chunks/xquery-UOVILRGN.js +1 -0
  479. package/neural-interface/public/vendor/codemirror/chunks/yacas-CV2APROL.js +1 -0
  480. package/neural-interface/public/vendor/codemirror/chunks/z80-3KFNVM4M.js +1 -0
  481. package/neural-interface/public/vendor/codemirror/codemirror-6.43.14-75f05e67.min.js +2 -0
  482. package/neural-interface/public/vendor/codemirror/manifest.json +60 -0
  483. package/neural-interface/scripts/build-vendor.mjs +78 -2
  484. package/neural-interface/scripts/vendor-codemirror-entry.js +121 -0
  485. package/neural-interface/server.js +2085 -1629
  486. package/neural-interface/templates/rulesets/README.md +1 -1
  487. package/package.json +5 -8
  488. package/setup.js +53 -15
  489. package/benchmarks/browser-live-results.json +0 -79
  490. package/benchmarks/browser-v2-results.json +0 -258
  491. package/neural-interface/lib/codex-capabilities.d.ts +0 -41
  492. package/neural-interface/public/category-logos/claude.svg +0 -1
  493. package/neural-interface/public/category-logos/gemini-color.svg +0 -1
  494. package/neural-interface/public/category-logos/openai.svg +0 -1
  495. package/neural-interface/public/category-logos/synabun.png +0 -0
  496. package/neural-interface/public/claude.svg +0 -1
  497. package/neural-interface/public/gallery.svg +0 -6
  498. package/neural-interface/public/leonardoai.svg +0 -1
  499. package/neural-interface/public/mcp.svg +0 -4
  500. package/neural-interface/public/opencode-logo-dark-square.svg +0 -1
  501. package/neural-interface/public/pin-svgrepo-com.svg +0 -25
  502. package/neural-interface/public/sessionicon.svg +0 -8
  503. package/neural-interface/public/toolbar-album.svg +0 -8
  504. package/neural-interface/public/toolbar-question.svg +0 -6
  505. package/neural-interface/public/toolbar-sessions.svg +0 -6
  506. package/neural-interface/scripts/codex-protocol-fixture.mjs +0 -83
  507. package/skills/synabun/synabunicon.png +0 -0
@@ -12,6 +12,7 @@
12
12
  // send/stop) · file input. The other runtime controls live in the toolbar.
13
13
 
14
14
  import { matchSlashCommands, SLASH_COMMANDS } from './asst-slash.js';
15
+ import { isLongPaste, typedText } from './asst-automation.js';
15
16
 
16
17
  const TEXT_EXTENSIONS = new Set([
17
18
  'txt', 'md', 'js', 'ts', 'jsx', 'tsx', 'json', 'html', 'css', 'scss', 'less', 'xml', 'svg',
@@ -151,6 +152,7 @@ export function createComposer(hostEl, hooks = {}) {
151
152
  files: [], // { name, content }
152
153
  uploads: [], // { name, size, mime, path, status: 'uploading'|'done', progress, controller }
153
154
  memories: [], // node objects
155
+ pastes: [], // long text pastes still to be told apart from typed words (asst-automation.js typedText)
154
156
  slashOpen: false,
155
157
  slashIndex: 0,
156
158
  slashItems: [],
@@ -321,6 +323,9 @@ export function createComposer(hostEl, hooks = {}) {
321
323
  if (!text && !state.images.length && !state.files.length && !state.uploads.length && !state.memories.length) return;
322
324
  const payload = {
323
325
  text,
326
+ // What the user typed, without the long pastes: all SynaBun may learn about them from (the prompt keeps everything).
327
+ typed: typedText(text, state.pastes),
328
+ pastes: state.pastes.slice(),
324
329
  images: state.images.slice(),
325
330
  files: state.files.slice(),
326
331
  uploads: state.uploads.map(({ name, size, mime, path }) => ({ name, size, mime, path })),
@@ -334,6 +339,7 @@ export function createComposer(hostEl, hooks = {}) {
334
339
  state.files = [];
335
340
  state.uploads = [];
336
341
  state.memories = [];
342
+ state.pastes = [];
337
343
  renderAttachments();
338
344
  closeSlash();
339
345
  autoResize();
@@ -409,9 +415,12 @@ export function createComposer(hostEl, hooks = {}) {
409
415
  return Number.isFinite(x) && Number.isFinite(y) ? { x, y } : null;
410
416
  }
411
417
 
412
- // ── Paste (images) ──
418
+ // ── Paste (images; long text is remembered as pasted, not typed) ──
413
419
  textarea.addEventListener('paste', (e) => {
414
420
  if (!e.clipboardData) return;
421
+ let pasted = '';
422
+ try { pasted = e.clipboardData.getData?.('text/plain') || ''; } catch { pasted = ''; }
423
+ if (isLongPaste(pasted)) state.pastes = [...state.pastes, pasted].slice(-20);
415
424
  for (const item of e.clipboardData.items) {
416
425
  if (item.type.startsWith('image/')) {
417
426
  const blob = item.getAsFile();
@@ -596,6 +605,7 @@ export function createComposer(hostEl, hooks = {}) {
596
605
  state.files.push(...(payload.files || []));
597
606
  state.uploads.push(...(payload.uploads || []).map((u) => ({ ...u, status: 'done', progress: 1 })));
598
607
  state.memories.push(...(payload.memories || []));
608
+ state.pastes = [...state.pastes, ...(payload.pastes || [])].slice(-20);
599
609
  renderAttachments();
600
610
  autoResize();
601
611
  updateSlash();
@@ -12,20 +12,25 @@
12
12
  // success, error once, when a turn that did real work ends (then idle)
13
13
  // watch while you type: eyes on the caret column, look() at most
14
14
  // every 120 ms; back 1.5 s after the last key, at once on send
15
+ // project/budget attention owned by this panel's empty-state chip/menu;
16
+ // commits react once; a menu prevents idle sleep
15
17
  // sleep the hero only, after 90 s with no input while nothing runs;
16
18
  // the rig wakes itself on a pointer move or a key
17
19
  //
18
20
  // Anything else is the renderer's pose: the hero idles, the "Thinking" row's
19
21
  // character thinks, the stage acts out its call. A pose the director sets is
20
22
  // held: the renderer leaves it alone until the director lets go and asks it
21
- // to restore its own. Only one panel animates its rigs: the visible one used
22
- // last (typing, focus, a new stage). Pure logic: the clock, timers and page
23
- // input come in through `env`, so node:test drives it with fakes
24
- // (tests/assistant-mascot.test.mjs).
23
+ // to restore its own. A director knows its own panel only: nothing in this
24
+ // module is shared between directors, so every open session dresses and
25
+ // animates its own character at the same time as every other one (a rig that
26
+ // is off screen parks itself: synabun-mascot.js). Pure logic: the clock,
27
+ // timers and page input come in through `env`, so node:test drives it with
28
+ // fakes (tests/assistant-mascot.test.mjs).
25
29
 
26
30
  export const MASCOT_TIMING = Object.freeze({
27
31
  watchEveryMs: 120,
28
32
  watchIdleMs: 1500,
33
+ controlDwellMs: 1500,
29
34
  sleepAfterMs: 90_000,
30
35
  realWorkMs: 3000,
31
36
  oneShotMs: Object.freeze({ success: 1000, error: 900 }),
@@ -60,32 +65,19 @@ export function lookToward(box, point, { horizontal = false } = {}) {
60
65
  return { x: unit((vx / d) * m), y: unit((vy / d) * m) };
61
66
  }
62
67
 
63
- // ── One panel animates: the visible one used last ──
64
- const directors = new Set();
65
- let lead = null;
66
- let seq = 0;
67
-
68
- function elect() {
69
- let best = null;
70
- for (const d of directors) if (d.visible() && (!best || d.activeAt() > best.activeAt())) best = d;
71
- if (!best) for (const d of directors) if (!best || d.activeAt() > best.activeAt()) best = d;
72
- if (best === lead) return;
73
- const prev = lead;
74
- lead = best;
75
- prev?.leadChanged();
76
- best?.leadChanged();
77
- }
78
-
79
68
  /**
80
- * createMascotDirector({ getTarget, caret, setEnabled, isVisible, env })
81
- * getTarget() → the renderer's visible mascot { kind: 'hero'|'stage', rig, el,
82
- * parked?(), hold(on), restore() } or null; caret() → the caret's client
83
- * point or null; setEnabled(on) → whether this panel's rigs animate;
84
- * isVisible() → the panel is on screen.
69
+ * createMascotDirector({ getTarget, caret, env })
70
+ * getTarget() → the renderer's visible mascot { kind: 'hero'|'stage'|'working',
71
+ * rig, el, parked?(), hold(on), restore() } or null; caret() → the caret's
72
+ * client point or null.
73
+ * Control API: attend(owner, { pose, rectOf, options, open }), leave(owner),
74
+ * controlCommitted({ pose, options, reaction }); type(present, pointOf?) also
75
+ * watches a budget amount field using the same throttling as the composer.
85
76
  * env: { now, setTimeout, clearTimeout, input(fn) → unsubscribe (page pointer
86
77
  * and key input), lastInputAt() }.
78
+ * All of its state is its own: one director per panel, any number at once.
87
79
  */
88
- export function createMascotDirector({ getTarget = () => null, caret = () => null, setEnabled = () => {}, isVisible = () => true, env = {} } = {}) {
80
+ export function createMascotDirector({ getTarget = () => null, caret = () => null, env = {} } = {}) {
89
81
  const now = env.now || (() => Date.now());
90
82
  const later = env.setTimeout || ((fn, ms) => setTimeout(fn, ms));
91
83
  const cancel = env.clearTimeout || ((id) => clearTimeout(id));
@@ -97,6 +89,9 @@ export function createMascotDirector({ getTarget = () => null, caret = () => nul
97
89
  let holding = false; // hold(true) is in force on `target`
98
90
  let shown = ''; // the pose (and then) set on `target`
99
91
  let typing = false;
92
+ let typingPoint = null;
93
+ let attention = null; // { owner, pose, rectOf, options, open }; this panel's empty-state control
94
+ let attentionTimer = 0;
100
95
  let typingTimer = 0;
101
96
  let lookTimer = 0;
102
97
  let lookedAt = -Infinity;
@@ -108,7 +103,6 @@ export function createMascotDirector({ getTarget = () => null, caret = () => nul
108
103
  let sleepTimer = 0;
109
104
  let lastInput = now();
110
105
  let stopInput = null;
111
- let activeAt = (seq += 1);
112
106
 
113
107
  const safe = (fn) => { try { return fn(); } catch { return undefined; } };
114
108
  const boxOf = (t) => safe(() => t.el?.getBoundingClientRect?.()) || null;
@@ -121,9 +115,16 @@ export function createMascotDirector({ getTarget = () => null, caret = () => nul
121
115
  const point = card && card.width ? { x: card.left + Math.min(card.width * 0.3, 160), y: card.top + Math.min(card.height / 2, 28) } : null;
122
116
  return { pose: 'blocked', look: lookToward(boxOf(t), point) };
123
117
  }
124
- if (oneShot && oneShot.target === t) return { pose: oneShot.pose, opts: { then: 'idle' } };
125
- if (typing) return { pose: 'watch', look: lookToward(boxOf(t), safe(caret), { horizontal: true }) };
118
+ const attentionLook = () => {
119
+ const rect = attention && safe(attention.rectOf);
120
+ return lookToward(boxOf(t), rect && { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 });
121
+ };
122
+ if (oneShot && oneShot.target === t) return { pose: oneShot.pose, opts: oneShot.opts || { then: 'idle' }, ...(attention ? { look: attentionLook() } : {}) };
123
+ if (typing) return { pose: 'watch', look: lookToward(boxOf(t), safe(typingPoint || caret), { horizontal: true }) };
126
124
  if (sleeping && t.kind === 'hero' && !safe(() => t.parked?.())) return { pose: 'sleep' };
125
+ if (attention && t.kind === 'hero' && !safe(() => t.parked?.())) {
126
+ return { pose: attention.pose, opts: safe(attention.options) || {}, look: attentionLook() };
127
+ }
127
128
  return null;
128
129
  }
129
130
 
@@ -151,7 +152,7 @@ export function createMascotDirector({ getTarget = () => null, caret = () => nul
151
152
  return;
152
153
  }
153
154
  if (!holding) { holding = true; safe(() => t.hold(true)); }
154
- const key = `${want.pose}|${want.opts?.then || ''}`;
155
+ const key = `${want.pose}|${want.opts?.then || ''}|${!!want.opts?.unlimited}`;
155
156
  if (key !== shown) { shown = key; safe(() => t.rig.setPose(want.pose, want.opts)); }
156
157
  if (want.look) safe(() => t.rig.look(want.look.x, want.look.y));
157
158
  }
@@ -170,7 +171,7 @@ export function createMascotDirector({ getTarget = () => null, caret = () => nul
170
171
 
171
172
  function canSleep() {
172
173
  const t = target;
173
- return !destroyed && !!t && t.kind === 'hero' && !safe(() => t.parked?.()) && !turnActive && !typing && !blocked && !offline && !oneShot;
174
+ return !destroyed && !!t && t.kind === 'hero' && !safe(() => t.parked?.()) && !turnActive && !typing && !attention?.open && !blocked && !offline && !oneShot;
174
175
  }
175
176
 
176
177
  function armSleep() {
@@ -192,18 +193,20 @@ export function createMascotDirector({ getTarget = () => null, caret = () => nul
192
193
  if (lookTimer) { cancel(lookTimer); lookTimer = 0; }
193
194
  if (!typing) return;
194
195
  typing = false;
196
+ typingPoint = null;
195
197
  apply();
196
198
  armSleep();
197
199
  }
198
200
 
199
201
  /** The draft changed: `present` false (an empty draft) ends the watch. */
200
- function type(present = true) {
202
+ function type(present = true, pointOf = null) {
201
203
  if (destroyed) return;
202
- touch();
203
204
  noteInput();
204
205
  if (!present) { stopTyping(); return; }
206
+ if (oneShot?.control) dropOneShot(); // a new edit resumes watching after an acknowledgement
205
207
  const first = !typing;
206
208
  typing = true;
209
+ typingPoint = typeof pointOf === 'function' ? pointOf : null;
207
210
  if (sleepTimer) { cancel(sleepTimer); sleepTimer = 0; }
208
211
  if (typingTimer) cancel(typingTimer);
209
212
  typingTimer = later(() => { typingTimer = 0; stopTyping(); }, MASCOT_TIMING.watchIdleMs);
@@ -237,27 +240,53 @@ export function createMascotDirector({ getTarget = () => null, caret = () => nul
237
240
  armSleep();
238
241
  }
239
242
 
240
- function touch() {
241
- activeAt = (seq += 1);
242
- elect();
243
+ function clearAttention() {
244
+ if (attentionTimer) { cancel(attentionTimer); attentionTimer = 0; }
245
+ attention = null;
243
246
  }
244
247
 
245
- const self = {
246
- visible: () => !destroyed && safe(isVisible) !== false,
247
- activeAt: () => activeAt,
248
- leadChanged() { if (!destroyed) safe(() => setEnabled(lead === self)); },
249
- };
250
- directors.add(self);
251
- touch();
252
- if (lead !== self) self.leadChanged();
253
-
254
248
  return {
255
249
  /** The draft changed (typing): the mascot watches the caret column. */
256
250
  type,
257
251
  /** Blur, or the draft went away. */
258
252
  stopTyping,
253
+ /** Chip previews relax after a short dwell; only open menus hold attention and block sleep. */
254
+ attend(owner, spec) {
255
+ if (destroyed || (attention?.open && attention.owner !== owner && !spec.open)) return;
256
+ noteInput();
257
+ clearAttention();
258
+ attention = { owner, ...spec };
259
+ if (!attention.open) attentionTimer = later(() => {
260
+ clearAttention();
261
+ apply();
262
+ armSleep();
263
+ }, MASCOT_TIMING.controlDwellMs);
264
+ apply();
265
+ armSleep();
266
+ },
267
+ leave(owner) {
268
+ if (attention?.owner !== owner) return;
269
+ clearAttention();
270
+ apply();
271
+ armSleep();
272
+ },
273
+ /** Only acknowledged user commits call this; loads, live updates and keystrokes do not. */
274
+ controlCommitted({ pose, options = {}, reaction = 'nod' }) {
275
+ if (destroyed) return;
276
+ const t = safe(getTarget);
277
+ if (!t || t.kind !== 'hero' || safe(() => t.parked?.()) || turnActive || offline || blocked) return;
278
+ stopTyping();
279
+ noteInput();
280
+ dropOneShot();
281
+ const shot = { pose, opts: options, target: t, timer: 0, control: true };
282
+ shot.timer = later(() => { if (oneShot !== shot) return; oneShot = null; apply(); armSleep(); }, 900);
283
+ oneShot = shot;
284
+ apply();
285
+ safe(() => t.rig.react(reaction));
286
+ armSleep();
287
+ },
259
288
  /** A prompt went out: the watch hands over at once (the renderer gives the hero to the "Thinking" row). */
260
- sent() { touch(); stopTyping(); },
289
+ sent() { clearAttention(); dropOneShot(); stopTyping(); apply(); armSleep(); },
261
290
  /** A card waits on the user (`rectOf()` → its box), or none (null). */
262
291
  setBlocked(rectOf) {
263
292
  blocked = typeof rectOf === 'function' ? rectOf : null;
@@ -278,6 +307,7 @@ export function createMascotDirector({ getTarget = () => null, caret = () => nul
278
307
  },
279
308
  turnStarted() {
280
309
  turnActive = true;
310
+ clearAttention();
281
311
  dropOneShot();
282
312
  if (sleeping) sleeping = false;
283
313
  apply();
@@ -285,29 +315,78 @@ export function createMascotDirector({ getTarget = () => null, caret = () => nul
285
315
  },
286
316
  /** The turn ended with `pose` ('success' | 'error' | null): played once on the rig on screen. */
287
317
  turnEnded,
288
- /** The renderer's visible mascot changed; a new stage makes this panel the one that animates. */
289
- retarget() {
290
- if (destroyed) return;
291
- const t = safe(getTarget) || null;
292
- if (t && t !== target && t.kind === 'stage') touch();
293
- apply();
294
- },
295
- /** The user is here (focus, a click): this panel animates. */
296
- touch() { touch(); noteInput(); },
297
- /** The host showed or hid the panel. */
298
- visibilityChanged() { elect(); },
299
- isLead: () => lead === self,
318
+ /** The renderer's visible mascot changed. */
319
+ retarget() { apply(); },
320
+ /** The user is here (focus, a click): a sleeping hero wakes, the quiet starts over. */
321
+ touch() { if (!destroyed) noteInput(); },
300
322
  /** For tests and devtools: what the director holds right now. */
301
- state: () => ({ target: target?.kind || null, pose: shown.split('|')[0] || null, typing, blocked: !!blocked, offline, sleeping, oneShot: oneShot?.pose || null, lead: lead === self }),
323
+ state: () => ({ target: target?.kind || null, pose: shown.split('|')[0] || null, typing, attention: attention?.pose || null, menuOpen: !!attention?.open, blocked: !!blocked, offline, sleeping, oneShot: oneShot?.pose || null }),
302
324
  destroy() {
303
325
  if (destroyed) return;
304
326
  if (target && holding) safe(() => target.hold(false));
305
327
  destroyed = true;
328
+ clearAttention();
306
329
  for (const id of [typingTimer, lookTimer, sleepTimer]) if (id) cancel(id);
307
330
  dropOneShot();
308
331
  if (stopInput) { safe(stopInput); stopInput = null; }
309
- directors.delete(self);
310
- if (lead === self) { lead = null; elect(); }
332
+ },
333
+ };
334
+ }
335
+
336
+ /** Wire one empty-state chip and its body-level menu to its panel's director. No rig or clock here. */
337
+ export function createMascotControl(anchor, { director = () => null, pose, options = () => ({}) } = {}) {
338
+ const owner = {};
339
+ let menu = null, preview = null, watched = false, destroyed = false;
340
+ const listeners = [];
341
+ const listen = (node, event, fn) => { node.addEventListener(event, fn); listeners.push(() => node.removeEventListener(event, fn)); };
342
+ function attend(node = anchor) {
343
+ if (destroyed) return;
344
+ preview = node;
345
+ director()?.attend(owner, { pose, options, open: !!menu, rectOf: () => node.getBoundingClientRect() });
346
+ }
347
+ const leave = () => { if (!menu) { preview = null; director()?.leave(owner); } };
348
+ listen(anchor, 'pointerenter', () => attend(menu || anchor));
349
+ listen(anchor, 'focus', () => attend(menu || anchor));
350
+ listen(anchor, 'pointerleave', () => { if (document.activeElement !== anchor) leave(); });
351
+ listen(anchor, 'blur', leave);
352
+ let stopMenu = () => {};
353
+ return {
354
+ open(node) {
355
+ if (destroyed || !node) return;
356
+ stopMenu();
357
+ menu = node;
358
+ const follow = e => {
359
+ const option = e.target.closest?.('.asst-dd-item, input, label, button');
360
+ if (option && node.contains(option)) attend(option);
361
+ };
362
+ const type = e => {
363
+ if (!e.target.matches?.('.asst-budget-input')) return;
364
+ watched = true;
365
+ director()?.type(true, () => {
366
+ const r = e.target.getBoundingClientRect();
367
+ return { x: r.left + Math.min(r.width - 12, 12 + e.target.value.length * 7), y: r.top + r.height / 2 };
368
+ });
369
+ };
370
+ node.addEventListener('pointerover', follow);
371
+ node.addEventListener('focusin', follow);
372
+ node.addEventListener('input', type);
373
+ stopMenu = () => {
374
+ node.removeEventListener('pointerover', follow);
375
+ node.removeEventListener('focusin', follow);
376
+ node.removeEventListener('input', type);
377
+ };
378
+ attend(node.contains(document.activeElement) ? document.activeElement : node);
379
+ },
380
+ close() {
381
+ stopMenu(); menu = null; preview = null;
382
+ if (watched) { director()?.stopTyping(); watched = false; }
383
+ director()?.leave(owner);
384
+ },
385
+ commit(reaction) { if (!destroyed) director()?.controlCommitted({ pose, options: options(), reaction }); },
386
+ refresh() { if (preview) attend(preview); },
387
+ destroy() {
388
+ this.close(); destroyed = true;
389
+ for (const off of listeners) off();
311
390
  },
312
391
  };
313
392
  }
@@ -21,6 +21,7 @@
21
21
  // info { label, value } → non-interactive row
22
22
  // tag { label } → non-interactive badge row
23
23
  // input { placeholder, value, onSubmit } → inline text field
24
+ // `restoreFocus` on an item or input sends focus back to the anchor once it is chosen.
24
25
  // Keys: ↑/↓ move, Home/End jump, Enter/Space activate, Esc closes (focus back
25
26
  // to the anchor), Tab closes. The optional filter hides non-matching items and
26
27
  // any group left empty.
@@ -72,6 +73,7 @@ export function closeMenu({ restoreFocus = false } = {}) {
72
73
  menu.remove();
73
74
  if (anchor?.isConnected) {
74
75
  if (anchor.getAttribute('aria-haspopup')) anchor.setAttribute('aria-expanded', 'false');
76
+ anchor.removeAttribute('aria-controls');
75
77
  if (restoreFocus) { try { anchor.focus({ preventScroll: true }); } catch { /* detached */ } }
76
78
  }
77
79
  try { onClose?.(); } catch { /* listener error */ }
@@ -115,7 +117,7 @@ function buildItems(list, items, { role, onActivate }) {
115
117
  input.value = spec.value || '';
116
118
  input.setAttribute('aria-label', spec.label || spec.placeholder || 'Value');
117
119
  input.dataset.submit = '1';
118
- input._asstSubmit = (value) => { closeMenu(); spec.onSubmit?.(value); };
120
+ input._asstSubmit = (value) => { closeMenu({ restoreFocus: !!spec.restoreFocus }); spec.onSubmit?.(value); };
119
121
  list.appendChild(input);
120
122
  continue;
121
123
  }
@@ -160,7 +162,7 @@ function buildItems(list, items, { role, onActivate }) {
160
162
  }
161
163
 
162
164
  /** Hide items that do not match `query`, then any header/separator left without items. */
163
- export function applyMenuFilter(list, query) {
165
+ export function applyMenuFilter(list, query, emptyLabel = 'No matches') {
164
166
  const q = String(query || '').trim().toLowerCase();
165
167
  const rows = [...list.children];
166
168
  for (const row of rows) {
@@ -192,7 +194,7 @@ export function applyMenuFilter(list, query) {
192
194
  let empty = list.querySelector('.asst-dd-empty.asst-dd-noresults');
193
195
  const hasItem = rows.some(r => !r.hidden && (r.classList.contains('asst-dd-item') || r.classList.contains('asst-dd-input')));
194
196
  if (q && !hasItem) {
195
- if (!empty) { empty = el('div', 'asst-dd-empty asst-dd-noresults', 'No matches'); list.appendChild(empty); }
197
+ if (!empty) { empty = el('div', 'asst-dd-empty asst-dd-noresults', emptyLabel); list.appendChild(empty); }
196
198
  empty.hidden = false;
197
199
  } else if (empty) empty.hidden = true;
198
200
  }
@@ -286,13 +288,15 @@ export function openMenu(anchor, opts = {}) {
286
288
  closeMenu({ restoreFocus: !!item.restoreFocus });
287
289
  item.onSelect?.(item, node);
288
290
  };
289
- buildItems(list, items, { role, onActivate: activate });
290
- if (width) menu.style.minWidth = `${width}px`;
291
+ if (opts.content) list.appendChild(opts.content);
292
+ else buildItems(list, items, { role, onActivate: activate });
293
+ if (width) menu.style.minWidth = opts.repositionOnResize ? `min(${width}px, calc(100vw - 16px))` : `${width}px`;
294
+ if (width && opts.repositionOnResize) menu.style.width = `min(${width}px, calc(100vw - 16px))`;
291
295
  document.body.appendChild(menu);
292
296
  positionMenu(menu, anchor, placement);
293
297
 
294
298
  if (filterInput) {
295
- filterInput.addEventListener('input', () => { applyMenuFilter(list, filterInput.value); positionMenu(menu, anchor, placement); });
299
+ filterInput.addEventListener('input', () => { applyMenuFilter(list, filterInput.value, opts.filterEmptyLabel); positionMenu(menu, anchor, placement); });
296
300
  }
297
301
 
298
302
  const onDocDown = (e) => {
@@ -308,6 +312,15 @@ export function openMenu(anchor, opts = {}) {
308
312
  const nodes = focusables(menu);
309
313
  const idx = nodes.indexOf(document.activeElement);
310
314
  if (e.key === 'Escape') { e.preventDefault(); e.stopPropagation(); closeMenu({ restoreFocus: true }); return; }
315
+ if (role === 'dialog') {
316
+ if (e.key === 'Tab') {
317
+ const fields = [...menu.querySelectorAll('button, input')].filter(node => !node.disabled && !node.hidden);
318
+ const index = fields.indexOf(document.activeElement);
319
+ if (e.shiftKey && index <= 0) { e.preventDefault(); fields.at(-1)?.focus(); }
320
+ else if (!e.shiftKey && (index < 0 || index === fields.length - 1)) { e.preventDefault(); fields[0]?.focus(); }
321
+ }
322
+ return; // native number/switch keys and Enter belong to the form
323
+ }
311
324
  if (e.key === 'Tab') { closeMenu(); return; }
312
325
  if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
313
326
  e.preventDefault(); e.stopPropagation();
@@ -341,21 +354,24 @@ export function openMenu(anchor, opts = {}) {
341
354
  // Keep single-letter global keybinds out of an open menu.
342
355
  e.stopPropagation();
343
356
  };
344
- const onResize = () => closeMenu();
357
+ const onResize = () => opts.repositionOnResize ? positionMenu(menu, anchor, placement) : closeMenu();
358
+ const onBlur = () => closeMenu();
345
359
  document.addEventListener('mousedown', onDocDown, true);
346
- document.addEventListener('keydown', onKey, true);
360
+ const keyTarget = role === 'dialog' ? window : document;
361
+ keyTarget.addEventListener('keydown', onKey, true);
347
362
  window.addEventListener('resize', onResize);
348
- window.addEventListener('blur', onResize);
363
+ window.addEventListener('blur', onBlur);
349
364
  const cleanup = () => {
350
365
  document.removeEventListener('mousedown', onDocDown, true);
351
- document.removeEventListener('keydown', onKey, true);
366
+ keyTarget.removeEventListener('keydown', onKey, true);
352
367
  window.removeEventListener('resize', onResize);
353
- window.removeEventListener('blur', onResize);
368
+ window.removeEventListener('blur', onBlur);
354
369
  };
355
370
 
356
371
  _open = { menu, anchor, onClose, cleanup };
357
372
  if (anchor.getAttribute('aria-haspopup')) anchor.setAttribute('aria-expanded', 'true');
358
373
  anchor.setAttribute('aria-controls', menu.id);
374
+ opts.onOpen?.(menu);
359
375
 
360
376
  const selected = list.querySelector('.asst-dd-item[data-checked="1"]:not([disabled])');
361
377
  const first = focusables(menu).find(n => n.classList.contains('asst-dd-item')) || focusables(menu)[0];