synabun 2.1.0 → 2.2.1

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 (332) hide show
  1. package/CHANGELOG.md +139 -0
  2. package/README.md +1 -1
  3. package/docs/whatsapp.md +11 -11
  4. package/lib/client-config-repair.js +13 -9
  5. package/lib/external-tools.js +6 -5
  6. package/lib/update-snapshot.js +77 -3
  7. package/lib/virustotal.mjs +12 -1
  8. package/mcp-server/dist/index.js +1 -1
  9. package/mcp-server/dist/index.js.map +1 -1
  10. package/mcp-server/dist/services/budget-judgments.d.ts +1 -1
  11. package/mcp-server/dist/services/budget-judgments.d.ts.map +1 -1
  12. package/mcp-server/dist/services/budget-judgments.js +5 -3
  13. package/mcp-server/dist/services/budget-judgments.js.map +1 -1
  14. package/mcp-server/dist/services/identity.d.ts +2 -2
  15. package/mcp-server/dist/services/identity.js +2 -2
  16. package/mcp-server/dist/services/memory-sessions.d.ts +1 -1
  17. package/mcp-server/dist/services/memory-sessions.d.ts.map +1 -1
  18. package/mcp-server/dist/services/memory-sessions.js +28 -2
  19. package/mcp-server/dist/services/memory-sessions.js.map +1 -1
  20. package/mcp-server/dist/services/neural-interface.d.ts +2 -1
  21. package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
  22. package/mcp-server/dist/services/neural-interface.js.map +1 -1
  23. package/mcp-server/dist/services/server-instructions.d.ts.map +1 -1
  24. package/mcp-server/dist/services/server-instructions.js +2 -1
  25. package/mcp-server/dist/services/server-instructions.js.map +1 -1
  26. package/mcp-server/dist/services/tool-catalog.js +1 -1
  27. package/mcp-server/dist/services/tool-catalog.js.map +1 -1
  28. package/mcp-server/dist/services/tool-list.d.ts +1 -1
  29. package/mcp-server/dist/services/tool-list.d.ts.map +1 -1
  30. package/mcp-server/dist/services/tool-runtime.d.ts +2 -2
  31. package/mcp-server/dist/services/tool-runtime.d.ts.map +1 -1
  32. package/mcp-server/dist/services/tool-runtime.js +1 -1
  33. package/mcp-server/dist/services/tool-runtime.js.map +1 -1
  34. package/mcp-server/dist/services/typesafe-config.js +1 -1
  35. package/mcp-server/dist/services/typesafe-config.js.map +1 -1
  36. package/mcp-server/dist/tools/agents.d.ts +8 -8
  37. package/mcp-server/dist/tools/agents.d.ts.map +1 -1
  38. package/mcp-server/dist/tools/agents.js +10 -9
  39. package/mcp-server/dist/tools/agents.js.map +1 -1
  40. package/mcp-server/dist/tools/choice.d.ts +6 -0
  41. package/mcp-server/dist/tools/choice.d.ts.map +1 -1
  42. package/mcp-server/dist/tools/choice.js +18 -13
  43. package/mcp-server/dist/tools/choice.js.map +1 -1
  44. package/mcp-server/dist/tools/git-tools.js +2 -2
  45. package/mcp-server/dist/tools/git-tools.js.map +1 -1
  46. package/mcp-server/dist/tools/morelogin.d.ts.map +1 -1
  47. package/mcp-server/dist/tools/morelogin.js +9 -6
  48. package/mcp-server/dist/tools/morelogin.js.map +1 -1
  49. package/mcp-server/dist/tools/skill-tools.d.ts +8 -8
  50. package/mcp-server/dist/tools/skill-tools.d.ts.map +1 -1
  51. package/mcp-server/dist/tools/skill-tools.js +11 -8
  52. package/mcp-server/dist/tools/skill-tools.js.map +1 -1
  53. package/mcp-server/package-lock.json +2 -2
  54. package/mcp-server/package.json +1 -1
  55. package/mcp-server/src/index.ts +1 -1
  56. package/mcp-server/src/services/budget-judgments.ts +5 -3
  57. package/mcp-server/src/services/identity.ts +2 -2
  58. package/mcp-server/src/services/memory-sessions.ts +24 -3
  59. package/mcp-server/src/services/neural-interface.ts +2 -1
  60. package/mcp-server/src/services/server-instructions.ts +2 -1
  61. package/mcp-server/src/services/tool-catalog.ts +1 -1
  62. package/mcp-server/src/services/tool-list.ts +1 -1
  63. package/mcp-server/src/services/tool-runtime.ts +2 -2
  64. package/mcp-server/src/services/typesafe-config.ts +1 -1
  65. package/mcp-server/src/tools/agents.ts +10 -9
  66. package/mcp-server/src/tools/choice.ts +17 -12
  67. package/mcp-server/src/tools/git-tools.ts +2 -2
  68. package/mcp-server/src/tools/morelogin.ts +6 -4
  69. package/mcp-server/src/tools/skill-tools.ts +11 -8
  70. package/neural-interface/i18n/en.json +504 -39
  71. package/neural-interface/i18n/pt-BR.json +504 -39
  72. package/neural-interface/lib/agent-sandbox-elevation.js +6 -0
  73. package/neural-interface/lib/antigravity/acp-client-host.js +554 -0
  74. package/neural-interface/lib/antigravity/acp-connection.js +299 -0
  75. package/neural-interface/lib/antigravity/bypass.js +100 -0
  76. package/neural-interface/lib/antigravity/catalog.js +273 -0
  77. package/neural-interface/lib/antigravity/home.js +260 -0
  78. package/neural-interface/lib/antigravity/migrate-gemini.js +139 -0
  79. package/neural-interface/lib/antigravity/panel-bridge.js +1058 -0
  80. package/neural-interface/lib/antigravity/policy-hook.mjs +78 -0
  81. package/neural-interface/lib/antigravity/policy.js +74 -0
  82. package/neural-interface/lib/antigravity/tool-call.js +178 -0
  83. package/neural-interface/lib/antigravity/transcripts.js +252 -0
  84. package/neural-interface/lib/antigravity/usage.js +43 -0
  85. package/neural-interface/lib/assistant-api.js +1 -0
  86. package/neural-interface/lib/assistant-brains/antigravity.js +423 -0
  87. package/neural-interface/lib/assistant-brains/codex.js +6 -1
  88. package/neural-interface/lib/assistant-budget-planner.js +7 -3
  89. package/neural-interface/lib/assistant-budget-split.js +42 -15
  90. package/neural-interface/lib/assistant-catalog.js +76 -9
  91. package/neural-interface/lib/assistant-config.js +6 -3
  92. package/neural-interface/lib/assistant-dispatch.js +211 -50
  93. package/neural-interface/lib/assistant-envelope.js +153 -1
  94. package/neural-interface/lib/assistant-evidence.js +80 -0
  95. package/neural-interface/lib/assistant-hidden-models.js +7 -5
  96. package/neural-interface/lib/assistant-persona.js +16 -11
  97. package/neural-interface/lib/assistant-plan-permissions.js +15 -0
  98. package/neural-interface/lib/assistant-router.js +237 -35
  99. package/neural-interface/lib/assistant-runtime.js +158 -12
  100. package/neural-interface/lib/assistant-task-prompt.js +10 -8
  101. package/neural-interface/lib/assistant-tools.js +2 -2
  102. package/neural-interface/lib/assistant-usage.js +29 -0
  103. package/neural-interface/lib/backup-service.js +6 -1
  104. package/neural-interface/lib/browser-guard-cdp.js +5 -4
  105. package/neural-interface/lib/browser-network-guard.js +274 -82
  106. package/neural-interface/lib/browser-tool-policy.js +12 -7
  107. package/neural-interface/lib/claude-agent-bridge.js +7 -0
  108. package/neural-interface/lib/claude-history-cache.js +210 -0
  109. package/neural-interface/lib/claude-model-catalog.js +5 -1
  110. package/neural-interface/lib/cli-install-api.js +1 -1
  111. package/neural-interface/lib/cli-install.js +160 -31
  112. package/neural-interface/lib/cli-resolver.js +9 -2
  113. package/neural-interface/lib/cli-update-planner.js +25 -15
  114. package/neural-interface/lib/codex-rollout-reader.js +52 -0
  115. package/neural-interface/lib/codex-worker-app-server.js +25 -6
  116. package/neural-interface/lib/editor-git-api.js +3 -20
  117. package/neural-interface/lib/exec-loop-command.js +9 -2
  118. package/neural-interface/lib/file-explorer-api.js +74 -40
  119. package/neural-interface/lib/file-explorer-watch.js +9 -3
  120. package/neural-interface/lib/git-bash/install.js +52 -6
  121. package/neural-interface/lib/git-bash/manager.js +59 -17
  122. package/neural-interface/lib/git-bash/pins.js +12 -0
  123. package/neural-interface/lib/git-read.js +2 -2
  124. package/neural-interface/lib/git-status-runner.js +390 -0
  125. package/neural-interface/lib/git-status-watchdog.js +40 -0
  126. package/neural-interface/lib/greeting/budgets.js +1 -0
  127. package/neural-interface/lib/greeting/engine.js +2 -2
  128. package/neural-interface/lib/greeting/sources.js +10 -12
  129. package/neural-interface/lib/greeting/writer.js +1 -1
  130. package/neural-interface/lib/mcp-client-config.js +37 -6
  131. package/neural-interface/lib/mcp-external-servers.js +40 -7
  132. package/neural-interface/lib/mcp-installer.js +2 -1
  133. package/neural-interface/lib/memory-session-capture.js +17 -3
  134. package/neural-interface/lib/morelogin.js +147 -25
  135. package/neural-interface/lib/native-loop-providers.js +423 -23
  136. package/neural-interface/lib/native-loop-runtime.js +23 -2
  137. package/neural-interface/lib/opencode-native-boundary.js +165 -22
  138. package/neural-interface/lib/owner-authority.js +1 -1
  139. package/neural-interface/lib/path-confine.js +12 -9
  140. package/neural-interface/lib/peer-identity.js +80 -6
  141. package/neural-interface/lib/pty-host/core.js +28 -1
  142. package/neural-interface/lib/pty-host/lifetime.js +12 -0
  143. package/neural-interface/lib/pty-host/manager.js +12 -1
  144. package/neural-interface/lib/remote-policy.js +1 -1
  145. package/neural-interface/lib/request-gate.js +3 -0
  146. package/neural-interface/lib/rulesets/api.js +6 -5
  147. package/neural-interface/lib/rulesets/fs-safe.js +1 -1
  148. package/neural-interface/lib/rulesets/installer.js +89 -10
  149. package/neural-interface/lib/rulesets/render.js +1 -1
  150. package/neural-interface/lib/session-cache-builder.js +64 -1
  151. package/neural-interface/lib/skills/catalog.js +5 -1
  152. package/neural-interface/lib/skills/creation-brief.js +1 -1
  153. package/neural-interface/lib/skills/creation-sources.js +19 -1
  154. package/neural-interface/lib/skills/deploy.js +9 -4
  155. package/neural-interface/lib/skills/draft-confinement.js +3 -1
  156. package/neural-interface/lib/skills/eval-confinement.js +3 -1
  157. package/neural-interface/lib/skills/eval-trace.js +16 -0
  158. package/neural-interface/lib/skills/export.js +5 -2
  159. package/neural-interface/lib/skills/known-bridge-digests.json +58 -4
  160. package/neural-interface/lib/skills/ownership.js +1 -1
  161. package/neural-interface/lib/skills/roots.js +10 -3
  162. package/neural-interface/lib/skills/runtime-checks.js +6 -3
  163. package/neural-interface/lib/skills/service.js +15 -7
  164. package/neural-interface/lib/skills/telemetry.js +6 -0
  165. package/neural-interface/lib/synabun-version.js +1 -1
  166. package/neural-interface/lib/system-activity.js +1 -1
  167. package/neural-interface/lib/whatsapp/brain.js +7 -3
  168. package/neural-interface/lib/whatsapp/cards.js +22 -9
  169. package/neural-interface/lib/whatsapp/config.js +1 -1
  170. package/neural-interface/lib/worker-confinement.js +77 -1
  171. package/neural-interface/lib/worker-question.js +74 -0
  172. package/neural-interface/lib/worker-resume.js +13 -4
  173. package/neural-interface/lib/worker-sandbox.js +46 -16
  174. package/neural-interface/package-lock.json +18 -2
  175. package/neural-interface/package.json +2 -1
  176. package/neural-interface/public/onboarding.html +1 -1
  177. package/neural-interface/public/shared/agent-runtime-options.js +89 -11
  178. package/neural-interface/public/shared/agy/agy-bypass.js +47 -0
  179. package/neural-interface/public/shared/agy/agy-client.js +266 -0
  180. package/neural-interface/public/shared/agy/agy-commands.js +120 -0
  181. package/neural-interface/public/shared/agy/agy-copy.js +326 -0
  182. package/neural-interface/public/shared/agy/agy-diff.js +161 -0
  183. package/neural-interface/public/shared/agy/agy-events.js +469 -0
  184. package/neural-interface/public/shared/agy/agy-greeting.js +26 -0
  185. package/neural-interface/public/shared/agy/agy-options.js +232 -0
  186. package/neural-interface/public/shared/agy/agy-panel.js +2543 -0
  187. package/neural-interface/public/shared/agy/agy-render.js +392 -0
  188. package/neural-interface/public/shared/agy/agy-setup.js +149 -0
  189. package/neural-interface/public/shared/agy/agy-styles.js +604 -0
  190. package/neural-interface/public/shared/agy/agy-tabs-logic.js +181 -0
  191. package/neural-interface/public/shared/api.js +4 -4
  192. package/neural-interface/public/shared/assistant/asst-agents-dock.js +3 -4
  193. package/neural-interface/public/shared/assistant/asst-api.js +15 -3
  194. package/neural-interface/public/shared/assistant/asst-brain-picker.js +56 -9
  195. package/neural-interface/public/shared/assistant/asst-budget.js +5 -4
  196. package/neural-interface/public/shared/assistant/asst-computer.js +26 -2
  197. package/neural-interface/public/shared/assistant/asst-control.js +48 -0
  198. package/neural-interface/public/shared/assistant/asst-hosts.js +16 -7
  199. package/neural-interface/public/shared/assistant/asst-limit-dock.js +6 -1
  200. package/neural-interface/public/shared/assistant/asst-limit-wait.js +38 -5
  201. package/neural-interface/public/shared/assistant/asst-motion-gate.js +80 -0
  202. package/neural-interface/public/shared/assistant/asst-panel.js +46 -9
  203. package/neural-interface/public/shared/assistant/asst-render.js +112 -18
  204. package/neural-interface/public/shared/assistant/asst-route.js +237 -57
  205. package/neural-interface/public/shared/assistant/asst-routes-editor.js +9 -8
  206. package/neural-interface/public/shared/assistant/asst-scene-styles.js +55 -23
  207. package/neural-interface/public/shared/assistant/asst-scenes.js +279 -80
  208. package/neural-interface/public/shared/assistant/asst-sidepanel-styles.js +4 -4
  209. package/neural-interface/public/shared/assistant/asst-sidepanel.js +51 -7
  210. package/neural-interface/public/shared/assistant/asst-slash.js +8 -2
  211. package/neural-interface/public/shared/assistant/asst-state.js +76 -9
  212. package/neural-interface/public/shared/assistant/asst-style-cache.js +44 -0
  213. package/neural-interface/public/shared/assistant/asst-styles.js +58 -4
  214. package/neural-interface/public/shared/assistant/asst-tool-kinds.js +34 -7
  215. package/neural-interface/public/shared/assistant/asst-usage.js +2 -1
  216. package/neural-interface/public/shared/cdx/cdx-output.js +26 -3
  217. package/neural-interface/public/shared/cdx/cdx-panel.js +14 -2
  218. package/neural-interface/public/shared/cdx/cdx-preview.js +12 -0
  219. package/neural-interface/public/shared/cdx/cdx-protocol.js +11 -10
  220. package/neural-interface/public/shared/cdx/cdx-render.js +10 -9
  221. package/neural-interface/public/shared/cdx/cdx-requests.js +11 -1
  222. package/neural-interface/public/shared/cdx/cdx-styles.js +4 -0
  223. package/neural-interface/public/shared/cdx/cdx-tabs.js +75 -5
  224. package/neural-interface/public/shared/cli-install-controller.js +218 -0
  225. package/neural-interface/public/shared/cli-install-view.js +7 -4
  226. package/neural-interface/public/shared/cli-status.js +9 -8
  227. package/neural-interface/public/shared/constants.js +3 -2
  228. package/neural-interface/public/shared/context-menu-model.js +1 -0
  229. package/neural-interface/public/shared/cp/cp-permissions.js +12 -0
  230. package/neural-interface/public/shared/cp/cp-restore.js +162 -0
  231. package/neural-interface/public/shared/cp/cp-tool-cards.js +10 -3
  232. package/neural-interface/public/shared/desktop-bridge.js +17 -0
  233. package/neural-interface/public/shared/desktop-titlebar-view.js +12 -0
  234. package/neural-interface/public/shared/desktop-titlebar.js +13 -4
  235. package/neural-interface/public/shared/desktop-update-view.js +1 -0
  236. package/neural-interface/public/shared/explorer/fx-api.js +4 -4
  237. package/neural-interface/public/shared/explorer/fx-links.js +3 -3
  238. package/neural-interface/public/shared/explorer/fx-logic.js +1 -1
  239. package/neural-interface/public/shared/explorer/fx-main.js +48 -16
  240. package/neural-interface/public/shared/explorer/fx-tree.js +1 -0
  241. package/neural-interface/public/shared/greeting/greeting-card.js +2 -2
  242. package/neural-interface/public/shared/greeting/greeting-view.js +1 -0
  243. package/neural-interface/public/shared/html-shell.js +21 -4
  244. package/neural-interface/public/shared/loading-teardown.js +34 -0
  245. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +6 -1
  246. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +28 -7
  247. package/neural-interface/public/shared/ocp-v2/ocp-v2-rehydrate.js +13 -4
  248. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +17 -1
  249. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +6 -0
  250. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +101 -68
  251. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +46 -3
  252. package/neural-interface/public/shared/provider-icons.js +4 -2
  253. package/neural-interface/public/shared/settings/settings-rules.js +2 -2
  254. package/neural-interface/public/shared/settings/settings-skill-folders-pane.js +1 -1
  255. package/neural-interface/public/shared/settings/settings-skill-folders.js +2 -2
  256. package/neural-interface/public/shared/showcase-privacy.js +6 -1
  257. package/neural-interface/public/shared/sidepanel-session-status.js +3 -3
  258. package/neural-interface/public/shared/skills/ss-creation.js +6 -3
  259. package/neural-interface/public/shared/skills/ss-dialog.js +5 -2
  260. package/neural-interface/public/shared/skills/ss-lint.js +9 -1
  261. package/neural-interface/public/shared/skills/ss-profiles.js +44 -6
  262. package/neural-interface/public/shared/skills/ss-review-view.js +2 -2
  263. package/neural-interface/public/shared/skills/studio/sx-actions.js +1 -1
  264. package/neural-interface/public/shared/skills/studio/sx-create-ai-view.js +1 -1
  265. package/neural-interface/public/shared/skills/studio/sx-create-ai.js +2 -1
  266. package/neural-interface/public/shared/skills/studio/sx-dom.js +2 -1
  267. package/neural-interface/public/shared/skills/studio/sx-fixes-view.js +359 -0
  268. package/neural-interface/public/shared/skills/studio/sx-fixes.js +327 -0
  269. package/neural-interface/public/shared/skills/studio/sx-inspector.js +1 -1
  270. package/neural-interface/public/shared/skills/studio/sx-install-flow.js +9 -6
  271. package/neural-interface/public/shared/skills/studio/sx-install.js +20 -3
  272. package/neural-interface/public/shared/skills/studio/sx-library-view.js +144 -35
  273. package/neural-interface/public/shared/skills/studio/sx-library.js +6 -2
  274. package/neural-interface/public/shared/skills/studio/sx-main.js +11 -2
  275. package/neural-interface/public/shared/skills/studio/sx-review-view.js +1 -1
  276. package/neural-interface/public/shared/skills/studio/sx-subject.js +7 -6
  277. package/neural-interface/public/shared/skills/studio/sx-workspace-view.js +6 -5
  278. package/neural-interface/public/shared/state.js +1 -1
  279. package/neural-interface/public/shared/storage-retry.js +47 -0
  280. package/neural-interface/public/shared/storage.js +25 -4
  281. package/neural-interface/public/shared/streaming-markdown.js +58 -0
  282. package/neural-interface/public/shared/styles.css +362 -35
  283. package/neural-interface/public/shared/synabun-mascot.js +27 -8
  284. package/neural-interface/public/shared/synabun-ticker.js +147 -12
  285. package/neural-interface/public/shared/text-cache.js +29 -0
  286. package/neural-interface/public/shared/tool-list-picker.js +2 -2
  287. package/neural-interface/public/shared/tool-list-view.js +2 -2
  288. package/neural-interface/public/shared/topright-overflow-logic.js +54 -0
  289. package/neural-interface/public/shared/ui-antigravity-panel.js +19 -0
  290. package/neural-interface/public/shared/ui-assistant.js +2 -0
  291. package/neural-interface/public/shared/ui-automation-studio.js +20 -12
  292. package/neural-interface/public/shared/ui-claude-panel.js +374 -134
  293. package/neural-interface/public/shared/ui-cli-banner.js +23 -167
  294. package/neural-interface/public/shared/ui-file-explorer.js +48 -6
  295. package/neural-interface/public/shared/ui-file-links.js +2 -2
  296. package/neural-interface/public/shared/ui-games.js +2 -2
  297. package/neural-interface/public/shared/ui-judgments.js +1 -1
  298. package/neural-interface/public/shared/ui-keybinds.js +8 -2
  299. package/neural-interface/public/shared/ui-link.js +3 -3
  300. package/neural-interface/public/shared/ui-loading.js +5 -4
  301. package/neural-interface/public/shared/ui-menubar.js +2 -2
  302. package/neural-interface/public/shared/ui-native-loop-router.js +4 -0
  303. package/neural-interface/public/shared/ui-navbar.js +45 -0
  304. package/neural-interface/public/shared/ui-notifications.js +1 -0
  305. package/neural-interface/public/shared/ui-resume.js +9 -7
  306. package/neural-interface/public/shared/ui-schedules-studio.js +41 -10
  307. package/neural-interface/public/shared/ui-scroll-follow.js +16 -5
  308. package/neural-interface/public/shared/ui-sessions.js +22 -12
  309. package/neural-interface/public/shared/ui-settings.js +90 -65
  310. package/neural-interface/public/shared/ui-sidepanel-session-entry.js +9 -2
  311. package/neural-interface/public/shared/ui-sidepanel-sessions.js +3 -3
  312. package/neural-interface/public/shared/ui-sidepanel-tray.js +48 -7
  313. package/neural-interface/public/shared/ui-sidepanel-windows.js +3 -3
  314. package/neural-interface/public/shared/ui-terminal.js +146 -10
  315. package/neural-interface/public/shared/ui-tool-updates.js +3 -3
  316. package/neural-interface/public/shared/ui-tooltip.js +71 -46
  317. package/neural-interface/public/shared/ui-topright-overflow.js +233 -0
  318. package/neural-interface/public/shared/ui-whiteboard.js +1 -1
  319. package/neural-interface/public/shared/ui-worker-end.js +1 -1
  320. package/neural-interface/public/shared/whatsapp/wa-view.js +2 -2
  321. package/neural-interface/public/shared/worker-questions.js +34 -0
  322. package/neural-interface/public/sidepanel-session.html +1 -1
  323. package/neural-interface/server.js +731 -466
  324. package/neural-interface/templates/rulesets/README.md +1 -1
  325. package/neural-interface/templates/rulesets/legacy-hashes.json +5 -5
  326. package/neural-interface/templates/rulesets/manifest.json +1 -1
  327. package/npm-shrinkwrap.json +18 -2
  328. package/package.json +2 -1
  329. package/skills/skill-forge/SKILL.md +4 -4
  330. package/skills/synabun/SKILL.md +1 -1
  331. package/skills/synabun/modules/schedule.md +3 -3
  332. /package/neural-interface/templates/rulesets/{gemini.md → antigravity.md} +0 -0
@@ -0,0 +1,181 @@
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun — Antigravity panel: tabs, attachments and dispatched-run events (no DOM)
3
+ // ═══════════════════════════════════════════
4
+ //
5
+ // A tab is one ACP session of Antigravity's in one project folder. What is saved
6
+ // per window (`synabun-antigravity-panel-tabs-<windowId>`, read by the side panel
7
+ // tray too) is `{ tabs, activeIdx }` with the fields below; the transcript is not
8
+ // saved: a tab with a session id replays it from Antigravity (session/load).
9
+ // `bypass` is the tab's Bypass (agy-bypass.js): off unless the owner turned it on.
10
+
11
+ export const TAB_FIELDS = Object.freeze(['id', 'sessionId', 'project', 'geminiDir', 'label', 'title', 'titleByUser', 'additionalDirectories', 'draft',
12
+ 'running', 'needsYou', 'automationRunId', 'automationActive', 'automationOwnerId', 'releasedRunId', 'createdAt', 'bypass']);
13
+ export const MAX_TABS = 12;
14
+ export const ATTACH_LIMITS = Object.freeze({ maxAttachments: 8, maxBytes: 15 * 1024 * 1024 });
15
+ const ATTACHABLE = /^(image\/(png|jpeg|gif|webp)|application\/pdf|text\/[a-z0-9.+-]+|application\/(json|xml|x-yaml|yaml)|audio\/(wav|x-wav|mpeg|mp4|aac|flac|ogg|opus|webm))$/;
16
+ const TEXT_EXTENSIONS = /\.(txt|md|markdown|json|ya?ml|xml|csv|log|js|mjs|cjs|ts|tsx|jsx|py|rb|go|rs|java|kt|swift|c|cc|cpp|h|hpp|cs|php|sh|zsh|bash|css|scss|html|sql|toml|ini|env)$/i;
17
+
18
+ let counter = 0;
19
+ const newId = () => (globalThis.crypto?.randomUUID?.() || `agy-${Date.now().toString(36)}-${(counter += 1)}`);
20
+
21
+ export function newTab({ project = '', sessionId = null, title = null, geminiDir = null } = {}) {
22
+ return {
23
+ id: newId(), sessionId, project: String(project || ''), geminiDir: typeof geminiDir === 'string' && geminiDir.trim() ? geminiDir.trim() : null,
24
+ label: 'New session', title, titleByUser: null, additionalDirectories: [], draft: '',
25
+ running: false, needsYou: false, automationRunId: null, automationActive: false, automationOwnerId: null,
26
+ releasedRunId: null, createdAt: Date.now(), bypass: false,
27
+ };
28
+ }
29
+
30
+ /**
31
+ * What the tray and the panel show for a tab: its session's title, else a short id, else "New session". `words`
32
+ * gives the page's language for the last three (the saved label stays English when none is given).
33
+ */
34
+ export function tabLabel(tab, words = {}) {
35
+ const title = typeof tab?.title === 'string' ? tab.title.trim() : '';
36
+ if (title) return title.length > 60 ? `${title.slice(0, 57)}…` : title;
37
+ if (tab?.automationRunId) return `${words.run || 'Dispatched run'} · ${String(tab.automationRunId).slice(0, 8)}`;
38
+ if (tab?.sessionId) return `Antigravity · ${String(tab.sessionId).slice(0, 8)}`;
39
+ return words.newSession || 'New session';
40
+ }
41
+
42
+ /** The saved form: `{ tabs, activeIdx }`, each tab its persisted fields only. */
43
+ export function serializeTabs(tabs, activeIdx) {
44
+ const list = (Array.isArray(tabs) ? tabs : []).slice(0, MAX_TABS).map((tab) => {
45
+ const out = {};
46
+ for (const key of TAB_FIELDS) if (tab?.[key] !== undefined) out[key] = tab[key];
47
+ out.label = tabLabel(tab);
48
+ return out;
49
+ });
50
+ const idx = Number.isInteger(activeIdx) ? Math.min(Math.max(activeIdx, 0), Math.max(list.length - 1, 0)) : 0;
51
+ return { tabs: list, activeIdx: idx };
52
+ }
53
+
54
+ /**
55
+ * Saved tabs as the panel restores them: invalid rows dropped, nothing marked running (the page that ran it is
56
+ * gone: a live run is reattached by the native-loop router, not by a flag), an id for every tab.
57
+ */
58
+ export function restoreTabs(raw) {
59
+ let data = raw;
60
+ if (typeof raw === 'string') { try { data = JSON.parse(raw); } catch { data = null; } }
61
+ const rows = Array.isArray(data?.tabs) ? data.tabs : [];
62
+ const tabs = [];
63
+ const seen = new Set();
64
+ for (const row of rows) {
65
+ if (!row || typeof row !== 'object') continue;
66
+ const tab = { ...newTab(), ...Object.fromEntries(TAB_FIELDS.filter((key) => row[key] !== undefined).map((key) => [key, row[key]])) };
67
+ if (typeof tab.id !== 'string' || !tab.id || seen.has(tab.id)) tab.id = newId();
68
+ seen.add(tab.id);
69
+ if (tab.sessionId !== null && (typeof tab.sessionId !== 'string' || !/^[A-Za-z0-9._:@-]{1,200}$/.test(tab.sessionId))) tab.sessionId = null;
70
+ tab.project = typeof tab.project === 'string' ? tab.project : '';
71
+ tab.geminiDir = typeof tab.geminiDir === 'string' && tab.geminiDir.trim() ? tab.geminiDir.trim() : null;
72
+ tab.draft = typeof tab.draft === 'string' ? tab.draft : '';
73
+ tab.titleByUser = typeof tab.titleByUser === 'string' && tab.titleByUser.trim() ? tab.titleByUser.trim().slice(0, 120) : null;
74
+ tab.additionalDirectories = Array.isArray(tab.additionalDirectories)
75
+ ? [...new Set(tab.additionalDirectories.filter((p) => typeof p === 'string' && p && p !== tab.project))].slice(0, 8)
76
+ : [];
77
+ // Bypass comes back only as the owner's own pick (a literal true), never from anything else in storage.
78
+ tab.bypass = tab.bypass === true;
79
+ tab.running = false;
80
+ tab.needsYou = false;
81
+ tabs.push(tab);
82
+ if (tabs.length >= MAX_TABS) break;
83
+ }
84
+ const activeIdx = Number.isInteger(data?.activeIdx) && data.activeIdx >= 0 && data.activeIdx < tabs.length ? data.activeIdx : 0;
85
+ return { tabs, activeIdx };
86
+ }
87
+
88
+ /** The MIME type a dropped or pasted file is sent as (a browser leaves it empty for many text files). */
89
+ export function attachmentType(file) {
90
+ const declared = String(file?.type || '').toLowerCase();
91
+ if (declared) return declared;
92
+ return TEXT_EXTENSIONS.test(String(file?.name || '')) ? 'text/plain' : '';
93
+ }
94
+
95
+ /** Whether a file may be attached now: null when it may, else { key, params } for agyText. */
96
+ export function attachmentProblem(file, staged = [], limits = ATTACH_LIMITS) {
97
+ const name = String(file?.name || 'file');
98
+ if (staged.length >= limits.maxAttachments) return { key: 'attachmentTooMany', params: { count: limits.maxAttachments } };
99
+ if (!ATTACHABLE.test(attachmentType(file))) return { key: 'attachmentType', params: { name } };
100
+ if (Number(file?.size) > limits.maxBytes) return { key: 'attachmentTooLarge', params: { name, size: Math.round(limits.maxBytes / 1048576) } };
101
+ return null;
102
+ }
103
+
104
+ /**
105
+ * A picture another part of the app hands the panel (the whiteboard's Send to Panel, also through a detached window)
106
+ * as a staged attachment: `{ file: { name, mimeType, dataUrl } }`, `{ problem: { key, params } }` when it may not
107
+ * be attached now (the same limits as a pasted file), or null when it is not a PNG, JPEG, GIF or WebP data URL.
108
+ */
109
+ export function stagedPicture(dataUrl, staged = [], { name = 'whiteboard', now = Date.now(), limits = ATTACH_LIMITS } = {}) {
110
+ const match = /^data:(image\/(png|jpeg|gif|webp));base64,([A-Za-z0-9+/]+={0,2})$/.exec(String(dataUrl || ''));
111
+ if (!match) return null;
112
+ const body = match[3];
113
+ const size = Math.floor((body.length * 3) / 4) - (body.endsWith('==') ? 2 : body.endsWith('=') ? 1 : 0);
114
+ const file = { name: `${name}-${now}.${match[2] === 'jpeg' ? 'jpg' : match[2]}`, type: match[1], size };
115
+ const problem = attachmentProblem(file, staged, limits);
116
+ return problem ? { problem } : { file: { name: file.name, mimeType: match[1], dataUrl } };
117
+ }
118
+
119
+ /** A data URL's base64 body, or '' for anything else. */
120
+ export function base64Of(dataUrl) {
121
+ const match = /^data:[^;,]*(?:;[^;,]*)*;base64,([A-Za-z0-9+/]+={0,2})$/.exec(String(dataUrl || ''));
122
+ return match ? match[1] : '';
123
+ }
124
+
125
+ /** The bridge's `prompt` request for a tab: its text and staged attachments ({ name, mimeType, dataUrl }). */
126
+ export function promptRequest(tab, text, staged = []) {
127
+ return {
128
+ sessionId: tab.sessionId,
129
+ text: String(text || ''),
130
+ attachments: staged.map((file) => ({ name: file.name, mimeType: file.mimeType, data: base64Of(file.dataUrl) })).filter((file) => file.data),
131
+ paths: staged.filter((file) => file.path).map((file) => file.path),
132
+ };
133
+ }
134
+
135
+ /**
136
+ * One event of a dispatched run (the native-loop router's `sync:sidepanel:provider-event` payload, or its `event`)
137
+ * as what the transcript does with it: { kind: 'user', text, iteration } | { kind: 'update', update } |
138
+ * { kind: 'notice', text, severity } | null.
139
+ */
140
+ export function automationEvent(payload) {
141
+ const message = payload?.event && typeof payload.event === 'object' && (payload.eventType || payload.provider) ? payload : (payload?.event || payload);
142
+ const eventType = message?.eventType || message?.event?.type || message?.type;
143
+ const event = message?.event ?? message;
144
+ if (!event || typeof event !== 'object') return null;
145
+ if (event.type === 'synabun.user_prompt' || eventType === 'synabun.user_prompt') return { kind: 'user', text: String(event.text || ''), iteration: message.iteration ?? null };
146
+ if (event.type === 'synabun.confinement') return { kind: 'notice', text: `${event.action === 'warned' ? 'Guard warning' : 'Refused'}: ${event.reason || event.rule || ''}`.trim(), severity: event.action === 'warned' ? 'warning' : 'error' };
147
+ if (event.type === 'synabun.provider_exit') return { kind: 'notice', text: `Antigravity stopped${event.error ? `: ${event.error}` : ''}`, severity: 'error' };
148
+ if (event.type === 'synabun.error') return { kind: 'notice', text: String(event.message || event.error || 'Error'), severity: 'error' };
149
+ if (typeof event.sessionUpdate === 'string') return { kind: 'update', update: event };
150
+ return null;
151
+ }
152
+
153
+ /** The per-run Antigravity home (`--gemini-dir`) a dispatched run executed in, or null when none is named. */
154
+ export function runGeminiDir(run) {
155
+ const raw = run?.antigravityHome ?? run?.geminiDir ?? run?.resumeInfo?.antigravityHome ?? run?.runtime?.antigravityHome ?? null;
156
+ return typeof raw === 'string' && raw.trim() ? raw.trim() : null;
157
+ }
158
+
159
+ /** The bridge request (`session:load` or `session:resume`) that opens an existing tab's session in the panel. */
160
+ export function openSessionRequest(tab, { resume = false, folders = [] } = {}) {
161
+ const dirs = Array.isArray(folders) ? folders.filter((p) => typeof p === 'string' && p) : [];
162
+ const geminiDir = typeof tab?.geminiDir === 'string' && tab.geminiDir ? tab.geminiDir : null;
163
+ return {
164
+ type: resume ? 'session:resume' : 'session:load',
165
+ payload: {
166
+ sessionId: tab.sessionId,
167
+ cwd: tab.project,
168
+ tabId: tab.id,
169
+ bypass: tab?.bypass === true,
170
+ ...(geminiDir ? { geminiDir } : {}),
171
+ ...(dirs.length ? { additionalDirectories: dirs } : {}),
172
+ },
173
+ };
174
+ }
175
+
176
+ /** The notice shown when loading or resuming a tab's session fails (null when sign-in handles it instead). */
177
+ export function sessionLoadNotice(error, textOf) {
178
+ if (error?.authRequired) return null;
179
+ if (error?.code === 'HOME_PRUNED') return { text: textOf('runHomePruned'), severity: 'error' };
180
+ return { text: textOf('historyFailed', { error: error?.message || String(error ?? '') }), severity: 'error' };
181
+ }
@@ -881,15 +881,15 @@ export async function fetchOpencodeSessions({ project, limit, offset, search } =
881
881
  return jsonFetch(`/api/opencode/sessions?${params}`);
882
882
  }
883
883
 
884
- // ─── Gemini CLI Sessions (Resume) ─────
884
+ // ─── Antigravity CLI Sessions (Resume) ─────
885
885
 
886
- export async function fetchGeminiSessions({ project, limit, offset, search } = {}) {
886
+ export async function fetchAntigravitySessions({ project, limit, offset, search } = {}) {
887
887
  const params = new URLSearchParams();
888
888
  if (project) params.set('project', project);
889
889
  if (limit) params.set('limit', String(limit));
890
890
  if (offset) params.set('offset', String(offset));
891
891
  if (search) params.set('search', search);
892
- return jsonFetch(`/api/gemini/sessions?${params}`);
892
+ return jsonFetch(`/api/antigravity/sessions?${params}`);
893
893
  }
894
894
 
895
895
  // ─── Hybrid session content search (FTS5 + semantic for Claude) ─────
@@ -1143,7 +1143,7 @@ export async function fetchRulesStatus() {
1143
1143
  }
1144
1144
  }
1145
1145
 
1146
- /** The rendered text for a host (claude, codex, opencode, gemini, cursor) or the copy-only `coexistence` snippet. */
1146
+ /** The rendered text for a host (claude, codex, opencode, antigravity, cursor) or the copy-only `coexistence` snippet. */
1147
1147
  export async function fetchRulesText(host) {
1148
1148
  return jsonFetch(`/api/setup/rules/${encodeURIComponent(host)}/text`);
1149
1149
  }
@@ -23,13 +23,12 @@
23
23
  // last card, so one stray click or key there must not end the session's work.
24
24
 
25
25
  import { on } from '../state.js';
26
- import { getProviderMeta } from '../provider-icons.js';
27
26
  import { listAssistantRuns } from './asst-api.js';
28
27
  import { buildRunCard, tickRunCard, updateRunCard } from './asst-render.js';
29
28
  import { awaitsAnswer, compactActivity, isLivePhase, phaseLine, sceneFor, sceneSvg, stageLabel, stagePhase, stepLine } from './asst-scenes.js';
30
29
  import { injectSceneStyles } from './asst-scene-styles.js';
31
30
  import { plural } from './asst-tool-kinds.js';
32
- import { isRemovableRun, isRunIdle, isTerminalRunStatus, mergeRunDescriptor, runFromPayload, runNeedsAttention, sortRuns } from './asst-state.js';
31
+ import { isRemovableRun, isRunIdle, isTerminalRunStatus, mergeRunDescriptor, providerMeta, runFromPayload, runNeedsAttention, sortRuns } from './asst-state.js';
33
32
  import { createConfirmGate } from './asst-stop.js';
34
33
 
35
34
  const TICK_MS = 5000;
@@ -166,8 +165,8 @@ export function createAgentsDock(hostEl, hooks = {}) {
166
165
  return {
167
166
  t: hooks.t,
168
167
  compact: true,
169
- providerIcon: (p) => getProviderMeta(p).icon,
170
- providerColor: (p) => getProviderMeta(p).color,
168
+ providerIcon: (p) => providerMeta(p).icon,
169
+ providerColor: (p) => providerMeta(p).color,
171
170
  onFocus: hooks.onFocus,
172
171
  onStop: hooks.onStop,
173
172
  onRead: hooks.onRead,
@@ -48,7 +48,7 @@ export function fetchAssistantCatalog({ force = false } = {}) {
48
48
 
49
49
  // ── Hidden models (Assistant → Models) ───────
50
50
 
51
- /** → { ok, providers: { 'claude-code': [], codex: [], opencode: [] } } */
51
+ /** → { ok, providers: { 'claude-code': [], codex: [], opencode: [], antigravity: [] } } */
52
52
  export function getHiddenModelLists() {
53
53
  return jsonFetch('/api/assistant/hidden-models');
54
54
  }
@@ -98,8 +98,20 @@ export function escalateAssistantRun(runId, target) {
98
98
 
99
99
  // ── Desktop (computer use) ───────────────────
100
100
 
101
- export function getDesktopStatus() {
102
- return jsonFetch('/api/desktop/status');
101
+ /** How long the status check may take before it counts as failed (the tray must never spin forever). */
102
+ export const DESKTOP_STATUS_TIMEOUT_MS = 10_000;
103
+
104
+ export async function getDesktopStatus({ timeoutMs = DESKTOP_STATUS_TIMEOUT_MS } = {}) {
105
+ const ctrl = typeof AbortController === 'function' ? new AbortController() : null;
106
+ const timer = ctrl ? setTimeout(() => ctrl.abort(), timeoutMs) : null;
107
+ try {
108
+ return await jsonFetch('/api/desktop/status', ctrl ? { signal: ctrl.signal } : {});
109
+ } catch (err) {
110
+ if (ctrl?.signal.aborted) throw Object.assign(new Error('The status check timed out'), { code: 'TIMEOUT', timedOut: true });
111
+ throw err;
112
+ } finally {
113
+ if (timer) clearTimeout(timer);
114
+ }
103
115
  }
104
116
 
105
117
  /** step: 'start'|'recheck'|'install_toolchain'|'request_screen'|'request_accessibility'|'open_settings' (+ { pane }). */
@@ -42,6 +42,7 @@ import { createMascotControl } from './asst-mascot.js';
42
42
  import { modelChipForms, modelChipName, modelChipTag } from './asst-model-chip.js';
43
43
  import {
44
44
  ACCOUNT_PROVIDERS,
45
+ BRAIN_PROVIDERS,
45
46
  brainRequiresSwitch,
46
47
  emptyProjectView,
47
48
  modelShortName,
@@ -50,6 +51,7 @@ import {
50
51
  PLAN_MODE_PROVIDERS,
51
52
  permissionModeLabel,
52
53
  PROVIDERS,
54
+ providerMeta,
53
55
  projectLabel,
54
56
  resolveBrainDefaults,
55
57
  TEST_DENSITIES,
@@ -113,6 +115,7 @@ const PLAN_DESC = {
113
115
  'claude-code': 'Plan first; approving the plan continues in the approval mode below',
114
116
  codex: 'Native Codex plan mode (read-only sandbox)',
115
117
  opencode: 'OpenCode plan agent (no file edits)',
118
+ antigravity: 'Antigravity explores and proposes a plan; code changes wait for your approval',
116
119
  };
117
120
 
118
121
  function agentName(agent) {
@@ -194,7 +197,7 @@ function hiddenOpencodeIds(remote) {
194
197
  }
195
198
 
196
199
  // The models the user hid (Assistant → Models), per provider, as the last catalog load saw them.
197
- let _hidden = { 'claude-code': new Set(), codex: new Set(), opencode: new Set() };
200
+ let _hidden = { 'claude-code': new Set(), codex: new Set(), opencode: new Set(), antigravity: new Set() };
198
201
 
199
202
  function hiddenSets(remote) {
200
203
  const out = {};
@@ -295,7 +298,7 @@ function catalogSaysNone(remote, provider, local) {
295
298
  }
296
299
 
297
300
  /**
298
- * { 'claude-code': [...], codex: [...], opencode: [...] }: the models of every
301
+ * { 'claude-code': [...], codex: [...], opencode: [...], antigravity: [...] }: the models of every
299
302
  * Assistant model menu and of the Routes editor, from GET /api/assistant/catalog
300
303
  * (see mergeProviderModels). Models the user hid (Assistant → Models, and
301
304
  * Settings → OpenCode for OpenCode) never appear. `force` asks the server for a
@@ -316,6 +319,7 @@ export async function loadModelCatalog({ force = false } = {}) {
316
319
  // No catalog (request failed): the last known disabled lists still apply.
317
320
  const sets = remote ? hiddenSets(remote) : { ..._hidden, opencode: new Set([...(_hidden.opencode || []), ...hiddenOpencodeIds(null)]) };
318
321
  _hidden = sets;
322
+ if (remote?.providers) _providerInfo = remote.providers;
319
323
  for (const provider of PROVIDERS) {
320
324
  const local = getModelsForProfile(provider) || [];
321
325
  // An empty list the catalog vouches for is its answer: the window's own rows do not stand in for it.
@@ -366,6 +370,33 @@ function keepCatalog(catalog, sets) {
366
370
  /** The last loadModelCatalog() result, synchronously (null before the first load). */
367
371
  export function cachedModelCatalog() { return _catalog; }
368
372
 
373
+ // The catalog's provider entries ([{ id, brain, accounts, priced, signedIn?, tier? }]) as the last load saw them:
374
+ // Antigravity's sign-in state is read from there (it signs in inside its own CLI; SynaBun keeps no account of it).
375
+ let _providerInfo = null;
376
+
377
+ /** One provider's catalog entry ({ id, brain, signedIn?, tier?, … }), from an array or a map; null when unknown. */
378
+ export function catalogProviderInfo(provider, providers = _providerInfo) {
379
+ if (!provider || !providers) return null;
380
+ if (Array.isArray(providers)) return providers.find(p => p && p.id === provider) || null;
381
+ const entry = providers[provider];
382
+ return entry && typeof entry === 'object' ? entry : null;
383
+ }
384
+
385
+ /**
386
+ * What the model menu and the model chip say about Antigravity's sign-in (`info`: its catalog entry):
387
+ * { state: 'signedIn' | 'signedOut' | 'unknown', text }. Antigravity signs in with Google inside its own panel;
388
+ * the tier is the plan its session reported ("Google AI Pro").
389
+ */
390
+ export function antigravitySignInView(info, t = null) {
391
+ const tr = tf(t);
392
+ const tier = typeof info?.tier === 'string' && info.tier.trim() ? info.tier.trim() : (typeof info?.tier?.name === 'string' ? info.tier.name.trim() : '');
393
+ if (info?.signedIn === true) {
394
+ return { state: 'signedIn', text: tier ? tr('assistant.brain.antigravitySignedInTier', 'Signed in with Google ({tier})', { tier }) : tr('assistant.brain.antigravitySignedIn', 'Signed in with Google') };
395
+ }
396
+ if (info?.signedIn === false) return { state: 'signedOut', text: tr('assistant.brain.antigravitySignedOut', 'Not signed in: open the Antigravity panel to sign in') };
397
+ return { state: 'unknown', text: tr('assistant.brain.antigravitySignInUnknown', 'Sign-in not checked yet: Antigravity signs in inside its own panel') };
398
+ }
399
+
369
400
  /** UI metadata from the same catalog load; no second list or extra endpoint. */
370
401
  export function modelCatalogStatus() {
371
402
  return { partial: _catalogPartial, hiddenCounts: Object.fromEntries(PROVIDERS.map(provider => [provider, _hidden[provider]?.size || 0])) };
@@ -474,7 +505,8 @@ function tierTag(m, tr) {
474
505
  /**
475
506
  * Model menu grouped by provider, filterable, with "Custom model id…".
476
507
  * opts: { brain, onSelect(target), includeHere, hereLabel, title, t, placement,
477
- * current:{provider, model}|null, warnSwitch, providers, onToast(text),
508
+ * current:{provider, model}|null, warnSwitch, providers (default: the brain
509
+ * providers, so a worker-only one is listed only when named), onToast(text),
478
510
  * accept(provider, model) → bool (only those models), emptyLabel (when none
479
511
  * passes), allowCustom (default true), zIndex (see asst-menu.js),
480
512
  * accountItems (menu items listed first, under accountTitle) }
@@ -546,13 +578,15 @@ export async function openModelMenu(anchor, opts = {}) {
546
578
  if (includeHere) {
547
579
  items.push({ kind: 'radio', id: '__here', label: hereLabel || tr('assistant.routes.brainDecides', 'Let the brain decide'), selected: !current, onSelect: () => onSelect?.({ here: true }) });
548
580
  }
549
- const list = Array.isArray(providers) && providers.length ? PROVIDERS.filter(p => providers.includes(p)) : PROVIDERS;
581
+ // Without `providers` the menu is a brain's (the toolbar's model field, the WhatsApp brain): every provider the
582
+ // Assistant can run on (BRAIN_PROVIDERS, Antigravity included); a route or a dispatch names its own providers.
583
+ const list = Array.isArray(providers) && providers.length ? PROVIDERS.filter(p => providers.includes(p)) : BRAIN_PROVIDERS;
550
584
  let listed = 0;
551
585
  for (const provider of list) {
552
586
  const models = (catalog[provider] || []).filter(m => typeof accept !== 'function' || accept(provider, m));
553
587
  if (!models.length && !opts.routingEditor) continue;
554
588
  listed += models.length;
555
- const meta = getProviderMeta(provider);
589
+ const meta = providerMeta(provider);
556
590
  const hiddenCount = metadata.hiddenCounts[provider] || 0;
557
591
  const note = opts.routingEditor ? tr('assistant.routes.menu.hiddenCount', '{count} hidden in Models', { count: hiddenCount }) : warnSwitch && provider !== b.provider ? tr('assistant.brain.newConversation', 'starts a new conversation') : '';
558
592
  items.push({ kind: 'header', label: meta.label, icon: meta.icon, color: meta.color, note });
@@ -780,6 +814,8 @@ export function createBrainPicker(host, hooks = {}) {
780
814
  // The composer chip's account cue: the account menu's initial avatar when the provider has more than one
781
815
  // account (or the account is signed out), "Auto" for Auto; null otherwise. Its full words are the tooltip's.
782
816
  function accountCue(brain) {
817
+ // Antigravity signed out: a warning cue on the chip (its words are the tooltip's and the Model menu's).
818
+ if (brain.provider === 'antigravity') return catalogProviderInfo('antigravity')?.signedIn === false ? { text: '!', tone: 'warn' } : null;
783
819
  if (!ACCOUNT_PROVIDERS.has(brain.provider)) return null;
784
820
  if (brain.accountId === 'auto') return { text: t('assistant.brain.accountAutoShort', 'Auto'), tone: 'muted' };
785
821
  const accounts = brain.provider === 'codex' ? getCachedCodexAccounts() : getCachedClaudeAccounts();
@@ -804,7 +840,8 @@ export function createBrainPicker(host, hooks = {}) {
804
840
  effortSupported: brain.provider === 'opencode' || hasEfforts,
805
841
  effort: brain.provider === 'opencode'
806
842
  ? (brain.agent || t('assistant.brain.agentDefault', 'default'))
807
- : (level ? level.label : (brain.effort || t('assistant.brain.effortOff', 'off'))),
843
+ // Antigravity has no "off" level: no level is the model's own default ("Default").
844
+ : (level ? level.label : (brain.effort || (brain.provider === 'antigravity' ? (efforts.find(e => e.id === 'off')?.label || t('assistant.brain.effortOff', 'off')) : t('assistant.brain.effortOff', 'off')))),
808
845
  variantSupported: brain.provider === 'opencode' && hasEfforts,
809
846
  variant: level ? level.label : (brain.effort || t('assistant.brain.variantDefault', 'default variant')),
810
847
  project: brain.project ? projectLabel(brain.project, _projectsCache || []) : t('assistant.brain.projectHome', 'Home'),
@@ -888,6 +925,7 @@ export function createBrainPicker(host, hooks = {}) {
888
925
  const tip = [
889
926
  v.meta.label, model, who ? t('assistant.brain.asAccount', 'as {account}', { account: who }) : '',
890
927
  acc ? accountIdentity(acc) : '', acc ? accountBadge(acc, p).text : '',
928
+ brain.provider === 'antigravity' ? antigravitySignInView(catalogProviderInfo('antigravity'), hooks.t).text : '',
891
929
  ].filter(Boolean).join(' · ');
892
930
  setBtn('model', { label: '', icon: v.meta.icon, color: v.meta.color, tooltip: tip, aria: `${t('assistant.brain.model', 'Model')}: ${tip}` });
893
931
 
@@ -1111,10 +1149,12 @@ export function createBrainPicker(host, hooks = {}) {
1111
1149
  const brain = currentBrain();
1112
1150
  if (field === 'model') {
1113
1151
  // The account rows come from the kept lists; they are read again in the background once loaded.
1114
- if (ACCOUNT_PROVIDERS.has(brain.provider)) {
1152
+ // Antigravity has no SynaBun account: its sign-in state comes with the catalog.
1153
+ if (ACCOUNT_PROVIDERS.has(brain.provider) || brain.provider === 'antigravity') {
1115
1154
  if (_warmProviders.has(brain.provider)) ensureLoaded(brain.provider).catch(() => {});
1116
1155
  else await ensureLoaded(brain.provider);
1117
1156
  }
1157
+ const antigravity = brain.provider === 'antigravity';
1118
1158
  await openModelMenu(anchor, {
1119
1159
  brain,
1120
1160
  shouldOpen,
@@ -1122,8 +1162,8 @@ export function createBrainPicker(host, hooks = {}) {
1122
1162
  placement,
1123
1163
  current: { provider: brain.provider, model: brain.model },
1124
1164
  warnSwitch: true,
1125
- accountItems: ACCOUNT_PROVIDERS.has(brain.provider) ? accountItems(brain) : [],
1126
- accountTitle: accountText('menuTitle', {}, accountProvider(brain)),
1165
+ accountItems: ACCOUNT_PROVIDERS.has(brain.provider) ? accountItems(brain) : antigravity ? antigravitySignInItems() : [],
1166
+ accountTitle: antigravity ? t('assistant.brain.antigravitySignIn', 'Antigravity sign-in') : accountText('menuTitle', {}, accountProvider(brain)),
1127
1167
  onToast: hooks.onToast,
1128
1168
  onManageModels: hooks.onManageModels,
1129
1169
  onSelect: (target) => {
@@ -1298,6 +1338,12 @@ export function createBrainPicker(host, hooks = {}) {
1298
1338
  };
1299
1339
  }
1300
1340
 
1341
+ // Antigravity's place for accounts in the Model menu: its sign-in state, one line (antigravitySignInView).
1342
+ function antigravitySignInItems() {
1343
+ const view = antigravitySignInView(catalogProviderInfo('antigravity'), hooks.t);
1344
+ return [{ kind: 'info', id: '__antigravity_signin', label: view.text, className: `asst-agy-signin asst-agy-signin-${view.state}` }];
1345
+ }
1346
+
1301
1347
  // The composer's Model menu lists the brain's accounts too (Claude or Codex), as the cog's account menu
1302
1348
  // does (asst-account-menu.js): Auto, every account once (a signed-out one opens its sign-in), the Uses
1303
1349
  // switch of an account holding both credentials, Add account… and Manage….
@@ -1369,6 +1415,7 @@ export function createBrainPicker(host, hooks = {}) {
1369
1415
  refresh,
1370
1416
  snapshot,
1371
1417
  openField,
1418
+ switchProvider,
1372
1419
  fieldSummary,
1373
1420
  modelLabel: () => modelLabel(),
1374
1421
  mountEmptyProject,
@@ -228,10 +228,11 @@ export function renderBudgetSession(view, { t } = {}) {
228
228
  ? tr('assistant.budget.providerSpend', '{spent} spent', { spent: money(row.spentUsd) })
229
229
  : tr('assistant.budget.providerAmounts', 'Share {share} · spent {spent} · left for tasks {left}', { share: row.shareUsd == null ? '—' : money(row.shareUsd), spent: money(row.spentUsd), left: row.workerLeftUsd == null ? '—' : money(row.workerLeftUsd) })));
230
230
  if (!view.unlimited && row.isBrain && view.split) item.append(el('div', 'asst-modal-note', tr('assistant.budget.reserve', '{reserve} reserved for the brain within this share.', { reserve: money(view.split.brainReserveLeftUsd) })));
231
- if (!view.unlimited && (view.dry || []).includes(row.provider)) item.append(el('div', 'asst-budget-dry', tr('assistant.budget.dry', 'Share used up')));
232
- if (!row.eligible && row.reason) {
233
- const reason = String(row.reason).replace(/^brain:/, '');
234
- item.append(el('div', 'asst-modal-note', tr(`assistant.budget.ineligible.${reason}`, { no_models: 'No available models', signed_out: 'Signed out', limited: 'Provider usage limit reached' }[reason] || reason)));
231
+ const reason = !row.eligible && row.reason ? String(row.reason).replace(/^brain:/, '') : '';
232
+ // A provider that reports no prices (Antigravity) gets no share under a budget: nothing of it was "used up".
233
+ if (!view.unlimited && (view.dry || []).includes(row.provider) && reason !== 'unpriced') item.append(el('div', 'asst-budget-dry', tr('assistant.budget.dry', 'Share used up')));
234
+ if (reason) {
235
+ item.append(el('div', 'asst-modal-note', tr(`assistant.budget.ineligible.${reason}`, { no_models: 'No available models', signed_out: 'Signed out', limited: 'Provider usage limit reached', unpriced: 'Reports tokens, not prices' }[reason] || reason)));
235
236
  }
236
237
  providers.append(item);
237
238
  }
@@ -290,6 +290,14 @@ export function requestDesktopStop(body = { scope: 'all' }) {
290
290
  const ICON_COMPUTER = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="12" rx="2"/><path d="M8 20h8M12 16v4"/></svg>';
291
291
  const ICON_X = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>';
292
292
  const POLL_MS = 3000;
293
+
294
+ /** What a failed status check was: { kind: 'forbidden' | 'timeout' | 'error', message }. */
295
+ export function computerCheckFailure(err) {
296
+ const message = str(err?.message) || 'The status check failed';
297
+ if (err?.status === 401 || err?.status === 403 || err?.forbidden) return { kind: 'forbidden', message };
298
+ if (err?.timedOut || err?.code === 'TIMEOUT' || err?.name === 'AbortError') return { kind: 'timeout', message };
299
+ return { kind: 'error', message };
300
+ }
293
301
  const THUMB_MIN_INTERVAL_MS = 500; // ≤ 2 live-thumbnail updates per second
294
302
 
295
303
  function el(tag, className, text) {
@@ -411,6 +419,7 @@ export function createComputerPanel(hostEl, hooks = {}) {
411
419
  thumbTimer: null,
412
420
  thumbPending: null,
413
421
  view: '',
422
+ checkFailed: null,
414
423
  };
415
424
 
416
425
  function isEnabled() { try { return !!hooks.isEnabled?.(); } catch { return false; } }
@@ -575,7 +584,17 @@ export function createComputerPanel(hostEl, hooks = {}) {
575
584
  const note = (text) => body.appendChild(el('div', 'asst-computer-tray-note', text));
576
585
  const spinner = () => head.insertBefore(el('span', 'asst-spinner'), head.firstChild);
577
586
 
578
- switch (info.state) {
587
+ const viewState = info.state === 'loading' && state.checkFailed ? 'check_failed' : info.state;
588
+ switch (viewState) {
589
+ case 'check_failed':
590
+ title.textContent = t('assistant.computer.checkFailed', 'Could not check computer use');
591
+ note(state.checkFailed.kind === 'forbidden'
592
+ ? t('assistant.computer.checkFailedForbidden', 'This window is not allowed to read computer use.')
593
+ : state.checkFailed.kind === 'timeout'
594
+ ? t('assistant.computer.checkFailedTimeout', 'The check did not answer in time.')
595
+ : state.checkFailed.message);
596
+ button(t('assistant.computer.checkAgain', 'Check again'), 'secondary', () => { state.checkFailed = null; render(); refresh().catch(() => {}); }, 'recheck');
597
+ break;
579
598
  case 'loading':
580
599
  spinner();
581
600
  title.textContent = t('assistant.computer.checking', 'Checking computer use…');
@@ -670,6 +689,7 @@ export function createComputerPanel(hostEl, hooks = {}) {
670
689
 
671
690
  function applyStatus(status) {
672
691
  if (!status || typeof status !== 'object') return;
692
+ state.checkFailed = null;
673
693
  state.status = status;
674
694
  state.info = computerSetupState(status);
675
695
  if (status.control && typeof status.control === 'object') {
@@ -688,7 +708,9 @@ export function createComputerPanel(hostEl, hooks = {}) {
688
708
  if (!state.destroyed) applyStatus(status);
689
709
  return status;
690
710
  } catch (err) {
691
- if (!state.destroyed) { state.error = ''; render(); }
711
+ // A check that failed is a settled state (Check again), never "Checking…" forever. A status already known
712
+ // stays on screen; only a tray that never got one shows the failure.
713
+ if (!state.destroyed) { state.error = ''; state.checkFailed = computerCheckFailure(err); render(); }
692
714
  throw err;
693
715
  }
694
716
  }
@@ -735,6 +757,8 @@ export function createComputerPanel(hostEl, hooks = {}) {
735
757
  stop,
736
758
  isActive: desktopActive,
737
759
  status: () => state.status,
760
+ /** A status check made elsewhere failed: settle the tray instead of leaving it on "Checking…". */
761
+ checkFailed(err) { if (state.destroyed || state.status) return; state.checkFailed = computerCheckFailure(err); render(); },
738
762
  info: () => state.info,
739
763
  destroy() {
740
764
  state.destroyed = true;
@@ -10,6 +10,8 @@
10
10
  // Claude → cp/cp-permissions.js { behavior, always, updatedInput, updatedPermissions, message, planDecision }
11
11
  // Codex → cdx/cdx-requests.js result:{ decision } | result:{ permissions, scope } | result:{ answers } | result:{ action, content }
12
12
  // OpenCode → ocp-v2/ocp-v2-render.js reply:'once'|'always'|'reject' | answers:[[label]]
13
+ // Antigravity (a dispatched worker's ACP session/request_permission, relayed by the broker as
14
+ // { toolName, input: <the ACP toolCall> }) → { behavior } once or deny, never "always"
13
15
  // Every response additionally carries `behavior`, `provider` and `kind` so the
14
16
  // server-side envelope translator can route without re-deriving them; Codex
15
17
  // replies repeat `answers` / `content` and OpenCode replies repeat `always` at
@@ -192,6 +194,9 @@ export function describeToolInput(toolName, input = {}) {
192
194
  function inferProvider(req, packet) {
193
195
  const explicit = normalizeProvider(packet?.provider ?? req?.provider);
194
196
  if (explicit) return explicit;
197
+ // An ACP permission request (Antigravity): a tool call with its id, options of allow_* / reject_* kinds.
198
+ if (req?.brain_native?.toolCall?.toolCallId || req?.toolCall?.toolCallId) return 'antigravity';
199
+ if (Array.isArray(req?.options) && req.options.some(o => typeof o?.optionId === 'string' && /^(allow|reject)_(once|always)$/.test(String(o?.kind || '')))) return 'antigravity';
195
200
  if (typeof req?.method === 'string' && req.method.includes('/')) return 'codex';
196
201
  if (typeof req?.brain_native?.method === 'string' && req.brain_native.method.includes('/')) return 'codex';
197
202
  if (req?.subtype === 'can_use_tool' || req?.subtype === 'permission_request' || req?.tool_name) return 'claude-code';
@@ -296,9 +301,49 @@ export function normalizeControlRequest(packet, { origin = null } = {}) {
296
301
  if (firstStr(req.tool_name, req.toolName) === 'ExitPlanMode') return normalizeClaude(req, base);
297
302
  if (provider === 'codex') return normalizeCodex(req, base);
298
303
  if (provider === 'opencode') return normalizeOpenCode(req, base);
304
+ if (provider === 'antigravity') return normalizeAntigravity(req, base);
299
305
  return normalizeClaude(req, base);
300
306
  }
301
307
 
308
+ // The path-like fields of an Antigravity tool call's arguments (lib/worker-confinement.js reads the same ones).
309
+ const ACP_PATH_KEYS = ['path', 'file_path', 'filePath', 'Path', 'AbsolutePath', 'TargetFile', 'DirectoryPath', 'SearchPath'];
310
+
311
+ /**
312
+ * An Antigravity worker's permission request: the broker relays the ACP tool call
313
+ * ({ toolCallId, title, kind, status, locations, rawInput }) as `input`. The card shows
314
+ * the call's own arguments, and its command (execute) or the paths it touches as the detail.
315
+ */
316
+ function normalizeAntigravity(req, base) {
317
+ // A worker's relay sends the ACP tool call as `input`; the brain sends the call's arguments as `input` and the
318
+ // call itself as `brain_native.toolCall`. Either way: the call, then its arguments.
319
+ const given = obj(req.input) || {};
320
+ const looksLikeCall = given.rawInput !== undefined || given.toolCallId !== undefined || Array.isArray(given.locations);
321
+ const call = obj(req.brain_native?.toolCall) || obj(req.toolCall) || (looksLikeCall ? given : {});
322
+ const args = obj(call.rawInput) || (looksLikeCall ? {} : given);
323
+ const kind = str(call.kind) || 'other';
324
+ // An MCP call names its server and tool in its arguments ({ ServerName, ToolName, Arguments }).
325
+ const mcpServer = firstStr(args.ServerName, args.serverName);
326
+ const mcpTool = firstStr(args.ToolName, args.toolName);
327
+ const mcpArgs = obj(args.Arguments) || obj(args.arguments);
328
+ const command = Array.isArray(args.command) ? args.command.map(str).join(' ') : firstStr(args.command, args.CommandLine, args.cmd);
329
+ const paths = [...new Set([
330
+ ...(Array.isArray(call.locations) ? call.locations : []).map(location => str(location?.path)),
331
+ ...ACP_PATH_KEYS.map(key => (typeof args[key] === 'string' ? str(args[key]) : '')),
332
+ ].filter(Boolean))];
333
+ let mcpDetail = '';
334
+ if (mcpServer && mcpTool && mcpArgs) { try { mcpDetail = JSON.stringify(mcpArgs).slice(0, 400); } catch { mcpDetail = ''; } }
335
+ const detail = mcpServer && mcpTool ? (mcpDetail || `${mcpServer} · ${mcpTool}`)
336
+ : kind === 'execute' && command ? command : paths.length ? paths.join('\n') : str(call.title);
337
+ return {
338
+ ...base,
339
+ kind: CONTROL_KINDS.PERMISSION,
340
+ toolName: (mcpServer && mcpTool ? `${mcpServer}_${mcpTool}` : '') || firstStr(req.toolName, req.tool_name, call.title, kind) || 'tool',
341
+ title: 'Permission required',
342
+ detail,
343
+ input: mcpServer && mcpTool && mcpArgs ? mcpArgs : args,
344
+ };
345
+ }
346
+
302
347
  function normalizeClaude(req, base) {
303
348
  const toolName = firstStr(req.tool_name, req.toolName) || 'Unknown';
304
349
  const input = obj(req.input) || {};
@@ -543,6 +588,8 @@ export function buildControlResponse(normalized, decision = {}) {
543
588
  if (normalized.kind === CONTROL_KINDS.PLAN) return buildClaude(normalized, decision, base, allow, always);
544
589
  if (normalized.provider === 'codex') return buildCodex(normalized, decision, base, allow, always);
545
590
  if (normalized.provider === 'opencode') return buildOpenCode(normalized, decision, base, allow, always);
591
+ // Antigravity: allowed once or denied; an "always" is never sent (see controlActions).
592
+ if (normalized.provider === 'antigravity') return buildClaude(normalized, decision, base, allow, false);
546
593
  return buildClaude(normalized, decision, base, allow, always);
547
594
  }
548
595
 
@@ -651,6 +698,7 @@ export function controlActions(normalized) {
651
698
  ];
652
699
  }
653
700
  const actions = [];
701
+ // Never for Antigravity: its "always" option is saved by Antigravity for later sessions too, beyond this run.
654
702
  const canAlways = n.provider === 'claude-code'
655
703
  || n.provider === 'opencode'
656
704
  || (n.provider === 'codex' && (n.method === 'item/permissions/requestApproval'