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
@@ -83,7 +83,8 @@
83
83
  ],
84
84
  "skillMd": [
85
85
  "78ebb9f149ff",
86
- "a28ce5188f4b"
86
+ "870138a74151",
87
+ "bb2df5d23b7d"
87
88
  ],
88
89
  "firstCommit": "ae9acec31b71",
89
90
  "firstDate": "2026-10-23"
@@ -96,7 +97,8 @@
96
97
  ],
97
98
  "skillMd": [
98
99
  "78ebb9f149ff",
99
- "a28ce5188f4b"
100
+ "870138a74151",
101
+ "bb2df5d23b7d"
100
102
  ],
101
103
  "firstCommit": "ae9acec31b71",
102
104
  "firstDate": "2026-10-23"
@@ -147,6 +149,7 @@
147
149
  "g3"
148
150
  ],
149
151
  "skillMd": [
152
+ "68a8323dfd0d",
150
153
  "8d262753dd16",
151
154
  "a9ac8e43510e",
152
155
  "ea38ed171e73",
@@ -163,6 +166,7 @@
163
166
  "g3"
164
167
  ],
165
168
  "skillMd": [
169
+ "68a8323dfd0d",
166
170
  "8d262753dd16",
167
171
  "a9ac8e43510e",
168
172
  "ea38ed171e73",
@@ -182,7 +186,8 @@
182
186
  "1019fc5ef755",
183
187
  "2a5ae45388b6",
184
188
  "740fdb3985fd",
185
- "b5380300a1f5"
189
+ "b5380300a1f5",
190
+ "f6a41a558efe"
186
191
  ],
187
192
  "firstCommit": "636f18afbe6b",
188
193
  "firstDate": "2026-07-21"
@@ -198,7 +203,8 @@
198
203
  "1019fc5ef755",
199
204
  "2a5ae45388b6",
200
205
  "740fdb3985fd",
201
- "b5380300a1f5"
206
+ "b5380300a1f5",
207
+ "f6a41a558efe"
202
208
  ],
203
209
  "firstCommit": "636f18afbe6b",
204
210
  "firstDate": "2026-07-21"
@@ -246,6 +252,30 @@
246
252
  "firstCommit": "ae9acec31b71",
247
253
  "firstDate": "2026-10-23"
248
254
  },
255
+ {
256
+ "digest": "b052a36caf1bbbebe994c5b8c029696306c64824837ae632968a94f0d97a8888",
257
+ "sep": "any",
258
+ "generators": [
259
+ "g3"
260
+ ],
261
+ "skillMd": [
262
+ "870138a74151"
263
+ ],
264
+ "firstCommit": "0a8f77317114",
265
+ "firstDate": "2026-10-25"
266
+ },
267
+ {
268
+ "digest": "7da57892ab385c2668a1afdb2da670ddcc732927103812c46a635eb476d34781",
269
+ "sep": "any",
270
+ "generators": [
271
+ "g3"
272
+ ],
273
+ "skillMd": [
274
+ "bb2df5d23b7d"
275
+ ],
276
+ "firstCommit": "52d9d1712c34",
277
+ "firstDate": "2026-10-31"
278
+ },
249
279
  {
250
280
  "digest": "4b619a432fc38e95310399f2d59a0f1501a3984b67007febd8ff92634afffa8f",
251
281
  "sep": "any",
@@ -380,6 +410,30 @@
380
410
  ],
381
411
  "firstCommit": "6776e42b885c",
382
412
  "firstDate": "2026-10-23"
413
+ },
414
+ {
415
+ "digest": "6765902e8854b8d4a859e96aa9233ba61cc0fe6c33d197b9815c01668a92612c",
416
+ "sep": "any",
417
+ "generators": [
418
+ "g3"
419
+ ],
420
+ "skillMd": [
421
+ "68a8323dfd0d"
422
+ ],
423
+ "firstCommit": "2be15e00f29b",
424
+ "firstDate": "2026-11-02"
425
+ },
426
+ {
427
+ "digest": "9f95ee55bf80bd711b191e1e94c74bd2808bcf82291b02bb20f52cab91f14c3b",
428
+ "sep": "any",
429
+ "generators": [
430
+ "g3"
431
+ ],
432
+ "skillMd": [
433
+ "f6a41a558efe"
434
+ ],
435
+ "firstCommit": "20ad54882a26",
436
+ "firstDate": "2026-11-03"
383
437
  }
384
438
  ]
385
439
  }
@@ -272,7 +272,7 @@ export function createSkillInstaller({ dataDir, now = () => new Date().toISOStri
272
272
  /**
273
273
  * Install `payload` at `destination` (a plain name in `root`).
274
274
  * @param {object} o
275
- * @param {'claude'|'codex'|'opencode'} o.target @param {string} o.name @param {string} o.root @param {string} o.destination
275
+ * @param {'claude'|'codex'|'opencode'|'antigravity'|'agents'} o.target @param {string} o.name @param {string} o.root @param {string} o.destination
276
276
  * @param {object} o.payload from payloadFromFolder / payloadFromText
277
277
  * @param {object} [o.source] where the payload came from ({ kind: 'bundled', name, digest })
278
278
  * @param {{ expectedDestinationDigest?: string }} [o.decision] the person confirmed replacing what is there now
@@ -23,6 +23,8 @@
23
23
  // <config>/opencode skill folders stay listed as read by no runtime.
24
24
  // OpenCode also reads ~/.claude/skills and both .agents roots (not in SynaBun's own OpenCode panel,
25
25
  // which turns the Claude ones off).
26
+ // Antigravity ~/.gemini/config/skills (Google Antigravity's `agy`; where SynaBun installs) and a project's
27
+ // .agents/skills (the Codex project folder, which Antigravity reads too). Not fixture-tested.
26
28
  // Shared ~/.agents/skills: Codex and OpenCode (and other agents) read it. SynaBun writes there only when
27
29
  // the owner turned that on (`sharedAgentsRoot`).
28
30
  // SynaBun PACKAGE_ROOT/skills, the bundled skills: read-only.
@@ -34,7 +36,7 @@ import { lstatSync, readdirSync, realpathSync } from 'node:fs';
34
36
  import { homedir } from 'node:os';
35
37
  import { join, resolve } from 'node:path';
36
38
 
37
- export const RUNTIMES = Object.freeze(['claude', 'codex', 'opencode']);
39
+ export const RUNTIMES = Object.freeze(['claude', 'codex', 'opencode', 'antigravity']);
38
40
 
39
41
  const realFs = { lstat: lstatSync, realpath: realpathSync, readdir: readdirSync };
40
42
 
@@ -186,6 +188,11 @@ export function createRootResolver({
186
188
  add({ family: 'opencode', kind: 'commands', scope: 'user', sourceKind: 'legacy-bridge', path: join(home, '.opencode', 'command'), runtimes: ['opencode'],
187
189
  policy: { write: 'migration', reason: 'An old place SynaBun wrote bridges to.' }, discovery: { contract: '~/.opencode/command/<name>.md', verified: 'unverified' } });
188
190
 
191
+ // Antigravity (Google's `agy`): its own skills folder under ~/.gemini/config. It needs no refresh: it reads the folder again by itself.
192
+ add({ family: 'antigravity', kind: 'skills', scope: 'user', sourceKind: 'native', path: join(home, '.gemini', 'config', 'skills'), runtimes: ['antigravity'],
193
+ policy: owner('Antigravity\'s own skills folder.'), discovery: { contract: '~/.gemini/config/skills/<name>/SKILL.md', verified: 'docs' },
194
+ installTarget: { runtime: 'antigravity', scope: 'user' } });
195
+
189
196
  // Shared.
190
197
  add({ family: 'shared', kind: 'skills', scope: 'shared', sourceKind: 'shared', path: join(home, '.agents', 'skills'), runtimes: ['codex', 'opencode'],
191
198
  policy: sharedOn ? owner('Shared by Codex, OpenCode and other agents; turned on by the owner.') : { write: 'owner-opt-in', reason: 'Shared by Codex, OpenCode and other agents: SynaBun writes here only when you turn it on.' },
@@ -206,8 +213,8 @@ export function createRootResolver({
206
213
  policy: owner('The project\'s commands.'), discovery: { contract: '.claude/commands/<ns>/<name>.md → /ns:name', verified: 'docs' } });
207
214
  add({ family: 'claude', kind: 'agents', scope: 'project', sourceKind: 'native', path: at('.claude', 'agents'), runtimes: ['claude'], ...p,
208
215
  policy: owner('The project\'s agents.'), discovery: { contract: '.claude/agents/<name>.md', verified: 'docs' } });
209
- add({ family: 'codex', kind: 'skills', scope: 'project', sourceKind: 'native', path: at('.agents', 'skills'), runtimes: ['codex', 'opencode'], ...p,
210
- policy: owner('The project\'s shared skills (Codex\'s repository folder; OpenCode reads it too).'), discovery: { contract: '.agents/skills/<name>/SKILL.md', verified: 'fixture codex-cli 0.163.0, opencode 2.0.32' },
216
+ add({ family: 'codex', kind: 'skills', scope: 'project', sourceKind: 'native', path: at('.agents', 'skills'), runtimes: ['codex', 'opencode', 'antigravity'], ...p,
217
+ policy: owner('The project\'s shared skills (Codex\'s repository folder; OpenCode and Antigravity read it too).'), discovery: { contract: '.agents/skills/<name>/SKILL.md', verified: 'fixture codex-cli 0.163.0, opencode 2.0.32; Antigravity: docs' },
211
218
  installTarget: { runtime: 'codex', scope: 'project' } });
212
219
  add({ family: 'codex', kind: 'skills', scope: 'project', sourceKind: 'compat', path: at('.codex', 'skills'), runtimes: ['codex'], ...p,
213
220
  policy: never('Read by Codex 0.163.0 but not in its guide: new skills go to .agents/skills.'), discovery: { contract: '.codex/skills/<name>/SKILL.md', verified: 'fixture codex-cli 0.163.0' } });
@@ -6,11 +6,11 @@
6
6
  // "off", a deny rule on Skill(name)), OpenCode's opencode.json and opencode.jsonc (the last rule of
7
7
  // action `skill` whose resource matches the name: OpenCode 2's `permissions`, an OpenCode 1
8
8
  // `permission.skill` read as OpenCode reads it). 'enabled', 'disabled', or 'unknown' when the
9
- // settings cannot be read.
9
+ // settings cannot be read (Antigravity's are not read: always 'unknown', with no reason).
10
10
  // probe asked of the runtime itself, only when the owner asks (Verify in the Studio): Codex lists its
11
11
  // skills through `codex app-server` (skills/list, forceReload); OpenCode through SynaBun's own
12
- // running serve. Claude Code has no listing to ask: it says null with the reason. A probe reads; it
13
- // changes no setting.
12
+ // running serve. Claude Code and Antigravity have no listing to ask: they say null with the reason.
13
+ // A probe reads; it changes no setting.
14
14
  // enable the one write here, on the owner's click only (POST /api/skills-studio/v2/eligibility/enable):
15
15
  // the setting that turns the skill off is changed to let it through, the file is backed up first
16
16
  // (DATA_HOME/data/skills/backups/settings/), and the answer names the file, the backup and the lines
@@ -160,6 +160,8 @@ export function createSkillsRuntimeChecks({ codexHome, claudeHome, opencodeConfi
160
160
  if (decided?.effect !== 'deny') return { state: 'enabled', reason: null };
161
161
  return { state: 'disabled', reason: { kind: 'opencode-permission', file: decided.file, pattern: decided.resource, action: 'deny' } };
162
162
  }
163
+ // Antigravity: SynaBun reads none of its settings, so nothing is said of them (no reason: no "could not be read").
164
+ if (runtime === 'antigravity') return { state: 'unknown', reason: null };
163
165
  return { state: 'unknown', reason: { kind: 'unknown-runtime' } };
164
166
  }
165
167
 
@@ -273,6 +275,7 @@ export function createSkillsRuntimeChecks({ codexHome, claudeHome, opencodeConfi
273
275
  } catch (err) { return { discovered: null, code: 'no-answer', method: 'opencode serve', reason: err?.message || String(err) }; }
274
276
  }
275
277
  if (runtime === 'claude') return { discovered: null, code: 'no-list', method: 'none', reason: 'Claude Code has no list to ask; its folder rules were checked instead.' };
278
+ if (runtime === 'antigravity') return { discovered: null, code: 'no-list', method: 'none', reason: 'Antigravity has no list SynaBun can ask; its folder rules were checked instead.' };
276
279
  return { discovered: null, method: 'none', reason: 'Unknown runtime.' };
277
280
  }
278
281
 
@@ -50,7 +50,7 @@ export const SERVICE_CAPABILITIES = Object.freeze([
50
50
  'evals', 'usage', 'health', 'discover', 'packages', 'external-scanner-status',
51
51
  ]);
52
52
 
53
- export const ASSISTANT_RUNTIMES = Object.freeze(['claude', 'codex', 'opencode']);
53
+ export const ASSISTANT_RUNTIMES = Object.freeze(['claude', 'codex', 'opencode', 'antigravity']);
54
54
 
55
55
  export class ServiceError extends PathConfineError {
56
56
  constructor(status, message, code, extra = {}) {
@@ -68,7 +68,7 @@ export class ServiceError extends PathConfineError {
68
68
  * @param {string|null} [o.bundledDir] PACKAGE_ROOT/skills
69
69
  * @param {string} [o.appVersion]
70
70
  * @param {(event) => void} [o.broadcast]
71
- * @param {() => ({ claude?: boolean, codex?: boolean, opencode?: boolean }|null)} [o.installedRuntimes] which CLIs are
71
+ * @param {() => ({ claude?: boolean, codex?: boolean, opencode?: boolean, antigravity?: boolean }|null)} [o.installedRuntimes] which CLIs are
72
72
  * installed on this computer (server.js: the CLI resolver); null when not known
73
73
  * @param {object} [o.runtimeChecks] lib/skills/runtime-checks.js (the owner's enable)
74
74
  */
@@ -116,11 +116,13 @@ export function createSkillsService({ dataDir, roots, catalog, library, deployer
116
116
  const used = usedRuntimes();
117
117
  const detected = ASSISTANT_RUNTIMES.filter((r) => (installed ? installed[r] !== false : true) && used[r]);
118
118
  const fallback = detected.length ? detected : ['claude'];
119
- return { chosen: config.assistants, detected: fallback, effective: config.assistants && config.assistants.length ? config.assistants : fallback, installed: installed ? ASSISTANT_RUNTIMES.filter((r) => installed[r] === true) : null };
119
+ // `installed` leaves out only a runtime the host says is missing (false): one it says nothing of (a host from before
120
+ // Antigravity) is never called "not installed".
121
+ return { chosen: config.assistants, detected: fallback, effective: config.assistants && config.assistants.length ? config.assistants : fallback, installed: installed ? ASSISTANT_RUNTIMES.filter((r) => installed[r] !== false) : null };
120
122
  }
121
123
  /** Which runtimes have a home on this computer (their skills folder or its parent exists), from the roots. */
122
124
  function usedRuntimes() {
123
- const out = { claude: false, codex: false, opencode: false };
125
+ const out = { claude: false, codex: false, opencode: false, antigravity: false };
124
126
  let list = [];
125
127
  try { list = roots.listRoots(); } catch { list = []; }
126
128
  for (const r of list) {
@@ -181,7 +183,7 @@ export function createSkillsService({ dataDir, roots, catalog, library, deployer
181
183
  const binaryHead = head ? !isEditableText(head.bytes) : false;
182
184
  const base = lintText({ type: 'skill', text, folderName, binary: binaryHead }).lint;
183
185
  const sidecar = readSidecar(files);
184
- const ids = Array.isArray(profiles) && profiles.length ? profiles.filter((p) => getProfile(p)) : ['portable-six-field', 'claude-code', 'codex-native', 'opencode-native'];
186
+ const ids = Array.isArray(profiles) && profiles.length ? profiles.filter((p) => getProfile(p)) : ['portable-six-field', 'claude-code', 'codex-native', 'opencode-native', 'antigravity-native'];
185
187
  const byProfile = {};
186
188
  for (const id of ids) {
187
189
  const lint = lintForProfile(id, { parsed, folderName, files: files.map((f) => ({ path: f.path, size: f.bytes.length })), sidecar });
@@ -281,6 +283,11 @@ export function createSkillsService({ dataDir, roots, catalog, library, deployer
281
283
  const entry = await catalog.find(artifactId);
282
284
  if (!entry) throw new ServiceError(404, 'There is no such entry in the catalog.', 'artifact_not_found');
283
285
  if (entry.kind !== 'skill') throw new ServiceError(400, 'Only a skill folder becomes a library skill.', 'not_a_skill');
286
+ // A folder SynaBun installed a library skill into is that skill already: adopting it again would make a second
287
+ // library skill of the same bytes (two Library rows, each flagged "same description" against the other).
288
+ const installed = deployer.activeAt(entry.path) || (entry.realPath && entry.realPath !== entry.path ? deployer.activeAt(entry.realPath) : null);
289
+ const owner = installed ? library.readSkill(installed.skillId) : null;
290
+ if (owner) return { skill: skillSummary(library.getSkill(owner.skillId)), revision: null, created: false, unchanged: true, installed: true, excluded: [] };
284
291
  const { files, dirs, excluded } = collectFolder(entry.realPath);
285
292
  const existing = library.findSkill((s) => s.origin?.kind === 'adopted' && s.origin?.artifactId === artifactId);
286
293
  const source = { kind: 'adopted', path: entry.path, rootId: entry.rootId, artifactId, name: entry.folderName };
@@ -475,7 +482,7 @@ export function createSkillsService({ dataDir, roots, catalog, library, deployer
475
482
  * backup, and the answer says what changed (lib/skills/runtime-checks.js enable).
476
483
  */
477
484
  function enableEligibility({ runtime, name, skillMd, accountId = 'default', projectPath = null, confirmDenyAll = false }) {
478
- if (!['claude', 'codex', 'opencode'].includes(runtime)) throw new ServiceError(400, 'runtime is claude, codex or opencode.', 'invalid_target');
485
+ if (!['claude', 'codex', 'opencode', 'antigravity'].includes(runtime)) throw new ServiceError(400, 'runtime is claude, codex, opencode or antigravity.', 'invalid_target');
479
486
  if (typeof name !== 'string' || !isPlainName(name)) throw new ServiceError(400, 'name is a skill\'s folder name.', 'invalid_name');
480
487
  if (typeof skillMd !== 'string' || !skillMd || !/[\\/]SKILL\.md$/.test(skillMd)) throw new ServiceError(400, 'skillMd is the path of the skill\'s SKILL.md.', 'invalid_path');
481
488
  // Only a skill at a place SynaBun installs to (one of its install roots, or a folder a runtime reads): never an
@@ -492,7 +499,8 @@ export function createSkillsService({ dataDir, roots, catalog, library, deployer
492
499
  /** The legacy chips' view of one bundled skill: what each runtime has of it (native folder, old bridge, neither). */
493
500
  function legacyTargetStates(name) {
494
501
  const out = {};
495
- for (const runtime of ['claude', 'codex', 'opencode']) {
502
+ // Antigravity installs to ~/.gemini/config/skills (lib/skills/roots.js), like the others to their own folders.
503
+ for (const runtime of ['claude', 'codex', 'opencode', 'antigravity']) {
496
504
  const root = roots.installRoot({ runtime, scope: 'user', accountId: 'default' });
497
505
  const native = root ? deployer.activeAt(join(root.path, name)) : null;
498
506
  let nativeState = 'absent';
@@ -9,6 +9,7 @@ export const USAGE_COVERAGE = Object.freeze({
9
9
  claude: { native: true, method: 'Skill tool request + successful tool result', limit: 'SynaBun dispatched runs only. Discovery is not invocation; task outcome is not skill quality.' },
10
10
  codex: { native: false, method: 'Successful exact cat of an installed SKILL.md', limit: 'No native invocation event. File reads are inferred loads, never invocation counts. Complex shell commands are not classified.' },
11
11
  opencode: { native: true, method: 'Completed skill tool part', limit: 'SynaBun dispatched runs only. Other terminals and historical transcripts are not mined.' },
12
+ antigravity: { native: false, method: 'Successful read of an installed SKILL.md', limit: 'No native invocation event: Antigravity reads a skill\'s SKILL.md itself. File reads are inferred loads, never invocation counts. SynaBun dispatched runs only.' },
12
13
  });
13
14
  const hash = (s) => createHash('sha256').update(s).digest('hex');
14
15
  // Native item IDs can restart on a follow-up turn; the dispatcher retains this turn across restarts.
@@ -65,6 +66,11 @@ export function createSkillTelemetry({ dataDir, deployer, settings, now = Date.n
65
66
  } else if (c.ok === true) add(run, i, 'invoked', 'native_skill_tool', c.id);
66
67
  else if (c.ok === false) add(run, i, 'failed', 'native_skill_tool', c.id);
67
68
  }
69
+ // Antigravity reads an installed skill's SKILL.md with its own file tool: a completed read of that exact file.
70
+ if (run.provider === 'antigravity' && (c.tool === 'read_file' || c.tool === 'view_file') && c.ok === true && typeof c.input.path === 'string') {
71
+ const matches = installs.filter((i) => resolve(c.input.path) === join(i.destination.path, 'SKILL.md'));
72
+ if (matches.length === 1) add(run, matches[0], 'loaded', 'successful_skill_file_read', c.id, 'inferred');
73
+ }
68
74
  if (run.provider === 'codex' && c.tool === 'shell' && c.phase === 'done' && c.ok === true) {
69
75
  // A conservative grammar: no pipes, substitutions, multiple commands, or merely mentioning a path.
70
76
  let command = c.input.command || '';
@@ -121,7 +121,7 @@ export function semverNewer(a, b) {
121
121
  // versions of the same scheme compare by compareSemver.
122
122
  //
123
123
  // Only SynaBun's own versions go through these functions. The CLI tools
124
- // SynaBun watches (Claude Code, Codex, Gemini, OpenCode) keep plain
124
+ // SynaBun watches (Claude Code, Codex, Antigravity, OpenCode) keep plain
125
125
  // compareSemver / semverNewer.
126
126
 
127
127
  /** True for a date-style SynaBun version (`2026.9.5`, `v.2026.07.34`, `2026.4.26-1`). */
@@ -14,7 +14,7 @@
14
14
 
15
15
  /** The kinds an answer may name, in the order the interface lists them. */
16
16
  export const ACTIVITY_KINDS = Object.freeze([
17
- 'assistant', 'claude-panel', 'codex-panel', 'opencode-panel', 'claude-cli', 'loop', 'schedule', 'backup', 'maintenance', 'unknown',
17
+ 'assistant', 'claude-panel', 'codex-panel', 'opencode-panel', 'antigravity-panel', 'claude-cli', 'loop', 'schedule', 'backup', 'maintenance', 'unknown',
18
18
  ]);
19
19
 
20
20
  /** A Claude Code session that took a prompt this recently may still be working on it. */
@@ -12,9 +12,13 @@
12
12
 
13
13
  import { findModel, hiddenModelId } from '../assistant-catalog.js';
14
14
  import { modelEfforts } from '../effort-levels.js';
15
- import { providerLabel, remoteBrainLimited } from '../remote-policy.js';
15
+ import { providerLabel as remoteProviderLabel, remoteBrainLimited } from '../remote-policy.js';
16
16
 
17
- export const BRAIN_PROVIDERS = Object.freeze(['claude-code', 'codex', 'opencode']);
17
+ // Every provider the Assistant can run on: Antigravity too (lib/assistant-brains/antigravity.js). Like Codex and
18
+ // OpenCode it holds a WhatsApp conversation read-only (lib/remote-policy.js remoteBrainLimited).
19
+ export const BRAIN_PROVIDERS = Object.freeze(['claude-code', 'codex', 'opencode', 'antigravity']);
20
+ // The provider's name in what the owner reads (remote-policy.js names the first three).
21
+ const providerLabel = (provider) => (provider === 'antigravity' ? 'Antigravity' : remoteProviderLabel(provider));
18
22
  const EFFORT_RE = /^[A-Za-z0-9][\w.-]{0,39}$/;
19
23
  const MODEL_MAX = 200;
20
24
 
@@ -55,7 +59,7 @@ function refusal(message, { code = 'CONFIG_INVALID', status = 400 } = {}) {
55
59
  */
56
60
  export function checkBrainChoice(choice, catalog) {
57
61
  const clean = cleanBrainChoice(choice);
58
- if (clean === undefined) return refusal('brain must be null (same as the Assistant) or { provider, model, effort } with a provider of claude-code, codex or opencode');
62
+ if (clean === undefined) return refusal('brain must be null (same as the Assistant) or { provider, model, effort } with a provider of claude-code, codex, opencode or antigravity');
59
63
  if (clean === null) return { ok: true, brain: null };
60
64
  if (!catalog || typeof catalog !== 'object') {
61
65
  return refusal('The model list is not available right now, so this model could not be checked. Try again in a few seconds.', { code: 'CATALOG_UNAVAILABLE', status: 503 });
@@ -49,7 +49,7 @@
49
49
 
50
50
  import { CONTROL_KINDS, buildControlResponse, describeToolInput, normalizeAnswers, normalizeControlRequest } from '../../public/shared/assistant/asst-control.js';
51
51
  import { buildRouteResponse, normalizeRouteRequest, routeOptions } from '../../public/shared/assistant/asst-route.js';
52
- import { isComputerTool, isDeniedRemoteTool } from '../remote-policy.js';
52
+ import { isComputerTool, isDeniedRemoteTool, providerLabel } from '../remote-policy.js';
53
53
 
54
54
  // Codes a person can read out loud: no 0/O, 1/I/L, 2/Z, 5/S, 8/B.
55
55
  const TAG_ALPHABET = 'ACDEFGHJKMNPQRTUVWXY34679';
@@ -266,9 +266,10 @@ function choiceName(choice) {
266
266
  const strip = (value) => clean(value).replace(/^do it here with /i, '');
267
267
  return strip(choice.label) || strip(choice.title) || 'that model';
268
268
  }
269
- function routePhrase(choice, { it = 'it', other = false } = {}) {
270
- if (choice.here) return `do ${it} here with ${choiceName(choice)}`;
271
- return other ? `use ${choiceName(choice)}` : `hand ${it} to ${choiceName(choice)}`;
269
+ /** A choice that runs in this conversation: "here" on its model, or on another model of its provider. */
270
+ const runsHere = (choice) => !!choice?.here || choice?.kind === 'direct';
271
+ function routePhrase(choice, { it = 'it' } = {}) {
272
+ return runsHere(choice) ? `do ${it} here with ${choiceName(choice)}` : `hand ${it} to ${choiceName(choice)}`;
272
273
  }
273
274
 
274
275
  /**
@@ -294,7 +295,9 @@ export function renderCard(card, { formatPlan = (text) => text } = {}) {
294
295
  lines.push(`I can't take that choice from the phone (${card.desktopOnly}): pick it in SynaBun on your computer, or say no and I'll drop it.`);
295
296
  return lines.join('\n');
296
297
  }
297
- if (others.length) lines.push(`I could also ${joinOr(others.slice(0, 4).map((choice) => routePhrase(choice, { other: true })))}: just name it.${mac ? ' Then I ask about the Mac separately.' : ''}`);
298
+ // The other choices, the ones done here first, then the workers: "do it here with …, hand it to … or hand it to …".
299
+ const rest = [...others.filter(runsHere), ...others.filter((choice) => !runsHere(choice))].slice(0, 4);
300
+ if (rest.length) lines.push(`I could also ${joinOr(rest.map((choice) => routePhrase(choice)))}: just name it.${mac ? ' Then I ask about the Mac separately.' : ''}`);
298
301
  return `${lines.join('\n')}${tagHint(card)}`;
299
302
  }
300
303
  if (card.kind === CONTROL_KINDS.CLARIFY) {
@@ -328,7 +331,8 @@ export function renderCard(card, { formatPlan = (text) => text } = {}) {
328
331
  if (card.desktopOnly) return `I'd need to control your Mac for this, and I can't take a yes for that from the phone (${card.desktopOnly}): answer in SynaBun on your computer, or say no and I'll skip it.`;
329
332
  return `${COMPUTER_ASK_TEXT}${tagHint(card)}`;
330
333
  }
331
- const worker = card.source === 'dispatch' ? `One of the agents${card.provider && card.provider !== 'claude-code' ? ` (${card.provider})` : ''}` : null;
334
+ // The worker's provider by its name ("Codex", "Antigravity"), not its id; Claude Code's is the default and goes unsaid.
335
+ const worker = card.source === 'dispatch' ? `One of the agents${card.provider && card.provider !== 'claude-code' ? ` (${providerLabel(card.provider)})` : ''}` : null;
332
336
  const lines = [worker ? `${worker} needs your OK to run this:` : 'I need your OK to run this:', ...toolLines(card)];
333
337
  if (n.message && n.message !== n.detail) lines.push(clip(n.message, 200));
334
338
  if (card.desktopOnly) lines.push(`I can't take a yes for this one from the phone (${card.desktopOnly}): approve it in SynaBun on your computer, or say no and I'll skip it.`);
@@ -426,12 +430,21 @@ function matchRouteChoice(card, text) {
426
430
  said = said.replace(PICK_LEAD, '').replace(/^the /, '');
427
431
  for (let i = 0; i < 3; i += 1) said = said.replace(/ (?:please|instead|then|thanks|model)$/, '');
428
432
  if (said.length < 2) return null;
433
+ // "Here" names the option on the Assistant's own model (else the first done here); "dispatch" / "a worker"
434
+ // names the first worker option, the one the card suggests for the worker side.
435
+ const firstHere = card.routeChoices.find((choice) => choice.here) || card.routeChoices.find(runsHere) || null;
436
+ const firstWorker = card.routeChoices.find((choice) => !runsHere(choice)) || null;
429
437
  const names = (choice) => {
430
438
  const list = [choice.label, choiceName(choice), choice.model, choice.title, String(choice.model || '').split('/').pop()].map(plain).filter(Boolean);
431
- if (choice.here) list.push('here', 'do it here', 'do it yourself', 'yourself', 'you do it', `here with ${plain(choice.label)}`);
439
+ if (choice === firstHere) list.push('here', 'do it here', 'do it yourself', 'yourself', 'you do it', `here with ${plain(choice.label)}`);
440
+ if (choice === firstWorker) list.push('dispatch', 'dispatch it', 'a worker', 'worker', 'hand it off', 'delegate', 'delegate it');
432
441
  return [...new Set(list)];
433
442
  };
434
- const hits = card.routeChoices.filter((choice) => names(choice).some((name) => name === said || (said.length >= 3 && name.startsWith(`${said} `))));
443
+ // A whole name first: "do it here" is the "here" option even when another option's label starts with it
444
+ // ("Do it here with Haiku 4.5"); only then the start of a name ("sonnet" for "Sonnet 5").
445
+ const exact = card.routeChoices.filter((choice) => names(choice).includes(said));
446
+ if (exact.length) return exact.length === 1 ? exact[0] : null;
447
+ const hits = card.routeChoices.filter((choice) => names(choice).some((name) => said.length >= 3 && name.startsWith(`${said} `)));
435
448
  return hits.length === 1 ? hits[0] : null;
436
449
  }
437
450
 
@@ -472,7 +485,7 @@ export function answerCard(card, reply, { quoted = false } = {}) {
472
485
  if (!tag.ok) return needTag();
473
486
  if (card.desktopOnly || !choice) return { action: 'desktop', message: 'I can\'t take that choice from the phone: pick it in SynaBun on your computer, or say no and I\'ll drop it.' };
474
487
  const onMac = routeControlsMac(card) && choice.id === card.routeChoices[0].id;
475
- return respond(card, buildRouteResponse(card.route, { optionId: choice.id, remember: false }), onMac ? `doing it on your Mac, here with ${choiceName(choice)}. Esc on the Mac stops me` : choice.here ? `doing it here with ${choiceName(choice)}` : `handing it to ${choiceName(choice)}`);
488
+ return respond(card, buildRouteResponse(card.route, { optionId: choice.id, remember: false }), onMac ? `doing it on your Mac, here with ${choiceName(choice)}. Esc on the Mac stops me` : runsHere(choice) ? `doing it here with ${choiceName(choice)}` : `handing it to ${choiceName(choice)}`);
476
489
  }
477
490
  if (stop || parsed.type === 'no' || (numbers.length === 1 && numbers[0] === 2)) return deny(card, parsed.rest);
478
491
  const yes = parsed.type === 'yes' || (numbers.length === 1 && numbers[0] === 1 && !parsed.rest);
@@ -164,7 +164,7 @@ const MESSAGES = {
164
164
  activityText: 'activityText must be true or false',
165
165
  strictWorkerApprovals: 'strictWorkerApprovals must be true or false',
166
166
  computerUse: 'computerUse must be true or false',
167
- brain: 'brain must be null (same as the Assistant) or { provider, model, effort } with a provider of claude-code, codex or opencode',
167
+ brain: 'brain must be null (same as the Assistant) or { provider, model, effort } with a provider of claude-code, codex, opencode or antigravity',
168
168
  transcriptionEnabled: 'transcriptionEnabled must be true or false',
169
169
  transcriptionLanguage: "transcriptionLanguage must be 'auto' or a 2-3 letter lowercase language code, such as en or pt",
170
170
  transcriptionEnginePath: 'transcriptionEnginePath must be empty (find whisper-cli by itself) or the absolute path of the whisper-cli program',
@@ -39,6 +39,7 @@
39
39
  import { existsSync, lstatSync, realpathSync } from 'node:fs';
40
40
  import { homedir, tmpdir } from 'node:os';
41
41
  import { dirname, isAbsolute, join, resolve, sep } from 'node:path';
42
+ import { acpCall, acpToolPaths } from './antigravity/tool-call.js';
42
43
 
43
44
  export const WORKER_GUARD_MODES = Object.freeze(['enforce', 'warn', 'off']);
44
45
  export const WORKER_GUARD_DEFAULT = 'enforce';
@@ -46,7 +47,7 @@ export const WORKER_CONFINEMENT = 'worker';
46
47
  /** The capabilities the guard applies to (never 'full'). */
47
48
  export const GUARDED_CAPABILITIES = Object.freeze(['read-only', 'workspace']);
48
49
  /** Folders under the home a worker must never write into (documented; the rule itself is "outside the writable roots"). */
49
- export const PROTECTED_HOME_DIRS = Object.freeze(['.claude', '.codex', '.agents', '.config/opencode', '.synabun', '.local/share/opencode', '.ssh', '.gnupg', '.aws', '.config']);
50
+ export const PROTECTED_HOME_DIRS = Object.freeze(['.claude', '.codex', '.agents', '.config/opencode', '.synabun', '.local/share/opencode', '.gemini', '.ssh', '.gnupg', '.aws', '.config']);
50
51
 
51
52
  export function normalizeWorkerGuardMode(value) {
52
53
  const text = String(value ?? '').trim().toLowerCase();
@@ -1163,6 +1164,81 @@ export function openCodePermissionDecision(request, confinement, { toolName = nu
1163
1164
  }
1164
1165
  }
1165
1166
 
1167
+ // The ACP tool kinds that change files (session/request_permission's toolCall.kind).
1168
+ const ACP_WRITE_KINDS = new Set(['edit', 'delete', 'move']);
1169
+ // Preserve the public import while using Antigravity's canonical path reader.
1170
+ export { acpToolPaths };
1171
+
1172
+ /**
1173
+ * An ACP permission request (session/request_permission: its toolCall { kind, title, locations, rawInput }) under
1174
+ * a confinement, the way Antigravity's `agy acp` asks before an edit, a command or work outside the workspace:
1175
+ * null when it may go ahead, else a refusal. `execute` is read through the shell heuristic, `edit` / `delete` /
1176
+ * `move` by every path the call names (all of them must be writable; read-only refuses them all), `read` /
1177
+ * `search` pass unless they reach one of SynaBun's protected home folders, `fetch` passes unless it calls one of
1178
+ * SynaBun's own local ports, and anything else (an MCP tool) goes through workerToolDecision by its title.
1179
+ */
1180
+ export function acpPermissionDecision(rawCall, confinement) {
1181
+ if (!confinement) return null;
1182
+ try {
1183
+ // Either shape Antigravity sends (a permission request's toolCall, a hook payload's { name, args }), read as one
1184
+ // (lib/antigravity/tool-call.js): its kind from its name when it has none, an MCP call as its server and tool.
1185
+ const call = rawCall?.args && 'mcp' in rawCall ? rawCall : acpCall(rawCall);
1186
+ const toolCall = { ...call, rawInput: call.args };
1187
+ // An MCP call is the tool it calls (agy reports it as kind `execute` with no command line).
1188
+ if (call.mcp) return workerToolDecision(`mcp__${call.mcp.server}__${call.mcp.tool}`, call.mcp.args, confinement);
1189
+ const kind = String(call.kind || 'other').toLowerCase();
1190
+ const input = call.args;
1191
+ const label = String(call.title || call.name || kind).slice(0, 120);
1192
+ if (kind === 'execute') {
1193
+ const command = typeof input.command === 'string' ? input.command
1194
+ : Array.isArray(input.command) ? input.command.join(' ')
1195
+ : typeof input.CommandLine === 'string' ? input.CommandLine
1196
+ : typeof input.cmd === 'string' ? input.cmd : '';
1197
+ if (!command) return refusal('unknown-command', `${label} names no command SynaBun could read, so it is refused; this run writes inside ${rootsText(confinement)} only.`);
1198
+ return workerShellDecision(command, confinement);
1199
+ }
1200
+ if (ACP_WRITE_KINDS.has(kind)) {
1201
+ if (confinement.capability === 'read-only') return refusal('read-only', `This run has capability read-only: ${label} (${kind}) is not available. Report what you found instead of changing files.`);
1202
+ const paths = acpToolPaths(toolCall);
1203
+ if (!paths.length) return refusal('unknown-path', `${label} (${kind}) names no path SynaBun could read; this run writes inside ${rootsText(confinement)} only.`);
1204
+ for (const raw of paths) {
1205
+ const place = placeOf(raw, { cwd: confinement.cwd, home: confinement.home });
1206
+ if (!place) return refusal('unknown-path', `${label} names a path SynaBun could not read ("${String(raw).slice(0, 200)}"); this run writes inside ${rootsText(confinement)} only.`);
1207
+ if (!mayWrite(place, confinement)) return refusal('write-outside', `${label} (${kind}) to ${place} is outside this run's workspace: this run writes inside ${rootsText(confinement)} only.`, place);
1208
+ }
1209
+ return null;
1210
+ }
1211
+ if (kind === 'read' || kind === 'search') {
1212
+ for (const raw of acpToolPaths(toolCall)) {
1213
+ const place = placeOf(raw, { cwd: confinement.cwd, home: confinement.home });
1214
+ if (place && isProtectedPlace(place, confinement)) {
1215
+ return refusal('protected-path', `${label} reaches ${place}, one of SynaBun's protected home folders (its data home or a runtime's credential store): a worker does not read there, whatever its capability.`, place);
1216
+ }
1217
+ }
1218
+ return null;
1219
+ }
1220
+ if (kind === 'fetch') {
1221
+ const url = typeof input.url === 'string' ? input.url : typeof input.Url === 'string' ? input.Url : '';
1222
+ if (url) {
1223
+ let parsed = null;
1224
+ try { parsed = new URL(url); } catch { /* not a URL: nothing to compare */ }
1225
+ if (parsed && isLoopbackHost(parsed.hostname)) {
1226
+ const port = Number(parsed.port || (parsed.protocol === 'https:' ? 443 : 80));
1227
+ const kindOfPort = controlPortOf(port, confinement);
1228
+ if (kindOfPort) return refusal('control-port', `${label}: ${controlPortText(kindOfPort, parsed.hostname, port, confinement)}; a worker does not call it.`);
1229
+ }
1230
+ }
1231
+ return null;
1232
+ }
1233
+ if (kind === 'think' || kind === 'switch_mode') return null;
1234
+ // MCP and other tools: by the name the call carries ("mcp__server__tool" when the agent says so).
1235
+ const name = typeof input.toolName === 'string' ? input.toolName : typeof toolCall?.title === 'string' ? toolCall.title : '';
1236
+ return name ? workerToolDecision(name, input, confinement) : null;
1237
+ } catch (err) {
1238
+ return refusal('check-failed', `The request could not be checked (${err?.message || err}), so it is refused.`);
1239
+ }
1240
+ }
1241
+
1166
1242
  /** What the run's transcript records for a decision: { type: 'synabun.confinement', action, tool, rule, reason, target? }. */
1167
1243
  export function confinementEvent(decision, { tool, confinement, provider = null } = {}) {
1168
1244
  return {
@@ -0,0 +1,74 @@
1
+ // Provider wire answers for the dispatcher's single, first-wins pending request.
2
+ import { codexControlRequestFromServerRequest, codexControlResponse } from './assistant-envelope.js';
3
+ import { validateCodexServerResponse } from './codex-compat-rpc.js';
4
+
5
+ export const CODEX_QUESTIONS = new Set(['item/tool/requestUserInput', 'tool/requestUserInput', 'mcpServer/elicitation/request']);
6
+
7
+ export function workerQuestions(params = {}) {
8
+ if (Array.isArray(params.questions)) return params.questions;
9
+ if (String(params.message || '').startsWith('[SYNABUN_CHOICE_V1]')) {
10
+ try { return JSON.parse(params.message.slice('[SYNABUN_CHOICE_V1]'.length)).questions || []; } catch { /* generic form */ }
11
+ }
12
+ return [];
13
+ }
14
+
15
+ export function elicitationReply(params, reply = {}) {
16
+ const action = reply.action || reply.result?.action || (reply.behavior === 'allow' ? 'accept' : 'cancel');
17
+ if (reply.behavior !== 'allow' || action !== 'accept') return { action: action === 'decline' ? 'decline' : 'cancel' };
18
+ const supplied = reply.content ?? reply.result?.content ?? reply.answers ?? {};
19
+ if (!supplied || typeof supplied !== 'object' || Array.isArray(supplied)) throw new Error('Question answers must be an object');
20
+ const content = {};
21
+ for (const [key, value] of Object.entries(supplied)) {
22
+ const answers = value?.answers;
23
+ const property = params.requestedSchema?.properties?.[key];
24
+ content[key] = Array.isArray(answers) ? (property?.type === 'array' ? answers : answers.join(', ')) : value;
25
+ // An Assistant answer may give free text instead of choice's wire sentinel.
26
+ if (property?.type === 'array' && !Array.isArray(content[key])) content[key] = [String(content[key])];
27
+ if (property?.oneOf && !property.oneOf.some((o) => o.const === content[key]) && params.requestedSchema.properties[`${key}__other`]) {
28
+ content[`${key}__other`] = String(content[key]); content[key] = '__synabun_other__';
29
+ }
30
+ }
31
+ for (const key of params.requestedSchema?.required || []) {
32
+ if (content[key] == null || content[key] === '' || (Array.isArray(content[key]) && !content[key].length)) throw new Error(`Missing answer for ${key}`);
33
+ }
34
+ for (const [key, value] of Object.entries(content)) {
35
+ const schema = params.requestedSchema?.properties?.[key];
36
+ if (!schema) continue;
37
+ if (schema.type === 'array') {
38
+ if (!Array.isArray(value) || value.some((v) => typeof v !== 'string')) throw new Error(`Invalid choices for ${key}`);
39
+ } else if (schema.type && typeof value !== schema.type.replace('integer', 'number')) throw new Error(`Invalid answer for ${key}`);
40
+ if ((value === '__synabun_other__' || Array.isArray(value) && value.includes('__synabun_other__')) && !content[`${key}__other`]?.trim()) throw new Error(`Missing custom answer for ${key}`);
41
+ }
42
+ return { action: 'accept', content };
43
+ }
44
+
45
+ export function codexWorkerReply(packet, reply) {
46
+ if (packet.method === 'mcpServer/elicitation/request') return { result: elicitationReply(packet.params || {}, reply) };
47
+ if (reply.result?.answers && !reply.answers) reply = { ...reply, answers: reply.result.answers };
48
+ const result = codexControlResponse(codexControlRequestFromServerRequest({ ...packet, requestId: packet.id }), reply);
49
+ if (result.result) validateCodexServerResponse(packet.method, packet.params, result.result);
50
+ return result.error ? { error: result.error } : { result: result.result };
51
+ }
52
+
53
+ export function openCodeAnswers(questions, answers) {
54
+ const list = Array.isArray(answers) ? answers : questions.map((q, i) => answers?.[q.id || q.question] ?? answers?.[q.header] ?? answers?.[i]);
55
+ return questions.map((q, i) => {
56
+ const value = list[i]?.answers ?? list[i];
57
+ const result = (Array.isArray(value) ? value : value == null ? [] : [value]).map(String);
58
+ if (!result.length || !result.some((v) => v.trim())) throw new Error(`Missing answer for ${q.header || q.question || i}`);
59
+ return result;
60
+ });
61
+ }
62
+
63
+ /** Validate before consuming the resolver, so a malformed answer leaves a retryable card. */
64
+ export function validateWorkerReply(pending, reply) {
65
+ if (reply.behavior !== 'allow' || pending.kind !== 'question') return;
66
+ const protocol = pending.protocol;
67
+ if (protocol?.method === 'elicitation/create') elicitationReply(pending.input || {}, reply);
68
+ else if (protocol?.provider === 'codex') codexWorkerReply({ id: protocol.id, method: protocol.method, params: pending.input || {} }, reply);
69
+ else if (protocol?.provider === 'opencode') openCodeAnswers(pending.questions || [], reply.answers);
70
+ else if (pending.toolName === 'AskUserQuestion') {
71
+ const answers = reply.answers ?? reply.updatedInput?.answers;
72
+ for (const q of pending.questions || []) if (!answers?.[q.question]) throw new Error(`Missing answer for ${q.question}`);
73
+ }
74
+ }