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
@@ -3,7 +3,7 @@ import { storedToolList, toolListRecord, readStoredToolListRecord } from './tool
3
3
  import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
4
4
  import { dirname, resolve } from 'node:path';
5
5
 
6
- export const NATIVE_LOOP_PROFILES = new Set(['claude-code', 'codex', 'opencode']);
6
+ export const NATIVE_LOOP_PROFILES = new Set(['claude-code', 'codex', 'opencode', 'antigravity']);
7
7
 
8
8
  export function isNativeLoopProfile(profile) {
9
9
  return NATIVE_LOOP_PROFILES.has(String(profile || ''));
@@ -22,6 +22,7 @@ function cleanDescriptor(record) {
22
22
  provider: record.provider,
23
23
  providerSessionId: record.providerSessionId || null,
24
24
  providerThreadId: record.providerThreadId || null,
25
+ ...(record.antigravityHome ? { antigravityHome: record.antigravityHome } : {}),
25
26
  source: record.source,
26
27
  focus: !!record.focus,
27
28
  status: record.status,
@@ -53,6 +54,7 @@ function cleanDescriptor(record) {
53
54
  // existing consumers (router, sidepanels, schedules) are unaffected.
54
55
  runMode: record.runMode || 'loop',
55
56
  turnState: record.turnState || null,
57
+ pending: record.status === 'running' ? (record.pendingRequests || []) : [],
56
58
  // What the last turn's result said (task mode: done, blocked or needs_input; the dispatcher's
57
59
  // lastResult.status): an idle warm worker whose turn ended asking waits on the user.
58
60
  outcome: record.outcome || null,
@@ -184,6 +186,14 @@ export class NativeLoopRuntime {
184
186
  return this._emit('sidepanel:run-updated', record, { reason: 'turn-state' });
185
187
  }
186
188
 
189
+ /** A live worker's requests, durable for a late attach or page reload. Resolvers stay in the dispatcher. */
190
+ setPendingRequests(runId, requests) {
191
+ const record = this.records.get(runId);
192
+ if (!record) return null;
193
+ record.pendingRequests = structuredClone(requests || []);
194
+ return this._emit('sidepanel:run-updated', record, { reason: 'pending-requests' });
195
+ }
196
+
187
197
  /**
188
198
  * Task-mode helper: records what the wrapper's last turn reported ('done' |
189
199
  * 'blocked' | 'needs_input', the dispatcher's lastResult.status) and
@@ -224,6 +234,7 @@ export class NativeLoopRuntime {
224
234
  record.suspendedAt = record.suspendedAt || iso(this.now());
225
235
  record.suspendReason = record.suspendReason || 'server_restart';
226
236
  record.turnState = 'suspended';
237
+ record.pendingRequests = [];
227
238
  record.stopping = false;
228
239
  this.records.set(record.runId, record);
229
240
  this._hold(record);
@@ -314,7 +325,8 @@ export class NativeLoopRuntime {
314
325
  }
315
326
 
316
327
  _writeState(runId, state) {
317
- writeJsonAtomic(this._statePath(runId), toolListRecord(state, this.toolListOptions()));
328
+ const { permissionBroker: _broker, ...stored } = state;
329
+ writeJsonAtomic(this._statePath(runId), toolListRecord(stored, this.toolListOptions()));
318
330
  }
319
331
 
320
332
  _writeTerminalState(record, patch) {
@@ -344,6 +356,7 @@ export class NativeLoopRuntime {
344
356
  _setIdentity(record, identity = {}) {
345
357
  const providerSessionId = identity.providerSessionId || identity.sessionId || null;
346
358
  const providerThreadId = identity.providerThreadId || identity.threadId || null;
359
+ const antigravityHome = typeof identity.antigravityHome === "string" && identity.antigravityHome ? identity.antigravityHome : null;
347
360
  let changed = false;
348
361
  if (providerSessionId && providerSessionId !== record.providerSessionId) {
349
362
  record.providerSessionId = providerSessionId;
@@ -353,6 +366,10 @@ export class NativeLoopRuntime {
353
366
  record.providerThreadId = providerThreadId;
354
367
  changed = true;
355
368
  }
369
+ if (antigravityHome && antigravityHome !== record.antigravityHome) {
370
+ record.antigravityHome = antigravityHome;
371
+ changed = true;
372
+ }
356
373
  if (changed) this._emit('sidepanel:run-updated', record, { reason: 'identity' });
357
374
  if ((record.providerSessionId || record.providerThreadId) && record._resolveIdentity) {
358
375
  record._resolveIdentity(cleanDescriptor(record));
@@ -397,6 +414,7 @@ export class NativeLoopRuntime {
397
414
  provider: state.profile,
398
415
  providerSessionId: null,
399
416
  providerThreadId: null,
417
+ antigravityHome: typeof state.antigravityHome === "string" && state.antigravityHome ? state.antigravityHome : null,
400
418
  source,
401
419
  focus,
402
420
  status: 'starting',
@@ -544,6 +562,7 @@ export class NativeLoopRuntime {
544
562
  runId,
545
563
  emit: (event) => this._emitProviderEvent(record, event),
546
564
  setTurnState: (turnState) => this.setTurnState(runId, turnState),
565
+ setPendingRequests: (requests) => this.setPendingRequests(runId, requests),
547
566
  setOutcome: (outcome) => this.setOutcome(runId, outcome),
548
567
  readState: () => this._readState(runId),
549
568
  })
@@ -639,6 +658,7 @@ export class NativeLoopRuntime {
639
658
  browserTabId: state.browserTabId || null,
640
659
  suspendedAt: null,
641
660
  suspendReason: null,
661
+ ...(typeof state.antigravityHome === "string" && state.antigravityHome ? { antigravityHome: state.antigravityHome } : {}),
642
662
  resumes: (Number(record.resumes) || 0) + 1,
643
663
  resumedAt: iso(this.now()),
644
664
  buildPrompt,
@@ -830,6 +850,7 @@ export class NativeLoopRuntime {
830
850
  record.startupAbortController = null;
831
851
  record.buildPrompt = null;
832
852
  record.wrapAdapter = null;
853
+ record.pendingRequests = [];
833
854
  if (turnState === 'suspended') record.turnState = 'suspended';
834
855
  else if (record.turnState && record.turnState !== 'terminal') record.turnState = 'terminal';
835
856
  }
@@ -2,14 +2,23 @@
2
2
  // listen time. This is a capability allowlist, not a generic OpenCode version
3
3
  // check: 2.1.4's HTTP/upgrade authentication and stdout password path were
4
4
  // inspected and tested. Unknown binaries/command wrappers keep the old guard.
5
+ // 2.2.5 (Homebrew anomalyco/tap/opencode-v2) checked 2026-11-01 against throwaway
6
+ // serves on 127.0.0.1: no password variable → it prints "server password <secret>"
7
+ // on stdout; every /api route (session, config, event, pty, shell, file, mcp, and
8
+ // the new tunnel and pairing) answers 401 without it, both plain and as a
9
+ // WebSocket upgrade, and 200 with it; one listener, bound to 127.0.0.1 only.
10
+ // Non-/api paths serve the static web UI (HTML), as before.
11
+ export const NATIVE_SERVE_VERSIONS = Object.freeze(['2.1.4', '2.2.5']);
12
+
5
13
  export function nativeServeCapability({ version, commandLine = false, passwordFrom } = {}) {
6
- return !commandLine && passwordFrom === 'stdout' && /^(?:opencode\s+)?v?2\.1\.4$/.test(String(version || '').trim());
14
+ const bare = String(version || '').trim().replace(/^opencode\s+/, '').replace(/^v/, '');
15
+ return !commandLine && passwordFrom === 'stdout' && NATIVE_SERVE_VERSIONS.includes(bare);
7
16
  }
8
17
 
9
18
  export function createNativeServeBoundary() {
10
19
  const processes = new Map();
11
20
  const contexts = new Map();
12
- const error = () => new Error('Native browser WebSockets require authenticated OpenCode 2.1.4 serves with private stdout credentials; wait for startup, stop unverified serves or use the verified executable path.');
21
+ const error = () => new Error(`Native browser WebSockets require authenticated OpenCode ${NATIVE_SERVE_VERSIONS.join(' or ')} serves with private stdout credentials; wait for startup, stop unverified serves or use the verified executable path.`);
13
22
  return {
14
23
  assertLaunch(safe) { if (!safe && contexts.size) throw error(); },
15
24
  track(proc, { port, safe = false }) {
@@ -74,7 +74,7 @@ export const AGENT_CALL_KINDS = Object.freeze(['agent', 'synabun-mcp']);
74
74
  */
75
75
  export const OWNER_READ_PREFIXES = Object.freeze([...ADMIN_ONLY_PREFIXES.filter((prefix) => prefix !== '/api/hook-'), '/api/git']);
76
76
  /** The owner's sockets: a terminal, the agent panels, the Assistant, the browser panel. */
77
- export const OWNER_SOCKET_PREFIXES = Object.freeze(['/ws/terminal/', '/ws/claude-skin', '/ws/codex-skin', '/ws/opencode-v2', '/ws/assistant/', '/ws/browser/']);
77
+ export const OWNER_SOCKET_PREFIXES = Object.freeze(['/ws/terminal/', '/ws/claude-skin', '/ws/codex-skin', '/ws/opencode-v2', '/ws/antigravity', '/ws/assistant/', '/ws/browser/']);
78
78
  /** Sockets that stay open for the app's pages to draw, read-only for a peer the owner's powers are refused to. */
79
79
  export const READ_ONLY_SOCKET_PATHS = Object.freeze(['/ws/sync']);
80
80
 
@@ -19,13 +19,15 @@ export class PathConfineError extends Error {
19
19
  export const SESSION_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
20
20
  export const isSessionId = (id) => typeof id === 'string' && SESSION_ID_RE.test(id);
21
21
 
22
- const fold = process.platform === 'win32' ? (s) => s.toLowerCase() : (s) => s;
22
+ const foldForPlatform = (platform = process.platform) => (platform === 'win32' ? (s) => s.toLowerCase() : (s) => s);
23
+ const fold = foldForPlatform();
23
24
 
24
25
  /** True when `target` is below `root` (or is `root` itself, with allowRoot). Lexical: both are resolved first. */
25
- export function isInside(root, target, { allowRoot = false } = {}) {
26
+ export function isInside(root, target, { allowRoot = false, platform = process.platform } = {}) {
26
27
  if (!root || !target) return false;
27
- const r = fold(resolve(String(root)));
28
- const t = fold(resolve(String(target)));
28
+ const f = platform === process.platform ? fold : foldForPlatform(platform);
29
+ const r = f(resolve(String(root)));
30
+ const t = f(resolve(String(target)));
29
31
  if (t === r) return allowRoot;
30
32
  return t.startsWith(r.endsWith(sep) ? r : r + sep);
31
33
  }
@@ -83,28 +85,29 @@ export function isInsideReal(root, target, { allowRoot = false, fs = realFs } =
83
85
  * @param o.projectsDir <config dir>/projects of the account
84
86
  * @param o.pathToKey (projectPath) => the folder name Claude Code uses for it
85
87
  */
86
- export function findSessionTranscript({ sessionId, project = '', projects = [], projectsDir, pathToKey, exists = existsSync, realpath = realpathSync } = {}) {
88
+ export function findSessionTranscript({ sessionId, project = '', projects = [], projectsDir, pathToKey, exists = existsSync, realpath = realpathSync, platform = process.platform } = {}) {
87
89
  if (!isSessionId(sessionId)) throw new PathConfineError(400, 'Not a session id');
88
90
  if (!projectsDir || typeof pathToKey !== 'function') return null;
91
+ const foldPath = platform === process.platform ? fold : foldForPlatform(platform);
89
92
  const known = (Array.isArray(projects) ? projects : []).filter(p => p?.path);
90
- const wanted = typeof project === 'string' && project ? resolve(project) : '';
93
+ const wanted = typeof project === 'string' && project ? foldPath(resolve(project)) : '';
91
94
  let order = known;
92
95
  if (wanted) {
93
- const named = known.find(p => resolve(p.path) === wanted);
96
+ const named = known.find(p => foldPath(resolve(p.path)) === wanted);
94
97
  if (!named) throw new PathConfineError(400, 'Not a registered project');
95
98
  order = [named];
96
99
  }
97
100
  for (const proj of order) {
98
101
  const key = String(pathToKey(proj.path) || '');
99
102
  // Claude Code has written the drive letter in both cases on Windows.
100
- const keys = [...new Set([key.replace(/^([A-Z])/, m => m.toLowerCase()), key])].filter(isPlainName);
103
+ const keys = [...new Set([key.replace(/^([A-Z])/, m => m.toLowerCase()), key.replace(/^([a-z])/, m => m.toUpperCase()), key])].filter(isPlainName);
101
104
  for (const k of keys) {
102
105
  const folder = join(projectsDir, k);
103
106
  const candidate = join(folder, `${sessionId}.jsonl`);
104
107
  if (!exists(candidate)) continue;
105
108
  let realFile; let realFolder;
106
109
  try { realFile = realpath(candidate); realFolder = realpath(folder); } catch { continue; }
107
- if (!isInside(realFolder, realFile)) continue;
110
+ if (!isInside(realFolder, realFile, { platform })) continue;
108
111
  // `realPath` / `realFolder`: the canonical paths that were just checked.
109
112
  // The transcript is read from those (lib/confined-fs.js), not from the
110
113
  // name it was found under, which could be swapped for a link afterwards.
@@ -244,7 +244,11 @@ export const RELAY_NAMES = Object.freeze([
244
244
  /** Proxy / tunnel apps found by their bundle (their helpers carry other names). */
245
245
  const RELAY_BUNDLE_RE = /\/(?:charles|proxyman|surge(?: mac)?|clashx[^/]*|clash verge|clash for windows|clash nyanpasu|flclash|shadowrocket|shadowsocksx[^/]*|v2rayu|v2rayx|qv2ray|v2rayn|stash|quantumult x|loon|proxifier|mitmproxy|ngrok|tailscale|cloudflare warp)\.app\//i;
246
246
  /** The agents whose process trees never hold the owner's authority (argv[0]'s name). */
247
- export const AGENT_NAMES = Object.freeze(['claude', 'codex', 'opencode', 'gemini', 'cursor-agent', 'aider', 'goose', 'qwen', 'crush']);
247
+ // Antigravity's CLI runs as `antigravity` (the kernel's name for the binary Homebrew links as `agy`). Gemini CLI stays on
248
+ // the list though SynaBun no longer drives it: an agent the user still runs never holds the owner's authority either.
249
+ export const AGENT_NAMES = Object.freeze(['claude', 'codex', 'opencode', 'agy', 'antigravity', 'gemini', 'cursor-agent', 'aider', 'goose', 'qwen', 'crush']);
250
+ /** The agent backend of the Antigravity app and IDE (a `language_server` inside their bundles: Codeium's name, so only by its place). */
251
+ const ANTIGRAVITY_SERVER_RE = /\/antigravity(?: ide)?\.app\/contents\/resources\/(?:app\/extensions\/[^/]+\/)?bin\/language_server/i;
248
252
  /** Browsers the owner browses with (their bundle or their network process): never taken for a relay because they
249
253
  * happen to hold a listening socket; an automation flag on them is still refused. */
250
254
  const BROWSER_BUNDLE_RE = /\/(?:safari|google chrome(?: beta| dev| canary)?|chromium|firefox(?: developer edition| nightly)?|arc|brave browser(?: beta| nightly)?|microsoft edge(?: beta| dev| canary)?|opera(?: gx)?|vivaldi|orion|zen(?: browser)?|thorium|comet|dia|sigmaos|yandex|duckduckgo|sidekick|wavebox|island)\.app\/|\/synabun\.app\/contents\/(?:macos\/synabun(?:\s|$)|frameworks\/synabun helper)/i;
@@ -275,10 +279,12 @@ export function processKinds({ ucomm = '', comm = '', args = '' } = {}) {
275
279
  else if (/(?:^|\s)-juggler(?:-pipe)?(?:\s|$)/.test(la) || /(?:^|\s)--?marionette(?:\s|$)/.test(la)) automation = 'Firefox automation';
276
280
 
277
281
  let agent = has(AGENT_NAMES);
282
+ if (agent === 'agy') agent = 'antigravity';
278
283
  if (!agent) {
279
284
  if (/\/claude\/versions\/[^/\s]+(?:\s|$)/.test(la) || /@anthropic-ai\/claude-code\/cli\.m?js(?:\s|$)/.test(la) || /@anthropic-ai\/claude-agent-sdk[^/\s]*\/claude(?:\s|$)/.test(la)) agent = 'claude';
280
285
  else if (/@openai\/codex\/bin\/codex(?:\.js)?(?:\s|$)/.test(la) || /(?:^|\s|\/)codex\s+(?:app-server|exec|mcp(?:-server)?|proto)\b/.test(la)) agent = 'codex';
281
286
  else if (/(?:^|\s|\/)opencode\s+serve\b/.test(la)) agent = 'opencode';
287
+ else if (/(?:^|\s|\/)(?:agy|antigravity)\s+acp\b/.test(la) || ANTIGRAVITY_SERVER_RE.test(c || a)) agent = 'antigravity';
282
288
  else if (/@google\/gemini-cli\//.test(la)) agent = 'gemini';
283
289
  }
284
290
 
@@ -402,6 +408,27 @@ export function isSynabunMcpServer(proc, { roots = [], cwd = null, realpath = nu
402
408
  return typeof real === 'string' && targets.has(real);
403
409
  }
404
410
 
411
+ // ── Processes SynaBun starts for an agent ────────────────────────────────────
412
+ //
413
+ // A command an agent runs through SynaBun (the Antigravity panel's client terminals, lib/antigravity/acp-client-host.js)
414
+ // is a child of this server, not of the agent, so no agent is in its process tree by name. Its pid is registered here
415
+ // while it runs, and a chain that passes through one is an agent's tree. Like the name rule, a command that detaches
416
+ // from its parent (and so from the chain) is not seen.
417
+ const AGENT_PROCESSES = new Map(); // pid -> label
418
+
419
+ /** A process SynaBun started on an agent's behalf: its tree never holds the owner's authority while it runs. */
420
+ export function registerAgentProcess(pid, label = 'agent command') {
421
+ if (Number.isInteger(pid) && pid > 1) AGENT_PROCESSES.set(pid, String(label || 'agent command').slice(0, 80));
422
+ }
423
+ /** That process ended (its pid can be reused). */
424
+ export function forgetAgentProcess(pid) {
425
+ AGENT_PROCESSES.delete(pid);
426
+ }
427
+ /** The registered processes (for tests and diagnostics). */
428
+ export function agentProcesses() {
429
+ return new Map(AGENT_PROCESSES);
430
+ }
431
+
405
432
  /**
406
433
  * Whether the process at the client end of a connection, and its parents, may hold the owner's authority.
407
434
  * @param {Array<{pid:number, ppid:number, ucomm:string, comm:string, args:string}>} chain the peer first, then its
@@ -409,9 +436,10 @@ export function isSynabunMcpServer(proc, { roots = [], cwd = null, realpath = nu
409
436
  * @param {{ selfPid:number, listening:Set<number>, synabunMcp?:number }} o `synabunMcp`: the peer's pid when it is
410
437
  * SynaBun's own MCP server over HTTP (isSynabunMcpServer): not refused as a relay, refused like an agent's tree
411
438
  * (kind 'synabun-mcp': lib/owner-authority.js takes its pinned calls as an agent's own)
439
+ * `agentPids`: processes SynaBun started for an agent (registerAgentProcess), refused like an agent's tree
412
440
  * @returns {{ self:boolean, refuse: null | { why:string, kind:string, pid:number, name:string } }}
413
441
  */
414
- export function classifyPeerChain(chain, { selfPid = process.pid, listening = new Set(), synabunMcp = 0 } = {}) {
442
+ export function classifyPeerChain(chain, { selfPid = process.pid, listening = new Set(), synabunMcp = 0, agentPids = AGENT_PROCESSES } = {}) {
415
443
  const list = Array.isArray(chain) ? chain : [];
416
444
  if (!list.length) return { self: false, refuse: null };
417
445
  if (list[0].pid === selfPid) return { self: true, refuse: null };
@@ -425,6 +453,8 @@ export function classifyPeerChain(chain, { selfPid = process.pid, listening = ne
425
453
  const k = i === 0 ? peerKinds : processKinds(p);
426
454
  if (k.automation) return refuse(p, 'automation-browser', `a browser started for automation (${k.automation})`);
427
455
  if (k.agent) return refuse(p, 'agent', `an AI agent's process tree (${k.agent})`);
456
+ const startedFor = agentPids?.get?.(p.pid);
457
+ if (startedFor) return refuse(p, 'agent', `an AI agent's process tree (${startedFor}, started by SynaBun)`);
428
458
  if (k.synabun) return refuse(p, 'synabun-instance', 'another SynaBun server');
429
459
  if (k.remoteLogin) return refuse(p, 'remote-login', 'a remote login session (sshd)');
430
460
  // SynaBun's own MCP server over HTTP relays for its MCP clients by design: not refused as a relay or a listener
@@ -19,6 +19,7 @@ import { evaluateFlow } from './flow.js';
19
19
  import { EV, OP, HOST_DEFAULTS } from './protocol.js';
20
20
  import { createEventLoopMonitor } from '../event-loop-monitor.js';
21
21
  import { withPathEnv } from '../augmented-path.js';
22
+ import { killOwnedPty } from './lifetime.js';
22
23
 
23
24
  const IS_WIN = process.platform === 'win32';
24
25
  const IS_LINUX = process.platform === 'linux';
@@ -39,8 +40,14 @@ export function createPtyHostCore({ send, config = {}, pty: injectedPty = null }
39
40
  let pty = injectedPty;
40
41
  let ptyError = null;
41
42
  let shuttingDown = false;
43
+ let shutdownTimer = null;
42
44
 
43
45
  const emit = (msg) => { try { send(msg); } catch {} };
46
+ // Covers process.exit(), including host.js's bounded shutdown, and the
47
+ // in-process transport. This must be synchronous: exit cannot await PTYs.
48
+ const onOwnerExit = () => {
49
+ for (const s of sessions.values()) if (!s.exited) killOwnedPty(s.proc.pid);
50
+ };
44
51
 
45
52
  // ── Startup ──
46
53
  async function init() {
@@ -65,6 +72,10 @@ export function createPtyHostCore({ send, config = {}, pty: injectedPty = null }
65
72
  // ── Spawn ──
66
73
  function spawnSession(spec) {
67
74
  const { id } = spec;
75
+ if (shuttingDown) {
76
+ emit({ t: EV.SPAWN_ERROR, id, message: 'Terminal host is shutting down' });
77
+ return;
78
+ }
68
79
  if (!id || sessions.has(id)) {
69
80
  emit({ t: EV.SPAWN_ERROR, id, message: 'Duplicate or missing session id' });
70
81
  return;
@@ -133,6 +144,7 @@ export function createPtyHostCore({ send, config = {}, pty: injectedPty = null }
133
144
  graceTimer: null,
134
145
  exited: false,
135
146
  };
147
+ if (!sessions.size) process.once('exit', onOwnerExit);
136
148
  sessions.set(id, s);
137
149
  counters.spawns++;
138
150
 
@@ -490,6 +502,11 @@ export function createPtyHostCore({ send, config = {}, pty: injectedPty = null }
490
502
  for (const f of s.tempFiles.splice(0)) unlink(f).catch(() => {});
491
503
  try { s.model.dispose(); } catch {}
492
504
  sessions.delete(s.id);
505
+ if (!sessions.size) {
506
+ process.removeListener('exit', onOwnerExit);
507
+ clearTimeout(shutdownTimer);
508
+ shutdownTimer = null;
509
+ }
493
510
  }
494
511
 
495
512
  function killSession(s, signal) {
@@ -571,7 +588,11 @@ export function createPtyHostCore({ send, config = {}, pty: injectedPty = null }
571
588
  return out;
572
589
  }
573
590
 
574
- function shutdownAll() {
591
+ function shutdownAll({ force = false } = {}) {
592
+ // The server's synchronous exit handler kills itself before later exit
593
+ // listeners can run. Its killNow() path must reap the PTYs right here.
594
+ if (force) onOwnerExit();
595
+ if (shuttingDown) return;
575
596
  shuttingDown = true;
576
597
  for (const s of sessions.values()) {
577
598
  s.draining = true;
@@ -581,6 +602,12 @@ export function createPtyHostCore({ send, config = {}, pty: injectedPty = null }
581
602
  for (const s of sessions.values()) {
582
603
  for (const c of s.clients) { try { c.ws.close(1001, 'terminal host shutting down'); } catch {} }
583
604
  }
605
+ // HUP can be ignored, or arrive while the macOS helper is attaching.
606
+ // Bound the grace period in both transports, even if the owner stays up.
607
+ if (sessions.size) {
608
+ shutdownTimer = setTimeout(onOwnerExit, 100);
609
+ shutdownTimer.unref?.();
610
+ }
584
611
  }
585
612
 
586
613
  function dispose() {
@@ -0,0 +1,12 @@
1
+ // node-pty's macOS helper starts a new session before exec. If its host dies
2
+ // before the helper acquires the controlling terminal, closing the master
3
+ // does not hang it up. Kill the known PTY process group as well as its leader.
4
+ // Callers must forget a PID on EXIT: these are owned children, never a sweep
5
+ // of unrelated processes or a list retained across host restarts.
6
+ export function killOwnedPty(pid, { kill = process.kill, platform = process.platform } = {}) {
7
+ if (!Number.isSafeInteger(pid) || pid <= 1) return;
8
+ if (platform !== 'win32') {
9
+ try { kill(-pid, 'SIGKILL'); } catch {}
10
+ }
11
+ try { kill(pid, 'SIGKILL'); } catch {}
12
+ }
@@ -10,6 +10,7 @@ import { fork } from 'node:child_process';
10
10
  import { fileURLToPath } from 'node:url';
11
11
  import { EV, OP, UPGRADE_HEADER_WHITELIST } from './protocol.js';
12
12
  import { createPtyHostCore } from './core.js';
13
+ import { killOwnedPty } from './lifetime.js';
13
14
 
14
15
  const HOST_PATH = fileURLToPath(new URL('./host.js', import.meta.url));
15
16
  const BACKOFF_MS = [0, 1000, 2000, 4000, 8000, 16000, 30000];
@@ -39,6 +40,7 @@ export function createPtyHostManager({
39
40
  const handlers = new Map(); // id → { onSpawned, onTap, onResized, onClients, onExit }
40
41
  const spawnWaiters = new Map(); // id → waiter
41
42
  const preparing = new Map(); // id → operations queued while PATH is read
43
+ const ptyPids = new Map(); // id → live PTY PID owned by the current host
42
44
  const requests = new Map(); // reqId → { resolve, reject, timer }
43
45
  let reqSeq = 0;
44
46
  let child = null;
@@ -76,6 +78,7 @@ export function createPtyHostManager({
76
78
  releaseHeldSockets();
77
79
  break;
78
80
  case EV.SPAWNED:
81
+ ptyPids.set(m.id, m.pid);
79
82
  try { handlers.get(m.id)?.onSpawned?.(m); } catch (err) { log.warn?.('[pty-host] onSpawned', err?.message); }
80
83
  settleSpawn(m.id, null, m);
81
84
  break;
@@ -97,6 +100,7 @@ export function createPtyHostManager({
97
100
  try { handlers.get(m.id)?.onClients?.(m); } catch {}
98
101
  break;
99
102
  case EV.EXIT: {
103
+ ptyPids.delete(m.id);
100
104
  const h = handlers.get(m.id);
101
105
  handlers.delete(m.id);
102
106
  spawnWaiters.delete(m.id);
@@ -150,6 +154,11 @@ export function createPtyHostManager({
150
154
  }
151
155
 
152
156
  function onChildExit(code, signal) {
157
+ // A SIGKILL cannot run host.js's disconnect/shutdown handlers. In
158
+ // particular, a macOS helper still attaching its terminal may survive
159
+ // closing the PTY master. Reap only this host's still-owned children.
160
+ for (const pid of ptyPids.values()) killOwnedPty(pid);
161
+ ptyPids.clear();
153
162
  child = null;
154
163
  ready = false;
155
164
  hostPid = null;
@@ -358,8 +367,10 @@ export function createPtyHostManager({
358
367
  for (const id of preparing.keys()) settleSpawn(id, new Error('Terminal host is shutting down'));
359
368
  preparing.clear();
360
369
  if (restartTimer) { clearTimeout(restartTimer); restartTimer = null; }
370
+ for (const pid of ptyPids.values()) killOwnedPty(pid);
371
+ ptyPids.clear();
361
372
  try { child?.kill('SIGTERM'); } catch {}
362
- try { core?.shutdownAll(); } catch {}
373
+ try { core?.shutdownAll({ force: true }); } catch {}
363
374
  }
364
375
 
365
376
  return {
@@ -49,7 +49,7 @@ const RANK = Object.freeze({ 'read-only': 0, ask: 1, autonomous: 2 });
49
49
 
50
50
  /** Brain providers that can hold a remote session at Ask and Autonomous (the in-process hook). */
51
51
  export const REMOTE_ENFORCING_PROVIDERS = Object.freeze(['claude-code']);
52
- const PROVIDER_LABELS = Object.freeze({ 'claude-code': 'Claude', codex: 'Codex', opencode: 'OpenCode' });
52
+ const PROVIDER_LABELS = Object.freeze({ 'claude-code': 'Claude', codex: 'Codex', opencode: 'OpenCode', antigravity: 'Antigravity' });
53
53
  export function providerLabel(provider) { return PROVIDER_LABELS[provider] || String(provider || 'this brain'); }
54
54
  /** True for a known brain other than Claude: a remote session on it is read-only. */
55
55
  export function remoteBrainLimited(provider) {
@@ -96,6 +96,9 @@ export const ADMIN_ONLY_PREFIXES = Object.freeze([
96
96
  '/api/project-files',
97
97
  '/api/browse-folder',
98
98
  '/api/codex/plans',
99
+ // Antigravity's own conversations and models: each uncached answer starts the owner's `agy` (its side panel
100
+ // socket is refused to guests too).
101
+ '/api/antigravity',
99
102
  '/api/sidepanel',
100
103
  '/api/sidepanel-runs',
101
104
  '/api/loop',
@@ -41,9 +41,10 @@
41
41
 
42
42
  import { isRulesetHost, readRulesetManifest, renderCoexistenceSnippet, renderRuleset, RULESET_HOSTS } from './render.js';
43
43
 
44
- // The pre-2.0 `format` names, as the onboarding wizard and Settings still send them.
45
- const LEGACY_FORMATS = Object.freeze({ claude: 'claude', codex: 'codex', cursor: 'cursor', generic: 'opencode', opencode: 'opencode', gemini: 'gemini' });
46
- const LEGACY_VERSION_FORMATS = Object.freeze(['claude', 'codex', 'cursor', 'generic', 'gemini']);
44
+ // The pre-2.0 `format` names, as the onboarding wizard and Settings still send them. `gemini` is what a page from
45
+ // before Antigravity replaced Gemini CLI asks for: Antigravity's text, which reads the same files.
46
+ const LEGACY_FORMATS = Object.freeze({ claude: 'claude', codex: 'codex', cursor: 'cursor', generic: 'opencode', opencode: 'opencode', antigravity: 'antigravity', gemini: 'antigravity' });
47
+ const LEGACY_VERSION_FORMATS = Object.freeze(['claude', 'codex', 'cursor', 'generic', 'antigravity']);
47
48
  // A copy someone edited, damaged markers, a CLI that is not there: the request was fine, the files said no.
48
49
  const REFUSED_STATES = new Set(['not-installed', 'modified', 'conflict', 'error']);
49
50
  const REFUSED_CODES = new Set(['REMOVE_INCOMPLETE', 'CHANGED_UNDERNEATH', 'BUSY']);
@@ -133,7 +134,7 @@ export function legacyRulesetPayload(format, { render = renderRuleset } = {}) {
133
134
  const name = String(format || 'claude').toLowerCase();
134
135
  if (name === 'coexistence') return { ok: true, ruleset: renderCoexistenceSnippet().text, format: name };
135
136
  const host = LEGACY_FORMATS[name];
136
- if (!host) return { status: 400, error: `Invalid format: ${name}. Use claude, cursor, generic, gemini, codex, or coexistence.` };
137
+ if (!host) return { status: 400, error: `Invalid format: ${name}. Use claude, cursor, generic, antigravity, codex, or coexistence.` };
137
138
  return { ok: true, ruleset: render(host).text, format: name };
138
139
  }
139
140
 
@@ -155,7 +156,7 @@ export function legacyRulesetVersionsPayload() {
155
156
 
156
157
  /** The two endpoints the pre-2.0 UI reads. Same shapes as before, served from the renderer. */
157
158
  export function registerLegacyRulesetRoutes(app, { render = renderRuleset } = {}) {
158
- // Supports ?format=claude (default) | cursor | generic | gemini | codex | coexistence
159
+ // Supports ?format=claude (default) | cursor | generic | antigravity | codex | coexistence
159
160
  app.get('/api/claude-code/ruleset', (req, res) => {
160
161
  try {
161
162
  const payload = legacyRulesetPayload(req.query.format, { render });
@@ -3,7 +3,7 @@
3
3
  // ═══════════════════════════════════════════
4
4
  //
5
5
  // The rules installer edits files that belong to the user and to other CLIs
6
- // (~/.codex/AGENTS.md, ~/.gemini/GEMINI.md, OpenCode's opencode.json). These
6
+ // (~/.codex/AGENTS.md, ~/.gemini/config/GEMINI.md, OpenCode's opencode.json). These
7
7
  // helpers make each write all-or-nothing, keep a symlinked file a symlink,
8
8
  // refuse to write through a link that points at nothing, keep a copy of what
9
9
  // was there before, never read a corrupt JSON file as `{}`, and never write