synabun 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (317) hide show
  1. package/CHANGELOG.md +121 -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 +501 -39
  71. package/neural-interface/i18n/pt-BR.json +501 -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-budget-planner.js +7 -3
  88. package/neural-interface/lib/assistant-budget-split.js +42 -15
  89. package/neural-interface/lib/assistant-catalog.js +76 -9
  90. package/neural-interface/lib/assistant-config.js +6 -3
  91. package/neural-interface/lib/assistant-dispatch.js +207 -49
  92. package/neural-interface/lib/assistant-envelope.js +153 -1
  93. package/neural-interface/lib/assistant-evidence.js +80 -0
  94. package/neural-interface/lib/assistant-hidden-models.js +7 -5
  95. package/neural-interface/lib/assistant-persona.js +16 -11
  96. package/neural-interface/lib/assistant-plan-permissions.js +15 -0
  97. package/neural-interface/lib/assistant-router.js +237 -35
  98. package/neural-interface/lib/assistant-runtime.js +158 -12
  99. package/neural-interface/lib/assistant-task-prompt.js +10 -8
  100. package/neural-interface/lib/assistant-tools.js +2 -2
  101. package/neural-interface/lib/assistant-usage.js +29 -0
  102. package/neural-interface/lib/backup-service.js +6 -1
  103. package/neural-interface/lib/browser-guard-cdp.js +5 -4
  104. package/neural-interface/lib/browser-network-guard.js +274 -82
  105. package/neural-interface/lib/browser-tool-policy.js +12 -7
  106. package/neural-interface/lib/claude-agent-bridge.js +7 -0
  107. package/neural-interface/lib/claude-history-cache.js +210 -0
  108. package/neural-interface/lib/claude-model-catalog.js +5 -1
  109. package/neural-interface/lib/cli-install-api.js +1 -1
  110. package/neural-interface/lib/cli-install.js +160 -31
  111. package/neural-interface/lib/cli-resolver.js +9 -2
  112. package/neural-interface/lib/cli-update-planner.js +25 -15
  113. package/neural-interface/lib/codex-rollout-reader.js +52 -0
  114. package/neural-interface/lib/codex-worker-app-server.js +25 -6
  115. package/neural-interface/lib/editor-git-api.js +3 -20
  116. package/neural-interface/lib/exec-loop-command.js +9 -2
  117. package/neural-interface/lib/file-explorer-api.js +74 -40
  118. package/neural-interface/lib/file-explorer-watch.js +9 -3
  119. package/neural-interface/lib/git-bash/install.js +52 -6
  120. package/neural-interface/lib/git-bash/manager.js +59 -17
  121. package/neural-interface/lib/git-bash/pins.js +12 -0
  122. package/neural-interface/lib/git-read.js +2 -2
  123. package/neural-interface/lib/git-status-runner.js +390 -0
  124. package/neural-interface/lib/git-status-watchdog.js +40 -0
  125. package/neural-interface/lib/greeting/budgets.js +1 -0
  126. package/neural-interface/lib/greeting/engine.js +2 -2
  127. package/neural-interface/lib/greeting/sources.js +10 -12
  128. package/neural-interface/lib/greeting/writer.js +1 -1
  129. package/neural-interface/lib/mcp-client-config.js +37 -6
  130. package/neural-interface/lib/mcp-external-servers.js +13 -4
  131. package/neural-interface/lib/mcp-installer.js +2 -1
  132. package/neural-interface/lib/memory-session-capture.js +17 -3
  133. package/neural-interface/lib/morelogin.js +147 -25
  134. package/neural-interface/lib/native-loop-providers.js +420 -21
  135. package/neural-interface/lib/native-loop-runtime.js +23 -2
  136. package/neural-interface/lib/opencode-native-boundary.js +11 -2
  137. package/neural-interface/lib/owner-authority.js +1 -1
  138. package/neural-interface/lib/path-confine.js +12 -9
  139. package/neural-interface/lib/peer-identity.js +32 -2
  140. package/neural-interface/lib/pty-host/core.js +28 -1
  141. package/neural-interface/lib/pty-host/lifetime.js +12 -0
  142. package/neural-interface/lib/pty-host/manager.js +12 -1
  143. package/neural-interface/lib/remote-policy.js +1 -1
  144. package/neural-interface/lib/request-gate.js +3 -0
  145. package/neural-interface/lib/rulesets/api.js +6 -5
  146. package/neural-interface/lib/rulesets/fs-safe.js +1 -1
  147. package/neural-interface/lib/rulesets/installer.js +89 -10
  148. package/neural-interface/lib/rulesets/render.js +1 -1
  149. package/neural-interface/lib/session-cache-builder.js +64 -1
  150. package/neural-interface/lib/skills/catalog.js +5 -1
  151. package/neural-interface/lib/skills/creation-brief.js +1 -1
  152. package/neural-interface/lib/skills/creation-sources.js +19 -1
  153. package/neural-interface/lib/skills/deploy.js +9 -4
  154. package/neural-interface/lib/skills/draft-confinement.js +3 -1
  155. package/neural-interface/lib/skills/eval-confinement.js +3 -1
  156. package/neural-interface/lib/skills/eval-trace.js +16 -0
  157. package/neural-interface/lib/skills/export.js +5 -2
  158. package/neural-interface/lib/skills/known-bridge-digests.json +58 -4
  159. package/neural-interface/lib/skills/ownership.js +1 -1
  160. package/neural-interface/lib/skills/roots.js +10 -3
  161. package/neural-interface/lib/skills/runtime-checks.js +6 -3
  162. package/neural-interface/lib/skills/service.js +15 -7
  163. package/neural-interface/lib/skills/telemetry.js +6 -0
  164. package/neural-interface/lib/synabun-version.js +1 -1
  165. package/neural-interface/lib/system-activity.js +1 -1
  166. package/neural-interface/lib/whatsapp/brain.js +7 -3
  167. package/neural-interface/lib/whatsapp/cards.js +22 -9
  168. package/neural-interface/lib/whatsapp/config.js +1 -1
  169. package/neural-interface/lib/worker-confinement.js +77 -1
  170. package/neural-interface/lib/worker-question.js +74 -0
  171. package/neural-interface/lib/worker-resume.js +13 -4
  172. package/neural-interface/lib/worker-sandbox.js +46 -16
  173. package/neural-interface/package-lock.json +18 -2
  174. package/neural-interface/package.json +2 -1
  175. package/neural-interface/public/onboarding.html +1 -1
  176. package/neural-interface/public/shared/agent-runtime-options.js +89 -11
  177. package/neural-interface/public/shared/agy/agy-bypass.js +47 -0
  178. package/neural-interface/public/shared/agy/agy-client.js +266 -0
  179. package/neural-interface/public/shared/agy/agy-commands.js +120 -0
  180. package/neural-interface/public/shared/agy/agy-copy.js +326 -0
  181. package/neural-interface/public/shared/agy/agy-diff.js +161 -0
  182. package/neural-interface/public/shared/agy/agy-events.js +469 -0
  183. package/neural-interface/public/shared/agy/agy-greeting.js +26 -0
  184. package/neural-interface/public/shared/agy/agy-options.js +232 -0
  185. package/neural-interface/public/shared/agy/agy-panel.js +2543 -0
  186. package/neural-interface/public/shared/agy/agy-render.js +392 -0
  187. package/neural-interface/public/shared/agy/agy-setup.js +149 -0
  188. package/neural-interface/public/shared/agy/agy-styles.js +604 -0
  189. package/neural-interface/public/shared/agy/agy-tabs-logic.js +181 -0
  190. package/neural-interface/public/shared/api.js +4 -4
  191. package/neural-interface/public/shared/assistant/asst-agents-dock.js +3 -4
  192. package/neural-interface/public/shared/assistant/asst-api.js +1 -1
  193. package/neural-interface/public/shared/assistant/asst-brain-picker.js +56 -9
  194. package/neural-interface/public/shared/assistant/asst-budget.js +5 -4
  195. package/neural-interface/public/shared/assistant/asst-control.js +48 -0
  196. package/neural-interface/public/shared/assistant/asst-hosts.js +16 -7
  197. package/neural-interface/public/shared/assistant/asst-limit-dock.js +6 -1
  198. package/neural-interface/public/shared/assistant/asst-limit-wait.js +38 -5
  199. package/neural-interface/public/shared/assistant/asst-panel.js +45 -8
  200. package/neural-interface/public/shared/assistant/asst-render.js +51 -12
  201. package/neural-interface/public/shared/assistant/asst-route.js +237 -57
  202. package/neural-interface/public/shared/assistant/asst-routes-editor.js +9 -8
  203. package/neural-interface/public/shared/assistant/asst-scenes.js +54 -1
  204. package/neural-interface/public/shared/assistant/asst-sidepanel.js +51 -7
  205. package/neural-interface/public/shared/assistant/asst-slash.js +8 -2
  206. package/neural-interface/public/shared/assistant/asst-state.js +76 -9
  207. package/neural-interface/public/shared/assistant/asst-styles.js +34 -0
  208. package/neural-interface/public/shared/assistant/asst-tool-kinds.js +34 -7
  209. package/neural-interface/public/shared/assistant/asst-usage.js +2 -1
  210. package/neural-interface/public/shared/cdx/cdx-output.js +26 -3
  211. package/neural-interface/public/shared/cdx/cdx-panel.js +14 -2
  212. package/neural-interface/public/shared/cdx/cdx-preview.js +12 -0
  213. package/neural-interface/public/shared/cdx/cdx-protocol.js +11 -10
  214. package/neural-interface/public/shared/cdx/cdx-render.js +10 -9
  215. package/neural-interface/public/shared/cdx/cdx-requests.js +11 -1
  216. package/neural-interface/public/shared/cdx/cdx-styles.js +4 -0
  217. package/neural-interface/public/shared/cdx/cdx-tabs.js +75 -5
  218. package/neural-interface/public/shared/cli-install-controller.js +218 -0
  219. package/neural-interface/public/shared/cli-install-view.js +7 -4
  220. package/neural-interface/public/shared/cli-status.js +9 -8
  221. package/neural-interface/public/shared/constants.js +3 -2
  222. package/neural-interface/public/shared/context-menu-model.js +1 -0
  223. package/neural-interface/public/shared/cp/cp-restore.js +162 -0
  224. package/neural-interface/public/shared/cp/cp-tool-cards.js +10 -3
  225. package/neural-interface/public/shared/desktop-bridge.js +17 -0
  226. package/neural-interface/public/shared/desktop-titlebar-view.js +12 -0
  227. package/neural-interface/public/shared/desktop-titlebar.js +13 -4
  228. package/neural-interface/public/shared/desktop-update-view.js +1 -0
  229. package/neural-interface/public/shared/explorer/fx-api.js +4 -4
  230. package/neural-interface/public/shared/explorer/fx-links.js +3 -3
  231. package/neural-interface/public/shared/explorer/fx-logic.js +1 -1
  232. package/neural-interface/public/shared/explorer/fx-main.js +48 -16
  233. package/neural-interface/public/shared/explorer/fx-tree.js +1 -0
  234. package/neural-interface/public/shared/greeting/greeting-card.js +2 -2
  235. package/neural-interface/public/shared/greeting/greeting-view.js +1 -0
  236. package/neural-interface/public/shared/html-shell.js +21 -4
  237. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +27 -7
  238. package/neural-interface/public/shared/ocp-v2/ocp-v2-rehydrate.js +13 -4
  239. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +7 -1
  240. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +17 -0
  241. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +46 -3
  242. package/neural-interface/public/shared/provider-icons.js +4 -2
  243. package/neural-interface/public/shared/settings/settings-rules.js +2 -2
  244. package/neural-interface/public/shared/settings/settings-skill-folders-pane.js +1 -1
  245. package/neural-interface/public/shared/settings/settings-skill-folders.js +2 -2
  246. package/neural-interface/public/shared/showcase-privacy.js +6 -1
  247. package/neural-interface/public/shared/sidepanel-session-status.js +3 -3
  248. package/neural-interface/public/shared/skills/ss-creation.js +6 -3
  249. package/neural-interface/public/shared/skills/ss-dialog.js +5 -2
  250. package/neural-interface/public/shared/skills/ss-lint.js +9 -1
  251. package/neural-interface/public/shared/skills/ss-profiles.js +44 -6
  252. package/neural-interface/public/shared/skills/ss-review-view.js +2 -2
  253. package/neural-interface/public/shared/skills/studio/sx-actions.js +1 -1
  254. package/neural-interface/public/shared/skills/studio/sx-create-ai-view.js +1 -1
  255. package/neural-interface/public/shared/skills/studio/sx-create-ai.js +2 -1
  256. package/neural-interface/public/shared/skills/studio/sx-dom.js +2 -1
  257. package/neural-interface/public/shared/skills/studio/sx-fixes-view.js +359 -0
  258. package/neural-interface/public/shared/skills/studio/sx-fixes.js +327 -0
  259. package/neural-interface/public/shared/skills/studio/sx-inspector.js +1 -1
  260. package/neural-interface/public/shared/skills/studio/sx-install-flow.js +9 -6
  261. package/neural-interface/public/shared/skills/studio/sx-install.js +20 -3
  262. package/neural-interface/public/shared/skills/studio/sx-library-view.js +144 -35
  263. package/neural-interface/public/shared/skills/studio/sx-library.js +6 -2
  264. package/neural-interface/public/shared/skills/studio/sx-main.js +11 -2
  265. package/neural-interface/public/shared/skills/studio/sx-review-view.js +1 -1
  266. package/neural-interface/public/shared/skills/studio/sx-subject.js +7 -6
  267. package/neural-interface/public/shared/skills/studio/sx-workspace-view.js +6 -5
  268. package/neural-interface/public/shared/state.js +1 -1
  269. package/neural-interface/public/shared/streaming-markdown.js +58 -0
  270. package/neural-interface/public/shared/styles.css +354 -34
  271. package/neural-interface/public/shared/text-cache.js +29 -0
  272. package/neural-interface/public/shared/tool-list-picker.js +2 -2
  273. package/neural-interface/public/shared/tool-list-view.js +2 -2
  274. package/neural-interface/public/shared/topright-overflow-logic.js +54 -0
  275. package/neural-interface/public/shared/ui-antigravity-panel.js +19 -0
  276. package/neural-interface/public/shared/ui-assistant.js +2 -0
  277. package/neural-interface/public/shared/ui-automation-studio.js +20 -12
  278. package/neural-interface/public/shared/ui-claude-panel.js +299 -77
  279. package/neural-interface/public/shared/ui-cli-banner.js +23 -167
  280. package/neural-interface/public/shared/ui-file-explorer.js +48 -6
  281. package/neural-interface/public/shared/ui-file-links.js +2 -2
  282. package/neural-interface/public/shared/ui-games.js +2 -2
  283. package/neural-interface/public/shared/ui-judgments.js +1 -1
  284. package/neural-interface/public/shared/ui-keybinds.js +8 -2
  285. package/neural-interface/public/shared/ui-link.js +3 -3
  286. package/neural-interface/public/shared/ui-menubar.js +2 -2
  287. package/neural-interface/public/shared/ui-native-loop-router.js +4 -0
  288. package/neural-interface/public/shared/ui-navbar.js +45 -0
  289. package/neural-interface/public/shared/ui-notifications.js +1 -0
  290. package/neural-interface/public/shared/ui-resume.js +9 -7
  291. package/neural-interface/public/shared/ui-schedules-studio.js +41 -10
  292. package/neural-interface/public/shared/ui-scroll-follow.js +16 -5
  293. package/neural-interface/public/shared/ui-sessions.js +22 -12
  294. package/neural-interface/public/shared/ui-settings.js +90 -65
  295. package/neural-interface/public/shared/ui-sidepanel-session-entry.js +9 -2
  296. package/neural-interface/public/shared/ui-sidepanel-sessions.js +3 -3
  297. package/neural-interface/public/shared/ui-sidepanel-tray.js +48 -7
  298. package/neural-interface/public/shared/ui-sidepanel-windows.js +3 -3
  299. package/neural-interface/public/shared/ui-terminal.js +146 -10
  300. package/neural-interface/public/shared/ui-tool-updates.js +3 -3
  301. package/neural-interface/public/shared/ui-tooltip.js +71 -46
  302. package/neural-interface/public/shared/ui-topright-overflow.js +233 -0
  303. package/neural-interface/public/shared/ui-whiteboard.js +1 -1
  304. package/neural-interface/public/shared/ui-worker-end.js +1 -1
  305. package/neural-interface/public/shared/whatsapp/wa-view.js +2 -2
  306. package/neural-interface/public/shared/worker-questions.js +34 -0
  307. package/neural-interface/public/sidepanel-session.html +1 -1
  308. package/neural-interface/server.js +644 -426
  309. package/neural-interface/templates/rulesets/README.md +1 -1
  310. package/neural-interface/templates/rulesets/legacy-hashes.json +5 -5
  311. package/neural-interface/templates/rulesets/manifest.json +1 -1
  312. package/npm-shrinkwrap.json +18 -2
  313. package/package.json +2 -1
  314. package/skills/skill-forge/SKILL.md +4 -4
  315. package/skills/synabun/SKILL.md +1 -1
  316. package/skills/synabun/modules/schedule.md +3 -3
  317. /package/neural-interface/templates/rulesets/{gemini.md → antigravity.md} +0 -0
@@ -16,6 +16,10 @@ import { dirname, resolve } from 'node:path';
16
16
  import { createClaudeBrain } from './assistant-brains/claude.js';
17
17
  import { createCodexBrain } from './assistant-brains/codex.js';
18
18
  import { createOpenCodeBrain } from './assistant-brains/opencode.js';
19
+ import { createAntigravityBrain } from './assistant-brains/antigravity.js';
20
+ import { acpPolicyInput, acpReadOnlyKind, acpToolName } from './antigravity/tool-call.js';
21
+ import { antigravityUsageTokens } from './assistant-usage.js';
22
+ import { UNPRICED_PROVIDER_REASON } from './assistant-budget-split.js';
19
23
  import { buildAssistantPersona, formatMailbox, reconcileRouteItems } from './assistant-persona.js';
20
24
  import { normalizeTestDensity, testDensityStamp } from './assistant-test-density.js';
21
25
  import { codexUsageCostUsd, floorCents } from './assistant-budget.js';
@@ -35,8 +39,9 @@ import { clampBrainModes, clampRouteMode, defaultRemotePolicyRegistry, effective
35
39
  import { browserToolDenial } from './browser-tool-policy.js';
36
40
  import { browserPolicyRoots } from './browser-policy-roots.js';
37
41
  import { stripFollowups } from '../public/shared/assistant/asst-followups.js';
42
+ import { readJsonStrict } from './rulesets/fs-safe.js';
38
43
 
39
- export const BRAIN_PROVIDERS = new Set(['claude-code', 'codex', 'opencode']);
44
+ export const BRAIN_PROVIDERS = new Set(['claude-code', 'codex', 'opencode', 'antigravity']);
40
45
  // Every tab is called SynaBun; the session title (the history list) comes from
41
46
  // the first prompt. Legacy "Assistant N" titles still count as unnamed.
42
47
  export const DEFAULT_SESSION_TITLE = 'SynaBun';
@@ -135,21 +140,33 @@ function readJson(path, fallback) { try { return JSON.parse(readFileSync(path, '
135
140
  function writeJsonAtomic(path, value) {
136
141
  mkdirSync(dirname(path), { recursive: true });
137
142
  const tmp = `${path}.${process.pid}.tmp`;
138
- writeFileSync(tmp, JSON.stringify(value, null, 2) + '\n', 'utf8');
139
- renameSync(tmp, path);
143
+ try {
144
+ writeFileSync(tmp, JSON.stringify(value, null, 2) + '\n', 'utf8');
145
+ renameSync(tmp, path);
146
+ } catch (error) {
147
+ try { rmSync(tmp, { force: true }); } catch { /* held too: the next write replaces it */ }
148
+ throw error;
149
+ }
140
150
  }
151
+ // What Windows answers, now and then, to a rename over a file another process holds open for a
152
+ // moment (an antivirus or indexer scanning it, a backup, the desktop app's pre-update snapshot).
153
+ const HELD_FILE_CODES = new Set(['EPERM', 'EACCES', 'EBUSY']);
154
+ // A write of the sessions index refused that way is tried again after these waits.
155
+ const INDEX_RETRY_MS = [200, 500, 1000, 2000, 5000, 10_000];
141
156
  function clip(value, max = 200) { const text = String(value ?? '').replace(/\s+/g, ' ').trim(); return text.length > max ? `${text.slice(0, max - 1)}…` : text; }
142
157
 
143
158
  /**
144
159
  * A brain effort checked against its model (effort-levels.js). "off" (or a
145
160
  * model with no levels) stays "off" for Claude and Codex, which reset to the
146
- * model's default on it; OpenCode has no such variant, so it gets null.
161
+ * model's default on it; OpenCode has no such variant and Antigravity's thinking
162
+ * level no "off" (its session keeps the model's own level), so they get null.
147
163
  */
148
164
  export function checkBrainEffort(provider, model, effort, catalog) {
149
165
  if (!effort) return null;
166
+ if (provider === 'antigravity' && /^(?:off|default|auto|none)$/i.test(String(effort))) return null;
150
167
  if (!catalog) return String(effort);
151
168
  const next = normalizeEffort({ provider, model, effort, catalog }).effort;
152
- return next || (provider === 'opencode' ? null : 'off');
169
+ return next || (provider === 'opencode' || provider === 'antigravity' ? null : 'off');
153
170
  }
154
171
 
155
172
  /** `catalog` (a catalog value) checks the effort against the brain's model (effort-levels.js). */
@@ -164,7 +181,8 @@ export function normalizeBrain(input = {}, defaults = {}, { catalog = null, cust
164
181
  model,
165
182
  effort: checkBrainEffort(provider, model, effort, catalog),
166
183
  agent: input?.agent ? String(input.agent) : null,
167
- accountId: input?.accountId ? String(input.accountId) : (provider === 'opencode' ? null : 'default'),
184
+ // OpenCode and Antigravity sign in inside their own CLI: SynaBun keeps no account of theirs.
185
+ accountId: provider === 'antigravity' ? null : input?.accountId ? String(input.accountId) : (provider === 'opencode' ? null : 'default'),
168
186
  cwd: input?.cwd ? String(input.cwd) : (input?.project ? String(input.project) : (defaults.cwd || null)),
169
187
  tools: provider === 'opencode' ? (storedToolList(input, { customPresets }) ?? storedToolList(defaults, { customPresets }) ?? [...OPENCODE_BRAIN_TOOLS]) : ['all'],
170
188
  permissionMode: input?.permissionMode && !legacyPlan ? String(input.permissionMode) : 'default',
@@ -206,6 +224,10 @@ export function createAssistantRuntime({
206
224
  ensureIsolatedServe = null,
207
225
  stopIsolatedServe = null,
208
226
  setupOpencodeSidepanelConfig = null,
227
+ // Antigravity brains (assistant-brains/antigravity.js): the CLI's launch spec, the environment its process gets,
228
+ // the brain's home (lib/antigravity/home.js), SynaBun's own MCP entry there, and the policy registry and loopback
229
+ // address its hook asks (lib/antigravity/policy.js).
230
+ antigravity = null,
209
231
  broadcastSync = () => {},
210
232
  dataDir,
211
233
  memory = null,
@@ -322,12 +344,43 @@ export function createAssistantRuntime({
322
344
  || !!row.remote || !!row.channel || Number(row.costUsd) > 0;
323
345
  return holds ? 'unstarted' : 'empty';
324
346
  }
347
+ // Whether the index file may be written: it was read, or there was none. A file that could not be
348
+ // read (Windows: held by an antivirus or a backup) is never replaced by what this process holds;
349
+ // reading it is tried again (INDEX_RETRY_MS), and sessions made meanwhile join what it held.
350
+ let indexReady = false;
351
+ let indexReadTries = 0;
352
+ let indexBlockedLogged = false;
353
+ function readIndexRows() {
354
+ try {
355
+ const rows = readJsonStrict(indexPath);
356
+ if (Array.isArray(rows)) return rows;
357
+ const error = new Error(`${indexPath} does not hold a list`);
358
+ error.code = 'INVALID_JSON';
359
+ throw error;
360
+ } catch (error) {
361
+ if (error?.code === 'ENOENT') return [];
362
+ if (error?.code !== 'INVALID_JSON') throw error;
363
+ // A corrupt file is moved aside, never deleted, and the index starts again from what is live.
364
+ const aside = `${indexPath}.corrupt-${Date.now()}`;
365
+ renameSync(indexPath, aside);
366
+ log('assistant:index-corrupt', `${error.message}; moved to ${aside}`);
367
+ return [];
368
+ }
369
+ }
325
370
  function loadIndex() {
326
- const rows = readJson(indexPath, []);
327
- if (!Array.isArray(rows)) return;
371
+ let rows;
372
+ try { rows = readIndexRows(); } catch (error) {
373
+ log('assistant:index-unreadable', `${error?.code || ''} ${error?.message || String(error)}`.trim());
374
+ if (indexReadTries < INDEX_RETRY_MS.length) {
375
+ setTimeout(loadIndex, INDEX_RETRY_MS[indexReadTries++]).unref?.();
376
+ } else log('assistant:index-unreadable', `${indexPath} is left as it is; sessions of this run stay in memory only, until a restart reads it`);
377
+ return;
378
+ }
379
+ indexReady = true;
380
+ const late = sessions.size > 0; // sessions made while the file could not be read
328
381
  let dropped = 0;
329
382
  for (const row of rows) {
330
- if (!row?.id) continue;
383
+ if (!row?.id || sessions.has(row.id)) continue;
331
384
  try { row.brain = normalizeSessionBrain(row.brain); }
332
385
  catch (error) { row.brain = { ...row.brain, tools: [], toolListError: { code: error.code, message: error.message, original: row.brain?.tools ?? row.brain?.mcpProfile } }; delete row.brain.mcpProfile; }
333
386
  if (settleLegacyRow(row) === 'empty') { dropped += 1; continue; }
@@ -339,16 +392,34 @@ export function createAssistantRuntime({
339
392
  }
340
393
  }
341
394
  // The index is written again without them (and with the started marks).
342
- if (dropped) { log('assistant:index', `${dropped} session record(s) without a conversation dropped`); persist(); }
395
+ if (dropped) log('assistant:index', `${dropped} session record(s) without a conversation dropped`);
396
+ if (dropped || late) persist();
343
397
  }
344
398
  let persistTimer = null;
399
+ let persistRetries = 0; // index writes Windows refused in a row (INDEX_RETRY_MS)
345
400
  function persist({ force = false } = {}) {
346
401
  const write = () => {
402
+ if (!indexReady) {
403
+ if (!indexBlockedLogged) { indexBlockedLogged = true; log('assistant:index', 'not written: the sessions index has not been read'); }
404
+ return;
405
+ }
347
406
  // A draft is never written: it exists on disk only once its first prompt was sent (startChat).
348
407
  const rows = [...sessions.values()].filter((live) => !live.draft).map((live) => publicRecord(live.record, { withLive: false, persisted: true }))
349
408
  .sort((a, b) => String(b.updatedAt || '').localeCompare(String(a.updatedAt || '')))
350
409
  .slice(0, settings.retention);
351
- try { writeJsonAtomic(indexPath, rows); } catch (error) { log('assistant:index-error', error?.message || String(error)); }
410
+ try {
411
+ writeJsonAtomic(indexPath, rows);
412
+ persistRetries = 0;
413
+ } catch (error) {
414
+ log('assistant:index-error', error?.message || String(error));
415
+ // The records changed (a rename already answered as saved, among them) and the file did not: on
416
+ // Windows it is written again shortly, with whatever the records hold by then, instead of waiting
417
+ // for the next change (a restart before it would bring the old records back).
418
+ if (process.platform === 'win32' && HELD_FILE_CODES.has(error?.code) && persistRetries < INDEX_RETRY_MS.length && !persistTimer) {
419
+ persistTimer = setTimeout(() => { persistTimer = null; write(); }, INDEX_RETRY_MS[persistRetries++]);
420
+ persistTimer.unref?.();
421
+ } else persistRetries = 0;
422
+ }
352
423
  };
353
424
  if (force) { if (persistTimer) { clearTimeout(persistTimer); persistTimer = null; } write(); return; }
354
425
  if (persistTimer) return;
@@ -1827,6 +1898,8 @@ export function createAssistantRuntime({
1827
1898
  function brainCostBasis(live) {
1828
1899
  const brain = live.record.brain || {};
1829
1900
  if (brain.provider === 'claude-code') return 'reported';
1901
+ // Antigravity reports tokens, not prices: its spend has no dollar figure.
1902
+ if (brain.provider === 'antigravity') return null;
1830
1903
  const info = brainCapabilities(live.record) || {};
1831
1904
  if (brain.provider === 'codex') {
1832
1905
  if (!pricing?.codexPrice) return null;
@@ -1868,6 +1941,10 @@ export function createAssistantRuntime({
1868
1941
  const money = sessionMoney(live.record.id);
1869
1942
  if (!money || money.unlimited) return null;
1870
1943
  // Fail closed: a brain whose spend has no price could spend past the budget.
1944
+ // Antigravity has no price at all (whatever the pricing tables): a dollar budget cannot hold its turns.
1945
+ if (live.record.brain?.provider === 'antigravity') {
1946
+ return { code: 'BRAIN_UNPRICED', message: `${UNPRICED_PROVIDER_REASON}, so the session budget cannot hold an Antigravity brain's spend. Switch the budget to No limit in ⋯ → Budget…, or pick another provider for the brain.` };
1947
+ }
1871
1948
  if (pricing && brainCostBasis(live) === null) {
1872
1949
  const model = live.record.brain?.model || brainCapabilities(live.record)?.id || 'its default model';
1873
1950
  return { code: 'BRAIN_UNPRICED', message: `No price is known for the ${live.record.brain?.provider || ''} brain model ${model}, so the session budget cannot hold its spend. Pick a priced or free model for the brain, or switch the budget to No limit.` };
@@ -2055,6 +2132,8 @@ export function createAssistantRuntime({
2055
2132
  remoteReadOnly: !!remote && remoteBrainLimited(brain.provider),
2056
2133
  // The engine keeps this conversation's thread record: pre-compact.mjs asks for no conversation memory.
2057
2134
  userContextThreads: brain.provider === 'claude-code' && userSuffix.nudges === true,
2135
+ // Antigravity (assistant-brains/antigravity.js): where it runs, and what its policy hook asks of this session.
2136
+ ...(brain.provider === 'antigravity' ? antigravityBrainDeps(live) : {}),
2058
2137
  };
2059
2138
  const gate = ensureGate(live);
2060
2139
  // A remote session's hook / plugin refuses a call it could not check (fail closed).
@@ -2064,7 +2143,7 @@ export function createAssistantRuntime({
2064
2143
  ? () => codexGateBootstrap({ url: gateUrl, session: live.record.id, token: live.gateToken, codexBin: typeof codexBin === 'function' ? codexBin() : codexBin, ...(remote ? { remote: true } : {}) })
2065
2144
  : async () => null;
2066
2145
  }
2067
- const factories = brainFactories || { 'claude-code': createClaudeBrain, codex: createCodexBrain, opencode: createOpenCodeBrain };
2146
+ const factories = brainFactories || { 'claude-code': createClaudeBrain, codex: createCodexBrain, opencode: createOpenCodeBrain, antigravity: createAntigravityBrain };
2068
2147
  const factory = factories[brain.provider];
2069
2148
  if (!factory) throw new Error(`Unsupported brain provider ${brain.provider}`);
2070
2149
  const memoryHooks = brain.provider === 'claude-code' && memory?.claudeHooks ? memory.claudeHooks({
@@ -2715,6 +2794,7 @@ export function createAssistantRuntime({
2715
2794
  if (message.type === 'assistant:permission-request') {
2716
2795
  sendToSockets(live, { type: 'event', event: { type: 'synabun.dispatch_control_request', runId: message.runId, request_id: message.request?.requestId, provider: dispatcher?.get?.(message.runId)?.provider || null, request: message.request } });
2717
2796
  } else if (message.type === 'assistant:permission-resolved') {
2797
+ live.mailbox = live.mailbox.filter((item) => !(item.run?.runId === message.runId && item.request?.requestId === message.requestId));
2718
2798
  sendToSockets(live, { type: 'event', event: { type: 'synabun.dispatch_control_resolved', runId: message.runId, request_id: message.requestId, resolvedBy: message.resolvedBy, behavior: message.behavior } });
2719
2799
  }
2720
2800
  }
@@ -3693,6 +3773,69 @@ export function createAssistantRuntime({
3693
3773
  const decision = decide(live, tool);
3694
3774
  return decision.allow ? { allow: true } : { allow: false, reason: decision.reason, refusals: decision.refusals, stopped: !!decision.abort };
3695
3775
  }
3776
+ /**
3777
+ * An Antigravity brain's tool call (its policy hook, lib/antigravity/policy.js), checked in this process the way
3778
+ * gateCheck checks the OpenCode plugin's and the Codex hook's: plan mode, a remote session's read-only rules,
3779
+ * the browser policy (every session, plan mode and subagents included), then the route gate. → { decision:
3780
+ * 'deny', reason } or null (no objection: the brain's approval mode decides). `call`: lib/antigravity/tool-call.js
3781
+ * acpCall. Antigravity's own bookkeeping steps (task boundaries, notes to the user) are never routed.
3782
+ */
3783
+ function antigravityGateCheck(live, call) {
3784
+ const name = acpToolName(call);
3785
+ const input = acpPolicyInput(call);
3786
+ const planned = planGate(live, 'antigravity', name, input);
3787
+ if (planned) return { decision: 'deny', reason: planned.reason };
3788
+ // A remote (WhatsApp) session's Antigravity brain is read-only for good, at every level.
3789
+ if (live.brainRemoteReadOnly && readOnlyToolDecision(name, input, { host: 'antigravity' }) === 'deny') {
3790
+ log('assistant:remote-policy', `${live.record.id}: refused ${name} (read-only)`);
3791
+ return { decision: 'deny', reason: `${readOnlyDenyMessage(name)} Tell the user to do this in SynaBun on their computer if they need it.` };
3792
+ }
3793
+ if (call.mcp) {
3794
+ const remoteDenied = remoteGate(live, name, input, { argsComplete: true, host: 'antigravity' });
3795
+ if (remoteDenied) return { decision: 'deny', reason: remoteDenied.reason };
3796
+ }
3797
+ const browserDenied = browserGate(live, name, input, { host: 'antigravity' });
3798
+ if (browserDenied) return { decision: 'deny', reason: browserDenied.reason };
3799
+ if (!live.gate || call.kind === 'think') return null;
3800
+ const decision = decide(live, name);
3801
+ return decision.allow ? null : { decision: 'deny', reason: decision.reason };
3802
+ }
3803
+ /** What an Antigravity brain needs from this session (assistant-brains/antigravity.js deps). */
3804
+ function antigravityBrainDeps(live) {
3805
+ const ag = antigravity || {};
3806
+ const saved = live.record.usageState?.antigravity;
3807
+ return {
3808
+ antigravityLaunch: typeof ag.launch === 'function' ? ag.launch : () => null,
3809
+ childEnv: typeof ag.childEnv === 'function' ? ag.childEnv : undefined,
3810
+ antigravityHome: typeof ag.home === 'function' ? (sessionId) => ag.home(sessionId) : null,
3811
+ synabunMcpEntry: typeof ag.synabunMcpEntry === 'function' ? ag.synabunMcpEntry : null,
3812
+ policy: ag.policy || null,
3813
+ policyUrl: typeof ag.policyUrl === 'function' ? ag.policyUrl() : (ag.policyUrl || null),
3814
+ connect: ag.connect || undefined,
3815
+ userGemini: ag.userGemini || undefined,
3816
+ policyCheck: (call) => antigravityGateCheck(live, call),
3817
+ // ACP counts tokens per session: what the session the record names had counted (a resume books only what follows).
3818
+ sessionUsageBase: saved && saved.sessionId && saved.sessionId === live.record.providerSessionId ? saved.usage || null : null,
3819
+ onAntigravitySession: (sessionId) => { if (sessionId && live.record.providerSessionId !== sessionId) { live.record.providerSessionId = sessionId; touch(live); } },
3820
+ onAntigravitySessionUsage: (sessionId, total) => { live.record.usageState ||= {}; live.record.usageState.antigravity = { sessionId, usage: total }; },
3821
+ onAntigravityUsage: (info) => settleAntigravityBrain(live, info),
3822
+ };
3823
+ }
3824
+ /** One Antigravity brain turn's tokens into the ledger: unpriced (tokens, not prices), exact when its total agrees. */
3825
+ function settleAntigravityBrain(live, { usage: turnUsage = null, model = null } = {}) {
3826
+ if (!usage || !turnUsage) return;
3827
+ metered('antigravity-settle', () => {
3828
+ const { tokens, mismatch } = antigravityUsageTokens(turnUsage);
3829
+ if (totalTokens(tokens) === 0) return;
3830
+ const taskId = currentTask(live.record.id) || ensureTaskId(live.record.id);
3831
+ usage.settle({
3832
+ sessionId: live.record.id, taskId, scope: 'brain', runId: null, turn: live.record.turns, provider: 'antigravity',
3833
+ model: model || live.record.brain?.model || null, part: 'main', tokens, costUsd: null, costBasis: 'unpriced',
3834
+ fidelity: mismatch ? 'partial' : 'exact', reason: mismatch ? 'antigravity-total-mismatch' : null, source: 'antigravity-acp-usage',
3835
+ });
3836
+ });
3837
+ persist({ force: true });
3838
+ }
3696
3839
  /**
3697
3840
  * A remote session's hard refusal for one tool call (every level), else null.
3698
3841
  * `argsComplete: false`: the host did not pass the call's arguments. An
@@ -3913,6 +4056,9 @@ export function createAssistantRuntime({
3913
4056
  return {
3914
4057
  handleWebSocket, listSessions, getSession, createSession, updateSession, closeSession, destroySession, removeSession,
3915
4058
  resolveTerminal, accountInUse, ownsIsolatedServe, noteDispatch, subscribe, shutdown,
4059
+ // The sessions index written now, synchronously: the server's exit handler runs it on every way out,
4060
+ // since an exit through process.exit() never reaches shutdown()'s own write.
4061
+ flushIndex: () => persist({ force: true }),
3916
4062
  observeBroadcast: onPermissionBroadcast,
3917
4063
  testDensityFor, routerSession, routerSend, routerEvent, routerMailbox, routerContinue, routerSinks, routerRouted, routerRouteFailed, routerComputerApproved, gateCheck, isPlanning, isPlanReadOnly,
3918
4064
  clarifySession, clarifyEvent, clarifyGate, clarifySinks,
@@ -25,6 +25,7 @@ const PROVIDER_LABELS = {
25
25
  'claude-code': 'Claude Code',
26
26
  codex: 'Codex',
27
27
  opencode: 'OpenCode',
28
+ antigravity: 'Antigravity',
28
29
  };
29
30
 
30
31
  function providerLabel(provider) {
@@ -57,7 +58,7 @@ function topKeywords(task, title) {
57
58
  /** The provider's host name for browserPolicyText (its own web tools). */
58
59
  function policyHost(provider) {
59
60
  const value = String(provider || '').toLowerCase();
60
- return value === 'claude-code' ? 'claude' : value === 'codex' || value === 'opencode' ? value : null;
61
+ return value === 'claude-code' ? 'claude' : value === 'codex' || value === 'opencode' || value === 'antigravity' ? value : null;
61
62
  }
62
63
 
63
64
  /**
@@ -197,12 +198,12 @@ export function styleGuideBlock(state = {}) {
197
198
  }
198
199
 
199
200
  function policyLine(policy) {
200
- if (String(policy) === 'ask') {
201
- return 'Permission requests are answered by the orchestrator or the user; keep them minimal and batch related actions. '
202
- + 'If you need a decision, ask once and precisely with the question tool available to you.';
203
- }
204
- return 'This run is unattended: do not ask questions, do not enter plan mode, and do not wait for approval. '
205
- + 'Make reasonable assumptions and state them in your result. If you truly cannot proceed, finish with status needs_input and one precise question.';
201
+ const permissions = String(policy) === 'ask'
202
+ ? 'Permission requests are answered by the orchestrator or the user; keep them minimal and batch related actions. '
203
+ : 'Continue autonomously within your permissions. Make reasonable assumptions and state them in your result. ';
204
+ return permissions + 'Ask only when genuinely blocked on a decision only the user can make. Use your question tool, never prose or a final needs_input result to ask: the user answers in this run’s live sidepanel and this SAME turn continues. '
205
+ + 'Claude Code: load the deferred AskUserQuestion schema with ToolSearch (select:AskUserQuestion) before calling it. Codex: use SynaBun choice in Default mode (request_user_input only in Plan mode). OpenCode: use the native question tool. Antigravity: use SynaBun choice through ACP form elicitation. '
206
+ + 'Do not enter plan mode just to ask. While a question is pending, wait for its tool result; do not end the turn or re-dispatch. If no question tool works, report blocked with the missing capability.';
206
207
  }
207
208
 
208
209
  /**
@@ -223,7 +224,7 @@ function capabilityLine(capability, state = {}) {
223
224
  case 'read-only':
224
225
  return 'CAPABILITY: read-only. You may read, search and analyze anywhere; you must not modify files, run state-changing shell commands, or publish anything. The shell runs read-only commands only (rg, grep, ls, cat, head, tail, find, stat, diff, jq, git log / diff / status / show / blame, node --check, npm ls, curl without an output file); programs, test runners and writes to files are not available.' + enforced;
225
226
  case 'workspace':
226
- return `CAPABILITY: workspace. You may edit files inside the working directory${scratch ? `, the OS temp folder and your scratch folder (${scratch})` : ' and the OS temp folder'} and run the commands the task needs; nothing is written anywhere else (not under the home folder, another project or a tool's configuration such as ~/.claude, ~/.codex, ~/.agents, ~/.config/opencode or ~/.synabun). Shell commands that write elsewhere (redirections, cp, mv, rm, ln, tee, sed -i, git -C another folder, a global install) are refused.` + enforced;
227
+ return `CAPABILITY: workspace. You may edit files inside the working directory${scratch ? `, the OS temp folder and your scratch folder (${scratch})` : ' and the OS temp folder'} and run the commands the task needs; nothing is written anywhere else (not under the home folder, another project or a tool's configuration such as ~/.claude, ~/.codex, ~/.agents, ~/.config/opencode, ~/.gemini or ~/.synabun). Shell commands that write elsewhere (redirections, cp, mv, rm, ln, tee, sed -i, git -C another folder, a global install) are refused.` + enforced;
227
228
  default:
228
229
  return 'CAPABILITY: full access as configured by the user.';
229
230
  }
@@ -418,6 +419,7 @@ export function buildResumePrompt(state = {}, { at = '', selfRestarts = false }
418
419
  const lines = [
419
420
  `SERVER RESTART (same run ${runId}, project "${project}"): SynaBun's server restarted${clean(at) ? ` at ${clean(at)}` : ''} while you were working on this task, and this conversation was resumed in a new process.`,
420
421
  '- A tool call that was running then was cut off: its result is lost, and a command it started may or may not have finished.',
422
+ '- A question pending before the restart was cancelled with its live tool call, not answered. If it is still needed, ask again with the question tool; never infer an answer from the cancellation.',
421
423
  '- Check the actual state before you go on instead of assuming it: git status and git log, the files you were changing, test or build output, processes you started.',
422
424
  '- Do not repeat a side effect that already happened (a commit, a push, a message or post sent, a memory stored, a deploy): check first.',
423
425
  ];
@@ -35,7 +35,7 @@ export function launchTools(selection) {
35
35
  export function toolsView(entry) {
36
36
  const selected = dispatchTools({ tools: entry.toolsRequested ?? undefined, provider: entry.provider, usesBrowser: entry.usesBrowser, taskClass: entry.taskClass });
37
37
  const current = requireToolList(resolveToolList(entry.tools ?? launchTools(selected), { source: 'runtime', provider: entry.provider }));
38
- const label = entry.provider === 'claude-code' ? 'Claude Code' : entry.provider === 'codex' ? 'Codex' : entry.provider || 'This worker';
38
+ const label = entry.provider === 'claude-code' ? 'Claude Code' : entry.provider === 'codex' ? 'Codex' : entry.provider === 'opencode' ? 'OpenCode' : entry.provider === 'antigravity' ? 'Antigravity' : entry.provider || 'This worker';
39
39
  return { requested: entry.toolsRequested ?? null, groups: current.groups, added: selected.added, effect: current.effect, count: current.count, servers: entry.externalServers?.names || [], disabledServers: entry.externalServers?.disabled || [],
40
40
  note: current.effect === 'focus' ? `${label} reaches every SynaBun tool on demand; this list is its focus.` : 'Loads exactly these SynaBun groups, plus always-on tools and role/grant tools.' };
41
41
  }
@@ -47,5 +47,5 @@ export function dispatchInput(body) {
47
47
  }
48
48
 
49
49
  export function workerToolsLine(catalog = toolCatalogView()) {
50
- return `SynaBun tools for workers: pass tools: [group ids] on agent_dispatch. Always there: ${catalog.alwaysOnTools.join(', ')}. With no tools a worker gets ${catalog.default.join(', ')}. Groups: ${catalog.groups.map(group => `${group.id} (${group.count})`).join(' · ')}. Host effect: ${Object.entries(catalog.effect).map(([host, effect]) => `${host}: ${effect}`).join(', ')}. Strict hosts load exactly those groups; Claude Code and Codex always reach every SynaBun tool on demand and the list only sets their focus. Never assume a Claude Code or Codex worker lacks a group you left out. Your MCP servers: name them as mcp:<name> in tools (or mcp:* for all allowed servers). Every unnamed external server is off for that worker on every supported provider. Available server refs: ${(catalog.servers || []).filter(row => row.selectable !== false).map(row => row.ref).join(', ') || 'none listed'}.`;
50
+ return `SynaBun tools for workers: pass tools: [group ids] on agent_dispatch. Always there: ${catalog.alwaysOnTools.join(', ')}. With no tools a worker gets ${catalog.default.join(', ')}. Groups: ${catalog.groups.map(group => `${group.id} (${group.count})`).join(' · ')}. Host effect: ${Object.entries(catalog.effect).map(([host, effect]) => `${host}: ${effect}`).join(', ')}. Strict hosts load exactly those groups; Claude Code and Codex always reach every SynaBun tool on demand and the list only sets their focus. Never assume a Claude Code or Codex worker lacks a group you left out. Your MCP servers: name them as mcp:<name> in tools (or mcp:* for all allowed servers). Every unnamed external server is off for that worker on Claude Code, Codex, OpenCode and Antigravity. Available server refs: ${(catalog.servers || []).filter(row => row.selectable !== false).map(row => row.ref).join(', ') || 'none listed'}.`;
51
51
  }
@@ -20,6 +20,7 @@
20
20
  // claudeModelUsageTokens(entry) one SDK ModelUsage entry
21
21
  // codexUsageTokens(usage) → { tokens, mismatch } (Codex input includes cached, output includes reasoning)
22
22
  // openCodeTokens(tokens) already five classes
23
+ // antigravityUsageTokens(usage) → { tokens, mismatch } (an ACP usage block; which classes nest is read off its total)
23
24
  //
24
25
  // 2. METERS one per provider session. Each keeps a small JSON-safe state()
25
26
  // the caller persists and hands back as { state }.
@@ -203,6 +204,34 @@ export function codexUsageTokens(usage) {
203
204
  return { tokens, mismatch: raw.total_tokens !== null && raw.total_tokens !== totalTokens(tokens) };
204
205
  }
205
206
 
207
+ /**
208
+ * Antigravity usage: the ACP `usage` block of a prompt answer ({ inputTokens, outputTokens, thoughtTokens,
209
+ * cachedReadTokens, cachedWriteTokens, totalTokens }, camelCase or snake_case). ACP defines totalTokens as the sum
210
+ * of every type, so the classes are taken as disjoint; when the agent's own total says the cached tokens sit inside
211
+ * the input, or the thought tokens inside the output (the Gemini API's own convention), they are taken out. Which
212
+ * reading applied is the one whose sum is the total; `mismatch` when none is.
213
+ * @returns {{ tokens: object, mismatch: boolean }}
214
+ */
215
+ export function antigravityUsageTokens(usage) {
216
+ const u = usage && typeof usage === 'object' ? usage : {};
217
+ const input = count(u.inputTokens ?? u.input_tokens);
218
+ const output = count(u.outputTokens ?? u.output_tokens);
219
+ const thought = count(u.thoughtTokens ?? u.thought_tokens ?? u.thinking_tokens ?? u.reasoningTokens);
220
+ const cacheRead = count(u.cachedReadTokens ?? u.cached_read_tokens ?? u.cache_read_tokens);
221
+ const cacheWrite = count(u.cachedWriteTokens ?? u.cached_write_tokens ?? u.cache_write_tokens);
222
+ const rawTotal = u.totalTokens ?? u.total_tokens;
223
+ const total = rawTotal === undefined || rawTotal === null || rawTotal === '' || !Number.isFinite(Number(rawTotal)) ? null : count(rawTotal);
224
+ const disjoint = { input, cacheWrite, cacheRead, output, reasoning: thought };
225
+ if (total === null) return { tokens: disjoint, mismatch: false };
226
+ const cachedInside = { ...disjoint, input: Math.max(0, input - cacheRead - cacheWrite) };
227
+ const thoughtInside = { ...disjoint, output: Math.max(0, output - thought) };
228
+ const bothInside = { ...cachedInside, output: thoughtInside.output };
229
+ for (const reading of [disjoint, cachedInside, thoughtInside, bothInside]) {
230
+ if (totalTokens(reading) === total) return { tokens: reading, mismatch: false };
231
+ }
232
+ return { tokens: disjoint, mismatch: true };
233
+ }
234
+
206
235
  /** OpenCode message tokens ({ input, output, reasoning, cache:{ read, write } }): already five additive classes. */
207
236
  export function openCodeTokens(tokens) {
208
237
  const t = tokens && typeof tokens === 'object' ? tokens : {};
@@ -38,6 +38,11 @@ export const DEFAULT_BACKUP_RETENTION = Object.freeze({
38
38
  dailyDays: 30,
39
39
  weeklyWeeks: 12,
40
40
  maxBytes: 10 * 1024 * 1024 * 1024,
41
+ // How many pre-update/pre-migration snapshots this generic sweep keeps in a shared folder; the
42
+ // dedicated pruner for the updater's own folder (lib/update-snapshot.js prunePreUpdateSnapshots)
43
+ // reads the same retention.preUpdateKeep through the settings mechanism (GET/PUT
44
+ // /api/system/auto-backup), with its own PRE_UPDATE_KEEP_ENV override.
45
+ preUpdateKeep: 1,
41
46
  });
42
47
 
43
48
  /** The DATA_HOME folders every backup copies (createVerifiedBackup); DATA_HOME/.env goes too, as a file. */
@@ -1322,7 +1327,7 @@ export function applyBackupRetention({
1322
1327
  keep.add(latestPreUpdate.path);
1323
1328
  protectedPaths.add(latestPreUpdate.path);
1324
1329
  }
1325
- for (const item of snapshots.filter(item => ['pre-update', 'pre-migration'].includes(item.kind)).slice(0, 5)) keep.add(item.path);
1330
+ for (const item of snapshots.filter(item => ['pre-update', 'pre-migration'].includes(item.kind)).slice(0, Math.max(0, Number(policy.preUpdateKeep) || 0))) keep.add(item.path);
1326
1331
 
1327
1332
  const daily = new Set();
1328
1333
  const weekly = new Set();
@@ -5,9 +5,10 @@ import { EventEmitter } from 'node:events';
5
5
  import WebSocket from 'ws';
6
6
 
7
7
  export class GuardCdpConnection extends EventEmitter {
8
- constructor(socket) {
8
+ constructor(socket, { timeoutMs = 5000 } = {}) {
9
9
  super();
10
10
  this.socket = socket;
11
+ this.timeoutMs = timeoutMs;
11
12
  this.pending = new Map();
12
13
  this.sequence = 0;
13
14
  this.closed = false;
@@ -33,7 +34,7 @@ export class GuardCdpConnection extends EventEmitter {
33
34
  if (this.closed || this.closing) return Promise.reject(new Error('Network guard CDP transport closed'));
34
35
  const id = ++this.sequence;
35
36
  return new Promise((resolve, reject) => {
36
- const timer = setTimeout(() => { this.pending.delete(id); reject(new Error(`Network guard: ${method} timed out`)); }, 5000);
37
+ const timer = setTimeout(() => { this.pending.delete(id); reject(new Error(`Network guard: ${method} timed out`)); }, this.timeoutMs);
37
38
  this.pending.set(id, { resolve, reject, timer, sessionId, method });
38
39
  const failed = error => {
39
40
  if (!error) return;
@@ -67,7 +68,7 @@ export class GuardCdpConnection extends EventEmitter {
67
68
  }
68
69
  }
69
70
 
70
- export async function connectGuardCdp(endpoint) {
71
+ export async function connectGuardCdp(endpoint, { timeoutMs } = {}) {
71
72
  let address = endpoint;
72
73
  if (!/^wss?:\/\//.test(address)) {
73
74
  // The owned DevTools JSON API, not page retrieval. No cookies or profile
@@ -79,5 +80,5 @@ export async function connectGuardCdp(endpoint) {
79
80
  if (typeof address !== 'string' || !/^wss?:\/\//.test(address)) throw new Error('Network guard: no DevTools WebSocket endpoint');
80
81
  const socket = new WebSocket(address, { handshakeTimeout: 5000, maxPayload: 16 * 1024 * 1024 });
81
82
  await new Promise((resolve, reject) => { socket.once('open', resolve); socket.once('error', reject); });
82
- return new GuardCdpConnection(socket);
83
+ return new GuardCdpConnection(socket, { timeoutMs });
83
84
  }