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
@@ -5,7 +5,7 @@ import { readStoredToolListRecord } from './tool-lists.js';
5
5
  // ═══════════════════════════════════════════
6
6
  //
7
7
  // A "dispatch" is one worker run launched by the central assistant: a single
8
- // provider conversation (Claude / Codex / OpenCode) that executes one task,
8
+ // provider conversation (Claude / Codex / OpenCode / Antigravity) that executes one task,
9
9
  // then stays warm for follow-up turns until the assistant completes it or the
10
10
  // idle timeout fires. It rides on NativeLoopRuntime (ledger, claims, sidepanel
11
11
  // attach) through the per-launch buildPrompt/wrapAdapter overrides, so the
@@ -43,16 +43,19 @@ import { loadPlaybook } from './assistant-playbooks.js';
43
43
  import { loadStyleGuideForRun } from './style-guide/store.js';
44
44
  import { claimCheckWaived, normalizeTestDensity, testingApplies } from './assistant-test-density.js';
45
45
  import { normalizeEffort } from './effort-levels.js';
46
- import { createTurnEvidence, recordProviderEvent, evidenceSnapshot, claimEvidenceLine } from './assistant-evidence.js';
46
+ import { acpCommandOf, createTurnEvidence, recordProviderEvent, evidenceSnapshot, claimEvidenceLine } from './assistant-evidence.js';
47
47
  import { BUDGET_MAX_USD, codexUsageCostUsd, floorCents } from './assistant-budget.js';
48
- import { MIN_SPEND_USD, PROVIDER_LABELS, isFreePrice } from './assistant-budget-split.js';
48
+ import { MIN_SPEND_USD, PROVIDER_LABELS, UNPRICED_PROVIDER_REASON, isFreePrice, isUnpricedProvider } from './assistant-budget-split.js';
49
49
  import { createBudgetPlanner } from './assistant-budget-planner.js';
50
- import { CLAUDE_ESTIMATE_SOURCE, CODEX_USAGE_KEYS, TOKEN_KEYS, addTokens, codexUsageTokens, createClaudeMeter, createCodexMeter, createOpenCodeMeter, totalTokens, withTotal, zeroTokens } from './assistant-usage.js';
50
+ import { CLAUDE_ESTIMATE_SOURCE, CODEX_USAGE_KEYS, TOKEN_KEYS, addTokens, antigravityUsageTokens, codexUsageTokens, createClaudeMeter, createCodexMeter, createOpenCodeMeter, totalTokens, withTotal, zeroTokens } from './assistant-usage.js';
51
51
  import { claudeTokensCostUsd } from './assistant-pricing.js';
52
52
  import { RemotePolicyError, clampDispatchSpec, getSessionPolicy, readRegisteredProjects } from './remote-policy.js';
53
+ import { validateWorkerReply } from './worker-question.js';
53
54
  import { limitFromText } from '../public/shared/assistant/asst-limit.js';
54
55
 
55
- export const NATIVE_PROVIDERS = new Set(['claude-code', 'codex', 'opencode']);
56
+ // Antigravity (Google's `agy`, over ACP: lib/native-loop-providers.js createAntigravityNativeLoopAdapter) is a worker
57
+ // provider only: never a brain, no accounts, its usage reported in tokens and never priced.
58
+ export const NATIVE_PROVIDERS = new Set(['claude-code', 'codex', 'opencode', 'antigravity']);
56
59
  // awaiting_route: an un-routed dispatch held until the user picks a model on
57
60
  // the route card (ask modes). It is active but consumes no rails.
58
61
  // suspended: a native worker the server suspended as it went down (its CLI
@@ -85,7 +88,7 @@ export function runStatusFromState(state) {
85
88
  export const MIN_MAX_MINUTES = 20;
86
89
 
87
90
  export const DEFAULT_LIMITS = Object.freeze({
88
- perProvider: Object.freeze({ 'claude-code': 3, codex: 4, opencode: 3 }),
91
+ perProvider: Object.freeze({ 'claude-code': 3, codex: 4, opencode: 3, antigravity: 3 }),
89
92
  perCodexAccount: 2,
90
93
  perSession: 6,
91
94
  queuedPerSession: 20,
@@ -298,9 +301,55 @@ export function summarizeProviderEvent(payload = {}) {
298
301
  if (/question\.asked/i.test(eventType)) return `question asked: ${clip((event.questions || []).map((q) => q.question).join(' | '), 200)}`;
299
302
  return null;
300
303
  }
304
+ if (payload.provider === 'antigravity') {
305
+ // eventType is the ACP session/update kind; event the update as the agent sent it.
306
+ if (eventType === 'agent_message_chunk') {
307
+ const text = event.content?.type === 'text' ? event.content.text : '';
308
+ return text && String(text).trim() ? `assistant: ${clip(text, 200)}` : null;
309
+ }
310
+ if (eventType === 'tool_call') return `${acpToolNoun(event)} started: ${acpToolDetail(event)}`;
311
+ if (eventType === 'tool_call_update') {
312
+ // Only the end of a call: its start was the tool_call line, its progress says nothing new.
313
+ if (event.status !== 'completed' && event.status !== 'failed') return null;
314
+ return `${acpToolNoun(event)} ${event.status === 'failed' ? 'failed' : 'done'}: ${acpToolDetail(event)}`;
315
+ }
316
+ if (eventType === 'plan') {
317
+ const entries = Array.isArray(event.entries) ? event.entries : [];
318
+ if (!entries.length) return null;
319
+ const done = entries.filter((step) => step?.status === 'completed').length;
320
+ const current = entries.find((step) => step?.status === 'in_progress');
321
+ return `plan: ${done}/${entries.length} done${current?.content ? `, now ${clip(current.content, 120)}` : ''}`;
322
+ }
323
+ if (eventType === 'synabun.provider_exit') {
324
+ const how = event.signal ? ` (${event.signal})` : event.code !== null && event.code !== undefined ? ` (code ${event.code})` : '';
325
+ return `error: Antigravity exited${how}${event.error ? `: ${clip(event.error, 160)}` : ''}`;
326
+ }
327
+ return null;
328
+ }
301
329
  return null;
302
330
  }
303
331
 
332
+ /** What an ACP tool call is, for a transcript line: command, file_change or tool (Codex's words). */
333
+ function acpToolNoun(call = {}) {
334
+ const kind = String(call.kind || '');
335
+ // agy names an MCP call call_mcp_tool, kind `execute`: it is a tool, never a command.
336
+ if (call.name === 'call_mcp_tool' || (call.rawInput && typeof call.rawInput === 'object' && call.rawInput.ServerName && call.rawInput.ToolName)) return 'tool';
337
+ if (kind === 'execute') return 'command';
338
+ if (kind === 'edit' || kind === 'delete' || kind === 'move') return 'file_change';
339
+ return 'tool';
340
+ }
341
+ /** The command, the paths or the title of an ACP tool call; its id when an update carries none of them. */
342
+ function acpToolDetail(call = {}) {
343
+ const input = call.rawInput && typeof call.rawInput === 'object' ? call.rawInput : {};
344
+ if (input.ServerName && input.ToolName) return clip(`${input.ServerName}_${input.ToolName}`, 140);
345
+ const command = call.kind === 'execute' ? acpCommandOf(call) : '';
346
+ if (command) return clip(command, 140);
347
+ const paths = (Array.isArray(call.locations) ? call.locations : []).map((location) => location?.path).filter((path) => typeof path === 'string' && path);
348
+ if (paths.length && call.kind !== 'execute' && call.kind !== 'fetch') return clip(paths.join(', '), 160);
349
+ if (call.title) return clip(call.title, 140);
350
+ return call.toolCallId ? `call ${String(call.toolCallId).slice(0, 12)}` : (call.kind || 'tool');
351
+ }
352
+
304
353
  // The old five-cap keys a config file may still hold: never limits (the budget planner owns the money).
305
354
  const LEGACY_BUDGET_LIMITS = new Set(['defaultBudgetUsd', 'maxBudgetUsd', 'sessionWarnUsd', 'sessionSoftBudgetUsd', 'sessionHardBudgetUsd', 'brainBudgetUsd', 'budgetVersion', 'budgetUpdatedAt']);
306
355
  function mergeLimits(overrides = {}) {
@@ -479,8 +528,10 @@ export function createAssistantDispatcher({
479
528
  let closing = false;
480
529
 
481
530
  // ── persistence ────────────────────────────────────────────────────────────
482
- /** Why the original provider account of a run cannot be used now ('' when it can). */
531
+ /** Why the original provider account of a run cannot be used now ('' when it can, or the provider has no accounts). */
483
532
  function providerAccountUnusable(accountId, provider = 'claude-code', entry = null) {
533
+ // OpenCode and Antigravity sign in inside their own CLI: SynaBun keeps no account of theirs.
534
+ if (provider !== 'codex' && provider !== 'claude-code') return '';
484
535
  if (provider === 'codex') {
485
536
  const home = codexAccounts?.homeFor?.(accountId);
486
537
  if (home && entry?.codexHome && resolve(home) !== resolve(entry.codexHome)) return 'its CODEX_HOME changed';
@@ -658,9 +709,11 @@ export function createAssistantDispatcher({
658
709
  status: runtimeRun.status, turnState: runtimeRun.turnState, stoppedReason: runtimeRun.stoppedReason,
659
710
  error: runtimeRun.error, claimedBy: runtimeRun.claimedBy, providerSessionId: runtimeRun.providerSessionId,
660
711
  providerThreadId: runtimeRun.providerThreadId,
712
+ ...((runtimeRun.antigravityHome || entry.resumeInfo?.antigravityHome || entry.antigravityHome) ? { antigravityHome: runtimeRun.antigravityHome || entry.resumeInfo?.antigravityHome || entry.antigravityHome } : {}),
661
713
  } : (entry.runtime || null),
662
714
  providerSessionId: runtimeRun?.providerSessionId || entry.providerSessionId || null,
663
715
  providerThreadId: runtimeRun?.providerThreadId || entry.providerThreadId || null,
716
+ ...((runtimeRun?.antigravityHome || entry.resumeInfo?.antigravityHome || entry.antigravityHome) ? { antigravityHome: runtimeRun?.antigravityHome || entry.resumeInfo?.antigravityHome || entry.antigravityHome } : {}),
664
717
  };
665
718
  }
666
719
  function safeRuntime() { try { return getRuntime(); } catch { return null; } }
@@ -771,7 +824,7 @@ export function createAssistantDispatcher({
771
824
  }
772
825
 
773
826
  /** `taskClass`: the run's class (its route's, else the spec's); image / video creation needs a model that makes the medium. */
774
- function normalizeSpec(spec = {}, { catalogValue = null, codexAskFallback = false, taskClass = null } = {}) {
827
+ function normalizeSpec(spec = {}, { catalogValue = null, taskClass = null } = {}) {
775
828
  const provider = String(spec.provider || '').trim().toLowerCase();
776
829
  if (!NATIVE_PROVIDERS.has(provider)) throw new DispatchError('INVALID_PROVIDER', `provider must be one of ${[...NATIVE_PROVIDERS].join(', ')}`);
777
830
  const task = String(spec.task || '').trim();
@@ -782,12 +835,6 @@ export function createAssistantDispatcher({
782
835
  let permissionPolicy = normalizePermissionPolicy(spec.permissionPolicy);
783
836
  let capability = normalizeCapability(spec.capability || 'full');
784
837
  const notes = [];
785
- if (provider === 'codex' && permissionPolicy === 'ask') {
786
- // A route card may move an 'ask' dispatch onto Codex: degrade instead of failing.
787
- if (!codexAskFallback) throw new DispatchError('PERMISSION_POLICY_UNSUPPORTED', 'Codex dispatches cannot use permissionPolicy "ask" (the Codex SDK has no approval channel). Use "restricted" for a workspace-write sandbox or "auto".', { suggested: 'restricted' });
788
- permissionPolicy = 'restricted';
789
- notes.push('permission policy "ask" became "restricted" (Codex has no approval channel)');
790
- }
791
838
  // "auto" (or nothing) = let routing pick / use the provider default.
792
839
  let model = spec.model && String(spec.model).trim() && String(spec.model).trim().toLowerCase() !== 'auto' ? String(spec.model).trim() : null;
793
840
  let modelInfo = null;
@@ -1070,10 +1117,11 @@ export function createAssistantDispatcher({
1070
1117
  /**
1071
1118
  * Why a run's spend could not be held to the budget (no price for its model), else null. Enforced
1072
1119
  * only with a budget and a price source (`pricing`): Claude reports cost, Codex needs a list price,
1073
- * OpenCode a catalog price (a free model is fine).
1120
+ * OpenCode a catalog price (a free model is fine), Antigravity never has one (tokens, not prices).
1074
1121
  */
1075
1122
  function unpricedReason(entry) {
1076
1123
  if (!pricing || entry.provider === 'claude-code' || !limitedNow()) return null;
1124
+ if (isUnpricedProvider(entry.provider)) return UNPRICED_PROVIDER_REASON;
1077
1125
  const model = entry.pricingModel || entry.model || null;
1078
1126
  if (entry.provider === 'codex') {
1079
1127
  let price = null;
@@ -1085,7 +1133,10 @@ export function createAssistantDispatcher({
1085
1133
  function refuseUnpriced(entry) {
1086
1134
  const why = unpricedReason(entry);
1087
1135
  if (!why) return;
1088
- throw new DispatchError('BUDGET_UNPRICED', `Not dispatched: ${why}, so the session budget could not hold its spend. Pick a priced model or a free one (agent_catalog shows prices).`, { status: 409, provider: entry.provider, model: entry.pricingModel || entry.model || null });
1136
+ const next = isUnpricedProvider(entry.provider)
1137
+ ? `Dispatch it to another provider (agent_catalog shows prices), or ask the user to switch the session budget to No limit (Assistant → Budget) to run it on ${providerName(entry.provider)}.`
1138
+ : 'Pick a priced model or a free one (agent_catalog shows prices).';
1139
+ throw new DispatchError('BUDGET_UNPRICED', `Not dispatched: ${why}, so the session budget could not hold its spend. ${next}`, { status: 409, provider: entry.provider, model: entry.pricingModel || entry.model || null });
1089
1140
  }
1090
1141
  const providerName = (provider) => PROVIDER_LABELS[provider] || provider;
1091
1142
  /** The session budget's words for a provider whose share is used up ("codex share used up: $4.00 of $4.00 spent"). */
@@ -1522,7 +1573,7 @@ export function createAssistantDispatcher({
1522
1573
  emit(entry, 'route_pending', { routeId: routed.routeId });
1523
1574
  return {
1524
1575
  ok: true, queued: true, awaitingRoute: true, routeId: routed.routeId, run: view(entry),
1525
- next: 'This run is waiting for the user to pick its model on the route card. Do not dispatch it again. It starts automatically after the pick; the decision arrives as a [SynaBun Mailbox] route_decided event.',
1576
+ next: 'This run is waiting for the user to pick its model on the route card. Do not dispatch it again. It starts automatically after the pick; the decision arrives as a [SynaBun Mailbox] route_decided event. If the user picks "here" instead, this run never starts and that event asks you to do the task yourself.',
1526
1577
  };
1527
1578
  }
1528
1579
 
@@ -1555,7 +1606,7 @@ export function createAssistantDispatcher({
1555
1606
  let spec;
1556
1607
  // The picked provider may need another clamp (Codex under strict worker approvals), or the level fell meanwhile.
1557
1608
  const remoteNotes = [];
1558
- try { input = clampRemote(input, entry.assistantSessionId, remoteNotes); spec = normalizeSpec(input, { catalogValue, codexAskFallback: true, taskClass: entry.taskClass || null }); }
1609
+ try { input = clampRemote(input, entry.assistantSessionId, remoteNotes); spec = normalizeSpec(input, { catalogValue, taskClass: entry.taskClass || null }); }
1559
1610
  catch (error) {
1560
1611
  entry.state = 'failed'; entry.completionReason = 'route_invalid'; entry.finishedAt = iso(now()); entry.turnState = 'terminal';
1561
1612
  entry.error = error?.message || String(error);
@@ -1581,7 +1632,10 @@ export function createAssistantDispatcher({
1581
1632
  const entry = entries.get(runId);
1582
1633
  if (!entry || entry.state !== 'awaiting_route') return false;
1583
1634
  entry.state = 'stopped'; entry.completionReason = reason; entry.finishedAt = iso(now()); entry.turnState = 'terminal'; entry.pending = [];
1584
- entry.route = { ...(entry.route || {}), status: reason === 'route_expired' ? 'expired' : 'declined', decidedBy: reason === 'route_expired' ? 'timeout' : 'user', decidedAt: iso(now()) };
1635
+ // route_here: the user picked "here" on its card, so the assistant does the task itself and this worker never starts.
1636
+ const status = reason === 'route_expired' ? 'expired' : reason === 'route_here' ? 'here' : 'declined';
1637
+ entry.route = { ...(entry.route || {}), status, decidedBy: reason === 'route_expired' ? 'timeout' : 'user', decidedAt: iso(now()) };
1638
+ if (reason === 'route_here') entry.notes.push('the user chose to do this task here, in the conversation: this worker was not started');
1585
1639
  delete entry.rawSpec;
1586
1640
  emit(entry, 'stopped');
1587
1641
  return true;
@@ -1605,7 +1659,7 @@ export function createAssistantDispatcher({
1605
1659
  accountId: to.accountId || (to.provider === entry.provider ? entry.accountId : null),
1606
1660
  title: clip(`${entry.title} ${retry ? '↻' : '↑'}`, 80), tags: entry.tags, workflowId: entry.workflowId, parentRunId: runId,
1607
1661
  tools: entry.tools?.some(item => item.startsWith('mcp:')) ? retainExternalRefs(entry.toolsRequested ?? dispatchTools({}).items, entry.tools) : entry.toolsRequested ?? undefined, capability: entry.capability,
1608
- permissionPolicy: to.provider === 'codex' && entry.permissionPolicy === 'ask' ? 'restricted' : entry.permissionPolicy,
1662
+ permissionPolicy: entry.permissionPolicy,
1609
1663
  usesBrowser: entry.usesBrowser, usesComputer: entry.usesComputer, maxMinutes: entry.maxMinutes, budgetUsd: entry.budgetUsd,
1610
1664
  outputSchema: entry.outputSchema, taskClass: entry.taskClass || null,
1611
1665
  }, { assistantSessionId: entry.assistantSessionId, origin: 'ui', brief: entry.brief || null });
@@ -1788,8 +1842,11 @@ export function createAssistantDispatcher({
1788
1842
  const acq = await acquireLoopBrowserAndTab({ requestedBrowserSessionId: entry.requestedBrowserSessionId, terminalSessionId: entry.runId, logTag: 'assistant:browser' });
1789
1843
  if (acq?.error) {
1790
1844
  entry.state = 'failed'; entry.completionReason = 'browser_unavailable'; entry.finishedAt = iso(now()); entry.notes.push(acq.error);
1845
+ // The native serve boundary's refusal keeps its own code and the serves it names, on the run and the error.
1846
+ const boundary = acq.code === 'NATIVE_SERVE_UNVERIFIED' ? { browserCode: acq.code, offenders: Array.isArray(acq.offenders) ? acq.offenders : [] } : null;
1847
+ if (boundary) entry.browserError = boundary;
1791
1848
  emit(entry, 'failed');
1792
- throw new DispatchError('PROVIDER_UNAVAILABLE', acq.error, { status: 500 });
1849
+ throw new DispatchError('PROVIDER_UNAVAILABLE', acq.error, { status: 500, ...(boundary || {}) });
1793
1850
  }
1794
1851
  browserSessionId = acq.browserSessionId || null;
1795
1852
  browserTabId = acq.browserTabId || null;
@@ -1808,7 +1865,7 @@ export function createAssistantDispatcher({
1808
1865
  } else entry.scratchDir = null;
1809
1866
  const desktopGrant = mintDesktopGrant(entry);
1810
1867
  const state = runState(entry, { browserSessionId, browserTabId, desktopGrant, startedAt: iso(now()) });
1811
- if (entry.permissionPolicy === 'ask') state.permissionBroker = createBroker(entry.runId);
1868
+ state.permissionBroker = createBroker(entry.runId);
1812
1869
  // A class's rules (design), read now so an edited override applies without a restart;
1813
1870
  // they go to the prompt only, not into the loop state file.
1814
1871
  const playbook = playbookFor(entry);
@@ -1892,8 +1949,13 @@ export function createAssistantDispatcher({
1892
1949
  adminSandbox: entry.resumeInfo?.adminSandbox === true,
1893
1950
  onCodexSandbox: (value) => { resumeInfoOf(entry).adminSandbox = value === true; persist({ force: true }); },
1894
1951
  onSandboxPlan: (plan) => { resumeInfoOf(entry).sandboxPlan = plan; persist({ force: true }); },
1952
+ // Antigravity counts tokens per session (ACP usage): what its session had counted, kept for a resume after a
1953
+ // restart, so the resumed run's first turn books only its own.
1954
+ sessionUsageBase: entry.provider === 'antigravity' ? entry.resumeInfo?.acpSessionUsage || null : null,
1955
+ onSessionUsage: (value) => { if (entry.provider === 'antigravity' && value && typeof value === 'object') resumeInfoOf(entry).acpSessionUsage = value; },
1895
1956
  guardMode: entry.guardMode, scratchDir: entry.scratchDir || null, serverPort: Number(serverPort) > 0 ? Number(serverPort) : null,
1896
1957
  localPortRange: localPortRange && Number(localPortRange.from) > 0 && Number(localPortRange.to) >= Number(localPortRange.from) ? { from: Number(localPortRange.from), to: Number(localPortRange.to) } : null,
1958
+ questionTimeoutMs: limits.questionTimeoutMs,
1897
1959
  idleTimeoutMs: entry.idleTimeoutMs, maxTurns: entry.maxTurns, outputSchema: entry.outputSchema, project: entry.project,
1898
1960
  title: entry.title, tags: entry.tags, workflowId: entry.workflowId, parentRunId: entry.parentRunId,
1899
1961
  // The user's brief after a clarification (assistant-task-prompt.js briefBlock).
@@ -1914,6 +1976,8 @@ export function createAssistantDispatcher({
1914
1976
  if (!Number.isInteger(pid) || pid <= 0) return;
1915
1977
  resumeInfoOf(entry).pid = pid;
1916
1978
  entry.resumeInfo.pidStartedAt = info.startedAt || iso(now());
1979
+ // An Antigravity run's own home (lib/antigravity/home.js): where its conversation is, for a resume.
1980
+ if (typeof info.home === 'string' && info.home) { entry.resumeInfo.antigravityHome = info.home; entry.antigravityHome = info.home; }
1917
1981
  persist({ force: true });
1918
1982
  },
1919
1983
  ...extra,
@@ -1990,36 +2054,38 @@ export function createAssistantDispatcher({
1990
2054
  return account?.home || CODEX_DEFAULT_HOME || null;
1991
2055
  }
1992
2056
 
1993
- // ── permission broker ('ask' policy) ──────────────────────────────────────
2057
+ // ── request broker (questions in every policy; tool approvals under 'ask') ──────────────────────────────────────
1994
2058
  function createBroker(runId) {
1995
2059
  return {
1996
2060
  request: (request = {}) => new Promise((resolvePermission) => {
1997
2061
  const entry = entries.get(runId);
1998
2062
  const liveState = live.get(runId);
1999
- if (!entry || !liveState) { resolvePermission({ behavior: 'deny', message: 'Run is no longer tracked.' }); return; }
2063
+ if (!entry || !liveState || liveState.dead || liveState.suspending || TERMINAL_STATES.has(entry.state) || request.signal?.aborted) { resolvePermission({ behavior: 'deny', message: 'Run is no longer tracked.' }); return; }
2000
2064
  const requestId = `perm-${randomBytes(6).toString('hex')}`;
2001
2065
  const kind = request.kind === 'ask' ? 'question' : 'permission';
2002
2066
  const timeoutMs = kind === 'question' ? limits.questionTimeoutMs : limits.permissionTimeoutMs;
2003
2067
  const pending = {
2004
2068
  requestId, kind, toolName: request.toolName || null, input: request.input ?? null,
2069
+ protocol: request.protocol || null,
2005
2070
  questions: Array.isArray(request.questions) ? request.questions : [], createdAt: iso(now()), expiresAt: iso(now() + timeoutMs),
2006
2071
  };
2007
2072
  entry.pending = [...(entry.pending || []), pending];
2008
2073
  entry.state = 'awaiting_permission';
2009
2074
  entry.turnState = 'awaiting_permission';
2075
+ // Install the resolver before publishing: a synchronous listener can answer at once.
2076
+ const timer = setTimeout(() => finishPending(runId, requestId, { behavior: 'deny', message: `No decision within ${Math.round(timeoutMs / 60000)} minutes.`, resolvedBy: 'timeout' }), timeoutMs);
2077
+ timer.unref?.();
2078
+ const abortSignal = request.signal;
2079
+ const onAbort = () => finishPending(runId, requestId, { behavior: 'deny', message: 'Run aborted.', resolvedBy: 'abort' });
2080
+ liveState.pendingResolvers.set(requestId, { resolve: resolvePermission, timer, cleanup: () => abortSignal?.removeEventListener?.('abort', onAbort) });
2081
+ abortSignal?.addEventListener?.('abort', onAbort, { once: true });
2010
2082
  // Waiting on the answer is not work: the request's own timeout bounds it.
2011
2083
  pauseWork(entry, liveState);
2084
+ liveState.ctx?.setPendingRequests?.(entry.pending);
2012
2085
  liveState.ctx?.setTurnState?.('awaiting_permission');
2013
2086
  liveState.ctx?.emit?.({ type: 'synabun.permission_request', requestId, kind, toolName: pending.toolName, input: pending.input, questions: pending.questions });
2014
- const timer = setTimeout(() => finishPending(runId, requestId, { behavior: 'deny', message: `No decision within ${Math.round(timeoutMs / 60000)} minutes.`, resolvedBy: 'timeout' }), timeoutMs);
2015
- timer.unref?.();
2016
- liveState.pendingResolvers.set(requestId, { resolve: resolvePermission, timer });
2017
2087
  try { broadcastSync({ type: 'assistant:permission-request', runId, assistantSessionId: entry.assistantSessionId, provider: entry.provider, request_id: requestId, request: pending }); } catch {}
2018
2088
  emit(entry, kind === 'question' ? 'needs_input' : 'permission_request', { request: pending });
2019
- const abortSignal = request.signal;
2020
- if (abortSignal && typeof abortSignal.addEventListener === 'function') {
2021
- abortSignal.addEventListener('abort', () => finishPending(runId, requestId, { behavior: 'deny', message: 'Run aborted.', resolvedBy: 'abort' }), { once: true });
2022
- }
2023
2089
  }),
2024
2090
  };
2025
2091
  }
@@ -2030,10 +2096,12 @@ export function createAssistantDispatcher({
2030
2096
  const resolver = liveState.pendingResolvers.get(requestId);
2031
2097
  if (!resolver) return false;
2032
2098
  clearTimeout(resolver.timer);
2099
+ resolver.cleanup?.();
2033
2100
  liveState.pendingResolvers.delete(requestId);
2034
2101
  const request = (entry.pending || []).find((item) => item.requestId === requestId) || null;
2035
2102
  entry.pending = (entry.pending || []).filter((item) => item.requestId !== requestId);
2036
- if (entry.state === 'awaiting_permission' && entry.pending.length === 0) {
2103
+ liveState.ctx?.setPendingRequests?.(entry.pending);
2104
+ if (entry.state === 'awaiting_permission' && entry.pending.length === 0 && !liveState.dead && !liveState.suspending && !liveState.abortReason) {
2037
2105
  entry.state = 'running';
2038
2106
  entry.turnState = 'running';
2039
2107
  startWork(entry, liveState);
@@ -2056,9 +2124,14 @@ export function createAssistantDispatcher({
2056
2124
  throw new DispatchError(known ? 'PERMISSION_ALREADY_RESOLVED' : 'NO_PENDING_REQUEST', known ? `Request ${requestId} was already resolved` : `No pending request ${requestId} on run ${runId}`, { status: 409 });
2057
2125
  }
2058
2126
  const behavior = response.behavior === 'allow' || response.decision === 'allow' || response.decision === 'answer' ? 'allow' : 'deny';
2127
+ // Antigravity saves an "always" answer for every later session of the user's, outside the run: a worker's
2128
+ // answer is for this request only.
2129
+ const always = entry.provider === 'antigravity' ? false : !!response.always;
2130
+ try { validateWorkerReply((entry.pending || []).find((p) => p.requestId === requestId), { ...response, behavior }); }
2131
+ catch (error) { throw new DispatchError('INVALID_QUESTION_ANSWER', error.message); }
2059
2132
  finishPending(runId, requestId, {
2060
- behavior, updatedInput: response.updatedInput, message: response.message || response.note, always: !!response.always,
2061
- answers: response.answers, resolvedBy: origin,
2133
+ behavior, updatedInput: response.updatedInput, message: response.message || response.note, always,
2134
+ answers: response.answers ?? response.updatedInput?.answers, result: response.result, content: response.content, action: response.action, error: response.error, resolvedBy: origin,
2062
2135
  }, { origin });
2063
2136
  return view(entry);
2064
2137
  }
@@ -2105,6 +2178,7 @@ export function createAssistantDispatcher({
2105
2178
  liveState.turnMeteredUsd = 0;
2106
2179
  let raw = await inner.runTurn(prompt, { ...meta, turn: turnNumber, outputSchema: entry.outputSchema || undefined });
2107
2180
  if (entry.provider === 'codex') settleCodexTurn(entry, liveState, raw);
2181
+ else if (entry.provider === 'antigravity') settleAntigravityTurn(entry, liveState, raw);
2108
2182
  let text = typeof raw?.text === 'string' ? raw.text : '';
2109
2183
  const evalRun = isEvalRun(entry);
2110
2184
  let parsed = evalRun ? { found: false } : parseResultContract(text);
@@ -2142,6 +2216,7 @@ export function createAssistantDispatcher({
2142
2216
  if (retryParsed.found) { parsed = { ...retryParsed, source: 'retry' }; text = `${text}\n\n${retryText}`; }
2143
2217
  retryCost = accumulateCost(entry, retry);
2144
2218
  if (entry.provider === 'codex') settleCodexTurn(entry, liveState, retry);
2219
+ else if (entry.provider === 'antigravity') settleAntigravityTurn(entry, liveState, retry);
2145
2220
  } catch (error) {
2146
2221
  // Stopped during the retry: the run ends here (handleTurnError books the retry), with no
2147
2222
  // turn_completed or follow-up after it. The first provider turn was paid for: book it first.
@@ -2149,6 +2224,7 @@ export function createAssistantDispatcher({
2149
2224
  if (liveState.abortReason || liveState.suspending) { accumulateCost(entry, raw); throw error; }
2150
2225
  retryCost = accumulateCost(entry, error);
2151
2226
  if (entry.provider === 'codex') settleCodexTurn(entry, liveState, error);
2227
+ else if (entry.provider === 'antigravity') settleAntigravityTurn(entry, liveState, error);
2152
2228
  entry.notes.push(`result retry failed: ${error?.message || error}`);
2153
2229
  }
2154
2230
  }
@@ -2174,7 +2250,8 @@ export function createAssistantDispatcher({
2174
2250
  liveState.turnOpen = false;
2175
2251
  stopUsagePolling(liveState);
2176
2252
  let turnUsage = raw?.usage || null;
2177
- if (entry.provider === 'codex') turnUsage = liveState.turnUsage || null;
2253
+ // Codex and Antigravity: what their provider turns (the retry included) reported, in provider keys.
2254
+ if (entry.provider === 'codex' || entry.provider === 'antigravity') turnUsage = liveState.turnUsage || null;
2178
2255
  else if (entry.provider === 'opencode') turnUsage = (await settleOpenCodeTurn(entry, liveState)) || turnUsage;
2179
2256
  // A reported cost below an earlier turn's estimate can make this negative: a turn never reads below $0.
2180
2257
  const turnCost = Math.max(0, accumulateCost(entry, raw) + retryCost + (Number(liveState.turnMeteredUsd) || 0));
@@ -2383,6 +2460,8 @@ export function createAssistantDispatcher({
2383
2460
  if (entry.provider === 'codex') settleCodexTurn(entry, liveState, error);
2384
2461
  // OpenCode: what streamed before the failure, as it stands (its serve may be gone: no reconcile).
2385
2462
  else if (entry.provider === 'opencode') bookOpenCodeTurn(entry, liveState, { reconciled: false });
2463
+ // Antigravity: a stopped turn's answer still carried its tokens (the adapter puts them on the abort).
2464
+ else if (entry.provider === 'antigravity') settleAntigravityTurn(entry, liveState, error);
2386
2465
  announceUnpriced(entry);
2387
2466
  checkSession(entry.assistantSessionId, { source: entry });
2388
2467
  if (budgetStopReason(entry.completionReason)) return { providerSessionId: null };
@@ -2465,7 +2544,8 @@ export function createAssistantDispatcher({
2465
2544
  if (!raw) return 0;
2466
2545
  const cost = Number(raw.costUsd);
2467
2546
  const added = bookCost(entry, cost, raw.basis || 'reported');
2468
- if (entry.provider !== 'codex') addLegacyUsage(entry, raw.usage);
2547
+ // Codex and Antigravity usage reaches entry.usage when it is settled (settleCodexTurn, settleAntigravityTurn).
2548
+ if (entry.provider !== 'codex' && entry.provider !== 'antigravity') addLegacyUsage(entry, raw.usage);
2469
2549
  return added;
2470
2550
  }
2471
2551
 
@@ -2752,6 +2832,38 @@ export function createAssistantDispatcher({
2752
2832
  persist({ force: true });
2753
2833
  }
2754
2834
 
2835
+ // Antigravity: each provider turn's answer carries that turn's own ACP usage (the adapter takes it off the
2836
+ // session's running totals; a failed or stopped turn has none, so the next answer carries it). Tokens only:
2837
+ // Antigravity is billed by its Google sign-in and reports no price, so every row is unpriced, never $0.
2838
+ /**
2839
+ * Book one Antigravity provider turn: its five token classes into the ledger (exact when the agent's own total
2840
+ * agrees with one reading of its classes, else partial: 'antigravity-total-mismatch'), its usage in provider keys
2841
+ * into the run's and the turn's legacy totals, and the run marked unpriced. Nothing without usage.
2842
+ */
2843
+ function settleAntigravityTurn(entry, liveState, raw) {
2844
+ if (entry.usageState?.closed) return;
2845
+ const usage = raw?.usage && typeof raw.usage === 'object' ? raw.usage : null;
2846
+ if (!usage) return;
2847
+ const { tokens, mismatch } = antigravityUsageTokens(usage);
2848
+ if (totalTokens(tokens) === 0) return;
2849
+ const used = { input_tokens: tokens.input, output_tokens: tokens.output, reasoning_tokens: tokens.reasoning, cache_read_input_tokens: tokens.cacheRead, cache_write_input_tokens: tokens.cacheWrite };
2850
+ addLegacyUsage(entry, used);
2851
+ if (liveState) {
2852
+ const prev = liveState.turnUsage || {};
2853
+ liveState.turnUsage = Object.fromEntries(Object.entries(used).map(([key, value]) => [key, (Number(prev[key]) || 0) + value]));
2854
+ }
2855
+ markUnpriced(entry, UNPRICED_PROVIDER_REASON);
2856
+ // No model named: the one the session runs (the adapter reads it off the session's config options).
2857
+ let model = entry.model || null;
2858
+ if (!model) { try { model = liveState?.inner?.describe?.()?.model || null; } catch { model = null; } }
2859
+ settleUsage(entry, {
2860
+ model, part: 'main', tokens, costUsd: null, costBasis: 'unpriced',
2861
+ fidelity: mismatch ? 'partial' : 'exact', reason: mismatch ? 'antigravity-total-mismatch' : null, source: 'antigravity-acp-usage',
2862
+ });
2863
+ // At once, not debounced: a kill must not leave the saved run behind the ledger.
2864
+ persist({ force: true });
2865
+ }
2866
+
2755
2867
  // OpenCode: every session of the run's own serve (the root and the `task` tool's child sessions).
2756
2868
  function openCodeMeterOf(entry) {
2757
2869
  const held = metersOf(entry);
@@ -3054,6 +3166,7 @@ export function createAssistantDispatcher({
3054
3166
  entry.suspendReason = String(reason || 'server_shutdown');
3055
3167
  delete entry.crashed;
3056
3168
  // An open permission request goes with the process: the resumed worker asks again.
3169
+ for (const requestId of [...liveState.pendingResolvers.keys()]) finishPending(entry.runId, requestId, { behavior: 'deny', message: 'Server restarting; ask again after resume.', resolvedBy: 'server_restart' });
3057
3170
  entry.pending = [];
3058
3171
  // What the cut-off turn spent so far is booked now (an estimate per call the stream showed: no result will
3059
3172
  // count them); the meter takes the resumed CLI's counters as its new baseline (rebaseClaudeUsage).
@@ -3245,14 +3358,14 @@ export function createAssistantDispatcher({
3245
3358
  const state = runState(entry, {
3246
3359
  browserSessionId, browserTabId, desktopGrant,
3247
3360
  startedAt: entry.startedAt || iso(now()),
3248
- extra: { resumeSessionId: entry.providerSessionId, resumeThreadId: entry.providerThreadId, costBaseUsd, resumingWorker: true },
3361
+ extra: { resumeSessionId: entry.providerSessionId, resumeThreadId: entry.providerThreadId, costBaseUsd, resumingWorker: true, antigravityHome: entry.resumeInfo?.antigravityHome || null },
3249
3362
  });
3250
3363
  // The CLI's spend stop (--max-budget-usd) counts from the total the resumed CLI restores (costBaseUsd): what the run
3251
3364
  // may still spend (its ceiling less what it spent, never more than the session has left) goes on top of that. A
3252
3365
  // backstop only: the dispatcher holds the run to its ceiling and its share between calls.
3253
3366
  const allowanceUsd = claudeRunCap(entry, { spentUsd: entry.costUsd });
3254
3367
  if (Number(allowanceUsd) > 0) state.maxBudgetUsd = Number((Number(allowanceUsd) + Math.max(0, Number(costBaseUsd) || 0)).toFixed(2));
3255
- if (entry.permissionPolicy === 'ask') state.permissionBroker = createBroker(entry.runId);
3368
+ state.permissionBroker = createBroker(entry.runId);
3256
3369
  const at = clockTime(entry.suspendedAt);
3257
3370
  const selfRestarts = isInside(entry.cwd, PACKAGE_ROOT);
3258
3371
  const playbook = mode === 'fresh' ? playbookFor(entry) : null;
@@ -3511,6 +3624,57 @@ export function createAssistantDispatcher({
3511
3624
  }
3512
3625
  processQueue().catch(() => {});
3513
3626
  }
3627
+ /** One transcript line into a live run's ring (bounded by lines and characters). */
3628
+ function pushRingLine(liveState, line) {
3629
+ liveState.ring.push(line);
3630
+ liveState.ringChars += line.length;
3631
+ // The message line still growing is no longer the last one.
3632
+ liveState.messageLine = null;
3633
+ while (liveState.ring.length > limits.ringSize || liveState.ringChars > limits.ringChars) {
3634
+ const removed = liveState.ring.shift();
3635
+ liveState.ringChars -= removed?.length || 0;
3636
+ }
3637
+ }
3638
+ /**
3639
+ * An Antigravity message chunk (ACP agent_message_chunk): the text joins the message's line when that line is
3640
+ * still the ring's last one (same messageId), else it starts a new "assistant:" line. Clipped as every line is.
3641
+ */
3642
+ function growMessageLine(liveState, update = {}) {
3643
+ const text = update?.content?.type === 'text' ? String(update.content.text || '') : '';
3644
+ if (!text) return;
3645
+ const id = update.messageId ?? null;
3646
+ const open = liveState.messageLine;
3647
+ if (open && open.id === id && liveState.ring.length && liveState.ring[liveState.ring.length - 1] === open.line) {
3648
+ open.text += text;
3649
+ const line = `${open.stamp} assistant: ${clip(open.text, 200)}`;
3650
+ if (line !== open.line) {
3651
+ liveState.ring[liveState.ring.length - 1] = line;
3652
+ liveState.ringChars += line.length - open.line.length;
3653
+ open.line = line;
3654
+ }
3655
+ return;
3656
+ }
3657
+ if (!text.trim()) return;
3658
+ const stamp = iso(now()).slice(11, 19);
3659
+ const line = `${stamp} assistant: ${clip(text, 200)}`;
3660
+ pushRingLine(liveState, line);
3661
+ liveState.messageLine = { id, text, stamp, line };
3662
+ }
3663
+ /**
3664
+ * An ACP tool_call_update carries only what changed: the call it updates, as the run has seen it so far, so its
3665
+ * end is said with the call's kind, command and title. Other payloads pass as they are. Bounded per run.
3666
+ */
3667
+ function withAcpCall(liveState, payload) {
3668
+ if ((payload.eventType !== 'tool_call' && payload.eventType !== 'tool_call_update') || !payload.event?.toolCallId) return payload;
3669
+ const calls = liveState.acpCalls || (liveState.acpCalls = new Map());
3670
+ const id = String(payload.event.toolCallId);
3671
+ const call = { ...(calls.get(id) || {}) };
3672
+ for (const [key, value] of Object.entries(payload.event)) if (value !== undefined && value !== null) call[key] = value;
3673
+ calls.delete(id);
3674
+ calls.set(id, call);
3675
+ if (calls.size > 200) calls.delete(calls.keys().next().value);
3676
+ return { ...payload, event: call };
3677
+ }
3514
3678
  function onRuntimeMessage(message) {
3515
3679
  const runId = message?.run?.runId;
3516
3680
  if (!runId || !entries.has(runId)) return;
@@ -3529,7 +3693,9 @@ export function createAssistantDispatcher({
3529
3693
  // trigger reading (lib/skills/eval-trace.js; read with transcript format 'tools').
3530
3694
  if (isEvalRun(entry)) { try { recordEvalCalls(liveState, message.event || {}); } catch {} }
3531
3695
  if (entry.provider === 'opencode') { try { meterOpenCode(entry, liveState, message.event || {}); } catch (error) { log(runId, 'assistant:meter-error', error?.message || String(error)); } }
3532
- const summary = summarizeProviderEvent(message.event || {});
3696
+ // Antigravity streams each message in small chunks: one transcript line per message, grown as it streams.
3697
+ if (entry.provider === 'antigravity' && message.event?.eventType === 'agent_message_chunk') { growMessageLine(liveState, message.event.event); return; }
3698
+ const summary = summarizeProviderEvent(entry.provider === 'antigravity' ? withAcpCall(liveState, message.event || {}) : message.event || {});
3533
3699
  // A guard decision (lib/worker-confinement.js) is also a note of the run: what was refused or warned about,
3534
3700
  // with its reason, so the brain and the panel see it without the transcript. The first twenty.
3535
3701
  if ((message.event?.eventType || message.event?.event?.type) === 'synabun.confinement') {
@@ -3537,15 +3703,7 @@ export function createAssistantDispatcher({
3537
3703
  if (count <= 20) entry.notes.push(`guard ${message.event.event?.action || 'refused'}: ${clip(summary, 300)}`);
3538
3704
  else if (count === 21) entry.notes.push('guard: more decisions follow in the transcript');
3539
3705
  }
3540
- if (summary) {
3541
- const line = `${iso(now()).slice(11, 19)} ${summary}`;
3542
- liveState.ring.push(line);
3543
- liveState.ringChars += line.length;
3544
- while (liveState.ring.length > limits.ringSize || liveState.ringChars > limits.ringChars) {
3545
- const removed = liveState.ring.shift();
3546
- liveState.ringChars -= removed?.length || 0;
3547
- }
3548
- }
3706
+ if (summary) pushRingLine(liveState, `${iso(now()).slice(11, 19)} ${summary}`);
3549
3707
  }
3550
3708
  return;
3551
3709
  }
@@ -3556,13 +3714,16 @@ export function createAssistantDispatcher({
3556
3714
  persist({ force: true });
3557
3715
  emit(entry, 'tools_changed');
3558
3716
  }
3559
- entry.runtime = { status: run.status, turnState: run.turnState, stoppedReason: run.stoppedReason, error: run.error, claimedBy: run.claimedBy, providerSessionId: run.providerSessionId, providerThreadId: run.providerThreadId };
3717
+ const runHome = run.antigravityHome || entry.resumeInfo?.antigravityHome || entry.antigravityHome || null;
3718
+ entry.runtime = { status: run.status, turnState: run.turnState, stoppedReason: run.stoppedReason, error: run.error, claimedBy: run.claimedBy, providerSessionId: run.providerSessionId, providerThreadId: run.providerThreadId, ...(runHome ? { antigravityHome: runHome } : {}) };
3560
3719
  // A new provider identity is saved at once: a crash before the run's next write (its first alive mark comes up to
3561
3720
  // a minute later) would otherwise leave no session to resume.
3562
3721
  const newIdentity = (run.providerSessionId && run.providerSessionId !== entry.providerSessionId)
3563
- || (run.providerThreadId && run.providerThreadId !== entry.providerThreadId);
3722
+ || (run.providerThreadId && run.providerThreadId !== entry.providerThreadId)
3723
+ || (run.antigravityHome && run.antigravityHome !== entry.antigravityHome);
3564
3724
  if (run.providerSessionId) entry.providerSessionId = run.providerSessionId;
3565
3725
  if (run.providerThreadId) entry.providerThreadId = run.providerThreadId;
3726
+ if (run.antigravityHome) entry.antigravityHome = run.antigravityHome;
3566
3727
  if (newIdentity) persist({ force: true });
3567
3728
  if (message.type === 'sidepanel:run-updated' && message.reason === 'started' && entry.state === 'starting') {
3568
3729
  entry.state = 'running';