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
@@ -1,6 +1,7 @@
1
+ import { codexWorkerReply, CODEX_QUESTIONS, elicitationReply, openCodeAnswers, workerQuestions } from './worker-question.js';
1
2
  import { mkdtempSync, rmSync } from 'node:fs';
2
3
  import { tmpdir } from 'node:os';
3
- import { externalServerInventory, selectExternalServers, codexMcpEnabledOverride, referenceClaudeMap, waitForClaudeMcpScope, redactMcpValues, writePrivateMcpConfig, mcpDefinitionValues } from './mcp-external-servers.js';
4
+ import { externalServerInventory, selectExternalServers, codexMcpEnabledOverride, referenceClaudeMap, waitForClaudeMcpScope, redactMcpValues, writePrivateMcpConfig, mcpSecretValues } from './mcp-external-servers.js';
4
5
  import { resolveStoredTools, runtimeToolsValue, readDefaultTools } from './tool-lists.js';
5
6
  import { waitForSynabunTools, synabunToolsTimeoutText } from './opencode-mcp-ready.js';
6
7
  import { config as memoryConfig, detectProject as memoryProject } from '../../mcp-server/dist/config.js';
@@ -21,7 +22,13 @@ import { CODEX_BROWSER_POLICY_UNTRUSTED_NOTE, codexBrowserPolicyHook } from './a
21
22
  import { codexWindowsSandbox, codexWindowsSandboxConfig } from './agent-sandbox-elevation.js';
22
23
  import { DRAFT_DISALLOWED_TOOLS, DRAFT_TOOLS, codexDraftOptions, draftOpenCodeDecision, draftPreToolUseHook, draftToolDecision, openCodeDraftConfig, resolveDraftConfinement } from './skills/draft-confinement.js';
23
24
  import { EVAL_CONFINEMENT, codexEvalOptions, evalClaudeOptions, evalGitSafetyEnv, evalOpenCodeDecision, evalPreToolUseHook, evalToolDecision, openCodeEvalConfig, openCodeEvalEnv, placeOpenCodeEvalSkill, resolveEvalConfinement } from './skills/eval-confinement.js';
24
- import { WORKER_GUARD_DEFAULT, codexSandboxFor, confinementEvent, openCodeCodeModeDecision, openCodeCodeModeNote, openCodePermissionDecision, openCodePermissionsFor, resolveWorkerConfinement, workerShellDecision, workerToolDecision } from './worker-confinement.js';
25
+ import { WORKER_GUARD_DEFAULT, acpPermissionDecision, codexSandboxFor, confinementEvent, openCodeCodeModeDecision, openCodeCodeModeNote, openCodePermissionDecision, openCodePermissionsFor, resolveWorkerConfinement, workerShellDecision, workerToolDecision } from './worker-confinement.js';
26
+ import { createAcpConnection } from './antigravity/acp-connection.js';
27
+ import { acpTurnUsage } from './antigravity/usage.js';
28
+ import { assertSafeAntigravityProject, antigravityHomeReadPolicy, antigravityHomeSettings, policyHookCommand, prepareAntigravityHome, toAntigravityMcpEntry, userAntigravityMcpServers, userAntigravitySettings, userGeminiDir } from './antigravity/home.js';
29
+ import { acpCall, acpPolicyInput, acpReadOnlyKind, acpToolName, acpToolPaths, acpWebTool } from './antigravity/tool-call.js';
30
+ import { splitAntigravityModel } from './antigravity/catalog.js';
31
+ import { fileURLToPath } from 'node:url';
25
32
 
26
33
  function nativeToolSelection(options, provider) { return resolveStoredTools(options, { source: options.runMode === 'task' ? 'dispatch' : 'launch', provider, defaultTools: options.runMode === 'task' ? undefined : readDefaultTools(memoryConfig.dataHome) }); }
27
34
 
@@ -355,7 +362,7 @@ export async function createCodexNativeLoopAdapter(options = {}) {
355
362
  runId, cwd, model, effort, codexHome, codexPath, browserSessionId,
356
363
  browserTabId, onIdentity = () => {}, onEvent = () => {},
357
364
  CodexClass = Codex,
358
- extraEnv = {}, permissionPolicy: rawPolicy = 'auto', capability: rawCapability = 'full',
365
+ extraEnv = {}, permissionPolicy: rawPolicy = 'auto', permissionBroker = null, capability: rawCapability = 'full',
359
366
  outputSchema = null, desktopGrantProvider = null, modelContextWindow = null, contextMode = null,
360
367
  // Image creation runs (assistant dispatch): 'image' → each turn returns the images it generated.
361
368
  collectMedia = null,
@@ -400,10 +407,8 @@ export async function createCodexNativeLoopAdapter(options = {}) {
400
407
  const extended = contextMode === 'extended' || legacyExtended;
401
408
  const runModel = legacyExtended ? String(model).replace(/\[extended\]$/i, '') : model;
402
409
  if (extended && !(Number(modelContextWindow) > 0)) throw new Error(`Extended context is unavailable for ${runModel}.`);
403
- // The Codex SDK drives `codex exec`, which has no approval channel: an 'ask'
404
- // policy would hang or fail mid-turn. Refuse up front so the dispatcher can
405
- // offer 'restricted' instead.
406
- if (permissionPolicy === 'ask') throw policyUnsupportedError('Codex', permissionPolicy);
410
+ // Legacy exec loops have no approval channel. Task workers use an app-server.
411
+ if (permissionPolicy === 'ask' && runMode !== 'task') throw policyUnsupportedError('Codex', permissionPolicy);
407
412
  // Assistant task runs: SynaBun's browser policy as a PreToolUse hook the
408
413
  // user's Codex trusts (the brain's route-gate mechanism), and no web search
409
414
  // of Codex's own. Without a trusted hook the policy is instructions only
@@ -416,6 +421,7 @@ export async function createCodexNativeLoopAdapter(options = {}) {
416
421
  }
417
422
  const pins = stringEnv({
418
423
  SYNABUN_TERMINAL_SESSION: runId,
424
+ SYNABUN_RUN_MODE: options.runMode === 'task' ? 'task' : undefined,
419
425
  SYNABUN_PROJECT: cwd ? memoryProject(cwd) : 'global',
420
426
  SYNABUN_BROWSER_SESSION: browserSessionId,
421
427
  SYNABUN_BROWSER_TAB: browserTabId,
@@ -436,7 +442,7 @@ export async function createCodexNativeLoopAdapter(options = {}) {
436
442
  const policyEnv = browserPolicy ? { SYNABUN_BROWSER_POLICY_CWD: cwd || process.cwd(), SYNABUN_BROWSER_POLICY_ROOTS: JSON.stringify(Array.isArray(policyRoots) ? policyRoots : []) } : {};
437
443
  const env = withServeEnv(stringEnv({ ...inherited, ...pins, CODEX_HOME: codexHome, ...policyEnv, ...stringEnv(extraEnv) }), evaluated ? evalGitSafetyEnv(evaluated.held.root) : null);
438
444
  const config = { ...codexWindowsSandboxConfig(adminSandbox) };
439
- if (Object.keys(pins).length) config.mcp_servers = { SynaBun: { env: pins } };
445
+ if (Object.keys(pins).length) config.mcp_servers = { SynaBun: { env: pins, ...(task ? { tool_timeout_sec: Math.ceil((options.questionTimeoutMs || 30 * 60_000) / 1000) + 30 } : {}) } };
440
446
  if (!boxed) {
441
447
  const inventory = externalServerInventory({ provider: 'codex', cwd, codexHome, env, home: env.HOME, dataHome: memoryConfig.dataHome, ...options.externalMcpContext });
442
448
  const list = nativeToolSelection(options, 'codex');
@@ -462,6 +468,13 @@ export async function createCodexNativeLoopAdapter(options = {}) {
462
468
  }
463
469
  const WorkerCodex = task && !boxed && CodexClass === Codex ? CodexWorkerAppServer : CodexClass;
464
470
  const codex = new WorkerCodex({
471
+ permissionPolicy,
472
+ onRequest: permissionBroker && !boxed ? async (packet, signal) => {
473
+ const question = CODEX_QUESTIONS.has(packet.method);
474
+ if (!question && permissionPolicy !== 'ask') return { error: { code: -32601, message: 'Unexpected worker approval request' } };
475
+ const reply = await permissionBroker.request({ kind: question ? 'ask' : 'tool', toolName: packet.method, input: packet.params, questions: workerQuestions(packet.params), protocol: { provider: 'codex', method: packet.method, id: packet.id }, signal });
476
+ return codexWorkerReply(packet, reply);
477
+ } : null,
465
478
  onProcess: options.onProcess, signal: options.signal, apiKey: options.apiKey, configArgs: options.configArgs,
466
479
  ...(codexPath ? { codexPathOverride: codexPath } : {}),
467
480
  env,
@@ -694,10 +707,10 @@ export async function createClaudeNativeLoopAdapter(options = {}) {
694
707
  const ownEnv = stringEnv({
695
708
  ...inherited,
696
709
  SYNABUN_TERMINAL_SESSION: runId,
710
+ SYNABUN_RUN_MODE: options.runMode === 'task' ? 'task' : undefined,
697
711
  SYNABUN_PROJECT: cwd ? memoryProject(cwd) : 'global',
698
712
  SYNABUN_BROWSER_SESSION: browserSessionId,
699
713
  SYNABUN_BROWSER_TAB: browserTabId,
700
- SYNABUN_RUN_MODE: runMode === 'task' ? 'task' : undefined,
701
714
  ...stringEnv(extraEnv),
702
715
  });
703
716
  // Apply after every inherited/child environment overlay: drop ALL GIT_* first, then SynaBun's own safety entries.
@@ -722,13 +735,11 @@ export async function createClaudeNativeLoopAdapter(options = {}) {
722
735
  // Computer-use workers: the grant unlocks the `computer` MCP group.
723
736
  'X-Synabun-Desktop-Grant': typeof desktopGrantProvider === 'function' ? desktopGrantProvider() || undefined : undefined,
724
737
  });
725
- // Under 'ask' the orchestrator answers permission prompts (and AskUserQuestion
726
- // becomes a relayed question); otherwise the run is unattended and both
727
- // interactive tools stay denied. ExitPlanMode is never useful for a worker.
728
- const denyMatcher = askMode ? 'ExitPlanMode' : 'AskUserQuestion|ExitPlanMode';
729
- const denyMessage = askMode
730
- ? 'Dispatched workers do not use plan mode.'
731
- : 'This unattended automation cannot wait for interactive input.';
738
+ // Questions use the broker under every policy, independently of tool approvals.
739
+ // ExitPlanMode stays denied: asking does not need a separate plan turn.
740
+ const questionMode = !held && typeof permissionBroker?.request === 'function';
741
+ const denyMatcher = questionMode ? 'ExitPlanMode' : 'AskUserQuestion|ExitPlanMode';
742
+ const denyMessage = 'Dispatched workers do not use plan mode; questions use the live run sidepanel.';
732
743
  // A confined run asks canUseTool for every write (permission mode 'default') on top of its PreToolUse hook, and
733
744
  // loads no settings file: no user hooks (they would ask it for memories it has no tool to write), no allow rules.
734
745
  const bypass = !askMode && !held;
@@ -767,6 +778,7 @@ export async function createClaudeNativeLoopAdapter(options = {}) {
767
778
  },
768
779
  })],
769
780
  },
781
+ ...(questionMode ? [{ matcher: 'AskUserQuestion', hooks: [async () => ({ hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'ask', permissionDecisionReason: 'The user answers in this run’s sidepanel.' } })] }] : []),
770
782
  // Assistant task runs: SynaBun's browser policy (browser-tool-policy.js)
771
783
  // on every call, subagents' included, before the bypass and canUseTool.
772
784
  ...(runMode === 'task' ? [{
@@ -789,7 +801,7 @@ export async function createClaudeNativeLoopAdapter(options = {}) {
789
801
  }
790
802
  if (toolName === 'ExitPlanMode') return { behavior: 'deny', message: denyMessage };
791
803
  if (toolName === 'AskUserQuestion') {
792
- if (!askMode) return { behavior: 'deny', message: denyMessage };
804
+ if (!questionMode) return { behavior: 'deny', message: denyMessage };
793
805
  const reply = await brokerRequest({ kind: 'ask', toolName, input: toolInput, questions: toolInput?.questions || [] });
794
806
  if (reply.behavior === 'allow') {
795
807
  const answers = reply.answers || reply.updatedInput?.answers || {};
@@ -797,6 +809,8 @@ export async function createClaudeNativeLoopAdapter(options = {}) {
797
809
  }
798
810
  return { behavior: 'deny', message: reply.message || 'The question was not answered.' };
799
811
  }
812
+ // Loading a deferred question schema is a read, never a permission decision.
813
+ if (toolName === 'ToolSearch') return { behavior: 'allow', updatedInput: toolInput };
800
814
  // Computer use is fully autonomous: its guards live in the desktop service,
801
815
  // so the orchestrator is never asked to approve individual actions.
802
816
  if (askMode && /^mcp__SynaBun__computer(_apps|_ax|_status)?$/.test(String(toolName))) return { behavior: 'allow', updatedInput: toolInput };
@@ -850,7 +864,8 @@ export async function createClaudeNativeLoopAdapter(options = {}) {
850
864
  if (definition.oauth) privateMcpServers[name].oauth = structuredClone(definition.oauth);
851
865
  if (definition.headersHelper) privateMcpServers[name].headersHelper = definition.headersHelper;
852
866
  }
853
- externalValues = [...Object.values(referenced.env), ...mcpDefinitionValues(definitions)];
867
+ // Only the external servers' secrets (expanded through the CLI's env); SynaBun's own headers are not secrets.
868
+ externalValues = mcpSecretValues(definitions, { env });
854
869
  externalScopeNames = Object.keys(definitions);
855
870
  }
856
871
  if (model) queryOptions.model = model;
@@ -1293,6 +1308,7 @@ export async function createOpenCodeNativeLoopAdapter(options = {}) {
1293
1308
  } catch {}
1294
1309
  return '';
1295
1310
  };
1311
+ const pendingQuestions = new Set();
1296
1312
  const brokerRequest = async (request) => {
1297
1313
  try {
1298
1314
  const reply = await permissionBroker.request({ runId, provider: 'opencode', signal: activeAbort?.signal, ...request });
@@ -1310,6 +1326,7 @@ export async function createOpenCodeNativeLoopAdapter(options = {}) {
1310
1326
  };
1311
1327
  const finishTurnWithText = () => {
1312
1328
  const turn = activeTurn;
1329
+ if (pendingQuestions.size) return;
1313
1330
  if (!turn || turn.mode !== 'async' || turn.sent === false || turn.settled || turn.finishing) return;
1314
1331
  turn.finishing = true;
1315
1332
  fetchAssistantText()
@@ -1336,15 +1353,23 @@ export async function createOpenCodeNativeLoopAdapter(options = {}) {
1336
1353
  if (/^message[.:]part[.:]delta$/i.test(eventType)) rememberDelta(event);
1337
1354
  if (/question\.asked/i.test(eventType)) {
1338
1355
  const requestID = event.id || event.requestID;
1339
- if (requestID && askMode && client.question?.reply) {
1340
- brokerRequest({ kind: 'ask', toolName: 'question', input: event, questions: event.questions || [] })
1356
+ if (requestID && !held && permissionBroker?.request && client.question?.reply) {
1357
+ if (pendingQuestions.has(requestID)) return;
1358
+ pendingQuestions.add(requestID);
1359
+ brokerRequest({ kind: 'ask', toolName: 'question', input: event, questions: event.questions || [], protocol: { provider: 'opencode', id: requestID } })
1341
1360
  .then((reply) => {
1361
+ // The tool is now answered; an idle SSE arriving before the HTTP ack is valid.
1362
+ pendingQuestions.delete(requestID);
1342
1363
  if (reply.behavior === 'allow') {
1343
- return client.question.reply({ requestID, answers: reply.answers || [], directory: cwd || undefined });
1364
+ return client.question.reply({ requestID, answers: openCodeAnswers(event.questions || [], reply.answers), directory: cwd || undefined });
1344
1365
  }
1345
1366
  return client.question?.reject?.({ requestID, directory: cwd || undefined });
1346
1367
  })
1347
- .catch(() => {});
1368
+ .catch(async (error) => {
1369
+ try { await client.question?.reject?.({ requestID, directory: cwd || undefined }); } catch {}
1370
+ settleActiveTurn(new Error(`OpenCode question reply failed: ${providerErrorMessage(error)}`));
1371
+ })
1372
+ .finally(() => pendingQuestions.delete(requestID));
1348
1373
  } else if (requestID && client.question?.reject) {
1349
1374
  client.question.reject({ requestID, directory: cwd || undefined }).catch(() => {});
1350
1375
  }
@@ -1383,7 +1408,8 @@ export async function createOpenCodeNativeLoopAdapter(options = {}) {
1383
1408
  }) || (() => {});
1384
1409
  // The rules of a confined run ride the session too (lib/opencode2-client.js session.create sends them as the
1385
1410
  // session's own `permissions`), on top of the serve's config.
1386
- const sessionRules = confined ? openCodeDraftConfig().permissions : evaluated ? openCodeEvalConfig(evaluated).permissions : guard ? openCodePermissionsFor(guard) : null;
1411
+ let sessionRules = confined ? openCodeDraftConfig().permissions : evaluated ? openCodeEvalConfig(evaluated).permissions : guard ? openCodePermissionsFor(guard) : null;
1412
+ if (runMode === 'task' && !held) sessionRules = [...(sessionRules || []), { action: 'question', resource: '*', effect: 'allow' }];
1387
1413
  try {
1388
1414
  await client.waitUntilConnected?.(10_000);
1389
1415
  const created = options.resumeSessionId
@@ -1538,3 +1564,377 @@ export async function createOpenCodeNativeLoopAdapter(options = {}) {
1538
1564
  },
1539
1565
  };
1540
1566
  }
1567
+
1568
+ // ── Antigravity (Google's `agy` CLI, over ACP) ──────────────────────────────
1569
+ //
1570
+ // One `agy acp` process and one ACP session per run (lib/antigravity/acp-connection.js), every turn a session/prompt,
1571
+ // in the run's own Antigravity home (`--gemini-dir`, lib/antigravity/home.js): its MCP servers are exactly SynaBun's
1572
+ // and the external servers its tool list names, and its PreToolUse hook asks SynaBun about every call before it runs
1573
+ // (lib/antigravity/policy.js → antigravityRunPolicy: the browser policy, the run's MCP scope, the worker's
1574
+ // confinement). A refusal reaches the model with its reason ("tool call denied by pre-tool hook: …") and the turn goes
1575
+ // on; an ACP permission refusal would end the turn without one. Approvals stay with ACP's session/request_permission:
1576
+ // the Assistant's broker answers them under 'ask', else they are allowed once. Never "always": Antigravity would save
1577
+ // it for later sessions. The model and thinking level are the session's own config options (set only to a value the
1578
+ // session offers). The home keeps the conversation, so a run resumes after a restart (session/resume, no replay).
1579
+ // SynaBun's MCP child gets the run's pins from the agent's environment, which agy's MCP children inherit.
1580
+
1581
+ /** The hook script every SynaBun home runs before a tool call. */
1582
+ export const ANTIGRAVITY_POLICY_HOOK = fileURLToPath(new URL('./antigravity/policy-hook.mjs', import.meta.url));
1583
+
1584
+ /** The ACP permission option for an answer: explicit kinds only, never allow-always. */
1585
+ export function acpPermissionOption(options, wanted) {
1586
+ const list = Array.isArray(options) ? options.filter((option) => option && typeof option.optionId === 'string') : [];
1587
+ for (const kind of wanted) {
1588
+ if (kind === 'allow_always') continue;
1589
+ const found = list.find((option) => option.kind === kind);
1590
+ if (found) return found.optionId;
1591
+ }
1592
+ return null;
1593
+ }
1594
+
1595
+ /** The last JSON object in a reply (an output schema's answer, which ACP has no channel for), or null. */
1596
+ export function lastJsonObject(text) {
1597
+ const source = String(text || '');
1598
+ const fenced = [...source.matchAll(/```(?:json)?\s*([\s\S]*?)```/g)].map((m) => m[1]).reverse();
1599
+ for (const candidate of [...fenced, source]) {
1600
+ const trimmed = candidate.trim();
1601
+ for (let start = trimmed.lastIndexOf('{'); start >= 0; start = trimmed.lastIndexOf('{', start - 1)) {
1602
+ const end = trimmed.lastIndexOf('}');
1603
+ if (end <= start) continue;
1604
+ try {
1605
+ const value = JSON.parse(trimmed.slice(start, end + 1));
1606
+ if (value && typeof value === 'object' && !Array.isArray(value)) return value;
1607
+ } catch { /* try an earlier brace */ }
1608
+ }
1609
+ }
1610
+ return null;
1611
+ }
1612
+
1613
+ /**
1614
+ * One Antigravity run's policy for one tool call (acpCall's shape): what its hook answers, and what a permission
1615
+ * request is checked against again. { decision: 'deny', reason, event? } | { decision: 'ask' | 'allow', event? }.
1616
+ * - MCP tool schemas in the run's home may be read; home configuration is refused;
1617
+ * - the browser policy (lib/browser-tool-policy.js: Antigravity's own web search, URL reading and browser, a shell
1618
+ * that starts a browser or browser code, another server's browser tools);
1619
+ * - the run's MCP scope (`allowedServers`: SynaBun and the external servers its tool list names;
1620
+ * enabled project MCP servers are refused before agy starts);
1621
+ * - the worker's confinement (lib/worker-confinement.js acpPermissionDecision; warn mode reports and allows);
1622
+ * - then the approval: 'ask' for anything but a read under the 'ask' policy, else 'allow'.
1623
+ */
1624
+ export function antigravityRunPolicy(call, { homeDir = null, cwd = null, allowedServers = null, guard = null, askMode = false, policyRoots = [] } = {}) {
1625
+ if (!call || (!call.name && !call.title && (!call.kind || call.kind === 'other'))) return { decision: 'deny', reason: 'SynaBun received no valid tool call, so it is refused.' };
1626
+ const approval = () => (askMode && !acpReadOnlyKind(call.kind) ? 'ask' : 'allow');
1627
+ const name = acpToolName(call);
1628
+ const input = acpPolicyInput(call);
1629
+ const browser = browserToolDenial(acpWebTool(call) && !name.startsWith('mcp__') ? (call.name || 'search_web') : name, input, { host: 'antigravity', cwd, roots: policyRoots });
1630
+ if (browser) return { decision: 'deny', reason: browser, event: { type: 'synabun.browser_policy', action: 'refused', tool: name, reason: browser } };
1631
+ if (call.mcp && allowedServers && !allowedServers.has(call.mcp.server)) {
1632
+ const reason = `SynaBun: the MCP server ${call.mcp.server} is not one of this run's servers (${[...allowedServers].join(', ') || 'none'}), so ${call.mcp.tool} is refused. Use the tools this run was given.`;
1633
+ return { decision: 'deny', reason, event: { type: 'synabun.mcp_scope', action: 'refused', server: call.mcp.server, tool: call.mcp.tool, reason } };
1634
+ }
1635
+ const homeRead = antigravityHomeReadPolicy(call, { homeDir, cwd });
1636
+ if (homeRead) return homeRead;
1637
+ if (guard) {
1638
+ const decision = acpPermissionDecision(call, guard);
1639
+ if (decision) {
1640
+ const event = confinementEvent(decision, { tool: call.title || name, confinement: guard, provider: 'antigravity' });
1641
+ if (guard.enforced) return { decision: 'deny', reason: decision.reason, event };
1642
+ return { decision: approval(), event };
1643
+ }
1644
+ }
1645
+ return { decision: approval() };
1646
+ }
1647
+
1648
+ /** The MCP servers of a run's home: SynaBun's own entry, then the external servers its tool list names. */
1649
+ function antigravityRunServers({ tools, synabunMcpEntry, userGemini, onNote }) {
1650
+ const servers = {};
1651
+ let inventory = null;
1652
+ let names = [];
1653
+ try {
1654
+ inventory = externalServerInventory({ provider: 'antigravity', antigravityConfigPath: join(userGemini, 'config', 'mcp_config.json'), dataHome: memoryConfig.dataHome });
1655
+ names = selectExternalServers(tools.items, inventory).names;
1656
+ } catch (error) {
1657
+ if (error?.code === 'TOOLS_INVALID') throw error;
1658
+ onNote?.(`Antigravity: the external MCP servers could not be read (${error?.message || error}); this run has SynaBun's tools only.`);
1659
+ }
1660
+ const own = synabunMcpEntry || userAntigravityMcpServers(userGemini).SynaBun || null;
1661
+ if (own) servers.SynaBun = toAntigravityMcpEntry(own);
1662
+ for (const name of names) {
1663
+ const definition = inventory?.definitionFor(name);
1664
+ if (definition && name !== 'SynaBun') servers[name] = toAntigravityMcpEntry(definition);
1665
+ }
1666
+ return servers;
1667
+ }
1668
+
1669
+ export async function createAntigravityNativeLoopAdapter(options = {}) {
1670
+ const {
1671
+ runId, cwd, model = null, effort = null, launch = null,
1672
+ onIdentity = () => {}, onEvent = () => {}, onProcess = null, onNote = null,
1673
+ permissionPolicy: rawPolicy = 'auto', permissionBroker = null,
1674
+ capability: rawCapability = 'full', outputSchema = null, runMode = 'loop',
1675
+ browserSessionId = null, browserTabId = null, desktopGrantProvider = null,
1676
+ extraEnv = {}, unsetEnv = [], resumeSessionId = null,
1677
+ confinement = null,
1678
+ guardMode = WORKER_GUARD_DEFAULT, scratchDir = null, serverPort = null, localPortRange = null,
1679
+ connect = createAcpConnection,
1680
+ // The run's Antigravity home (lib/antigravity/home.js). null: agy runs in the user's own ~/.gemini, with every
1681
+ // server of theirs and no hook (an older server's wiring, a test of the bare protocol).
1682
+ homeDir = null, synabunMcpEntry = null, userGemini = userGeminiDir(),
1683
+ // The hook's way back (lib/antigravity/policy.js): the registry, the loopback address of POST /api/antigravity/policy.
1684
+ policy = null, policyUrl = null, policyHookScript = ANTIGRAVITY_POLICY_HOOK, policyRoots = [],
1685
+ // ACP counts tokens per session: what the session had counted when the run was suspended (a resume books only
1686
+ // what follows), and where each turn's count is kept for the next resume.
1687
+ sessionUsageBase = null, onSessionUsage = null,
1688
+ } = options;
1689
+ if (!launch?.command) {
1690
+ const error = new Error('The Antigravity CLI (agy) is not installed or could not be resolved.');
1691
+ error.code = 'CLI_MISSING';
1692
+ throw error;
1693
+ }
1694
+ // Skill drafting and evaluation runs hold their own confinement; Antigravity has no setup for one, and a confined
1695
+ // run never starts unconfined.
1696
+ if (confinement) throw confinementUnsupportedError('Antigravity', 'skill drafting and evaluation run on Claude Code, Codex and OpenCode', confinement?.kind === EVAL_CONFINEMENT ? 'an evaluation folder' : 'a drafting folder');
1697
+ assertSafeAntigravityProject(cwd);
1698
+ const permissionPolicy = normalizePermissionPolicy(rawPolicy);
1699
+ const capability = normalizeCapability(rawCapability);
1700
+ const guard = workerGuardFor({ capability, cwd, guardMode, scratchDir, confinement: null, serverPort, localPortRange });
1701
+ const askMode = permissionPolicy === 'ask' && typeof permissionBroker?.request === 'function';
1702
+ const tools = nativeToolSelection(options, 'antigravity');
1703
+
1704
+ // The run's home: its servers, its hook, its settings, the user's rules and skills.
1705
+ let allowedServers = null;
1706
+ let hookCommand = null;
1707
+ if (homeDir) {
1708
+ const servers = antigravityRunServers({ tools, synabunMcpEntry, userGemini, onNote });
1709
+ allowedServers = new Set(Object.keys(servers));
1710
+ if (typeof policy?.register === 'function' && policyUrl) hookCommand = policyHookCommand({ script: policyHookScript });
1711
+ else onNote?.('Antigravity: SynaBun\'s tool-call hook is not available in this server, so the browser policy and the run\'s MCP scope are checked only when Antigravity asks for approval.');
1712
+ prepareAntigravityHome({ dir: homeDir, mcpServers: servers, hookCommand, settings: antigravityHomeSettings({ userSettings: userAntigravitySettings(userGemini) }), userGemini });
1713
+ }
1714
+ const policyOptions = { homeDir, cwd, allowedServers, guard, askMode, policyRoots };
1715
+
1716
+ const pins = stringEnv({
1717
+ SYNABUN_TERMINAL_SESSION: runId,
1718
+ SYNABUN_RUN_MODE: options.runMode === 'task' ? 'task' : undefined,
1719
+ SYNABUN_PROJECT: cwd ? memoryProject(cwd) : 'global',
1720
+ SYNABUN_BROWSER_SESSION: browserSessionId,
1721
+ SYNABUN_BROWSER_TAB: browserTabId,
1722
+ SYNABUN_TOOLS: runtimeToolsValue(tools.items.includes('all') ? tools.items : [...tools.groups, ...tools.servers]),
1723
+ SYNABUN_DESKTOP_GRANT: typeof desktopGrantProvider === 'function' ? desktopGrantProvider() || undefined : undefined,
1724
+ });
1725
+ const inherited = { ...process.env };
1726
+ for (const key of ['SYNABUN_PROFILE', 'SYNABUN_RUNTIME_PROFILE_PATH', 'SYNABUN_TOOLS', 'SYNABUN_TOOLS_PATH', 'SYNABUN_TOOL_CATALOG_MODE', 'SYNABUN_ROLE', 'SYNABUN_AGY_POLICY_URL', 'SYNABUN_AGY_POLICY_TOKEN']) delete inherited[key];
1727
+ for (const key of Array.isArray(unsetEnv) ? unsetEnv : []) delete inherited[key];
1728
+ delete inherited.SYNABUN_BRAIN_TOKEN;
1729
+ for (const key of LIVE_DATA_ENV) delete inherited[key];
1730
+
1731
+ let alive = true;
1732
+ let sessionId = null;
1733
+ let providerTurn = Number(options.providerTurnBase) || 0;
1734
+ let activeTurn = null; // { iteration, providerTurn, text, controller }
1735
+ let lastSessionUsage = sessionUsageBase && typeof sessionUsageBase === 'object' ? sessionUsageBase : null;
1736
+ const openPermissions = new Set(); // resolvers of permission answers still waiting
1737
+ const startedAt = Date.now();
1738
+
1739
+ const emit = (eventType, event, extra = {}) => {
1740
+ try { onEvent({ provider: 'antigravity', runId, eventType, event, ...extra }); } catch { /* observers never break a run */ }
1741
+ };
1742
+ const turnMeta = () => (activeTurn ? { iteration: activeTurn.iteration, providerTurn: activeTurn.providerTurn } : {});
1743
+ // The hook's question (and, without a hook, the permission request's): the answer, its event in the run's events.
1744
+ const checkCall = (call) => {
1745
+ const answer = antigravityRunPolicy(call, policyOptions);
1746
+ if (answer.event) emit(answer.event.type, answer.event, turnMeta());
1747
+ return answer;
1748
+ };
1749
+ const registration = hookCommand ? policy.register((call) => (alive ? checkCall(call) : { decision: 'deny', reason: 'This run has ended.' }), { label: `run ${runId}` }) : null;
1750
+ const env = stringEnv({
1751
+ ...inherited, ...pins, ...stringEnv(extraEnv),
1752
+ ...(registration ? { SYNABUN_AGY_POLICY_URL: policyUrl, SYNABUN_AGY_POLICY_TOKEN: registration.token } : {}),
1753
+ });
1754
+
1755
+ const decidePermission = async (params, signal) => {
1756
+ const call = acpCall(params);
1757
+ const options = params?.options || [];
1758
+ const allowOnce = () => ({ outcome: { outcome: 'selected', optionId: acpPermissionOption(options, ['allow_once']) } });
1759
+ const rejectOnce = () => ({ outcome: { outcome: 'selected', optionId: acpPermissionOption(options, ['reject_once', 'reject_always']) } });
1760
+ const settle = (answer) => (answer.outcome.optionId ? answer : { outcome: { outcome: 'cancelled' } });
1761
+ if (!alive) return { outcome: { outcome: 'cancelled' } };
1762
+ // With the hook the call was checked before Antigravity asked: checked again here without reporting it twice
1763
+ // (a refusal here ends the turn, which only a call the hook never saw can earn).
1764
+ const verdict = registration ? antigravityRunPolicy(call, policyOptions) : checkCall(call);
1765
+ if (verdict.decision === 'deny') return settle(rejectOnce());
1766
+ if (askMode && !acpReadOnlyKind(call.kind)) {
1767
+ try {
1768
+ // Allowed once whatever the answer said about "always": Antigravity would save an "always" for every later
1769
+ // session of the user's, outside this run (the dispatcher drops it for Antigravity too).
1770
+ const reply = await permissionBroker.request({ runId, provider: 'antigravity', signal, kind: 'tool', toolName: call.mcp ? `${call.mcp.server}_${call.mcp.tool}` : (call.title || call.name || call.kind || 'tool'), input: params?.toolCall || {} });
1771
+ if (reply?.behavior === 'allow') return settle(allowOnce());
1772
+ return settle(rejectOnce());
1773
+ } catch {
1774
+ return settle(rejectOnce());
1775
+ }
1776
+ }
1777
+ return settle(allowOnce());
1778
+ };
1779
+
1780
+ const conn = connect({
1781
+ command: launch.command,
1782
+ args: launch.args || [],
1783
+ acpArgs: homeDir ? ['--gemini-dir', homeDir] : [],
1784
+ cwd: cwd || undefined,
1785
+ env,
1786
+ clientInfo: { name: 'synabun', title: 'SynaBun', version: '0' },
1787
+ onUpdate: (params) => {
1788
+ if (!params || params.sessionId !== sessionId || !params.update) return;
1789
+ const update = params.update;
1790
+ if (activeTurn && update.sessionUpdate === 'agent_message_chunk' && update.content?.type === 'text') activeTurn.text += update.content.text || '';
1791
+ emit(update.sessionUpdate || 'session/update', update, turnMeta());
1792
+ },
1793
+ onElicitation: async (params, { signal } = {}) => {
1794
+ if (!alive || !activeTurn || params.sessionId !== sessionId || !permissionBroker?.request) return { action: 'cancel' };
1795
+ const reply = await permissionBroker.request({ runId, provider: 'antigravity', signal: AbortSignal.any([activeTurn?.controller?.signal, signal].filter(Boolean)), kind: 'ask', toolName: 'choice', input: params, questions: workerQuestions(params), protocol: { provider: 'antigravity', method: 'elicitation/create' } });
1796
+ return elicitationReply(params, reply);
1797
+ },
1798
+ onPermission: (params, { signal } = {}) => new Promise((resolve) => {
1799
+ const done = (answer) => { openPermissions.delete(done); resolve(answer); };
1800
+ openPermissions.add(done);
1801
+ decidePermission(params, signal).then(done, () => done({ outcome: { outcome: 'cancelled' } }));
1802
+ }),
1803
+ onExit: (info) => {
1804
+ alive = false;
1805
+ for (const done of [...openPermissions]) done({ outcome: { outcome: 'cancelled' } });
1806
+ if (info?.error || (info?.code && info.code !== 0)) emit('synabun.provider_exit', { type: 'synabun.provider_exit', code: info.code, signal: info.signal, error: info.error || null });
1807
+ },
1808
+ });
1809
+ try { onProcess?.({ pid: conn.pid, startedAt, home: homeDir || null }); } catch { /* informational */ }
1810
+
1811
+ const fail = async (error) => {
1812
+ alive = false;
1813
+ registration?.dispose();
1814
+ try { await conn.close(); } catch { /* gone */ }
1815
+ throw error;
1816
+ };
1817
+ let session;
1818
+ try {
1819
+ const init = await conn.initialize({ clientCapabilities: { fs: { readTextFile: false, writeTextFile: false }, terminal: false, elicitation: { form: {} } } });
1820
+ if (resumeSessionId) {
1821
+ // session/resume picks the conversation up without replaying it; an agent without it replays (session/load),
1822
+ // which onUpdate drops (sessionId is set only once the load answered).
1823
+ const caps = init?.agentCapabilities?.sessionCapabilities || {};
1824
+ const method = caps.resume ? 'session/resume' : 'session/load';
1825
+ session = await conn.request(method, { sessionId: String(resumeSessionId), cwd, mcpServers: [] });
1826
+ sessionId = String(session?.sessionId || resumeSessionId);
1827
+ } else {
1828
+ session = await conn.request('session/new', { cwd, mcpServers: [] });
1829
+ sessionId = session.sessionId;
1830
+ }
1831
+ } catch (error) {
1832
+ const message = Number(error?.code) === -32000
1833
+ ? 'Antigravity is not signed in: open the Antigravity panel (or run `agy` in a terminal) and sign in, then try again.'
1834
+ : providerErrorMessage(error, resumeSessionId ? 'Antigravity could not resume the run\'s session' : 'Antigravity could not start a session');
1835
+ return fail(Object.assign(new Error(message), { code: Number(error?.code) === -32000 ? 'AUTH_REQUIRED' : (error?.code || 'START_FAILED') }));
1836
+ }
1837
+ onIdentity({ providerSessionId: sessionId, ...(homeDir ? { antigravityHome: homeDir } : {}) });
1838
+
1839
+ // The model and the thinking level, as the session's own config options name them. A model id with its level
1840
+ // (gemini-4-argon-high, as `agy models` and ACP's model list spell it) is that model at that level.
1841
+ let configOptions = Array.isArray(session?.configOptions) ? session.configOptions : [];
1842
+ const offered = (category) => {
1843
+ const option = configOptions.find((o) => o?.category === category);
1844
+ const accepted = [];
1845
+ for (const entry of option?.options || []) for (const v of Array.isArray(entry?.options) ? entry.options : [entry]) if (v && v.value !== undefined) accepted.push(String(v.value));
1846
+ return { option, accepted };
1847
+ };
1848
+ const setOption = async (category, value, label) => {
1849
+ if (!value) return;
1850
+ const { option, accepted } = offered(category);
1851
+ if (!option) {
1852
+ if (category === 'model') {
1853
+ try { await conn.request('session/set_model', { sessionId, modelId: String(value) }); } catch (error) { onNote?.(`Antigravity kept its default model: ${providerErrorMessage(error, 'set_model failed')}`); }
1854
+ }
1855
+ return;
1856
+ }
1857
+ if (!accepted.includes(String(value))) {
1858
+ if (category === 'model') throw Object.assign(new Error(`Antigravity does not offer the model ${value} (it offers ${accepted.join(', ') || 'none'}).`), { code: 'MODEL_UNAVAILABLE' });
1859
+ onNote?.(`Antigravity has no ${label} "${value}" for this model; it keeps ${option.currentValue}.`);
1860
+ return;
1861
+ }
1862
+ if (String(option.currentValue) === String(value)) return;
1863
+ const answer = await conn.request('session/set_config_option', { sessionId, configId: option.id, value: String(value) });
1864
+ if (Array.isArray(answer?.configOptions)) configOptions = answer.configOptions;
1865
+ };
1866
+ const wanted = splitAntigravityModel(model, offered('model').accepted);
1867
+ const level = effort && !['off', 'default', 'auto', 'none'].includes(String(effort).toLowerCase()) ? String(effort) : wanted.effort;
1868
+ try {
1869
+ await setOption('model', wanted.model, 'model');
1870
+ await setOption('thought_level', level, 'thinking level');
1871
+ } catch (error) {
1872
+ return fail(error);
1873
+ }
1874
+ const optionValue = (category) => configOptions.find((o) => o?.category === category)?.currentValue ?? null;
1875
+
1876
+ return {
1877
+ identity: () => ({ providerSessionId: sessionId, ...(homeDir ? { antigravityHome: homeDir } : {}) }),
1878
+ isAlive: () => alive && conn.running,
1879
+ processInfo: () => ({ pid: conn.pid, startedAt, home: homeDir || null }),
1880
+ describe: () => ({
1881
+ provider: 'antigravity', permissionPolicy, capability, askMode, model: optionValue('model'), effort: optionValue('thought_level'),
1882
+ ...(homeDir ? { home: homeDir, servers: [...(allowedServers || [])], hook: !!registration } : {}),
1883
+ ...(guard ? { guard: { mode: guard.mode, capability: guard.capability, writable: [...guard.writable] } } : {}),
1884
+ }),
1885
+ async runTurn(prompt, meta = {}) {
1886
+ if (!alive || !conn.running) throw new Error('Antigravity native loop is closed');
1887
+ if (activeTurn) throw new Error('Antigravity native loop already has an active turn');
1888
+ providerTurn += 1;
1889
+ const turn = { iteration: meta.iteration, providerTurn, text: '', cancelled: false, controller: new AbortController() };
1890
+ activeTurn = turn;
1891
+ emit('synabun.user_prompt', { type: 'synabun.user_prompt', text: String(prompt || '') }, { iteration: meta.iteration, providerTurn });
1892
+ const schema = meta.outputSchema || outputSchema;
1893
+ const body = schema && typeof schema === 'object'
1894
+ ? `${String(prompt || '')}\n\nEnd your reply with one JSON object (in a \`\`\`json block) that matches this JSON Schema:\n${JSON.stringify(schema)}`
1895
+ : String(prompt || '');
1896
+ try {
1897
+ const answer = await conn.request('session/prompt', { sessionId, prompt: [{ type: 'text', text: body }] }, { timeoutMs: 0 });
1898
+ // The turn's own tokens, whatever its end (a stopped turn spent them too).
1899
+ const usage = acpTurnUsage(answer?.usage, lastSessionUsage);
1900
+ if (answer?.usage) {
1901
+ lastSessionUsage = answer.usage;
1902
+ try { onSessionUsage?.(answer.usage); } catch { /* bookkeeping */ }
1903
+ }
1904
+ if (answer?.stopReason === 'cancelled' || turn.cancelled) throw Object.assign(asAbortError('aborted'), { usage, sessionUsage: answer?.usage || null, providerSessionId: sessionId });
1905
+ const text = turn.text;
1906
+ const result = { providerSessionId: sessionId, text, usage, sessionUsage: answer?.usage || null, stopReason: answer?.stopReason || 'end_turn', result: { text, usage } };
1907
+ if (answer?.stopReason === 'refusal') result.refused = true;
1908
+ if (schema && typeof schema === 'object') {
1909
+ const structured = lastJsonObject(text);
1910
+ if (structured) result.structured = structured;
1911
+ }
1912
+ return result;
1913
+ } catch (error) {
1914
+ if (error?.name === 'AbortError') throw error;
1915
+ if (!alive || !conn.running) throw new Error(`Antigravity stopped during the turn${conn.stderrTail() ? `: ${conn.stderrTail().slice(-400)}` : ''}`);
1916
+ throw new Error(providerErrorMessage(error, 'Antigravity turn failed'));
1917
+ } finally {
1918
+ turn.controller.abort();
1919
+ if (activeTurn === turn) activeTurn = null;
1920
+ }
1921
+ },
1922
+ async abort() {
1923
+ if (activeTurn) { activeTurn.cancelled = true; activeTurn.controller.abort(); }
1924
+ for (const done of [...openPermissions]) done({ outcome: { outcome: 'cancelled' } });
1925
+ try { if (sessionId && conn.running) await settleWithin(conn.notify('session/cancel', { sessionId })); } catch {}
1926
+ return true;
1927
+ },
1928
+ // The process is the run's own: it always ends with the run. Its conversation stays in the run's home, where a
1929
+ // resume after a restart finds it (the dispatcher's prune removes the home once the run is gone).
1930
+ async dispose() {
1931
+ const wasAlive = alive;
1932
+ alive = false;
1933
+ activeTurn?.controller.abort();
1934
+ for (const done of [...openPermissions]) done({ outcome: { outcome: 'cancelled' } });
1935
+ if (wasAlive && activeTurn && sessionId && conn.running) { try { await settleWithin(conn.notify('session/cancel', { sessionId })); } catch {} }
1936
+ await settleWithin(conn.close(), 3000);
1937
+ registration?.dispose();
1938
+ },
1939
+ };
1940
+ }