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
@@ -10,6 +10,7 @@ import { fork } from 'node:child_process';
10
10
  import { fileURLToPath } from 'node:url';
11
11
  import { EV, OP, UPGRADE_HEADER_WHITELIST } from './protocol.js';
12
12
  import { createPtyHostCore } from './core.js';
13
+ import { killOwnedPty } from './lifetime.js';
13
14
 
14
15
  const HOST_PATH = fileURLToPath(new URL('./host.js', import.meta.url));
15
16
  const BACKOFF_MS = [0, 1000, 2000, 4000, 8000, 16000, 30000];
@@ -39,6 +40,7 @@ export function createPtyHostManager({
39
40
  const handlers = new Map(); // id → { onSpawned, onTap, onResized, onClients, onExit }
40
41
  const spawnWaiters = new Map(); // id → waiter
41
42
  const preparing = new Map(); // id → operations queued while PATH is read
43
+ const ptyPids = new Map(); // id → live PTY PID owned by the current host
42
44
  const requests = new Map(); // reqId → { resolve, reject, timer }
43
45
  let reqSeq = 0;
44
46
  let child = null;
@@ -76,6 +78,7 @@ export function createPtyHostManager({
76
78
  releaseHeldSockets();
77
79
  break;
78
80
  case EV.SPAWNED:
81
+ ptyPids.set(m.id, m.pid);
79
82
  try { handlers.get(m.id)?.onSpawned?.(m); } catch (err) { log.warn?.('[pty-host] onSpawned', err?.message); }
80
83
  settleSpawn(m.id, null, m);
81
84
  break;
@@ -97,6 +100,7 @@ export function createPtyHostManager({
97
100
  try { handlers.get(m.id)?.onClients?.(m); } catch {}
98
101
  break;
99
102
  case EV.EXIT: {
103
+ ptyPids.delete(m.id);
100
104
  const h = handlers.get(m.id);
101
105
  handlers.delete(m.id);
102
106
  spawnWaiters.delete(m.id);
@@ -150,6 +154,11 @@ export function createPtyHostManager({
150
154
  }
151
155
 
152
156
  function onChildExit(code, signal) {
157
+ // A SIGKILL cannot run host.js's disconnect/shutdown handlers. In
158
+ // particular, a macOS helper still attaching its terminal may survive
159
+ // closing the PTY master. Reap only this host's still-owned children.
160
+ for (const pid of ptyPids.values()) killOwnedPty(pid);
161
+ ptyPids.clear();
153
162
  child = null;
154
163
  ready = false;
155
164
  hostPid = null;
@@ -358,8 +367,10 @@ export function createPtyHostManager({
358
367
  for (const id of preparing.keys()) settleSpawn(id, new Error('Terminal host is shutting down'));
359
368
  preparing.clear();
360
369
  if (restartTimer) { clearTimeout(restartTimer); restartTimer = null; }
370
+ for (const pid of ptyPids.values()) killOwnedPty(pid);
371
+ ptyPids.clear();
361
372
  try { child?.kill('SIGTERM'); } catch {}
362
- try { core?.shutdownAll(); } catch {}
373
+ try { core?.shutdownAll({ force: true }); } catch {}
363
374
  }
364
375
 
365
376
  return {
@@ -49,7 +49,7 @@ const RANK = Object.freeze({ 'read-only': 0, ask: 1, autonomous: 2 });
49
49
 
50
50
  /** Brain providers that can hold a remote session at Ask and Autonomous (the in-process hook). */
51
51
  export const REMOTE_ENFORCING_PROVIDERS = Object.freeze(['claude-code']);
52
- const PROVIDER_LABELS = Object.freeze({ 'claude-code': 'Claude', codex: 'Codex', opencode: 'OpenCode' });
52
+ const PROVIDER_LABELS = Object.freeze({ 'claude-code': 'Claude', codex: 'Codex', opencode: 'OpenCode', antigravity: 'Antigravity' });
53
53
  export function providerLabel(provider) { return PROVIDER_LABELS[provider] || String(provider || 'this brain'); }
54
54
  /** True for a known brain other than Claude: a remote session on it is read-only. */
55
55
  export function remoteBrainLimited(provider) {
@@ -96,6 +96,9 @@ export const ADMIN_ONLY_PREFIXES = Object.freeze([
96
96
  '/api/project-files',
97
97
  '/api/browse-folder',
98
98
  '/api/codex/plans',
99
+ // Antigravity's own conversations and models: each uncached answer starts the owner's `agy` (its side panel
100
+ // socket is refused to guests too).
101
+ '/api/antigravity',
99
102
  '/api/sidepanel',
100
103
  '/api/sidepanel-runs',
101
104
  '/api/loop',
@@ -41,9 +41,10 @@
41
41
 
42
42
  import { isRulesetHost, readRulesetManifest, renderCoexistenceSnippet, renderRuleset, RULESET_HOSTS } from './render.js';
43
43
 
44
- // The pre-2.0 `format` names, as the onboarding wizard and Settings still send them.
45
- const LEGACY_FORMATS = Object.freeze({ claude: 'claude', codex: 'codex', cursor: 'cursor', generic: 'opencode', opencode: 'opencode', gemini: 'gemini' });
46
- const LEGACY_VERSION_FORMATS = Object.freeze(['claude', 'codex', 'cursor', 'generic', 'gemini']);
44
+ // The pre-2.0 `format` names, as the onboarding wizard and Settings still send them. `gemini` is what a page from
45
+ // before Antigravity replaced Gemini CLI asks for: Antigravity's text, which reads the same files.
46
+ const LEGACY_FORMATS = Object.freeze({ claude: 'claude', codex: 'codex', cursor: 'cursor', generic: 'opencode', opencode: 'opencode', antigravity: 'antigravity', gemini: 'antigravity' });
47
+ const LEGACY_VERSION_FORMATS = Object.freeze(['claude', 'codex', 'cursor', 'generic', 'antigravity']);
47
48
  // A copy someone edited, damaged markers, a CLI that is not there: the request was fine, the files said no.
48
49
  const REFUSED_STATES = new Set(['not-installed', 'modified', 'conflict', 'error']);
49
50
  const REFUSED_CODES = new Set(['REMOVE_INCOMPLETE', 'CHANGED_UNDERNEATH', 'BUSY']);
@@ -133,7 +134,7 @@ export function legacyRulesetPayload(format, { render = renderRuleset } = {}) {
133
134
  const name = String(format || 'claude').toLowerCase();
134
135
  if (name === 'coexistence') return { ok: true, ruleset: renderCoexistenceSnippet().text, format: name };
135
136
  const host = LEGACY_FORMATS[name];
136
- if (!host) return { status: 400, error: `Invalid format: ${name}. Use claude, cursor, generic, gemini, codex, or coexistence.` };
137
+ if (!host) return { status: 400, error: `Invalid format: ${name}. Use claude, cursor, generic, antigravity, codex, or coexistence.` };
137
138
  return { ok: true, ruleset: render(host).text, format: name };
138
139
  }
139
140
 
@@ -155,7 +156,7 @@ export function legacyRulesetVersionsPayload() {
155
156
 
156
157
  /** The two endpoints the pre-2.0 UI reads. Same shapes as before, served from the renderer. */
157
158
  export function registerLegacyRulesetRoutes(app, { render = renderRuleset } = {}) {
158
- // Supports ?format=claude (default) | cursor | generic | gemini | codex | coexistence
159
+ // Supports ?format=claude (default) | cursor | generic | antigravity | codex | coexistence
159
160
  app.get('/api/claude-code/ruleset', (req, res) => {
160
161
  try {
161
162
  const payload = legacyRulesetPayload(req.query.format, { render });
@@ -3,7 +3,7 @@
3
3
  // ═══════════════════════════════════════════
4
4
  //
5
5
  // The rules installer edits files that belong to the user and to other CLIs
6
- // (~/.codex/AGENTS.md, ~/.gemini/GEMINI.md, OpenCode's opencode.json). These
6
+ // (~/.codex/AGENTS.md, ~/.gemini/config/GEMINI.md, OpenCode's opencode.json). These
7
7
  // helpers make each write all-or-nothing, keep a symlinked file a symlink,
8
8
  // refuse to write through a link that points at nothing, keep a copy of what
9
9
  // was there before, never read a corrupt JSON file as `{}`, and never write
@@ -8,7 +8,9 @@
8
8
  // claude owned file ~/.claude/rules/synabun.md (and each Claude account's config dir)
9
9
  // opencode owned file <opencode config dir>/synabun.md + one `instructions` entry in opencode.json
10
10
  // codex block ~/.codex/AGENTS.md (account homes link to it, or get their own block)
11
- // gemini block ~/.gemini/GEMINI.md
11
+ // antigravity block ~/.gemini/config/GEMINI.md (Antigravity's global customization root; the block an
12
+ // older SynaBun wrote for Gemini CLI in ~/.gemini/GEMINI.md, which Antigravity also
13
+ // reads, is moved here so the rules are never loaded twice)
12
14
  // cursor none User Rules live inside the app: copy and paste ("manual")
13
15
  //
14
16
  // What it will not do: create the config directory of a CLI that is not
@@ -83,11 +85,18 @@ const HOSTS = Object.freeze({
83
85
  claude: { label: 'Claude Code', carrier: 'file' },
84
86
  codex: { label: 'Codex CLI', carrier: 'block' },
85
87
  opencode: { label: 'OpenCode', carrier: 'file' },
86
- gemini: { label: 'Gemini CLI', carrier: 'block' },
88
+ // `legacyHosts`: the stamps an earlier SynaBun wrote for what this host replaced (Gemini CLI's rules, host=gemini).
89
+ antigravity: { label: 'Antigravity', carrier: 'block', legacyHosts: Object.freeze(['gemini']) },
87
90
  cursor: { label: 'Cursor', carrier: 'none' },
88
91
  });
89
92
  const MANAGED_HOSTS = Object.freeze(RULESET_HOSTS.filter((host) => HOSTS[host].carrier !== 'none'));
90
- const PROJECT_FILES = Object.freeze([['CLAUDE.md', 'claude'], ['AGENTS.md', 'codex'], ['GEMINI.md', 'gemini']]);
93
+ const PROJECT_FILES = Object.freeze([['CLAUDE.md', 'claude'], ['AGENTS.md', 'codex'], ['GEMINI.md', 'antigravity']]);
94
+ // State rows an earlier SynaBun kept under a host this one replaced: read as the new host's row (readState).
95
+ const RENAMED_HOSTS = Object.freeze({ gemini: 'antigravity' });
96
+ /** Whether a block's stamp is this host's own: its name, or one it replaced. */
97
+ function ownStamp(host, stamped) {
98
+ return stamped === host || (HOSTS[host]?.legacyHosts || []).includes(stamped);
99
+ }
91
100
  const RANK = Object.freeze({ missing: 0, current: 1, newer: 2, outdated: 3, modified: 4, conflict: 5, error: 6 });
92
101
  const MAX_SCAN_BYTES = 2 * 1024 * 1024;
93
102
 
@@ -259,6 +268,17 @@ export function createRulesetInstaller({
259
268
  return view();
260
269
  }
261
270
  if (!isPlainObject(saved)) return view();
271
+ // A row kept under a host this SynaBun replaced (Gemini CLI → Antigravity) is the new host's row, marked
272
+ // `movedFrom` until reconcile has put the rules where the new host reads them (see reconcileHosts).
273
+ for (const [old, now] of Object.entries(RENAMED_HOSTS)) {
274
+ const hosts = isPlainObject(saved.hosts) ? saved.hosts : null;
275
+ if (hosts && isPlainObject(hosts[old]) && !isPlainObject(hosts[now])) {
276
+ saved.hosts = { ...hosts, [now]: { ...hosts[old], ...(hosts[old].managed === true ? { movedFrom: old } : {}) } };
277
+ delete saved.hosts[old];
278
+ }
279
+ if (Array.isArray(saved.offerAcked)) saved.offerAcked = saved.offerAcked.map((host) => (host === old ? now : host));
280
+ if (isPlainObject(saved.notice) && Array.isArray(saved.notice.hosts)) saved.notice = { ...saved.notice, hosts: unique(saved.notice.hosts.map((host) => (host === old ? now : host))) };
281
+ }
262
282
  if (Number.isSafeInteger(saved.revision) && saved.revision > 0) state.revision = saved.revision;
263
283
  if (typeof saved.autoUpdate === 'boolean') state.autoUpdate = saved.autoUpdate;
264
284
  if (isPlainObject(saved.notice)) state.notice = saved.notice;
@@ -352,7 +372,7 @@ export function createRulesetInstaller({
352
372
 
353
373
  // ── Where things live ──────────────────────────────────────────────────────
354
374
  // Resolved the way the MCP toggle routes resolve them: Claude, Codex and
355
- // Gemini under the home directory, OpenCode in its configuration folder
375
+ // Antigravity under the home directory, OpenCode in its configuration folder
356
376
  // (OPENCODE_CONFIG_DIR, else XDG_CONFIG_HOME/opencode, else ~/.config/opencode),
357
377
  // whose opencode.json OpenCode 2 reads (lib/opencode-config.js).
358
378
  function layout() {
@@ -363,6 +383,9 @@ export function createRulesetInstaller({
363
383
  return {
364
384
  claudeDir: join(home, '.claude'),
365
385
  codexHome: join(home, '.codex'),
386
+ // Antigravity's global customization root (rules, skills, mcp_config.json), and the folder above it, where
387
+ // Gemini CLI's GEMINI.md lives (Antigravity reads that file too).
388
+ antigravityDir: join(home, '.gemini', 'config'),
366
389
  geminiDir: join(home, '.gemini'),
367
390
  opencodeDir: dirname(opencodeConfig),
368
391
  opencodeConfig,
@@ -416,7 +439,7 @@ export function createRulesetInstaller({
416
439
  ...codexAccounts().filter((account) => account.kind === 'file').map(({ path, dir }) => ({ path, dir })),
417
440
  ];
418
441
  }
419
- if (host === 'gemini') return [{ path: join(where.geminiDir, 'GEMINI.md'), dir: where.geminiDir }];
442
+ if (host === 'antigravity') return [{ path: join(where.antigravityDir, 'GEMINI.md'), dir: where.antigravityDir, legacyBlock: join(where.geminiDir, 'GEMINI.md') }];
420
443
  return [];
421
444
  }
422
445
  function projectPaths() {
@@ -428,7 +451,10 @@ export function createRulesetInstaller({
428
451
  const where = layout();
429
452
  try {
430
453
  if (host === 'claude') return !!JSON.parse(readFileSync(join(home, '.claude.json'), 'utf8'))?.mcpServers?.SynaBun;
431
- if (host === 'gemini') return !!JSON.parse(readFileSync(join(where.geminiDir, 'settings.json'), 'utf8'))?.mcpServers?.SynaBun;
454
+ if (host === 'antigravity') {
455
+ const text = readFileSync(join(where.antigravityDir, 'mcp_config.json'), 'utf8').replace(/^\uFEFF/, '');
456
+ return text.trim() ? !!JSON.parse(text)?.mcpServers?.SynaBun : false;
457
+ }
432
458
  if (host === 'codex') return /^\[mcp_servers\.SynaBun\]/m.test(readFileSync(join(where.codexHome, 'config.toml'), 'utf8'));
433
459
  if (host === 'opencode') return !!mcpServersOf(mergedOpencodeConfig(readOpencodeConfigDocuments(where.opencodeDir))).SynaBun;
434
460
  } catch { /* no config, no registration */ }
@@ -481,7 +507,7 @@ export function createRulesetInstaller({
481
507
  }
482
508
  const block = blocks[0] || null;
483
509
  // A stamp for another tool is not this host's copy, however untouched it is: never updated, never removed.
484
- if (block && block.host !== host) return { status: 'conflict', text, bytes, detail: `${foreignBlock(host, block, target.path)} Replace does not apply here: take that block out by hand, then install again.` };
510
+ if (block && !ownStamp(host, block.host)) return { status: 'conflict', text, bytes, detail: `${foreignBlock(host, block, target.path)} Replace does not apply here: take that block out by hand, then install again.` };
485
511
  if (HOSTS[host].carrier === 'file') {
486
512
  if (normalise(text) === expected.block) return { status: 'current', text, bytes, block };
487
513
  if (!block) return { status: 'modified', text, bytes, block, detail: `${target.path} has no SynaBun markers, so SynaBun treats it as your own file and never replaces it. Move or delete it, then install again.` };
@@ -756,6 +782,45 @@ export function createRulesetInstaller({
756
782
  }
757
783
  tally.changed = true;
758
784
  }
785
+ /**
786
+ * The block an earlier SynaBun wrote where this host's tool also reads rules, under a name this host replaced
787
+ * (Gemini CLI's ~/.gemini/GEMINI.md, host=gemini, which Antigravity reads beside its own ~/.gemini/config/GEMINI.md):
788
+ * once the rules are where the new host keeps them, an untouched old block goes, or the tool would load them twice.
789
+ * An edited one is the user's: it stays, with a warning. A file that held nothing else and that SynaBun created is
790
+ * deleted; any other file keeps everything but the block. Never forced.
791
+ */
792
+ function moveLegacyBlock(host, file, row, tally) {
793
+ let bytes;
794
+ try { bytes = readBytes(file); } catch { return; }
795
+ if (bytes === null) return;
796
+ const text = bytes.toString('utf8');
797
+ const { blocks, conflict } = findBlocks(text);
798
+ if (conflict) {
799
+ tally.warnings.push(`${conflict.message} in ${file}, which ${HOSTS[host].label} also reads, so SynaBun left it as it is: remove the old SynaBun marker lines by hand.`);
800
+ return;
801
+ }
802
+ const block = blocks[0];
803
+ if (!block || !ownStamp(host, block.host)) return;
804
+ if (!isPristine(block)) {
805
+ tally.warnings.push(`The SynaBun block an earlier version wrote in ${file} was edited, so it stays there. ${HOSTS[host].label} reads that file as well: take that block out by hand, or the rules are loaded twice.`);
806
+ return;
807
+ }
808
+ const next = removeBlock(text);
809
+ const created = Array.isArray(row.created) && row.created.some((path) => samePath(path, file));
810
+ try {
811
+ tally.backup(backedUp(file, host, () => {
812
+ if (created && !isSymlink(file) && withoutBom(next).trim() === '') unlinkUnchanged(file, text);
813
+ else commit(file, next, text);
814
+ }));
815
+ } catch (error) {
816
+ if (error?.code !== 'CHANGED_UNDERNEATH') throw error;
817
+ tally.warnings.push(`${file} changed while SynaBun was moving its old rules out of it, so it was left as it is.`);
818
+ return;
819
+ }
820
+ tally.changed = true;
821
+ row.paths = row.paths.filter((path) => !samePath(path, file));
822
+ row.created = row.created.filter((path) => !samePath(path, file));
823
+ }
759
824
  /**
760
825
  * One carrier. Returns the status it ended in; writes only when the copy is
761
826
  * missing, untouched and old, or (forced) one well-formed copy that was
@@ -951,7 +1016,7 @@ export function createRulesetInstaller({
951
1016
  };
952
1017
  for (const target of targets('claude')) add(target.legacyFile, 'claude', 'global');
953
1018
  for (const target of targets('codex')) add(target.path, 'codex', 'global');
954
- for (const target of targets('gemini')) add(target.path, 'gemini', 'global');
1019
+ for (const target of targets('antigravity')) { add(target.path, 'antigravity', 'global'); add(target.legacyBlock, 'antigravity', 'global'); }
955
1020
  for (const target of targets('opencode')) add(target.legacyFile, 'opencode', 'global');
956
1021
  for (const dir of projectPaths()) {
957
1022
  for (const [name, host] of PROJECT_FILES) add(join(dir, name), host, 'project');
@@ -1047,6 +1112,7 @@ export function createRulesetInstaller({
1047
1112
  }
1048
1113
  }
1049
1114
  if (host === 'opencode' && !moveOpencode1Entry(row, tally)) addOpencodeEntry(row, tally);
1115
+ for (const target of list) if (target.legacyBlock) moveLegacyBlock(host, target.legacyBlock, row, tally);
1050
1116
 
1051
1117
  Object.assign(row, {
1052
1118
  managed: true,
@@ -1181,7 +1247,7 @@ export function createRulesetInstaller({
1181
1247
  const created = new Set(row.created);
1182
1248
  // Every place a copy can be: the host's carriers, the row's paths, and what an unfinished removal recorded.
1183
1249
  const recorded = Array.isArray(row.removal?.paths) ? row.removal.paths.filter((path) => typeof path === 'string') : [];
1184
- const paths = unique([...list.map((target) => target.path), ...row.paths, ...recorded]);
1250
+ const paths = unique([...list.map((target) => target.path), ...list.map((target) => target.legacyBlock).filter(Boolean), ...row.paths, ...recorded]);
1185
1251
  /** Intent first: the paths about to go are saved before the first of them is touched. */
1186
1252
  const recordIntent = (planned) => {
1187
1253
  row.removal = { at: stamp(), error: INTERRUPTED, paths: unique(planned) };
@@ -1256,7 +1322,7 @@ export function createRulesetInstaller({
1256
1322
  if (conflict) { refuse(path, 'conflict', `${conflict.message} in ${path}. SynaBun will not guess: remove the SynaBun marker lines by hand, then remove again.`); continue; }
1257
1323
  const block = blocks[0];
1258
1324
  if (!block) continue;
1259
- if (block.host !== host) { refuse(path, 'conflict', `${foreignBlock(host, block, path)} Take that block out by hand if you no longer want it.`); continue; }
1325
+ if (!ownStamp(host, block.host)) { refuse(path, 'conflict', `${foreignBlock(host, block, path)} Take that block out by hand if you no longer want it.`); continue; }
1260
1326
  const pristine = isPristine(block);
1261
1327
  if (!force && !pristine) {
1262
1328
  left.push({ path, reason: 'edited', message: `The SynaBun block in ${path} was edited, so SynaBun left it in place and no longer manages it.` });
@@ -1413,6 +1479,19 @@ export function createRulesetInstaller({
1413
1479
  // config.json does not list any more is not moved: the user took it out, which is the opt-out below.
1414
1480
  const older = config && first.status !== 'missing' && !config.hasEntry ? opencode1Record(row) : null;
1415
1481
  const moving = older !== null && opencode1Listed(older);
1482
+ if (first.status === 'missing' && row.movedFrom && isDir(primary.dir)) {
1483
+ // A host this SynaBun replaced (Gemini CLI → Antigravity) was managed: its rules go where the new host reads
1484
+ // them, and the old block goes (installHost moves it). Not an opt-out: nothing was deleted by the user.
1485
+ const moved = installHost(state, host, { force: false }, call);
1486
+ if (moved.ok) { delete row.movedFrom; updated.push(host); to = expected.version; from = from || row.installedVersion || null; }
1487
+ states[host] = hostStatus(host, state).state;
1488
+ continue;
1489
+ }
1490
+ if (first.status === 'missing' && row.movedFrom) {
1491
+ // The new host's folder is not there yet (Antigravity not installed): nothing to move to; asked again later.
1492
+ states[host] = 'not-installed';
1493
+ continue;
1494
+ }
1416
1495
  if (first.status === 'missing' || (config && !config.hasEntry && !moving)) {
1417
1496
  // The user deleted it: an opt-out, never a reason to put it back. (A copy SynaBun's own removal took is
1418
1497
  // not this case: that removal is on record and was dealt with above.)
@@ -18,7 +18,7 @@ import { normalise, sha12 } from './normalise.js';
18
18
 
19
19
  export { normalise, sha12 };
20
20
 
21
- export const RULESET_HOSTS = Object.freeze(['claude', 'codex', 'opencode', 'gemini', 'cursor']);
21
+ export const RULESET_HOSTS = Object.freeze(['claude', 'codex', 'opencode', 'antigravity', 'cursor']);
22
22
  export const RULESETS_DIR = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..', 'templates', 'rulesets');
23
23
 
24
24
  const HOST_SLOT = '<!-- host -->';
@@ -3,7 +3,7 @@ import { normalizeMemoryEvents, ingestMemoryEvents } from '../../mcp-server/dist
3
3
  import { detectProject } from '../../mcp-server/dist/config.js';
4
4
  /**
5
5
  * Session Cache Builder — populates session_cache + session_fts for
6
- * Claude Code, Codex CLI, and OpenCode so the Resume surfaces can do
6
+ * Claude Code, Codex CLI, OpenCode and Antigravity so the Resume surfaces can do
7
7
  * full-body keyword search via FTS5.
8
8
  *
9
9
  * Per-provider rebuilders walk the provider's session storage, extract
@@ -25,6 +25,7 @@ import {
25
25
  import { parseLine } from './session-chunker.js';
26
26
  import { listCodexSessionsFromAccounts } from './codex-session-catalog.js';
27
27
  import { OPENCODE_SESSION_TABLES, hasOpencodeSessionTables, v2MessageText } from './opencode-db-schema.js';
28
+ import { listAntigravityConversations, parseAntigravityTranscript, readTranscriptText } from './antigravity/transcripts.js';
28
29
 
29
30
  const MAX_BODY = 256 * 1024;
30
31
  const MAX_MSG_CHARS = 8 * 1024;
@@ -493,6 +494,68 @@ export function rebuildOpencodeCache({ getOpencodeDb, onProgress } = {}) {
493
494
  return { sessions: total, updated };
494
495
  }
495
496
 
497
+ // --- Antigravity ---
498
+
499
+ // agy keeps one transcript.jsonl per conversation (lib/antigravity/transcripts.js). Every conversation it holds,
500
+ // SynaBun's brain and run homes included, is queued for memory capture; the user's own (the panel's and a terminal
501
+ // agy's, the only ones the panel can load again) also go into the Resume index, titled as Antigravity titles them.
502
+ // A conversation unchanged since the last pass (size, time, folder, title) is not read again. `only`: one
503
+ // conversation (a panel turn just ended), the others left as they are. Request/timer paths read at most
504
+ // MAX_BODY bytes per transcript: the index is bounded; full transcripts remain available to memory capture.
505
+ export function rebuildAntigravityCache({ stores = [], only = null, onProgress, list = listAntigravityConversations, read = readTranscriptText } = {}) {
506
+ const emit = onProgress || (() => {});
507
+ let conversations;
508
+ try { conversations = list(stores); } catch { return { sessions: 0, updated: 0 }; }
509
+ if (only) conversations = conversations.filter((c) => c.sessionId === only);
510
+ const listed = new Set(conversations.filter((c) => c.store === 'user').map((c) => c.sessionId));
511
+ const total = listed.size;
512
+ let updated = 0;
513
+ for (const c of conversations) {
514
+ try {
515
+ queueMemoryCapture({ host: 'antigravity', sessionId: c.sessionId, filePath: c.transcript, cwd: c.cwd || undefined, project: c.cwd ? detectProject(c.cwd) : 'global', size: c.size });
516
+ } catch { /* memory capture is best effort */ }
517
+ if (c.store !== 'user') continue;
518
+ const mtimeIso = new Date(c.mtimeMs).toISOString();
519
+ const existing = getSessionCacheEntry(c.sessionId, 'antigravity');
520
+ if (existing && !existing.deleted && Number(existing.file_size) === c.size && existing.file_mtime === mtimeIso
521
+ && (existing.project_path || null) === (c.cwd || null) && (!c.title || existing.first_prompt === c.title)) continue;
522
+ const parsed = parseAntigravityTranscript(read(c.transcript, { maxBytes: MAX_BODY }));
523
+ let body = '';
524
+ for (const message of parsed.messages) {
525
+ if (message.role !== 'user' && message.role !== 'assistant') continue;
526
+ if (message.text) body = appendBody(body, message.text.slice(0, MAX_MSG_CHARS));
527
+ }
528
+ const firstPrompt = c.title || parsed.firstPrompt;
529
+ const project = c.cwd ? basename(c.cwd) : null;
530
+ upsertSessionCache({
531
+ session_id: c.sessionId,
532
+ provider: 'antigravity',
533
+ project,
534
+ project_path: c.cwd,
535
+ git_branch: null,
536
+ first_prompt: firstPrompt,
537
+ message_count: parsed.messageCount,
538
+ created: parsed.created,
539
+ modified: c.updatedAt || parsed.modified || mtimeIso,
540
+ file_path: c.transcript,
541
+ file_size: c.size,
542
+ file_mtime: mtimeIso,
543
+ body_size: body.length,
544
+ deleted: 0,
545
+ });
546
+ upsertSessionFts({ session_id: c.sessionId, provider: 'antigravity', project, first_prompt: firstPrompt, body });
547
+ updated++;
548
+ emit({ type: 'cache:session', provider: 'antigravity', sessionId: c.sessionId, done: updated, total });
549
+ }
550
+ // A conversation Antigravity no longer has (deleted there, or its folder cleared) leaves Resume.
551
+ if (!only) {
552
+ for (const cached of listSessionCache('antigravity', { limit: 100000 })) {
553
+ if (!listed.has(cached.session_id) && !cached.deleted) markSessionDeleted(cached.session_id, 'antigravity', true);
554
+ }
555
+ }
556
+ return { sessions: total, updated };
557
+ }
558
+
496
559
  // --- Single-session refresh (for piggyback from session-indexer) ---
497
560
 
498
561
  export function updateSessionCacheEntry(provider, sessionId, opts = {}) {
@@ -178,8 +178,12 @@ export function createSkillsCatalog({ roots, readHead, describe, ownership = ()
178
178
  });
179
179
  }
180
180
  } else {
181
+ // Antigravity's rule for two skills of one name (its folder and a project's .agents/skills) is not known here.
182
+ const why = runtime === 'antigravity'
183
+ ? 'Antigravity sees more than one skill of this name; which one it uses is not certain. Give them different names.'
184
+ : `${runtime === 'codex' ? 'Codex' : 'OpenCode'} lists every skill of this name; which one the model picks is not fixed. Give them different names.`;
181
185
  for (const e of list) {
182
- (e.collisions ||= []).push({ runtime, with: list.filter((x) => x !== e).map((x) => x.id), outcome: 'both-listed', why: `${runtime === 'codex' ? 'Codex' : 'OpenCode'} lists every skill of this name; which one the model picks is not fixed. Give them different names.` });
186
+ (e.collisions ||= []).push({ runtime, with: list.filter((x) => x !== e).map((x) => x.id), outcome: 'both-listed', why });
183
187
  }
184
188
  }
185
189
  }
@@ -82,7 +82,7 @@ const literal = (value, max) => { const s = String(value ?? ''); return JSON.str
82
82
  */
83
83
  export function buildDraftingBrief({ jobId, request, sources = [] }) {
84
84
  const name = request?.name || '';
85
- const runtimes = [...new Set((request?.targets || []).map((t) => ({ claude: 'Claude Code', codex: 'Codex', opencode: 'OpenCode', agents: 'Codex and OpenCode (shared folder)' }[t.runtime])).filter(Boolean))];
85
+ const runtimes = [...new Set((request?.targets || []).map((t) => ({ claude: 'Claude Code', codex: 'Codex', opencode: 'OpenCode', antigravity: 'Antigravity', agents: 'Codex and OpenCode (shared folder)' }[t.runtime])).filter(Boolean))];
86
86
  const prompt = request?.prompt ? quoteBounded(request.prompt, PROMPT_IN_BRIEF) : null;
87
87
  const example = request?.example ? quoteBounded(request.example, EXAMPLE_IN_BRIEF) : null;
88
88
  const lines = [
@@ -1,6 +1,6 @@
1
1
  // ── AI skill creation: the sources a draft is made from (P3) ──
2
2
  // What lib/skills/creation.js puts in a drafting job's workspace (`sources/`) before the worker starts: one session
3
- // transcript of Claude Code, Codex or OpenCode, SynaBun memories, a read-only snapshot of a registered project, the
3
+ // transcript of Claude Code, Codex, OpenCode or Antigravity, SynaBun memories, a read-only snapshot of a registered project, the
4
4
  // files of an existing skill. Every file opens with a line saying it is untrusted data, secrets are taken out
5
5
  // (lib/log-redact.js: before anything is cut short, so half a secret never survives), and every source has a
6
6
  // budget: what does not fit is cut, and the manifest says so. A web page is read by the server with the SynaBun
@@ -91,6 +91,24 @@ export function opencodeRowsToMessages(rows) {
91
91
  return out;
92
92
  }
93
93
 
94
+ /**
95
+ * Antigravity's transcript as lib/antigravity/transcripts.js parses it (parseAntigravityTranscript().messages:
96
+ * { role: user | assistant | tool_result, text, tools }) → messages: the person's words, the model's answers and
97
+ * the tools it called (never its reasoning), and what the tools gave back.
98
+ */
99
+ export function antigravityMessagesToMessages(messages) {
100
+ const out = [];
101
+ for (const m of Array.isArray(messages) ? messages : []) {
102
+ if (!m || typeof m !== 'object') continue;
103
+ if (m.role === 'user' && typeof m.text === 'string' && m.text.trim()) out.push({ role: 'user', text: m.text });
104
+ else if (m.role === 'assistant') {
105
+ if (typeof m.text === 'string' && m.text.trim()) out.push({ role: 'assistant', text: m.text });
106
+ for (const t of Array.isArray(m.tools) ? m.tools : []) out.push({ role: 'tool', tool: String(t?.name || 'tool'), text: safeJson(t?.input) });
107
+ } else if (m.role === 'tool_result' && typeof m.text === 'string' && m.text.trim()) out.push({ role: 'tool_result', text: m.text, error: m.error === true });
108
+ }
109
+ return out;
110
+ }
111
+
94
112
  function safeJson(value) {
95
113
  try { return typeof value === 'string' ? value : JSON.stringify(value ?? {}); } catch { return ''; }
96
114
  }
@@ -1,8 +1,9 @@
1
1
  // ── Installing library revisions into runtime folders ──
2
2
  // An install puts one revision of a library skill (lib/skills/library.js) into one or more runtime folders as a
3
3
  // native skill folder: Claude Code (<config dir>/skills or a project's .claude/skills), Codex ($CODEX_HOME/skills
4
- // or a project's .agents/skills), OpenCode (<config>/opencode/skills or a project's .opencode/skills), and the
5
- // shared ~/.agents/skills only when the owner turned it on. It is two steps:
4
+ // or a project's .agents/skills, which Antigravity reads too), OpenCode (<config>/opencode/skills or a project's
5
+ // .opencode/skills), Antigravity (~/.gemini/config/skills), and the shared ~/.agents/skills only when the owner
6
+ // turned it on. It is two steps:
6
7
  //
7
8
  // plan(…) what each target would receive (the export of the revision for that runtime, with its digest),
8
9
  // where, which runtimes see that folder (and what else of that name they already see), what is there
@@ -39,7 +40,7 @@ import { PORTABLE_NAME_RE, TARGET_PROFILE, getProfile, lintForProfile } from '..
39
40
  import { analyzeResources, argumentDiagnostics } from '../../public/shared/skills/ss-resources.js';
40
41
  import { isEditableText } from '../../public/shared/skills/ss-doc.js';
41
42
 
42
- export const INSTALL_TARGETS = Object.freeze(['claude', 'codex', 'opencode', 'agents']);
43
+ export const INSTALL_TARGETS = Object.freeze(['claude', 'codex', 'opencode', 'antigravity', 'agents']);
43
44
  export const INSTALLATIONS_SCHEMA = 2;
44
45
  const MAX_INSTALLATIONS = 2000;
45
46
 
@@ -66,9 +67,11 @@ const REFRESH = {
66
67
  opencode: (rootExisted, root) => (root?.family === 'claude'
67
68
  ? { kind: 'terminal-only', message: 'An OpenCode open in a terminal reads Claude Code\'s folder (new session); SynaBun\'s own OpenCode panel and an OpenCode Assistant do not: install for OpenCode for those.' }
68
69
  : { kind: 'panel-reload', message: 'SynaBun\'s OpenCode panel lists it now; an OpenCode open in a terminal needs a new session.' }),
70
+ // Antigravity reads its skill folders again by itself: there is no command to run.
71
+ antigravity: () => ({ kind: 'automatic', message: 'Antigravity picks up the change by itself.' }),
69
72
  };
70
73
 
71
- const RUNTIME_LABEL = { claude: 'Claude Code', codex: 'Codex', opencode: 'OpenCode', agents: 'the shared agents folder' };
74
+ const RUNTIME_LABEL = { claude: 'Claude Code', codex: 'Codex', opencode: 'OpenCode', antigravity: 'Antigravity', agents: 'the shared agents folder' };
72
75
 
73
76
  /** A target as a request may name it, checked. */
74
77
  export function normalizeTarget(t) {
@@ -78,6 +81,8 @@ export function normalizeTarget(t) {
78
81
  const scope = t.scope === undefined ? 'user' : t.scope;
79
82
  if (scope !== 'user' && scope !== 'project') throw new DeployError(400, 'A target\'s scope is user or project.', 'invalid_target');
80
83
  if (runtime === 'agents' && scope !== 'user') throw new DeployError(400, 'The shared agents folder is a user folder; a project\'s .agents/skills is the codex target with scope project.', 'invalid_target');
84
+ // Antigravity has no project folder of its own: it reads the project's .agents/skills, the Codex project folder.
85
+ if (runtime === 'antigravity' && scope !== 'user') throw new DeployError(400, 'Antigravity reads a project\'s .agents/skills: that is the codex target with scope project.', 'invalid_target');
81
86
  const accountId = t.accountId === undefined || t.accountId === null || t.accountId === '' ? 'default' : t.accountId;
82
87
  if (typeof accountId !== 'string' || !/^[A-Za-z0-9_-]{1,64}$/.test(accountId)) throw new DeployError(400, 'Not an account id.', 'invalid_target');
83
88
  if (scope === 'project' && (typeof t.projectPath !== 'string' || !t.projectPath)) throw new DeployError(400, 'A project target names its project (projectPath).', 'invalid_target');
@@ -139,7 +139,9 @@ export function draftConfinementSupport(provider, { platform = process.platform
139
139
  };
140
140
  }
141
141
  if (name === 'opencode') return { ok: true, level: 'rules', note: 'OpenCode with its shell, web, subagent and skill tools switched off, no MCP server, and every edit answered inside its folder only.' };
142
- return { ok: false, reason: `${name || 'That provider'} cannot be held to a drafting folder.` };
142
+ // Antigravity (a worker of the Assistant) has no drafting confinement in SynaBun: a drafting run never starts unconfined.
143
+ if (name === 'antigravity') return { ok: false, reason: 'Antigravity cannot be held to a drafting folder: skill drafting runs on Claude Code, Codex and OpenCode.' };
144
+ return { ok: false, reason: `${name || 'That provider'} cannot be held to a drafting folder: skill drafting runs on Claude Code, Codex and OpenCode.` };
143
145
  }
144
146
 
145
147
  /** The MCP server names a Codex config.toml declares ([mcp_servers.NAME] tables), for disabling them one by one. */
@@ -96,7 +96,9 @@ export function evalConfinementSupport(provider, { platform = process.platform }
96
96
  if (name === 'claude-code') return { ok: true, level: 'strict', shell: platform === 'darwin' ? 'sandbox' : 'never', optIn: false, note: 'Read, Write, Edit, Glob, Grep and Skill inside its folder; no settings file, hook or plugin but the skill under test; a shell only under the evaluation\'s macOS sandbox (no network, no home folder); no web, no MCP tools, no subagents.' };
97
97
  if (name === 'codex') return { ok: true, level: 'sandbox', shell: 'own-sandbox', optIn: true, note: `Codex keeps its shell under its ${platform === 'win32' ? 'Windows' : platform === 'darwin' ? 'Seatbelt' : 'Linux'} sandbox: writes inside its folder only, no network, no web search, no MCP server; its shell can read files anywhere on this computer, and what it reads can reach its model provider.` };
98
98
  if (name === 'opencode') return { ok: true, level: 'rules', shell: platform === 'darwin' ? 'sandbox' : 'never', optIn: false, note: 'OpenCode with no project configuration or plugin of the folder, its web, subagent and todo tools off, no MCP server, reads and edits inside its folder only; a shell only under the evaluation\'s macOS sandbox (no network, no home folder).' };
99
- return { ok: false, reason: `${name || 'That provider'} cannot be held to an evaluation folder.` };
99
+ // Antigravity (a worker of the Assistant) has no evaluation confinement in SynaBun: an evaluation never runs unconfined.
100
+ if (name === 'antigravity') return { ok: false, reason: 'Antigravity cannot be held to an evaluation folder: skill evaluations run on Claude Code, Codex and OpenCode.' };
101
+ return { ok: false, reason: `${name || 'That provider'} cannot be held to an evaluation folder: skill evaluations run on Claude Code, Codex and OpenCode.` };
100
102
  }
101
103
 
102
104
  /**
@@ -60,6 +60,22 @@ export function toolCallsOf(payload = {}) {
60
60
  }
61
61
  return out;
62
62
  }
63
+ if (payload.provider === 'antigravity') {
64
+ // An ACP tool call (session/update tool_call, then tool_call_update with its status). Antigravity names its
65
+ // tool (read_file, run_command, call_mcp_tool …) and its arguments in its own words: the path a read names
66
+ // and a command's line are kept under `path` / `command`; an MCP call is <server>.<tool>.
67
+ if ((eventType === 'tool_call' || eventType === 'tool_call_update') && event.toolCallId) {
68
+ const raw = isObject(event.rawInput) ? event.rawInput : {};
69
+ const mcp = String(event.name || '') === 'call_mcp_tool' && raw.ServerName && raw.ToolName ? `${raw.ServerName}.${raw.ToolName}` : '';
70
+ const location = Array.isArray(event.locations) ? event.locations.find((l) => typeof l?.path === 'string' && l.path)?.path : '';
71
+ const path = [raw.AbsolutePath, raw.Path, raw.TargetFile, raw.path, raw.file_path, location].find((v) => typeof v === 'string' && v) || '';
72
+ const command = typeof raw.CommandLine === 'string' ? raw.CommandLine : typeof raw.command === 'string' ? raw.command : '';
73
+ const input = { ...(path ? { path } : {}), ...(command ? { command } : {}) };
74
+ const status = String(event.status || '');
75
+ out.push({ id: String(event.toolCallId), tool: mcp || String(event.name || event.kind || ''), input, phase: status, ok: status === 'completed' ? true : status === 'failed' ? false : null });
76
+ }
77
+ return out;
78
+ }
63
79
  return out;
64
80
  }
65
81
 
@@ -7,7 +7,10 @@
7
7
  // - Claude Code: `disable-model-invocation: true` / `user-invocable: false` in SKILL.md (that key only);
8
8
  // - Codex: `policy.allow_implicit_invocation: false` in agents/openai.yaml (made when there is none; otherwise
9
9
  // that one value is set and every other value, comment and order is kept);
10
- // - OpenCode: metadata `opencode/autoinvoke: "false"` in SKILL.md (the metadata key only).
10
+ // - OpenCode: metadata `opencode/autoinvoke: "false"` in SKILL.md (the metadata key only);
11
+ // - Antigravity: `disable-slash-command: true` in SKILL.md (that key only) for a skill the person may not start by
12
+ // name. Antigravity has no setting that keeps its model from starting a skill: an export of an explicit-only
13
+ // skill for it is refused (L-08-invocation-unrepresentable), never written wider.
11
14
  // A Codex export also carries the harness metadata Codex shows in its skill picker (agents/openai.yaml
12
15
  // `interface`: display_name, short_description, default_prompt, from the skill's own name and description; the
13
16
  // 02-standards-runtimes report, §6.3). A sidecar that is already there keeps every value it has: only the interface
@@ -140,7 +143,7 @@ export function buildExport({ files, dirs = [] }, profileId) {
140
143
  }
141
144
  const next = patchHead(headText, changes);
142
145
  if (next === null) {
143
- diagnostics.push({ ruleId: 'L-08-adapt-failed', severity: 'error', message: `The SKILL.md frontmatter could not be changed to keep ${profile?.title || profileId} from ${t.key === 'user-invocable' ? 'showing it to the person' : 'starting it on its own'}: fix the YAML first.`, file: 'SKILL.md', line: null });
146
+ diagnostics.push({ ruleId: 'L-08-adapt-failed', severity: 'error', message: `The SKILL.md frontmatter could not be changed to keep ${profile?.title || profileId} from ${t.key === 'user-invocable' || t.key === 'disable-slash-command' ? 'showing it to the person' : 'starting it on its own'}: fix the YAML first.`, file: 'SKILL.md', line: null });
144
147
  continue;
145
148
  }
146
149
  headText = next;