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
@@ -10,13 +10,15 @@
10
10
  // normalizeRouteRequest / routeOptions / buildRouteResponse / routeLineParts
11
11
  // are pure (node:test covers them); nothing touches `document` at import time.
12
12
  //
13
+ // The card's Here / Dispatch switch reads the packet's `sides` / `defaults`
14
+ // (routeCardSides; derived from the options' kinds for an older server).
13
15
  // Wire contract: answer = control_response { kind:'route', optionId, target?,
14
16
  // remember, decline? }; "Other…" sends optionId:'other' + target. Invalid
15
17
  // answers come back as { type:'error', code:'ROUTE_INVALID' } — the card stays
16
18
  // open. `synabun.route` events (phase card|auto|decided|pending|expired|
17
19
  // declined|cancelled) update the line in place by routeId.
18
20
 
19
- import { getProviderMeta } from '../provider-icons.js';
21
+ import { fetchClaudeAccounts, fetchCodexAccounts, getEffortLevelsForModel } from '../agent-runtime-options.js';
20
22
  import { fetchManageCatalog, patchHiddenModels } from './asst-api.js';
21
23
  import { catalogModelRow, fmtModelPrice, hiddenModelFor, ICON_CARET, invalidateModelCatalog, openModelMenu } from './asst-brain-picker.js';
22
24
  // The Routes editor's model lists (where they come from, how they stay current): asst-route-models.js.
@@ -27,12 +29,17 @@ import { accountUsageText, autoAccountLabel } from '../account-usage.js';
27
29
  import {
28
30
  DEFAULT_ROUTE_MODE,
29
31
  fmtConfidence,
32
+ modelShortName,
30
33
  normalizeBrain,
31
34
  normalizeProvider,
32
35
  normalizeRouteMode,
36
+ providerLabel,
37
+ providerMeta,
33
38
  ROUTE_MODES,
34
39
  routeKindLabel,
35
40
  routeTargetLabel,
41
+ OPTIONAL_PROVIDERS,
42
+ UNPRICED_PROVIDERS,
36
43
  } from './asst-state.js';
37
44
 
38
45
  function str(v) { return v == null ? '' : String(v).trim(); }
@@ -107,9 +114,13 @@ function normalizeRouteOption(o) {
107
114
  provider: normalizeProvider(src.provider, str(src.provider) || null),
108
115
  model: str(src.model),
109
116
  effort: str(src.effort) || null,
110
- ...(Object.hasOwn(src, 'accountId') ? { accountId: src.accountId, account: obj(src.account), accountError: obj(src.accountError) } : {}),
117
+ ...(Object.hasOwn(src, 'accountId') ? { accountId: src.accountId, accountChoice: src.accountChoice || null, account: obj(src.account), accountError: obj(src.accountError) } : {}),
118
+ // 'direct' on a worker ("here" on an account other than this conversation's): it runs on a worker.
119
+ accountKind: str(src.accountKind) || null,
111
120
  label: str(src.label),
112
121
  badge: str(src.badge) || null,
122
+ // Always ask: the user's saved route for the class (its side starts on it; the card still asks).
123
+ saved: src.saved === true,
113
124
  tier: str(src.tier) || null,
114
125
  vision: src.vision === true,
115
126
  price: priceOf(src.price),
@@ -122,6 +133,13 @@ function normalizeRouteOption(o) {
122
133
  };
123
134
  }
124
135
 
136
+ function normalizeRouteSides(sides) {
137
+ const src = obj(sides);
138
+ if (!src) return null;
139
+ const side = (s) => (obj(s) ? { available: s.available !== false, reason: str(s.reason) || null, code: str(s.code) || null } : null);
140
+ return { direct: side(src.direct), dispatch: side(src.dispatch) };
141
+ }
142
+
125
143
  /** Normalize a route control packet (or its `request`) into the card model. */
126
144
  export function normalizeRouteRequest(packet) {
127
145
  const p = obj(packet);
@@ -161,6 +179,9 @@ export function normalizeRouteRequest(packet) {
161
179
  },
162
180
  options: arr(req.options).map(normalizeRouteOption).filter(Boolean),
163
181
  defaultOptionId: str(req.defaultOptionId) || null,
182
+ // The Here / Dispatch switch, as the router read it (null from a server older than the switch).
183
+ sides: normalizeRouteSides(req.sides),
184
+ defaults: obj(req.defaults) ? { direct: str(req.defaults.direct) || null, dispatch: str(req.defaults.dispatch) || null } : null,
164
185
  other: {
165
186
  allowed: other.allowed !== false,
166
187
  providers: arr(other.providers).map(String),
@@ -219,15 +240,25 @@ export function routeReasonLabels(n, t) {
219
240
  return arr(n?.reasons).map(r => reasonLabel(r, tr, n?.requires)).filter(Boolean);
220
241
  }
221
242
 
243
+ /** The word for a route kind in the user's language: "here", "dispatch", "computer". */
244
+ export function routeKindWord(kind, t = null) {
245
+ const key = str(kind).toLowerCase();
246
+ const word = key === 'direct' ? 'here' : key;
247
+ return ['here', 'dispatch', 'computer'].includes(word) ? tf(t)(`assistant.routes.kind.${word}`, routeKindLabel(key)) : routeKindLabel(key);
248
+ }
249
+
222
250
  function badgeText(o, n, tr) {
223
251
  const pct = fmtConfidence(n?.confidence);
252
+ if (o.saved) return tr('assistant.routes.badge.saved', 'Saved route');
224
253
  switch (o.badge) {
225
254
  case 'suggested': return pct ? tr('assistant.routes.badge.suggestedPct', 'Suggested · {pct}', { pct }) : tr('assistant.routes.badge.suggested', 'Suggested');
226
255
  case 'remembered': return tr('assistant.routes.badge.remembered', 'Your choice');
227
- case 'current': return tr('assistant.routes.badge.current', 'Current');
256
+ // On a worker, the model this conversation runs on is "the same model"; here it is the current one.
257
+ case 'current': return o.kind === 'dispatch' ? tr('assistant.routes.badge.sameModel', 'Same model') : tr('assistant.routes.badge.current', 'Current');
228
258
  case 'capable':
229
- if (!n?.requires && n?.visionOnly) return tr('assistant.routes.badge.canSee', 'Can see');
230
- return n?.requires === 'video' ? tr('assistant.routes.badge.makesVideo', 'Makes video') : tr('assistant.routes.badge.makesImages', 'Makes images');
259
+ if (n?.requires) return n.requires === 'video' ? tr('assistant.routes.badge.makesVideo', 'Makes video') : tr('assistant.routes.badge.makesImages', 'Makes images');
260
+ if (n?.visionOnly || n?.needsVision) return tr('assistant.routes.badge.canSee', 'Can see');
261
+ return tr('assistant.routes.badge.canRun', 'Can run it');
231
262
  case 'cheaper': return tr('assistant.routes.badge.cheaper', 'Cheaper');
232
263
  case 'stronger': return tr('assistant.routes.badge.stronger', 'Stronger');
233
264
  default: return o.badge ? humanizeClass(o.badge) : '';
@@ -271,26 +302,78 @@ export function routeOptions(n, { brainLabel = '', t = null } = {}) {
271
302
  if (!o.isHere && o.id !== 'here' && o.kind === 'direct' && brainProvider && o.provider === brainProvider
272
303
  && modelKey(o.model) === modelKey(n.brain?.model)) o.isHere = true;
273
304
  }
274
- const rank = (o) => (o.id === defaultId ? -2 : (o.id === 'here' || o.isHere) ? -1 : (BADGE_RANK[o.badge] ?? 9));
305
+ // Only a direct option is "here": a "here" the account rule moved to a worker (kind "dispatch") is a worker row.
306
+ const hereRow = (o) => o.kind === 'direct' && (o.id === 'here' || !!o.isHere);
307
+ const rank = (o) => (o.id === defaultId ? -2 : hereRow(o) ? -1 : (BADGE_RANK[o.badge] ?? 9));
275
308
  kept.sort((a, b) => rank(a) - rank(b));
276
309
  if (!kept.some(o => o.id === defaultId && !o.disabled)) defaultId = kept.find(o => !o.disabled)?.id || null;
277
310
  const hereModel = brainLabel || routeTargetLabel(n.brain) || tr('assistant.routes.currentModel', 'the current model');
311
+ // The router labels every option done here "Do it here with <model>" (English): the title names the model once,
312
+ // in the user's language ("Do it here with Do it here with Sonnet 5" before).
313
+ const labelOf = (o) => str(o.label).replace(/^do it here with\s+/i, '');
278
314
  return kept.map((o, i) => {
279
- const isHere = o.id === 'here' || !!o.isHere;
315
+ const isHere = hereRow(o);
280
316
  const label = routeTargetLabel(o);
281
317
  return {
282
318
  ...o,
283
319
  number: i + 1,
284
320
  here: isHere,
285
- // "here" is the brain's own model: prefer its display label over a raw id.
286
- title: isHere ? tr('assistant.routes.doHere', 'Do it here with {model}', { model: str(o.label) || hereModel }) : (label || o.id),
321
+ // "here" is the brain's own model: prefer its display label over a raw id. Another model of its
322
+ // provider (a continuation) is done here too, and says so the same way.
323
+ title: isHere ? tr('assistant.routes.doHere', 'Do it here with {model}', { model: labelOf(o) || hereModel })
324
+ : o.kind === 'direct' ? tr('assistant.routes.doHere', 'Do it here with {model}', { model: labelOf(o) || modelShortName(o.model) || o.id }) : (label || o.id),
287
325
  badgeText: badgeText(o, n, tr),
288
- disabledReason: o.budget === 'share_used_up' ? routeBudgetReason({ provider: o.provider }, t) : o.disabledReason,
326
+ disabledReason: o.budget === 'share_used_up' || o.budget === 'unpriced' ? routeBudgetReason({ provider: o.provider, unpriced: o.budget === 'unpriced' }, t) : o.disabledReason,
289
327
  selected: o.id === defaultId,
290
328
  };
291
329
  });
292
330
  }
293
331
 
332
+ export const ROUTE_SIDES = Object.freeze(['direct', 'dispatch']);
333
+
334
+ /**
335
+ * The card's Here / Dispatch switch (pure): `options` (routeOptions) split by where they run, each
336
+ * side numbered from 1, with whether it can be chosen, why not, and the option it starts on.
337
+ * → { initial: 'direct' | 'dispatch', sides: { direct, dispatch } }, a side being
338
+ * { kind, options, available, reason, code, needsPick, defaultId }.
339
+ * The router says which side can run (n.sides) and where each starts (n.defaults); a card from a
340
+ * server older than the switch has neither, so both are read from the options: there, a card with
341
+ * no worker option still has a Dispatch side, empty, that asks for a model (`needsPick`: the model
342
+ * picker answers optionId "other" with a dispatch target, which every server takes).
343
+ */
344
+ export function routeCardSides(n, options, { t = null } = {}) {
345
+ const tr = tf(t);
346
+ const server = n?.sides || null;
347
+ const side = (kind) => {
348
+ const list = arr(options).filter((o) => (o.kind === 'direct' ? 'direct' : 'dispatch') === kind).map((o, i) => ({ ...o, number: i + 1 }));
349
+ const usable = list.filter((o) => !o.disabled);
350
+ const said = server?.[kind] || null;
351
+ const out = { kind, options: list, available: usable.length > 0, reason: null, code: null, needsPick: false, defaultId: null };
352
+ out.defaultId = (usable.find((o) => o.id === n?.defaultOptionId) || usable.find((o) => o.id === n?.defaults?.[kind]) || usable.find((o) => o.selected) || usable[0])?.id || null;
353
+ if (out.available) return out;
354
+ if (kind === 'direct' && n?.dispatchOnly) {
355
+ return { ...out, code: 'worker_only', reason: tr('assistant.routes.side.workerOnly', '{taskClass} always runs on a worker.', { taskClass: n.taskClassLabel || humanizeClass(n.taskClass) }) };
356
+ }
357
+ if (kind === 'direct' && (said?.code === 'account_worker' || arr(options).some((o) => o.kind === 'dispatch' && o.accountKind === 'direct'))) {
358
+ return { ...out, code: 'account_worker', reason: tr('assistant.routes.side.accountWorker', "This conversation's account can't take it now, so it runs on a worker.") };
359
+ }
360
+ // A worker can still be picked by model (an older server sent none, or every one offered is blocked).
361
+ if (kind === 'dispatch' && n?.other?.allowed !== false) {
362
+ return { ...out, available: true, needsPick: true, code: list.length ? 'disabled' : 'none', reason: tr('assistant.routes.side.pickWorker', 'Pick a worker model to run it.') };
363
+ }
364
+ const blocked = list.find((o) => o.disabled);
365
+ return {
366
+ ...out, code: said?.code || (blocked ? 'disabled' : 'none'),
367
+ reason: blocked?.disabledReason || said?.reason || (kind === 'direct' ? tr('assistant.routes.side.noHere', 'Nothing in this conversation can run this task.') : tr('assistant.routes.side.noWorker', 'No worker model can run this task.')),
368
+ };
369
+ };
370
+ const sides = { direct: side('direct'), dispatch: side('dispatch') };
371
+ const chosen = arr(options).find((o) => o.selected && !o.disabled);
372
+ let initial = chosen ? (chosen.kind === 'direct' ? 'direct' : 'dispatch') : null;
373
+ if (!initial || !sides[initial].available) initial = sides.direct.available && sides.direct.defaultId ? 'direct' : 'dispatch';
374
+ return { initial, sides };
375
+ }
376
+
294
377
  /** control_response payload for a route card. `decline:true` is the Cancel answer. */
295
378
  export function buildRouteResponse(n, choice = {}) {
296
379
  if (choice.decline) return { kind: 'route', optionId: null, remember: false, decline: true };
@@ -303,6 +386,7 @@ export function buildRouteResponse(n, choice = {}) {
303
386
  provider: normalizeProvider(target.provider, str(target.provider) || null),
304
387
  model: str(target.model),
305
388
  effort: str(target.effort) || null,
389
+ accountId: str(target.accountId) || null,
306
390
  };
307
391
  }
308
392
  return out;
@@ -367,11 +451,16 @@ export function routeCatalogRow(pref, catalog) {
367
451
  return pref.model ? catalogModelRow(pref.provider, pref.model, catalog) || aliasRow(pref.provider, pref.model, rows) : rows.find(m => m.isDefault) || null;
368
452
  }
369
453
 
370
- /** Budget messages are UI copy; never show the server's English disabledReason. */
371
- export function routeBudgetReason({ provider, dry } = {}, t) {
372
- const tr = tf(t), names = { 'claude-code': 'Claude Code', codex: 'Codex', opencode: 'OpenCode' };
373
- if (provider) return tr('assistant.routes.budgetProvider', "Budget: the {provider} share of this session's budget is used up.", { provider: names[provider] || provider });
374
- const list = [...new Set(arr(dry))].map(id => names[id] || id);
454
+ /**
455
+ * Budget messages are UI copy; never show the server's English disabledReason. `unpriced` (option.budget
456
+ * 'unpriced'): the provider reports tokens and no price, so a session with a budget cannot hold its spend.
457
+ */
458
+ export function routeBudgetReason({ provider, dry, unpriced = false } = {}, t) {
459
+ const tr = tf(t);
460
+ if (provider && unpriced) return tr('assistant.routes.budgetUnpriced', 'Budget: {provider} reports tokens, not prices, so a session with a budget cannot run it. Pick another option, or switch the budget to No limit.', { provider: providerLabel(provider) });
461
+ if (provider) return tr('assistant.routes.budgetProvider', "Budget: the {provider} share of this session's budget is used up.", { provider: providerLabel(provider) });
462
+ // A provider that reports no prices (Antigravity) never had a share under a budget: it is not "used up".
463
+ const list = [...new Set(arr(dry))].filter(id => !UNPRICED_PROVIDERS.includes(id)).map(id => providerLabel(id));
375
464
  if (!list.length) return tr('assistant.routes.budgetSpent', "Budget: this session's budget is spent.");
376
465
  return tr(list.length === 1 ? 'assistant.routes.budgetDryOne' : 'assistant.routes.budgetDryMany',
377
466
  list.length === 1 ? 'Budget: the {list} share is used up.' : 'Budget: the {list} shares are used up.', { list: list.join(', ') });
@@ -413,7 +502,7 @@ export function routeLineParts(route, phase = 'decided', { brainLabel = '', t =
413
502
  return {
414
503
  taskClass: cls,
415
504
  kind,
416
- kindLabel: kind && kind !== 'direct' ? routeKindLabel(kind) : '',
505
+ kindLabel: kind && kind !== 'direct' ? routeKindWord(kind, t) : '',
417
506
  provider: target ? normalizeProvider(target.provider, str(target.provider) || null) : null,
418
507
  targetLabel: target && !state ? routeTargetLabel(target) : '',
419
508
  meta,
@@ -439,6 +528,9 @@ function correctionText(c, tr) {
439
528
  }
440
529
  if (c.reason === 'remembered_route') return tr('assistant.routes.correction.remembered', 'your saved route {to} replaced {from}', { to: str(c.to), from: str(c.from) });
441
530
  if (c.reason === 'remembered_disabled') return tr('assistant.routes.correction.rememberedDisabled', 'saved route {from} is disabled, kept the pick', { from: str(c.from) });
531
+ // A "here" pick that runs on a worker: another account, or another provider than this conversation's.
532
+ if (c.reason === 'account_worker') return tr('assistant.routes.correction.accountWorker', '{to}: runs on a worker, because this conversation belongs to another account', { to: str(c.to) });
533
+ if (c.reason === 'other_provider' && c.provider) return tr('assistant.routes.correction.otherProvider', "{provider} is not this conversation's provider, so it runs on a worker", { provider: providerLabel(c.provider) });
442
534
  const text = firstStr(c.text, c.summary, c.message);
443
535
  if (text) return text;
444
536
  if (c.field && (c.from || c.to)) return `${c.field}: ${str(c.from) || '—'} → ${str(c.to) || 'default'}`;
@@ -483,12 +575,12 @@ function providerIconHtml(provider, hooks) {
483
575
  if (!provider) return '';
484
576
  try {
485
577
  if (typeof hooks.providerIcon === 'function') return hooks.providerIcon(provider) || '';
486
- return getProviderMeta(provider).icon || '';
578
+ return providerMeta(provider).icon || '';
487
579
  } catch { return ''; }
488
580
  }
489
581
 
490
582
  function providerColor(provider) {
491
- try { return getProviderMeta(provider).color || ''; } catch { return ''; }
583
+ try { return providerMeta(provider).color || ''; } catch { return ''; }
492
584
  }
493
585
 
494
586
  let _lineUid = 0;
@@ -709,9 +801,12 @@ export function renderRouteCard(container, input, hooks = {}) {
709
801
  const tr = tf(hooks.t);
710
802
  const brainLabel = () => (typeof hooks.brainLabel === 'function' ? hooks.brainLabel() : '') || routeTargetLabel(n.brain) || tr('assistant.routes.theBrainLower', 'the assistant');
711
803
  const options = routeOptions(n, { brainLabel: brainLabel(), t: hooks.t });
804
+ // Here / Dispatch: each side has its own list, pick and "other" model; the switch decides which is shown and answered.
805
+ const view = routeCardSides(n, options, { t: hooks.t });
712
806
  const state = {
713
- selectedId: (options.find(o => o.selected) || options.find(o => !o.disabled) || {}).id || null,
714
- other: null,
807
+ side: view.initial,
808
+ picks: { direct: view.sides.direct.defaultId, dispatch: view.sides.dispatch.defaultId },
809
+ others: { direct: null, dispatch: null },
715
810
  remember: false,
716
811
  pending: false,
717
812
  resolved: false,
@@ -750,6 +845,25 @@ export function renderRouteCard(container, input, hooks = {}) {
750
845
  card.appendChild(note);
751
846
  }
752
847
 
848
+ // The switch: Here (this conversation) or Dispatch (a worker). A side that cannot run the task stays
849
+ // in view, marked unavailable, with why (worker-only classes, a blind or hidden model, another account).
850
+ const sidesEl = el('div', 'asst-route-sides');
851
+ sidesEl.setAttribute('role', 'radiogroup');
852
+ sidesEl.setAttribute('aria-label', tr('assistant.routes.side.label', 'Where it runs'));
853
+ const sideBtns = {};
854
+ for (const kind of ROUTE_SIDES) {
855
+ const btn = el('button', 'asst-route-side', kind === 'direct' ? tr('assistant.routes.side.here', 'Here') : tr('assistant.routes.side.dispatch', 'Dispatch'));
856
+ btn.type = 'button';
857
+ btn.dataset.side = kind;
858
+ btn.setAttribute('role', 'radio');
859
+ btn.addEventListener('click', () => setSide(kind));
860
+ sideBtns[kind] = btn;
861
+ sidesEl.appendChild(btn);
862
+ }
863
+ const sideNote = el('div', 'asst-route-side-note');
864
+ sideNote.setAttribute('aria-live', 'polite');
865
+ card.append(sidesEl, sideNote);
866
+
753
867
  const list = el('div', 'asst-route-options');
754
868
  list.setAttribute('role', 'radiogroup');
755
869
  list.setAttribute('aria-label', tr('assistant.routes.chooseModel', 'Where should this run?'));
@@ -762,7 +876,7 @@ export function renderRouteCard(container, input, hooks = {}) {
762
876
  row.setAttribute('aria-checked', 'false');
763
877
  row.setAttribute('aria-disabled', o.disabled ? 'true' : 'false');
764
878
  row.tabIndex = -1;
765
- const kindWord = o.kind && o.kind !== 'direct' ? routeKindLabel(o.kind) : '';
879
+ const kindWord = o.kind && o.kind !== 'direct' ? routeKindWord(o.kind, hooks.t) : '';
766
880
  const sub = [kindWord, routeDisplayAccount(o, hooks.t), o.effort && o.effort !== 'off' ? o.effort : '', o.price ? priceText(o.price) : '', o.vision ? tr('assistant.brain.vision', 'vision') : '', o.reason].filter(Boolean).join(' · ');
767
881
  row.innerHTML = `
768
882
  <span class="asst-route-num" aria-hidden="true">${esc(o.number)}</span>
@@ -789,7 +903,11 @@ export function renderRouteCard(container, input, hooks = {}) {
789
903
  return b != null ? `$${a != null ? f(a) : '?'}/$${f(b)}` : `$${f(a)}`;
790
904
  }
791
905
 
792
- const allOptions = () => (state.other ? [...options, state.other] : options);
906
+ // The shown side's options (numbered from 1), then its "other" model when one was picked.
907
+ const sideOptions = () => view.sides[state.side].options;
908
+ const allOptions = () => (state.others[state.side] ? [...sideOptions(), state.others[state.side]] : sideOptions());
909
+ const selectedId = () => state.picks[state.side] || null;
910
+ const busy = () => state.pending || state.resolved || state.expired;
793
911
 
794
912
  function renderOptions() {
795
913
  list.innerHTML = '';
@@ -799,25 +917,120 @@ export function renderRouteCard(container, input, hooks = {}) {
799
917
 
800
918
  function syncSelection() {
801
919
  for (const row of list.querySelectorAll('.asst-route-option')) {
802
- const on = row.dataset.optionId === state.selectedId;
920
+ const on = row.dataset.optionId === selectedId();
803
921
  row.setAttribute('aria-checked', on ? 'true' : 'false');
804
922
  row.classList.toggle('selected', on);
805
923
  row.tabIndex = on ? 0 : -1;
806
924
  }
807
- runBtn.disabled = state.pending || state.resolved || state.expired || !state.selectedId;
925
+ syncFields();
926
+ syncSides();
927
+ runBtn.disabled = busy() || !selectedId();
928
+ }
929
+
930
+ /** The switch, its note (why the other side is unavailable, what a worker side still needs, else what the side means) and the Run label. */
931
+ function syncSides() {
932
+ card.dataset.side = state.side;
933
+ for (const kind of ROUTE_SIDES) {
934
+ const btn = sideBtns[kind];
935
+ const side = view.sides[kind];
936
+ const on = state.side === kind;
937
+ btn.setAttribute('aria-checked', on ? 'true' : 'false');
938
+ btn.classList.toggle('selected', on);
939
+ btn.setAttribute('aria-disabled', side.available && !busy() ? 'false' : 'true');
940
+ btn.tabIndex = on ? 0 : -1;
941
+ if (!side.available && side.reason) btn.setAttribute('title', side.reason);
942
+ else btn.removeAttribute('title');
943
+ }
944
+ const current = view.sides[state.side];
945
+ const other = view.sides[state.side === 'direct' ? 'dispatch' : 'direct'];
946
+ sideNote.textContent = !other.available && other.reason ? other.reason
947
+ : current.needsPick && !selectedId() ? current.reason
948
+ : state.side === 'direct' ? tr('assistant.routes.side.hereDesc', 'Runs in this conversation.') : tr('assistant.routes.side.dispatchDesc', 'A worker runs it; the result comes back here.');
949
+ runBtn.textContent = state.side === 'direct' ? tr('assistant.routes.runHere', 'Run here') : tr('assistant.routes.runDispatch', 'Dispatch');
950
+ }
951
+
952
+ /** Show the other side (its own pick stays as it was). `focus`: 'switch' (arrow keys on it) or 'option'. */
953
+ function setSide(kind, { focus = null } = {}) {
954
+ if (busy() || !view.sides[kind]?.available) return;
955
+ if (state.side !== kind) {
956
+ state.side = kind;
957
+ error.hidden = true;
958
+ renderOptions();
959
+ }
960
+ if (focus === 'switch') sideBtns[kind].focus();
961
+ else if (focus === 'option') entry.focus();
808
962
  }
809
963
 
810
964
  function select(id, { focus = false } = {}) {
811
- if (state.pending || state.resolved) return;
965
+ if (busy()) return;
812
966
  const o = allOptions().find(x => x.id === id);
813
967
  if (!o || o.disabled) return;
814
- state.selectedId = id;
968
+ state.picks[state.side] = id;
815
969
  syncSelection();
816
970
  if (focus) list.querySelector(`.asst-route-option[data-option-id="${CSS.escape(id)}"]`)?.focus();
817
971
  }
818
972
 
819
- // Other model…
820
- const otherBtn = el('button', 'asst-link asst-route-other', tr('assistant.routes.other', 'Other model…'));
973
+ // Task-local controls; these never edit the routing preference table.
974
+ const fields = el('div', 'asst-route-fields');
975
+ function taskField(className) {
976
+ const button = el('button', `asst-dd ${className}`);
977
+ button.type = 'button';
978
+ button.setAttribute('aria-haspopup', 'menu');
979
+ button.innerHTML = `<span class="asst-dd-label"></span><span class="asst-dd-caret" aria-hidden="true">${ICON_CARET}</span>`;
980
+ return button;
981
+ }
982
+ const effortBtn = taskField('asst-route-effort');
983
+ const accountBtn = taskField('asst-route-account');
984
+ const setFieldLabel = (button, text) => { button.querySelector('.asst-dd-label').textContent = text; button.title = text; };
985
+ const selected = () => allOptions().find(o => o.id === selectedId());
986
+ function editTarget(patch) {
987
+ if (busy()) return;
988
+ const from = selected();
989
+ if (!from) return;
990
+ state.others[state.side] = { ...from, accountId: from.accountChoice || from.accountId || null, accountChoice: Object.hasOwn(patch, 'accountId') ? patch.accountId : from.accountChoice || null, disabled: false, disabledReason: null, accountError: null, ...patch, id: 'other', number: sideOptions().length + 1, badge: null, badgeText: tr('assistant.routes.badge.other', 'Your choice') };
991
+ state.picks[state.side] = 'other';
992
+ renderOptions();
993
+ }
994
+ function syncFields() {
995
+ const target = selected();
996
+ setFieldLabel(otherBtn, target ? routeTargetLabel(target) : tr('assistant.routes.other', 'Other model…'));
997
+ otherBtn.setAttribute('aria-label', tr('assistant.brain.model', 'Model'));
998
+ otherBtn.disabled = busy();
999
+ setFieldLabel(effortBtn, target?.effort || tr('assistant.routes.editorEffortAuto', 'Auto'));
1000
+ effortBtn.setAttribute('aria-label', tr('assistant.brain.effort', 'Thinking effort'));
1001
+ setFieldLabel(accountBtn, routeDisplayAccount(target, hooks.t) || tr('assistant.routes.account.workersDefault', 'Workers default'));
1002
+ accountBtn.setAttribute('aria-label', tr('assistant.brain.account', 'Account profile'));
1003
+ const levels = target ? getEffortLevelsForModel(target.provider, catalogModelRow(target.provider, target.model)) : [];
1004
+ effortBtn.disabled = busy() || !target || !levels.some(e => e.id !== 'off');
1005
+ // Here runs on this conversation's own account (another one is a worker): only a worker picks one.
1006
+ accountBtn.hidden = state.side === 'direct' || !['claude-code', 'codex'].includes(target?.provider);
1007
+ accountBtn.disabled = busy();
1008
+ }
1009
+ effortBtn.addEventListener('click', () => {
1010
+ const target = selected();
1011
+ if (!target) return;
1012
+ const levels = getEffortLevelsForModel(target.provider, catalogModelRow(target.provider, target.model));
1013
+ openMenu(effortBtn, { title: tr('assistant.brain.effort', 'Thinking effort'), placement: 'auto', items: [
1014
+ { kind: 'radio', label: tr('assistant.routes.editorEffortAuto', 'Auto'), selected: !target.effort, onSelect: () => editTarget({ effort: null }) },
1015
+ ...levels.filter(e => e.id !== 'off').map(e => ({ kind: 'radio', label: e.label || e.id, selected: target.effort === e.id, onSelect: () => editTarget({ effort: e.id }) })),
1016
+ ] });
1017
+ });
1018
+ accountBtn.addEventListener('click', async () => {
1019
+ const target = selected();
1020
+ if (!target) return;
1021
+ try {
1022
+ const rows = await (target.provider === 'codex' ? fetchCodexAccounts() : fetchClaudeAccounts());
1023
+ if (!card.isConnected || state.pending || state.resolved || state.expired || selected() !== target) return;
1024
+ openMenu(accountBtn, { title: tr('assistant.brain.account', 'Account profile'), placement: 'auto', items: [
1025
+ { kind: 'radio', label: tr('assistant.routes.account.workersDefault', 'Workers default'), selected: !(target.accountChoice || target.accountId), onSelect: () => editTarget({ accountId: null, account: null }) },
1026
+ { kind: 'radio', label: autoAccountLabel(hooks.t), selected: (target.accountChoice || target.accountId) === 'auto', onSelect: () => editTarget({ accountId: 'auto', account: null }) },
1027
+ ...(Array.isArray(rows) ? rows : []).map(a => ({ kind: 'radio', label: accountSummary(a, target.provider === 'codex' ? 'codex' : 'claude'), disabled: a.signedIn === false, selected: (target.accountChoice || target.accountId) === a.id, onSelect: () => editTarget({ accountId: a.id, account: a }) })),
1028
+ ] });
1029
+ } catch (error) { hooks.onToast?.(error.message); }
1030
+ });
1031
+
1032
+ // Model menu, scoped to this task.
1033
+ const otherBtn = taskField('asst-route-other');
821
1034
  otherBtn.type = 'button';
822
1035
  otherBtn.setAttribute('aria-haspopup', 'menu');
823
1036
  otherBtn.hidden = !n.other.allowed;
@@ -825,34 +1038,50 @@ export function renderRouteCard(container, input, hooks = {}) {
825
1038
  e.stopPropagation();
826
1039
  if (isMenuOpen(otherBtn)) { closeMenu(); return; }
827
1040
  const brain = normalizeBrain(hooks.getBrain?.() || { provider: n.brain.provider || 'claude-code', model: n.brain.model });
1041
+ // The menu follows the switch: Here lists this conversation's provider (another model continues it there);
1042
+ // Dispatch lists every provider, and its pick is always a worker.
1043
+ const here = state.side === 'direct' && !n.dispatchOnly;
1044
+ // A worker: the providers the server lists (its CATALOG_PROVIDERS).
1045
+ const workerMenu = {
1046
+ providers: n.other.providers.length ? n.other.providers.map(p => normalizeProvider(p, p)) : null,
1047
+ };
828
1048
  openModelMenu(otherBtn, {
829
1049
  brain,
1050
+ shouldOpen: () => card.isConnected && !busy(),
830
1051
  t: hooks.t,
831
- title: tr('assistant.routes.otherTitle', 'Run on another model'),
1052
+ title: here ? tr('assistant.routes.otherHereTitle', 'Continue here on another model') : tr('assistant.routes.otherTitle', 'Run on another model'),
832
1053
  placement: 'auto',
833
- providers: n.other.providers.length ? n.other.providers.map(p => normalizeProvider(p, p)) : null,
834
- current: state.other ? { provider: state.other.provider, model: state.other.model } : null,
1054
+ ...(here ? { providers: [brain.provider] } : workerMenu),
1055
+ current: selected() ? { provider: selected().provider, model: selected().model } : null,
835
1056
  onToast: hooks.onToast,
836
1057
  // Image / video creation: only models that generate the medium (no custom id: its output is unknown).
837
1058
  ...(n.requires ? { accept: (_p, m) => m?.outputs?.[n.requires] === true, allowCustom: false, emptyLabel: tr('assistant.routes.noCapableModel', 'No model in your catalog can make {medium} yet.', { medium: mediumWords(n.requires, tr) }) } : {}),
838
1059
  // Design: only models that can see.
839
1060
  ...(!n.requires && n.visionOnly ? { accept: (_p, m) => m?.vision === true && m?.status !== 'unavailable', allowCustom: false, emptyLabel: tr('assistant.routes.noSeeingModel', 'No model in your catalog can see images yet.') } : {}),
1061
+ // Computer use: never a model known to be blind.
1062
+ ...(!n.requires && !n.visionOnly && n.needsVision ? { accept: (_p, m) => m?.vision !== false } : {}),
840
1063
  onSelect: (target) => {
841
- if (!target?.model) return;
842
- const kind = n.dispatchOnly || (target.provider && target.provider !== brain.provider) ? 'dispatch' : 'direct';
843
- state.other = {
1064
+ if (!target?.model || !card.isConnected || busy()) return;
1065
+ const previous = selected();
1066
+ const kind = here && target.provider === brain.provider ? 'direct' : 'dispatch';
1067
+ const side = kind === 'direct' ? 'direct' : 'dispatch';
1068
+ state.others[side] = {
844
1069
  id: 'other', kind, provider: target.provider, model: target.model, effort: null,
845
- label: target.label || '', badge: null, number: options.length + 1, here: false,
846
- title: target.label || target.model, badgeText: tr('assistant.routes.badge.other', 'Your choice'),
1070
+ ...(kind === 'dispatch' ? { accountId: previous?.provider === target.provider ? previous.accountChoice || previous.accountId || null : null, account: previous?.provider === target.provider ? previous.account || null : null } : {}),
1071
+ label: target.label || '', badge: null, number: view.sides[side].options.length + 1, here: false,
1072
+ title: kind === 'direct' ? tr('assistant.routes.doHere', 'Do it here with {model}', { model: target.label || target.model }) : (target.label || target.model),
1073
+ badgeText: tr('assistant.routes.badge.other', 'Your choice'),
847
1074
  vision: target.vision === true, price: target.price || null, reason: '', warning: null, disabled: false,
848
1075
  };
849
- state.selectedId = 'other';
1076
+ state.picks[side] = 'other';
1077
+ state.side = side;
850
1078
  renderOptions();
851
1079
  list.querySelector('.asst-route-option[data-option-id="other"]')?.focus();
852
1080
  },
853
1081
  });
854
1082
  });
855
- card.appendChild(otherBtn);
1083
+ fields.append(otherBtn, effortBtn, accountBtn);
1084
+ card.appendChild(fields);
856
1085
 
857
1086
  // Remember for ‹class› tasks
858
1087
  let rememberInput = null;
@@ -880,7 +1109,8 @@ export function renderRouteCard(container, input, hooks = {}) {
880
1109
  cancelBtn.hidden = !n.decline.allowed;
881
1110
  const runBtn = el('button', 'asst-btn asst-btn-primary asst-route-run', tr('assistant.routes.run', 'Run'));
882
1111
  runBtn.type = 'button';
883
- const hint = el('span', 'asst-route-keys', tr('assistant.routes.keys', '1–4 choose · Enter runs · Esc cancels'));
1112
+ const bothSides = view.sides.direct.available && view.sides.dispatch.available;
1113
+ const hint = el('span', 'asst-route-keys', bothSides ? tr('assistant.routes.keysSides', '← → Here or Dispatch · 1–9 choose · Enter runs · Esc cancels') : tr('assistant.routes.keys', '1–4 choose · Enter runs · Esc cancels'));
884
1114
  actions.append(hint, cancelBtn, runBtn);
885
1115
  card.appendChild(actions);
886
1116
 
@@ -896,7 +1126,7 @@ export function renderRouteCard(container, input, hooks = {}) {
896
1126
  }
897
1127
 
898
1128
  function chosenRoute() {
899
- const o = allOptions().find(x => x.id === state.selectedId) || null;
1129
+ const o = selected() || null;
900
1130
  return {
901
1131
  routeId: n.routeId,
902
1132
  taskClass: n.taskClass,
@@ -904,10 +1134,10 @@ export function renderRouteCard(container, input, hooks = {}) {
904
1134
  summary: n.summary,
905
1135
  confidence: n.confidence,
906
1136
  decidedBy: 'user',
907
- optionId: state.selectedId,
1137
+ optionId: selectedId(),
908
1138
  remembered: state.remember,
909
- target: o ? { kind: o.kind || 'direct', provider: o.provider, model: o.model, effort: o.effort, label: o.label || (o.here ? routeTargetLabel(o) : o.title) } : null,
910
- alternatives: allOptions().filter(x => x.id !== state.selectedId).map(x => ({ provider: x.provider, model: x.model, effort: x.effort, label: x.label })),
1139
+ target: o ? { kind: o.kind || 'direct', provider: o.provider, model: o.model, effort: o.effort, accountId: o.accountId || null, label: (o.kind === 'direct' ? str(o.label).replace(/^do it here with\s+/i, '') : o.label) || (o.here ? routeTargetLabel(o) : o.title) } : null,
1140
+ alternatives: allOptions().filter(x => x.id !== selectedId()).map(x => ({ provider: x.provider, model: x.model, effort: x.effort, label: x.label })),
911
1141
  };
912
1142
  }
913
1143
 
@@ -926,6 +1156,9 @@ export function renderRouteCard(container, input, hooks = {}) {
926
1156
  state.line = line;
927
1157
  state.collapsed = true;
928
1158
  state.resolved = true;
1159
+ // No longer waits: the renderer puts it back from the open card's slot at the transcript's bottom
1160
+ // (asst-ask-tail.js) before it becomes the route line in its own place.
1161
+ try { card._ask?.onChange?.(); } catch { /* the renderer is gone */ }
929
1162
  if (card.isConnected) card.replaceWith(line.el);
930
1163
  try { hooks.onCollapse?.(n.routeId, line); } catch { /* ignore */ }
931
1164
  if (hadFocus) { try { hooks.onDone?.(); } catch { /* ignore */ } }
@@ -937,6 +1170,8 @@ export function renderRouteCard(container, input, hooks = {}) {
937
1170
  if (state.line?.el?.isConnected) state.line.el.replaceWith(card);
938
1171
  state.collapsed = false;
939
1172
  state.resolved = false;
1173
+ // Open again (ROUTE_INVALID): it waits on the user, so it goes back to the bottom slot.
1174
+ try { card._ask?.onChange?.(); } catch { /* the renderer is gone */ }
940
1175
  }
941
1176
 
942
1177
  function clearTimers() {
@@ -945,8 +1180,8 @@ export function renderRouteCard(container, input, hooks = {}) {
945
1180
  }
946
1181
 
947
1182
  function submit() {
948
- if (state.pending || state.resolved || state.expired || !state.selectedId) return;
949
- const response = buildRouteResponse(n, { optionId: state.selectedId, target: state.other, remember: state.remember });
1183
+ if (busy() || !selectedId()) return;
1184
+ const response = buildRouteResponse(n, { optionId: selectedId(), target: state.others[state.side], remember: state.remember });
950
1185
  error.hidden = true;
951
1186
  setPending(true, tr('assistant.routes.routing', 'Routing…'));
952
1187
  try { hooks.onRespond?.(response, n); } catch { /* ignore */ }
@@ -955,7 +1190,7 @@ export function renderRouteCard(container, input, hooks = {}) {
955
1190
  }
956
1191
 
957
1192
  function cancel() {
958
- if (state.pending || state.resolved) return;
1193
+ if (state.pending || state.resolved || state.expired) return;
959
1194
  if (!n.decline.allowed) {
960
1195
  // Nothing to cancel on the server: leave the card and hand focus back.
961
1196
  try { hooks.onCancel?.(buildRouteCancel(n, 'user'), n); } catch { /* ignore */ }
@@ -979,11 +1214,18 @@ export function renderRouteCard(container, input, hooks = {}) {
979
1214
  if (o && !o.disabled) { e.preventDefault(); e.stopPropagation(); select(o.id, { focus: true }); }
980
1215
  return;
981
1216
  }
1217
+ // ← → switch Here / Dispatch, from the switch itself or from the list.
1218
+ if ((e.key === 'ArrowLeft' || e.key === 'ArrowRight') && !e.metaKey && !e.ctrlKey && !e.altKey
1219
+ && (e.target?.classList?.contains('asst-route-side') || e.target?.classList?.contains('asst-route-option') || e.target === card)) {
1220
+ e.preventDefault(); e.stopPropagation();
1221
+ setSide(e.key === 'ArrowLeft' ? 'direct' : 'dispatch', { focus: e.target?.classList?.contains('asst-route-side') ? 'switch' : 'option' });
1222
+ return;
1223
+ }
982
1224
  if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
983
1225
  const enabled = all.filter(o => !o.disabled);
984
1226
  if (!enabled.length) return;
985
1227
  e.preventDefault(); e.stopPropagation();
986
- const idx = Math.max(0, enabled.findIndex(o => o.id === state.selectedId));
1228
+ const idx = Math.max(0, enabled.findIndex(o => o.id === selectedId()));
987
1229
  const next = enabled[(idx + (e.key === 'ArrowDown' ? 1 : -1) + enabled.length) % enabled.length];
988
1230
  select(next.id, { focus: true });
989
1231
  return;
@@ -1021,11 +1263,16 @@ export function renderRouteCard(container, input, hooks = {}) {
1021
1263
  state.timer = null;
1022
1264
  countdown.textContent = tr('assistant.routes.state.expired', 'route expired');
1023
1265
  syncSelection();
1266
+ // No answer is coming from the user any more: it goes back to its own place (asst-ask-tail.js).
1267
+ try { card._ask?.onChange?.(); } catch { /* the renderer is gone */ }
1024
1268
  }
1025
1269
  }
1026
- if (expires && Number.isFinite(expires)) { tick(); state.timer = setInterval(tick, 1000); }
1270
+ if (expires && Number.isFinite(expires)) { tick(); if (!state.expired) state.timer = setInterval(tick, 1000); }
1027
1271
 
1028
1272
  renderOptions();
1273
+ // The renderer lifts a card that waits on the user to the transcript's bottom while it is open
1274
+ // (asst-ask-tail.js): a route card waits like a follow-ups set or a question does.
1275
+ card._ask = { el: card, presenter: null, isOpen: () => !state.collapsed && !state.resolved && !state.expired, onChange: null };
1029
1276
  if (container) container.appendChild(card);
1030
1277
  try { hooks.scrollEnd?.(); } catch { /* ignore */ }
1031
1278
 
@@ -1039,7 +1286,8 @@ export function renderRouteCard(container, input, hooks = {}) {
1039
1286
  get line() { return state.line; },
1040
1287
  /** Focus the preselected option (the panel calls this when the composer is empty). */
1041
1288
  focus() {
1042
- const row = list.querySelector('.asst-route-option[aria-checked="true"]') || list.querySelector('.asst-route-option');
1289
+ // A side with nothing listed yet (a worker to pick by model) focuses the switch instead.
1290
+ const row = list.querySelector('.asst-route-option[aria-checked="true"]') || list.querySelector('.asst-route-option') || sideBtns[state.side];
1043
1291
  try { row?.focus({ preventScroll: false }); } catch { row?.focus(); }
1044
1292
  },
1045
1293
  focusIfIdle() { if (typeof hooks.shouldFocus !== 'function' || hooks.shouldFocus()) entry.focus(); },
@@ -1075,7 +1323,7 @@ export { openRoutesEditor } from './asst-routes-editor.js';
1075
1323
 
1076
1324
  // ── Models manager (Assistant → Models…) ───────────────────────────────────
1077
1325
 
1078
- const MODEL_SECTIONS = ['claude-code', 'codex', 'opencode'];
1326
+ const MODEL_SECTIONS = ['claude-code', 'codex', 'opencode', 'antigravity'];
1079
1327
  const MODEL_TABS = ['available', 'archived'];
1080
1328
  let _modelsManager = null;
1081
1329
 
@@ -1300,7 +1548,7 @@ export async function openModelsManager(hooks = {}) {
1300
1548
  }
1301
1549
 
1302
1550
  function sectionHead(provider, countText) {
1303
- const meta = getProviderMeta(provider);
1551
+ const meta = providerMeta(provider);
1304
1552
  const section = el('section', 'asst-routes-section asst-models-section');
1305
1553
  section.dataset.provider = provider;
1306
1554
  const head = el('div', 'asst-routes-section-title asst-models-head');
@@ -1310,8 +1558,11 @@ export async function openModelsManager(hooks = {}) {
1310
1558
  return { section, head };
1311
1559
  }
1312
1560
 
1561
+ // An optional provider's section shows once the server lists it (an older server knows no Antigravity).
1562
+ const shownSections = () => MODEL_SECTIONS.filter(p => !OPTIONAL_PROVIDERS.includes(p) || Array.isArray(state.models?.[p]) || arr(state.archived?.[p]).length > 0);
1563
+
1313
1564
  function renderAvailable() {
1314
- for (const provider of MODEL_SECTIONS) {
1565
+ for (const provider of shownSections()) {
1315
1566
  const rows = arr(state.models[provider]);
1316
1567
  const { section } = sectionHead(provider, modelsOnText(rows, hooks.t));
1317
1568
  if (provider === 'opencode') section.appendChild(el('div', 'asst-modal-note', tr('assistant.models.opencodeNote', 'Connected OpenCode models only. Settings → OpenCode edits the same list.')));
@@ -1326,7 +1577,7 @@ export async function openModelsManager(hooks = {}) {
1326
1577
 
1327
1578
  function renderArchived() {
1328
1579
  let any = false;
1329
- for (const provider of MODEL_SECTIONS) {
1580
+ for (const provider of shownSections()) {
1330
1581
  const entries = arr(state.archived[provider]);
1331
1582
  if (!entries.length) continue;
1332
1583
  any = true;