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
@@ -1,6 +1,6 @@
1
1
  // ═══════════════════════════════════════════
2
2
  // SynaBun — CLI Status (shared)
3
- // Detects whether Claude / Codex / OpenCode CLIs are installed by
3
+ // Detects whether Claude / Codex / OpenCode / Antigravity CLIs are installed by
4
4
  // querying /api/system/tool-versions. Caches the response, fans out
5
5
  // updates to subscribers, and auto-polls (every 30s) while at least
6
6
  // one subscribed tool is not installed (missing, or found but broken).
@@ -17,33 +17,34 @@ import { cliStateOf, cliInstallOffer, INSTALLABLE_CLIS } from './cli-install-vie
17
17
 
18
18
  export { cliStateOf, cliInstallOffer };
19
19
 
20
- const TOOL_KEYS = ['claude-code', 'codex', 'opencode', 'gemini'];
20
+ const TOOL_KEYS = ['claude-code', 'codex', 'opencode', 'antigravity'];
21
21
 
22
22
  // The vendors' install pages. The server's `guideUrl` wins when it sends one.
23
23
  const CLI_DOC_URLS = {
24
24
  'claude-code': 'https://code.claude.com/docs/en/setup',
25
25
  'codex': 'https://learn.chatgpt.com/docs/codex/cli',
26
26
  'opencode': 'https://opencode.ai/v2/docs',
27
- 'gemini': 'https://github.com/google-gemini/gemini-cli',
27
+ 'antigravity': 'https://antigravity.google/docs/getting-started',
28
28
  };
29
29
 
30
30
  // Fallback only. The server computes the command that fits the machine (the
31
31
  // vendor's recommended installer for its system, or the package manager the
32
- // other CLIs came from: buildInstallCommand in lib/cli-update-planner.js) and
33
- // returns it as `installCommand`. These apply only before the first
34
- // tool-versions response.
32
+ // other CLIs came from: buildInstallCommand in lib/cli-update-planner.js; for
33
+ // the Antigravity CLI, the command of the install its button runs there, which
34
+ // is Homebrew's cask on a Mac that has Homebrew) and returns it as
35
+ // `installCommand`. These apply only before the first tool-versions response.
35
36
  const CLI_INSTALL_CMDS = {
36
37
  'claude-code': { posix: 'curl -fsSL https://claude.ai/install.sh | bash', windows: 'irm https://claude.ai/install.ps1 | iex' },
37
38
  'codex': { posix: 'curl -fsSL https://chatgpt.com/codex/install.sh | sh', windows: 'powershell -ExecutionPolicy ByPass -c "irm https://chatgpt.com/codex/install.ps1 | iex"' },
38
39
  'opencode': { posix: 'curl -fsSL https://opencode.ai/v2/install | bash', windows: 'winget install --id Anomaly.OpenCode --exact' },
39
- 'gemini': { posix: 'npm install -g @google/gemini-cli', windows: 'npm install -g @google/gemini-cli' },
40
+ 'antigravity': { posix: 'curl -fsSL https://antigravity.google/cli/install.sh | bash', windows: 'irm https://antigravity.google/cli/install.ps1 | iex' },
40
41
  };
41
42
 
42
43
  const CLI_LABELS = {
43
44
  'claude-code': 'Claude Code',
44
45
  'codex': 'Codex',
45
46
  'opencode': 'OpenCode',
46
- 'gemini': 'Gemini CLI',
47
+ 'antigravity': 'Antigravity CLI',
47
48
  };
48
49
 
49
50
  const POLL_INTERVAL_MS = 30000;
@@ -66,6 +66,7 @@ export const KEYS = {
66
66
  ACTIVE_WORKSPACE: 'neural-active-workspace', // sessionStorage
67
67
  GRID_SNAP: 'neural-grid-snap',
68
68
  TOPRIGHT_TOOLS_HIDDEN: 'neural-topright-tools-hidden', // the workspace tools toggle is off ('true')
69
+ WHITEBOARD_TOOLBAR_HIDDEN: 'neural-whiteboard-toolbar-hidden', // the whiteboard rail (#wb-toolbar) is hidden behind the logo button ('true')
69
70
  CATEGORIES_VISIBLE: 'neural-categories-visible',
70
71
  WHITEBOARD: 'neural-whiteboard',
71
72
  WHITEBOARD_RULESET_ALERT_DISMISSED: 'neural-whiteboard-ruleset-alert-dismissed',
@@ -127,7 +128,7 @@ export const DEFAULT_KEYBINDS = {
127
128
  'toggle-assistant': 'Ctrl+a',
128
129
  'launch-claude': '1',
129
130
  'launch-codex': '2',
130
- 'launch-gemini': '3',
131
+ 'launch-antigravity': '3',
131
132
  'launch-opencode': '4',
132
133
  'launch-browser': 'b',
133
134
  'launch-youtube': 'y',
@@ -154,7 +155,7 @@ export const KEYBIND_META = {
154
155
  'toggle-assistant-panel': { label: 'Toggle Assistant Sidepanel', group: 'Launch App', icon: 'assistant' },
155
156
  'launch-claude': { label: 'Claude Code', group: 'Launch App', icon: 'claude' },
156
157
  'launch-codex': { label: 'Codex CLI', group: 'Launch App', icon: 'codex' },
157
- 'launch-gemini': { label: 'Gemini CLI', group: 'Launch App', icon: 'gemini' },
158
+ 'launch-antigravity': { label: 'Antigravity CLI', group: 'Launch App', icon: 'antigravity' },
158
159
  'launch-opencode': { label: 'OpenCode', group: 'Launch App', icon: 'opencode' },
159
160
  'launch-browser': { label: 'Browser', group: 'Launch App', icon: 'browser' },
160
161
  'launch-youtube': { label: 'YouTube', group: 'Launch App', icon: 'youtube' },
@@ -90,6 +90,7 @@ export const OPEN_ENTRIES = [
90
90
  { id: 'open-claude-panel', key: 'claudePanel', action: 'toggle-claude-panel', active: 'topright-claude-panel-btn', appIcon: 'menu-terminal-claude', icon: 'panel' },
91
91
  { id: 'open-codex-panel', key: 'codexPanel', press: 'topright-codex-panel-btn', active: 'topright-codex-panel-btn', appIcon: 'menu-terminal-codex', icon: 'panel' },
92
92
  { id: 'open-opencode-panel', key: 'opencodePanel', press: 'topright-opencode-panel-btn', active: 'topright-opencode-panel-btn', appIcon: 'menu-terminal-opencode', icon: 'panel' },
93
+ { id: 'open-antigravity-panel', key: 'antigravityPanel', press: 'topright-antigravity-panel-btn', active: 'topright-antigravity-panel-btn', appIcon: 'menu-terminal-antigravity', icon: 'panel' },
93
94
  null,
94
95
  { id: 'open-terminal', key: 'terminal', event: ['terminal:open', { profile: 'shell' }], perm: 'terminal', appIcon: 'menu-terminal-shell', icon: 'terminal' },
95
96
  { id: 'open-claude-terminal', key: 'claudeTerminal', action: 'launch-claude', perm: 'terminal', icon: 'terminal' },
@@ -6,6 +6,7 @@
6
6
 
7
7
  import { normalizeSession } from './cp-session-model.js';
8
8
  import { TOOL_POLICIES, BYPASS_MODE } from './cp-permission-model.js';
9
+ import { applyTaskTool } from './cp-tool-results.js';
9
10
 
10
11
  const ACCOUNT_ID = /^[A-Za-z0-9_-]{1,64}$/;
11
12
  const PERMISSION_MODES = new Set(['default', 'acceptEdits', 'plan', 'bypassPermissions', 'dontAsk', 'auto']);
@@ -222,3 +223,164 @@ export function pagerState(dataset, labelText = '') {
222
223
  if (!Number.isInteger(start) || !Number.isInteger(total) || start <= 0 || total <= start) return null;
223
224
  return { start, total };
224
225
  }
226
+
227
+ /**
228
+ * Split a history page into `{ head, tail }` so the visible tail (~40 rows) can
229
+ * paint and scroll immediately while earlier rows prepend in chunked tasks.
230
+ * Never splits an `assistant` `tool_use` from its matching `tool_result`.
231
+ */
232
+ export function splitHistoryTail(messages, tailSize = 40) {
233
+ const list = Array.isArray(messages) ? messages : [];
234
+ const cap = Math.max(1, Number(tailSize) || 40);
235
+ if (list.length <= Math.ceil(cap * 1.5)) return { head: [], tail: list };
236
+ let cut = list.length - cap;
237
+ const indexOfToolUse = new Map();
238
+ for (let i = 0; i < list.length; i++) {
239
+ const m = list[i];
240
+ if (m?.role === 'assistant' && Array.isArray(m.tools)) {
241
+ for (const t of m.tools) if (t?.id) indexOfToolUse.set(String(t.id), i);
242
+ }
243
+ }
244
+ for (let i = cut; i < list.length; i++) {
245
+ const m = list[i];
246
+ if (m?.role === 'tool_result' && m.toolUseId) {
247
+ const ownerIdx = indexOfToolUse.get(String(m.toolUseId));
248
+ if (ownerIdx !== undefined && ownerIdx < cut) cut = ownerIdx;
249
+ }
250
+ }
251
+ while (cut > 0 && list[cut]?.role === 'tool_result') cut--;
252
+ if (cut <= 0) return { head: [], tail: list };
253
+ return { head: list.slice(0, cut), tail: list.slice(cut) };
254
+ }
255
+
256
+ /**
257
+ * Chronologically replay TaskCreate / TaskUpdate / TaskList results from a
258
+ * history page so tail-first DOM rendering still preserves exact todo order.
259
+ */
260
+ export function replayTodosFromMessages(messages, initialTodos = []) {
261
+ const list = Array.isArray(messages) ? messages : [];
262
+ const calls = new Map();
263
+ for (const m of list) {
264
+ if (m?.role === 'assistant' && Array.isArray(m.tools)) {
265
+ for (const t of m.tools) {
266
+ if (t?.id && (t.name === 'TaskCreate' || t.name === 'TaskUpdate' || t.name === 'TaskList')) {
267
+ calls.set(String(t.id), t);
268
+ }
269
+ }
270
+ }
271
+ }
272
+ if (!calls.size) return null;
273
+ let todos = Array.isArray(initialTodos) ? initialTodos : [];
274
+ let changed = false;
275
+ for (const m of list) {
276
+ if (m?.role !== 'tool_result' || m.isError || !m.toolUseId) continue;
277
+ const call = calls.get(String(m.toolUseId));
278
+ if (!call) continue;
279
+ const next = applyTaskTool(todos, call.name, call.input || {}, m.structured);
280
+ if (next) {
281
+ todos = next;
282
+ changed = true;
283
+ }
284
+ }
285
+ return changed ? todos : null;
286
+ }
287
+
288
+ // ── One transcript container, several history loads ──
289
+ // A container can be asked for its history more than once while a load is still
290
+ // running (a restored tab shown while its reattach says "resynced", an in-tab
291
+ // session switch). Loads of one container run one after the other, and only the
292
+ // newest asked for renders; rows a rebuild put in are marked so the next one
293
+ // takes them out instead of appending a second copy after them. Rows that came
294
+ // live stay, after the history.
295
+
296
+ /** The property a history row carries: the load (ticket) that rendered it. */
297
+ export const HISTORY_ROW = '_cpHistoryRow';
298
+
299
+ export function markHistoryRows(nodes, ticket) {
300
+ for (const n of nodes || []) if (n && typeof n === 'object') n[HISTORY_ROW] = ticket;
301
+ }
302
+
303
+ /** Rows an earlier rebuild (finished, aborted, or a snapshot put back) left in the container. */
304
+ export function earlierHistoryRows(children, ticket) {
305
+ return [...(children || [])].filter((n) => n && n[HISTORY_ROW] != null && n[HISTORY_ROW] !== ticket);
306
+ }
307
+
308
+ /** Rows that came live: no history mark and not the empty-state placeholder. */
309
+ export function liveRows(children) {
310
+ return [...(children || [])].filter((n) => n && n[HISTORY_ROW] == null && !n.classList?.contains?.('cp-empty'));
311
+ }
312
+
313
+ /**
314
+ * Starts a load of `$msgs`: it waits for the one running there and is skipped when
315
+ * a newer load was asked for meanwhile. `run(isStale)` does the work; `isStale()`
316
+ * is true as soon as a newer load was asked for (the running one stops at its next
317
+ * yield). Returns the promise of this load.
318
+ */
319
+ export function queueHistoryLoad(container, run) {
320
+ const ticket = (container._historyTicket || 0) + 1;
321
+ container._historyTicket = ticket;
322
+ const isStale = () => container._historyTicket !== ticket;
323
+ const prev = container._historyChain || Promise.resolve();
324
+ const job = prev.catch(() => {}).then(() => (isStale() ? undefined : run(isStale, ticket)));
325
+ container._historyChain = job;
326
+ return job;
327
+ }
328
+
329
+ // Messages that put rows in a transcript. One of these for a restored tab whose
330
+ // history is still deferred loads that history first; while any history load of
331
+ // the tab runs, they wait in the tab's buffer, so the history always comes first.
332
+ const TRANSCRIPT_MESSAGES = new Set(['event', 'turn_started', 'stderr', 'done', 'aborted', 'error']);
333
+
334
+ /** 'load' (start the deferred history, then hold the message), 'hold', or 'pass'. */
335
+ export function historyGate(tab, msg, bypass = false) {
336
+ if (!tab || bypass) return 'pass';
337
+ const deferred = !!tab._historyDeferred && tab._historyDeferred === tab.sessionId && !tab.automationRunId;
338
+ if (deferred && TRANSCRIPT_MESSAGES.has(msg?.type)) return 'load';
339
+ if (tab._historyHold > 0) return 'hold';
340
+ return 'pass';
341
+ }
342
+
343
+ /**
344
+ * A live tool result whose card a running replay has not rendered yet (it sits in
345
+ * a head chunk still to come) is kept here and applied when the card is built.
346
+ */
347
+ export function holdPendingResult(tab, ev) {
348
+ if (!tab || !ev?.tool_use_id || !(tab._historyRendering > 0) || tab._replayRow) return false;
349
+ if (!(tab._pendingResults instanceof Map)) tab._pendingResults = new Map();
350
+ tab._pendingResults.set(String(ev.tool_use_id), ev);
351
+ return true;
352
+ }
353
+
354
+ export function takePendingResult(tab, toolUseId) {
355
+ const map = tab?._pendingResults;
356
+ if (!(map instanceof Map) || !toolUseId) return null;
357
+ const ev = map.get(String(toolUseId)) || null;
358
+ if (ev) map.delete(String(toolUseId));
359
+ return ev;
360
+ }
361
+
362
+ /** Whether a task tool's result may change the todo list now (a replayed one is in the precomputed list). */
363
+ export function taskResultAppliesTodos(tab, ev) {
364
+ if (!tab || tab._agentScope || ev?.is_error) return false;
365
+ return !(tab._skipReplayTodos && tab._replayRow);
366
+ }
367
+
368
+ /**
369
+ * Renders `rows` with `render(row)` (which appends to `container`), marks the
370
+ * nodes it added as load `ticket`'s and moves them before `before` (a live row,
371
+ * or the first row of the tail already shown), so rows already on screen are
372
+ * never moved or rebuilt: the browser's scroll anchoring keeps a reader who
373
+ * scrolled up where they were while older rows go in above. `skip` holds nodes
374
+ * that are never taken as added (the live rows). Returns the added nodes.
375
+ */
376
+ export function renderHistoryRowsInto(container, rows, render, { before = null, ticket = null, skip = null } = {}) {
377
+ const marker = container.lastElementChild;
378
+ for (const row of rows || []) render(row);
379
+ const added = [];
380
+ for (let n = marker ? marker.nextElementSibling : container.firstElementChild; n; n = n.nextElementSibling) {
381
+ if (!skip || !skip.has(n)) added.push(n);
382
+ }
383
+ markHistoryRows(added, ticket);
384
+ if (before && before.parentNode === container) for (const n of added) container.insertBefore(n, before);
385
+ return added;
386
+ }
@@ -3,6 +3,7 @@
3
3
  // puts that on the card. Live tool results and replayed history take the same
4
4
  // path, so a reopened session looks like the live one did.
5
5
 
6
+ import { taskResultAppliesTodos } from './cp-restore.js';
6
7
  import { cpCtx } from './cp-ctx.js';
7
8
  import { escapeHtml } from './cp-markdown.js';
8
9
  import {
@@ -212,11 +213,12 @@ export function applyStructuredResult(tab, card, ev) {
212
213
  body.appendChild(stats);
213
214
  }
214
215
  // The task tools keep the list the todo dock shows (root conversation only).
215
- if (!tab._agentScope && !ev.is_error) {
216
+ // A replayed result is already in the list the replay computed; a live one is applied over it.
217
+ if (taskResultAppliesTodos(tab, ev)) {
216
218
  const next = applyTaskTool(tab.todos, name, input, ev.tool_use_result);
217
219
  if (next) {
218
220
  tab.todos = next;
219
- try { cpCtx.renderTodoWidget(tab); } catch {}
221
+ if (!tab._replaying) { try { cpCtx.renderTodoWidget(tab); } catch {} }
220
222
  }
221
223
  }
222
224
  return { view, content: view.content };
@@ -285,7 +287,12 @@ export function buildHistoryPlanCard(block) {
285
287
 
286
288
  /** A replayed result for one of the two cards above. True when it was one of them. */
287
289
  export function fillHistoryCard($msgs, row) {
288
- const card = $msgs.querySelector(`.cp-history-card[data-tool-id="${CSS.escape(String(row.toolUseId || ''))}"]`);
290
+ const id = String(row.toolUseId || '');
291
+ let card = null;
292
+ // A replay indexes the cards it builds (no selector scan per result); a card it did not build is looked up.
293
+ const indexed = $msgs?._replayCards instanceof Map ? $msgs._replayCards.get(id) : null;
294
+ if (indexed) card = indexed.classList?.contains('cp-history-card') ? indexed : null;
295
+ else card = $msgs.querySelector(`.cp-history-card[data-tool-id="${CSS.escape(id)}"]`);
289
296
  if (!card) return false;
290
297
  if (card.classList.contains('cp-ask-history')) {
291
298
  const rows = askAnswerRows(card._toolInput, row.structured, row.isError ? '' : (row.text || ''));
@@ -48,3 +48,20 @@ export function desktopWindow(win = globalThis.window) {
48
48
  if (!facts || facts.style !== 'overlay' || !['darwin', 'win32', 'linux'].includes(facts.platform)) return null;
49
49
  return api;
50
50
  }
51
+
52
+ /** True on Windows (desktop bridge platform `'win32'`, or Windows browser/window navigator). */
53
+ export function isWindowsPlatform(win = globalThis.window) {
54
+ const platform = desktopBridge(win)?.platform;
55
+ if (typeof platform === 'string' && platform) return platform === 'win32';
56
+ try {
57
+ const nav = win?.navigator ?? globalThis.navigator;
58
+ if (!nav || typeof nav !== 'object') return false;
59
+ const uaPlatform = nav.userAgentData?.platform;
60
+ if (typeof uaPlatform === 'string' && uaPlatform) return /^win/i.test(uaPlatform);
61
+ if (typeof nav.platform === 'string' && nav.platform) return /^win/i.test(nav.platform);
62
+ if (typeof nav.userAgent === 'string' && nav.userAgent) return /\bwindows\b/i.test(nav.userAgent);
63
+ } catch {
64
+ return false;
65
+ }
66
+ return false;
67
+ }
@@ -197,3 +197,15 @@ export function drawnUnderBar(style, barLevel) {
197
197
  const level = Number.isFinite(z) ? (positioned ? z : Math.max(z, 0)) : 0;
198
198
  return level < barLevel;
199
199
  }
200
+
201
+ /**
202
+ * True for a body-level overlay that never receives pointer events on itself or any child (such as `.ui-tooltip` or
203
+ * `#synabun-desktop-drag-strip`), so its hover/move mutations can never cover the bar's drag region.
204
+ */
205
+ export function isPassiveOverlay(node) {
206
+ if (!node || typeof node !== 'object') return false;
207
+ if (node.id === 'synabun-desktop-drag-strip') return true;
208
+ if (typeof node.classList?.contains === 'function' && node.classList.contains('ui-tooltip')) return true;
209
+ const cls = typeof node.className === 'string' ? node.className : '';
210
+ return /(?:^|\s)ui-tooltip(?:\s|$)/.test(cls);
211
+ }
@@ -27,7 +27,7 @@
27
27
 
28
28
  import { desktopWindow } from './desktop-bridge.js';
29
29
  import { colorSender, textTone } from './desktop-titlebar-page.js';
30
- import { barCovered, changedClasses, drawnUnderBar, fallbackTitlebarArea, overlayColorsFor, stackLevel, styleChange, titleBarActive } from './desktop-titlebar-view.js';
30
+ import { barCovered, changedClasses, drawnUnderBar, fallbackTitlebarArea, isPassiveOverlay, overlayColorsFor, stackLevel, styleChange, titleBarActive } from './desktop-titlebar-view.js';
31
31
  import { t } from './i18n.js';
32
32
 
33
33
  const MENU_ICON = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 7h16"/><path d="M4 12h16"/><path d="M4 17h16"/></svg>';
@@ -52,6 +52,7 @@ export function initDesktopTitleBar({ doc = document, view = window } = {}) {
52
52
  const facts = api.titleBar;
53
53
  const html = doc.documentElement;
54
54
  html.classList.add('desktop-titlebar', `desktop-titlebar--${facts.platform}`);
55
+ if (facts.platform === 'win32') html.classList.add('platform-win32');
55
56
  const wco = view.navigator?.windowControlsOverlay || null;
56
57
  let state = { maximized: false, fullScreen: false, focused: true };
57
58
  const send = colorSender(api, doc);
@@ -168,14 +169,22 @@ export function initDesktopTitleBar({ doc = document, view = window } = {}) {
168
169
  return null;
169
170
  };
170
171
  const hasElement = (nodes) => { for (const node of nodes || []) if (node?.nodeType === 1) return true; return false; };
172
+ const onlyPassiveElements = (nodes) => {
173
+ for (const node of nodes || []) if (node?.nodeType === 1 && !isPassiveOverlay(node)) return false;
174
+ return true;
175
+ };
171
176
  // Each change → [IGNORE | SOON | NOW, whether the bar has to be measured].
172
177
  const QUIET = [IGNORE, false];
173
178
  const childListChange = (record) => {
174
179
  if (!hasElement(record.addedNodes) && !hasElement(record.removedNodes)) return QUIET; // text
175
180
  const target = record.target;
176
- if (target === body) { judgeAgain(); return [NOW, true]; } // an overlay's root; a sibling selector may match anew
181
+ if (target === body) {
182
+ if (onlyPassiveElements(record.addedNodes) && onlyPassiveElements(record.removedNodes)) return QUIET;
183
+ judgeAgain();
184
+ return [NOW, true]; // an overlay's root; a sibling selector may match anew
185
+ }
177
186
  const part = partOf(target);
178
- if (!part || part === bar) return QUIET; // off the page, or what the bar holds: that counts as the bar
187
+ if (!part || part === bar || isPassiveOverlay(part)) return QUIET; // off the page, passive tooltip, or inside the bar
179
188
  toJudge.add(part);
180
189
  if (target === part) return [NOW, true]; // an overlay's root got or lost a child (:empty, :has() can show it)
181
190
  return [SOON, !underBar.has(part)];
@@ -189,7 +198,7 @@ export function initDesktopTitleBar({ doc = document, view = window } = {}) {
189
198
  return [NOW, true];
190
199
  }
191
200
  const part = partOf(target);
192
- if (!part) return QUIET;
201
+ if (!part || isPassiveOverlay(part)) return QUIET;
193
202
  if (part === bar) return target === bar ? [NOW, true] : QUIET;
194
203
  toJudge.add(part);
195
204
  // Shown, hidden or restacked: on the next frame; only moved, sized or repainted: at the throttled pace. Inside a
@@ -87,6 +87,7 @@ export const DESKTOP_COPY = Object.freeze({
87
87
  'claude-panel': 'a Claude Code panel reply',
88
88
  'codex-panel': 'a Codex panel reply',
89
89
  'opencode-panel': 'an OpenCode panel reply',
90
+ 'antigravity-panel': 'an Antigravity panel reply',
90
91
  'claude-cli': 'a Claude Code session in use',
91
92
  loop: 'a loop',
92
93
  schedule: 'a schedule',
@@ -96,15 +96,15 @@ export function createExplorerApi({ fetchImpl = (...args) => fetch(...args), Eve
96
96
  return old.ok ? fromLegacySearch(old) : old;
97
97
  }
98
98
 
99
- async function git(root) {
99
+ async function git(root, signal, refresh = false) {
100
100
  await capabilities();
101
- if (caps.explorer) return request(`/api/explorer/git?root=${encodeURIComponent(root)}`);
102
- const old = await request(`/api/git/status?path=${encodeURIComponent(root)}`);
101
+ if (caps.explorer) return request(`/api/explorer/git?root=${encodeURIComponent(root)}${refresh ? '&refresh=1' : ''}`, { signal });
102
+ const old = await request(`/api/git/status?path=${encodeURIComponent(root)}${refresh ? '&refresh=1' : ''}`, { signal });
103
103
  if (!old.ok) return old;
104
104
  if (!old.isGit) return { ok: true, isGit: false };
105
105
  const changes = {};
106
106
  for (const change of old.changes || []) if (change?.path) changes[change.path] = change.status;
107
- return { ok: true, isGit: true, branch: old.branch || '', changes, ignored: [], legacy: true };
107
+ return { ok: true, isGit: true, branch: old.branch || '', changes, ignored: [], legacy: true, ...(old.partial ? { partial: true, scanState: old.scanState } : {}) };
108
108
  }
109
109
 
110
110
  async function create(parent, name, type) {
@@ -110,13 +110,13 @@ export function createEditorLink({ openFile, save, tabs, repoint, subscribe, onC
110
110
 
111
111
  // ── The agents ──
112
112
 
113
- export const SEND_TARGETS = Object.freeze(['claude', 'codex', 'opencode', 'assistant']);
114
- const TARGET_NAMES = { claude: 'Claude Code', codex: 'Codex', opencode: 'OpenCode' };
113
+ export const SEND_TARGETS = Object.freeze(['claude', 'codex', 'opencode', 'antigravity', 'assistant']);
114
+ const TARGET_NAMES = { claude: 'Claude Code', codex: 'Codex', opencode: 'OpenCode', antigravity: 'Antigravity' };
115
115
 
116
116
  /**
117
117
  * @param {object} o
118
118
  * @param {object} o.deps what each panel offers: `sendToFocusedSession(kind, data)`, `claude(path)`, `codex(path)`,
119
- * `opencode(path)`, `assistant(path)`, and `lastActive()` (the panel used last)
119
+ * `opencode(path)`, `antigravity(path)`, `assistant(path)`, and `lastActive()` (the panel used last)
120
120
  * @param {(key: string, params?: object) => string} o.t
121
121
  */
122
122
  export function createSender({ deps, t }) {
@@ -56,7 +56,7 @@ export function sameListing(a, b) {
56
56
  if (!a || !b || a.length !== b.length) return false;
57
57
  for (let i = 0; i < a.length; i++) {
58
58
  const x = a[i]; const y = b[i];
59
- if (x.name !== y.name || x.type !== y.type || x.size !== y.size || x.mtime !== y.mtime || !!x.link !== !!y.link || !!x.outside !== !!y.outside || !!x.broken !== !!y.broken) return false;
59
+ if (x.name !== y.name || x.type !== y.type || x.size !== y.size || x.mtime !== y.mtime || !!x.link !== !!y.link || !!x.outside !== !!y.outside || !!x.broken !== !!y.broken || !!x.ignored !== !!y.ignored || (x.ignored === null) !== (y.ignored === null)) return false;
60
60
  }
61
61
  return true;
62
62
  }
@@ -48,7 +48,9 @@ const EXPAND_LEVEL_MAX = 200;
48
48
  * @param {object} host.send what each side panel offers for a path (fx-links.js createSender)
49
49
  * @param {(name: string) => object} host.fileIcon @param {string} host.folderSvg
50
50
  * @param {{ all: { hex: string, key: string }[], of: (path: string) => string | undefined, set: Function, move: Function }} host.colors
51
- * @param {(git: { isGit: boolean, branch: string, count: number }) => void} host.onGit
51
+ * @param {(git: { isGit: boolean, branch: string, count: number, scanState?: 'ready' | 'scanning' | 'timed-out' | 'failed',
52
+ * shown?: boolean, loud?: boolean }) => void} host.onGit `shown`: a result of this project is already on screen;
53
+ * `loud`: the scan is the project's first or one the owner asked for (Refresh), not a background one.
52
54
  * @param {() => void} host.addProject
53
55
  * @param {typeof fetch} [host.fetchImpl]
54
56
  */
@@ -77,7 +79,10 @@ export function createExplorer(host) {
77
79
  let watcher = null;
78
80
  let saidLimited = false; // this project was told once that not everything on disk is followed
79
81
  let loaded = Promise.resolve();
80
- let gitTimer = 0; let gitBusy = false; let gitAgain = false; let lastGitAt = 0;
82
+ let gitTimer = 0; let gitRequest = null; let gitRefresh = false; let gitAgain = false; let lastGitAt = 0;
83
+ // gitShown: a result of this project is on screen. gitLoud: the owner asked (Refresh), so a slow scan may say so.
84
+ // gitSettled: the last state a scan ended in, put back when a scan that said 'scanning' is called off.
85
+ let gitShown = false; let gitLoud = false; let gitSettled = 'ready';
81
86
  let diskTimer = 0; let lastDiskAt = 0; const diskDue = new Set();
82
87
  let filterTimer = 0; let searchAbort = null;
83
88
  let persistTimer = 0; let lastFocusRefresh = 0;
@@ -114,18 +119,20 @@ export function createExplorer(host) {
114
119
  const ed = editor.state();
115
120
  const status = item.missing ? 'deleted' : S.git.statusOf(below, isDir);
116
121
  const tone = item.missing ? 'deleted' : S.git.toneOf(below, isDir);
117
- const ignored = S.git.isIgnored(below);
122
+ const ignored = !!item.ignored || S.git.isIgnored(below);
123
+ const ignoreUnknown = item.ignored === null && !ignored;
118
124
  const open = !isDir && ed.open.has(path);
119
125
  const dirty = !isDir && ed.dirty.has(path);
120
126
  const cut = !!S.clipboard && S.clipboard.mode === 'cut' && S.clipboard.paths.some((held) => isWithin(held, path));
121
127
  const words = [];
122
128
  if (status) words.push(t(`fileExplorer.git.${status}`)); else if (tone) words.push(t('fileExplorer.git.contains'));
123
129
  if (ignored) words.push(t('fileExplorer.git.ignored'));
130
+ else if (ignoreUnknown) words.push(t('fileExplorer.git.ignoreUnknown'));
124
131
  if (dirty) words.push(t('fileExplorer.row.unsaved')); else if (open) words.push(t('fileExplorer.row.open'));
125
132
  if (item.outside) words.push(t('fileExplorer.row.linkOutside')); else if (item.broken) words.push(t('fileExplorer.row.linkBroken')); else if (item.link) words.push(t('fileExplorer.row.link'));
126
133
  if (cut) words.push(t('fileExplorer.row.cut'));
127
134
  return {
128
- tone, letter: status ? GIT_MARKS[status].letter : '', ignored, open, dirty, cut, words,
135
+ tone, letter: status ? GIT_MARKS[status].letter : '', ignored, ignoreUnknown, open, dirty, cut, words,
129
136
  active: !isDir && ed.active === path,
130
137
  missing: !!item.missing,
131
138
  faint: !!item.outside || !!item.broken,
@@ -296,24 +303,46 @@ export function createExplorer(host) {
296
303
 
297
304
  // ── Git ──
298
305
 
306
+ function cancelGit() {
307
+ clearTimeout(gitTimer);
308
+ gitRequest?.controller.abort();
309
+ gitRequest = null; gitAgain = false; gitRefresh = false; gitLoud = false;
310
+ }
299
311
  async function loadGit() {
300
- if (!S.root) return;
301
- if (gitBusy) { gitAgain = true; return; }
302
- gitBusy = true;
312
+ if (!S.root || !S.visible) return;
313
+ if (gitRequest?.root === S.root) { gitAgain = true; return; }
314
+ gitRequest?.controller.abort();
315
+ const request = { root: S.root, controller: new AbortController() };
316
+ gitRequest = request;
303
317
  const mine = epoch;
304
- const got = await api.git(S.root);
305
- gitBusy = false;
318
+ const refresh = gitRefresh; gitRefresh = false;
319
+ // A background scan (the watcher, focus, the popover) never puts words in the header while a result is shown:
320
+ // only the first scan of a project, or one the owner asked for, is loud.
321
+ const loud = gitLoud || !gitShown; gitLoud = false;
322
+ let said = '';
323
+ const say = (scanState) => { said = scanState; host.onGit({ ...gitState(scanState), loud }); };
324
+ const slow = setTimeout(() => { if (gitRequest === request && mine === epoch) say('scanning'); }, 1500);
325
+ const got = await api.git(request.root, request.controller.signal, refresh);
326
+ clearTimeout(slow);
327
+ if (gitRequest !== request) return;
328
+ gitRequest = null;
306
329
  if (mine === epoch && got.ok) {
307
- S.git = gitIndex(got);
308
- host.onGit({ isGit: S.git.isGit, branch: S.git.branch, count: S.git.count });
330
+ if (!got.partial || !S.git.isGit) S.git = gitIndex(got);
331
+ gitShown = true;
332
+ say(gitSettled = got.scanState || 'ready');
309
333
  if (S.changedOnly) { buildView(); tree.render(); } else tree.refreshRows();
310
334
  updateCount();
311
- }
335
+ } else if (mine === epoch && got.code !== 'aborted') say(gitSettled = 'failed');
336
+ else if (mine === epoch && said === 'scanning') say(gitSettled);
312
337
  if (gitAgain) { gitAgain = false; queueGit(); }
313
338
  }
339
+ function gitState(scanState) {
340
+ return { isGit: S.git.isGit, branch: S.git.branch, count: S.git.count, scanState, shown: gitShown };
341
+ }
314
342
  // Soon after the last sign of a change, and never more often than once in a while: a file that is written
315
343
  // without pause (a log, a database) does not make git run without pause.
316
344
  function queueGit() {
345
+ if (!S.root || !S.visible) return;
317
346
  clearTimeout(gitTimer);
318
347
  gitTimer = setTimeout(() => { lastGitAt = Date.now(); loadGit(); }, Math.max(GIT_DELAY_MS, lastGitAt + GIT_MIN_INTERVAL_MS - Date.now()));
319
348
  }
@@ -654,7 +683,7 @@ export function createExplorer(host) {
654
683
  case 'sortBy': return [back(), ...SORTS.map((mode) => ({ id: `sort:${mode}`, label: t(`fileExplorer.menu.sort.${mode}`), checked: S.sort === mode }))];
655
684
  case 'expandAll': expandLevel(); break;
656
685
  case 'collapseAll': collapseAll(); break;
657
- case 'refresh': refreshAll(); break;
686
+ case 'refresh': gitRefresh = true; gitLoud = true; refreshAll(); break;
658
687
  default:
659
688
  }
660
689
  return undefined;
@@ -739,7 +768,7 @@ export function createExplorer(host) {
739
768
  });
740
769
  on('fe-collapse-all', collapseAll);
741
770
  // Refresh also asks what the server has: one that was restarted since the page loaded is found out here.
742
- on('fe-refresh', async () => { await host.readServer?.(); await api.capabilities(); syncToolbar(); await refreshAll(); if (!watcher) startWatch(); });
771
+ on('fe-refresh', async () => { gitRefresh = true; gitLoud = true; await host.readServer?.(); await api.capabilities(); syncToolbar(); await refreshAll(); if (!watcher) startWatch(); });
743
772
  on('fe-more', (event) => { const box = event.currentTarget.getBoundingClientRect(); openMenu({ y: box.top + box.height / 2, keyboard: event.detail === 0 }); });
744
773
  on('fe-filter-clear', () => { clearFilter(); filterInput.focus(); });
745
774
  filterInput.addEventListener('input', () => setFilter(filterInput.value));
@@ -773,11 +802,13 @@ export function createExplorer(host) {
773
802
  function setProject(project) {
774
803
  persist();
775
804
  epoch++;
805
+ cancelGit();
776
806
  stopWatch();
777
807
  saidLimited = false;
778
808
  closeFlyout({ refocus: false });
779
809
  searchAbort?.abort();
780
810
  clearTimeout(gitTimer);
811
+ gitShown = false; gitSettled = 'ready';
781
812
  S.dirs = new Map(); S.git = gitIndex(null); S.view = null; S.viewOpen = new Set(); S.viewClosed = new Set();
782
813
  S.search = null; S.filter = ''; S.clipboard = null;
783
814
  reads.clear();
@@ -824,7 +855,7 @@ export function createExplorer(host) {
824
855
  /** The panel is shown or hidden: the disk is followed only while it is shown. */
825
856
  function setVisible(visible) {
826
857
  S.visible = !!visible;
827
- if (S.visible) { if (S.root) { startWatch(); if (S.dirs.size) refreshAll(); } } else { persist(); stopWatch(); closeFlyout({ refocus: false }); }
858
+ if (S.visible) { if (S.root) { startWatch(); if (S.dirs.size) refreshAll(); } } else { persist(); cancelGit(); host.onGit(gitState(gitSettled)); stopWatch(); closeFlyout({ refocus: false }); }
828
859
  }
829
860
 
830
861
  /** Show a file of the project in the tree: its folders open, its row is selected and marked. */
@@ -850,7 +881,8 @@ export function createExplorer(host) {
850
881
  setProject,
851
882
  setVisible,
852
883
  reveal,
853
- refresh: refreshAll,
884
+ /** Read the disk and git again. `quiet`: nobody asked for it in so many words (the git popover opened). */
885
+ refresh: ({ quiet = false } = {}) => { gitRefresh = true; if (!quiet) gitLoud = true; return refreshAll(); },
854
886
  collapseAll,
855
887
  /** The icons changed: every row is drawn again. */
856
888
  redraw() { if (S.root) { const now = tree.selection(); const top = tree.scrollTop(); tree.setRoot(S.root); tree.render(); tree.select(now.selected, { focus: now.focus, quiet: true, scroll: false }); tree.setScrollTop(top); } },
@@ -161,6 +161,7 @@ export function createTree({ el, mac, icons, hooks }) {
161
161
  node.classList.toggle('fe-node--selected', selected.has(path));
162
162
  node.classList.toggle('fe-node--focus', focus === path);
163
163
  node.classList.toggle('fe-node--ignored', !!info.ignored);
164
+ node.dataset.ignoreState = info.ignoreUnknown ? 'unknown' : info.ignored ? 'ignored' : 'known';
164
165
  node.classList.toggle('fe-node--cut', !!info.cut);
165
166
  node.classList.toggle('fe-node--open', !!info.open);
166
167
  node.classList.toggle('fe-node--active', !!info.active);
@@ -2,8 +2,8 @@
2
2
  // SynaBun — Greeting: the card every surface mounts
3
3
  // ═══════════════════════════════════════════
4
4
  //
5
- // One renderer for the greeting: the Assistant and the Claude Code, Codex and
6
- // OpenCode panels mount it inside the empty state they already have, Settings
5
+ // One renderer for the greeting: the Assistant and the Claude Code, Codex,
6
+ // OpenCode and Antigravity panels mount it inside the empty state they already have, Settings
7
7
  // shows the same card as its preview. greeting-view.js decides the words; this
8
8
  // file asks the server (`POST /api/greeting/open`) and draws.
9
9
  //
@@ -41,6 +41,7 @@ export const GREETING_COPY = Object.freeze({
41
41
  'agent.claude-code': 'Claude Code',
42
42
  'agent.codex': 'Codex',
43
43
  'agent.opencode': 'OpenCode',
44
+ 'agent.antigravity': 'Antigravity',
44
45
  open: 'Open memory',
45
46
  'source.writer': 'Written ahead by {model}',
46
47
  'source.fallback': 'Put together from the records, no model',