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
@@ -177,10 +177,10 @@ export function getSharedHTML() {
177
177
  <span class="menu-text"><span class="menu-icon"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M9.205 8.658v-2.26c0-.19.072-.333.238-.428l4.543-2.616c.619-.357 1.356-.523 2.117-.523 2.854 0 4.662 2.212 4.662 4.566 0 .167 0 .357-.024.547l-4.71-2.759a.797.797 0 00-.856 0l-5.97 3.473zm10.609 8.8V12.06c0-.333-.143-.57-.429-.737l-5.97-3.473 1.95-1.118a.433.433 0 01.476 0l4.543 2.617c1.309.76 2.189 2.378 2.189 3.948 0 1.808-1.07 3.473-2.76 4.163zM7.802 12.703l-1.95-1.142c-.167-.095-.239-.238-.239-.428V5.899c0-2.545 1.95-4.472 4.591-4.472 1 0 1.927.333 2.712.928L8.23 5.067c-.285.166-.428.404-.428.737v6.898zM12 15.128l-2.795-1.57v-3.33L12 8.658l2.795 1.57v3.33L12 15.128zm1.796 7.23c-1 0-1.927-.332-2.712-.927l4.686-2.712c.285-.166.428-.404.428-.737v-6.898l1.974 1.142c.167.095.238.238.238.428v5.233c0 2.545-1.974 4.472-4.614 4.472zm-5.637-5.303l-4.544-2.617c-1.308-.761-2.188-2.378-2.188-3.948A4.482 4.482 0 014.21 6.327v5.423c0 .333.143.571.428.738l5.947 3.449-1.95 1.118a.432.432 0 01-.476 0zm-.262 3.9c-2.688 0-4.662-2.021-4.662-4.519 0-.19.024-.38.047-.57l4.686 2.71c.286.167.571.167.856 0l5.97-3.448v2.26c0 .19-.07.333-.237.428l-4.543 2.616c-.619.357-1.356.523-2.117.523zm5.899 2.83a5.947 5.947 0 005.827-4.756C22.287 18.339 24 15.84 24 13.296c0-1.665-.713-3.282-1.998-4.448.119-.5.19-.999.19-1.498 0-3.401-2.759-5.947-5.946-5.947-.642 0-1.26.095-1.88.31A5.962 5.962 0 0010.205 0a5.947 5.947 0 00-5.827 4.757C1.713 5.447 0 7.945 0 10.49c0 1.666.713 3.283 1.998 4.448-.119.5-.19 1-.19 1.499 0 3.401 2.759 5.946 5.946 5.946.642 0 1.26-.095 1.88-.309a5.96 5.96 0 004.162 1.713z"/></svg></span> ${t('menu.apps.codexCli')} <span class="count-badge count-badge--green menu-update-badge" id="codex-menu-update-badge"></span></span>
178
178
  <span class="menu-shortcut" data-keybind-for="launch-codex"></span>
179
179
  </div>
180
- <div class="menu-item" id="menu-terminal-gemini">
180
+ <div class="menu-item" id="menu-terminal-antigravity">
181
181
  <span class="menu-check"></span>
182
- <span class="menu-text"><span class="menu-icon"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z"/></svg></span> ${t('menu.apps.geminiCli')} <span class="count-badge count-badge--green menu-update-badge" id="gemini-menu-update-badge"></span></span>
183
- <span class="menu-shortcut" data-keybind-for="launch-gemini"></span>
182
+ <span class="menu-text"><span class="menu-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4.5 20 12 4l7.5 16"/><path d="M8.2 20a3.8 3.8 0 0 1 7.6 0"/></svg></span> ${t('menu.apps.antigravityCli')} <span class="count-badge count-badge--green menu-update-badge" id="antigravity-menu-update-badge"></span></span>
183
+ <span class="menu-shortcut" data-keybind-for="launch-antigravity"></span>
184
184
  </div>
185
185
  <div class="menu-item" id="menu-terminal-opencode">
186
186
  <span class="menu-check"></span>
@@ -495,7 +495,8 @@ ${is3D ? '' : `
495
495
  </div>
496
496
 
497
497
  <!-- Top-Right Controls: one row. The glass dock (workspace switcher, then the tools), then #topright-buttons: the
498
- tools toggle (#topright-tools-btn, shows and hides the dock) and the agent side panels, always one row of their own.
498
+ ⋯ (#topright-overflow-btn: what does not fit the band, ui-topright-overflow.js), the tools toggle
499
+ (#topright-tools-btn, shows and hides the dock) and the agent side panels. Always one row, never wrapped.
499
500
  Snap to grid and Tile terminals are on the whiteboard rail (#wb-toolbar) and in View. The session pills
500
501
  (#term-minimized-tray) are a row under the bar, right-aligned with it (styles.css: "Fit"). -->
501
502
  <div id="topright-controls">
@@ -569,6 +570,10 @@ ${is3D ? '' : `
569
570
  </button>
570
571
  </div>
571
572
  <div id="topright-buttons">
573
+ <button id="topright-overflow-btn" class="topright-icon-btn" type="button" aria-haspopup="menu" aria-expanded="false" aria-controls="topright-overflow-menu" aria-label="${t('toolbar.more')}" data-tooltip="${t('toolbar.more')}">
574
+ <svg viewBox="0 0 24 24"><circle cx="5" cy="12" r="1.4" fill="currentColor" stroke="none"/><circle cx="12" cy="12" r="1.4" fill="currentColor" stroke="none"/><circle cx="19" cy="12" r="1.4" fill="currentColor" stroke="none"/></svg>
575
+ </button>
576
+ <div id="topright-overflow-menu" role="menu" aria-label="${t('toolbar.more')}" hidden></div>
572
577
  <button id="topright-tools-btn" class="topright-icon-btn" type="button" aria-expanded="true" aria-controls="topright-inner" aria-label="${t('toolbar.hideTools')}" data-tooltip="${t('toolbar.hideTools')}">
573
578
  <svg viewBox="0 0 24 24"><path d="M13 17l5-5-5-5"/><path d="M6 17l5-5-5-5"/></svg>
574
579
  <span class="topright-tools-dot" aria-hidden="true"></span>
@@ -588,9 +593,16 @@ ${is3D ? '' : `
588
593
  <svg viewBox="0 0 24 30" fill="currentColor"><path d="M18 6H6V24H18V6ZM24 30H0V0H24V30Z"/></svg>
589
594
  <span class="count-badge count-badge--green" id="opencode-update-badge"></span>
590
595
  </button>
596
+ <button id="topright-antigravity-panel-btn" class="topright-icon-btn" data-tooltip="${t('antigravityPanel.toolbar')}">
597
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4.5 20 12 4l7.5 16"/><path d="M8.2 20a3.8 3.8 0 0 1 7.6 0"/></svg>
598
+ <span class="count-badge count-badge--green" id="antigravity-update-badge"></span>
599
+ </button>
591
600
  </div>
592
601
  <div id="term-minimized-tray"></div>
593
602
  </div>
603
+ <button id="toolbar-show-btn" type="button" aria-pressed="true" aria-controls="wb-toolbar" aria-label="${t('toolbar.showWhiteboardToolbar')}" data-tooltip="${t('toolbar.showWhiteboardToolbar')}" hidden>
604
+ <img src="logo-synabun.png" alt="" draggable="false">
605
+ </button>
594
606
 
595
607
  <!-- Help Modal -->
596
608
  <div id="help-overlay">
@@ -701,6 +713,7 @@ ${is3D ? '' : `
701
713
  <span class="fe-branch-btn-name" id="fe-branch-btn-name"></span>
702
714
  <span class="fe-branch-btn-badge" id="fe-branch-btn-badge"></span>
703
715
  </button>
716
+ <span class="fe-git-scan-live" id="fe-git-scan-live" role="status" aria-live="polite"></span>
704
717
  </div>
705
718
  <div class="fe-filter">
706
719
  <svg class="fe-filter-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M3 5h18l-7 8v6l-4-2v-4z"/></svg>
@@ -821,6 +834,10 @@ ${getEditorPanelHTML()}
821
834
  <button class="wb-tool wb-tool-sm" id="wb-toggle-logo" data-tooltip="Toggle logo">
822
835
  <img src="logo-synabun.png" alt="" style="width:18px;height:auto;opacity:0.6;">
823
836
  </button>
837
+ <div class="wb-tool-sep"></div>
838
+ <button class="wb-tool" id="wb-hide-btn" type="button" aria-pressed="false" aria-controls="wb-toolbar" aria-label="${t('toolbar.hideWhiteboardToolbar')}" data-tooltip="${t('toolbar.hideWhiteboardToolbar')}">
839
+ <svg viewBox="0 0 24 24" fill="none" stroke-linecap="round" stroke-linejoin="round"><path d="M15 6l-6 6 6 6"/></svg>
840
+ </button>
824
841
  </div>
825
842
 
826
843
  <div id="wb-canvas">
@@ -1,3 +1,4 @@
1
+ import { syncOpenCodeWorkerQuestions, forgetWorkerQuestions } from './ocp-v2-ws.js';
1
2
  import { workerRestart } from '../worker-restart.js';
2
3
  import { t } from '../i18n.js';
3
4
  import { hostedSidepanelId, sidepanelHost } from '../ui-sidepanel-runtime.js';
@@ -1616,7 +1617,12 @@ async function boot() {
1616
1617
  // the session on screen stays.
1617
1618
  renderPill();
1618
1619
  } else if (liveTabs.length) {
1619
- const targetSid = liveTabs[liveTabs.length - 1];
1620
+ const pendingSid = globalThis.__synabunPendingTab?.provider === 'opencode'
1621
+ ? String(globalThis.__synabunPendingTab.tabId || '')
1622
+ : '';
1623
+ const targetSid = (pendingSid && liveTabs.includes(pendingSid))
1624
+ ? pendingSid
1625
+ : liveTabs[liveTabs.length - 1];
1620
1626
  const targetInfo = _tabInfoCache.get(targetSid) || null;
1621
1627
  bootNav.apply(() => {
1622
1628
  setSession(targetSid, targetInfo);
@@ -2233,6 +2239,8 @@ function removeTab(sessionId, { byUser = false } = {}) {
2233
2239
  forgetWorkerEnd(_automationRunIds.get(sessionId));
2234
2240
  _workerEnds.delete(sessionId);
2235
2241
  _automationRunIds.delete(sessionId);
2242
+ forgetWorkerQuestions(sessionId);
2243
+ forgetWorkerQuestions(sessionId);
2236
2244
  unregisterAutomationSession(sessionId);
2237
2245
  _automationRunning.delete(sessionId);
2238
2246
  _automationTurn.delete(sessionId);
@@ -2273,7 +2281,7 @@ function createTabPill(sessionId) {
2273
2281
  });
2274
2282
  pill.querySelector('.term-minimized-pill-close')?.addEventListener('click', (event) => {
2275
2283
  event.stopPropagation();
2276
- closeTabByUser(sessionId, 'pill close');
2284
+ closeTabByUser(sessionId, 'pill close', { deleteBeforeClose: true });
2277
2285
  });
2278
2286
  tray.appendChild(pill);
2279
2287
  return pill;
@@ -2283,10 +2291,16 @@ function createTabPill(sessionId) {
2283
2291
  // continued the terminal automation manually; backend cleanup is best-effort.
2284
2292
  // Asked first when prompts are still waiting in that session; declined: the
2285
2293
  // tab stays, nothing is deleted, and this resolves false.
2286
- async function closeTabByUser(sessionId, context) {
2294
+ async function closeTabByUser(sessionId, context, { deleteBeforeClose = false } = {}) {
2287
2295
  if (!(await mayCloseTab(sessionId))) return false;
2288
- const wasRunning = sessionId === getState().sessionId && !!getState().running;
2289
- closeSessionBackend(sessionId, { wasRunning, context }).catch(() => {});
2296
+ // Only the tray X waits for deletion, including a turn in a background tab.
2297
+ // Worker tabs retain their existing stop/dismiss and transcript ownership.
2298
+ const deletePillSession = deleteBeforeClose && !_automationRunIds.has(sessionId);
2299
+ const wasRunning = sessionId === getState().sessionId
2300
+ ? !!getState().running
2301
+ : deletePillSession && _sessionBusy.get(sessionId) === true;
2302
+ const backendClose = closeSessionBackend(sessionId, { wasRunning, context }).catch(() => {});
2303
+ if (deletePillSession) await backendClose;
2290
2304
  try { await handlePillClose(sessionId, { byUser: true }); }
2291
2305
  catch (err) { console.warn(`[ocp-v2-panel] local ${context} failed`, err); }
2292
2306
  return true;
@@ -2436,9 +2450,13 @@ async function handlePillClose(sessionId, { byUser = false } = {}) {
2436
2450
  }
2437
2451
 
2438
2452
  // Sub-agent child panel coordination ─────────────────────────────────────────
2439
- on('opencode-panel:show', async () => {
2453
+ on('opencode-panel:show', async (data) => {
2440
2454
  try {
2455
+ const requestedId = typeof data?.tabId === 'string' ? data.tabId : '';
2441
2456
  await showPrimaryOpencodePanel();
2457
+ if (requestedId && _tabSessionIds.includes(requestedId)) {
2458
+ await switchToSession(requestedId, _tabInfoCache.get(requestedId) || null);
2459
+ }
2442
2460
  } catch (err) {
2443
2461
  console.warn('[ocp-v2-panel] show request failed', err);
2444
2462
  }
@@ -2457,7 +2475,7 @@ on('opencode-panel:request-show', async () => {
2457
2475
  on('opencode-panel:request-new-session', () => { beginNewSession(); });
2458
2476
 
2459
2477
  // Whiteboard "Send to Panel" → attach image to compose footer if OCP v2 is the
2460
- // active sidepanel. Mirrors the gating used by Claude/Codex/OCP v1 so the event
2478
+ // active sidepanel. Mirrors the gating used by Claude/Codex/Antigravity so the event
2461
2479
  // is consumed by exactly one panel.
2462
2480
  on('wb:send-to-panel', async ({ dataUrl }) => {
2463
2481
  if (!dataUrl) return;
@@ -2527,6 +2545,7 @@ export async function attachOpenCodeAutomation(run, { focus = !!run?.focus, isCa
2527
2545
  && (_automationContinuedManually.has(sessionId) || _automationManualSendInFlight.has(sessionId));
2528
2546
  _automationRunIds.set(sessionId, run.runId);
2529
2547
  registerAutomationSession(sessionId, run.runId);
2548
+ syncOpenCodeWorkerQuestions(run);
2530
2549
  if (running || previousRunId !== run.runId) {
2531
2550
  _automationContinuedManually.delete(sessionId);
2532
2551
  _automationManualSendInFlight.delete(sessionId);
@@ -2595,6 +2614,7 @@ export async function detachOpenCodeAutomation(runId) {
2595
2614
  if (!entry) return false;
2596
2615
  const [sessionId] = entry;
2597
2616
  _automationRunIds.delete(sessionId);
2617
+ forgetWorkerQuestions(sessionId);
2598
2618
  _automationRunning.delete(sessionId);
2599
2619
  _automationTurn.delete(sessionId);
2600
2620
  _automationContinuedManually.delete(sessionId);
@@ -284,20 +284,29 @@ export async function rehydratePanelSession(store, api, {
284
284
  const left = () => { out.moved = true; return out; };
285
285
  if (!current()) return left();
286
286
 
287
- if (refreshInfo) {
287
+ if (refreshInfo && requireInfo) {
288
288
  let res;
289
289
  try { res = await api.sessionGet(sessionId); } catch (err) { res = { ok: false, error: err?.message || String(err) }; }
290
290
  if (!current()) return left();
291
291
  if (!replyFailed(res) && res.data) { store.setSession(sessionId, res.data); out.info = true; }
292
- else if (requireInfo) {
292
+ else {
293
293
  out.unavailable = true;
294
294
  out.error = (typeof res?.error === 'string' && res.error) || `Session ${String(sessionId).slice(0, 8)} is unavailable`;
295
295
  return out;
296
296
  }
297
- // Otherwise the transcript may still answer.
298
297
  }
298
+ const infoTask = (refreshInfo && !requireInfo)
299
+ ? (async () => {
300
+ let res;
301
+ try { res = await api.sessionGet(sessionId); } catch (err) { res = { ok: false, error: err?.message || String(err) }; }
302
+ if (!current()) return false;
303
+ if (!replyFailed(res) && res.data) { store.setSession(sessionId, res.data); out.info = true; }
304
+ return true;
305
+ })()
306
+ : null;
299
307
  const transcript = await hydrateTranscript(store, api, { sessionId, isCurrent: current });
300
- if (transcript.superseded) return left();
308
+ if (infoTask) await infoTask;
309
+ if (!current() || transcript.superseded) return left();
301
310
  out.transcript = transcript.applied;
302
311
  if (!transcript.applied) out.error = transcript.error;
303
312
  const s = store.getState();
@@ -10,7 +10,7 @@
10
10
  // ─────────────────────────────────────────────────────────────────────────────
11
11
 
12
12
  import { getDefaultStore } from './ocp-v2-state.js';
13
- import { api, supports, isDescendantSession, isKnownDescendantSession, onEvent } from './ocp-v2-ws.js';
13
+ import { hasWorkerQuestions, api, supports, isDescendantSession, isKnownDescendantSession, onEvent } from './ocp-v2-ws.js';
14
14
  import {
15
15
  permissionCardView, ALWAYS_ALLOW_SCOPE, replyToPermission, FOREIGN_REQUEST_MESSAGE,
16
16
  transcriptQuestionCard, answerQuestion, skipQuestion,
@@ -1001,6 +1001,12 @@ function renderToolPart(part, store) {
1001
1001
  done.textContent = 'Question answered.';
1002
1002
  return done;
1003
1003
  }
1004
+ if (hasWorkerQuestions(store.getState().sessionId)) {
1005
+ const stub = document.createElement('div');
1006
+ stub.className = 'ocpv2-part ocpv2-part-question-stub';
1007
+ stub.textContent = store.getState().pendingQuestions?.length ? 'Awaiting your answer…' : 'Question answered.';
1008
+ return stub;
1009
+ }
1004
1010
  // Dedup: if an SSE-driven QuestionRequest exists whose tool.callID matches
1005
1011
  // this tool part, let the pendingQuestions render handle it instead.
1006
1012
  const partCallId = part.callID || part.id;
@@ -45,6 +45,10 @@ const CSS = `
45
45
  opacity: 1;
46
46
  overflow: visible;
47
47
  }
48
+ /* Windows: the shell is 92% opaque, so the blur behind it barely shows, yet Chromium recomputes it on every
49
+ repaint inside the panel (a transcript rendering, a stream). There it goes; the colour stays. */
50
+ html.platform-win32 .ocpv2-panel,
51
+ html.desktop-titlebar--win32 .ocpv2-panel { backdrop-filter: none; -webkit-backdrop-filter: none; }
48
52
  /* Keep footer menus above focused floating terminal windows. */
49
53
  .ocpv2-panel:has(.ocpv2-dropdown.open) {
50
54
  z-index: 99999;
@@ -464,6 +468,19 @@ const CSS = `
464
468
  rgba(232,224,220,0.0) 100%
465
469
  );
466
470
  }
471
+ html.platform-win32 .ocpv2-session-pill.ocpv2-pill-running,
472
+ html.desktop-titlebar--win32 .ocpv2-session-pill.ocpv2-pill-running {
473
+ border-color: rgba(232, 224, 220, 0.38);
474
+ }
475
+ html.platform-win32 .ocpv2-session-pill.ocpv2-pill-running:hover,
476
+ html.desktop-titlebar--win32 .ocpv2-session-pill.ocpv2-pill-running:hover {
477
+ border-color: rgba(232, 224, 220, 0.58);
478
+ }
479
+ html.platform-win32 .ocpv2-session-pill.ocpv2-pill-running::before,
480
+ html.desktop-titlebar--win32 .ocpv2-session-pill.ocpv2-pill-running::before {
481
+ animation: none;
482
+ opacity: 0;
483
+ }
467
484
  .ocpv2-session-pill.ocpv2-pill-running .term-minimized-pill-label::before {
468
485
  content: '';
469
486
  display: inline-block;
@@ -1,3 +1,5 @@
1
+ import { t } from '../i18n.js';
2
+ import { answerWorkerQuestion } from '../worker-questions.js';
1
3
  // ─────────────────────────────────────────────────────────────────────────────
2
4
  // OpenCode V2 — WebSocket client (multi-panel)
3
5
  // One WS connection serves every OCP v2 panel on the page. Panels register a
@@ -27,6 +29,12 @@ const _outcomes = createOutcomeNotifications();
27
29
 
28
30
  function _fireOutcomeNotify(store, type) {
29
31
  const s = store.getState();
32
+ // A sub-agent finishing is an intermediate step of its parent's task, not
33
+ // news on its own: ensureChildSpawn gives every Task/subagent call its own
34
+ // panel and store, so without this a multi-step turn popped one "done" per
35
+ // sub-agent plus one for the task itself. Its own errors still surface —
36
+ // those need attention wherever they happen.
37
+ if (type !== NOTIF_TYPE.ERROR && s.parentSessionId) return;
30
38
  if (!_outcomes.take(s, type === NOTIF_TYPE.ERROR ? 'error' : 'done')) return;
31
39
  const baseLabel = s.sessionInfo?.title || s.sessionInfo?.info?.title || 'OpenCode';
32
40
  const label = s.parentSessionId ? `↳ ${baseLabel}` : baseLabel;
@@ -627,6 +635,41 @@ function request(payload, timeoutMs = 30000, { turnOf = '' } = {}) {
627
635
  });
628
636
  }
629
637
 
638
+ const workerQuestionRuns = new Map();
639
+ export const hasWorkerQuestions = (sessionId) => workerQuestionRuns.has(sessionId);
640
+ export const forgetWorkerQuestions = (sessionId) => workerQuestionRuns.delete(sessionId);
641
+ const workerQuestions = (run) => (run?.pending || []).filter((p) => p.kind === 'question').map((p) => ({ ...p.input, id: p.protocol?.id, sessionID: run.providerSessionId, _workerRequestId: p.requestId }));
642
+
643
+ export function syncOpenCodeWorkerQuestions(run) {
644
+ if (run.runMode !== 'task') return;
645
+ const sid = run.providerSessionId;
646
+ const previous = workerQuestions(workerQuestionRuns.get(sid));
647
+ workerQuestionRuns.set(sid, run);
648
+ const next = workerQuestions(run);
649
+ for (const req of previous) if (!next.some((n) => n.id === req.id)) _asks.note('question.replied', { id: req.id, sessionID: sid });
650
+ for (const req of next) _asks.note('question.asked', req);
651
+ for (const store of _storesBySession.get(sid) || []) {
652
+ for (const req of previous) if (!next.some((n) => n.id === req.id)) store.removePendingQuestion(req.id);
653
+ for (const req of next) store.addPendingQuestion(req);
654
+ }
655
+ }
656
+
657
+ async function answerOpenCodeWorker({ sessionId, requestId, answers }, reject = false) {
658
+ let run = workerQuestionRuns.get(sessionId);
659
+ let pending = workerQuestions(run).find((q) => q.id === requestId);
660
+ // An SSE question can precede the run update. Read the authoritative pending request before answering.
661
+ if (!pending) {
662
+ const res = await fetch(`/api/assistant/runs/${encodeURIComponent(run.runId)}`);
663
+ if (!res.ok) throw new Error(t('workerQuestion.deliveryFailed'));
664
+ const body = await res.json();
665
+ if (!body.run) throw new Error(t('workerQuestion.deliveryFailed'));
666
+ run = body.run; pending = workerQuestions(run).find((q) => q.id === requestId);
667
+ }
668
+ if (pending) await answerWorkerQuestion(run.runId, pending._workerRequestId, { decision: reject ? 'deny' : 'answer', answers });
669
+ for (const store of _storesBySession.get(sessionId) || []) store.removePendingQuestion(requestId);
670
+ return { ok: true, data: true };
671
+ }
672
+
630
673
  export const api = {
631
674
  init: () => request({ type: 'init', windowId: nativeLoopWindowId }),
632
675
  // options: { search, archived, roots, limit }. A server without
@@ -645,9 +688,9 @@ export const api = {
645
688
  compact: ({ sessionId, cwd, tools } = {}) => request({ type: 'session:compact', sessionId, cwd, tools }, 5 * 60 * 1000),
646
689
  permissionReply: ({ sessionId, permissionId, response, cwd, message }) =>
647
690
  request({ type: 'permission:reply', sessionId, permissionId, response, cwd, message }),
648
- questionList: ({ cwd, sessionId } = {}) => request({ type: 'question:list', cwd, sessionId }),
649
- questionReply: ({ requestId, answers, cwd, sessionId }) => request({ type: 'question:reply', requestId, answers, cwd, sessionId }),
650
- questionReject: ({ requestId, sessionId }) => request({ type: 'question:reject', requestId, sessionId }),
691
+ questionList: ({ cwd, sessionId } = {}) => workerQuestionRuns.has(sessionId) ? Promise.resolve({ data: workerQuestions(workerQuestionRuns.get(sessionId)) }) : request({ type: 'question:list', cwd, sessionId }),
692
+ questionReply: ({ requestId, answers, cwd, sessionId }) => workerQuestionRuns.has(sessionId) ? answerOpenCodeWorker({ requestId, answers, sessionId }) : request({ type: 'question:reply', requestId, answers, cwd, sessionId }),
693
+ questionReject: ({ requestId, sessionId }) => workerQuestionRuns.has(sessionId) ? answerOpenCodeWorker({ requestId, sessionId }, true) : request({ type: 'question:reject', requestId, sessionId }),
651
694
  toolsGet: (sessionId) => gated({ type: 'mcp:tools:get', sessionId }),
652
695
  toolsSet: (sessionId, tools) => gated({ type: 'mcp:tools:set', sessionId, tools }, 45_000),
653
696
 
@@ -4,7 +4,9 @@
4
4
 
5
5
  const SVG_CLAUDE = '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M4.709 15.955l4.72-2.647.08-.23-.08-.128H9.2l-.79-.048-2.698-.073-2.339-.097-2.266-.122-.571-.121L0 11.784l.055-.352.48-.321.686.06 1.52.103 2.278.158 1.652.097 2.449.255h.389l.055-.157-.134-.098-.103-.097-2.358-1.596-2.552-1.688-1.336-.972-.724-.491-.364-.462-.158-1.008.656-.722.881.06.225.061.893.686 1.908 1.476 2.491 1.833.365.304.145-.103.019-.073-.164-.274-1.355-2.446-1.446-2.49-.644-1.032-.17-.619a2.97 2.97 0 01-.104-.729L6.283.134 6.696 0l.996.134.42.364.62 1.414 1.002 2.229 1.555 3.03.456.898.243.832.091.255h.158V9.01l.128-1.706.237-2.095.23-2.695.08-.76.376-.91.747-.492.584.28.48.685-.067.444-.286 1.851-.559 2.903-.364 1.942h.212l.243-.242.985-1.306 1.652-2.064.73-.82.85-.904.547-.431h1.033l.76 1.129-.34 1.166-1.064 1.347-.881 1.142-1.264 1.7-.79 1.36.073.11.188-.02 2.856-.606 1.543-.28 1.841-.315.833.388.091.395-.328.807-1.969.486-2.309.462-3.439.813-.042.03.049.061 1.549.146.662.036h1.622l3.02.225.79.522.474.638-.079.485-1.215.62-1.64-.389-3.829-.91-1.312-.329h-.182v.11l1.093 1.068 2.006 1.81 2.509 2.33.127.578-.322.455-.34-.049-2.205-1.657-.851-.747-1.926-1.62h-.128v.17l.444.649 2.345 3.521.122 1.08-.17.353-.608.213-.668-.122-1.374-1.925-1.415-2.167-1.143-1.943-.14.08-.674 7.254-.316.37-.729.28-.607-.461-.322-.747.322-1.476.389-1.924.315-1.53.286-1.9.17-.632-.012-.042-.14.018-1.434 1.967-2.18 2.945-1.726 1.845-.414.164-.717-.37.067-.662.401-.589 2.388-3.036 1.44-1.882.93-1.086-.006-.158h-.055L4.132 18.56l-1.13.146-.487-.456.061-.746.231-.243 1.908-1.312-.006.006z"/></svg>';
6
6
  const SVG_OPENAI = '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M9.205 8.658v-2.26c0-.19.072-.333.238-.428l4.543-2.616c.619-.357 1.356-.523 2.117-.523 2.854 0 4.662 2.212 4.662 4.566 0 .167 0 .357-.024.547l-4.71-2.759a.797.797 0 0 0-.856 0l-5.97 3.473zm10.609 8.8V12.06c0-.333-.143-.57-.429-.737l-5.97-3.473 1.95-1.118a.433.433 0 0 1 .476 0l4.543 2.617c1.309.76 2.189 2.378 2.189 3.948 0 1.808-1.07 3.473-2.76 4.163zM7.802 12.703l-1.95-1.142c-.167-.095-.239-.238-.239-.428V5.899c0-2.545 1.95-4.472 4.591-4.472 1 0 1.927.333 2.712.928L8.23 5.067c-.285.166-.428.404-.428.737v6.898zM12 15.128l-2.795-1.57v-3.33L12 8.658l2.795 1.57v3.33L12 15.128zm1.796 7.23c-1 0-1.927-.332-2.712-.927l4.686-2.712c.285-.166.428-.404.428-.737v-6.898l1.974 1.142c.167.095.238.238.238.428v5.233c0 2.545-1.974 4.472-4.614 4.472zm-5.637-5.303l-4.544-2.617c-1.308-.761-2.188-2.378-2.188-3.948A4.482 4.482 0 0 1 4.21 6.327v5.423c0 .333.143.571.428.738l5.947 3.449-1.95 1.118a.432.432 0 0 1-.476 0zm-.262 3.9c-2.688 0-4.662-2.021-4.662-4.519 0-.19.024-.38.047-.57l4.686 2.71c.286.167.571.167.856 0l5.97-3.448v2.26c0 .19-.07.333-.237.428l-4.543 2.616c-.619.357-1.356.523-2.117.523zm5.899 2.83a5.947 5.947 0 0 0 5.827-4.756C22.287 18.339 24 15.84 24 13.296c0-1.665-.713-3.282-1.998-4.448.119-.5.19-.999.19-1.498 0-3.401-2.759-5.947-5.946-5.947-.642 0-1.26.095-1.88.31A5.962 5.962 0 0 0 10.205 0a5.947 5.947 0 0 0-5.827 4.757C1.713 5.447 0 7.945 0 10.49c0 1.666.713 3.283 1.998 4.448-.119.5-.19 1-.19 1.499 0 3.401 2.759 5.946 5.946 5.946.642 0 1.26-.095 1.88-.309a5.96 5.96 0 0 0 4.162 1.713z"/></svg>';
7
- const SVG_GEMINI = '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 0 0-.975 0 14.134 14.134 0 0 1-3.679 6.452 14.155 14.155 0 0 1-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 0 0 0 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 0 1 4.45 3.001 14.112 14.112 0 0 1 3.679 6.453.502.502 0 0 0 .975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 0 1 3.001-4.45 14.113 14.113 0 0 1 6.453-3.678.503.503 0 0 0 0-.975 13.245 13.245 0 0 1-2.003-.678z"/></svg>';
7
+ // A line drawing: its paths carry fill="none" themselves, because some places fill every icon they hold
8
+ // (`.resume-provider-icon svg { fill: currentColor }`), which would turn the strokes into a solid triangle.
9
+ const SVG_ANTIGRAVITY = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4.5 20 12 4l7.5 16" fill="none"/><path d="M8.2 20a3.8 3.8 0 0 1 7.6 0" fill="none"/></svg>';
8
10
  const SVG_OPENCODE = '<svg viewBox="0 0 240 300" fill="currentColor"><path fill-rule="evenodd" d="M0 0h240v300H0V0zm60 60v180h120V60H60z"/><rect x="60" y="120" width="120" height="120" opacity=".45"/></svg>';
9
11
  const SVG_SHELL = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>';
10
12
  const SVG_BROWSER = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>';
@@ -15,7 +17,7 @@ const PROVIDER_META = {
15
17
  'claude-code': { id: 'claude-code', label: 'Claude Code', shortLabel: 'Claude', icon: SVG_CLAUDE, color: '#D4A27F' },
16
18
  codex: { id: 'codex', label: 'Codex', shortLabel: 'Codex', icon: SVG_OPENAI, color: '#74C7A5' },
17
19
  opencode: { id: 'opencode', label: 'OpenCode', shortLabel: 'OpenCode', icon: SVG_OPENCODE, color: '#E8E0DC' },
18
- gemini: { id: 'gemini', label: 'Gemini', shortLabel: 'Gemini', icon: SVG_GEMINI, color: '#669DF6' },
20
+ antigravity: { id: 'antigravity', label: 'Antigravity', shortLabel: 'Antigravity', icon: SVG_ANTIGRAVITY, color: '#669DF6' },
19
21
  synabun: { id: 'synabun', label: 'SynaBun', shortLabel: 'SynaBun', icon: SVG_SYNABUN, color: '#FFD23C' },
20
22
  shell: { id: 'shell', label: 'Shell', shortLabel: 'Shell', icon: SVG_SHELL, color: '#AAAAAA' },
21
23
  browser: { id: 'browser', label: 'Browser', shortLabel: 'Browser', icon: SVG_BROWSER, color: '#4FC3F7' },
@@ -20,9 +20,9 @@
20
20
  // `tx` in Settings; the wizard reads the same catalogue, i18n/en.json).
21
21
 
22
22
  /** The tools SynaBun installs rules for, in the order both surfaces list them. Cursor is copy-only. */
23
- export const RULES_INSTALL_HOSTS = Object.freeze(['claude', 'gemini', 'codex', 'opencode']);
23
+ export const RULES_INSTALL_HOSTS = Object.freeze(['claude', 'antigravity', 'codex', 'opencode']);
24
24
 
25
- export const RULES_HOST_LABELS = Object.freeze({ claude: 'Claude Code', codex: 'Codex', opencode: 'OpenCode', gemini: 'Gemini', cursor: 'Cursor', coexistence: 'coexistence' });
25
+ export const RULES_HOST_LABELS = Object.freeze({ claude: 'Claude Code', codex: 'Codex', opencode: 'OpenCode', antigravity: 'Antigravity', cursor: 'Cursor', coexistence: 'coexistence' });
26
26
 
27
27
  /** The states that count as "the rules are in place". `shadowed` is installed too, only not loaded (Codex's override file). */
28
28
  export const RULES_IN_PLACE = Object.freeze(['installed', 'newer', 'shadowed']);
@@ -66,7 +66,7 @@ export async function refreshSkillChips(root) {
66
66
  const skill = byDir.get(chip.dataset.ccSkill);
67
67
  if (!skill || chip.style.pointerEvents === 'none') continue;
68
68
  const target = chip.dataset.ccTarget;
69
- const installed = skill.installedTargets || { claude: !!skill.installed, codex: false, opencode: false };
69
+ const installed = skill.installedTargets || { claude: !!skill.installed, codex: false, opencode: false, antigravity: false };
70
70
  const now = chipState(skill.targetStates?.[target], !!installed[target]);
71
71
  chip.classList.toggle('on', now.on);
72
72
  chip.dataset.state = now.on ? 'on' : 'off';
@@ -8,7 +8,7 @@
8
8
  // each runtime chip's state (a native folder, an old bridge, someone else's folder). Every sentence is a
9
9
  // settings.redesign.skills.* key; the pane (settings-skill-folders-pane.js) draws it.
10
10
 
11
- const RUNTIME = { claude: 'settings.redesign.product.claude', codex: 'settings.redesign.product.codex', opencode: 'settings.redesign.product.opencode' };
11
+ const RUNTIME = { claude: 'settings.redesign.product.claude', codex: 'settings.redesign.product.codex', opencode: 'settings.redesign.product.opencode', antigravity: 'settings.redesign.product.antigravity' };
12
12
 
13
13
  /** What the status answer says, or that the server cannot say it yet (one from before the Skills Service). */
14
14
  export function readFoldersStatus(answer) {
@@ -26,7 +26,7 @@ export function readFoldersStatus(answer) {
26
26
  };
27
27
  }
28
28
 
29
- const ASSISTANTS = ['claude', 'codex', 'opencode'];
29
+ const ASSISTANTS = ['claude', 'codex', 'opencode', 'antigravity'];
30
30
 
31
31
  /**
32
32
  * The owner's assistants (GET /v2/config `assistants`: chosen, detected, effective): one chip per runtime, on when
@@ -55,13 +55,18 @@ export const WHOLESALE_SELECTORS = [
55
55
  // OpenCode side panel transcript
56
56
  '.ocpv2-part-text', '.ocpv2-reasoning-body', '.ocpv2-tc-summary', '.ocpv2-tc-meta', '.ocpv2-tc-body',
57
57
  '.ocpv2-tool-input', '.ocpv2-tool-output', '.ocpv2-sb-summary',
58
+ // Antigravity side panel: session names, the transcript, tool and request cards, the plan, what waits to be sent
59
+ '.agy-session-label', '.agy-tab-label', '.agy-sess-list .agy-pop-item-title', '.agy-sess-list .agy-pop-item-meta',
60
+ '.agy-msg-text', '.agy-msg .agy-md', '.agy-msg-image', '.agy-thought-body',
61
+ '.agy-card-title', '.agy-card-subtitle', '.agy-card-text', '.agy-card-body', '.agy-card-error', '.agy-plan-text',
62
+ '.agy-queue-text', '.agy-chip', '.agy-folder-chip',
58
63
  // Assistant transcript, run and worker cards
59
64
  '.asst-msg-text', '.asst-md', '.asst-thinking-content', '.asst-run-title', '.asst-run-summary', '.asst-run-escalate-reason',
60
65
  '.asst-tool-detail', '.asst-tool-body', '.asst-tool-section', '.asst-tl-said', '.asst-tl-subject', '.asst-bubble-text',
61
66
  '.asst-station-sub', '.asst-station-log', '.asst-auto-title', '.asst-auto-summary', '.asst-auto-detail',
62
67
  // Composers, once they hold text (an empty one keeps its placeholder sharp)
63
68
  '#cp-input:not(:placeholder-shown)', '#cxp-input:not(:placeholder-shown)', '.ocpv2-compose-input:not(:placeholder-shown)',
64
- '.asst-input:not(:placeholder-shown)',
69
+ '.asst-input:not(:placeholder-shown)', '.agy-input:not(:placeholder-shown)',
65
70
  // Terminal output (the WebGL canvas and the DOM renderer alike) and the terminal's browser tab
66
71
  '.term-viewport .xterm-screen', '.term-float-tab-body .xterm', '.browser-canvas', '.browser-url-input', '.browser-title-label',
67
72
  // Resume results
@@ -8,8 +8,8 @@
8
8
  // Assistant (sessionStatus). The same source as the tray pills, never a second
9
9
  // guess. DOM-free.
10
10
 
11
- const WORKING = /^(cp|cxp|ocpv2|asp)-pill-running$/;
12
- const NEEDS_YOU = /^((cp|cxp|asp)-pill-attention|ocpv2-pill-waiting)$/;
11
+ const WORKING = /^(cp|cxp|ocpv2|agy|asp)-pill-running$/;
12
+ const NEEDS_YOU = /^((cp|cxp|agy|asp)-pill-attention|ocpv2-pill-waiting)$/;
13
13
 
14
14
  /** The states a detached window's pill shows: a session in it needs you > one works > none. */
15
15
  export const SESSION_WINDOW_STATES = Object.freeze(['action', 'working', 'idle']);
@@ -44,6 +44,6 @@ export function normalizeSessionWindowStatus(status) {
44
44
  */
45
45
  export function runAwaitsAnswer(run) {
46
46
  if (run?.status !== 'starting' && run?.status !== 'running') return false;
47
- if (run.turnState === 'needs_input') return true;
47
+ if (run.turnState === 'needs_input' || run.turnState === 'awaiting_permission' || run.pending?.some((p) => p.kind === 'question')) return true;
48
48
  return run.turnState === 'idle' && (run.outcome || run.lastResult?.status) === 'needs_input';
49
49
  }
@@ -8,14 +8,14 @@
8
8
  // one reading of a request for the page and the server. No DOM and no imports.
9
9
  //
10
10
  // describe the owner's words only
11
- // conversation one session transcript of Claude Code, Codex or OpenCode (read by the server, as data)
11
+ // conversation one session transcript of Claude Code, Codex, OpenCode or Antigravity (read by the server, as data)
12
12
  // url a web page, read by the server with the SynaBun browser before the worker starts (as data)
13
13
  // repo a registered project, read by the server: its file tree, docs, manifests and the files named
14
14
  // memory a recall over SynaBun's memory
15
15
  // import an imported skill (a staged import), or a skill of the catalog or the library, as the starting point
16
16
 
17
17
  export const SOURCE_KINDS = Object.freeze(['describe', 'conversation', 'url', 'repo', 'memory', 'import']);
18
- export const CONVERSATION_PROVIDERS = Object.freeze(['claude-code', 'codex', 'opencode']);
18
+ export const CONVERSATION_PROVIDERS = Object.freeze(['claude-code', 'codex', 'opencode', 'antigravity']);
19
19
  /**
20
20
  * Who drafts: Claude Code only. A drafting worker reads hostile sources, so it is held to its folder with file tools
21
21
  * only (lib/skills/draft-confinement.js), which Claude Code enforces before any permission mode; Codex starts MCP
@@ -23,7 +23,8 @@ export const CONVERSATION_PROVIDERS = Object.freeze(['claude-code', 'codex', 'op
23
23
  */
24
24
  /** The providers a drafting run can be held to its folder on (lib/skills/draft-confinement.js draftConfinementSupport). */
25
25
  export const DRAFT_PROVIDERS = Object.freeze(['claude-code', 'codex', 'opencode']);
26
- export const CREATION_TARGET_RUNTIMES = Object.freeze(['claude', 'codex', 'opencode', 'agents']);
26
+ /** Where a drafted skill may be installed later (a deploy.js target): Antigravity too, though no Antigravity worker drafts. */
27
+ export const CREATION_TARGET_RUNTIMES = Object.freeze(['claude', 'codex', 'opencode', 'antigravity', 'agents']);
27
28
 
28
29
  /** The stages of a creation, in order; the last four end it. */
29
30
  export const CREATION_STAGES = Object.freeze(['queued', 'gathering', 'drafting', 'checking', 'submitting', 'done', 'failed', 'cancelled', 'interrupted']);
@@ -184,6 +185,8 @@ export function normalizeCreationRequest(body, { submitDefault = true } = {}) {
184
185
  const scope = t.scope === undefined || t.scope === null || t.scope === '' ? 'user' : t.scope;
185
186
  if (scope !== 'user' && scope !== 'project') { problems.push({ field: `${at}.scope`, code: 'targets-scope' }); return; }
186
187
  if (t.runtime === 'agents' && scope !== 'user') { problems.push({ field: `${at}.scope`, code: 'targets-agents-scope' }); return; }
188
+ // Antigravity reads a project's .agents/skills, which is the codex target with scope project.
189
+ if (t.runtime === 'antigravity' && scope !== 'user') { problems.push({ field: `${at}.scope`, code: 'targets-antigravity-scope' }); return; }
187
190
  const accountId = t.accountId === undefined || t.accountId === null || t.accountId === '' ? 'default' : t.accountId;
188
191
  if (typeof accountId !== 'string' || !ACCOUNT_RE.test(accountId)) { problems.push({ field: `${at}.accountId`, code: 'targets-account' }); return; }
189
192
  let projectPath = null;
@@ -30,9 +30,11 @@ export function closeDialog() { _open?.giveUp(); }
30
30
  * @param {string|null} [o.cancelId] what Escape and a click outside answer; null: they answer null
31
31
  * @param {HTMLElement|null} [o.extra] more content after the text (a checkbox, a list)
32
32
  * @param {(answer: string, ui: object) => boolean} [o.accept] called before the answer is given: false keeps it open
33
+ * @param {(ui: object) => void} [o.onOpen] called once it is drawn, with { box, body, bar, byId, setDisabled, done }
34
+ * @param {string} [o.className] one more class on the dialog (a variant: `ss-dlg--confirm`)
33
35
  * @returns {Promise<string|null>} the id of the button, or `cancelId` / null
34
36
  */
35
- export function askInPanel({ host, title, lines = [], buttons, focus = null, cancelId = 'cancel', extra = null, accept = null, wide = false }) {
37
+ export function askInPanel({ host, title, lines = [], buttons, focus = null, cancelId = 'cancel', extra = null, accept = null, wide = false, onOpen = null, className = '' }) {
36
38
  return new Promise((resolve) => {
37
39
  _open?.giveUp();
38
40
  const before = document.activeElement;
@@ -41,7 +43,7 @@ export function askInPanel({ host, title, lines = [], buttons, focus = null, can
41
43
  const backdrop = document.createElement('div');
42
44
  backdrop.className = 'ss-fd-backdrop';
43
45
  const box = document.createElement('div');
44
- box.className = `ss-file-dialog ss-dlg${wide ? ' ss-dlg--wide' : ''}`;
46
+ box.className = `ss-file-dialog ss-dlg${wide ? ' ss-dlg--wide' : ''}${className ? ` ${className}` : ''}`;
45
47
  const uid = `ss-dlg-${Math.random().toString(36).slice(2, 8)}`;
46
48
  box.setAttribute('role', 'alertdialog');
47
49
  box.setAttribute('aria-modal', 'true');
@@ -126,5 +128,6 @@ export function askInPanel({ host, title, lines = [], buttons, focus = null, can
126
128
  const start = (focus && byId.get(focus)) || [...buttons].reverse().map((b) => byId.get(b.id)).find((n) => n && !n.disabled && !n.classList.contains('ss-dlg-btn--danger')) || enabled()[0];
127
129
  start?.focus({ preventScroll: true });
128
130
  ui.done = done;
131
+ if (typeof onOpen === 'function') onOpen(ui);
129
132
  });
130
133
  }
@@ -81,7 +81,12 @@ export function lintArtifact({ type, parsed, folderName = '', knownNames = [] })
81
81
  if (typeof desc !== 'string') add('description-type', 'error', `"description" must be text; it is ${kindOf(desc)}.`, lineOf('description'));
82
82
  else if (!desc.trim()) add('description-empty', 'error', '"description" is empty.', lineOf('description'));
83
83
  else {
84
- if (desc.length > DESCRIPTION_MAX) add('description-length', 'error', `"description" is ${desc.length} characters; the limit is ${DESCRIPTION_MAX}.`, lineOf('description'));
84
+ // The 1024-character limit is the skill format's (Claude Code, Codex and the portable format refuse a longer
85
+ // SKILL.md). An agent or a command has none: a long description only costs context, which is no error.
86
+ if (desc.length > DESCRIPTION_MAX) {
87
+ if (type === 'skill') add('description-length', 'error', `"description" is ${desc.length} characters; the limit is ${DESCRIPTION_MAX}.`, lineOf('description'));
88
+ else add('description-length', 'info', `"description" is ${desc.length} characters: every session carries it, so a shorter one leaves more room.`, lineOf('description'));
89
+ }
85
90
  const hidden = findHiddenChars(desc);
86
91
  if (hidden.length) add('description-hidden-chars', 'error', `"description" holds characters nobody can see: ${hidden.slice(0, 5).map((h) => h.label).join(', ')}${hidden.length > 5 ? '…' : ''}.`, lineOf('description'));
87
92
  }
@@ -99,6 +104,9 @@ export function lintArtifact({ type, parsed, folderName = '', knownNames = [] })
99
104
  if (has(key) && typeof data[key] !== 'boolean') add(`${key}-type`, 'error', `"${key}" must be true or false; it is ${kindOf(data[key])}.`, lineOf(key));
100
105
  }
101
106
  for (const key of STRING_KEYS) {
107
+ // `argument-hint: [message]` is the form Claude Code's own documentation writes: YAML reads the brackets as a
108
+ // list of text and Claude Code shows it as written. Only another kind of value is an error.
109
+ if (key === 'argument-hint' && Array.isArray(data[key]) && data[key].every((v) => typeof v === 'string' || typeof v === 'number')) continue;
102
110
  if (has(key) && data[key] !== null && typeof data[key] !== 'string') add(`${key}-type`, 'error', `"${key}" must be text; it is ${kindOf(data[key])}.`, lineOf(key));
103
111
  }
104
112
  for (const key of TOOL_KEYS) {