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
@@ -6,6 +6,16 @@
6
6
  // Auto-attach pauses new pages, OOPIFs and workers so
7
7
  // their FIRST request is guarded too. Closing this client releases its sessions;
8
8
  // it never closes a user's target or browser.
9
+ //
10
+ // Faults are per target first. A target is protected once its Fetch rules are in
11
+ // place; releasing it (Runtime.runIfWaitingForDebugger) is not part of that, and its
12
+ // reply comes from the target itself once it actually runs: Chromium holds a new
13
+ // service worker until EVERY auto-attached CDP client has released it, and does not
14
+ // answer a stopped worker's Runtime commands until it runs again. A slow or silent
15
+ // reply is therefore never a fault. A paused service/shared worker whose rules cannot
16
+ // be installed stays paused and is retried; pages, frames, dedicated workers and
17
+ // targets already running still fail closed for the whole context, and a faulted
18
+ // context re-arms with a fresh attachment (recoverBrowserNetworkGuard).
9
19
  import { connectGuardCdp } from './browser-guard-cdp.js';
10
20
  const guards = new WeakMap();
11
21
  const endpoints = new WeakMap();
@@ -15,35 +25,68 @@ const AUTO = { autoAttach: true, waitForDebuggerOnStart: true, flatten: true, fi
15
25
  const gone = error => /closed|detached|No session|No target|Target.*not found|Session with given id not found/i.test(error?.message || '');
16
26
  const expiredRequest = error => /^Fetch\.(?:continueRequest|failRequest)$/.test(error?.method || '')
17
27
  && /Invalid InterceptionId|Invalid interception id/i.test(error.message);
28
+ // Workers whose own requests only their own Fetch rules intercept. Dedicated workers
29
+ // are intercepted by their owning page or frame instead.
30
+ const OWN_FETCH_WORKERS = new Set(['service_worker', 'shared_worker']);
31
+ const TIMING = {
32
+ quarantineRetryMs: [1000, 5000, 15000, 60000], // the last delay repeats while the worker exists
33
+ rearmBackoffMs: [2000, 5000, 15000, 30000, 60000], // after a failed re-arm; the last repeats
34
+ recoverTimeoutMs: 10000, // the longest a caller waits for one re-arm
35
+ };
18
36
  export const NETWORK_GUARD_FAULT_EVENT = 'synabun-network-guard-fault';
37
+ export const NETWORK_GUARD_RECOVERED_EVENT = 'synabun-network-guard-recovered';
19
38
 
20
39
  /** Register only externally owned Chromium contexts with an existing endpoint. */
21
- export function registerBrowserNetworkEndpoint(context, endpoint, { connect = connectGuardCdp } = {}) {
22
- endpoints.set(context, { endpoint, connect });
40
+ export function registerBrowserNetworkEndpoint(context, endpoint, { connect = connectGuardCdp, timing } = {}) {
41
+ endpoints.set(context, { endpoint, connect, timing: { ...TIMING, ...timing } });
23
42
  }
24
43
 
25
44
  export function usesNativeBrowserNetworkGuard(context) { return endpoints.has(context); }
26
45
 
27
- class ContextGuard {
28
- get fault() { return this._fault; }
29
- set fault(error) {
30
- if (this.disposed) return;
31
- this._fault = error;
32
- if (error) {
33
- this.context.__browserNetworkGuardError = error.message;
34
- this.context.emit?.(NETWORK_GUARD_FAULT_EVENT, error.message);
35
- }
36
- }
37
- constructor(context) {
38
- this.context = context;
39
- this.rules = new Map();
46
+ /** One flattened CDP connection and every session it holds. Re-arming replaces it whole. */
47
+ class GuardLink {
48
+ constructor(guard) {
49
+ this.guard = guard;
40
50
  this.children = new Map();
41
51
  this.installing = new Set();
52
+ this.timers = new Set();
42
53
  this.fault = null;
43
- this.queue = Promise.resolve();
54
+ this.closed = false;
44
55
  }
45
- patterns() {
46
- return [...new Set([...this.rules.values()].flatMap(rule => rule.patterns))].map(urlPattern => ({ urlPattern, requestStage: 'Request' }));
56
+ /** A fault of this link. It is the context's fault only while this link is the current one. */
57
+ failed(error) {
58
+ if (this.closed) return;
59
+ this.fault ||= error;
60
+ if (this.guard.link === this) this.guard.fault = error;
61
+ }
62
+ later(delay, work) {
63
+ if (this.closed) return;
64
+ const timer = setTimeout(() => { this.timers.delete(timer); if (!this.closed) work(); }, delay);
65
+ timer.unref?.();
66
+ this.timers.add(timer);
67
+ }
68
+ async open() {
69
+ const { endpoint, connect } = endpoints.get(this.guard.context);
70
+ this.connecting = connect(endpoint);
71
+ this.root = await this.connecting;
72
+ if (this.closed) { await this.root.detach(); throw new Error('Network guard disposed during setup'); }
73
+ this.root.on('event', event => this.dispatch(event.sessionId ? this.children.get(event.sessionId) : this.root, event.method, event.params));
74
+ this.root.on('close', () => {
75
+ if (this.closed) return;
76
+ this.failed(new Error('Network guard CDP disconnected; reconnect the browser session'));
77
+ // Cancel pending automation and refuse subsequent actions until a fresh
78
+ // protected attachment exists. Never reinstate context.route on MoreLogin:
79
+ // it would mutate unrelated native traffic and cache even during teardown.
80
+ console.error('[browser-network-guard] CDP disconnected; browser automation refused until the guard re-arms');
81
+ });
82
+ // A page-target attachment can arrive after a popup's initial navigation
83
+ // has started (another CDP client may resume it). The browser Fetch domain
84
+ // closes that gap for selective Document requests only. Attribute each to
85
+ // this profile before deciding; never apply rules to a foreign context.
86
+ await this.configure(this.root);
87
+ await this.root.send('Target.setAutoAttach', AUTO);
88
+ while (this.installing.size) await Promise.all([...this.installing]);
89
+ if (this.fault) throw this.fault;
47
90
  }
48
91
  async configure(child) {
49
92
  // Dedicated workers have no Fetch domain in Chromium. Their requests are
@@ -51,68 +94,89 @@ class ContextGuard {
51
94
  // Playwright's CRNetworkManager workerFrame path. Never resume a worker
52
95
  // until that parent's current selective guard is installed.
53
96
  if (child.target?.type === 'worker') {
54
- if (!child.parent?.guarded || !child.parent.configuration) {
97
+ if (!child.parent?.guarded || child.parent.quarantined || !child.parent.configuration) {
55
98
  throw new Error(`Network guard worker ${child.target.targetId} has no protected network parent`);
56
99
  }
57
100
  await child.parent.configuration;
58
101
  return;
59
102
  }
60
- const patterns = this.patterns().map(pattern => child === this.root ? { ...pattern, resourceType: 'Document' } : pattern);
103
+ const patterns = this.guard.patterns().map(pattern => child === this.root ? { ...pattern, resourceType: 'Document' } : pattern);
61
104
  await child.send(patterns.length ? 'Fetch.enable' : 'Fetch.disable', patterns.length ? { patterns } : {});
62
105
  }
106
+ /** Rules in place for the target and for what it starts next. */
107
+ async protect(child) {
108
+ await (child.configuration = this.configure(child));
109
+ if (this.closed) return;
110
+ await child.send('Target.setAutoAttach', AUTO);
111
+ child.protected = true;
112
+ }
113
+ /** Release a target this client holds paused. Never awaited and never a fault: the
114
+ * reply comes from the target once it actually runs, which another CDP client of the
115
+ * profile, or a stopped service worker, can delay indefinitely. The target's rules are
116
+ * already in place, so its requests are guarded whenever it runs. */
117
+ resume(child) {
118
+ child.send('Runtime.runIfWaitingForDebugger').catch(error => {
119
+ if (this.closed || gone(error)) return;
120
+ console.warn(`[browser-network-guard] ${error.message}; the target keeps its rules and runs when Chromium releases it`);
121
+ });
122
+ }
123
+ /** Whether a failed command's target is gone, which is no fault. A reply can be lost
124
+ * while its target is destroyed, so a timeout asks the browser before deciding. */
125
+ async vanished(child, error) {
126
+ if (this.closed || gone(error) || this.children.get(child.id) !== child) return true;
127
+ try {
128
+ const { targetInfo } = await this.root.send('Target.getTargetInfo', { targetId: child.target.targetId });
129
+ return !targetInfo;
130
+ } catch (probe) {
131
+ return /No target with given id/i.test(probe.protocolMessage || probe.message);
132
+ }
133
+ }
134
+ /** A paused service/shared worker whose rules could not be installed. Nothing else
135
+ * intercepts its requests, so it must not run unprotected: keep holding it paused
136
+ * (no other target is affected), retry, and release it once its rules are in place. */
137
+ quarantine(child, error, attempt = 0) {
138
+ child.quarantined = error;
139
+ const delays = this.guard.timing.quarantineRetryMs;
140
+ const delay = delays[Math.min(attempt, delays.length - 1)];
141
+ if (!attempt) console.error(`[browser-network-guard] ${error.message}; this worker stays paused, retrying its protection`);
142
+ this.later(delay, async () => {
143
+ if (this.children.get(child.id) !== child) return;
144
+ try {
145
+ await this.protect(child);
146
+ } catch (retryError) {
147
+ if (await this.vanished(child, retryError)) return;
148
+ this.quarantine(child, retryError, attempt + 1);
149
+ return;
150
+ }
151
+ if (this.closed) return;
152
+ child.quarantined = null;
153
+ console.warn(`[browser-network-guard] worker ${child.target.targetId} protected after ${attempt + 1} retr${attempt ? 'ies' : 'y'}; releasing it`);
154
+ this.resume(child);
155
+ });
156
+ }
63
157
  async ownsDocument(frameId) {
64
158
  try {
65
159
  const { targetInfo } = await this.root.send('Target.getTargetInfo', { targetId: frameId });
66
- return targetInfo.browserContextId === this.contextId;
160
+ return targetInfo.browserContextId === this.guard.contextId;
67
161
  } catch (error) {
68
162
  // Same-process subframes have frame IDs, but no separate Target. Find
69
163
  // their owning protected page; unrelated incognito frames stay native.
70
164
  if (!/No target with given id found/i.test(error.message)) throw error;
71
165
  const contains = tree => tree.frame.id === frameId || tree.childFrames?.some(contains);
166
+ let uncertain = false;
72
167
  for (const child of this.children.values()) {
73
168
  if (!child.guarded || !['page', 'iframe'].includes(child.target.type)) continue;
74
169
  try {
75
170
  if (contains((await child.send('Page.getFrameTree')).frameTree)) return true;
76
- } catch (error) { if (!gone(error)) throw error; }
171
+ } catch (treeError) { if (!gone(treeError)) uncertain = true; }
77
172
  }
78
- return false;
173
+ // A protected page that cannot list its frames may hold this one. Decide it as
174
+ // the profile's (the stricter answer) instead of faulting every tab.
175
+ return uncertain;
79
176
  }
80
177
  }
81
- async start() {
82
- this.onContextClose = () => { void this.dispose(); };
83
- this.onBrowserDisconnect = () => { void this.dispose(); };
84
- this.context.once('close', this.onContextClose);
85
- this.context.browser().once('disconnected', this.onBrowserDisconnect);
86
- const page = this.context.pages().find(page => !page.isClosed());
87
- if (!page) throw new Error('Network guard requires the attachment tab');
88
- const probe = await this.context.newCDPSession(page);
89
- try { this.contextId = (await probe.send('Target.getTargetInfo')).targetInfo.browserContextId; }
90
- finally { await probe.detach(); }
91
- if (this.disposed) throw new Error('Network guard disposed during setup');
92
- const { endpoint, connect } = endpoints.get(this.context);
93
- this.connecting = connect(endpoint);
94
- this.root = await this.connecting;
95
- if (this.disposed) { await this.root.detach(); throw new Error('Network guard disposed during setup'); }
96
- this.root.on('event', event => this.dispatch(event.sessionId ? this.children.get(event.sessionId) : this.root, event.method, event.params));
97
- this.root.on('close', () => {
98
- if (this.disposed) return;
99
- this.fault = new Error('Network guard CDP disconnected; reconnect the browser session');
100
- // Cancel pending automation and refuse subsequent actions until a fresh
101
- // protected attachment exists. Never reinstate context.route on MoreLogin:
102
- // it would mutate unrelated native traffic and cache even during teardown.
103
- console.error('[browser-network-guard] CDP disconnected; browser automation disabled until reconnection');
104
- });
105
- // A page-target attachment can arrive after a popup's initial navigation
106
- // has started (another CDP client may resume it). The browser Fetch domain
107
- // closes that gap for selective Document requests only. Attribute each to
108
- // this profile before deciding; never apply rules to a foreign context.
109
- await this.configure(this.root);
110
- await this.root.send('Target.setAutoAttach', AUTO);
111
- while (this.installing.size) await Promise.all([...this.installing]);
112
- if (this.fault) throw this.fault;
113
- }
114
178
  dispatch(parent, method, event) {
115
- if (this.disposed) return;
179
+ if (this.closed) return;
116
180
  if (method === 'Target.attachedToTarget') {
117
181
  const child = {
118
182
  id: event.sessionId, parent, target: event.targetInfo,
@@ -130,9 +194,9 @@ class ContextGuard {
130
194
  this.children.set(event.sessionId, child);
131
195
  const installing = this.attach(child, event).catch(error => {
132
196
  if (!gone(error)) {
133
- this.fault = error;
197
+ this.failed(error);
134
198
  // Do not resume an unguarded new target. Existing user tabs are never
135
- // closed. Future tool operations fail until a clean reconnect.
199
+ // closed. Tool operations fail until the guard re-arms.
136
200
  console.error('[browser-network-guard] target protection failed:', error.message);
137
201
  }
138
202
  }).finally(() => this.installing.delete(installing));
@@ -143,35 +207,51 @@ class ContextGuard {
143
207
  } else if (method === 'Fetch.requestPaused') {
144
208
  if (!parent) return; // event queued before this target's detach arrived
145
209
  void this.request(parent, event).catch(error => {
146
- if (!gone(error)) { this.fault = error; console.error('[browser-network-guard] request refused after guard failure:', error.message); }
210
+ if (!gone(error)) { this.failed(error); console.error('[browser-network-guard] request refused after guard failure:', error.message); }
147
211
  });
212
+ } else if (method === 'Inspector.targetReloadedAfterCrash') {
213
+ // A stopped service worker that runs again keeps this session and its rules.
214
+ // Release it as Playwright does, so this client never holds it.
215
+ if (parent?.protected && !parent.quarantined) this.resume(parent);
148
216
  }
149
217
  }
150
218
  async attach(child, event) {
151
- if (this.disposed) return;
219
+ if (this.closed) return;
220
+ const waiting = !!event.waitingForDebugger;
152
221
  const inherited = !event.targetInfo.browserContextId && child.parent?.guarded;
153
- if (event.targetInfo.browserContextId !== this.contextId && !inherited) {
154
- // A browser-level CDP connection also sees other incognito contexts.
155
- if (event.waitingForDebugger) await child.send('Runtime.runIfWaitingForDebugger');
156
- await child.parent.send('Target.detachFromTarget', { sessionId: child.id });
222
+ if (event.targetInfo.browserContextId !== this.guard.contextId && !inherited) {
223
+ // A browser-level CDP connection also sees other incognito contexts. Our
224
+ // session there intercepts nothing: releasing it is never this profile's fault.
225
+ if (waiting) this.resume(child);
226
+ await child.parent.send('Target.detachFromTarget', { sessionId: child.id }).catch(error => {
227
+ if (!gone(error)) console.warn('[browser-network-guard] could not release a foreign target:', error.message);
228
+ });
157
229
  return;
158
230
  }
159
231
  child.guarded = true;
160
- await (child.configuration = this.configure(child));
161
- if (this.disposed) return;
162
- await child.send('Target.setAutoAttach', AUTO);
163
- await child.send('Runtime.runIfWaitingForDebugger');
232
+ try {
233
+ await this.protect(child);
234
+ } catch (error) {
235
+ if (await this.vanished(child, error)) return;
236
+ if (waiting && OWN_FETCH_WORKERS.has(child.target.type)) { this.quarantine(child, error); return; }
237
+ // Pages, frames, dedicated workers and anything already running fail closed.
238
+ throw error;
239
+ }
240
+ if (this.closed) return;
241
+ // Only a target that waits for us is released; a running or stopped one is not.
242
+ if (waiting) this.resume(child);
164
243
  }
165
244
  async request(child, event) {
166
245
  const { requestId, request } = event;
167
246
  try {
168
- if (this.fault) throw this.fault;
247
+ const fault = this.fault || this.guard.fault;
248
+ if (fault) throw fault;
169
249
  if (child === this.root && !await this.ownsDocument(event.frameId)) {
170
250
  await child.send('Fetch.continueRequest', { requestId });
171
251
  return;
172
252
  }
173
253
  let headers;
174
- for (const rule of this.rules.values()) {
254
+ for (const rule of this.guard.rules.values()) {
175
255
  const decision = rule.decide(request);
176
256
  if (decision?.abort) {
177
257
  await child.send('Fetch.failRequest', { requestId, errorReason: 'BlockedByClient' });
@@ -195,36 +275,148 @@ class ContextGuard {
195
275
  throw error;
196
276
  }
197
277
  }
278
+ /** Current rules onto every protected target of this link. */
279
+ async reconfigure() {
280
+ await this.configure(this.root);
281
+ // A quarantined worker takes the current rules on its next retry.
282
+ await Promise.all([...this.children.values()].filter(child => child.guarded && !child.quarantined).map(async child => {
283
+ try { await (child.configuration = this.configure(child)); }
284
+ catch (error) { if (!await this.vanished(child, error)) throw error; }
285
+ }));
286
+ }
287
+ close() {
288
+ if (this.closing) return this.closing;
289
+ this.closed = true;
290
+ for (const timer of this.timers) clearTimeout(timer);
291
+ this.timers.clear();
292
+ // Detaching our browser CDP session also detaches its child sessions and
293
+ // releases paused targets. No Target.closeTarget or Browser.close.
294
+ this.closing = (async () => {
295
+ // Closing may race a handshake. Await and detach that late connection
296
+ // as well; returning early would briefly leave a new client attached.
297
+ if (!this.root && this.connecting) this.root = await this.connecting.catch(() => null);
298
+ await this.root?.detach().catch(() => {});
299
+ for (const child of this.children.values()) child.close();
300
+ this.children.clear();
301
+ })();
302
+ return this.closing;
303
+ }
304
+ }
305
+
306
+ class ContextGuard {
307
+ get fault() { return this._fault; }
308
+ set fault(error) {
309
+ if (this.disposed) return;
310
+ this._fault = error;
311
+ if (error) {
312
+ this.context.__browserNetworkGuardError = error.message;
313
+ this.context.emit?.(NETWORK_GUARD_FAULT_EVENT, error.message);
314
+ }
315
+ }
316
+ constructor(context) {
317
+ this.context = context;
318
+ this.timing = endpoints.get(context)?.timing || TIMING;
319
+ this.rules = new Map();
320
+ this.fault = null;
321
+ this.queue = Promise.resolve();
322
+ this.rearmFailures = 0;
323
+ this.rearmAt = 0;
324
+ }
325
+ patterns() {
326
+ return [...new Set([...this.rules.values()].flatMap(rule => rule.patterns))].map(urlPattern => ({ urlPattern, requestStage: 'Request' }));
327
+ }
328
+ async start() {
329
+ this.onContextClose = () => { void this.dispose(); };
330
+ this.onBrowserDisconnect = () => { void this.dispose(); };
331
+ this.context.once('close', this.onContextClose);
332
+ this.context.browser().once('disconnected', this.onBrowserDisconnect);
333
+ const page = this.context.pages().find(page => !page.isClosed());
334
+ if (!page) throw new Error('Network guard requires the attachment tab');
335
+ const probe = await this.context.newCDPSession(page);
336
+ try { this.contextId = (await probe.send('Target.getTargetInfo')).targetInfo.browserContextId; }
337
+ finally { await probe.detach(); }
338
+ if (this.disposed) throw new Error('Network guard disposed during setup');
339
+ this.link = new GuardLink(this);
340
+ await this.link.open();
341
+ }
198
342
  async update(key, rule) {
199
343
  if (this.disposed) throw new Error('Network guard disposed');
200
344
  if (this.fault) throw this.fault;
201
345
  if (rule) this.rules.set(key, rule); else this.rules.delete(key);
202
346
  if (!this.ready) this.ready = this.start().catch(async error => { await this.dispose(); throw error; });
203
347
  await this.ready;
204
- await this.configure(this.root);
205
- await Promise.all([...this.children.values()].filter(child => child.guarded).map(child =>
206
- (child.configuration = this.configure(child)).catch(error => { if (!gone(error)) throw error; })));
348
+ await this.link.reconfigure();
349
+ }
350
+ /** Re-arm a faulted guard; resolves true once the context is protected again. One
351
+ * attempt at a time, in order with rule updates, and none during the backoff that
352
+ * follows a failed one. */
353
+ rearm() {
354
+ if (this.disposed || !this.link) return Promise.resolve(false);
355
+ if (!this.fault) return Promise.resolve(true);
356
+ if (this.rearming) return this.rearming;
357
+ if (Date.now() < this.rearmAt) return Promise.resolve(false);
358
+ const attempt = this.queue.then(() => this.replaceLink());
359
+ this.queue = attempt.catch(() => {});
360
+ this.rearming = attempt.catch(() => false).finally(() => { this.rearming = null; });
361
+ return this.rearming;
362
+ }
363
+ async replaceLink() {
364
+ if (this.disposed) return false;
365
+ if (!this.fault) return true;
366
+ // The faulted link stays attached (failing its own paused requests closed) until
367
+ // the fresh one has every current target protected, so no target is ever left
368
+ // without an attachment holding its rules.
369
+ const fresh = this.pendingLink = new GuardLink(this);
370
+ try {
371
+ await fresh.open();
372
+ } catch (error) {
373
+ await fresh.close();
374
+ const delays = this.timing.rearmBackoffMs;
375
+ this.rearmAt = Date.now() + delays[Math.min(this.rearmFailures++, delays.length - 1)];
376
+ console.error('[browser-network-guard] re-arm failed; actions stay refused:', error.message);
377
+ return false;
378
+ } finally { this.pendingLink = null; }
379
+ if (this.disposed) { await fresh.close(); return false; }
380
+ const old = this.link;
381
+ this.link = fresh;
382
+ this.rearmFailures = 0;
383
+ this.rearmAt = 0;
384
+ this._fault = null;
385
+ delete this.context.__browserNetworkGuardError;
386
+ this.context.emit?.(NETWORK_GUARD_RECOVERED_EVENT);
387
+ console.warn('[browser-network-guard] re-armed with a fresh protected attachment');
388
+ await old.close();
389
+ return true;
207
390
  }
208
391
  dispose() {
209
392
  if (this.disposal) return this.disposal;
210
393
  this.disposed = true;
211
394
  if (this.onContextClose) this.context.removeListener('close', this.onContextClose);
212
395
  if (this.onBrowserDisconnect) this.context.browser().removeListener('disconnected', this.onBrowserDisconnect);
213
- // Detaching our browser CDP session also detaches its child sessions and
214
- // releases paused targets. No Target.closeTarget or Browser.close.
215
396
  this.disposal = (async () => {
216
- // Disposal may race a handshake. Await and detach that late connection
217
- // as well; returning early would briefly leave a new client attached.
218
- if (!this.root && this.connecting) this.root = await this.connecting.catch(() => null);
219
- await this.root?.detach().catch(() => {});
220
- for (const child of this.children.values()) child.close();
221
- this.children.clear();
397
+ await Promise.all([this.link?.close(), this.pendingLink?.close()]);
222
398
  if (guards.get(this.context) === this) guards.delete(this.context);
223
399
  })();
224
400
  return this.disposal;
225
401
  }
226
402
  }
227
403
 
404
+ /** Re-arm a faulted native guard: a fresh protected attachment replaces the broken one,
405
+ * so the context needs no server restart. Resolves true when the context is protected
406
+ * (again), false while its actions stay refused. Never rejects. */
407
+ export async function recoverBrowserNetworkGuard(context, { timeoutMs } = {}) {
408
+ const guard = guards.get(context);
409
+ if (!guard) return !context?.__browserNetworkGuardError;
410
+ let timer;
411
+ try {
412
+ return await Promise.race([
413
+ guard.rearm(),
414
+ new Promise(resolve => { timer = setTimeout(() => resolve(false), timeoutMs ?? guard.timing.recoverTimeoutMs); timer.unref?.(); }),
415
+ ]);
416
+ } catch { return false; }
417
+ finally { clearTimeout(timer); }
418
+ }
419
+
228
420
  /** Explicitly release only this context's safety transport, including failed setup. */
229
421
  export async function disposeBrowserNetworkGuard(context) {
230
422
  await guards.get(context)?.dispose();
@@ -2,7 +2,7 @@
2
2
  // SynaBun — Browser tool policy: every page through the SynaBun browser
3
3
  // ═══════════════════════════════════════════
4
4
  //
5
- // The Assistant's brain (Claude, Codex, OpenCode) and the workers it
5
+ // The Assistant's brain (Claude, Codex, OpenCode, Antigravity) and the workers it
6
6
  // dispatches look at pages — public or localhost — only through SynaBun's
7
7
  // browser tools (browser_*), which open the browser configured in SynaBun
8
8
  // (MoreLogin). They never start a browser of their own, and when the SynaBun
@@ -48,13 +48,14 @@ const MAX_DEPTH = 4;
48
48
 
49
49
  // ── the rule text ───────────────────────────────────────────────────────────
50
50
 
51
- const HOSTS = { claude: 'claude', 'claude-code': 'claude', codex: 'codex', opencode: 'opencode' };
51
+ const HOSTS = { claude: 'claude', 'claude-code': 'claude', codex: 'codex', opencode: 'opencode', antigravity: 'antigravity' };
52
52
  const hostKey = (host) => HOSTS[String(host || '').toLowerCase()] || null;
53
53
  // Each host's own web search / fetch (the rule names the host's; unknown: all of them).
54
54
  const WEB_TOOL_WORDS = {
55
55
  claude: 'WebSearch / WebFetch',
56
56
  codex: 'Codex\'s web_search',
57
57
  opencode: 'webfetch / websearch',
58
+ antigravity: 'Antigravity\'s own web search, URL reading or browser',
58
59
  };
59
60
 
60
61
  /**
@@ -78,7 +79,7 @@ const TEST_RUN_RULE = 'Automated tests run through their test runner (npm test,
78
79
  /**
79
80
  * The browser rule, as bullet lines: the brain's persona (its host's tool
80
81
  * names in `tools`), the worker prompts of browser runs. `host`: claude /
81
- * codex / opencode (names that host's own web tools), else all of them.
82
+ * codex / opencode / antigravity (names that host's own web tools), else all of them.
82
83
  */
83
84
  export function browserPolicyText({ tools = '', host = null } = {}) {
84
85
  const t = browserToolNames(tools);
@@ -114,9 +115,11 @@ const OPENCODE_BROWSER_TOOL = new RegExp(`^([a-z0-9-]*?(?:${BROWSER_SERVER_WORD}
114
115
  const PAGE_TOOL = /^(?:browser_.+|navigate(?:_(?:page|to|to_url|url|back|forward))?|go_?to(?:_(?:url|page))?|go_(?:back|forward)|open_(?:url|page|link|website|webpage|browser|in_browser)|new_page|launch_browser|(?:take|capture)_(?:page_|full_page_|web_?page_|website_)?screenshot|(?:page|full_page|web_?page|website)_screenshot|screenshot(?:_(?:page|url|website|webpage))?)$/;
115
116
  const snakeName = (text) => String(text || '').replace(/([a-z0-9])([A-Z])/g, '$1_$2').replace(/[-.\s]+/g, '_').toLowerCase();
116
117
  // Web search / fetch outside the SynaBun browser: Claude's, OpenCode's, Codex's.
117
- const WEB_TOOLS = new Set(['websearch', 'webfetch', 'web_search', 'web_fetch', 'web_search_preview']);
118
+ const WEB_TOOLS = new Set(['websearch', 'webfetch', 'web_search', 'web_fetch', 'web_search_preview', 'search_web', 'read_url_content', 'execute_url']);
119
+ // Antigravity's own browser and browser subagent (its tools' own names, lib/antigravity/tool-call.js).
120
+ const ANTIGRAVITY_BROWSER_TOOLS = new Set(['open_browser_url', 'read_browser_page', 'browser_subagent', 'list_browser_pages', 'capture_browser_screenshot', 'capture_browser_console_logs', 'execute_browser_javascript', 'click_browser_pixel']);
118
121
  // Shell tools: Claude's Bash / PowerShell, Codex's shell family, OpenCode's bash.
119
- const SHELL_TOOLS = new Set(['bash', 'powershell', 'shell', 'shell_command', 'exec_command', 'local_shell', 'container.exec', 'unified_exec']);
122
+ const SHELL_TOOLS = new Set(['bash', 'powershell', 'shell', 'shell_command', 'exec_command', 'local_shell', 'container.exec', 'unified_exec', 'run_command']);
120
123
 
121
124
  /**
122
125
  * What another MCP server's tool does to a browser (for the refusal), else null:
@@ -983,8 +986,8 @@ function argvDenial(argv, ctx) {
983
986
  }
984
987
 
985
988
  // Where each host puts a shell tool's command and its working directory.
986
- const COMMAND_KEYS = ['command', 'cmd', 'commands', 'script'];
987
- const WORKDIR_KEYS = ['workdir', 'cwd', 'working_directory', 'workingDirectory', 'dir'];
989
+ const COMMAND_KEYS = ['command', 'cmd', 'commands', 'script', 'CommandLine'];
990
+ const WORKDIR_KEYS = ['workdir', 'cwd', 'working_directory', 'workingDirectory', 'dir', 'Cwd'];
988
991
 
989
992
  function shellDenial(input, ctx) {
990
993
  const args = input && typeof input === 'object' ? input : {};
@@ -1043,6 +1046,8 @@ export function browserToolDenial(tool, input = {}, { host = null, cwd = null, r
1043
1046
  if (WEB_TOOLS.has(lower)) return refusal(name, 'web search / fetch outside the SynaBun browser');
1044
1047
  // OpenCode's own browser tool drives a browser that is not SynaBun's.
1045
1048
  if (kind === 'opencode' && lower === 'browser') return refusal(name, 'OpenCode\'s own browser');
1049
+ // Antigravity's own browser and browser subagent (its built-in tools carry no server: never SynaBun's browser_*).
1050
+ if (kind === 'antigravity' && (ANTIGRAVITY_BROWSER_TOOLS.has(lower) || /^browser_/.test(lower))) return refusal(name, 'Antigravity\'s own browser');
1046
1051
  if (!SHELL_TOOLS.has(lower)) return null;
1047
1052
  const project = cwd ? resolve(String(cwd)) : process.cwd();
1048
1053
  const why = shellDenial(args, { roots: testRoots(project, roots), cwd: project, readFile: typeof readFile === 'function' ? readFile : defaultReadFile, depth: 0 });
@@ -104,6 +104,7 @@ export const PANEL_CAPABILITIES = Object.freeze([
104
104
  'ask_annotations', // AskUserQuestion answers keep their `annotations`
105
105
  'tool_policy', // query `toolPolicy`: 'full' | 'read-only' | 'no-web'
106
106
  'elicitation', // MCP elicitation as control_request subtype `elicitation` (for clients that declare the feature)
107
+ 'effort_off', // query `effort: 'off'`: thinking disabled, over any effortLevel in the user's settings
107
108
  'session_settings', // query `session`: fallback model, limits, directories, plugins, strict MCP, instructions, sandbox, thinking, fast mode, output style, agent
108
109
  'reload_plugins', // inbound `reload_plugins` → `reload_result` + fresh commands_list / mcp_status / plugins_list
109
110
  'message_flags', // session.hookEvents / promptSuggestions / subagentText / agentSummaries → the matching SDK options
@@ -754,6 +755,9 @@ export class ClaudeSession {
754
755
  if (this.sessionId && this._resumeAt) { options.resumeSessionAt = this._resumeAt; this._resumeAt = null; }
755
756
  }
756
757
  if (this.model) options.model = this.model;
758
+ // Off is the user's pick, not the model's default: without it an
759
+ // effortLevel in the user's settings would still apply.
760
+ if (this.panel && this.effort === 'off') options.thinking = { type: 'disabled' };
757
761
  if (this.effort && CLAUDE_EFFORT_LEVELS.includes(this.effort)) {
758
762
  // Panel sessions use the typed option; every other caller keeps the flag it had.
759
763
  if (this.panel) options.effort = this.effort;
@@ -2966,6 +2970,7 @@ export class ClaudeSession {
2966
2970
  if (cwd) this.cwd = cwd;
2967
2971
  // A panel tab that sends no effort means "the model's default": a level that
2968
2972
  // was set goes back to it, live when a session exists.
2973
+ const prevEffort = this.effort;
2969
2974
  const effortCleared = this.panel && !effort && !!this.effort;
2970
2975
  if (effort) this.effort = effort;
2971
2976
  else if (effortCleared) this.effort = null;
@@ -3006,7 +3011,9 @@ export class ClaudeSession {
3006
3011
 
3007
3012
  // Effort alone switches on the live CLI. A restart would cold-start the
3008
3013
  // session and kill any background agents it is running.
3014
+ // (Off disables thinking, a start-up option: to or from it the session restarts.)
3009
3015
  if (this.q && effortChanged && !cwdChanged && !sessionChanged && !newChat
3016
+ && prevEffort !== 'off' && effort !== 'off'
3010
3017
  && typeof this.q.applyFlagSettings === 'function') {
3011
3018
  const effortLevel = CLAUDE_EFFORT_LEVELS.includes(effort) ? effort : null;
3012
3019
  this.q.applyFlagSettings({ effortLevel }).catch(err => log('applyFlagSettings(effort) failed:', err.message));