synabun 2.0.10 → 2.2.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 (656) hide show
  1. package/CHANGELOG.md +207 -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 +13 -13
  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 +30 -17
  11. package/lib/desktop-app.js +7 -1
  12. package/lib/external-tools.js +7 -6
  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 +302 -0
  24. package/lib/virustotal.mjs +203 -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/budget-judgments.d.ts +1 -1
  37. package/mcp-server/dist/services/budget-judgments.d.ts.map +1 -1
  38. package/mcp-server/dist/services/budget-judgments.js +5 -3
  39. package/mcp-server/dist/services/budget-judgments.js.map +1 -1
  40. package/mcp-server/dist/services/codex-config-heal.d.ts +6 -2
  41. package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
  42. package/mcp-server/dist/services/codex-config-heal.js +81 -28
  43. package/mcp-server/dist/services/codex-config-heal.js.map +1 -1
  44. package/mcp-server/dist/services/identity.d.ts +2 -2
  45. package/mcp-server/dist/services/identity.js +2 -2
  46. package/mcp-server/dist/services/memory-judge-backfill.d.ts +6 -0
  47. package/mcp-server/dist/services/memory-judge-backfill.d.ts.map +1 -1
  48. package/mcp-server/dist/services/memory-judge-backfill.js +7 -1
  49. package/mcp-server/dist/services/memory-judge-backfill.js.map +1 -1
  50. package/mcp-server/dist/services/memory-sessions.d.ts +1 -1
  51. package/mcp-server/dist/services/memory-sessions.d.ts.map +1 -1
  52. package/mcp-server/dist/services/memory-sessions.js +28 -2
  53. package/mcp-server/dist/services/memory-sessions.js.map +1 -1
  54. package/mcp-server/dist/services/neural-interface.d.ts +18 -11
  55. package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
  56. package/mcp-server/dist/services/neural-interface.js +67 -11
  57. package/mcp-server/dist/services/neural-interface.js.map +1 -1
  58. package/mcp-server/dist/services/server-instructions.d.ts +7 -2
  59. package/mcp-server/dist/services/server-instructions.d.ts.map +1 -1
  60. package/mcp-server/dist/services/server-instructions.js +22 -63
  61. package/mcp-server/dist/services/server-instructions.js.map +1 -1
  62. package/mcp-server/dist/services/tool-catalog.d.ts +120 -0
  63. package/mcp-server/dist/services/tool-catalog.d.ts.map +1 -0
  64. package/mcp-server/dist/services/tool-catalog.js +339 -0
  65. package/mcp-server/dist/services/tool-catalog.js.map +1 -0
  66. package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts +12 -0
  67. package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts.map +1 -0
  68. package/mcp-server/dist/services/tool-list-refresh-barrier.js +40 -0
  69. package/mcp-server/dist/services/tool-list-refresh-barrier.js.map +1 -0
  70. package/mcp-server/dist/services/tool-list.d.ts +108 -0
  71. package/mcp-server/dist/services/tool-list.d.ts.map +1 -0
  72. package/mcp-server/dist/services/tool-list.js +334 -0
  73. package/mcp-server/dist/services/tool-list.js.map +1 -0
  74. package/mcp-server/dist/services/tool-runtime.d.ts +161 -0
  75. package/mcp-server/dist/services/tool-runtime.d.ts.map +1 -0
  76. package/mcp-server/dist/services/tool-runtime.js +400 -0
  77. package/mcp-server/dist/services/tool-runtime.js.map +1 -0
  78. package/mcp-server/dist/services/typesafe-config.js +1 -1
  79. package/mcp-server/dist/services/typesafe-config.js.map +1 -1
  80. package/mcp-server/dist/services/whiteboard-geometry.d.ts +26 -2
  81. package/mcp-server/dist/services/whiteboard-geometry.d.ts.map +1 -1
  82. package/mcp-server/dist/services/whiteboard-geometry.js +91 -8
  83. package/mcp-server/dist/services/whiteboard-geometry.js.map +1 -1
  84. package/mcp-server/dist/tools/agents.d.ts +14 -14
  85. package/mcp-server/dist/tools/agents.d.ts.map +1 -1
  86. package/mcp-server/dist/tools/agents.js +29 -19
  87. package/mcp-server/dist/tools/agents.js.map +1 -1
  88. package/mcp-server/dist/tools/browser-batch.d.ts +3 -3
  89. package/mcp-server/dist/tools/choice.d.ts +6 -0
  90. package/mcp-server/dist/tools/choice.d.ts.map +1 -1
  91. package/mcp-server/dist/tools/choice.js +18 -13
  92. package/mcp-server/dist/tools/choice.js.map +1 -1
  93. package/mcp-server/dist/tools/git-tools.js +2 -2
  94. package/mcp-server/dist/tools/git-tools.js.map +1 -1
  95. package/mcp-server/dist/tools/image-tools.d.ts +1 -1
  96. package/mcp-server/dist/tools/image-tools.js +1 -1
  97. package/mcp-server/dist/tools/morelogin.d.ts.map +1 -1
  98. package/mcp-server/dist/tools/morelogin.js +9 -6
  99. package/mcp-server/dist/tools/morelogin.js.map +1 -1
  100. package/mcp-server/dist/tools/skill-tools.d.ts +505 -0
  101. package/mcp-server/dist/tools/skill-tools.d.ts.map +1 -0
  102. package/mcp-server/dist/tools/skill-tools.js +642 -0
  103. package/mcp-server/dist/tools/skill-tools.js.map +1 -0
  104. package/mcp-server/dist/tools/skill.d.ts +7 -0
  105. package/mcp-server/dist/tools/skill.d.ts.map +1 -0
  106. package/mcp-server/dist/tools/skill.js +11 -0
  107. package/mcp-server/dist/tools/skill.js.map +1 -0
  108. package/mcp-server/dist/tools/tool-groups.d.ts +68 -0
  109. package/mcp-server/dist/tools/tool-groups.d.ts.map +1 -0
  110. package/mcp-server/dist/tools/tool-groups.js +147 -0
  111. package/mcp-server/dist/tools/tool-groups.js.map +1 -0
  112. package/mcp-server/dist/tools/whiteboard-format.d.ts.map +1 -1
  113. package/mcp-server/dist/tools/whiteboard-format.js +7 -4
  114. package/mcp-server/dist/tools/whiteboard-format.js.map +1 -1
  115. package/mcp-server/dist/tools/whiteboard-tools.d.ts +64 -13
  116. package/mcp-server/dist/tools/whiteboard-tools.d.ts.map +1 -1
  117. package/mcp-server/dist/tools/whiteboard-tools.js +30 -5
  118. package/mcp-server/dist/tools/whiteboard-tools.js.map +1 -1
  119. package/mcp-server/package-lock.json +2 -2
  120. package/mcp-server/package.json +1 -1
  121. package/mcp-server/src/http.ts +75 -12
  122. package/mcp-server/src/index.ts +147 -154
  123. package/mcp-server/src/preload.ts +12 -12
  124. package/mcp-server/src/services/budget-judgments.ts +5 -3
  125. package/mcp-server/src/services/codex-config-heal.ts +80 -29
  126. package/mcp-server/src/services/identity.ts +2 -2
  127. package/mcp-server/src/services/memory-judge-backfill.ts +7 -1
  128. package/mcp-server/src/services/memory-sessions.ts +24 -3
  129. package/mcp-server/src/services/neural-interface.ts +81 -21
  130. package/mcp-server/src/services/server-instructions.ts +29 -64
  131. package/mcp-server/src/services/tool-catalog.ts +403 -0
  132. package/mcp-server/src/services/tool-list-refresh-barrier.ts +33 -0
  133. package/mcp-server/src/services/tool-list.ts +410 -0
  134. package/mcp-server/src/services/tool-runtime.ts +457 -0
  135. package/mcp-server/src/services/typesafe-config.ts +1 -1
  136. package/mcp-server/src/services/whiteboard-geometry.ts +94 -9
  137. package/mcp-server/src/tools/agents.ts +29 -20
  138. package/mcp-server/src/tools/choice.ts +17 -12
  139. package/mcp-server/src/tools/git-tools.ts +2 -2
  140. package/mcp-server/src/tools/image-tools.ts +1 -1
  141. package/mcp-server/src/tools/morelogin.ts +6 -4
  142. package/mcp-server/src/tools/skill-tools.ts +592 -0
  143. package/mcp-server/src/tools/skill.ts +13 -0
  144. package/mcp-server/src/tools/tool-groups.ts +164 -0
  145. package/mcp-server/src/tools/whiteboard-format.ts +9 -3
  146. package/mcp-server/src/tools/whiteboard-tools.ts +39 -5
  147. package/neural-interface/i18n/en.json +3953 -228
  148. package/neural-interface/i18n/pt-BR.json +3901 -179
  149. package/neural-interface/lib/agent-sandbox-elevation.js +6 -0
  150. package/neural-interface/lib/antigravity/acp-client-host.js +554 -0
  151. package/neural-interface/lib/antigravity/acp-connection.js +299 -0
  152. package/neural-interface/lib/antigravity/bypass.js +100 -0
  153. package/neural-interface/lib/antigravity/catalog.js +273 -0
  154. package/neural-interface/lib/antigravity/home.js +260 -0
  155. package/neural-interface/lib/antigravity/migrate-gemini.js +139 -0
  156. package/neural-interface/lib/antigravity/panel-bridge.js +1058 -0
  157. package/neural-interface/lib/antigravity/policy-hook.mjs +78 -0
  158. package/neural-interface/lib/antigravity/policy.js +74 -0
  159. package/neural-interface/lib/antigravity/tool-call.js +178 -0
  160. package/neural-interface/lib/antigravity/transcripts.js +252 -0
  161. package/neural-interface/lib/antigravity/usage.js +43 -0
  162. package/neural-interface/lib/assistant-api.js +128 -28
  163. package/neural-interface/lib/assistant-brains/antigravity.js +423 -0
  164. package/neural-interface/lib/assistant-brains/claude.js +4 -3
  165. package/neural-interface/lib/assistant-brains/codex-browser-policy-hook.mjs +12 -2
  166. package/neural-interface/lib/assistant-brains/codex-route-gate-hook.mjs +32 -9
  167. package/neural-interface/lib/assistant-brains/codex.js +9 -5
  168. package/neural-interface/lib/assistant-brains/opencode.js +120 -54
  169. package/neural-interface/lib/assistant-brains/opencode2-browser-policy/index.js +28 -13
  170. package/neural-interface/lib/assistant-brains/opencode2-route-gate/index.js +59 -19
  171. package/neural-interface/lib/assistant-budget-planner.js +7 -3
  172. package/neural-interface/lib/assistant-budget-split.js +42 -15
  173. package/neural-interface/lib/assistant-catalog.js +81 -15
  174. package/neural-interface/lib/assistant-config.js +6 -3
  175. package/neural-interface/lib/assistant-dispatch.js +1073 -200
  176. package/neural-interface/lib/assistant-envelope.js +156 -2
  177. package/neural-interface/lib/assistant-evidence.js +80 -0
  178. package/neural-interface/lib/assistant-hidden-models.js +7 -5
  179. package/neural-interface/lib/assistant-persona.js +93 -19
  180. package/neural-interface/lib/assistant-plan-permissions.js +25 -9
  181. package/neural-interface/lib/assistant-route-gate-codex.js +3 -0
  182. package/neural-interface/lib/assistant-route-gate.js +6 -4
  183. package/neural-interface/lib/assistant-router.js +245 -38
  184. package/neural-interface/lib/assistant-runtime.js +214 -30
  185. package/neural-interface/lib/assistant-task-prompt.js +87 -21
  186. package/neural-interface/lib/assistant-tools.js +51 -0
  187. package/neural-interface/lib/assistant-usage.js +29 -0
  188. package/neural-interface/lib/backup-service.js +626 -4
  189. package/neural-interface/lib/backup-zip-reader.js +10 -5
  190. package/neural-interface/lib/backup-zip-writer.js +257 -0
  191. package/neural-interface/lib/browser-execution.js +10 -2
  192. package/neural-interface/lib/browser-guard-cdp.js +84 -0
  193. package/neural-interface/lib/browser-network-guard.js +476 -0
  194. package/neural-interface/lib/browser-policy-roots.js +64 -0
  195. package/neural-interface/lib/browser-tool-policy.js +198 -38
  196. package/neural-interface/lib/claude-agent-bridge.js +11 -2
  197. package/neural-interface/lib/claude-history-cache.js +210 -0
  198. package/neural-interface/lib/claude-hooks-status.js +103 -0
  199. package/neural-interface/lib/claude-model-catalog.js +5 -1
  200. package/neural-interface/lib/claude-temporary.js +5 -11
  201. package/neural-interface/lib/cli-install-api.js +1 -1
  202. package/neural-interface/lib/cli-install.js +163 -154
  203. package/neural-interface/lib/cli-resolver.js +13 -3
  204. package/neural-interface/lib/cli-update-planner.js +52 -41
  205. package/neural-interface/lib/cloudflared/tunnel.js +42 -2
  206. package/neural-interface/lib/codex-mcp-approvals.js +11 -0
  207. package/neural-interface/lib/codex-rollout-reader.js +52 -0
  208. package/neural-interface/lib/codex-worker-app-server.js +191 -0
  209. package/neural-interface/lib/confined-fs.js +30 -3
  210. package/neural-interface/lib/crash-notice-api.js +98 -0
  211. package/neural-interface/lib/editor-completion-opencode.js +68 -68
  212. package/neural-interface/lib/editor-git-api.js +3 -20
  213. package/neural-interface/lib/exec-loop-command.js +13 -10
  214. package/neural-interface/lib/file-explorer-api.js +74 -40
  215. package/neural-interface/lib/file-explorer-watch.js +9 -3
  216. package/neural-interface/lib/git-bash/install.js +52 -6
  217. package/neural-interface/lib/git-bash/manager.js +59 -17
  218. package/neural-interface/lib/git-bash/pins.js +12 -0
  219. package/neural-interface/lib/git-read.js +2 -2
  220. package/neural-interface/lib/git-status-runner.js +390 -0
  221. package/neural-interface/lib/git-status-watchdog.js +40 -0
  222. package/neural-interface/lib/greeting/budgets.js +1 -0
  223. package/neural-interface/lib/greeting/engine.js +2 -2
  224. package/neural-interface/lib/greeting/sources.js +10 -12
  225. package/neural-interface/lib/greeting/writer.js +1 -1
  226. package/neural-interface/lib/http-guards.js +8 -2
  227. package/neural-interface/lib/install-details.js +4 -1
  228. package/neural-interface/lib/mcp-agent-servers.js +67 -0
  229. package/neural-interface/lib/mcp-client-config.js +35 -33
  230. package/neural-interface/lib/mcp-external-servers.js +332 -0
  231. package/neural-interface/lib/mcp-installer.js +2 -1
  232. package/neural-interface/lib/{mcp-profile-refresh-coordinator.js → mcp-tools-refresh-coordinator.js} +4 -4
  233. package/neural-interface/lib/memory-session-capture.js +17 -3
  234. package/neural-interface/lib/morelogin.js +177 -25
  235. package/neural-interface/lib/native-loop-providers.js +965 -66
  236. package/neural-interface/lib/native-loop-runtime.js +290 -42
  237. package/neural-interface/lib/opencode-config.js +557 -0
  238. package/neural-interface/lib/opencode-db-schema.js +17 -34
  239. package/neural-interface/lib/opencode-history.js +48 -218
  240. package/neural-interface/lib/opencode-mcp-ready.js +108 -0
  241. package/neural-interface/lib/opencode-native-boundary.js +53 -0
  242. package/neural-interface/lib/opencode-protocol.js +249 -112
  243. package/neural-interface/lib/opencode-runtime-config.js +32 -22
  244. package/neural-interface/lib/opencode-serve-records.js +39 -21
  245. package/neural-interface/lib/opencode-v2-client.js +48 -153
  246. package/neural-interface/lib/opencode-v2-ws-requests.js +36 -210
  247. package/neural-interface/lib/opencode-worker-data.js +27 -0
  248. package/neural-interface/lib/opencode2-client.js +73 -37
  249. package/neural-interface/lib/opencode2-proxy.js +23 -63
  250. package/neural-interface/lib/opencode2-translate.js +51 -2
  251. package/neural-interface/lib/owner-authority.js +249 -0
  252. package/neural-interface/lib/page-session.js +275 -0
  253. package/neural-interface/lib/path-confine.js +12 -9
  254. package/neural-interface/lib/peer-identity.js +793 -0
  255. package/neural-interface/lib/pty-host/core.js +28 -1
  256. package/neural-interface/lib/pty-host/lifetime.js +12 -0
  257. package/neural-interface/lib/pty-host/manager.js +12 -1
  258. package/neural-interface/lib/remote-policy.js +1 -1
  259. package/neural-interface/lib/request-gate.js +20 -2
  260. package/neural-interface/lib/rulesets/api.js +7 -6
  261. package/neural-interface/lib/rulesets/fs-safe.js +1 -1
  262. package/neural-interface/lib/rulesets/installer.js +180 -36
  263. package/neural-interface/lib/rulesets/json-edit.js +1 -1
  264. package/neural-interface/lib/rulesets/render.js +1 -1
  265. package/neural-interface/lib/session-cache-builder.js +81 -31
  266. package/neural-interface/lib/session-title-generator.js +58 -36
  267. package/neural-interface/lib/skills/api-v2.js +495 -0
  268. package/neural-interface/lib/skills/bundle.js +418 -0
  269. package/neural-interface/lib/skills/catalog.js +298 -0
  270. package/neural-interface/lib/skills/creation-api.js +191 -0
  271. package/neural-interface/lib/skills/creation-brief.js +247 -0
  272. package/neural-interface/lib/skills/creation-sources.js +285 -0
  273. package/neural-interface/lib/skills/creation.js +843 -0
  274. package/neural-interface/lib/skills/deploy.js +569 -0
  275. package/neural-interface/lib/skills/digest.js +119 -0
  276. package/neural-interface/lib/skills/discover.js +111 -0
  277. package/neural-interface/lib/skills/draft-confinement.js +216 -0
  278. package/neural-interface/lib/skills/eval-confinement.js +399 -0
  279. package/neural-interface/lib/skills/eval-sandbox.js +280 -0
  280. package/neural-interface/lib/skills/eval-trace.js +135 -0
  281. package/neural-interface/lib/skills/evals-api.js +212 -0
  282. package/neural-interface/lib/skills/evals.js +1573 -0
  283. package/neural-interface/lib/skills/export.js +203 -0
  284. package/neural-interface/lib/skills/frontmatter.js +64 -0
  285. package/neural-interface/lib/skills/health.js +75 -0
  286. package/neural-interface/lib/skills/json-edit.js +165 -0
  287. package/neural-interface/lib/skills/known-bridge-digests.json +441 -0
  288. package/neural-interface/lib/skills/known-bridges.js +96 -0
  289. package/neural-interface/lib/skills/library.js +823 -0
  290. package/neural-interface/lib/skills/local-store.js +21 -0
  291. package/neural-interface/lib/skills/migration.js +433 -0
  292. package/neural-interface/lib/skills/operations.js +45 -0
  293. package/neural-interface/lib/skills/ownership.js +427 -0
  294. package/neural-interface/lib/skills/packages.js +88 -0
  295. package/neural-interface/lib/skills/reviews.js +212 -0
  296. package/neural-interface/lib/skills/revision.js +80 -0
  297. package/neural-interface/lib/skills/roots.js +269 -0
  298. package/neural-interface/lib/skills/runtime-checks.js +358 -0
  299. package/neural-interface/lib/skills/scan-assets.js +252 -0
  300. package/neural-interface/lib/skills/scan-context.js +26 -0
  301. package/neural-interface/lib/skills/scan-shell.js +324 -0
  302. package/neural-interface/lib/skills/scan.js +364 -0
  303. package/neural-interface/lib/skills/scanners.js +54 -0
  304. package/neural-interface/lib/skills/service.js +530 -0
  305. package/neural-interface/lib/skills/telemetry.js +119 -0
  306. package/neural-interface/lib/sqlite-integrity-worker.js +22 -0
  307. package/neural-interface/lib/synabun-version.js +1 -1
  308. package/neural-interface/lib/system-activity.js +1 -1
  309. package/neural-interface/lib/tool-lists.js +124 -0
  310. package/neural-interface/lib/whatsapp/brain.js +7 -3
  311. package/neural-interface/lib/whatsapp/bridge.js +6 -3
  312. package/neural-interface/lib/whatsapp/cards.js +22 -9
  313. package/neural-interface/lib/whatsapp/config.js +1 -1
  314. package/neural-interface/lib/worker-confinement.js +1268 -0
  315. package/neural-interface/lib/worker-guard-setting.js +56 -0
  316. package/neural-interface/lib/worker-mcp-channel.js +175 -0
  317. package/neural-interface/lib/worker-question.js +74 -0
  318. package/neural-interface/lib/worker-relay-ports.js +227 -0
  319. package/neural-interface/lib/worker-resume.js +238 -0
  320. package/neural-interface/lib/worker-sandbox.js +329 -0
  321. package/neural-interface/package-lock.json +34 -12
  322. package/neural-interface/package.json +3 -2
  323. package/neural-interface/public/index.html +3 -1
  324. package/neural-interface/public/index2d.html +19 -11
  325. package/neural-interface/public/onboarding.html +3 -3
  326. package/neural-interface/public/shared/account-menu.js +8 -5
  327. package/neural-interface/public/shared/agent-runtime-options.js +125 -31
  328. package/neural-interface/public/shared/agy/agy-bypass.js +47 -0
  329. package/neural-interface/public/shared/agy/agy-client.js +266 -0
  330. package/neural-interface/public/shared/agy/agy-commands.js +120 -0
  331. package/neural-interface/public/shared/agy/agy-copy.js +326 -0
  332. package/neural-interface/public/shared/agy/agy-diff.js +161 -0
  333. package/neural-interface/public/shared/agy/agy-events.js +469 -0
  334. package/neural-interface/public/shared/agy/agy-greeting.js +26 -0
  335. package/neural-interface/public/shared/agy/agy-options.js +232 -0
  336. package/neural-interface/public/shared/agy/agy-panel.js +2543 -0
  337. package/neural-interface/public/shared/agy/agy-render.js +392 -0
  338. package/neural-interface/public/shared/agy/agy-setup.js +149 -0
  339. package/neural-interface/public/shared/agy/agy-styles.js +604 -0
  340. package/neural-interface/public/shared/agy/agy-tabs-logic.js +181 -0
  341. package/neural-interface/public/shared/api.js +1527 -1336
  342. package/neural-interface/public/shared/app-frame.js +192 -0
  343. package/neural-interface/public/shared/assistant/asst-agents-dock.js +36 -6
  344. package/neural-interface/public/shared/assistant/asst-api.js +1 -1
  345. package/neural-interface/public/shared/assistant/asst-ask-styles.js +270 -0
  346. package/neural-interface/public/shared/assistant/asst-ask-tail.js +118 -0
  347. package/neural-interface/public/shared/assistant/asst-ask.js +397 -0
  348. package/neural-interface/public/shared/assistant/asst-brain-picker.js +105 -44
  349. package/neural-interface/public/shared/assistant/asst-budget.js +5 -4
  350. package/neural-interface/public/shared/assistant/asst-control.js +205 -26
  351. package/neural-interface/public/shared/assistant/asst-followups-card.js +383 -0
  352. package/neural-interface/public/shared/assistant/asst-followups.js +417 -0
  353. package/neural-interface/public/shared/assistant/asst-hosts.js +16 -7
  354. package/neural-interface/public/shared/assistant/asst-limit-dock.js +45 -0
  355. package/neural-interface/public/shared/assistant/asst-limit-wait.js +327 -0
  356. package/neural-interface/public/shared/assistant/asst-limit.js +168 -0
  357. package/neural-interface/public/shared/assistant/asst-mascot.js +49 -12
  358. package/neural-interface/public/shared/assistant/asst-menu.js +7 -6
  359. package/neural-interface/public/shared/assistant/asst-panel.js +151 -19
  360. package/neural-interface/public/shared/assistant/asst-rack-styles.js +66 -49
  361. package/neural-interface/public/shared/assistant/asst-render.js +445 -82
  362. package/neural-interface/public/shared/assistant/asst-route.js +304 -53
  363. package/neural-interface/public/shared/assistant/asst-routes-editor.js +9 -8
  364. package/neural-interface/public/shared/assistant/asst-scenes.js +56 -1
  365. package/neural-interface/public/shared/assistant/asst-sidepanel.js +117 -10
  366. package/neural-interface/public/shared/assistant/asst-slash.js +17 -9
  367. package/neural-interface/public/shared/assistant/asst-state.js +87 -24
  368. package/neural-interface/public/shared/assistant/asst-stop.js +127 -0
  369. package/neural-interface/public/shared/assistant/asst-styles.js +93 -21
  370. package/neural-interface/public/shared/assistant/asst-tool-kinds.js +190 -23
  371. package/neural-interface/public/shared/assistant/asst-usage.js +2 -1
  372. package/neural-interface/public/shared/automation-context.js +5 -2
  373. package/neural-interface/public/shared/cc-dropdown.js +89 -19
  374. package/neural-interface/public/shared/cdx/cdx-context-menu.js +10 -9
  375. package/neural-interface/public/shared/cdx/cdx-copy.js +16 -0
  376. package/neural-interface/public/shared/cdx/cdx-output-model.js +22 -1
  377. package/neural-interface/public/shared/cdx/cdx-output.js +34 -10
  378. package/neural-interface/public/shared/cdx/cdx-panel.js +59 -151
  379. package/neural-interface/public/shared/cdx/cdx-preview.js +12 -0
  380. package/neural-interface/public/shared/cdx/cdx-protocol.js +39 -11
  381. package/neural-interface/public/shared/cdx/cdx-render.js +57 -38
  382. package/neural-interface/public/shared/cdx/cdx-requests.js +14 -4
  383. package/neural-interface/public/shared/cdx/cdx-skill-hints.js +54 -0
  384. package/neural-interface/public/shared/cdx/cdx-styles.js +15 -3
  385. package/neural-interface/public/shared/cdx/cdx-tabs.js +127 -44
  386. package/neural-interface/public/shared/cdx/cdx-ws.js +0 -1
  387. package/neural-interface/public/shared/cli-install-controller.js +218 -0
  388. package/neural-interface/public/shared/cli-install-view.js +11 -12
  389. package/neural-interface/public/shared/cli-status.js +11 -10
  390. package/neural-interface/public/shared/constants.js +5 -4
  391. package/neural-interface/public/shared/context-menu-areas.js +34 -0
  392. package/neural-interface/public/shared/context-menu-logic.js +243 -0
  393. package/neural-interface/public/shared/context-menu-model.js +367 -0
  394. package/neural-interface/public/shared/context-menu.js +372 -0
  395. package/neural-interface/public/shared/cp/cp-context-menu.js +10 -9
  396. package/neural-interface/public/shared/cp/cp-restore.js +162 -0
  397. package/neural-interface/public/shared/cp/cp-tool-cards.js +10 -3
  398. package/neural-interface/public/shared/crash-notice-view.js +168 -0
  399. package/neural-interface/public/shared/desktop-bridge.js +17 -0
  400. package/neural-interface/public/shared/desktop-titlebar-view.js +12 -0
  401. package/neural-interface/public/shared/desktop-titlebar.js +13 -4
  402. package/neural-interface/public/shared/desktop-update-view.js +21 -0
  403. package/neural-interface/public/shared/editor/editor-panel-logic.js +2 -2
  404. package/neural-interface/public/shared/explorer/fx-api.js +4 -4
  405. package/neural-interface/public/shared/explorer/fx-links.js +3 -3
  406. package/neural-interface/public/shared/explorer/fx-logic.js +1 -1
  407. package/neural-interface/public/shared/explorer/fx-main.js +48 -16
  408. package/neural-interface/public/shared/explorer/fx-tree.js +1 -0
  409. package/neural-interface/public/shared/greeting/greeting-card.js +2 -2
  410. package/neural-interface/public/shared/greeting/greeting-view.js +1 -0
  411. package/neural-interface/public/shared/html-shell.js +30 -15
  412. package/neural-interface/public/shared/i18n.js +2 -0
  413. package/neural-interface/public/shared/ocp-v2/ocp-v2-approvals.js +7 -7
  414. package/neural-interface/public/shared/ocp-v2/ocp-v2-binding.js +4 -4
  415. package/neural-interface/public/shared/ocp-v2/ocp-v2-changes-logic.js +3 -101
  416. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +2 -0
  417. package/neural-interface/public/shared/ocp-v2/ocp-v2-compact-button.js +1 -3
  418. package/neural-interface/public/shared/ocp-v2/ocp-v2-composer-logic.js +2 -6
  419. package/neural-interface/public/shared/ocp-v2/ocp-v2-context-menu.js +11 -11
  420. package/neural-interface/public/shared/ocp-v2/ocp-v2-context-model.js +0 -3
  421. package/neural-interface/public/shared/ocp-v2/ocp-v2-events.js +8 -4
  422. package/neural-interface/public/shared/ocp-v2/ocp-v2-manager.js +7 -7
  423. package/neural-interface/public/shared/ocp-v2/ocp-v2-outcome-notifications.js +16 -0
  424. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +74 -259
  425. package/neural-interface/public/shared/ocp-v2/ocp-v2-plan.js +1 -2
  426. package/neural-interface/public/shared/ocp-v2/ocp-v2-projectbar.js +95 -202
  427. package/neural-interface/public/shared/ocp-v2/ocp-v2-rehydrate.js +13 -4
  428. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +13 -22
  429. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +4 -4
  430. package/neural-interface/public/shared/ocp-v2/ocp-v2-session-actions.js +8 -61
  431. package/neural-interface/public/shared/ocp-v2/ocp-v2-sessions-logic.js +2 -91
  432. package/neural-interface/public/shared/ocp-v2/ocp-v2-slash-hints.js +16 -15
  433. package/neural-interface/public/shared/ocp-v2/ocp-v2-state.js +8 -21
  434. package/neural-interface/public/shared/ocp-v2/ocp-v2-status-logic.js +1 -99
  435. package/neural-interface/public/shared/ocp-v2/ocp-v2-status.js +5 -35
  436. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +17 -21
  437. package/neural-interface/public/shared/ocp-v2/{ocp-v2-profile-inheritance.js → ocp-v2-tools-inheritance.js} +16 -16
  438. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +60 -50
  439. package/neural-interface/public/shared/page-session.js +96 -0
  440. package/neural-interface/public/shared/provider-icons.js +4 -2
  441. package/neural-interface/public/shared/registry.js +5 -3
  442. package/neural-interface/public/shared/retired-2d.js +3 -0
  443. package/neural-interface/public/shared/schedules-view.js +195 -61
  444. package/neural-interface/public/shared/settings/settings-claude-hooks.js +135 -0
  445. package/neural-interface/public/shared/settings/settings-data.js +3 -1
  446. package/neural-interface/public/shared/settings/settings-ia.js +6 -6
  447. package/neural-interface/public/shared/settings/settings-opencode.js +89 -0
  448. package/neural-interface/public/shared/settings/settings-rules.js +2 -2
  449. package/neural-interface/public/shared/settings/settings-skill-folders-pane.js +265 -0
  450. package/neural-interface/public/shared/settings/settings-skill-folders.js +130 -0
  451. package/neural-interface/public/shared/settings/settings-worker-guard.js +72 -0
  452. package/neural-interface/public/shared/showcase-privacy.js +6 -1
  453. package/neural-interface/public/shared/sidepanel-session-status.js +3 -3
  454. package/neural-interface/public/shared/skills/ss-args.js +96 -0
  455. package/neural-interface/public/shared/skills/ss-creation.js +222 -0
  456. package/neural-interface/public/shared/skills/ss-dialog.js +133 -0
  457. package/neural-interface/public/shared/skills/ss-doc.js +173 -0
  458. package/neural-interface/public/shared/skills/ss-eval-adapters.js +269 -0
  459. package/neural-interface/public/shared/skills/ss-eval-grade.js +359 -0
  460. package/neural-interface/public/shared/skills/ss-eval-optimise.js +153 -0
  461. package/neural-interface/public/shared/skills/ss-eval-plan.js +315 -0
  462. package/neural-interface/public/shared/skills/ss-evals.js +417 -0
  463. package/neural-interface/public/shared/skills/ss-form.js +159 -0
  464. package/neural-interface/public/shared/skills/ss-frontmatter.js +376 -0
  465. package/neural-interface/public/shared/skills/ss-ids.js +32 -0
  466. package/neural-interface/public/shared/skills/ss-lint.js +149 -0
  467. package/neural-interface/public/shared/skills/ss-presets.js +117 -0
  468. package/neural-interface/public/shared/skills/ss-profiles.js +513 -0
  469. package/neural-interface/public/shared/skills/ss-resources.js +165 -0
  470. package/neural-interface/public/shared/skills/ss-review-view.js +190 -0
  471. package/neural-interface/public/shared/skills/ss-tabs.js +79 -0
  472. package/neural-interface/public/shared/skills/ss-unicode.js +135 -0
  473. package/neural-interface/public/shared/skills/studio/sx-actions.js +222 -0
  474. package/neural-interface/public/shared/skills/studio/sx-cm.js +101 -0
  475. package/neural-interface/public/shared/skills/studio/sx-create-ai-view.js +749 -0
  476. package/neural-interface/public/shared/skills/studio/sx-create-ai.js +237 -0
  477. package/neural-interface/public/shared/skills/studio/sx-create-view.js +254 -0
  478. package/neural-interface/public/shared/skills/studio/sx-create.js +126 -0
  479. package/neural-interface/public/shared/skills/studio/sx-diff-view.js +43 -0
  480. package/neural-interface/public/shared/skills/studio/sx-diff.js +133 -0
  481. package/neural-interface/public/shared/skills/studio/sx-docs.js +480 -0
  482. package/neural-interface/public/shared/skills/studio/sx-dom.js +230 -0
  483. package/neural-interface/public/shared/skills/studio/sx-edit-view.js +848 -0
  484. package/neural-interface/public/shared/skills/studio/sx-editor-model.js +193 -0
  485. package/neural-interface/public/shared/skills/studio/sx-errors.js +22 -0
  486. package/neural-interface/public/shared/skills/studio/sx-fixes-view.js +359 -0
  487. package/neural-interface/public/shared/skills/studio/sx-fixes.js +327 -0
  488. package/neural-interface/public/shared/skills/studio/sx-geometry.js +82 -0
  489. package/neural-interface/public/shared/skills/studio/sx-import.js +262 -0
  490. package/neural-interface/public/shared/skills/studio/sx-inspector.js +308 -0
  491. package/neural-interface/public/shared/skills/studio/sx-install-flow.js +282 -0
  492. package/neural-interface/public/shared/skills/studio/sx-install.js +313 -0
  493. package/neural-interface/public/shared/skills/studio/sx-library-view.js +748 -0
  494. package/neural-interface/public/shared/skills/studio/sx-library.js +479 -0
  495. package/neural-interface/public/shared/skills/studio/sx-main.js +690 -0
  496. package/neural-interface/public/shared/skills/studio/sx-operations-view.js +98 -0
  497. package/neural-interface/public/shared/skills/studio/sx-operations.js +11 -0
  498. package/neural-interface/public/shared/skills/studio/sx-plan.js +158 -0
  499. package/neural-interface/public/shared/skills/studio/sx-publish-view.js +621 -0
  500. package/neural-interface/public/shared/skills/studio/sx-review-view.js +369 -0
  501. package/neural-interface/public/shared/skills/studio/sx-review.js +148 -0
  502. package/neural-interface/public/shared/skills/studio/sx-scan.js +35 -0
  503. package/neural-interface/public/shared/skills/studio/sx-snippets.js +96 -0
  504. package/neural-interface/public/shared/skills/studio/sx-subject.js +249 -0
  505. package/neural-interface/public/shared/skills/studio/sx-test-view.js +994 -0
  506. package/neural-interface/public/shared/skills/studio/sx-test.js +328 -0
  507. package/neural-interface/public/shared/skills/studio/sx-versions-view.js +241 -0
  508. package/neural-interface/public/shared/skills/studio/sx-workspace-view.js +426 -0
  509. package/neural-interface/public/shared/state.js +1 -1
  510. package/neural-interface/public/shared/streaming-markdown.js +58 -0
  511. package/neural-interface/public/shared/styleguide/sg-panel.js +4 -2
  512. package/neural-interface/public/shared/styles.css +1822 -1756
  513. package/neural-interface/public/shared/synabun-mascot.js +1115 -9
  514. package/neural-interface/public/shared/text-cache.js +29 -0
  515. package/neural-interface/public/shared/tool-list-picker.js +55 -0
  516. package/neural-interface/public/shared/tool-list-storage.js +24 -0
  517. package/neural-interface/public/shared/tool-list-view.js +112 -0
  518. package/neural-interface/public/shared/topright-overflow-logic.js +54 -0
  519. package/neural-interface/public/shared/tour/tour-actor.js +268 -0
  520. package/neural-interface/public/shared/tour/tour-beats.js +336 -0
  521. package/neural-interface/public/shared/tour/tour-engine.js +884 -0
  522. package/neural-interface/public/shared/tour/tour-ink.js +390 -0
  523. package/neural-interface/public/shared/tour/tour-stage.js +440 -0
  524. package/neural-interface/public/shared/tour/tour-state.js +40 -0
  525. package/neural-interface/public/shared/tour/tour-tips.js +99 -0
  526. package/neural-interface/public/shared/tour/tour.css +144 -0
  527. package/neural-interface/public/shared/ui-antigravity-panel.js +19 -0
  528. package/neural-interface/public/shared/ui-assistant.js +2 -0
  529. package/neural-interface/public/shared/ui-automation-studio.js +111 -140
  530. package/neural-interface/public/shared/ui-claude-panel.js +376 -82
  531. package/neural-interface/public/shared/ui-cli-banner.js +23 -167
  532. package/neural-interface/public/shared/ui-command-runner.js +6 -3
  533. package/neural-interface/public/shared/ui-context-menu.js +295 -0
  534. package/neural-interface/public/shared/ui-cost-widget.js +57 -59
  535. package/neural-interface/public/shared/ui-crash-notice.js +187 -0
  536. package/neural-interface/public/shared/ui-detail.js +15 -3
  537. package/neural-interface/public/shared/ui-file-explorer.js +48 -6
  538. package/neural-interface/public/shared/ui-file-links.js +2 -2
  539. package/neural-interface/public/shared/ui-games.js +2 -2
  540. package/neural-interface/public/shared/ui-image-gallery.js +50 -47
  541. package/neural-interface/public/shared/ui-judgments.js +1 -1
  542. package/neural-interface/public/shared/ui-keybinds.js +33 -2
  543. package/neural-interface/public/shared/ui-link.js +3 -3
  544. package/neural-interface/public/shared/ui-menubar.js +10 -20
  545. package/neural-interface/public/shared/ui-native-loop-router-state.js +5 -0
  546. package/neural-interface/public/shared/ui-native-loop-router.js +11 -1
  547. package/neural-interface/public/shared/ui-navbar.js +49 -24
  548. package/neural-interface/public/shared/ui-notifications.js +1 -0
  549. package/neural-interface/public/shared/ui-panels.js +96 -65
  550. package/neural-interface/public/shared/ui-resume.js +9 -7
  551. package/neural-interface/public/shared/ui-schedules-studio.js +546 -431
  552. package/neural-interface/public/shared/ui-scroll-follow.js +16 -5
  553. package/neural-interface/public/shared/ui-sessions.js +27 -15
  554. package/neural-interface/public/shared/ui-settings.js +683 -526
  555. package/neural-interface/public/shared/ui-sidepanel-session-entry.js +9 -2
  556. package/neural-interface/public/shared/ui-sidepanel-sessions.js +4 -4
  557. package/neural-interface/public/shared/ui-sidepanel-state.js +23 -12
  558. package/neural-interface/public/shared/ui-sidepanel-tray.js +48 -7
  559. package/neural-interface/public/shared/ui-sidepanel-windows.js +18 -13
  560. package/neural-interface/public/shared/ui-skills.js +33 -2830
  561. package/neural-interface/public/shared/ui-styleguide.js +5 -2
  562. package/neural-interface/public/shared/ui-sync.js +15 -5
  563. package/neural-interface/public/shared/ui-terminal.js +270 -190
  564. package/neural-interface/public/shared/ui-tool-updates.js +6 -4
  565. package/neural-interface/public/shared/ui-tooltip.js +81 -51
  566. package/neural-interface/public/shared/ui-topright-overflow.js +233 -0
  567. package/neural-interface/public/shared/ui-trash.js +6 -4
  568. package/neural-interface/public/shared/ui-tutorial-layout.js +3 -117
  569. package/neural-interface/public/shared/ui-tutorial-preview.js +103 -0
  570. package/neural-interface/public/shared/ui-tutorial-steps.js +3 -65
  571. package/neural-interface/public/shared/ui-tutorial.css +47 -100
  572. package/neural-interface/public/shared/ui-tutorial.js +285 -509
  573. package/neural-interface/public/shared/ui-update.js +38 -2
  574. package/neural-interface/public/shared/ui-whiteboard.js +312 -59
  575. package/neural-interface/public/shared/ui-worker-end.js +5 -1
  576. package/neural-interface/public/shared/ui-workspaces.js +3 -2
  577. package/neural-interface/public/shared/view-variant.js +66 -0
  578. package/neural-interface/public/shared/whatsapp/wa-view.js +3 -3
  579. package/neural-interface/public/shared/whiteboard-list.js +172 -0
  580. package/neural-interface/public/shared/worker-end.js +10 -3
  581. package/neural-interface/public/shared/worker-questions.js +34 -0
  582. package/neural-interface/public/shared/worker-restart.js +12 -0
  583. package/neural-interface/public/sidepanel-session.html +2 -2
  584. package/neural-interface/public/variant/2d/background.js +2 -1
  585. package/neural-interface/public/variant/2d/context-menu.js +8 -2
  586. package/neural-interface/public/variant/2d/graph.js +42 -20
  587. package/neural-interface/public/variant/2d/hulls.js +14 -9
  588. package/neural-interface/public/variant/2d/lasso.js +11 -7
  589. package/neural-interface/public/variant/2d/main.js +32 -1
  590. package/neural-interface/public/variant/2d/minimap.js +11 -13
  591. package/neural-interface/public/variant/3d/main.js +69 -6
  592. package/neural-interface/public/variant/3d/map-controls.js +24 -9
  593. package/neural-interface/public/vendor/caveat/OFL.txt +93 -0
  594. package/neural-interface/public/vendor/caveat/caveat-latin-400-normal.woff2 +0 -0
  595. package/neural-interface/public/vendor/caveat/caveat-latin-700-normal.woff2 +0 -0
  596. package/neural-interface/public/vendor/caveat/caveat.css +17 -0
  597. package/neural-interface/public/vendor/caveat/manifest.json +12 -0
  598. package/neural-interface/public/vendor/yaml/LICENSE +13 -0
  599. package/neural-interface/public/vendor/yaml/manifest.json +4 -0
  600. package/neural-interface/public/vendor/yaml/yaml-2.9.1.min.js +137 -0
  601. package/neural-interface/scripts/build-vendor.mjs +32 -0
  602. package/neural-interface/server.js +4553 -2855
  603. package/neural-interface/templates/rulesets/README.md +1 -1
  604. package/neural-interface/templates/rulesets/core.md +1 -1
  605. package/neural-interface/templates/rulesets/legacy-hashes.json +23 -5
  606. package/neural-interface/templates/rulesets/manifest.json +8 -8
  607. package/npm-shrinkwrap.json +34 -12
  608. package/package.json +17 -3
  609. package/postinstall.js +9 -4
  610. package/preuninstall.js +1 -1
  611. package/setup.js +97 -322
  612. package/skills/skill-forge/SKILL.md +177 -0
  613. package/skills/skill-forge/evals/evals.json +54 -0
  614. package/skills/skill-forge/references/package.md +105 -0
  615. package/skills/skill-forge/references/review-and-recovery.md +63 -0
  616. package/skills/skill-forge/references/runtime-profiles.md +20 -0
  617. package/skills/skill-forge/references/sources-and-capabilities.md +51 -0
  618. package/skills/synabun/SKILL.md +10 -7
  619. package/skills/synabun/modules/audit.md +1 -5
  620. package/skills/synabun/modules/changelog.md +1 -5
  621. package/skills/synabun/modules/idea.md +1 -1
  622. package/skills/synabun/modules/memorize.md +6 -9
  623. package/skills/synabun/modules/schedule.md +6 -6
  624. package/mcp-server/dist/services/profiles.d.ts +0 -120
  625. package/mcp-server/dist/services/profiles.d.ts.map +0 -1
  626. package/mcp-server/dist/services/profiles.js +0 -387
  627. package/mcp-server/dist/services/profiles.js.map +0 -1
  628. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts +0 -97
  629. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts.map +0 -1
  630. package/mcp-server/dist/tools/leonardo-browser-tools.js +0 -228
  631. package/mcp-server/dist/tools/leonardo-browser-tools.js.map +0 -1
  632. package/mcp-server/dist/tools/leonardo.d.ts +0 -13
  633. package/mcp-server/dist/tools/leonardo.d.ts.map +0 -1
  634. package/mcp-server/dist/tools/leonardo.js +0 -21
  635. package/mcp-server/dist/tools/leonardo.js.map +0 -1
  636. package/mcp-server/dist/tools/profile.d.ts +0 -17
  637. package/mcp-server/dist/tools/profile.d.ts.map +0 -1
  638. package/mcp-server/dist/tools/profile.js +0 -114
  639. package/mcp-server/dist/tools/profile.js.map +0 -1
  640. package/mcp-server/src/services/profiles.ts +0 -428
  641. package/mcp-server/src/tools/leonardo-browser-tools.ts +0 -291
  642. package/mcp-server/src/tools/leonardo.ts +0 -30
  643. package/mcp-server/src/tools/profile.ts +0 -122
  644. package/neural-interface/lib/assistant-brains/opencode-browser-policy.js +0 -47
  645. package/neural-interface/lib/assistant-brains/opencode-route-gate.js +0 -62
  646. package/neural-interface/lib/mcp-profile-servers.js +0 -120
  647. package/neural-interface/public/shared/ui-tutorial-draw.js +0 -311
  648. package/skills/leonardo/SKILL.md +0 -168
  649. package/skills/leonardo/icon.svg +0 -1
  650. package/skills/leonardo/modules/browser-guide.md +0 -378
  651. package/skills/leonardo/modules/image-prompter.md +0 -283
  652. package/skills/leonardo/modules/model-advisor.md +0 -100
  653. package/skills/leonardo/modules/prompt-library.md +0 -110
  654. package/skills/leonardo/modules/style-guide.md +0 -186
  655. package/skills/leonardo/modules/video-prompter.md +0 -317
  656. /package/neural-interface/templates/rulesets/{gemini.md → antigravity.md} +0 -0
@@ -0,0 +1,118 @@
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun Assistant — the open card at the bottom of the transcript
3
+ // ═══════════════════════════════════════════
4
+ // A card that waits on the user (a follow-ups set, a question, a clarify, a route) must not be
5
+ // buried by what streams in after it: while it is open it is drawn in a slot under every other
6
+ // row (still above the "Working…" row), and rows appended meanwhile go above it. Once answered,
7
+ // dismissed or expired it goes back where it belongs: a follow-ups card under its message, a
8
+ // control or route card in the transcript. Cards are moved, never redrawn, so the picks, the
9
+ // typed answer, focus and the mascot's rig survive the move; a home row that was pruned (or a
10
+ // cleared transcript) takes its card with it.
11
+ //
12
+ // createAskTail(container, { anchor: () => node | null, slotClass? })
13
+ // → { pin(handle), sync(), insert(node), clear() }
14
+ // `handle`: { el, isOpen() → bool } (asst-render.js adds seq / onChange). `anchor` is the row
15
+ // that stays last (the "Working…" row); `insert` places a new row above the open cards, or
16
+ // above that row when none waits.
17
+
18
+ export const ASK_TAIL_CLASS = 'asst-ask-tail';
19
+
20
+ export function createAskTail(container, { anchor = () => null, slotClass = ASK_TAIL_CLASS } = {}) {
21
+ const pinned = new Set(); // { el, isOpen, home: { parent, next } | null, at: 'slot' | 'home' | null }
22
+ let slot = null;
23
+ let seq = 0;
24
+
25
+ const anchorNode = () => {
26
+ const node = anchor();
27
+ return node && node.parentNode === container ? node : null;
28
+ };
29
+
30
+ function place(node, before = null) {
31
+ if (!node) return;
32
+ if (before && before.parentNode === container) {
33
+ if (node.parentNode === container && node.nextSibling === before) return; // already where it belongs
34
+ container.insertBefore(node, before);
35
+ } else {
36
+ if (node.parentNode === container && node === container.lastChild) return; // already last
37
+ container.appendChild(node);
38
+ }
39
+ }
40
+
41
+ function ensureSlot() {
42
+ if (!slot || slot.parentNode !== container) {
43
+ slot = container.ownerDocument.createElement('div');
44
+ slot.className = slotClass;
45
+ }
46
+ return slot;
47
+ }
48
+
49
+ /** The card back where it was: its own row (a follow-ups card), else the transcript above the open cards. */
50
+ function restore(handle) {
51
+ const { el, home } = handle;
52
+ const parent = home?.parent;
53
+ if (!parent?.isConnected) { el.remove(); return; }
54
+ // A direct row goes back above every card that still waits, never under one: the open slot is last.
55
+ if (parent === container) { place(el, slot?.parentNode === container ? slot : anchorNode()); return; }
56
+ if (home.next && home.next.parentNode === parent) parent.insertBefore(el, home.next);
57
+ else parent.appendChild(el);
58
+ }
59
+
60
+ /** Every open card in the slot, in the order they appeared; every closed one back home. */
61
+ function sync() {
62
+ for (const handle of [...pinned]) {
63
+ if (!handle.el?.isConnected) { pinned.delete(handle); continue; }
64
+ // Its message row was pruned (the transcript cap): the card goes with it, never waits alone.
65
+ if (handle.at === 'slot' && handle.home?.parent && !handle.home.parent.isConnected) {
66
+ handle.el.remove();
67
+ pinned.delete(handle);
68
+ }
69
+ }
70
+ const open = [];
71
+ for (const handle of pinned) {
72
+ if (handle.isOpen?.()) { open.push(handle); continue; }
73
+ if (handle.at !== 'slot') continue;
74
+ restore(handle);
75
+ handle.at = 'home';
76
+ }
77
+ if (!open.length) {
78
+ slot?.remove();
79
+ slot = null;
80
+ return;
81
+ }
82
+ const box = ensureSlot();
83
+ place(box, anchorNode());
84
+ open.sort((a, b) => a.seq - b.seq);
85
+ const order = open.map((handle) => handle.el);
86
+ const same = box.childNodes.length === order.length && order.every((el, i) => box.childNodes[i] === el);
87
+ if (!same) for (const el of order) box.appendChild(el);
88
+ for (const handle of open) handle.at = 'slot';
89
+ }
90
+
91
+ /** A card that waits on the user joins the bottom slot now; sync() puts it back once it closes. */
92
+ function pin(handle) {
93
+ if (!handle?.el) return handle;
94
+ handle.seq ||= (seq += 1);
95
+ if (pinned.has(handle)) { sync(); return handle; }
96
+ handle.home = handle.el.parentNode ? { parent: handle.el.parentNode, next: handle.el.nextSibling } : null;
97
+ handle.at = null;
98
+ pinned.add(handle);
99
+ sync();
100
+ return handle;
101
+ }
102
+
103
+ /** A new transcript row: above the open cards (and above the "Working…" row when it is last). */
104
+ function insert(node) {
105
+ place(node, slot?.parentNode === container ? slot : anchorNode());
106
+ return node;
107
+ }
108
+
109
+ /** Forget the cards and put them back home (a cleared transcript keeps none). */
110
+ function clear() {
111
+ for (const handle of pinned) if (handle.at === 'slot') restore(handle);
112
+ pinned.clear();
113
+ slot?.remove();
114
+ slot = null;
115
+ }
116
+
117
+ return { pin, sync, insert, clear, get slot() { return slot; } };
118
+ }
@@ -0,0 +1,397 @@
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun Assistant — the cards the mascot presents: the shared kit
3
+ // ═══════════════════════════════════════════
4
+ // What a finished turn offers (the follow-ups card, asst-followups-card.js)
5
+ // and what the brain asks (the question cards of asst-control.js: agent_clarify,
6
+ // AskUserQuestion, Codex's choice and requestUserInput, OpenCode's question)
7
+ // are drawn the same way: the mascot beside the card, presenting, asking and
8
+ // reacting, its words in a bubble and the answers under it. This module is the
9
+ // part they share:
10
+ //
11
+ // - createPresenter(): the mascot's slot. A painted still (paintMascot +
12
+ // stillFrame, never a rig) until the renderer makes it live (setLive): then a
13
+ // rig from synabun-mascot.js, drawn by the one shared ticker and parked off
14
+ // screen by the rig's own observer. Only the newest open card of a panel is
15
+ // live, and none while calm (replay, reduced motion, a drag): asst-render.js
16
+ // activateRigs. `target` is what the panel's director (asst-mascot.js) dresses
17
+ // while this presenter is the character on screen; `waits` marks a card that
18
+ // waits on the user itself (a question card), which the director never
19
+ // poses "blocked" at its own card.
20
+ // - askOption() / setOptionChecked(): one option row (key · label · description
21
+ // · badge), the same markup in both kinds of card.
22
+ // - optionKeyIntent() (pure) and the wiring around it: one tab stop per option
23
+ // group (arrows, Home, End), 1-9 pick an option while focus is in the card,
24
+ // Esc goes back to the composer; the mascot looks at the option pointed at
25
+ // or focused (wireGaze) and watches a text field being typed in (wireWatch).
26
+ // Nothing touches the DOM at import: the server imports asst-control.js
27
+ // (WhatsApp cards) and node:test draws cards with a stand-in document.
28
+
29
+ import * as mascotLib from '../synabun-mascot.js';
30
+ import { isReducedMotion } from '../synabun-ticker.js';
31
+ import { lookToward } from './asst-mascot.js';
32
+ import { injectAskStyles } from './asst-ask-styles.js';
33
+
34
+ export { injectAskStyles };
35
+
36
+ /** The presenter's SVG, before the sheet's container queries shrink it. */
37
+ export const PRESENTER_SIZE = Object.freeze({ width: 64, height: 32 });
38
+ const HOP_MS = 320; // the hop (300 ms) and a frame to land on
39
+ const WATCH_IDLE_MS = 1500; // a field nobody typed in for this long: back to the card's own pose
40
+ const WATCH_EVERY_MS = 120; // the caret is looked at no more often than this
41
+ // Where a still looks: toward what the card offers (the answers sit right of the face and under it).
42
+ const STILL_LOOK = Object.freeze({ present: { x: 0.5, y: 0.35 }, pick: { x: 0.5, y: 0.25 }, ask: { x: 0.35, y: -0.15 }, success: { x: 0, y: 0 }, idle: { x: 0, y: 0 } });
43
+
44
+ function el(tag, className, text) {
45
+ const node = document.createElement(tag);
46
+ if (className) node.className = className;
47
+ if (text != null) node.textContent = text;
48
+ return node;
49
+ }
50
+
51
+ function reduced() {
52
+ try { return !!isReducedMotion(); } catch { return false; }
53
+ }
54
+
55
+ const centre = (node) => {
56
+ try {
57
+ const r = node?.getBoundingClientRect?.();
58
+ return r && r.width ? { x: r.left + r.width / 2, y: r.top + r.height / 2 } : null;
59
+ } catch { return null; }
60
+ };
61
+
62
+ /** A text field: typing there never picks an option (1-9 are its characters). */
63
+ export function isTextField(node) {
64
+ const tag = String(node?.tagName || '').toUpperCase();
65
+ return tag === 'INPUT' || tag === 'TEXTAREA' || !!node?.isContentEditable;
66
+ }
67
+
68
+ /**
69
+ * createPresenter({ pose, options, waits, entrance })
70
+ * → { el, target, setLive(on), isLive(), setPose(name, opts), react(name), lookAt(node|null), setHome(node),
71
+ * watch(point), unwatch(), settle(pose, opts, { hop }), destroy() }
72
+ * `pose` / `options`: the card's own pose (present, pick, ask). `entrance`: the reaction it plays the first
73
+ * time it goes live (none on a card drawn while calm). settle(): the card is answered or left behind: the
74
+ * still it ends on ('success' is the "got it" face), after the hop when `hop` and it is live.
75
+ */
76
+ export function createPresenter({ pose = 'present', options = null, waits = false, entrance = null } = {}) {
77
+ const face = el('span', 'asst-ask-face');
78
+ face.setAttribute('aria-hidden', 'true');
79
+ let want = { pose, options: options && typeof options === 'object' ? { ...options } : {} };
80
+ let rig = null;
81
+ let held = false; // the director dresses the rig (asst-mascot.js hold / restore)
82
+ let watching = false;
83
+ let watchedAt = -Infinity;
84
+ let final = null; // { pose, opts }: settled, a still from now on
85
+ let leaving = 0; // the hop plays out before the settled still
86
+ let destroyed = false;
87
+ let home = null; // what it looks at when nothing is pointed at
88
+ let looking = null; // what it looks at now
89
+ let entered = false;
90
+
91
+ const stillOpts = (name, opts = {}) => ({ ...opts, look: opts.look || STILL_LOOK[name] || { x: 0, y: 0 } });
92
+
93
+ function dropRig() {
94
+ if (!rig) return;
95
+ try { rig.destroy(); } catch { /* gone */ }
96
+ rig = null;
97
+ }
98
+
99
+ function paintStill(name, opts) {
100
+ dropRig();
101
+ try {
102
+ face.innerHTML = mascotLib.mascotSvg({ width: PRESENTER_SIZE.width, height: PRESENTER_SIZE.height, className: 'synabun-mascot asst-still' });
103
+ const svg = face.firstElementChild;
104
+ if (svg && typeof svg.setAttribute === 'function') {
105
+ svg.setAttribute('data-pose', name);
106
+ mascotLib.paintMascot(svg, mascotLib.stillFrame(name, stillOpts(name, opts)));
107
+ }
108
+ } catch { /* no SVG here (a stand-in document): the card reads without its face */ }
109
+ }
110
+
111
+ function look(node) {
112
+ looking = node || null;
113
+ if (!rig || held || watching || final) return;
114
+ const point = centre(looking || home);
115
+ if (!point) return;
116
+ let box = null;
117
+ try { box = rig.el.getBoundingClientRect(); } catch { box = null; }
118
+ const to = lookToward(box, point);
119
+ try { rig.look(to.x, to.y); } catch { /* gone */ }
120
+ }
121
+
122
+ function applyWant() {
123
+ if (!rig || held || watching || final) return;
124
+ try { rig.setPose(want.pose, want.options); } catch { /* keeps its pose */ }
125
+ look(looking);
126
+ }
127
+
128
+ function setLive(on) {
129
+ if (destroyed || final) return;
130
+ if (on && !rig) {
131
+ try {
132
+ face.textContent = '';
133
+ rig = mascotLib.createMascot(face, { width: PRESENTER_SIZE.width, height: PRESENTER_SIZE.height, pose: want.pose, poseOptions: want.options });
134
+ } catch { rig = null; paintStill(want.pose, want.options); return; }
135
+ look(looking);
136
+ if (!entered) {
137
+ entered = true;
138
+ if (entrance && !reduced()) { try { rig.react(entrance); } catch { /* still */ } }
139
+ }
140
+ } else if (!on && rig) {
141
+ watching = false;
142
+ paintStill(want.pose, want.options);
143
+ }
144
+ }
145
+
146
+ function settle(name = 'success', opts = {}, { hop = false } = {}) {
147
+ if (destroyed) return;
148
+ final = { pose: name, opts };
149
+ watching = false;
150
+ face.classList.add('is-settled');
151
+ if (leaving) { clearTimeout(leaving); leaving = 0; }
152
+ if (hop && rig && !reduced()) {
153
+ try { rig.react('hop'); } catch { /* lands at once */ }
154
+ leaving = setTimeout(() => { leaving = 0; if (!destroyed && final) paintStill(final.pose, final.opts); }, HOP_MS);
155
+ return;
156
+ }
157
+ paintStill(name, opts);
158
+ }
159
+
160
+ const target = {
161
+ kind: 'card',
162
+ waits: !!waits,
163
+ get rig() { return rig; },
164
+ get el() { return rig ? rig.el : face; },
165
+ hold(on) { held = !!on; },
166
+ restore() { applyWant(); },
167
+ };
168
+
169
+ paintStill(want.pose, want.options);
170
+
171
+ return {
172
+ el: face,
173
+ target,
174
+ setLive,
175
+ isLive: () => !!rig && !final,
176
+ /** The card's own pose; while the director holds the rig (or a field is watched) it waits for its turn. */
177
+ setPose(name, opts = null) {
178
+ want = { pose: name, options: opts && typeof opts === 'object' ? { ...opts } : {} };
179
+ if (final || destroyed) return;
180
+ if (rig) applyWant();
181
+ else paintStill(want.pose, want.options);
182
+ },
183
+ react(name) {
184
+ if (!rig || final || destroyed || reduced()) return;
185
+ try { rig.react(name); } catch { /* still */ }
186
+ },
187
+ /** Eyes on `node` (an option, a button); null: back to what it offers first. */
188
+ lookAt: look,
189
+ setHome(node) { home = node || null; if (!looking) look(null); },
190
+ /** Someone types in the card's field: the watch pose, its eyes on the caret (`point`, client px). */
191
+ watch(point) {
192
+ if (!rig || held || final || !point) return;
193
+ if (!watching) {
194
+ watching = true;
195
+ try { rig.setPose('watch'); } catch { /* keeps its pose */ }
196
+ }
197
+ const now = Date.now();
198
+ if (now - watchedAt < WATCH_EVERY_MS) return;
199
+ watchedAt = now;
200
+ let box = null;
201
+ try { box = rig.el.getBoundingClientRect(); } catch { box = null; }
202
+ const to = lookToward(box, point, { horizontal: true });
203
+ try { rig.look(to.x, to.y); } catch { /* gone */ }
204
+ },
205
+ unwatch() {
206
+ if (!watching) return;
207
+ watching = false;
208
+ applyWant();
209
+ },
210
+ settle,
211
+ destroy() {
212
+ if (destroyed) return;
213
+ destroyed = true;
214
+ if (leaving) { clearTimeout(leaving); leaving = 0; }
215
+ dropRig();
216
+ },
217
+ };
218
+ }
219
+
220
+ // ── Options ──
221
+
222
+ /**
223
+ * One option row: [key] label · description [badge] [↵]. `role` 'radio' / 'checkbox' (or none: a plain
224
+ * action button). The key shows its number shortcut (1-9); a checked checkbox shows ✓ instead.
225
+ */
226
+ export function askOption({ label, description = '', index = 0, role = '', badge = '', go = false, title = '' } = {}) {
227
+ const btn = el('button', 'asst-ask-option');
228
+ btn.type = 'button';
229
+ btn.dataset.index = String(index);
230
+ btn.setAttribute('data-ask-option', '');
231
+ if (role) {
232
+ btn.setAttribute('role', role);
233
+ btn.setAttribute('aria-checked', 'false');
234
+ }
235
+ if (title) btn.title = title;
236
+ const key = el('span', 'asst-ask-key', index < 9 ? String(index + 1) : '');
237
+ key.setAttribute('aria-hidden', 'true');
238
+ btn.appendChild(key);
239
+ const main = el('span', 'asst-ask-main');
240
+ main.appendChild(el('span', 'asst-ask-label', label));
241
+ if (description) main.appendChild(el('span', 'asst-ask-desc', description));
242
+ btn.appendChild(main);
243
+ if (badge) btn.appendChild(el('span', 'asst-ask-badge', badge));
244
+ if (go) {
245
+ const arrow = el('span', 'asst-ask-go', '↵');
246
+ arrow.setAttribute('aria-hidden', 'true');
247
+ btn.appendChild(arrow);
248
+ }
249
+ btn._askKey = key;
250
+ return btn;
251
+ }
252
+
253
+ /** Check or clear an option row (aria-checked, the gold, a checkbox's ✓). */
254
+ export function setOptionChecked(btn, on) {
255
+ if (!btn) return;
256
+ btn.classList.toggle('selected', !!on);
257
+ if (btn.getAttribute?.('role') || btn.attributes?.role) btn.setAttribute('aria-checked', on ? 'true' : 'false');
258
+ const key = btn._askKey;
259
+ const role = btn.getAttribute?.('role') || btn.attributes?.role || '';
260
+ if (key && role === 'checkbox') {
261
+ const index = Number(btn.dataset.index);
262
+ key.textContent = on ? '✓' : (index < 9 ? String(index + 1) : '');
263
+ }
264
+ }
265
+
266
+ /**
267
+ * What a key does in a card: { composer } (Esc: back to the message box), { pick: i } (1-9, never while
268
+ * typing in a field), { move: i } (arrows, Home, End inside an option group: `index` is the focused option,
269
+ * -1 when focus is elsewhere), or null.
270
+ */
271
+ export function optionKeyIntent({ key, index = -1, count = 0, typing = false, alt = false, ctrl = false, meta = false } = {}) {
272
+ if (key === 'Escape') return { composer: true };
273
+ if (typing || alt || ctrl || meta) return null;
274
+ if (/^[1-9]$/.test(String(key))) {
275
+ const i = Number(key) - 1;
276
+ return i < count ? { pick: i } : null;
277
+ }
278
+ if (index < 0 || count < 1) return null;
279
+ switch (key) {
280
+ case 'ArrowDown':
281
+ case 'ArrowRight': return { move: (index + 1) % count };
282
+ case 'ArrowUp':
283
+ case 'ArrowLeft': return { move: (index - 1 + count) % count };
284
+ case 'Home': return { move: 0 };
285
+ case 'End': return { move: count - 1 };
286
+ default: return null;
287
+ }
288
+ }
289
+
290
+ const usable = (btn) => !!btn && !btn.disabled && !btn.hidden;
291
+
292
+ /**
293
+ * One tab stop for an option group (roving tabindex): the checked option, else the first. Arrows, Home and
294
+ * End move focus; `onMove(button, index)` follows it (a radio group checks the option, as radios do).
295
+ */
296
+ export function wireOptionGroup(group, { options = () => [], onMove = null } = {}) {
297
+ const all = () => (typeof options === 'function' ? options() : []) || [];
298
+ function sync(current = null) {
299
+ const list = all();
300
+ const checked = list.find((b) => (b.getAttribute?.('aria-checked') || b.attributes?.['aria-checked']) === 'true');
301
+ const pick = list.includes(current) ? current : (checked || list.find(usable) || list[0]);
302
+ for (const b of list) b.tabIndex = b === pick ? 0 : -1;
303
+ }
304
+ group.addEventListener('keydown', (e) => {
305
+ const list = all().filter(usable);
306
+ const index = list.indexOf(e.target);
307
+ if (index < 0) return;
308
+ const intent = optionKeyIntent({ key: e.key, index, count: list.length, alt: e.altKey, ctrl: e.ctrlKey, meta: e.metaKey });
309
+ if (!intent || intent.move == null) return;
310
+ e.preventDefault();
311
+ e.stopPropagation();
312
+ const next = list[intent.move];
313
+ sync(next);
314
+ try { next.focus(); } catch { /* not focusable here */ }
315
+ onMove?.(next, intent.move);
316
+ });
317
+ group.addEventListener('focusin', (e) => { if (all().includes(e.target)) sync(e.target); });
318
+ sync();
319
+ return { sync };
320
+ }
321
+
322
+ /**
323
+ * The card's own keys, wherever focus is inside it: 1-9 pick an option of `options()` (the group in play),
324
+ * Esc goes back to the composer, Enter is `onEnter(event)`'s (true: handled). A key a group or a field
325
+ * already handled (defaultPrevented) is left alone.
326
+ */
327
+ export function wireCardKeys(card, { options = () => [], onPick = null, onComposer = null, onEnter = null } = {}) {
328
+ card.addEventListener('keydown', (e) => {
329
+ if (e.defaultPrevented) return;
330
+ const list = ((typeof options === 'function' ? options() : []) || []).filter(usable);
331
+ const typing = isTextField(e.target);
332
+ const intent = optionKeyIntent({ key: e.key, count: list.length, typing, alt: e.altKey, ctrl: e.ctrlKey, meta: e.metaKey });
333
+ if (intent?.composer) {
334
+ e.preventDefault();
335
+ e.stopPropagation();
336
+ onComposer?.();
337
+ return;
338
+ }
339
+ if (intent?.pick != null) {
340
+ e.preventDefault();
341
+ e.stopPropagation();
342
+ onPick?.(intent.pick, list[intent.pick]);
343
+ return;
344
+ }
345
+ if (e.key === 'Enter' && typeof onEnter === 'function' && onEnter(e) === true) {
346
+ e.preventDefault();
347
+ e.stopPropagation();
348
+ }
349
+ });
350
+ }
351
+
352
+ /** The presenter looks at the option pointed at or focused inside `root`, and at what it offers first when none is. */
353
+ export function wireGaze(root, presenter, { selector = '[data-ask-option]' } = {}) {
354
+ const optionOf = (node) => {
355
+ const hit = node?.closest?.(selector);
356
+ return hit && root.contains?.(hit) ? hit : null;
357
+ };
358
+ const focusedOption = () => {
359
+ try { return optionOf(document.activeElement); } catch { return null; }
360
+ };
361
+ root.addEventListener('pointerover', (e) => { const hit = optionOf(e.target); if (hit) presenter.lookAt(hit); });
362
+ root.addEventListener('pointerleave', () => presenter.lookAt(focusedOption()));
363
+ root.addEventListener('focusin', (e) => presenter.lookAt(optionOf(e.target)));
364
+ root.addEventListener('focusout', (e) => { if (!root.contains?.(e.relatedTarget)) presenter.lookAt(null); });
365
+ }
366
+
367
+ /** Where a text field's caret is, roughly (client px): its left edge plus the typed width, mid-height. */
368
+ function caretOf(input) {
369
+ try {
370
+ const r = input.getBoundingClientRect();
371
+ const typed = String(input.value || '').slice(0, input.selectionStart ?? undefined).length;
372
+ return { x: r.left + Math.min(r.width - 12, 12 + typed * 7), y: r.top + r.height / 2 };
373
+ } catch { return null; }
374
+ }
375
+
376
+ /** The presenter watches `input` while it is typed in (its eyes on the caret), and goes back to its pose 1.5 s after. */
377
+ export function wireWatch(input, presenter) {
378
+ let timer = 0;
379
+ const stop = () => { if (timer) clearTimeout(timer); timer = 0; presenter.unwatch(); };
380
+ input.addEventListener('input', () => {
381
+ presenter.watch(caretOf(input));
382
+ if (timer) clearTimeout(timer);
383
+ timer = setTimeout(stop, WATCH_IDLE_MS);
384
+ });
385
+ input.addEventListener('blur', stop);
386
+ }
387
+
388
+ /** "1–4" for four options, "1" for one: the keys a hint names. */
389
+ export function keyRange(count) {
390
+ const n = Math.max(0, Math.min(9, Number(count) || 0));
391
+ return n > 1 ? `1–${n}` : n === 1 ? '1' : '';
392
+ }
393
+
394
+ /** The key that sends a multi-select from anywhere in its card, as the platform names it. */
395
+ export function sendModifier() {
396
+ try { return /Mac|iPhone|iPad/.test(globalThis.navigator?.platform || globalThis.navigator?.userAgent || '') ? '⌘' : 'Ctrl'; } catch { return 'Ctrl'; }
397
+ }