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
@@ -37,7 +37,7 @@ import { effectiveRouting, normalizeRouteMode } from './assistant-config.js';
37
37
  import { normalizeEffort } from './effort-levels.js';
38
38
  import { hasRouteAccounts, routeAccountText } from './assistant-route-accounts.js';
39
39
  import { chooseUsageAccount } from './account-usage.js';
40
- import { PROVIDER_LABELS, coversWork, dryProviders, isFreePrice, listPriceOf, rowOf } from './assistant-budget-split.js';
40
+ import { PROVIDER_LABELS, coversWork, dryProviders, isFreePrice, isUnpricedProvider, listPriceOf, rowOf } from './assistant-budget-split.js';
41
41
 
42
42
  export const TASK_CLASS_META = Object.freeze({
43
43
  chat: { label: 'Chat', description: 'Conversation and questions answered from knowledge or memory', defaultKind: 'direct' },
@@ -75,6 +75,8 @@ export function requiresVision(taskClass) { return TASK_CLASS_META[taskClass]?.v
75
75
  export const CANNOT_SEE_TEXT = "This model can't see images.";
76
76
  export const UNKNOWN_SIGHT_TEXT = "This model isn't known to see images.";
77
77
  export const UNAVAILABLE_TEXT = "This model isn't available right now.";
78
+ /** A "here" on an account other than this conversation's: provider histories belong to their account. */
79
+ export const WORKER_FOR_ACCOUNT_TEXT = 'Runs on a worker because this conversation belongs to another account.';
78
80
  /** The card / answer text for a model design cannot use (a designFit() verdict). */
79
81
  function fitText(fit) { return fit === 'blind' ? CANNOT_SEE_TEXT : fit === 'unavailable' ? UNAVAILABLE_TEXT : UNKNOWN_SIGHT_TEXT; }
80
82
  /** What a run of the class collects into its media: the medium it makes, else what it gathers (Design's screenshots). */
@@ -185,7 +187,8 @@ export function normalizeConfidence(value) {
185
187
 
186
188
  /**
187
189
  * Normalize the brain's proposal (agent_route body, or a dispatch spec reshaped
188
- * into one). A "direct" proposal on another provider is really a dispatch; for a
190
+ * into one). A "direct" proposal on another provider is really a dispatch
191
+ * (`coercedProvider` records the first, so the result can say so); for a
189
192
  * dispatch-only class (image / video creation, design) every proposal is a dispatch,
190
193
  * a "here" one on the brain's own model (`coercedDirect` records the first).
191
194
  * Design (`visionOnly`) also always needs a model that can see.
@@ -201,14 +204,17 @@ export function normalizeRouteRequest(body = {}, { brain = {} } = {}) {
201
204
  const dispatchOnly = meta.dispatchOnly === true;
202
205
  const proposals = [];
203
206
  let coercedDirect = false;
207
+ let coercedProvider = null;
204
208
  for (const raw of Array.isArray(body.proposals) ? body.proposals.slice(0, 3) : []) {
205
209
  if (!raw || typeof raw !== 'object') continue;
206
210
  let kind = raw.kind === 'direct' ? 'direct' : 'dispatch';
207
211
  let provider = CATALOG_PROVIDERS.includes(raw.provider) ? raw.provider : null;
208
212
  let model = raw.model ? String(raw.model).trim().slice(0, 200) : null;
209
213
  if (kind === 'direct') {
210
- if (provider && provider !== brain.provider) kind = 'dispatch';
211
- else provider = brain.provider || provider;
214
+ if (provider && provider !== brain.provider) {
215
+ kind = 'dispatch';
216
+ if (!proposals.length && !dispatchOnly) coercedProvider = provider;
217
+ } else provider = brain.provider || provider;
212
218
  }
213
219
  if (!provider) continue;
214
220
  if (dispatchOnly && kind === 'direct') {
@@ -230,9 +236,18 @@ export function normalizeRouteRequest(body = {}, { brain = {} } = {}) {
230
236
  const request = { taskClass, taskClassLabel: meta.label, summary: clip(body.summary || '', 400), confidence, needsVision, proposals };
231
237
  if (needsOutput || dispatchOnly) Object.assign(request, { needsOutput, dispatchOnly, coercedDirect });
232
238
  if (visionOnly) request.visionOnly = true;
239
+ if (coercedProvider) request.coercedProvider = coercedProvider;
233
240
  return request;
234
241
  }
235
242
 
243
+ /** Why a "here" on `provider` runs on a worker: only the brain's own provider runs in this conversation. */
244
+ function otherProviderCorrection(provider, brain = {}) {
245
+ return {
246
+ field: 'kind', from: 'direct', to: 'dispatch', reason: 'other_provider', provider,
247
+ text: `${providerLabel(provider)} is not this conversation's provider${brain.provider ? ` (${providerLabel(brain.provider)})` : ''}, so it runs on a worker`,
248
+ };
249
+ }
250
+
236
251
  /** Catalog-backed description of a target: label, tier, vision, outputs, price, context. */
237
252
  export function describeTarget(target, { catalog = null, brain = {}, brainInfo = null } = {}) {
238
253
  if (!target) return null;
@@ -478,6 +493,9 @@ export function budgetMove({ target, request, catalog = null, brain = {}, brainI
478
493
  export function evaluateRoute({ mode, routing = {}, request, brain = {}, brainInfo = null, catalog = null, budget = null }) {
479
494
  const reasons = [];
480
495
  const corrections = [];
496
+ const effective = normalizeRouteMode(mode) || 'ask-unsure';
497
+ // Always Ask is a task-local choice; the Never Ask table must not seed it.
498
+ if (effective === 'always-ask') routing = { ...routing, preferences: {} };
481
499
  const first = request.proposals[0];
482
500
  let proposal = { kind: first.kind, provider: first.provider, model: first.model, effort: first.effort };
483
501
  if (first.accountId) proposal.accountId = first.accountId;
@@ -496,6 +514,8 @@ export function evaluateRoute({ mode, routing = {}, request, brain = {}, brainIn
496
514
  if (request.coercedDirect) {
497
515
  corrections.push({ field: 'kind', from: 'direct', to: 'dispatch', reason: 'dispatch_only', text: `${label} always runs on a worker, not here` });
498
516
  }
517
+ // "Here" on another provider was always a worker: the result now says so instead of changing the kind silently.
518
+ if (request.coercedProvider) corrections.push(otherProviderCorrection(request.coercedProvider, brain));
499
519
  const sees = (target) => describeTarget(target, { catalog, brain, brainInfo })?.vision;
500
520
  const fit = (target) => designFit(catalog, target, { brain, brainInfo });
501
521
  // Too blind for this task: design needs a confirmed true (null never counts) on an available row; other vision work refuses a confirmed false.
@@ -546,7 +566,6 @@ export function evaluateRoute({ mode, routing = {}, request, brain = {}, brainIn
546
566
  if (request.confidence === null) reasons.push('no_confidence');
547
567
  else if (request.confidence < (routing.askBelow ?? 0.75)) reasons.push('low_confidence');
548
568
 
549
- const effective = normalizeRouteMode(mode) || 'ask-unsure';
550
569
  if (effective === 'always-ask' || effective === 'ask-unsure') {
551
570
  // The suggestion a card shows (or the pick that runs) is one the budget can pay for.
552
571
  const budgeted = applyBudget(proposal);
@@ -641,7 +660,8 @@ export function evaluateRoute({ mode, routing = {}, request, brain = {}, brainIn
641
660
  if (dispatchOnly && auto.kind !== 'dispatch') auto = { ...auto, kind: 'dispatch' };
642
661
  // A corrected model or provider re-checks the effort.
643
662
  auto = effortChecked(auto, catalog, corrections);
644
- if (!bound && corrections.some((c) => c.reason !== 'remembered_disabled' && c.reason !== 'remembered_incapable')) source = 'corrected';
663
+ // The provider rule only names what always happened (a "here" elsewhere is a worker): it corrects nothing.
664
+ if (!bound && corrections.some((c) => c.reason !== 'remembered_disabled' && c.reason !== 'remembered_incapable' && c.reason !== 'other_provider')) source = 'corrected';
645
665
  // With a budget the saved route yields: a provider with no share left moves the task on.
646
666
  const budgeted = applyBudget(auto);
647
667
  if (budgeted.declined) return budgetDeclined(budgeted);
@@ -669,11 +689,13 @@ function optionFor(id, rawTarget, badge, { catalog, brain, brainInfo, needsVisio
669
689
  else if (fit !== 'ok') { option.disabled = true; option.disabledReason = fitText(fit); }
670
690
  else if (needsVision && d.vision === false) { option.disabled = true; option.disabledReason = 'This model cannot see images, and this task needs to.'; }
671
691
  else if (needsVision && d.vision === null) option.warning = 'Image support unknown for this model.';
672
- // The session budget: a provider with no share left cannot take the task (a free model still can).
692
+ // The session budget: a provider with no share left cannot take the task (a free model still can), and one
693
+ // that reports no prices cannot run under a budget at all.
673
694
  if (!option.disabled && coverage && !budgetCovers(coverage, catalog, target)) {
695
+ const unpriced = coverage.leftUsd < 0.01 ? null : unpricedText(target.provider);
674
696
  option.disabled = true;
675
- option.budget = 'share_used_up';
676
- option.disabledReason = coverage.leftUsd < 0.01 ? "Budget: this session's budget is spent." : `Budget: the ${providerLabel(target.provider)} share of this session's budget is used up.`;
697
+ option.budget = unpriced ? 'unpriced' : 'share_used_up';
698
+ option.disabledReason = unpriced || (coverage.leftUsd < 0.01 ? "Budget: this session's budget is spent." : `Budget: the ${providerLabel(target.provider)} share of this session's budget is used up.`);
677
699
  }
678
700
  if (here && brainInfo?.contextWindow && d.contextWindow && d.contextWindow < brainInfo.contextWindow) {
679
701
  option.warning = `Smaller context (${Math.round(d.contextWindow / 1000)}k) than the current model; a long conversation may not fit.`;
@@ -682,7 +704,12 @@ function optionFor(id, rawTarget, badge, { catalog, brain, brainInfo, needsVisio
682
704
  }
683
705
 
684
706
  export function providerLabel(provider) {
685
- return provider === 'claude-code' ? 'Claude Code' : provider === 'codex' ? 'Codex' : provider === 'opencode' ? 'OpenCode' : String(provider || 'Assistant');
707
+ return provider === 'claude-code' ? 'Claude Code' : provider === 'codex' ? 'Codex' : provider === 'opencode' ? 'OpenCode' : provider === 'antigravity' ? 'Antigravity' : String(provider || 'Assistant');
708
+ }
709
+
710
+ /** Why a session with a budget cannot run `provider` at all (it reports tokens, never prices), else null. */
711
+ export function unpricedText(provider) {
712
+ return isUnpricedProvider(provider) ? `Budget: ${providerLabel(provider)} reports tokens, not prices, so a session with a budget cannot run it. Pick another option, or switch the budget to No limit.` : null;
686
713
  }
687
714
 
688
715
  /** `seeingOnly` (design): only rows with a confirmed vision: true, where `needsVision` only drops a confirmed false. */
@@ -723,8 +750,17 @@ function strongestClaude(catalog) {
723
750
  * Image / video creation: no "here" (always a worker), models that cannot make
724
751
  * the medium disabled, and — when the suggestion cannot — the model that would
725
752
  * run it instead (badge "capable"). Design: the same with "can see".
753
+ *
754
+ * Both sides, always: the card switches between Here (kind "direct": this
755
+ * conversation, on its model or another of its provider) and Dispatch (a worker).
756
+ * A side the options leave without one that can run gets one: a worker on the
757
+ * brain's own model ("w1", badge "current"), else a capable model of its provider,
758
+ * else elsewhere; a "here" on a model of the brain's provider that can see when the
759
+ * brain cannot ("h1", badge "capable"). Worker-only classes get no "here".
760
+ * → { options, defaultOptionId, sides, defaults } (routeSides).
726
761
  */
727
762
  export function buildRouteOptions({ request, evaluation, brain = {}, brainInfo = null, catalog = null, routing = {}, coverage = null }) {
763
+ const alwaysAsk = evaluation.reasons?.includes('always-ask') === true;
728
764
  const medium = request.needsOutput || null;
729
765
  const dispatchOnly = request.dispatchOnly === true;
730
766
  const visionOnly = request.visionOnly === true;
@@ -732,16 +768,26 @@ export function buildRouteOptions({ request, evaluation, brain = {}, brainInfo =
732
768
  const options = [];
733
769
  const push = (option) => { if (!options.some((o) => sameTarget(o, option))) options.push(option); };
734
770
  const hereTarget = { kind: 'direct', provider: brain.provider, model: brain.model || null, effort: brain.effort || null };
771
+ // A direct pick on the brain's own model with no effort is "here" as the brain runs now: one row, not two.
772
+ const onBrainModel = (t) => t.kind === 'direct' && t.provider === brain.provider && String(t.model || '') === String(brain.model || '');
735
773
  request.proposals.forEach((p, index) => {
736
774
  const target = { kind: p.kind, provider: p.provider, model: p.model || (index === 0 ? evaluation.proposal?.model || null : null), effort: p.effort };
737
775
  if (index === 0 && evaluation.proposal) Object.assign(target, evaluation.proposal);
776
+ if (onBrainModel(target) && !target.effort && brain.effort) target.effort = brain.effort;
738
777
  push(optionFor(`s${index + 1}`, target, 'suggested', { ...ctx, reason: p.reason }));
739
778
  });
740
779
  // The session budget moved the suggestion: the brain's own pick stays on the card, disabled with why.
741
780
  if (evaluation.budgetFrom && coverage) push(optionFor('b0', evaluation.budgetFrom, 'suggested', { ...ctx, reason: request.proposals[0]?.reason || null }));
742
781
  if (!dispatchOnly) push(optionFor('here', hereTarget, 'current', ctx));
743
782
  const pref = preferenceTarget(routing.preferences?.[request.taskClass], brain, catalog, { dispatchOnly });
744
- if (pref) push(optionFor('r1', pref, 'remembered', ctx));
783
+ // Always ask: the saved route is offered and its side starts on it (never binding: the card still asks).
784
+ if (pref) {
785
+ const saved = optionFor('r1', pref, 'remembered', ctx);
786
+ // A proposal of the same target keeps its row and carries the mark.
787
+ const row = options.find((o) => sameTarget(o, saved)) || saved;
788
+ if (alwaysAsk && !saved.disabled) Object.assign(row, { preferred: true, saved: true });
789
+ push(saved);
790
+ }
745
791
  const anchor = evaluation.proposal?.kind === 'dispatch' || dispatchOnly ? evaluation.proposal || request.proposals[0] : hereTarget;
746
792
  const cheaper = neighbour(catalog, anchor, 'cheaper', { needsVision: request.needsVision, seeingOnly: visionOnly, needsOutput: medium });
747
793
  if (cheaper) push(optionFor('c1', { kind: anchor.provider === brain.provider && anchor.kind === 'direct' ? 'direct' : 'dispatch', provider: anchor.provider, model: cheaper.id, effort: null }, 'cheaper', ctx));
@@ -763,11 +809,103 @@ export function buildRouteOptions({ request, evaluation, brain = {}, brainInfo =
763
809
  const option = seer ? optionFor('g1', seer, 'capable', ctx) : null;
764
810
  if (option && !option.disabled) push(option);
765
811
  }
812
+ // Both sides, always: a side with nothing that can run it gets the first candidate that can
813
+ // (or, with none, the first candidate disabled with why, so the card can say it).
814
+ // A saved route on an account is checked later (it may be signed out): it does not count, so its side keeps a fallback.
815
+ const runnable = (kind) => options.some((o) => o.kind === kind && !o.disabled && !(o.saved && o.accountId));
816
+ const offer = (kind, id, candidates) => {
817
+ let first = null;
818
+ for (const [target, badge] of candidates) {
819
+ if (!target?.provider) continue;
820
+ const option = optionFor(id, target, badge, ctx);
821
+ if (options.some((o) => sameTarget(o, option))) continue;
822
+ if (!option.disabled) { push(option); return; }
823
+ first = first || option;
824
+ }
825
+ if (first && !options.some((o) => o.kind === kind)) push(first);
826
+ };
827
+ if (request.taskClass !== 'chat' && !dispatchOnly && !runnable('direct')) offer('direct', 'h1', hereCandidates({ request, catalog, brain }));
828
+ if (request.taskClass !== 'chat' && !runnable('dispatch')) offer('dispatch', 'w1', workerCandidates({ request, catalog, brain, brainInfo }));
766
829
  // Order: suggested first, then here, remembered, a capable stand-in, cheaper, stronger.
767
830
  const order = { suggested: 0, current: 1, remembered: 2, capable: 3, cheaper: 4, stronger: 5 };
768
831
  options.sort((a, b) => (order[a.badge] ?? 9) - (order[b.badge] ?? 9) || String(a.id).localeCompare(String(b.id)));
769
- const defaultOption = options.find((o) => o.badge === 'suggested' && !o.disabled) || options.find((o) => !o.disabled) || options[0];
770
- return { options, defaultOptionId: defaultOption?.id || 'here' };
832
+ return { options, ...routeSides({ options, request }) };
833
+ }
834
+
835
+ /**
836
+ * "Here" on another model of the brain's provider when "here" itself cannot run the task
837
+ * (the brain cannot see a computer task's screen, or the user hid its model): the
838
+ * cheapest one of that provider that can (one that can see first for vision work).
839
+ */
840
+ function hereCandidates({ request, catalog, brain }) {
841
+ if (!brain.provider) return [];
842
+ const rows = visibleRows(catalog, brain.provider)
843
+ .filter((row) => row.status !== 'unavailable' && row.toolcall !== false && row.id !== brain.model && (!request.needsVision || row.vision === true))
844
+ .sort((a, b) => outPrice(a) - outPrice(b) || tierRank(a.tier) - tierRank(b.tier));
845
+ return rows.map((row) => [{ kind: 'direct', provider: brain.provider, model: row.id, effort: null }, request.needsVision ? 'capable' : null]);
846
+ }
847
+
848
+ /**
849
+ * A worker for a card whose options have none that can run: the brain's own model on a
850
+ * worker (the same model, off this conversation), else a model of its provider that can
851
+ * (the brain's tier first, one that can see for vision work), else elsewhere (one that
852
+ * can see, or the strongest Claude model).
853
+ */
854
+ function workerCandidates({ request, catalog, brain, brainInfo }) {
855
+ const candidates = [];
856
+ if (brain.provider) candidates.push([{ kind: 'dispatch', provider: brain.provider, model: brain.model || null, effort: brain.effort || null }, 'current']);
857
+ const brainTier = brainInfo?.tier || (brain.model ? findModel(catalog, brain.provider, brain.model)?.row?.tier : null) || null;
858
+ const fits = (row) => row.status !== 'unavailable' && row.toolcall !== false && (!request.needsVision || row.vision === true);
859
+ const closeness = (row) => (brainTier ? Math.abs(tierRank(row.tier) - tierRank(brainTier)) : 0);
860
+ const badge = request.needsVision ? 'capable' : null;
861
+ if (brain.provider) {
862
+ visibleRows(catalog, brain.provider).filter(fits).filter((row) => row.id !== brain.model)
863
+ .sort((a, b) => closeness(a) - closeness(b) || outPrice(a) - outPrice(b))
864
+ .forEach((row) => candidates.push([{ kind: 'dispatch', provider: brain.provider, model: row.id, effort: null }, badge]));
865
+ }
866
+ if (request.needsVision) {
867
+ CATALOG_PROVIDERS.filter((p) => p !== brain.provider)
868
+ .flatMap((p) => visibleRows(catalog, p).filter(fits).map((row) => ({ provider: p, row })))
869
+ .sort((a, b) => outPrice(a.row) - outPrice(b.row) || tierRank(a.row.tier) - tierRank(b.row.tier))
870
+ .forEach(({ provider, row }) => candidates.push([{ kind: 'dispatch', provider, model: row.id, effort: null }, 'capable']));
871
+ } else if (brain.provider !== 'claude-code') {
872
+ const opus = strongestClaude(catalog);
873
+ if (opus) candidates.push([{ kind: 'dispatch', provider: 'claude-code', model: opus.id, effort: null }, null]);
874
+ }
875
+ return candidates;
876
+ }
877
+
878
+ /**
879
+ * Which side of the card's Here / Dispatch switch can run the task, and the option each side
880
+ * starts on: { sides: { direct, dispatch }, defaults: { direct, dispatch }, defaultOptionId }.
881
+ * A side is { available, reason, code } (code: worker_only, account_worker, disabled, none).
882
+ * Read from the options as they are sent, after the account check (a "here" that moved to a
883
+ * worker belongs to the dispatch side: its kind is "dispatch").
884
+ */
885
+ export function routeSides({ options = [], request = {}, defaultOptionId = null } = {}) {
886
+ const usable = (o) => o && !o.disabled;
887
+ const chosen = options.find((o) => o.id === defaultOptionId && usable(o))
888
+ || options.find((o) => o.badge === 'suggested' && usable(o)) || options.find(usable) || options[0] || null;
889
+ // A side starts on the saved route when Always ask marked it (preferred), else on the chosen option, else its first.
890
+ const firstOf = (kind) => options.find((o) => o.preferred === true && o.kind === kind && usable(o))
891
+ || (chosen?.kind === kind && usable(chosen) ? chosen : null) || options.find((o) => o.kind === kind && usable(o)) || null;
892
+ const label = request.taskClassLabel || TASK_CLASS_META[request.taskClass]?.label || 'This task';
893
+ const side = (kind) => {
894
+ if (firstOf(kind)) return { available: true, reason: null, code: null };
895
+ if (kind === 'direct' && request.dispatchOnly) return { available: false, code: 'worker_only', reason: `${label} always runs on a worker.` };
896
+ if (kind === 'direct' && options.some((o) => o.kind === 'dispatch' && o.accountKind === 'direct')) {
897
+ return { available: false, code: 'account_worker', reason: "This conversation's account cannot take it, so it runs on a worker." };
898
+ }
899
+ const blocked = options.find((o) => o.kind === kind && o.disabled);
900
+ if (blocked) return { available: false, code: 'disabled', reason: blocked.disabledReason || null };
901
+ return { available: false, code: 'none', reason: kind === 'direct' ? 'Nothing in this conversation can run this task.' : 'No worker model can run this task.' };
902
+ };
903
+ return {
904
+ // The card opens on the chosen option's side, on that side's default.
905
+ defaultOptionId: (chosen && usable(chosen) ? firstOf(chosen.kind)?.id : null) || chosen?.id || 'here',
906
+ sides: { direct: side('direct'), dispatch: side('dispatch') },
907
+ defaults: { direct: firstOf('direct')?.id || null, dispatch: firstOf('dispatch')?.id || null },
908
+ };
771
909
  }
772
910
 
773
911
  /** Validate a route-card answer; returns { declined } or { target, optionId, remember }. */
@@ -776,13 +914,19 @@ export function targetFromAnswer({ card, response = {}, catalog = null, brain =
776
914
  const optionId = String(response.optionId || response.option_id || '').trim();
777
915
  const remember = response.remember === true || (typeof response.remember === 'string' && response.remember.length > 0);
778
916
  const option = optionId && optionId !== 'other' ? (card.options || []).find((o) => o.id === optionId) : null;
917
+ // The model this conversation already runs on needs no catalog entry to stay on (an OpenCode model
918
+ // no longer listed as connected, an id the list spells otherwise): "here" on it changes nothing.
919
+ // A hidden one is still refused, as everywhere.
920
+ const onBrain = (target) => target.kind === 'direct' && !!brain.model && target.provider === brain.provider && String(target.model || '') === String(brain.model);
779
921
  // A card can remain open while the catalog or hidden-model list changes.
780
922
  // Check the chosen target again instead of trusting the option's old state.
781
923
  const ensureAvailable = (target) => {
782
- if (target.model && findModel(catalog, target.provider, target.model) === null) {
924
+ const own = onBrain(target);
925
+ if (target.model && (own ? isDisabled(catalog, target) : findModel(catalog, target.provider, target.model) === null)) {
783
926
  if (isDisabled(catalog, target)) throw routeError('MODEL_DISABLED', `${target.model} is disabled in the Assistant's Models list. Pick an enabled model.`);
784
927
  throw routeError('ROUTE_INVALID', `Unknown model ${target.model} for ${target.provider}.`);
785
928
  }
929
+ if (!own && findModel(catalog, target.provider, target.model)?.row?.status === 'unavailable') throw routeError('ROUTE_INVALID', UNAVAILABLE_TEXT);
786
930
  if (!target.model && isDisabled(catalog, target)) throw routeError('MODEL_DISABLED', `Every ${target.provider} model is disabled in the Assistant's Models list. Pick another provider.`);
787
931
  // Design: only a confirmed vision: true on an available row (unknown or unavailable is refused too);
788
932
  // other vision work: a confirmed false.
@@ -796,10 +940,16 @@ export function targetFromAnswer({ card, response = {}, catalog = null, brain =
796
940
  };
797
941
  // Image / video creation and design always run on a worker: a "here" pick is a dispatch onto that model.
798
942
  const dispatchOnly = card.dispatchOnly === true;
943
+ // Only the brain's own provider runs in this conversation: a "here" on another one (an option made
944
+ // before the brain changed provider, or an "other" pick) is a worker, and the answer says so.
945
+ const elsewhere = (provider) => !!brain.provider && provider !== brain.provider;
799
946
  if (option) {
800
947
  if (option.disabled && !option.accountError) throw routeError('ROUTE_INVALID', option.disabledReason || 'That option is not available for this task.');
801
948
  const corrections = [];
802
- const target = effortChecked({ kind: dispatchOnly ? 'dispatch' : option.kind, provider: option.provider, model: option.model || (dispatchOnly && option.kind === 'direct' ? brain.model || null : null), effort: option.effort, ...(option.accountId ? { accountId: option.accountChoice || option.accountId, ...(option.accountKind ? { accountKind: dispatchOnly ? 'dispatch' : option.accountKind } : {}) } : {}) }, catalog, corrections);
949
+ const moved = !dispatchOnly && option.kind === 'direct' && elsewhere(option.provider);
950
+ if (moved) corrections.push(otherProviderCorrection(option.provider, brain));
951
+ const kind = dispatchOnly || moved ? 'dispatch' : option.kind;
952
+ const target = effortChecked({ kind, provider: option.provider, model: option.model || (dispatchOnly && option.kind === 'direct' ? brain.model || null : null), effort: option.effort, ...(option.accountId ? { accountId: option.accountChoice || option.accountId, ...(option.accountKind ? { accountKind: dispatchOnly || moved ? 'dispatch' : option.accountKind } : {}) } : {}) }, catalog, corrections);
803
953
  ensureAvailable(target);
804
954
  return { target, optionId, remember, corrections };
805
955
  }
@@ -807,7 +957,12 @@ export function targetFromAnswer({ card, response = {}, catalog = null, brain =
807
957
  if (!t || typeof t !== 'object' || !CATALOG_PROVIDERS.includes(t.provider)) throw routeError('ROUTE_INVALID', 'Pick one of the options or a catalog model.');
808
958
  const kind = t.kind === 'direct' && t.provider === brain.provider && !dispatchOnly ? 'direct' : 'dispatch';
809
959
  const corrections = [];
810
- const target = effortChecked({ kind, provider: t.provider, model: t.model ? String(t.model) : null, effort: t.effort ? String(t.effort).trim().slice(0, 40) : null }, catalog, corrections);
960
+ if (t.kind === 'direct' && kind === 'dispatch') {
961
+ corrections.push(dispatchOnly
962
+ ? { field: 'kind', from: 'direct', to: 'dispatch', reason: 'dispatch_only', text: `${TASK_CLASS_META[card.taskClass]?.label || 'This task'} always runs on a worker, not here` }
963
+ : otherProviderCorrection(t.provider, brain));
964
+ }
965
+ const target = effortChecked({ kind, provider: t.provider, model: t.model ? String(t.model) : null, effort: t.effort ? String(t.effort).trim().slice(0, 40) : null, accountId: t.accountId ? String(t.accountId).trim() : null }, catalog, corrections);
811
966
  ensureAvailable(target);
812
967
  return { target, optionId: 'other', remember, corrections };
813
968
  }
@@ -1013,16 +1168,20 @@ export function createAssistantRouter({
1013
1168
  : row.signedIn === false ? `Account ${row.label || row.id} (${row.id}) is signed out. Sign it in from the ${providerLabel(target.provider)} account menu, or save another Account in Model routes.` : '';
1014
1169
  const accountError = message ? { code: row ? 'ACCOUNT_SIGNED_OUT' : choice === 'auto' ? 'ACCOUNT_NO_SUBSCRIPTION' : 'ACCOUNT_NOT_FOUND', message } : null;
1015
1170
  // Provider histories are account-owned. Another account can do the task on
1016
- // a worker; it cannot resume this account's conversation.
1171
+ // a worker; it cannot resume this account's conversation. That move is always
1172
+ // said (a pinned account the user picked carries no usage reason of its own).
1017
1173
  const moved = target.kind === 'direct' && accountId !== (brain.accountId || 'default');
1018
- const accountCorrection = why ? { reason, from: before?.label || choice, to: row?.label || accountId, usage, workerForAccount: moved, message: why + (moved ? ' Runs on a worker because this conversation belongs to another account.' : '') } : null;
1174
+ const accountCorrection = why
1175
+ ? { reason, from: before?.label || choice, to: row?.label || accountId, usage, workerForAccount: moved, message: why + (moved ? ` ${WORKER_FOR_ACCOUNT_TEXT}` : '') }
1176
+ : moved ? { field: 'kind', reason: 'account_worker', from: rows.find(a => a.id === (brain.accountId || 'default'))?.label || brain.accountId || 'default', to: row?.label || accountId, usage, workerForAccount: true, message: `${WORKER_FOR_ACCOUNT_TEXT.replace(/\.$/, '')} (${row?.label || accountId}).` }
1177
+ : null;
1019
1178
  const enriched = { ...target, accountKind: target.accountKind || target.kind, ...(moved ? { kind: 'dispatch' } : {}), accountId, account, accountError, accountChoice: target.accountChoice || choice, accountCorrection };
1020
1179
  if (check && accountError) throw routeError(accountError.code, message, 409, { accountId, target: enriched });
1021
1180
  return enriched;
1022
1181
  }
1023
1182
  function accountCorrections(target, base = []) { return target.accountCorrection && !base.some(c => c.reason === target.accountCorrection.reason && c.from === target.accountCorrection.from && c.to === target.accountCorrection.to && c.message === target.accountCorrection.message) ? [...base, target.accountCorrection] : base; }
1024
- function checkedTarget(target, brain, cat, route = null, fallback = null) {
1025
- try { return accountTarget(target, brain, cat, { fallback }); }
1183
+ function checkedTarget(target, brain, cat, route = null, fallback = null, exact = false) {
1184
+ try { return accountTarget(target, brain, cat, { fallback, select: !exact || !target.accountId || target.accountId === 'auto' || target.accountChoice === 'auto' }); }
1026
1185
  catch (error) {
1027
1186
  if (route) call('routeEvent', route.sessionId, 'declined', routeView({ ...route, target: error.target, status: 'declined', reason: error.message, reasonCode: error.code }, { catalog: cat, brain }));
1028
1187
  throw error;
@@ -1081,8 +1240,14 @@ export function createAssistantRouter({
1081
1240
  return call('continueDirect', sessionId, { target, route: source, text: source.summary || '' }) !== false;
1082
1241
  }
1083
1242
 
1084
- function resultFor(status, { routeId, mode, source = null, target = null, card = null, runIds = [], corrections = [], cat = null, brain = {}, brainInfo = null, remembered = false, continuation = false, sessionId = null } = {}) {
1243
+ function resultFor(status, { routeId, mode, source = null, target = null, card = null, runIds = [], corrections = [], cat = null, brain = {}, brainInfo = null, remembered = false, continuation = false, sessionId = null, switched = null, droppedRuns = [] } = {}) {
1085
1244
  const described = target ? { ...describeTarget(target, { catalog: cat, brain, brainInfo }), continuation } : null;
1245
+ // The user's pick was the other kind than the brain proposed; a "here" pick on another account runs on a worker.
1246
+ const kindLead = source !== 'user' ? ''
1247
+ : target?.kind === 'dispatch' && target.accountKind === 'direct'
1248
+ ? 'The user chose to do this here, but on another account than this conversation\'s, so it runs on a worker. '
1249
+ : switched === 'direct' ? `The user chose to do this here, not on a worker${droppedRuns.length ? ` (the held run${droppedRuns.length === 1 ? '' : 's'} ${droppedRuns.join(', ')} ${droppedRuns.length === 1 ? 'was' : 'were'} not started)` : ''}. `
1250
+ : switched === 'dispatch' ? 'The user chose a worker for this, not here. ' : '';
1086
1251
  // A saved route that replaced the brain's pick (never mode) says so first.
1087
1252
  const binding = (corrections || []).find((c) => c?.reason === 'remembered_route');
1088
1253
  const skipped = (corrections || []).find((c) => c?.reason === 'remembered_disabled');
@@ -1099,7 +1264,7 @@ export function createAssistantRouter({
1099
1264
  ok: true, status, routeId, mode, source, target: described, remembered, continuation,
1100
1265
  corrections, runIds, expiresAt: card?.expiresAt ? new Date(card.expiresAt).toISOString() : null,
1101
1266
  ...(shares ? { budget: shares } : {}),
1102
- next: `${lead}${nextText(status, described, { routeId, label: described?.label })}`,
1267
+ next: `${lead}${status === 'approved' ? kindLead : ''}${nextText(status, described, { routeId, label: described?.label })}`,
1103
1268
  };
1104
1269
  }
1105
1270
 
@@ -1151,13 +1316,23 @@ export function createAssistantRouter({
1151
1316
  const routeId = `route-${randomId()}`;
1152
1317
  const requestId = routeId;
1153
1318
  const r = routing();
1154
- const { options, defaultOptionId } = buildRouteOptions({ request, evaluation, brain, brainInfo, catalog: cat, routing: r, coverage: evaluation.coverage || null });
1319
+ const built = buildRouteOptions({ request, evaluation, brain, brainInfo, catalog: cat, routing: r, coverage: evaluation.coverage || null });
1320
+ const { options } = built;
1155
1321
  for (const option of options) {
1156
1322
  const first = request.proposals[0];
1157
1323
  const fallback = origin === 'dispatch' && option.badge !== 'remembered' && option.provider === first?.provider ? first.accountId : null;
1158
1324
  Object.assign(option, accountTarget(option, brain, cat, { check: false, fallback }));
1159
1325
  if (option.accountError) { option.disabled = true; option.disabledReason = option.accountError.message; }
1326
+ // A "here" whose account cannot take it (this conversation's is out of usage) runs on a worker
1327
+ // on another account: the card names it as a worker and says why, never "Do it here".
1328
+ if (option.kind === 'dispatch' && option.accountKind === 'direct') {
1329
+ const described = describeTarget(option, { catalog: cat, brain, brainInfo });
1330
+ option.label = `${providerLabel(option.provider)} · ${described?.label || option.model || 'default'}${option.effort ? ` · ${option.effort}` : ''}`;
1331
+ option.warning = option.accountCorrection?.message || WORKER_FOR_ACCOUNT_TEXT;
1332
+ }
1160
1333
  }
1334
+ // The sides and defaults as the options are sent (the account check can disable or move one).
1335
+ const { defaultOptionId, sides, defaults } = routeSides({ options, request, defaultOptionId: built.defaultOptionId });
1161
1336
  const computer = computerConsent({ session, request, options, defaultOptionId, brain, origin });
1162
1337
  const expiresAt = now() + r.cardTimeoutMinutes * 60_000;
1163
1338
  const pref = r.preferences?.[request.taskClass];
@@ -1165,7 +1340,7 @@ export function createAssistantRouter({
1165
1340
  routeId, requestId, sessionId, origin, status: 'pending', mode, workflowId,
1166
1341
  taskClass: request.taskClass, summary: request.summary, confidence: request.confidence, needsVision: request.needsVision,
1167
1342
  needsOutput: request.needsOutput || null, dispatchOnly: request.dispatchOnly === true, visionOnly: request.visionOnly === true,
1168
- reasons: evaluation.reasons, request, options, defaultOptionId, runIds: new Set(runIds), waiters: new Set(),
1343
+ reasons: evaluation.reasons, request, options, defaultOptionId, sides, defaults, runIds: new Set(runIds), waiters: new Set(),
1169
1344
  createdAt: now(), expiresAt, timer: null, mailboxOnDecision: origin === 'dispatch', brain,
1170
1345
  // The option whose approval is also the computer approval (computerConsent), as this card was sent.
1171
1346
  computer,
@@ -1184,6 +1359,8 @@ export function createAssistantRouter({
1184
1359
  visionOnly: request.visionOnly === true,
1185
1360
  brain: { provider: brain.provider, model: brain.model || null, label: brainInfo?.label || brain.model || providerLabel(brain.provider), vision: brainInfo?.vision ?? null },
1186
1361
  options, defaultOptionId,
1362
+ // The card's Here / Dispatch switch: which side can run the task (and why not), and the option each side starts on.
1363
+ sides, defaults,
1187
1364
  other: { allowed: true, providers: CATALOG_PROVIDERS, catalogPath: '/api/assistant/catalog' },
1188
1365
  remember: { available: request.taskClass !== 'chat', taskClass: request.taskClass, label: request.taskClassLabel, current: pref ? (pref.label || [pref.provider, pref.model].filter(Boolean).join('/')) : null },
1189
1366
  decline: { allowed: true, label: 'Cancel' },
@@ -1384,7 +1561,7 @@ export function createAssistantRouter({
1384
1561
  return result;
1385
1562
  }
1386
1563
  const { optionId, remember } = parsed;
1387
- const target = checkedTarget(parsed.target, brain, cat);
1564
+ const target = checkedTarget(parsed.target, brain, cat, null, null, card.mode === 'always-ask');
1388
1565
  const corrections = accountCorrections(target, parsed.corrections || []);
1389
1566
  // The session budget must be able to pay for the pick: a provider with no share left divides the
1390
1567
  // unspent budget again first; still none, and the card stays open with the reason.
@@ -1392,21 +1569,28 @@ export function createAssistantRouter({
1392
1569
  try { await budget.ensureCovered(card.sessionId, { provider: target.provider, forBrain: target.kind === 'direct', free: targetIsFree(cat, target) }); } catch { /* decided on the shares as they are */ }
1393
1570
  if (cards.get(routeId) !== card || card.status !== 'pending') openCard(routeId, { sessionId });
1394
1571
  if (!budgetCovers(coverageOf(card.sessionId), cat, target)) {
1572
+ const unpriced = unpricedText(target.provider);
1573
+ if (unpriced) throw routeError('BUDGET_UNPRICED', unpriced, 409, { provider: target.provider });
1395
1574
  throw routeError('BUDGET_SHARE_EXHAUSTED', `Budget: the ${providerLabel(target.provider)} share of this session's budget is used up. Pick another option (or a free model), or raise the budget.`, 409, { provider: target.provider });
1396
1575
  }
1397
1576
  }
1577
+ // "Here" on a held dispatch's card: the brain does the task in this conversation, so the held
1578
+ // worker runs are not started (they stop, route_here) and the brain is told to do it itself.
1579
+ const keptHere = card.runIds.size > 0 && target.kind === 'direct';
1398
1580
  // Every held run must be able to start on the pick (the dispatcher's checkHeld: a design run's
1399
1581
  // sight and browser settings on that provider). A pick one would refuse is not approved, saved
1400
- // or reported started: the card stays open with the reason.
1401
- for (const runId of card.runIds) {
1402
- const refusal = call('checkHeld', runId, target);
1403
- if (refusal) throw routeError(refusal.code || 'ROUTE_INVALID', refusal.message || 'The held run cannot start on that model.', Number(refusal.status) || 400);
1582
+ // or reported started: the card stays open with the reason. A "here" pick starts none.
1583
+ if (!keptHere) {
1584
+ for (const runId of card.runIds) {
1585
+ const refusal = call('checkHeld', runId, target);
1586
+ if (refusal) throw routeError(refusal.code || 'ROUTE_INVALID', refusal.message || 'The held run cannot start on that model.', Number(refusal.status) || 400);
1587
+ }
1404
1588
  }
1405
1589
  // "Do it here" on another model continues in the turn the card was raised in,
1406
1590
  // handed over first. The runtime refuses a pick that turn no longer owns (a
1407
1591
  // newer prompt, a Stop or the session closing came after it): then nothing is
1408
1592
  // approved or remembered, and the card expires on every host.
1409
- const continuation = !card.runIds.size && continuesHere(target, brain);
1593
+ const continuation = (keptHere || !card.runIds.size) && continuesHere(target, brain);
1410
1594
  // The decision is claimed here, before the first side effect (the runtime's continuation, the
1411
1595
  // held runs, the mailbox): nothing a sink does can close or decide this card again meanwhile.
1412
1596
  card.status = 'approved';
@@ -1424,21 +1608,23 @@ export function createAssistantRouter({
1424
1608
  remembered = true;
1425
1609
  } catch (error) { log('router:remember-error', error?.message || String(error)); }
1426
1610
  }
1611
+ const heldRuns = keptHere ? [] : [...card.runIds];
1612
+ const droppedRuns = keptHere ? [...card.runIds] : [];
1427
1613
  const route = {
1428
1614
  routeId, status: 'approved', sessionId: card.sessionId, taskClass: card.taskClass, summary: card.summary, confidence: card.confidence,
1429
- target, decidedBy: 'user', remembered, optionId, corrections, mode: card.mode, runIds: [...card.runIds],
1615
+ target, decidedBy: 'user', remembered, optionId, corrections, mode: card.mode, runIds: heldRuns,
1430
1616
  reason: card.options.find((o) => o.id === optionId)?.reason || null, alternatives: card.alternatives, expiresAt: now() + DECIDED_TTL_MS,
1431
1617
  };
1432
1618
  decided.set(routeId, route);
1433
1619
  noteBudgetTask(card.sessionId, { taskClass: card.taskClass, summary: card.summary }, target);
1434
- const heldRuns = [...card.runIds];
1435
1620
  // A remote session: the held run this card was for is its one dispatch.
1436
1621
  if (heldRuns.length && session.remote === true) route.consumed = true;
1622
+ for (const runId of droppedRuns) call('declineHeld', runId, 'route_here');
1437
1623
  for (const runId of heldRuns) call('startHeld', runId, target, { routeId, decidedBy: 'user', remembered, optionId });
1438
1624
  const view = routeView(route, { catalog: cat, brain, brainInfo });
1439
1625
  call('routeEvent', card.sessionId, 'decided', view);
1440
1626
  const inTurnWaiter = card.waiters.size > 0;
1441
- const viaMailbox = !inTurnWaiter && !continuation && (card.mailboxOnDecision || heldRuns.length > 0);
1627
+ const viaMailbox = !inTurnWaiter && !continuation && (card.mailboxOnDecision || card.runIds.size > 0);
1442
1628
  // The card said the Mac will be controlled, and this is the option it said it for: the yes is
1443
1629
  // also the computer approval of the turn that carries the route out (this one, or the mailbox
1444
1630
  // turn that executes the decided route). From what the card recorded when it was sent; the
@@ -1447,9 +1633,19 @@ export function createAssistantRouter({
1447
1633
  // `authority` is passed on untouched and never kept: the runtime alone can tell whose it is.
1448
1634
  call('computerApproved', card.sessionId, { routeId, origin, viaMailbox, authority });
1449
1635
  }
1450
- const result = resultFor('approved', { routeId, mode: card.mode, source: 'user', target, corrections, cat, brain, brainInfo, remembered, runIds: heldRuns, continuation, sessionId: card.sessionId });
1636
+ // The brain proposed one kind and the user picked the other (the card's Here / Dispatch switch):
1637
+ // the result says so first, so the brain follows the pick, not its proposal.
1638
+ const proposed = card.request?.proposals?.[0]?.kind || null;
1639
+ const picked = target.accountKind || target.kind;
1640
+ const switched = proposed && proposed !== picked ? picked : null;
1641
+ const result = resultFor('approved', { routeId, mode: card.mode, source: 'user', target, corrections, cat, brain, brainInfo, remembered, runIds: heldRuns, continuation, sessionId: card.sessionId, switched, droppedRuns });
1451
1642
  if (viaMailbox) {
1452
- call('mailbox', card.sessionId, { kind: 'route_decided', route: view, runIds: heldRuns, text: heldRuns.length ? `The user picked ${view.target?.label || 'a model'}; the held run${heldRuns.length === 1 ? '' : 's'} started.` : `The user picked ${view.target?.label || 'a model'}. ${result.next}` });
1643
+ const label = view.target?.label || 'a model';
1644
+ const what = card.summary ? `"${card.summary}"` : 'this task';
1645
+ const text = heldRuns.length ? `The user picked ${label}; the held run${heldRuns.length === 1 ? '' : 's'} started.`
1646
+ : droppedRuns.length ? `The user chose to do ${what} here with ${label} instead of on a worker: the held run${droppedRuns.length === 1 ? '' : 's'} ${droppedRuns.join(', ')} ${droppedRuns.length === 1 ? 'was' : 'were'} not started. Do that task here yourself now; do not dispatch it again.`
1647
+ : `The user picked ${label}. ${result.next}`;
1648
+ call('mailbox', card.sessionId, { kind: 'route_decided', route: view, runIds: heldRuns, ...(droppedRuns.length ? { droppedRunIds: droppedRuns } : {}), text });
1453
1649
  }
1454
1650
  settle(card, { status: 'approved', result });
1455
1651
  closeCard(card);
@@ -1479,11 +1675,17 @@ export function createAssistantRouter({
1479
1675
  const remote = session.remote === true;
1480
1676
  if (routeId) {
1481
1677
  const approved = decided.get(routeId);
1678
+ // The user picked "here" on this route's card: it runs in this conversation, never on a worker
1679
+ // (a brain's own direct route, or a saved one in Never ask, keeps its old dispatch behaviour).
1680
+ if (approved && approved.sessionId === sessionId && approved.target?.kind === 'direct' && approved.decidedBy === 'user') {
1681
+ const where = approved.target.model ? ` on ${approved.target.model}` : '';
1682
+ throw routeError('ROUTE_IS_DIRECT', `Route ${routeId} was decided by the user to run here, in this conversation${where}, not on a worker. Do the task yourself now; do not dispatch it. If it should go to a worker after all, ask the user, then call agent_route again.`, 409, { routeId, target: approved.target });
1683
+ }
1482
1684
  if (approved && approved.sessionId === sessionId && approved.target && !approved.consumed) {
1483
1685
  // A remote session's route is claimed here, so a second dispatch cannot take it meanwhile.
1484
1686
  // `claimed` tells the dispatcher to give it back (releaseRoute) if it refuses the launch
1485
1687
  // before a run exists: only the run it starts uses the route up.
1486
- const t = checkedTarget(approved.target, brain, cat, approved);
1688
+ const t = checkedTarget(approved.target, brain, cat, approved, null, approved.mode === 'always-ask' && approved.decidedBy === 'user');
1487
1689
  approved.target = t;
1488
1690
  approved.corrections = accountCorrections(t, approved.corrections);
1489
1691
  // The approved provider may have run out of share since the approval: the unspent budget is
@@ -1491,6 +1693,11 @@ export function createAssistantRouter({
1491
1693
  let runTarget = t;
1492
1694
  let budgetCorrection = null;
1493
1695
  if (budgeted() && !budgetCovers(coverageOf(sessionId), cat, t)) {
1696
+ if (approved.mode === 'always-ask' && approved.decidedBy === 'user') {
1697
+ const unpriced = unpricedText(t.provider);
1698
+ if (unpriced) throw routeError('BUDGET_UNPRICED', `${unpriced} Choose a new task route.`, 409, { routeId, provider: t.provider });
1699
+ throw routeError('BUDGET_SHARE_EXHAUSTED', 'The selected model has no session budget left. Choose a new task route or raise the budget.', 409, { routeId, provider: t.provider });
1700
+ }
1494
1701
  try { await budget.ensureCovered(sessionId, { provider: t.provider, forBrain: false, free: targetIsFree(cat, t), task: { taskClass: approved.taskClass, summary: approved.summary || null, provider: t.provider, model: t.model || null } }); } catch { /* decided on the shares as they are */ }
1495
1702
  const request = normalizeRouteRequest({ task_class: approved.taskClass, summary: approved.summary || '', confidence: 1, needs_vision: !!spec.usesComputer, proposals: [{ kind: 'dispatch', provider: t.provider, model: t.model || null, effort: t.effort || null }] }, { brain });
1496
1703
  const moved = budgetMove({ target: { ...t, kind: 'dispatch' }, request, catalog: cat, brain, brainInfo, routing: r, coverage: coverageOf(sessionId) });
@@ -1500,7 +1707,7 @@ export function createAssistantRouter({
1500
1707
  if (moved.correction) { runTarget = moved.target; budgetCorrection = moved.correction; }
1501
1708
  }
1502
1709
  if (remote) approved.consumed = true;
1503
- const applied = { ...spec, provider: runTarget.provider, model: runTarget.model || null, effort: runTarget.effort || spec.effort || null, ...(runTarget.accountId ? { accountId: runTarget.accountId, codexAccountId: null, claudeAccountId: null } : runTarget !== t ? { accountId: null, codexAccountId: null, claudeAccountId: null } : {}) };
1710
+ const applied = { ...spec, provider: runTarget.provider, model: runTarget.model || null, effort: runTarget.effort ?? null, accountId: runTarget.accountId || null, codexAccountId: null, claudeAccountId: null };
1504
1711
  const meta = routeMeta(approved, cat, brain, brainInfo);
1505
1712
  const route = budgetCorrection ? { ...meta, target: describeTarget(runTarget, { catalog: cat, brain, brainInfo }), corrections: [...(meta.corrections || []), budgetCorrection] } : meta;
1506
1713
  return { action: 'start', spec: applied, route: { ...route, requested: { provider: spec.provider || null, model: spec.model || null, effort: spec.effort || null } }, ...(remote ? { claimed: true } : {}) };
@@ -1630,7 +1837,7 @@ export function createAssistantRouter({
1630
1837
  const mode = modeFor(sessionId);
1631
1838
  const prefs = Object.entries(r.preferences || {}).filter(([, p]) => p?.provider)
1632
1839
  .map(([cls, p]) => `${cls} → ${p.kind === 'direct' ? 'here' : p.provider}${p.model ? `/${p.model}` : ''}${p.effort ? ` (${p.effort})` : ''}${hasRouteAccounts(p.provider) ? ` as ${routeAccountText(p, { accounts: liveAccounts(catalog.peek?.()), brain: getSession(sessionId)?.brain, workers: { codex: configStore?.workers?.()?.codexAccount, 'claude-code': configStore?.workers?.()?.claudeAccount } })}` : ''}`);
1633
- const modeLine = mode === 'always-ask' ? 'always ask (call agent_route before every actionable task; the user picks the model)'
1840
+ const modeLine = mode === 'always-ask' ? 'always ask (call agent_route before every actionable task; the user picks the model, and whether it runs here or on a worker)'
1634
1841
  : mode === 'never' ? 'never ask (agent_route approves at once; the saved routes below are binding and agent_route applies them over your pick, so classify the task correctly)'
1635
1842
  : `ask when unsure (agent_route asks the user below ${Math.round(r.askBelow * 100)}% confidence or for unknown/missing models)`;
1636
1843
  const text = `[SynaBun Router] Route mode: ${modeLine}.${prefs.length ? ` ${mode === 'never' ? 'Saved routes (binding)' : 'Remembered routes'}: ${prefs.join('; ')}.` : ''}`;