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
@@ -0,0 +1,392 @@
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun — Antigravity panel: the transcript and its cards as HTML (no DOM)
3
+ // ═══════════════════════════════════════════
4
+ //
5
+ // Every builder returns a string; every value in it goes through esc() (text or a
6
+ // quoted attribute), Markdown through the caller's `md` (cp/cp-markdown.js
7
+ // renderMarkdown: marked + the allowlist sanitiser). The look is the Codex
8
+ // panel's (docs/codex-sidepanel.md → Visual language): one card anatomy (head:
9
+ // icon, title, subtitle, status, chevron; then a body), one container treatment
10
+ // per level, colour only for a status. `T(key, params)` is agyText.
11
+ //
12
+ // Output detail levels (the panel's `detail` setting): compact hides thoughts and
13
+ // keeps every card closed, standard folds thoughts and opens file changes,
14
+ // verbose opens everything and adds each tool call's raw input and output.
15
+
16
+ import { diffView, DIFF_LIMITS } from './agy-diff.js';
17
+ import { durationLabel, toolSummary, usageParts, compactNumber } from './agy-events.js';
18
+ import { relativeToProject } from './agy-commands.js';
19
+ import { elicitationFields, elicitationUrl } from './agy-options.js';
20
+
21
+ const HTML_ESCAPES = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;', '`': '&#96;' };
22
+ export const esc = (value) => String(value ?? '').replace(/[&<>"'`]/g, (c) => HTML_ESCAPES[c]);
23
+
24
+ // ── Icons (stroke, currentColor) ──
25
+ const svg = (body, { size = 14, fill = 'none', width = 2 } = {}) => `<svg viewBox="0 0 24 24" width="${size}" height="${size}" fill="${fill}" stroke="currentColor" stroke-width="${width}" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${body}</svg>`;
26
+ export const ICONS = Object.freeze({
27
+ mark: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4.5 20 12 4l7.5 16"/><path d="M8.2 20a3.8 3.8 0 0 1 7.6 0"/></svg>',
28
+ read: svg('<path d="M14 3H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"/><path d="M14 3v6h6M8 13h8M8 17h5"/>'),
29
+ edit: svg('<path d="M12 20h9"/><path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4z"/>'),
30
+ delete: svg('<path d="M3 6h18M8 6V4h8v2M19 6l-1 14H6L5 6"/>'),
31
+ move: svg('<path d="M5 12h14M13 6l6 6-6 6"/>'),
32
+ search: svg('<circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/>'),
33
+ execute: svg('<path d="m4 17 6-5-6-5M12 19h8"/>'),
34
+ think: svg('<path d="M9 18h6M10 22h4M12 2a7 7 0 0 0-4 12.7V16h8v-1.3A7 7 0 0 0 12 2z"/>'),
35
+ fetch: svg('<circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18"/>'),
36
+ switch_mode: svg('<rect x="2" y="7" width="20" height="10" rx="5"/><circle cx="16" cy="12" r="3"/>'),
37
+ other: svg('<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1.1 1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z"/>'),
38
+ plan: svg('<path d="M9 6h11M9 12h11M9 18h11"/><path d="m3 6 1 1 2-2M3 12l1 1 2-2M3 18l1 1 2-2"/>'),
39
+ compaction: svg('<path d="M4 14h6v6M20 10h-6V4M14 10l7-7M3 21l7-7"/>'),
40
+ subagent: svg('<circle cx="12" cy="7" r="3"/><path d="M5 21v-2a7 7 0 0 1 14 0v2"/>'),
41
+ permission: svg('<path d="M12 3 4 6v6c0 5 3.4 8.5 8 9 4.6-.5 8-4 8-9V6z"/>'),
42
+ question: svg('<circle cx="12" cy="12" r="9"/><path d="M9.5 9a2.5 2.5 0 1 1 3.3 2.4c-.5.2-.8.7-.8 1.2V14M12 17.5v.01"/>'),
43
+ copy: svg('<rect x="9" y="9" width="12" height="12" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>', { size: 12 }),
44
+ retry: svg('<path d="M3 12a9 9 0 1 0 3-6.7L3 8"/><path d="M3 3v5h5"/>', { size: 12 }),
45
+ check: svg('<path d="m5 12 5 5L20 7"/>', { size: 12, width: 2.5 }),
46
+ cross: svg('<path d="M6 6l12 12M18 6 6 18"/>', { size: 12, width: 2.5 }),
47
+ });
48
+
49
+ const KIND_ICON = { read: 'read', edit: 'edit', delete: 'delete', move: 'move', search: 'search', execute: 'execute', think: 'think', fetch: 'fetch', switch_mode: 'switch_mode' };
50
+
51
+ const STATUS_TONE = { pending: 'running', in_progress: 'running', completed: 'success', failed: 'error' };
52
+ const NOTICE_KEYS = { cancelled: 'stopped', refusal: 'refusal', max_tokens: 'maxTokens', max_turn_requests: 'maxTurns' };
53
+
54
+ // ── Pieces ──
55
+
56
+ function statusPill(text, tone = '') {
57
+ return `<span class="agy-status-pill"${tone ? ` data-tone="${esc(tone)}"` : ''}>${esc(text)}</span>`;
58
+ }
59
+
60
+ /** One card: head (icon, title, subtitle, status, chevron) and an optional body. */
61
+ export function cardHtml({ cls = '', icon = '', title = '', subtitle = '', status = '', body = '', open = false, attrs = '', titleAttr = '' }) {
62
+ const expandable = !!body;
63
+ const head = `<div class="agy-card-head"${expandable ? ` role="button" tabindex="0" aria-expanded="${open ? 'true' : 'false'}"` : ''}>`
64
+ + `<span class="agy-card-icon">${icon}</span>`
65
+ + `<span class="agy-card-titles"><span class="agy-card-title"${titleAttr ? ` title="${esc(titleAttr)}"` : ''}>${title}</span>${subtitle ? `<span class="agy-card-subtitle">${subtitle}</span>` : ''}</span>`
66
+ + status
67
+ + (expandable ? '<span class="agy-card-chevron" aria-hidden="true"></span>' : '')
68
+ + '</div>';
69
+ return `<div class="agy-card ${cls}${expandable && !open ? ' agy-collapsed' : ''}"${attrs}>${head}${expandable ? `<div class="agy-card-body">${body}</div>` : ''}</div>`;
70
+ }
71
+
72
+ function preBlock(text, { cls = '', max = 20000, label = '' } = {}) {
73
+ const value = String(text ?? '');
74
+ const clipped = value.length > max;
75
+ return `<div class="agy-pre-wrap"><button type="button" class="agy-copy-btn" data-copy-pre="1" aria-label="${esc(label)}" title="${esc(label)}">${ICONS.copy}</button><pre class="agy-pre ${cls}">${esc(clipped ? value.slice(0, max) : value)}</pre>${clipped ? '<div class="agy-pre-note">…</div>' : ''}</div>`;
76
+ }
77
+
78
+ function jsonBlock(value, T) {
79
+ let text;
80
+ try { text = JSON.stringify(value, null, 2); } catch { text = String(value); }
81
+ return preBlock(text, { cls: 'agy-pre-json', max: 12000, label: T('copy') });
82
+ }
83
+
84
+ /** A unified diff of one ACP diff item: path header with counts, numbered rows, "Show the whole diff" past the preview. */
85
+ export function diffHtml(item, { T, cwd = '', full = false } = {}) {
86
+ const view = diffView(item);
87
+ const shown = full ? view.rows : view.rows.slice(0, DIFF_LIMITS.previewRows);
88
+ const more = view.rows.length - shown.length;
89
+ const rel = relativeToProject(cwd, view.path);
90
+ const status = view.status === 'added' ? T('diffAdded') : view.status === 'deleted' ? T('diffDeleted') : T('diffModified');
91
+ const rows = shown.map((row) => {
92
+ if (row.kind === 'hunk') return `<div class="agy-diff-line" data-kind="hunk"><span class="agy-diff-old"></span><span class="agy-diff-new"></span><span class="agy-diff-sign"></span><span class="agy-diff-text">${esc(row.text)}</span></div>`;
93
+ if (row.kind === 'note') return `<div class="agy-diff-line" data-kind="note"><span class="agy-diff-old"></span><span class="agy-diff-new"></span><span class="agy-diff-sign"></span><span class="agy-diff-text">${esc(T('diffTruncated'))}</span></div>`;
94
+ const sign = row.kind === 'add' ? '+' : row.kind === 'delete' ? '−' : ' ';
95
+ return `<div class="agy-diff-line" data-kind="${row.kind}"><span class="agy-diff-old">${row.old ?? ''}</span><span class="agy-diff-new">${row.new ?? ''}</span><span class="agy-diff-sign">${sign}</span><span class="agy-diff-text">${esc(row.text)}</span></div>`;
96
+ }).join('');
97
+ const note = view.identical ? `<div class="agy-diff-empty">${esc(T('diffIdentical'))}</div>` : view.fallback ? `<div class="agy-diff-empty">${esc(T('diffTooLarge'))}</div>` : '';
98
+ return `<div class="agy-diff" data-path="${esc(view.path)}" aria-label="${esc(T('diffFor', { path: rel }))}">`
99
+ + `<div class="agy-diff-header"><a class="agy-diff-path" href="${esc(view.path)}" data-file-target="${esc(view.path)}" title="${esc(view.path)}">${esc(rel)}</a>`
100
+ + `<span class="agy-diff-status">${esc(status)}</span><span class="agy-diff-stats"><span class="add">+${view.added}</span> <span class="del">−${view.removed}</span></span>`
101
+ + `<button type="button" class="agy-copy-btn" data-copy-diff="${esc(view.path)}" aria-label="${esc(T('copyPatch'))}" title="${esc(T('copyPatch'))}">${ICONS.copy}</button></div>`
102
+ + `${note}<div class="agy-diff-lines">${rows}</div>`
103
+ + (more > 0 ? `<button type="button" class="agy-link-btn" data-diff-full="${esc(view.path)}">${esc(T('diffShowAll', { count: view.rows.length }))}</button>` : '')
104
+ + '</div>';
105
+ }
106
+
107
+ /** The output of a terminal SynaBun runs for the agent, live while it runs. */
108
+ export function terminalHtml(terminal, { T }) {
109
+ if (!terminal) return `<div class="agy-terminal agy-terminal--waiting">${esc(T('terminalWaiting'))}</div>`;
110
+ const exit = terminal.exitStatus;
111
+ const state = !exit ? `<span class="agy-terminal-state" data-tone="running">${esc(T('terminalRunning'))}</span>`
112
+ : exit.signal ? `<span class="agy-terminal-state" data-tone="error">${esc(T('terminalSignal', { signal: exit.signal }))}</span>`
113
+ : `<span class="agy-terminal-state" data-tone="${exit.exitCode === 0 ? 'success' : 'error'}">${esc(T('terminalExit', { code: exit.exitCode ?? '?' }))}</span>`;
114
+ return `<div class="agy-terminal">${preBlock(terminal.output || '', { cls: 'agy-pre-terminal', max: 64000, label: T('copy') })}<div class="agy-terminal-foot">${terminal.truncated ? `<span>${esc(T('terminalTruncated'))}</span>` : ''}${state}</div></div>`;
115
+ }
116
+
117
+ function locationsHtml(locations, cwd) {
118
+ if (!Array.isArray(locations) || !locations.length) return '';
119
+ return `<div class="agy-locations">${locations.slice(0, 20).map((l) => {
120
+ const target = l.line ? `${l.path}:${l.line}` : l.path;
121
+ const shown = l.line ? `${relativeToProject(cwd, l.path)}:${l.line}` : relativeToProject(cwd, l.path);
122
+ return `<a class="agy-file-link" href="${esc(l.path)}" data-file-target="${esc(target)}" title="${esc(target)}">${esc(shown)}</a>`;
123
+ }).join('')}</div>`;
124
+ }
125
+
126
+ // ── Rows ──
127
+
128
+ /** Whether a tool card starts open: the user's choice first, else the detail level and what it holds. */
129
+ export function toolOpen(row, { detail = 'standard', opened = new Set(), closed = new Set() } = {}) {
130
+ if (opened.has(row.toolCallId)) return true;
131
+ if (closed.has(row.toolCallId)) return false;
132
+ if (detail === 'verbose') return true;
133
+ if (detail === 'compact') return false;
134
+ // Standard: file changes open; a command's live output while it runs; a failure with something to say.
135
+ const live = (row.status === 'pending' || row.status === 'in_progress') && row.content.some((item) => item.type === 'terminal');
136
+ return live || row.content.some((item) => item.type === 'diff') || (row.status === 'failed' && row.content.length > 0);
137
+ }
138
+
139
+ export function toolHtml(row, { T, cwd = '', detail = 'standard', opened, closed, terminals = new Map(), fullDiffs = new Set() } = {}) {
140
+ const parts = [];
141
+ for (const item of row.content || []) {
142
+ if (item.type === 'diff') parts.push(diffHtml(item, { T, cwd, full: fullDiffs.has(item.path) }));
143
+ else if (item.type === 'terminal') parts.push(terminalHtml(terminals.get(item.terminalId), { T }));
144
+ else if (item.type === 'content' && item.text) parts.push(preBlock(item.text, { label: T('copy') }));
145
+ else if (item.type === 'content' && item.attachment?.kind === 'image' && /^[A-Za-z0-9+/=]+$/.test(item.attachment.data || '')) {
146
+ parts.push(`<img class="agy-tool-image" src="data:${esc(item.attachment.mimeType)};base64,${item.attachment.data}" alt="">`);
147
+ }
148
+ }
149
+ parts.push(locationsHtml(row.locations, cwd));
150
+ if (detail === 'verbose') {
151
+ if (row.rawInput !== null && row.rawInput !== undefined) parts.push(`<div class="agy-raw"><div class="agy-raw-label">${esc(T('rawInput'))}</div>${jsonBlock(row.rawInput, T)}</div>`);
152
+ if (row.rawOutput !== null && row.rawOutput !== undefined) parts.push(`<div class="agy-raw"><div class="agy-raw-label">${esc(T('rawOutput'))}</div>${jsonBlock(row.rawOutput, T)}</div>`);
153
+ }
154
+ const body = parts.filter(Boolean).join('');
155
+ const summary = toolSummary(row);
156
+ const shown = row.toolKind === 'execute' ? summary : relativeToProject(cwd, summary);
157
+ const duration = durationLabel(row.startedAt, row.endedAt);
158
+ const kindWord = T(`kind_${KIND_ICON[row.toolKind] ? row.toolKind : 'other'}`);
159
+ const subtitle = esc([kindWord, row.name && row.name !== kindWord ? row.name : '', duration].filter(Boolean).join(' · '));
160
+ const statusWord = row.status === 'completed' ? T('toolDone') : row.status === 'failed' ? T('toolFailed') : row.status === 'in_progress' ? T('toolRunning') : T('toolPending');
161
+ return cardHtml({
162
+ cls: `agy-tool status-${esc(row.status)}`,
163
+ icon: ICONS[KIND_ICON[row.toolKind] || 'other'],
164
+ title: `<span class="agy-mono">${esc(shown)}</span>`,
165
+ titleAttr: summary,
166
+ subtitle,
167
+ status: statusPill(statusWord, STATUS_TONE[row.status] === 'success' ? '' : STATUS_TONE[row.status] || ''),
168
+ body,
169
+ open: toolOpen(row, { detail, opened, closed }),
170
+ attrs: ` data-tool="${esc(row.toolCallId)}"`,
171
+ });
172
+ }
173
+
174
+ function planHtml(row, { T, md, cwd }) {
175
+ if (row.format === 'markdown') {
176
+ return cardHtml({ cls: 'agy-plan', icon: ICONS.plan, title: esc(T('plan')), body: `<div class="agy-md">${md(row.content || '')}</div>`, open: true, attrs: row.planId ? ` data-plan="${esc(row.planId)}"` : '' });
177
+ }
178
+ if (row.format === 'file') {
179
+ const path = String(row.uri || '').replace(/^file:\/\//, '');
180
+ return cardHtml({ cls: 'agy-plan', icon: ICONS.plan, title: esc(T('plan')), subtitle: `<a class="agy-file-link" href="${esc(path)}" data-file-target="${esc(path)}">${esc(relativeToProject(cwd, path))}</a>`, attrs: row.planId ? ` data-plan="${esc(row.planId)}"` : '' });
181
+ }
182
+ const entries = row.entries || [];
183
+ const done = entries.filter((e) => e.status === 'completed').length;
184
+ const list = `<ol class="agy-plan-list">${entries.map((e) => `<li class="agy-plan-item" data-status="${esc(e.status)}"><span class="agy-plan-mark" aria-hidden="true"></span><span class="agy-plan-text">${esc(e.content)}</span>${e.priority === 'high' ? `<span class="agy-plan-priority">${esc(T('priorityHigh'))}</span>` : ''}</li>`).join('')}</ol>`;
185
+ const running = entries.some((e) => e.status === 'in_progress');
186
+ return cardHtml({
187
+ cls: 'agy-plan', icon: ICONS.plan, title: esc(T('plan')), subtitle: esc(T('planProgress', { done, total: entries.length })),
188
+ status: statusPill(done === entries.length && entries.length ? T('planDone') : running ? T('toolRunning') : T('toolPending'), done === entries.length && entries.length ? '' : running ? 'running' : ''),
189
+ body: list, open: true, attrs: row.planId ? ` data-plan="${esc(row.planId)}"` : '',
190
+ });
191
+ }
192
+
193
+ function thoughtHtml(row, { T, detail, running, folds = new Map() }) {
194
+ if (detail === 'compact') {
195
+ return running && !row.closed ? `<div class="agy-system working">${esc(T('thinkingNow'))}</div>` : '';
196
+ }
197
+ const duration = durationLabel(row.startedAt, row.endedAt);
198
+ const label = !row.closed && running ? T('thinkingNow') : duration ? T('thoughtFor', { duration }) : T('thought');
199
+ // The user's own open or close of this fold wins over the detail level, through every redraw.
200
+ const open = folds.has(row.id) ? folds.get(row.id) : detail === 'verbose';
201
+ return `<details class="agy-thought"${open ? ' open' : ''} data-row="${esc(row.id)}"><summary>${esc(label)}</summary><div class="agy-thought-body">${esc(row.text)}</div></details>`;
202
+ }
203
+
204
+ function attachmentsHtml(list) {
205
+ if (!Array.isArray(list) || !list.length) return '';
206
+ return `<div class="agy-attach-list">${list.map((a) => {
207
+ if (a.kind === 'image' && typeof a.data === 'string' && /^data:image\/(png|jpeg|gif|webp);base64,/.test(a.data)) return `<img class="agy-msg-image" src="${esc(a.data)}" alt="${esc(a.name || '')}">`;
208
+ if (a.kind === 'image' && a.data && a.mimeType && /^image\/(png|jpeg|gif|webp)$/.test(a.mimeType) && /^[A-Za-z0-9+/=]+$/.test(a.data)) return `<img class="agy-msg-image" src="data:${esc(a.mimeType)};base64,${a.data}" alt="">`;
209
+ return `<span class="agy-chip"><span>${esc(a.name || a.uri || 'file')}</span></span>`;
210
+ }).join('')}</div>`;
211
+ }
212
+
213
+ function subagentHtml(row, { T }) {
214
+ const tone = row.state === 'running' ? 'running' : row.state === 'requires_action' ? 'warning' : '';
215
+ const word = row.state === 'running' ? T('subagentRunning') : row.state === 'idle' ? T('subagentIdle') : row.state === 'requires_action' ? T('subagentNeedsYou') : T('subagentUnknown');
216
+ return cardHtml({
217
+ cls: 'agy-subagent', icon: ICONS.subagent, title: esc(row.title || T('subagent')), subtitle: esc(row.description || ''),
218
+ status: statusPill(word, tone), attrs: ` data-subagent="${esc(row.sessionId)}"`,
219
+ });
220
+ }
221
+
222
+ function compactionHtml(row, { T, md }) {
223
+ const status = row.status === 'completed' ? T('compactionDone') : row.status === 'failed' ? T('compactionFailed') : row.status === 'cancelled' ? T('compactionCancelled') : T('compactionRunning');
224
+ const tone = row.status === 'in_progress' ? 'running' : row.status === 'failed' ? 'error' : row.status === 'cancelled' ? 'warning' : '';
225
+ const body = [row.summary ? `<div class="agy-md">${md(row.summary)}</div>` : '', row.error ? `<div class="agy-card-error">${esc(row.error)}</div>` : ''].join('');
226
+ return cardHtml({ cls: 'agy-compaction', icon: ICONS.compaction, title: esc(T('compaction')), status: statusPill(status, tone), body, open: false, attrs: ` data-compaction="${esc(row.compactionId)}"` });
227
+ }
228
+
229
+ /** One transcript row. `ctx`: { T, md, cwd, detail, running, opened, closed, terminals, fullDiffs }. */
230
+ export function rowHtml(row, ctx) {
231
+ const { T, md } = ctx;
232
+ switch (row.kind) {
233
+ case 'user':
234
+ return `<div class="agy-msg agy-msg-user" data-row="${esc(row.id)}"><div class="agy-msg-card"><div class="agy-msg-text">${esc(row.text)}</div>${attachmentsHtml(row.attachments)}</div></div>`;
235
+ case 'agent':
236
+ if (!row.text && !(row.attachments || []).length) return '';
237
+ return `<div class="agy-msg agy-msg-agent" data-row="${esc(row.id)}"><div class="agy-md">${md(row.text)}</div>${attachmentsHtml(row.attachments)}</div>`;
238
+ case 'thought':
239
+ return thoughtHtml(row, ctx);
240
+ case 'tool':
241
+ return toolHtml(row, ctx);
242
+ case 'plan':
243
+ return planHtml(row, ctx);
244
+ case 'notice': {
245
+ const tone = row.severity === 'error' ? ' error' : row.severity === 'warning' ? ' warning' : '';
246
+ // A turn's own ending (agy-events.js endTurn) is said in the page's language.
247
+ const key = NOTICE_KEYS[row.code];
248
+ return `<div class="agy-system${tone}" role="${row.severity === 'error' ? 'alert' : 'status'}">${esc(key ? T(key) : row.text)}</div>`;
249
+ }
250
+ case 'compaction':
251
+ return compactionHtml(row, ctx);
252
+ case 'subagent':
253
+ return subagentHtml(row, ctx);
254
+ case 'message':
255
+ return `<div class="agy-msg agy-msg-relay" data-row="${esc(row.id)}"><div class="agy-msg-relay-label">${esc(T('relayedMessage'))}</div><div class="agy-md">${md(row.text)}</div></div>`;
256
+ default:
257
+ return '';
258
+ }
259
+ }
260
+
261
+ /** The line under a finished turn: its usage and time, Copy, and Retry on the last one. */
262
+ export function turnFooterHtml(turn, { T, last = false, answer = '' } = {}) {
263
+ const usage = usageParts(turn.usage).map((part) => `<span>${esc(T(`usage_${part.key}`, { value: part.value }))}</span>`).join('<span aria-hidden="true">·</span>');
264
+ const duration = durationLabel(turn.startedAt, turn.endedAt);
265
+ return `<div class="agy-turn-foot" data-turn="${esc(turn.id)}">`
266
+ + `<span class="agy-turn-meta">${usage}${usage && duration ? '<span aria-hidden="true">·</span>' : ''}${duration ? `<span>${esc(duration)}</span>` : ''}</span>`
267
+ + `<span class="agy-turn-actions">${answer ? `<button type="button" class="agy-icon-btn" data-copy-turn="${esc(turn.id)}" aria-label="${esc(T('copyAnswer'))}" title="${esc(T('copyAnswer'))}">${ICONS.copy}</button>` : ''}`
268
+ + `${last ? `<button type="button" class="agy-icon-btn" data-retry="${esc(turn.id)}" aria-label="${esc(T('retry'))}" title="${esc(T('retry'))}">${ICONS.retry}</button>` : ''}</span></div>`;
269
+ }
270
+
271
+ // ── Requests the agent waits on ──
272
+
273
+ export function permissionHtml(requestId, entry, { T, cwd = '' } = {}) {
274
+ const { card } = entry;
275
+ const done = entry.state !== 'open';
276
+ const buttons = card.options.map((option) => {
277
+ const cls = option.tone === 'deny' ? 'agy-btn-deny' : option.kind === 'allow_once' ? 'agy-btn-primary' : '';
278
+ return `<button type="button" class="agy-action ${cls}" data-permission="${esc(requestId)}" data-option="${esc(option.optionId)}"${done ? ' disabled' : ''}>${esc(option.label)}</button>`;
279
+ }).join('') || `<button type="button" class="agy-action agy-btn-deny" data-permission="${esc(requestId)}" data-option=""${done ? ' disabled' : ''}>${esc(T('permissionDeny'))}</button>`;
280
+ const stateWord = entry.state === 'answered' ? T('permissionAnswered') : entry.state === 'cancelled' ? T('permissionCancelled') : entry.state === 'answering' ? T('permissionSending') : '';
281
+ const diffs = (card.diffs || []).map((item) => diffHtml(item, { T, cwd })).join('');
282
+ const title = T('permissionTitle', { verb: T(`verb_${card.kind}`) });
283
+ const body = `${card.title ? `<p class="agy-card-text">${esc(card.title)}</p>` : ''}${diffs || (card.detail ? preBlock(card.detail, { label: T('copy') }) : '')}`
284
+ + `<div class="agy-card-actions">${done ? `<span class="agy-card-state">${esc(stateWord)}</span>` : buttons}</div>`
285
+ + (!done ? `<div class="agy-card-hint">${esc(T('permissionKeys'))}</div>` : '');
286
+ return `<div class="agy-card agy-request${done ? ' agy-request-done' : ''}" role="group" aria-label="${esc(title)}" data-request="${esc(requestId)}">`
287
+ + `<div class="agy-card-head"><span class="agy-card-icon">${ICONS.permission}</span><span class="agy-card-titles"><span class="agy-card-title">${esc(title)}</span></span>${done ? '' : statusPill(T('needsYou'), 'warning')}</div>`
288
+ + `<div class="agy-card-body">${body}</div></div>`;
289
+ }
290
+
291
+ function fieldHtml(requestId, field, { T, error = '' }) {
292
+ const id = `agy-el-${esc(requestId)}-${esc(field.name)}`;
293
+ const label = `<label class="agy-field-label" for="${id}">${esc(field.title)}${field.required ? ' <span aria-hidden="true">*</span>' : ''}</label>`;
294
+ const help = field.description ? `<div class="agy-field-help">${esc(field.description)}</div>` : '';
295
+ const err = error ? `<div class="agy-field-error" role="alert">${esc(T(error))}</div>` : '';
296
+ if (field.type === 'unsupported') return `<div class="agy-field">${label}<div class="agy-field-help">${esc(T('elicitUnsupported'))}</div></div>`;
297
+ if (field.type === 'boolean') {
298
+ return `<div class="agy-field agy-field-check"><label class="agy-check"><input type="checkbox" id="${id}" data-field="${esc(field.name)}"${field.defaultValue === true ? ' checked' : ''}><span>${esc(field.title)}</span></label>${help}${err}</div>`;
299
+ }
300
+ if (field.choices.length) {
301
+ const type = field.multiple ? 'checkbox' : 'radio';
302
+ const defaults = new Set(Array.isArray(field.defaultValue) ? field.defaultValue : field.defaultValue != null ? [String(field.defaultValue)] : []);
303
+ return `<fieldset class="agy-field" role="${field.multiple ? 'group' : 'radiogroup'}"><legend class="agy-field-label">${esc(field.title)}${field.required ? ' <span aria-hidden="true">*</span>' : ''}</legend><div class="agy-choices">${field.choices.map((choice) => `<label class="agy-choice"><input type="${type}" name="${id}" value="${esc(choice.value)}" data-field="${esc(field.name)}"${defaults.has(choice.value) ? ' checked' : ''}><span>${esc(choice.label)}</span></label>`).join('')}</div>${help}${err}</fieldset>`;
304
+ }
305
+ const inputType = field.type === 'number' || field.type === 'integer' ? 'number' : field.format === 'email' ? 'email' : field.format === 'uri' ? 'url' : field.format === 'date' ? 'date' : 'text';
306
+ const attrs = [
307
+ field.min !== null && inputType === 'number' ? ` min="${field.min}"` : '',
308
+ field.max !== null && inputType === 'number' ? ` max="${field.max}"` : '',
309
+ field.type === 'integer' ? ' step="1"' : '',
310
+ field.maxLength !== null ? ` maxlength="${field.maxLength}"` : '',
311
+ field.defaultValue != null && typeof field.defaultValue !== 'object' ? ` value="${esc(field.defaultValue)}"` : '',
312
+ ].join('');
313
+ return `<div class="agy-field">${label}<input class="agy-field-input" type="${inputType}" id="${id}" data-field="${esc(field.name)}"${attrs}>${help}${err}</div>`;
314
+ }
315
+
316
+ /** The agent asks for input (ACP elicitation): a form, or a link to open. */
317
+ export function elicitationHtml(requestId, entry, { T } = {}) {
318
+ const done = entry.state !== 'open';
319
+ const head = `<div class="agy-card-head"><span class="agy-card-icon">${ICONS.question}</span><span class="agy-card-titles"><span class="agy-card-title">${esc(T('elicitTitle'))}</span></span>${done ? '' : statusPill(T('needsYou'), 'warning')}</div>`;
320
+ const message = `<p class="agy-card-text">${esc(entry.message || '')}</p>`;
321
+ let body;
322
+ if (done) {
323
+ const word = entry.state === 'answered' ? T('elicitAnswered') : entry.state === 'declined' ? T('elicitDeclined') : T('permissionCancelled');
324
+ body = `${message}<div class="agy-card-actions"><span class="agy-card-state">${esc(word)}</span></div>`;
325
+ } else if (entry.mode === 'url') {
326
+ const url = elicitationUrl(entry);
327
+ body = `${message}${url ? `<div class="agy-url-box"><code>${esc(url)}</code></div>` : `<div class="agy-card-error">${esc(T('elicitBadUrl'))}</div>`}`
328
+ + `<div class="agy-card-actions">${url ? `<a class="agy-action agy-btn-primary" href="${esc(url)}" target="_blank" rel="noopener noreferrer" data-elicit-open="${esc(requestId)}">${esc(T('elicitOpen'))}</a><button type="button" class="agy-action" data-elicit="${esc(requestId)}" data-action="accept">${esc(T('elicitDone'))}</button>` : ''}`
329
+ + `<button type="button" class="agy-action agy-btn-deny" data-elicit="${esc(requestId)}" data-action="decline">${esc(T('elicitDecline'))}</button></div>`;
330
+ } else if (entry.mode === 'form') {
331
+ const fields = elicitationFields(entry.requestedSchema);
332
+ const errors = entry.errors || {};
333
+ body = `${message}<form class="agy-elicit-form" data-elicit-form="${esc(requestId)}" novalidate>${fields.map((field) => fieldHtml(requestId, field, { T, error: errors[field.name] || '' })).join('')}`
334
+ + `<div class="agy-card-actions"><button type="submit" class="agy-action agy-btn-primary">${esc(T('elicitSubmit'))}</button>`
335
+ + `<button type="button" class="agy-action" data-elicit="${esc(requestId)}" data-action="decline">${esc(T('elicitDecline'))}</button>`
336
+ + `<button type="button" class="agy-action agy-btn-deny" data-elicit="${esc(requestId)}" data-action="cancel">${esc(T('elicitCancel'))}</button></div></form>`;
337
+ } else {
338
+ body = `${message}<div class="agy-card-text">${esc(T('elicitUnsupportedMode'))}</div><div class="agy-card-actions"><button type="button" class="agy-action agy-btn-deny" data-elicit="${esc(requestId)}" data-action="decline">${esc(T('elicitDecline'))}</button></div>`;
339
+ }
340
+ return `<div class="agy-card agy-request${done ? ' agy-request-done' : ''}" role="group" aria-label="${esc(T('elicitTitle'))}" data-elicit-card="${esc(requestId)}">${head}<div class="agy-card-body">${body}</div></div>`;
341
+ }
342
+
343
+ // ── Getting started ──
344
+
345
+ /** The setup card (agy-setup.js setupView). `centered`: it stands alone in an empty transcript. */
346
+ export function setupHtml(view, { T, centered = false } = {}) {
347
+ if (!view?.show) return '';
348
+ const steps = Array.isArray(view.steps) ? `<ol class="agy-steps" aria-label="${esc(T('setupStepsLabel'))}">${view.steps.map((step) => `<li class="agy-step" data-state="${esc(step.state)}"><span class="agy-step-dot" aria-hidden="true">${step.state === 'done' ? ICONS.check : step.state === 'failed' ? ICONS.cross : ''}</span><span class="agy-step-label">${esc(T(`step_${step.id}`))}</span><span class="agy-visually-hidden">${esc(T(`stepState_${step.state}`))}</span></li>`).join('')}</ol>` : '';
349
+ const body = view.text ? esc(view.text) : view.bodyKey ? esc(T(view.bodyKey, view.params || {})) : '';
350
+ const command = view.command ? `<div class="agy-command"><code>${esc(view.command)}</code><button type="button" class="agy-command-copy" data-setup="copy" aria-label="${esc(T('copyCommand'))}">${ICONS.copy}<span>${esc(T('copy'))}</span></button></div>` : '';
351
+ const progress = view.progress ? `<div class="agy-setup-progress"><div class="agy-setup-step-text">${esc(view.progressKey ? T(view.progressKey) : view.progress.text || '')}</div>${typeof view.progress.percent === 'number' ? `<div class="agy-bar" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="${view.progress.percent}"><span style="width:${Math.max(0, Math.min(100, view.progress.percent))}%"></span></div>` : '<div class="agy-bar agy-bar--busy" aria-hidden="true"><span></span></div>'}</div>` : '';
352
+ const error = view.error ? `<div class="agy-setup-error"><code>${esc(view.error)}</code></div>` : '';
353
+ const note = view.note ? `<div class="agy-setup-note">${esc(view.note)}</div>` : '';
354
+ const log = (view.log || []).length ? `<details class="agy-setup-log"${view.stage === 'failed' || view.logOpen ? ' open' : ''}><summary>${esc(T('setupLog'))}</summary><pre aria-label="${esc(T('setupLog'))}">${esc(view.log.join('\n'))}</pre></details>` : '';
355
+ const signIn = Array.isArray(view.signIn) ? view.signIn.map((m, i) => `<button type="button" class="agy-action${i === 0 ? ' agy-btn-primary' : ''}" data-auth="${esc(m.id)}"${m.terminal ? ' data-auth-kind="terminal"' : ''} title="${esc(m.description)}"${view.waiting ? ' disabled' : ''}>${esc(m.name)}</button>`).join('') : '';
356
+ const actions = [
357
+ view.install ? `<button type="button" class="agy-action agy-btn-primary" data-setup="install"${view.install.disabled ? ' disabled' : ''}>${esc(T('setupInstall'))}</button>` : '',
358
+ view.retry ? `<button type="button" class="agy-action agy-btn-primary" data-setup="install">${esc(T('setupRetry'))}</button>` : '',
359
+ signIn,
360
+ view.terminal ? `<button type="button" class="agy-action" data-auth-terminal="1">${esc(T('signInTerminal'))}</button>` : '',
361
+ view.checkSignIn ? `<button type="button" class="agy-action" data-setup="signin-check">${esc(T('signInCheck'))}</button>` : '',
362
+ view.addPath ? `<button type="button" class="agy-action" data-setup="path"${view.addPath.disabled ? ' disabled' : ''}>${esc(view.addPath.label || T('addToPath'))}</button>` : '',
363
+ view.recheck ? `<button type="button" class="agy-action" data-setup="recheck"${view.checking ? ' disabled' : ''}>${esc(T(view.checking ? 'setupChecking' : 'setupRecheck'))}</button>` : '',
364
+ view.dismiss ? `<button type="button" class="agy-action" data-setup="dismiss">${esc(T('setupDismiss'))}</button>` : '',
365
+ ].join('');
366
+ const guide = view.guide ? `<a class="agy-setup-guide" href="${esc(view.guide)}" target="_blank" rel="noopener noreferrer">${esc(T('installGuide'))}</a>` : '';
367
+ return `<section class="agy-setup agy-setup--${esc(view.stage || 'x')}${centered ? ' agy-setup--centered' : ''}" data-tone="${esc(view.tone || '')}" role="${view.live === 'assertive' ? 'alert' : 'status'}" aria-live="${esc(view.live || 'polite')}">`
368
+ + `<span class="agy-setup-mark" aria-hidden="true">${ICONS.mark}</span>`
369
+ + `<h3 class="agy-setup-title">${esc(T(view.titleKey || 'setupMissingTitle'))}</h3>`
370
+ + (body ? `<p class="agy-setup-body">${body}</p>` : '')
371
+ + steps + progress + command + error + note + log
372
+ + (actions ? `<div class="agy-setup-actions">${actions}</div>` : '')
373
+ + guide
374
+ + '</section>';
375
+ }
376
+
377
+ /** The empty transcript: the mark, the name, what to do, and the two keys that help. */
378
+ export function emptyHtml({ T, project = '' } = {}) {
379
+ return `<div class="agy-empty"><span class="agy-empty-mark" aria-hidden="true">${ICONS.mark}</span><div class="agy-empty-name">${esc(T('emptyTitle'))}</div>`
380
+ + `<div class="agy-empty-status">${esc(project ? T('emptyBody') : T('noProject'))}</div>`
381
+ + (project ? `<div class="agy-empty-keys"><span><kbd>/</kbd> ${esc(T('emptyKeyCommands'))}</span><span><kbd>@</kbd> ${esc(T('emptyKeyFiles'))}</span></div>` : '')
382
+ + '</div>';
383
+ }
384
+
385
+ /** The context gauge in words: "50k / 1M · 5%" and the cost when the agent reports one. */
386
+ export function contextWords(gauge, T) {
387
+ if (!gauge) return T('contextUnknown');
388
+ const cost = gauge.cost ? ` · ${gauge.cost.currency} ${Number(gauge.cost.amount).toFixed(2)}` : '';
389
+ return `${gauge.label} · ${gauge.percent}%${cost}`;
390
+ }
391
+
392
+ export { compactNumber };
@@ -0,0 +1,149 @@
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun — Antigravity panel: getting from "not installed" to a session (no DOM)
3
+ // ═══════════════════════════════════════════
4
+ //
5
+ // One card stands where the transcript starts while Antigravity cannot be used
6
+ // yet: the CLI is missing (Install runs Homebrew's cask or Google's installer on
7
+ // the server, lib/cli-install.js), being installed, failed to install, found but
8
+ // failed to start, or installed but signed out. While an install this page
9
+ // started goes on, the card shows four steps — checking, installing, verifying,
10
+ // sign in — and keeps them until the first session is up. What it says comes
11
+ // from the CLI's state (cli-install-controller.js view), the panel's socket
12
+ // (`hello`: whether `agy acp` runs, its sign-in methods) and the tab's sign-in.
13
+
14
+ export const SETUP_STEPS = Object.freeze(['check', 'install', 'verify', 'signin']);
15
+
16
+ // cli-install-view.js INSTALL_STEPS → the card's steps.
17
+ const STEP_OF = Object.freeze({ start: 'check', download: 'install', verify: 'install', run: 'install', check: 'verify', path: 'verify' });
18
+
19
+ function steps(active, { failed = null, done = [] } = {}) {
20
+ const at = SETUP_STEPS.indexOf(active);
21
+ return SETUP_STEPS.map((id, i) => {
22
+ let state = 'todo';
23
+ if (done.includes(id) || (at >= 0 && i < at)) state = 'done';
24
+ if (id === active) state = failed === id ? 'failed' : 'active';
25
+ if (failed === id) state = 'failed';
26
+ return { id, state };
27
+ });
28
+ }
29
+
30
+ /**
31
+ * The card, or { show: false }.
32
+ *
33
+ * @param {object} o
34
+ * @param {object | null} o.cli the controller's view (cliBannerView + checking): kind hidden | missing | installing |
35
+ * failed | broken | done | path, and its command, error, note, log, progress, install, guide, recheck, addPath
36
+ * @param {object | null} o.hello the socket's hello: { running, cliMissing, error, authMethods }
37
+ * @param {object | null} o.auth the active tab's sign-in: { state: 'needed' | 'waiting' | 'failed', error }
38
+ * @param {boolean} o.installed the CLI is known to be installed (tool-versions said so, or the socket runs it)
39
+ * @param {boolean} o.ran an install was started from this page and the card has not been dismissed since
40
+ * @param {boolean} o.connecting the panel is (re)starting `agy acp` after an install
41
+ * @returns {{ show: boolean, stage?: string, tone?: string, steps?: object[] | null, titleKey?: string,
42
+ * bodyKey?: string, params?: object, command?: string, error?: string, note?: string, log?: string[],
43
+ * progress?: object | null, install?: object | null, retry?: boolean, recheck?: boolean, checking?: boolean,
44
+ * guide?: string | null, signIn?: object[] | null, terminal?: boolean, addPath?: object | null, dismiss?: boolean,
45
+ * live?: 'polite' | 'assertive' }}
46
+ */
47
+ export function setupView({ cli = null, hello = null, auth = null, installed = false, ran = false, connecting = false } = {}) {
48
+ const kind = cli?.kind || 'hidden';
49
+ const base = {
50
+ show: true, tone: 'warn', steps: null, params: {}, command: '', error: '', note: '', log: [], progress: null,
51
+ install: null, retry: false, recheck: false, checking: !!cli?.checking, guide: cli?.guide || null,
52
+ signIn: null, terminal: false, addPath: null, dismiss: false, live: 'polite',
53
+ };
54
+
55
+ if (kind === 'installing') {
56
+ const job = cli.job || null;
57
+ const active = STEP_OF[job?.step] || 'check';
58
+ // Through Homebrew the steps are Homebrew's (cli-install-view.js words them for an installer script).
59
+ const brew = job?.method === 'brew' || cli.info?.install?.method === 'brew';
60
+ return {
61
+ ...base, stage: 'installing', tone: 'busy', titleKey: 'setupInstallingTitle', bodyKey: `setupStep_${active}`,
62
+ steps: steps(active), progress: cli.progress || null, progressKey: brew && active === 'install' ? 'setupBrewRunning' : null,
63
+ log: cli.log || [], guide: null,
64
+ };
65
+ }
66
+
67
+ if (kind === 'failed') {
68
+ const job = cli.job || null;
69
+ const failedStep = STEP_OF[job?.step] || 'install';
70
+ return {
71
+ ...base, stage: 'failed', tone: 'err', live: 'assertive', titleKey: 'setupFailedTitle', bodyKey: null,
72
+ text: cli.text || '', error: cli.error || '', note: cli.note || '', command: cli.command || '', log: cli.log || [],
73
+ steps: steps(failedStep, { failed: failedStep }), retry: !!cli.install, recheck: true,
74
+ };
75
+ }
76
+
77
+ if (kind === 'missing') {
78
+ // The server says how one click installs it here: Homebrew's cask, or Google's installer script.
79
+ const method = cli.info?.install?.method;
80
+ const body = cli.install ? (method === 'brew' ? 'setupMissingBodyBrew' : 'setupMissingBody') : (cli.command ? 'setupMissingManual' : 'setupMissingGuide');
81
+ return {
82
+ ...base, stage: 'missing', titleKey: 'setupMissingTitle', bodyKey: body,
83
+ command: cli.command || '', install: cli.install ? { disabled: !!cli.install.disabled } : null, recheck: true,
84
+ };
85
+ }
86
+
87
+ if (kind === 'broken') {
88
+ return {
89
+ ...base, stage: 'broken', tone: 'err', live: 'assertive', titleKey: 'setupBrokenTitle', bodyKey: null,
90
+ text: cli.text || '', error: cli.error || '', note: cli.note || '', recheck: true,
91
+ };
92
+ }
93
+
94
+ // Installed (or the socket runs it). After an install from here, the card stays through sign-in.
95
+ const runningOk = hello?.running === true;
96
+ const methods = Array.isArray(hello?.authMethods) ? hello.authMethods.filter((m) => m && typeof m.id === 'string') : [];
97
+ const pathNotice = kind === 'path' || kind === 'done' ? { addPath: cli.addPath || null, note: cli.text || '', dismiss: !!cli.dismiss } : null;
98
+
99
+ if (auth && (auth.state === 'needed' || auth.state === 'waiting' || auth.state === 'failed')) {
100
+ return {
101
+ ...base, stage: 'signin', tone: auth.state === 'failed' ? 'err' : 'warn', titleKey: 'signInTitle',
102
+ bodyKey: auth.state === 'failed' ? 'signInFailed' : auth.state === 'waiting' ? 'signInWaiting' : auth.terminal ? 'signInTerminalBody' : 'signInBody',
103
+ params: auth.state === 'failed' ? { error: String(auth.error || '') } : {},
104
+ checkSignIn: !!auth.terminal,
105
+ steps: ran ? steps('signin', { failed: auth.state === 'failed' ? 'signin' : null }) : null,
106
+ signIn: methods.map((m) => ({ id: m.id, name: String(m.name || m.id), description: String(m.description || ''), terminal: m.type === 'terminal' })),
107
+ terminal: true, waiting: auth.state === 'waiting', guide: null,
108
+ ...(pathNotice || {}),
109
+ };
110
+ }
111
+
112
+ // The CLI is there but `agy acp` did not start: what it said stays on the card (the server already redacted
113
+ // it), and an agy without the ACP server (1.4.6) is told to update rather than shown as a crash.
114
+ if (!connecting && hello?.running === false && !hello.cliMissing && hello.error?.message) {
115
+ const update = !!hello.acpUnsupported;
116
+ return {
117
+ ...base, stage: 'broken', tone: 'err', live: 'assertive', titleKey: update ? 'setupUpdateTitle' : 'setupBrokenTitle',
118
+ bodyKey: update ? 'setupUpdateBody' : null, command: update ? 'agy update' : '',
119
+ error: update ? String(hello.error.stderr || '') : String(hello.error.message), recheck: true, guide: null,
120
+ };
121
+ }
122
+
123
+ if (ran) {
124
+ if (connecting || (!runningOk && (installed || kind === 'done' || kind === 'path'))) {
125
+ return { ...base, stage: 'connecting', tone: 'busy', titleKey: 'setupConnectingTitle', bodyKey: 'setupConnectingBody', steps: steps('signin', { done: ['check', 'install', 'verify'] }), guide: null, ...(pathNotice || {}) };
126
+ }
127
+ if (runningOk) {
128
+ return { ...base, stage: 'ready', tone: 'ok', titleKey: 'setupReadyTitle', bodyKey: 'setupReadyBody', steps: steps(null, { done: SETUP_STEPS }), dismiss: true, guide: null, ...(pathNotice ? { addPath: pathNotice.addPath, note: pathNotice.note } : {}) };
129
+ }
130
+ }
131
+
132
+ // A PATH note after an install (the CLI works; other terminals do not see it yet).
133
+ if (kind === 'path' || kind === 'done') {
134
+ return { ...base, stage: 'path', tone: kind === 'done' ? 'ok' : 'warn', titleKey: kind === 'done' ? 'setupReadyTitle' : 'setupPathTitle', bodyKey: null, text: cli.text || '', error: cli.error || '', addPath: cli.addPath || null, recheck: !!cli.recheck, dismiss: !!cli.dismiss, guide: null, compact: true };
135
+ }
136
+
137
+ return { show: false };
138
+ }
139
+
140
+ /** The tray/composer word for what blocks sending, from the card: a copy key, or '' when nothing does. */
141
+ export function blockedReasonKey(view) {
142
+ if (!view?.show) return '';
143
+ if (view.stage === 'installing') return 'blockedInstalling';
144
+ if (view.stage === 'missing' || view.stage === 'failed') return 'blockedMissing';
145
+ if (view.stage === 'broken') return 'blockedBroken';
146
+ if (view.stage === 'signin') return 'blockedSignIn';
147
+ if (view.stage === 'connecting') return 'blockedConnecting';
148
+ return '';
149
+ }