synabun 2.0.10 → 2.1.0

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 (517) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +25 -2
  3. package/THIRD-PARTY-LICENSES.md +22 -0
  4. package/bin/synabun.js +71 -24
  5. package/docs/whatsapp.md +2 -2
  6. package/hooks/claude-code/pre-websearch.mjs +12 -1
  7. package/hooks/claude-code/shared.mjs +5 -0
  8. package/hooks/claude-code/stop.mjs +1 -1
  9. package/lib/claude-hooks.js +172 -18
  10. package/lib/client-config-repair.js +17 -8
  11. package/lib/desktop-app.js +7 -1
  12. package/lib/external-tools.js +1 -1
  13. package/lib/first-launch-protection.js +57 -12
  14. package/lib/mcp-dist.js +176 -0
  15. package/lib/release-npm-ci.mjs +138 -0
  16. package/lib/release-publish.mjs +91 -0
  17. package/lib/release-scan.mjs +50 -0
  18. package/lib/server-crash-record.js +123 -0
  19. package/lib/server-exit.js +318 -0
  20. package/lib/server-supervisor.js +508 -0
  21. package/lib/start-launcher.js +37 -4
  22. package/lib/tools-cli.js +45 -0
  23. package/lib/update-snapshot.js +228 -0
  24. package/lib/virustotal.mjs +192 -0
  25. package/lib/windows-crash-event.js +269 -0
  26. package/mcp-server/dist/http.d.ts +20 -1
  27. package/mcp-server/dist/http.d.ts.map +1 -1
  28. package/mcp-server/dist/http.js +59 -12
  29. package/mcp-server/dist/http.js.map +1 -1
  30. package/mcp-server/dist/index.d.ts +33 -27
  31. package/mcp-server/dist/index.d.ts.map +1 -1
  32. package/mcp-server/dist/index.js +128 -143
  33. package/mcp-server/dist/index.js.map +1 -1
  34. package/mcp-server/dist/preload.js +1 -1
  35. package/mcp-server/dist/preload.js.map +1 -1
  36. package/mcp-server/dist/services/codex-config-heal.d.ts +6 -2
  37. package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
  38. package/mcp-server/dist/services/codex-config-heal.js +81 -28
  39. package/mcp-server/dist/services/codex-config-heal.js.map +1 -1
  40. package/mcp-server/dist/services/memory-judge-backfill.d.ts +6 -0
  41. package/mcp-server/dist/services/memory-judge-backfill.d.ts.map +1 -1
  42. package/mcp-server/dist/services/memory-judge-backfill.js +7 -1
  43. package/mcp-server/dist/services/memory-judge-backfill.js.map +1 -1
  44. package/mcp-server/dist/services/neural-interface.d.ts +16 -10
  45. package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
  46. package/mcp-server/dist/services/neural-interface.js +67 -11
  47. package/mcp-server/dist/services/neural-interface.js.map +1 -1
  48. package/mcp-server/dist/services/server-instructions.d.ts +7 -2
  49. package/mcp-server/dist/services/server-instructions.d.ts.map +1 -1
  50. package/mcp-server/dist/services/server-instructions.js +20 -62
  51. package/mcp-server/dist/services/server-instructions.js.map +1 -1
  52. package/mcp-server/dist/services/tool-catalog.d.ts +120 -0
  53. package/mcp-server/dist/services/tool-catalog.d.ts.map +1 -0
  54. package/mcp-server/dist/services/tool-catalog.js +339 -0
  55. package/mcp-server/dist/services/tool-catalog.js.map +1 -0
  56. package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts +12 -0
  57. package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts.map +1 -0
  58. package/mcp-server/dist/services/tool-list-refresh-barrier.js +40 -0
  59. package/mcp-server/dist/services/tool-list-refresh-barrier.js.map +1 -0
  60. package/mcp-server/dist/services/tool-list.d.ts +108 -0
  61. package/mcp-server/dist/services/tool-list.d.ts.map +1 -0
  62. package/mcp-server/dist/services/tool-list.js +334 -0
  63. package/mcp-server/dist/services/tool-list.js.map +1 -0
  64. package/mcp-server/dist/services/tool-runtime.d.ts +161 -0
  65. package/mcp-server/dist/services/tool-runtime.d.ts.map +1 -0
  66. package/mcp-server/dist/services/tool-runtime.js +400 -0
  67. package/mcp-server/dist/services/tool-runtime.js.map +1 -0
  68. package/mcp-server/dist/services/whiteboard-geometry.d.ts +26 -2
  69. package/mcp-server/dist/services/whiteboard-geometry.d.ts.map +1 -1
  70. package/mcp-server/dist/services/whiteboard-geometry.js +91 -8
  71. package/mcp-server/dist/services/whiteboard-geometry.js.map +1 -1
  72. package/mcp-server/dist/tools/agents.d.ts +10 -10
  73. package/mcp-server/dist/tools/agents.d.ts.map +1 -1
  74. package/mcp-server/dist/tools/agents.js +22 -13
  75. package/mcp-server/dist/tools/agents.js.map +1 -1
  76. package/mcp-server/dist/tools/browser-batch.d.ts +3 -3
  77. package/mcp-server/dist/tools/image-tools.d.ts +1 -1
  78. package/mcp-server/dist/tools/image-tools.js +1 -1
  79. package/mcp-server/dist/tools/skill-tools.d.ts +505 -0
  80. package/mcp-server/dist/tools/skill-tools.d.ts.map +1 -0
  81. package/mcp-server/dist/tools/skill-tools.js +639 -0
  82. package/mcp-server/dist/tools/skill-tools.js.map +1 -0
  83. package/mcp-server/dist/tools/skill.d.ts +7 -0
  84. package/mcp-server/dist/tools/skill.d.ts.map +1 -0
  85. package/mcp-server/dist/tools/skill.js +11 -0
  86. package/mcp-server/dist/tools/skill.js.map +1 -0
  87. package/mcp-server/dist/tools/tool-groups.d.ts +68 -0
  88. package/mcp-server/dist/tools/tool-groups.d.ts.map +1 -0
  89. package/mcp-server/dist/tools/tool-groups.js +147 -0
  90. package/mcp-server/dist/tools/tool-groups.js.map +1 -0
  91. package/mcp-server/dist/tools/whiteboard-format.d.ts.map +1 -1
  92. package/mcp-server/dist/tools/whiteboard-format.js +7 -4
  93. package/mcp-server/dist/tools/whiteboard-format.js.map +1 -1
  94. package/mcp-server/dist/tools/whiteboard-tools.d.ts +64 -13
  95. package/mcp-server/dist/tools/whiteboard-tools.d.ts.map +1 -1
  96. package/mcp-server/dist/tools/whiteboard-tools.js +30 -5
  97. package/mcp-server/dist/tools/whiteboard-tools.js.map +1 -1
  98. package/mcp-server/package-lock.json +2 -2
  99. package/mcp-server/package.json +1 -1
  100. package/mcp-server/src/http.ts +75 -12
  101. package/mcp-server/src/index.ts +147 -154
  102. package/mcp-server/src/preload.ts +12 -12
  103. package/mcp-server/src/services/codex-config-heal.ts +80 -29
  104. package/mcp-server/src/services/memory-judge-backfill.ts +7 -1
  105. package/mcp-server/src/services/neural-interface.ts +79 -20
  106. package/mcp-server/src/services/server-instructions.ts +27 -63
  107. package/mcp-server/src/services/tool-catalog.ts +403 -0
  108. package/mcp-server/src/services/tool-list-refresh-barrier.ts +33 -0
  109. package/mcp-server/src/services/tool-list.ts +410 -0
  110. package/mcp-server/src/services/tool-runtime.ts +457 -0
  111. package/mcp-server/src/services/whiteboard-geometry.ts +94 -9
  112. package/mcp-server/src/tools/agents.ts +22 -14
  113. package/mcp-server/src/tools/image-tools.ts +1 -1
  114. package/mcp-server/src/tools/skill-tools.ts +589 -0
  115. package/mcp-server/src/tools/skill.ts +13 -0
  116. package/mcp-server/src/tools/tool-groups.ts +164 -0
  117. package/mcp-server/src/tools/whiteboard-format.ts +9 -3
  118. package/mcp-server/src/tools/whiteboard-tools.ts +39 -5
  119. package/neural-interface/i18n/en.json +3463 -200
  120. package/neural-interface/i18n/pt-BR.json +3411 -151
  121. package/neural-interface/lib/assistant-api.js +127 -28
  122. package/neural-interface/lib/assistant-brains/claude.js +4 -3
  123. package/neural-interface/lib/assistant-brains/codex-browser-policy-hook.mjs +12 -2
  124. package/neural-interface/lib/assistant-brains/codex-route-gate-hook.mjs +32 -9
  125. package/neural-interface/lib/assistant-brains/codex.js +9 -5
  126. package/neural-interface/lib/assistant-brains/opencode.js +120 -54
  127. package/neural-interface/lib/assistant-brains/opencode2-browser-policy/index.js +28 -13
  128. package/neural-interface/lib/assistant-brains/opencode2-route-gate/index.js +59 -19
  129. package/neural-interface/lib/assistant-catalog.js +6 -7
  130. package/neural-interface/lib/assistant-dispatch.js +871 -156
  131. package/neural-interface/lib/assistant-envelope.js +3 -1
  132. package/neural-interface/lib/assistant-persona.js +80 -11
  133. package/neural-interface/lib/assistant-plan-permissions.js +10 -9
  134. package/neural-interface/lib/assistant-route-gate-codex.js +3 -0
  135. package/neural-interface/lib/assistant-route-gate.js +6 -4
  136. package/neural-interface/lib/assistant-router.js +13 -8
  137. package/neural-interface/lib/assistant-runtime.js +56 -18
  138. package/neural-interface/lib/assistant-task-prompt.js +78 -14
  139. package/neural-interface/lib/assistant-tools.js +51 -0
  140. package/neural-interface/lib/backup-service.js +620 -3
  141. package/neural-interface/lib/backup-zip-reader.js +10 -5
  142. package/neural-interface/lib/backup-zip-writer.js +257 -0
  143. package/neural-interface/lib/browser-execution.js +10 -2
  144. package/neural-interface/lib/browser-guard-cdp.js +83 -0
  145. package/neural-interface/lib/browser-network-guard.js +284 -0
  146. package/neural-interface/lib/browser-policy-roots.js +64 -0
  147. package/neural-interface/lib/browser-tool-policy.js +186 -31
  148. package/neural-interface/lib/claude-agent-bridge.js +4 -2
  149. package/neural-interface/lib/claude-hooks-status.js +103 -0
  150. package/neural-interface/lib/claude-temporary.js +5 -11
  151. package/neural-interface/lib/cli-install.js +18 -138
  152. package/neural-interface/lib/cli-resolver.js +4 -1
  153. package/neural-interface/lib/cli-update-planner.js +32 -31
  154. package/neural-interface/lib/cloudflared/tunnel.js +42 -2
  155. package/neural-interface/lib/codex-mcp-approvals.js +11 -0
  156. package/neural-interface/lib/codex-worker-app-server.js +172 -0
  157. package/neural-interface/lib/confined-fs.js +30 -3
  158. package/neural-interface/lib/crash-notice-api.js +98 -0
  159. package/neural-interface/lib/editor-completion-opencode.js +68 -68
  160. package/neural-interface/lib/exec-loop-command.js +4 -8
  161. package/neural-interface/lib/http-guards.js +8 -2
  162. package/neural-interface/lib/install-details.js +4 -1
  163. package/neural-interface/lib/mcp-agent-servers.js +67 -0
  164. package/neural-interface/lib/mcp-client-config.js +0 -29
  165. package/neural-interface/lib/mcp-external-servers.js +323 -0
  166. package/neural-interface/lib/{mcp-profile-refresh-coordinator.js → mcp-tools-refresh-coordinator.js} +4 -4
  167. package/neural-interface/lib/morelogin.js +30 -0
  168. package/neural-interface/lib/native-loop-providers.js +547 -47
  169. package/neural-interface/lib/native-loop-runtime.js +268 -41
  170. package/neural-interface/lib/opencode-config.js +557 -0
  171. package/neural-interface/lib/opencode-db-schema.js +17 -34
  172. package/neural-interface/lib/opencode-history.js +48 -218
  173. package/neural-interface/lib/opencode-mcp-ready.js +108 -0
  174. package/neural-interface/lib/opencode-native-boundary.js +44 -0
  175. package/neural-interface/lib/opencode-protocol.js +249 -112
  176. package/neural-interface/lib/opencode-runtime-config.js +32 -22
  177. package/neural-interface/lib/opencode-serve-records.js +39 -21
  178. package/neural-interface/lib/opencode-v2-client.js +48 -153
  179. package/neural-interface/lib/opencode-v2-ws-requests.js +36 -210
  180. package/neural-interface/lib/opencode-worker-data.js +27 -0
  181. package/neural-interface/lib/opencode2-client.js +73 -37
  182. package/neural-interface/lib/opencode2-proxy.js +23 -63
  183. package/neural-interface/lib/opencode2-translate.js +51 -2
  184. package/neural-interface/lib/owner-authority.js +249 -0
  185. package/neural-interface/lib/page-session.js +275 -0
  186. package/neural-interface/lib/peer-identity.js +763 -0
  187. package/neural-interface/lib/request-gate.js +17 -2
  188. package/neural-interface/lib/rulesets/api.js +1 -1
  189. package/neural-interface/lib/rulesets/fs-safe.js +1 -1
  190. package/neural-interface/lib/rulesets/installer.js +92 -27
  191. package/neural-interface/lib/rulesets/json-edit.js +1 -1
  192. package/neural-interface/lib/session-cache-builder.js +17 -30
  193. package/neural-interface/lib/session-title-generator.js +58 -36
  194. package/neural-interface/lib/skills/api-v2.js +495 -0
  195. package/neural-interface/lib/skills/bundle.js +418 -0
  196. package/neural-interface/lib/skills/catalog.js +294 -0
  197. package/neural-interface/lib/skills/creation-api.js +191 -0
  198. package/neural-interface/lib/skills/creation-brief.js +247 -0
  199. package/neural-interface/lib/skills/creation-sources.js +267 -0
  200. package/neural-interface/lib/skills/creation.js +843 -0
  201. package/neural-interface/lib/skills/deploy.js +564 -0
  202. package/neural-interface/lib/skills/digest.js +119 -0
  203. package/neural-interface/lib/skills/discover.js +111 -0
  204. package/neural-interface/lib/skills/draft-confinement.js +214 -0
  205. package/neural-interface/lib/skills/eval-confinement.js +397 -0
  206. package/neural-interface/lib/skills/eval-sandbox.js +280 -0
  207. package/neural-interface/lib/skills/eval-trace.js +119 -0
  208. package/neural-interface/lib/skills/evals-api.js +212 -0
  209. package/neural-interface/lib/skills/evals.js +1573 -0
  210. package/neural-interface/lib/skills/export.js +200 -0
  211. package/neural-interface/lib/skills/frontmatter.js +64 -0
  212. package/neural-interface/lib/skills/health.js +75 -0
  213. package/neural-interface/lib/skills/json-edit.js +165 -0
  214. package/neural-interface/lib/skills/known-bridge-digests.json +387 -0
  215. package/neural-interface/lib/skills/known-bridges.js +96 -0
  216. package/neural-interface/lib/skills/library.js +823 -0
  217. package/neural-interface/lib/skills/local-store.js +21 -0
  218. package/neural-interface/lib/skills/migration.js +433 -0
  219. package/neural-interface/lib/skills/operations.js +45 -0
  220. package/neural-interface/lib/skills/ownership.js +427 -0
  221. package/neural-interface/lib/skills/packages.js +88 -0
  222. package/neural-interface/lib/skills/reviews.js +212 -0
  223. package/neural-interface/lib/skills/revision.js +80 -0
  224. package/neural-interface/lib/skills/roots.js +262 -0
  225. package/neural-interface/lib/skills/runtime-checks.js +355 -0
  226. package/neural-interface/lib/skills/scan-assets.js +252 -0
  227. package/neural-interface/lib/skills/scan-context.js +26 -0
  228. package/neural-interface/lib/skills/scan-shell.js +324 -0
  229. package/neural-interface/lib/skills/scan.js +364 -0
  230. package/neural-interface/lib/skills/scanners.js +54 -0
  231. package/neural-interface/lib/skills/service.js +522 -0
  232. package/neural-interface/lib/skills/telemetry.js +113 -0
  233. package/neural-interface/lib/sqlite-integrity-worker.js +22 -0
  234. package/neural-interface/lib/tool-lists.js +124 -0
  235. package/neural-interface/lib/whatsapp/bridge.js +6 -3
  236. package/neural-interface/lib/worker-confinement.js +1192 -0
  237. package/neural-interface/lib/worker-guard-setting.js +56 -0
  238. package/neural-interface/lib/worker-mcp-channel.js +175 -0
  239. package/neural-interface/lib/worker-relay-ports.js +227 -0
  240. package/neural-interface/lib/worker-resume.js +229 -0
  241. package/neural-interface/lib/worker-sandbox.js +299 -0
  242. package/neural-interface/package-lock.json +18 -12
  243. package/neural-interface/package.json +2 -2
  244. package/neural-interface/public/index.html +3 -1
  245. package/neural-interface/public/index2d.html +19 -11
  246. package/neural-interface/public/onboarding.html +2 -2
  247. package/neural-interface/public/shared/account-menu.js +8 -5
  248. package/neural-interface/public/shared/agent-runtime-options.js +35 -19
  249. package/neural-interface/public/shared/api.js +1527 -1336
  250. package/neural-interface/public/shared/app-frame.js +192 -0
  251. package/neural-interface/public/shared/assistant/asst-agents-dock.js +33 -2
  252. package/neural-interface/public/shared/assistant/asst-ask-styles.js +270 -0
  253. package/neural-interface/public/shared/assistant/asst-ask-tail.js +118 -0
  254. package/neural-interface/public/shared/assistant/asst-ask.js +397 -0
  255. package/neural-interface/public/shared/assistant/asst-brain-picker.js +49 -35
  256. package/neural-interface/public/shared/assistant/asst-control.js +157 -26
  257. package/neural-interface/public/shared/assistant/asst-followups-card.js +383 -0
  258. package/neural-interface/public/shared/assistant/asst-followups.js +417 -0
  259. package/neural-interface/public/shared/assistant/asst-limit-dock.js +40 -0
  260. package/neural-interface/public/shared/assistant/asst-limit-wait.js +294 -0
  261. package/neural-interface/public/shared/assistant/asst-limit.js +168 -0
  262. package/neural-interface/public/shared/assistant/asst-mascot.js +49 -12
  263. package/neural-interface/public/shared/assistant/asst-menu.js +7 -6
  264. package/neural-interface/public/shared/assistant/asst-panel.js +106 -11
  265. package/neural-interface/public/shared/assistant/asst-rack-styles.js +66 -49
  266. package/neural-interface/public/shared/assistant/asst-render.js +401 -77
  267. package/neural-interface/public/shared/assistant/asst-route.js +83 -12
  268. package/neural-interface/public/shared/assistant/asst-scenes.js +2 -0
  269. package/neural-interface/public/shared/assistant/asst-sidepanel.js +67 -4
  270. package/neural-interface/public/shared/assistant/asst-slash.js +9 -7
  271. package/neural-interface/public/shared/assistant/asst-state.js +11 -15
  272. package/neural-interface/public/shared/assistant/asst-stop.js +127 -0
  273. package/neural-interface/public/shared/assistant/asst-styles.js +59 -21
  274. package/neural-interface/public/shared/assistant/asst-tool-kinds.js +156 -16
  275. package/neural-interface/public/shared/automation-context.js +5 -2
  276. package/neural-interface/public/shared/cc-dropdown.js +89 -19
  277. package/neural-interface/public/shared/cdx/cdx-context-menu.js +10 -9
  278. package/neural-interface/public/shared/cdx/cdx-copy.js +16 -0
  279. package/neural-interface/public/shared/cdx/cdx-output-model.js +22 -1
  280. package/neural-interface/public/shared/cdx/cdx-output.js +8 -7
  281. package/neural-interface/public/shared/cdx/cdx-panel.js +45 -149
  282. package/neural-interface/public/shared/cdx/cdx-protocol.js +28 -1
  283. package/neural-interface/public/shared/cdx/cdx-render.js +47 -29
  284. package/neural-interface/public/shared/cdx/cdx-requests.js +3 -3
  285. package/neural-interface/public/shared/cdx/cdx-skill-hints.js +54 -0
  286. package/neural-interface/public/shared/cdx/cdx-styles.js +11 -3
  287. package/neural-interface/public/shared/cdx/cdx-tabs.js +52 -39
  288. package/neural-interface/public/shared/cdx/cdx-ws.js +0 -1
  289. package/neural-interface/public/shared/cli-install-view.js +4 -8
  290. package/neural-interface/public/shared/cli-status.js +2 -2
  291. package/neural-interface/public/shared/constants.js +2 -2
  292. package/neural-interface/public/shared/context-menu-areas.js +34 -0
  293. package/neural-interface/public/shared/context-menu-logic.js +243 -0
  294. package/neural-interface/public/shared/context-menu-model.js +366 -0
  295. package/neural-interface/public/shared/context-menu.js +372 -0
  296. package/neural-interface/public/shared/cp/cp-context-menu.js +10 -9
  297. package/neural-interface/public/shared/crash-notice-view.js +168 -0
  298. package/neural-interface/public/shared/desktop-update-view.js +20 -0
  299. package/neural-interface/public/shared/editor/editor-panel-logic.js +2 -2
  300. package/neural-interface/public/shared/html-shell.js +9 -11
  301. package/neural-interface/public/shared/i18n.js +2 -0
  302. package/neural-interface/public/shared/ocp-v2/ocp-v2-approvals.js +7 -7
  303. package/neural-interface/public/shared/ocp-v2/ocp-v2-binding.js +4 -4
  304. package/neural-interface/public/shared/ocp-v2/ocp-v2-changes-logic.js +3 -101
  305. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +2 -0
  306. package/neural-interface/public/shared/ocp-v2/ocp-v2-compact-button.js +1 -3
  307. package/neural-interface/public/shared/ocp-v2/ocp-v2-composer-logic.js +2 -6
  308. package/neural-interface/public/shared/ocp-v2/ocp-v2-context-menu.js +11 -11
  309. package/neural-interface/public/shared/ocp-v2/ocp-v2-context-model.js +0 -3
  310. package/neural-interface/public/shared/ocp-v2/ocp-v2-events.js +8 -4
  311. package/neural-interface/public/shared/ocp-v2/ocp-v2-manager.js +7 -7
  312. package/neural-interface/public/shared/ocp-v2/ocp-v2-outcome-notifications.js +16 -0
  313. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +47 -252
  314. package/neural-interface/public/shared/ocp-v2/ocp-v2-plan.js +1 -2
  315. package/neural-interface/public/shared/ocp-v2/ocp-v2-projectbar.js +95 -202
  316. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +6 -21
  317. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +4 -4
  318. package/neural-interface/public/shared/ocp-v2/ocp-v2-session-actions.js +8 -61
  319. package/neural-interface/public/shared/ocp-v2/ocp-v2-sessions-logic.js +2 -91
  320. package/neural-interface/public/shared/ocp-v2/ocp-v2-slash-hints.js +16 -15
  321. package/neural-interface/public/shared/ocp-v2/ocp-v2-state.js +8 -21
  322. package/neural-interface/public/shared/ocp-v2/ocp-v2-status-logic.js +1 -99
  323. package/neural-interface/public/shared/ocp-v2/ocp-v2-status.js +5 -35
  324. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +0 -21
  325. package/neural-interface/public/shared/ocp-v2/{ocp-v2-profile-inheritance.js → ocp-v2-tools-inheritance.js} +16 -16
  326. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +14 -47
  327. package/neural-interface/public/shared/page-session.js +96 -0
  328. package/neural-interface/public/shared/registry.js +5 -3
  329. package/neural-interface/public/shared/retired-2d.js +3 -0
  330. package/neural-interface/public/shared/schedules-view.js +195 -61
  331. package/neural-interface/public/shared/settings/settings-claude-hooks.js +135 -0
  332. package/neural-interface/public/shared/settings/settings-data.js +3 -1
  333. package/neural-interface/public/shared/settings/settings-ia.js +6 -6
  334. package/neural-interface/public/shared/settings/settings-opencode.js +89 -0
  335. package/neural-interface/public/shared/settings/settings-skill-folders-pane.js +265 -0
  336. package/neural-interface/public/shared/settings/settings-skill-folders.js +130 -0
  337. package/neural-interface/public/shared/settings/settings-worker-guard.js +72 -0
  338. package/neural-interface/public/shared/skills/ss-args.js +96 -0
  339. package/neural-interface/public/shared/skills/ss-creation.js +219 -0
  340. package/neural-interface/public/shared/skills/ss-dialog.js +130 -0
  341. package/neural-interface/public/shared/skills/ss-doc.js +173 -0
  342. package/neural-interface/public/shared/skills/ss-eval-adapters.js +269 -0
  343. package/neural-interface/public/shared/skills/ss-eval-grade.js +359 -0
  344. package/neural-interface/public/shared/skills/ss-eval-optimise.js +153 -0
  345. package/neural-interface/public/shared/skills/ss-eval-plan.js +315 -0
  346. package/neural-interface/public/shared/skills/ss-evals.js +417 -0
  347. package/neural-interface/public/shared/skills/ss-form.js +159 -0
  348. package/neural-interface/public/shared/skills/ss-frontmatter.js +376 -0
  349. package/neural-interface/public/shared/skills/ss-ids.js +32 -0
  350. package/neural-interface/public/shared/skills/ss-lint.js +141 -0
  351. package/neural-interface/public/shared/skills/ss-presets.js +117 -0
  352. package/neural-interface/public/shared/skills/ss-profiles.js +475 -0
  353. package/neural-interface/public/shared/skills/ss-resources.js +165 -0
  354. package/neural-interface/public/shared/skills/ss-review-view.js +190 -0
  355. package/neural-interface/public/shared/skills/ss-tabs.js +79 -0
  356. package/neural-interface/public/shared/skills/ss-unicode.js +135 -0
  357. package/neural-interface/public/shared/skills/studio/sx-actions.js +222 -0
  358. package/neural-interface/public/shared/skills/studio/sx-cm.js +101 -0
  359. package/neural-interface/public/shared/skills/studio/sx-create-ai-view.js +749 -0
  360. package/neural-interface/public/shared/skills/studio/sx-create-ai.js +236 -0
  361. package/neural-interface/public/shared/skills/studio/sx-create-view.js +254 -0
  362. package/neural-interface/public/shared/skills/studio/sx-create.js +126 -0
  363. package/neural-interface/public/shared/skills/studio/sx-diff-view.js +43 -0
  364. package/neural-interface/public/shared/skills/studio/sx-diff.js +133 -0
  365. package/neural-interface/public/shared/skills/studio/sx-docs.js +480 -0
  366. package/neural-interface/public/shared/skills/studio/sx-dom.js +229 -0
  367. package/neural-interface/public/shared/skills/studio/sx-edit-view.js +848 -0
  368. package/neural-interface/public/shared/skills/studio/sx-editor-model.js +193 -0
  369. package/neural-interface/public/shared/skills/studio/sx-errors.js +22 -0
  370. package/neural-interface/public/shared/skills/studio/sx-geometry.js +82 -0
  371. package/neural-interface/public/shared/skills/studio/sx-import.js +262 -0
  372. package/neural-interface/public/shared/skills/studio/sx-inspector.js +308 -0
  373. package/neural-interface/public/shared/skills/studio/sx-install-flow.js +279 -0
  374. package/neural-interface/public/shared/skills/studio/sx-install.js +296 -0
  375. package/neural-interface/public/shared/skills/studio/sx-library-view.js +639 -0
  376. package/neural-interface/public/shared/skills/studio/sx-library.js +475 -0
  377. package/neural-interface/public/shared/skills/studio/sx-main.js +681 -0
  378. package/neural-interface/public/shared/skills/studio/sx-operations-view.js +98 -0
  379. package/neural-interface/public/shared/skills/studio/sx-operations.js +11 -0
  380. package/neural-interface/public/shared/skills/studio/sx-plan.js +158 -0
  381. package/neural-interface/public/shared/skills/studio/sx-publish-view.js +621 -0
  382. package/neural-interface/public/shared/skills/studio/sx-review-view.js +369 -0
  383. package/neural-interface/public/shared/skills/studio/sx-review.js +148 -0
  384. package/neural-interface/public/shared/skills/studio/sx-scan.js +35 -0
  385. package/neural-interface/public/shared/skills/studio/sx-snippets.js +96 -0
  386. package/neural-interface/public/shared/skills/studio/sx-subject.js +248 -0
  387. package/neural-interface/public/shared/skills/studio/sx-test-view.js +994 -0
  388. package/neural-interface/public/shared/skills/studio/sx-test.js +328 -0
  389. package/neural-interface/public/shared/skills/studio/sx-versions-view.js +241 -0
  390. package/neural-interface/public/shared/skills/studio/sx-workspace-view.js +425 -0
  391. package/neural-interface/public/shared/styleguide/sg-panel.js +4 -2
  392. package/neural-interface/public/shared/styles.css +1525 -1779
  393. package/neural-interface/public/shared/synabun-mascot.js +1115 -9
  394. package/neural-interface/public/shared/tool-list-picker.js +55 -0
  395. package/neural-interface/public/shared/tool-list-storage.js +24 -0
  396. package/neural-interface/public/shared/tool-list-view.js +112 -0
  397. package/neural-interface/public/shared/tour/tour-actor.js +268 -0
  398. package/neural-interface/public/shared/tour/tour-beats.js +336 -0
  399. package/neural-interface/public/shared/tour/tour-engine.js +884 -0
  400. package/neural-interface/public/shared/tour/tour-ink.js +390 -0
  401. package/neural-interface/public/shared/tour/tour-stage.js +440 -0
  402. package/neural-interface/public/shared/tour/tour-state.js +40 -0
  403. package/neural-interface/public/shared/tour/tour-tips.js +99 -0
  404. package/neural-interface/public/shared/tour/tour.css +144 -0
  405. package/neural-interface/public/shared/ui-automation-studio.js +91 -128
  406. package/neural-interface/public/shared/ui-claude-panel.js +77 -5
  407. package/neural-interface/public/shared/ui-command-runner.js +6 -3
  408. package/neural-interface/public/shared/ui-context-menu.js +295 -0
  409. package/neural-interface/public/shared/ui-cost-widget.js +57 -59
  410. package/neural-interface/public/shared/ui-crash-notice.js +187 -0
  411. package/neural-interface/public/shared/ui-detail.js +15 -3
  412. package/neural-interface/public/shared/ui-image-gallery.js +50 -47
  413. package/neural-interface/public/shared/ui-keybinds.js +26 -1
  414. package/neural-interface/public/shared/ui-menubar.js +8 -18
  415. package/neural-interface/public/shared/ui-native-loop-router-state.js +5 -0
  416. package/neural-interface/public/shared/ui-native-loop-router.js +7 -1
  417. package/neural-interface/public/shared/ui-navbar.js +4 -24
  418. package/neural-interface/public/shared/ui-panels.js +96 -65
  419. package/neural-interface/public/shared/ui-schedules-studio.js +507 -423
  420. package/neural-interface/public/shared/ui-sessions.js +5 -3
  421. package/neural-interface/public/shared/ui-settings.js +594 -462
  422. package/neural-interface/public/shared/ui-sidepanel-sessions.js +1 -1
  423. package/neural-interface/public/shared/ui-sidepanel-state.js +23 -12
  424. package/neural-interface/public/shared/ui-sidepanel-windows.js +16 -11
  425. package/neural-interface/public/shared/ui-skills.js +33 -2830
  426. package/neural-interface/public/shared/ui-styleguide.js +5 -2
  427. package/neural-interface/public/shared/ui-sync.js +15 -5
  428. package/neural-interface/public/shared/ui-terminal.js +124 -180
  429. package/neural-interface/public/shared/ui-tool-updates.js +3 -1
  430. package/neural-interface/public/shared/ui-tooltip.js +10 -5
  431. package/neural-interface/public/shared/ui-trash.js +6 -4
  432. package/neural-interface/public/shared/ui-tutorial-layout.js +3 -117
  433. package/neural-interface/public/shared/ui-tutorial-preview.js +103 -0
  434. package/neural-interface/public/shared/ui-tutorial-steps.js +3 -65
  435. package/neural-interface/public/shared/ui-tutorial.css +47 -100
  436. package/neural-interface/public/shared/ui-tutorial.js +285 -509
  437. package/neural-interface/public/shared/ui-update.js +38 -2
  438. package/neural-interface/public/shared/ui-whiteboard.js +311 -58
  439. package/neural-interface/public/shared/ui-worker-end.js +4 -0
  440. package/neural-interface/public/shared/ui-workspaces.js +3 -2
  441. package/neural-interface/public/shared/view-variant.js +66 -0
  442. package/neural-interface/public/shared/whatsapp/wa-view.js +1 -1
  443. package/neural-interface/public/shared/whiteboard-list.js +172 -0
  444. package/neural-interface/public/shared/worker-end.js +10 -3
  445. package/neural-interface/public/shared/worker-restart.js +12 -0
  446. package/neural-interface/public/sidepanel-session.html +1 -1
  447. package/neural-interface/public/variant/2d/background.js +2 -1
  448. package/neural-interface/public/variant/2d/context-menu.js +8 -2
  449. package/neural-interface/public/variant/2d/graph.js +42 -20
  450. package/neural-interface/public/variant/2d/hulls.js +14 -9
  451. package/neural-interface/public/variant/2d/lasso.js +11 -7
  452. package/neural-interface/public/variant/2d/main.js +32 -1
  453. package/neural-interface/public/variant/2d/minimap.js +11 -13
  454. package/neural-interface/public/variant/3d/main.js +69 -6
  455. package/neural-interface/public/variant/3d/map-controls.js +24 -9
  456. package/neural-interface/public/vendor/caveat/OFL.txt +93 -0
  457. package/neural-interface/public/vendor/caveat/caveat-latin-400-normal.woff2 +0 -0
  458. package/neural-interface/public/vendor/caveat/caveat-latin-700-normal.woff2 +0 -0
  459. package/neural-interface/public/vendor/caveat/caveat.css +17 -0
  460. package/neural-interface/public/vendor/caveat/manifest.json +12 -0
  461. package/neural-interface/public/vendor/yaml/LICENSE +13 -0
  462. package/neural-interface/public/vendor/yaml/manifest.json +4 -0
  463. package/neural-interface/public/vendor/yaml/yaml-2.9.1.min.js +137 -0
  464. package/neural-interface/scripts/build-vendor.mjs +32 -0
  465. package/neural-interface/server.js +3934 -2454
  466. package/neural-interface/templates/rulesets/core.md +1 -1
  467. package/neural-interface/templates/rulesets/legacy-hashes.json +18 -0
  468. package/neural-interface/templates/rulesets/manifest.json +8 -8
  469. package/npm-shrinkwrap.json +18 -12
  470. package/package.json +16 -3
  471. package/postinstall.js +9 -4
  472. package/preuninstall.js +1 -1
  473. package/setup.js +97 -322
  474. package/skills/skill-forge/SKILL.md +177 -0
  475. package/skills/skill-forge/evals/evals.json +54 -0
  476. package/skills/skill-forge/references/package.md +105 -0
  477. package/skills/skill-forge/references/review-and-recovery.md +63 -0
  478. package/skills/skill-forge/references/runtime-profiles.md +20 -0
  479. package/skills/skill-forge/references/sources-and-capabilities.md +51 -0
  480. package/skills/synabun/SKILL.md +10 -7
  481. package/skills/synabun/modules/audit.md +1 -5
  482. package/skills/synabun/modules/changelog.md +1 -5
  483. package/skills/synabun/modules/idea.md +1 -1
  484. package/skills/synabun/modules/memorize.md +6 -9
  485. package/skills/synabun/modules/schedule.md +4 -4
  486. package/mcp-server/dist/services/profiles.d.ts +0 -120
  487. package/mcp-server/dist/services/profiles.d.ts.map +0 -1
  488. package/mcp-server/dist/services/profiles.js +0 -387
  489. package/mcp-server/dist/services/profiles.js.map +0 -1
  490. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts +0 -97
  491. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts.map +0 -1
  492. package/mcp-server/dist/tools/leonardo-browser-tools.js +0 -228
  493. package/mcp-server/dist/tools/leonardo-browser-tools.js.map +0 -1
  494. package/mcp-server/dist/tools/leonardo.d.ts +0 -13
  495. package/mcp-server/dist/tools/leonardo.d.ts.map +0 -1
  496. package/mcp-server/dist/tools/leonardo.js +0 -21
  497. package/mcp-server/dist/tools/leonardo.js.map +0 -1
  498. package/mcp-server/dist/tools/profile.d.ts +0 -17
  499. package/mcp-server/dist/tools/profile.d.ts.map +0 -1
  500. package/mcp-server/dist/tools/profile.js +0 -114
  501. package/mcp-server/dist/tools/profile.js.map +0 -1
  502. package/mcp-server/src/services/profiles.ts +0 -428
  503. package/mcp-server/src/tools/leonardo-browser-tools.ts +0 -291
  504. package/mcp-server/src/tools/leonardo.ts +0 -30
  505. package/mcp-server/src/tools/profile.ts +0 -122
  506. package/neural-interface/lib/assistant-brains/opencode-browser-policy.js +0 -47
  507. package/neural-interface/lib/assistant-brains/opencode-route-gate.js +0 -62
  508. package/neural-interface/lib/mcp-profile-servers.js +0 -120
  509. package/neural-interface/public/shared/ui-tutorial-draw.js +0 -311
  510. package/skills/leonardo/SKILL.md +0 -168
  511. package/skills/leonardo/icon.svg +0 -1
  512. package/skills/leonardo/modules/browser-guide.md +0 -378
  513. package/skills/leonardo/modules/image-prompter.md +0 -283
  514. package/skills/leonardo/modules/model-advisor.md +0 -100
  515. package/skills/leonardo/modules/prompt-library.md +0 -110
  516. package/skills/leonardo/modules/style-guide.md +0 -186
  517. package/skills/leonardo/modules/video-prompter.md +0 -317
@@ -25,11 +25,22 @@
25
25
  // Submit answers them, Skip lets the brain go on its assumptions; the reply is
26
26
  // { kind:'clarify', behavior, answers: string[][] } and the server's clarifier
27
27
  // (lib/assistant-clarify.js) owns it, never the brain.
28
+ //
29
+ // Every card that asks (question, elicitation, clarify) is drawn by the mascot,
30
+ // like the follow-ups card (asst-ask.js): its face (`ask`, live while it is
31
+ // the newest open card of the panel) beside its first bubble, the questions in
32
+ // its bubbles, the options as numbered rows (1-9, arrows, Enter; Esc goes to
33
+ // the composer). Only the drawing changed: what each provider is sent is built by
34
+ // buildControlResponse from the same answers as before.
28
35
 
29
36
  import { normalizeProvider } from './asst-state.js';
30
37
  import { sanitizeInto } from './asst-sanitize.js';
31
38
  import { fill, maskEcho, redactInput } from './asst-tool-kinds.js';
32
39
  import { mascotSvg, paintMascot, stillFrame } from '../synabun-mascot.js';
40
+ import { splitRecommended } from './asst-followups.js';
41
+ import {
42
+ askOption, createPresenter, injectAskStyles, keyRange, setOptionChecked, wireCardKeys, wireGaze, wireOptionGroup, wireWatch,
43
+ } from './asst-ask.js';
33
44
  import {
34
45
  SYNABUN_CHOICE_MARKER,
35
46
  SYNABUN_OTHER_VALUE,
@@ -691,6 +702,17 @@ export function renderControlCard(container, normalized, hooks = {}) {
691
702
  card.setAttribute('role', 'group');
692
703
  card.setAttribute('aria-label', t(`assistant.control.${n.kind}`, n.title || n.kind));
693
704
 
705
+ // A card that asks is the mascot asking (asst-ask.js): its face beside its first bubble (renderQuestionBody).
706
+ const asking = n.kind === CONTROL_KINDS.QUESTION || n.kind === CONTROL_KINDS.ELICITATION || n.kind === CONTROL_KINDS.CLARIFY;
707
+ const presenter = asking ? createPresenter({ pose: 'ask', waits: true, entrance: 'pulse' }) : null;
708
+ if (presenter) {
709
+ injectAskStyles();
710
+ card.classList.add('asst-ask');
711
+ }
712
+ // What the renderer reads to make this card's face live while it is the newest open card (asst-render.js).
713
+ const ask = presenter ? { el: card, presenter, isOpen: () => !state.resolved, onChange: null } : null;
714
+ if (ask) card._ask = ask;
715
+
694
716
  const head = el('div', 'asst-control-head');
695
717
  const kindLabel = el('span', 'asst-control-kind', t(`assistant.control.${n.kind}`, n.title || n.kind));
696
718
  head.appendChild(kindLabel);
@@ -710,11 +732,14 @@ export function renderControlCard(container, normalized, hooks = {}) {
710
732
  card.appendChild(body);
711
733
 
712
734
  const state = { resolved: false, answers: n.questions.map(() => ({ selected: new Set(), custom: '' })), bashInput: null, denyMsg: null, checkedSuggestions: new Set() };
735
+ // Back to the message box (Esc in the card, and after an answer the card had focus for).
736
+ const toComposer = () => { try { hooks.focusComposer?.(); } catch { /* the host is gone */ } };
737
+ let groups = [];
713
738
 
714
739
  if (n.kind === CONTROL_KINDS.PERMISSION) renderPermissionBody(body, n, state, t);
715
740
  else if (n.kind === CONTROL_KINDS.COMPUTER) body.appendChild(el('div', 'asst-control-message', t('assistant.control.computerBody', n.detail)));
716
741
  else if (n.kind === CONTROL_KINDS.PLAN) renderPlanBody(body, n, hooks);
717
- else renderQuestionBody(body, n, state, t);
742
+ else groups = renderQuestionBody(body, n, state, t, { presenter, onEscape: toComposer });
718
743
 
719
744
  const actions = el('div', 'asst-control-actions');
720
745
  const denyMsg = el('textarea', 'asst-control-deny-msg');
@@ -726,11 +751,15 @@ export function renderControlCard(container, normalized, hooks = {}) {
726
751
  denyMsg.setAttribute('aria-label', denyMsg.placeholder);
727
752
  denyMsg.rows = 1;
728
753
  denyMsg.hidden = n.kind !== CONTROL_KINDS.PLAN;
729
- denyMsg.addEventListener('keydown', (e) => e.stopPropagation());
754
+ denyMsg.addEventListener('keydown', (e) => {
755
+ if (presenter && e.key === 'Escape') { e.preventDefault(); toComposer(); }
756
+ e.stopPropagation();
757
+ });
730
758
  state.denyMsg = denyMsg;
731
759
 
732
760
  const buttons = [];
733
- const finish = (label) => {
761
+ /** `answered`: the user answered here (the mascot hops and settles on its "got it" face); else it just goes still. */
762
+ const finish = (label, answered = false) => {
734
763
  state.resolved = true;
735
764
  card.classList.remove('active');
736
765
  card.classList.add('resolved');
@@ -739,7 +768,13 @@ export function renderControlCard(container, normalized, hooks = {}) {
739
768
  card.querySelectorAll('button, input, textarea').forEach(node => { if (!node.classList?.contains('asst-reveal')) node.disabled = true; });
740
769
  status.textContent = label;
741
770
  status.hidden = false;
771
+ if (presenter) {
772
+ presenter.settle(answered ? 'success' : 'idle', {}, { hop: answered });
773
+ presenter.el.classList.toggle('is-answered', answered);
774
+ }
775
+ try { ask?.onChange?.(); } catch { /* the renderer is gone */ }
742
776
  };
777
+ const focusInside = () => { try { return !!card.contains?.(globalThis.document?.activeElement); } catch { return false; } };
743
778
 
744
779
  for (const action of controlActions(n)) {
745
780
  const btn = el('button', `asst-btn asst-btn-${action.tone}`);
@@ -757,6 +792,7 @@ export function renderControlCard(container, normalized, hooks = {}) {
757
792
  }
758
793
  btn.addEventListener('click', () => {
759
794
  if (state.resolved) return;
795
+ const hadFocus = presenter && focusInside();
760
796
  const decision = { ...action.decision };
761
797
  if (decision.behavior === 'deny' && denyMsg.value.trim()) decision.message = denyMsg.value.trim();
762
798
  if (n.kind === CONTROL_KINDS.PERMISSION && decision.behavior === 'allow') {
@@ -769,17 +805,29 @@ export function renderControlCard(container, normalized, hooks = {}) {
769
805
  decision.answers = state.answers.map(a => (a.custom ? [a.custom] : [...a.selected]));
770
806
  }
771
807
  if (n.kind === CONTROL_KINDS.CLARIFY) {
772
- finish(decision.behavior === 'allow' ? t('assistant.control.state.answered', 'Answered') : t('assistant.control.state.skipped', 'Skipped'));
808
+ finish(decision.behavior === 'allow' ? t('assistant.control.state.answered', 'Answered') : t('assistant.control.state.skipped', 'Skipped'), decision.behavior === 'allow');
809
+ } else if (presenter) {
810
+ // A question is answered or declined, not allowed or denied (the reply is the same as before).
811
+ finish(decision.behavior === 'allow' ? t('assistant.control.state.answered', 'Answered') : t('assistant.control.state.declined', 'Declined'), decision.behavior === 'allow');
773
812
  } else {
774
813
  finish(decision.behavior === 'allow'
775
814
  ? (decision.always ? t('assistant.control.state.always', 'Always') : t('assistant.control.state.allowed', 'Allowed'))
776
- : t('assistant.control.state.denied', 'Denied'));
815
+ : t('assistant.control.state.denied', 'Denied'), !!presenter && decision.behavior === 'allow');
777
816
  }
778
817
  hooks.onRespond?.(n, decision);
818
+ if (hadFocus) toComposer();
779
819
  });
780
820
  buttons.push(btn);
781
821
  actions.appendChild(btn);
782
822
  }
823
+ if (presenter) {
824
+ // The keys, said once focus is in the card (asst-ask-styles.js).
825
+ const most = Math.max(0, ...groups.map((g) => g.buttons.length));
826
+ const keys = el('span', 'asst-ask-keys', most
827
+ ? fill(t('assistant.control.keys', '{keys} to pick · Enter to answer · Esc for the message box', { keys: keyRange(most) }), { keys: keyRange(most) })
828
+ : t('assistant.control.keysText', 'Enter to answer · Esc for the message box'));
829
+ actions.appendChild(keys);
830
+ }
783
831
  state.updateSubmit = () => {
784
832
  if (!state.submitBtn) return;
785
833
  const needed = n.questions.map((q, i) => ({ q, i })).filter(({ q }) => q.required !== false);
@@ -795,6 +843,7 @@ export function renderControlCard(container, normalized, hooks = {}) {
795
843
 
796
844
  card.appendChild(denyMsg);
797
845
  card.appendChild(actions);
846
+ if (presenter) wireAsking(card, { groups, state, presenter, toComposer });
798
847
  if (container) container.appendChild(card);
799
848
  try { hooks.scrollEnd?.(); } catch { /* ignore */ }
800
849
 
@@ -804,9 +853,61 @@ export function renderControlCard(container, normalized, hooks = {}) {
804
853
  lock(label) { if (!state.resolved) finish(label || t('assistant.control.state.resolved', 'Resolved')); },
805
854
  // Answered here, but the request closed before the answer reached it: the answer did nothing.
806
855
  expire(label) { finish(label || t('assistant.control.state.expired', 'Expired')); card.classList.add('expired'); },
856
+ // The mascot's part, for the renderer (null on a card that does not ask).
857
+ ask,
807
858
  };
808
859
  }
809
860
 
861
+ /**
862
+ * A question card's keys and gaze (asst-ask.js). Each question's options are one tab stop (arrows move;
863
+ * a radio follows focus, as radios do); 1-9 pick an option of the question focus is in (else the first one
864
+ * still unanswered); Enter on a radio picks it and moves on: to the next question still unanswered, else
865
+ * Submit, which a card of one question presses at once; Esc goes back to the composer. The mascot looks
866
+ * at the option pointed at or focused, and watches a typed answer.
867
+ */
868
+ function wireAsking(card, { groups, state, presenter, toComposer }) {
869
+ const active = () => { try { return globalThis.document?.activeElement || null; } catch { return null; } };
870
+ const done = (g) => !!(state.answers[g.qIdx]?.custom || state.answers[g.qIdx]?.selected.size);
871
+ const current = () => groups.find((g) => g.section.contains?.(active())) || groups.find((g) => !done(g)) || groups[0] || null;
872
+ const focus = (node) => { try { node?.focus?.(); } catch { /* not focusable here */ } };
873
+ const focusGroup = (g) => focus(g.buttons.find((b) => b.classList.contains('selected')) || g.buttons[0] || g.input);
874
+ /** After a pick by key: the next question still unanswered, else Submit (pressed at once for a lone question). */
875
+ function advance(g) {
876
+ const after = groups.slice(groups.indexOf(g) + 1).find((x) => !done(x)) || groups.find((x) => x !== g && !done(x));
877
+ if (after) { focusGroup(after); return; }
878
+ const submit = state.submitBtn;
879
+ if (!submit || submit.disabled) return;
880
+ if (groups.length === 1) submit.click();
881
+ else focus(submit);
882
+ }
883
+ for (const g of groups) {
884
+ if (!g.options || !g.buttons.length) continue;
885
+ wireOptionGroup(g.options, { options: () => g.buttons, onMove: (btn) => { if (!g.q.multiple && !btn.classList.contains('selected')) btn.click(); } });
886
+ }
887
+ wireCardKeys(card, {
888
+ options: () => current()?.buttons || [],
889
+ onPick: (_i, btn) => {
890
+ const g = current();
891
+ if (state.resolved || !btn || !g) return;
892
+ focus(btn);
893
+ btn.click();
894
+ if (!g.q.multiple) advance(g);
895
+ },
896
+ onComposer: toComposer,
897
+ onEnter: (e) => {
898
+ if (state.resolved) return false;
899
+ const g = groups.find((x) => x.buttons.includes(e.target));
900
+ if (!g || g.q.multiple) return false; // a checkbox toggles, a button presses: their own Enter
901
+ if (!e.target.classList.contains('selected')) e.target.click();
902
+ advance(g);
903
+ return true;
904
+ },
905
+ });
906
+ wireGaze(card, presenter);
907
+ presenter.setHome(groups[0]?.buttons[0] || groups[0]?.input || null);
908
+ for (const g of groups) if (g.input) wireWatch(g.input, presenter);
909
+ }
910
+
810
911
  /**
811
912
  * A card that waits on the user says so the same way everywhere (this card,
812
913
  * the route card): a still blocked cameo before its kind, which the
@@ -922,52 +1023,79 @@ function renderPlanBody(body, n, hooks) {
922
1023
  body.appendChild(planEl);
923
1024
  }
924
1025
 
925
- function renderQuestionBody(body, n, state, t) {
926
- if (n.message) body.appendChild(el('div', 'asst-control-message', n.message));
1026
+ /**
1027
+ * The questions of a card that asks, as the mascot asks them: its words in bubbles (the card's message
1028
+ * first, then each question), the first beside its face with the notch pointing at it, each question's
1029
+ * options as numbered rows (asst-ask.js askOption, radios or checkboxes), and a field for a typed answer.
1030
+ * Picks and typed answers go to `state.answers` as they always did. → the question groups, for the keys.
1031
+ */
1032
+ function renderQuestionBody(body, n, state, t, { presenter = null, onEscape = null } = {}) {
1033
+ let lead = true;
1034
+ /** A bubble; the first one shares a stage row with the mascot's face. */
1035
+ const speech = (node) => {
1036
+ node.classList.add('asst-ask-bubble');
1037
+ if (!lead) return node;
1038
+ lead = false;
1039
+ node.classList.add('is-lead');
1040
+ if (!presenter) return node;
1041
+ const stage = el('div', 'asst-ask-stage');
1042
+ stage.appendChild(presenter.el);
1043
+ stage.appendChild(node);
1044
+ return stage;
1045
+ };
1046
+ // Nothing for it to say (no message, no question text): the face still leads the card.
1047
+ if (presenter && !n.message && !n.questions.some((q) => q.text || q.header)) body.appendChild(presenter.el);
1048
+ const groups = [];
1049
+ if (n.message) body.appendChild(speech(el('div', 'asst-control-message', n.message)));
927
1050
  n.questions.forEach((q, qIdx) => {
928
1051
  const section = el('div', 'asst-control-q');
929
- if (q.header) section.appendChild(el('div', 'asst-control-q-header', q.header));
930
- if (q.text && q.text !== q.header) section.appendChild(el('div', 'asst-control-q-text', q.text));
1052
+ // The mascot says the question; a header that only repeats it is not said twice.
1053
+ if (q.header && q.text && q.text !== q.header) section.appendChild(el('div', 'asst-control-q-header', q.header));
1054
+ if (q.text || q.header) section.appendChild(speech(el('div', 'asst-control-q-text', q.text || q.header)));
931
1055
  if (q.multiple) section.appendChild(el('div', 'asst-control-q-hint', t('assistant.control.multiHint', 'Select all that apply')));
932
1056
  const optionButtons = [];
933
1057
  let customInput = null;
1058
+ let opts = null;
934
1059
  if (q.options.length) {
935
- const opts = el('div', `asst-control-options${q.multiple ? ' multi' : ''}`);
1060
+ opts = el('div', `asst-control-options${q.multiple ? ' multi' : ''}`);
936
1061
  opts.setAttribute('role', q.multiple ? 'group' : 'radiogroup');
937
1062
  opts.setAttribute('aria-label', q.header || q.text || t('assistant.control.question', 'Question'));
938
- for (const opt of q.options) {
939
- const btn = el('button', 'asst-control-option');
940
- btn.type = 'button';
941
- btn.setAttribute('role', q.multiple ? 'checkbox' : 'radio');
942
- btn.setAttribute('aria-checked', 'false');
943
- btn.appendChild(el('span', 'asst-control-option-label', opt.label));
944
- if (opt.description) btn.appendChild(el('span', 'asst-control-option-desc', opt.description));
1063
+ q.options.forEach((opt, oIdx) => {
1064
+ // Shown without a "(Recommended)" suffix, badged instead; the answer keeps the label as written.
1065
+ const shown = splitRecommended(opt.label);
1066
+ const btn = askOption({
1067
+ label: shown.label,
1068
+ description: opt.description,
1069
+ index: oIdx,
1070
+ role: q.multiple ? 'checkbox' : 'radio',
1071
+ badge: shown.recommended ? t('assistant.followups.recommended', 'Recommended') : '',
1072
+ });
1073
+ btn.classList.add('asst-control-option');
945
1074
  btn.addEventListener('click', () => {
946
1075
  if (state.resolved) return;
947
1076
  const ans = state.answers[qIdx];
948
1077
  ans.custom = '';
949
1078
  if (customInput) customInput.value = '';
1079
+ const was = ans.selected.has(opt.label);
950
1080
  if (q.multiple) {
951
- if (ans.selected.has(opt.label)) ans.selected.delete(opt.label); else ans.selected.add(opt.label);
1081
+ if (was) ans.selected.delete(opt.label); else ans.selected.add(opt.label);
952
1082
  } else {
953
1083
  ans.selected = new Set([opt.label]);
954
1084
  }
955
- for (const b of optionButtons) {
956
- const on = ans.selected.has(b.dataset.label);
957
- b.classList.toggle('selected', on);
958
- b.setAttribute('aria-checked', on ? 'true' : 'false');
959
- }
1085
+ for (const b of optionButtons) setOptionChecked(b, ans.selected.has(b.dataset.label));
1086
+ // The mascot answers the pick: a pop for a tick, a nod for a choice.
1087
+ if (!was) presenter?.react(q.multiple ? 'pop' : 'nod');
960
1088
  state.updateSubmit?.();
961
1089
  });
962
1090
  btn.dataset.label = opt.label;
963
1091
  optionButtons.push(btn);
964
1092
  opts.appendChild(btn);
965
- }
1093
+ });
966
1094
  section.appendChild(opts);
967
1095
  }
968
1096
  customInput = document.createElement('input');
969
1097
  customInput.type = q.type === 'number' || q.type === 'integer' ? 'number' : 'text';
970
- customInput.className = 'asst-control-custom';
1098
+ customInput.className = 'asst-control-custom asst-ask-input';
971
1099
  customInput.placeholder = q.options.length
972
1100
  ? t('assistant.control.customAnswer', 'Or type your own answer…')
973
1101
  : t('assistant.control.typeAnswer', 'Type your answer…');
@@ -977,16 +1105,18 @@ function renderQuestionBody(body, n, state, t) {
977
1105
  ans.custom = customInput.value.trim();
978
1106
  if (ans.custom) {
979
1107
  ans.selected.clear();
980
- for (const b of optionButtons) { b.classList.remove('selected'); b.setAttribute('aria-checked', 'false'); }
1108
+ for (const b of optionButtons) setOptionChecked(b, false);
981
1109
  }
982
1110
  state.updateSubmit?.();
983
1111
  });
984
1112
  customInput.addEventListener('keydown', (e) => {
985
1113
  if (e.key === 'Enter' && state.submitBtn && !state.submitBtn.disabled) { e.preventDefault(); state.submitBtn.click(); }
1114
+ if (e.key === 'Escape' && onEscape) { e.preventDefault(); onEscape(); }
986
1115
  e.stopPropagation();
987
1116
  });
988
1117
  section.appendChild(customInput);
989
1118
  body.appendChild(section);
1119
+ groups.push({ q, qIdx, section, options: opts, buttons: optionButtons, input: customInput });
990
1120
  });
991
1121
  // A clarify card also shows what the assistant will assume for the rest, so the user can correct it.
992
1122
  const assumptions = n.clarify?.assumptions || [];
@@ -994,6 +1124,7 @@ function renderQuestionBody(body, n, state, t) {
994
1124
  const list = assumptions.join(' · ');
995
1125
  body.appendChild(el('div', 'asst-control-assume', t('assistant.control.assuming', `Otherwise I'll assume: ${list}`, { list })));
996
1126
  }
1127
+ return groups;
997
1128
  }
998
1129
 
999
1130
  export function describeSuggestion(s) {