synabun 2.0.10 → 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 (656) hide show
  1. package/CHANGELOG.md +207 -0
  2. package/README.md +25 -2
  3. package/THIRD-PARTY-LICENSES.md +22 -0
  4. package/bin/synabun.js +71 -24
  5. package/docs/whatsapp.md +13 -13
  6. package/hooks/claude-code/pre-websearch.mjs +12 -1
  7. package/hooks/claude-code/shared.mjs +5 -0
  8. package/hooks/claude-code/stop.mjs +1 -1
  9. package/lib/claude-hooks.js +172 -18
  10. package/lib/client-config-repair.js +30 -17
  11. package/lib/desktop-app.js +7 -1
  12. package/lib/external-tools.js +7 -6
  13. package/lib/first-launch-protection.js +57 -12
  14. package/lib/mcp-dist.js +176 -0
  15. package/lib/release-npm-ci.mjs +138 -0
  16. package/lib/release-publish.mjs +91 -0
  17. package/lib/release-scan.mjs +50 -0
  18. package/lib/server-crash-record.js +123 -0
  19. package/lib/server-exit.js +318 -0
  20. package/lib/server-supervisor.js +508 -0
  21. package/lib/start-launcher.js +37 -4
  22. package/lib/tools-cli.js +45 -0
  23. package/lib/update-snapshot.js +302 -0
  24. package/lib/virustotal.mjs +203 -0
  25. package/lib/windows-crash-event.js +269 -0
  26. package/mcp-server/dist/http.d.ts +20 -1
  27. package/mcp-server/dist/http.d.ts.map +1 -1
  28. package/mcp-server/dist/http.js +59 -12
  29. package/mcp-server/dist/http.js.map +1 -1
  30. package/mcp-server/dist/index.d.ts +33 -27
  31. package/mcp-server/dist/index.d.ts.map +1 -1
  32. package/mcp-server/dist/index.js +128 -143
  33. package/mcp-server/dist/index.js.map +1 -1
  34. package/mcp-server/dist/preload.js +1 -1
  35. package/mcp-server/dist/preload.js.map +1 -1
  36. package/mcp-server/dist/services/budget-judgments.d.ts +1 -1
  37. package/mcp-server/dist/services/budget-judgments.d.ts.map +1 -1
  38. package/mcp-server/dist/services/budget-judgments.js +5 -3
  39. package/mcp-server/dist/services/budget-judgments.js.map +1 -1
  40. package/mcp-server/dist/services/codex-config-heal.d.ts +6 -2
  41. package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
  42. package/mcp-server/dist/services/codex-config-heal.js +81 -28
  43. package/mcp-server/dist/services/codex-config-heal.js.map +1 -1
  44. package/mcp-server/dist/services/identity.d.ts +2 -2
  45. package/mcp-server/dist/services/identity.js +2 -2
  46. package/mcp-server/dist/services/memory-judge-backfill.d.ts +6 -0
  47. package/mcp-server/dist/services/memory-judge-backfill.d.ts.map +1 -1
  48. package/mcp-server/dist/services/memory-judge-backfill.js +7 -1
  49. package/mcp-server/dist/services/memory-judge-backfill.js.map +1 -1
  50. package/mcp-server/dist/services/memory-sessions.d.ts +1 -1
  51. package/mcp-server/dist/services/memory-sessions.d.ts.map +1 -1
  52. package/mcp-server/dist/services/memory-sessions.js +28 -2
  53. package/mcp-server/dist/services/memory-sessions.js.map +1 -1
  54. package/mcp-server/dist/services/neural-interface.d.ts +18 -11
  55. package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
  56. package/mcp-server/dist/services/neural-interface.js +67 -11
  57. package/mcp-server/dist/services/neural-interface.js.map +1 -1
  58. package/mcp-server/dist/services/server-instructions.d.ts +7 -2
  59. package/mcp-server/dist/services/server-instructions.d.ts.map +1 -1
  60. package/mcp-server/dist/services/server-instructions.js +22 -63
  61. package/mcp-server/dist/services/server-instructions.js.map +1 -1
  62. package/mcp-server/dist/services/tool-catalog.d.ts +120 -0
  63. package/mcp-server/dist/services/tool-catalog.d.ts.map +1 -0
  64. package/mcp-server/dist/services/tool-catalog.js +339 -0
  65. package/mcp-server/dist/services/tool-catalog.js.map +1 -0
  66. package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts +12 -0
  67. package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts.map +1 -0
  68. package/mcp-server/dist/services/tool-list-refresh-barrier.js +40 -0
  69. package/mcp-server/dist/services/tool-list-refresh-barrier.js.map +1 -0
  70. package/mcp-server/dist/services/tool-list.d.ts +108 -0
  71. package/mcp-server/dist/services/tool-list.d.ts.map +1 -0
  72. package/mcp-server/dist/services/tool-list.js +334 -0
  73. package/mcp-server/dist/services/tool-list.js.map +1 -0
  74. package/mcp-server/dist/services/tool-runtime.d.ts +161 -0
  75. package/mcp-server/dist/services/tool-runtime.d.ts.map +1 -0
  76. package/mcp-server/dist/services/tool-runtime.js +400 -0
  77. package/mcp-server/dist/services/tool-runtime.js.map +1 -0
  78. package/mcp-server/dist/services/typesafe-config.js +1 -1
  79. package/mcp-server/dist/services/typesafe-config.js.map +1 -1
  80. package/mcp-server/dist/services/whiteboard-geometry.d.ts +26 -2
  81. package/mcp-server/dist/services/whiteboard-geometry.d.ts.map +1 -1
  82. package/mcp-server/dist/services/whiteboard-geometry.js +91 -8
  83. package/mcp-server/dist/services/whiteboard-geometry.js.map +1 -1
  84. package/mcp-server/dist/tools/agents.d.ts +14 -14
  85. package/mcp-server/dist/tools/agents.d.ts.map +1 -1
  86. package/mcp-server/dist/tools/agents.js +29 -19
  87. package/mcp-server/dist/tools/agents.js.map +1 -1
  88. package/mcp-server/dist/tools/browser-batch.d.ts +3 -3
  89. package/mcp-server/dist/tools/choice.d.ts +6 -0
  90. package/mcp-server/dist/tools/choice.d.ts.map +1 -1
  91. package/mcp-server/dist/tools/choice.js +18 -13
  92. package/mcp-server/dist/tools/choice.js.map +1 -1
  93. package/mcp-server/dist/tools/git-tools.js +2 -2
  94. package/mcp-server/dist/tools/git-tools.js.map +1 -1
  95. package/mcp-server/dist/tools/image-tools.d.ts +1 -1
  96. package/mcp-server/dist/tools/image-tools.js +1 -1
  97. package/mcp-server/dist/tools/morelogin.d.ts.map +1 -1
  98. package/mcp-server/dist/tools/morelogin.js +9 -6
  99. package/mcp-server/dist/tools/morelogin.js.map +1 -1
  100. package/mcp-server/dist/tools/skill-tools.d.ts +505 -0
  101. package/mcp-server/dist/tools/skill-tools.d.ts.map +1 -0
  102. package/mcp-server/dist/tools/skill-tools.js +642 -0
  103. package/mcp-server/dist/tools/skill-tools.js.map +1 -0
  104. package/mcp-server/dist/tools/skill.d.ts +7 -0
  105. package/mcp-server/dist/tools/skill.d.ts.map +1 -0
  106. package/mcp-server/dist/tools/skill.js +11 -0
  107. package/mcp-server/dist/tools/skill.js.map +1 -0
  108. package/mcp-server/dist/tools/tool-groups.d.ts +68 -0
  109. package/mcp-server/dist/tools/tool-groups.d.ts.map +1 -0
  110. package/mcp-server/dist/tools/tool-groups.js +147 -0
  111. package/mcp-server/dist/tools/tool-groups.js.map +1 -0
  112. package/mcp-server/dist/tools/whiteboard-format.d.ts.map +1 -1
  113. package/mcp-server/dist/tools/whiteboard-format.js +7 -4
  114. package/mcp-server/dist/tools/whiteboard-format.js.map +1 -1
  115. package/mcp-server/dist/tools/whiteboard-tools.d.ts +64 -13
  116. package/mcp-server/dist/tools/whiteboard-tools.d.ts.map +1 -1
  117. package/mcp-server/dist/tools/whiteboard-tools.js +30 -5
  118. package/mcp-server/dist/tools/whiteboard-tools.js.map +1 -1
  119. package/mcp-server/package-lock.json +2 -2
  120. package/mcp-server/package.json +1 -1
  121. package/mcp-server/src/http.ts +75 -12
  122. package/mcp-server/src/index.ts +147 -154
  123. package/mcp-server/src/preload.ts +12 -12
  124. package/mcp-server/src/services/budget-judgments.ts +5 -3
  125. package/mcp-server/src/services/codex-config-heal.ts +80 -29
  126. package/mcp-server/src/services/identity.ts +2 -2
  127. package/mcp-server/src/services/memory-judge-backfill.ts +7 -1
  128. package/mcp-server/src/services/memory-sessions.ts +24 -3
  129. package/mcp-server/src/services/neural-interface.ts +81 -21
  130. package/mcp-server/src/services/server-instructions.ts +29 -64
  131. package/mcp-server/src/services/tool-catalog.ts +403 -0
  132. package/mcp-server/src/services/tool-list-refresh-barrier.ts +33 -0
  133. package/mcp-server/src/services/tool-list.ts +410 -0
  134. package/mcp-server/src/services/tool-runtime.ts +457 -0
  135. package/mcp-server/src/services/typesafe-config.ts +1 -1
  136. package/mcp-server/src/services/whiteboard-geometry.ts +94 -9
  137. package/mcp-server/src/tools/agents.ts +29 -20
  138. package/mcp-server/src/tools/choice.ts +17 -12
  139. package/mcp-server/src/tools/git-tools.ts +2 -2
  140. package/mcp-server/src/tools/image-tools.ts +1 -1
  141. package/mcp-server/src/tools/morelogin.ts +6 -4
  142. package/mcp-server/src/tools/skill-tools.ts +592 -0
  143. package/mcp-server/src/tools/skill.ts +13 -0
  144. package/mcp-server/src/tools/tool-groups.ts +164 -0
  145. package/mcp-server/src/tools/whiteboard-format.ts +9 -3
  146. package/mcp-server/src/tools/whiteboard-tools.ts +39 -5
  147. package/neural-interface/i18n/en.json +3953 -228
  148. package/neural-interface/i18n/pt-BR.json +3901 -179
  149. package/neural-interface/lib/agent-sandbox-elevation.js +6 -0
  150. package/neural-interface/lib/antigravity/acp-client-host.js +554 -0
  151. package/neural-interface/lib/antigravity/acp-connection.js +299 -0
  152. package/neural-interface/lib/antigravity/bypass.js +100 -0
  153. package/neural-interface/lib/antigravity/catalog.js +273 -0
  154. package/neural-interface/lib/antigravity/home.js +260 -0
  155. package/neural-interface/lib/antigravity/migrate-gemini.js +139 -0
  156. package/neural-interface/lib/antigravity/panel-bridge.js +1058 -0
  157. package/neural-interface/lib/antigravity/policy-hook.mjs +78 -0
  158. package/neural-interface/lib/antigravity/policy.js +74 -0
  159. package/neural-interface/lib/antigravity/tool-call.js +178 -0
  160. package/neural-interface/lib/antigravity/transcripts.js +252 -0
  161. package/neural-interface/lib/antigravity/usage.js +43 -0
  162. package/neural-interface/lib/assistant-api.js +128 -28
  163. package/neural-interface/lib/assistant-brains/antigravity.js +423 -0
  164. package/neural-interface/lib/assistant-brains/claude.js +4 -3
  165. package/neural-interface/lib/assistant-brains/codex-browser-policy-hook.mjs +12 -2
  166. package/neural-interface/lib/assistant-brains/codex-route-gate-hook.mjs +32 -9
  167. package/neural-interface/lib/assistant-brains/codex.js +9 -5
  168. package/neural-interface/lib/assistant-brains/opencode.js +120 -54
  169. package/neural-interface/lib/assistant-brains/opencode2-browser-policy/index.js +28 -13
  170. package/neural-interface/lib/assistant-brains/opencode2-route-gate/index.js +59 -19
  171. package/neural-interface/lib/assistant-budget-planner.js +7 -3
  172. package/neural-interface/lib/assistant-budget-split.js +42 -15
  173. package/neural-interface/lib/assistant-catalog.js +81 -15
  174. package/neural-interface/lib/assistant-config.js +6 -3
  175. package/neural-interface/lib/assistant-dispatch.js +1073 -200
  176. package/neural-interface/lib/assistant-envelope.js +156 -2
  177. package/neural-interface/lib/assistant-evidence.js +80 -0
  178. package/neural-interface/lib/assistant-hidden-models.js +7 -5
  179. package/neural-interface/lib/assistant-persona.js +93 -19
  180. package/neural-interface/lib/assistant-plan-permissions.js +25 -9
  181. package/neural-interface/lib/assistant-route-gate-codex.js +3 -0
  182. package/neural-interface/lib/assistant-route-gate.js +6 -4
  183. package/neural-interface/lib/assistant-router.js +245 -38
  184. package/neural-interface/lib/assistant-runtime.js +214 -30
  185. package/neural-interface/lib/assistant-task-prompt.js +87 -21
  186. package/neural-interface/lib/assistant-tools.js +51 -0
  187. package/neural-interface/lib/assistant-usage.js +29 -0
  188. package/neural-interface/lib/backup-service.js +626 -4
  189. package/neural-interface/lib/backup-zip-reader.js +10 -5
  190. package/neural-interface/lib/backup-zip-writer.js +257 -0
  191. package/neural-interface/lib/browser-execution.js +10 -2
  192. package/neural-interface/lib/browser-guard-cdp.js +84 -0
  193. package/neural-interface/lib/browser-network-guard.js +476 -0
  194. package/neural-interface/lib/browser-policy-roots.js +64 -0
  195. package/neural-interface/lib/browser-tool-policy.js +198 -38
  196. package/neural-interface/lib/claude-agent-bridge.js +11 -2
  197. package/neural-interface/lib/claude-history-cache.js +210 -0
  198. package/neural-interface/lib/claude-hooks-status.js +103 -0
  199. package/neural-interface/lib/claude-model-catalog.js +5 -1
  200. package/neural-interface/lib/claude-temporary.js +5 -11
  201. package/neural-interface/lib/cli-install-api.js +1 -1
  202. package/neural-interface/lib/cli-install.js +163 -154
  203. package/neural-interface/lib/cli-resolver.js +13 -3
  204. package/neural-interface/lib/cli-update-planner.js +52 -41
  205. package/neural-interface/lib/cloudflared/tunnel.js +42 -2
  206. package/neural-interface/lib/codex-mcp-approvals.js +11 -0
  207. package/neural-interface/lib/codex-rollout-reader.js +52 -0
  208. package/neural-interface/lib/codex-worker-app-server.js +191 -0
  209. package/neural-interface/lib/confined-fs.js +30 -3
  210. package/neural-interface/lib/crash-notice-api.js +98 -0
  211. package/neural-interface/lib/editor-completion-opencode.js +68 -68
  212. package/neural-interface/lib/editor-git-api.js +3 -20
  213. package/neural-interface/lib/exec-loop-command.js +13 -10
  214. package/neural-interface/lib/file-explorer-api.js +74 -40
  215. package/neural-interface/lib/file-explorer-watch.js +9 -3
  216. package/neural-interface/lib/git-bash/install.js +52 -6
  217. package/neural-interface/lib/git-bash/manager.js +59 -17
  218. package/neural-interface/lib/git-bash/pins.js +12 -0
  219. package/neural-interface/lib/git-read.js +2 -2
  220. package/neural-interface/lib/git-status-runner.js +390 -0
  221. package/neural-interface/lib/git-status-watchdog.js +40 -0
  222. package/neural-interface/lib/greeting/budgets.js +1 -0
  223. package/neural-interface/lib/greeting/engine.js +2 -2
  224. package/neural-interface/lib/greeting/sources.js +10 -12
  225. package/neural-interface/lib/greeting/writer.js +1 -1
  226. package/neural-interface/lib/http-guards.js +8 -2
  227. package/neural-interface/lib/install-details.js +4 -1
  228. package/neural-interface/lib/mcp-agent-servers.js +67 -0
  229. package/neural-interface/lib/mcp-client-config.js +35 -33
  230. package/neural-interface/lib/mcp-external-servers.js +332 -0
  231. package/neural-interface/lib/mcp-installer.js +2 -1
  232. package/neural-interface/lib/{mcp-profile-refresh-coordinator.js → mcp-tools-refresh-coordinator.js} +4 -4
  233. package/neural-interface/lib/memory-session-capture.js +17 -3
  234. package/neural-interface/lib/morelogin.js +177 -25
  235. package/neural-interface/lib/native-loop-providers.js +965 -66
  236. package/neural-interface/lib/native-loop-runtime.js +290 -42
  237. package/neural-interface/lib/opencode-config.js +557 -0
  238. package/neural-interface/lib/opencode-db-schema.js +17 -34
  239. package/neural-interface/lib/opencode-history.js +48 -218
  240. package/neural-interface/lib/opencode-mcp-ready.js +108 -0
  241. package/neural-interface/lib/opencode-native-boundary.js +53 -0
  242. package/neural-interface/lib/opencode-protocol.js +249 -112
  243. package/neural-interface/lib/opencode-runtime-config.js +32 -22
  244. package/neural-interface/lib/opencode-serve-records.js +39 -21
  245. package/neural-interface/lib/opencode-v2-client.js +48 -153
  246. package/neural-interface/lib/opencode-v2-ws-requests.js +36 -210
  247. package/neural-interface/lib/opencode-worker-data.js +27 -0
  248. package/neural-interface/lib/opencode2-client.js +73 -37
  249. package/neural-interface/lib/opencode2-proxy.js +23 -63
  250. package/neural-interface/lib/opencode2-translate.js +51 -2
  251. package/neural-interface/lib/owner-authority.js +249 -0
  252. package/neural-interface/lib/page-session.js +275 -0
  253. package/neural-interface/lib/path-confine.js +12 -9
  254. package/neural-interface/lib/peer-identity.js +793 -0
  255. package/neural-interface/lib/pty-host/core.js +28 -1
  256. package/neural-interface/lib/pty-host/lifetime.js +12 -0
  257. package/neural-interface/lib/pty-host/manager.js +12 -1
  258. package/neural-interface/lib/remote-policy.js +1 -1
  259. package/neural-interface/lib/request-gate.js +20 -2
  260. package/neural-interface/lib/rulesets/api.js +7 -6
  261. package/neural-interface/lib/rulesets/fs-safe.js +1 -1
  262. package/neural-interface/lib/rulesets/installer.js +180 -36
  263. package/neural-interface/lib/rulesets/json-edit.js +1 -1
  264. package/neural-interface/lib/rulesets/render.js +1 -1
  265. package/neural-interface/lib/session-cache-builder.js +81 -31
  266. package/neural-interface/lib/session-title-generator.js +58 -36
  267. package/neural-interface/lib/skills/api-v2.js +495 -0
  268. package/neural-interface/lib/skills/bundle.js +418 -0
  269. package/neural-interface/lib/skills/catalog.js +298 -0
  270. package/neural-interface/lib/skills/creation-api.js +191 -0
  271. package/neural-interface/lib/skills/creation-brief.js +247 -0
  272. package/neural-interface/lib/skills/creation-sources.js +285 -0
  273. package/neural-interface/lib/skills/creation.js +843 -0
  274. package/neural-interface/lib/skills/deploy.js +569 -0
  275. package/neural-interface/lib/skills/digest.js +119 -0
  276. package/neural-interface/lib/skills/discover.js +111 -0
  277. package/neural-interface/lib/skills/draft-confinement.js +216 -0
  278. package/neural-interface/lib/skills/eval-confinement.js +399 -0
  279. package/neural-interface/lib/skills/eval-sandbox.js +280 -0
  280. package/neural-interface/lib/skills/eval-trace.js +135 -0
  281. package/neural-interface/lib/skills/evals-api.js +212 -0
  282. package/neural-interface/lib/skills/evals.js +1573 -0
  283. package/neural-interface/lib/skills/export.js +203 -0
  284. package/neural-interface/lib/skills/frontmatter.js +64 -0
  285. package/neural-interface/lib/skills/health.js +75 -0
  286. package/neural-interface/lib/skills/json-edit.js +165 -0
  287. package/neural-interface/lib/skills/known-bridge-digests.json +441 -0
  288. package/neural-interface/lib/skills/known-bridges.js +96 -0
  289. package/neural-interface/lib/skills/library.js +823 -0
  290. package/neural-interface/lib/skills/local-store.js +21 -0
  291. package/neural-interface/lib/skills/migration.js +433 -0
  292. package/neural-interface/lib/skills/operations.js +45 -0
  293. package/neural-interface/lib/skills/ownership.js +427 -0
  294. package/neural-interface/lib/skills/packages.js +88 -0
  295. package/neural-interface/lib/skills/reviews.js +212 -0
  296. package/neural-interface/lib/skills/revision.js +80 -0
  297. package/neural-interface/lib/skills/roots.js +269 -0
  298. package/neural-interface/lib/skills/runtime-checks.js +358 -0
  299. package/neural-interface/lib/skills/scan-assets.js +252 -0
  300. package/neural-interface/lib/skills/scan-context.js +26 -0
  301. package/neural-interface/lib/skills/scan-shell.js +324 -0
  302. package/neural-interface/lib/skills/scan.js +364 -0
  303. package/neural-interface/lib/skills/scanners.js +54 -0
  304. package/neural-interface/lib/skills/service.js +530 -0
  305. package/neural-interface/lib/skills/telemetry.js +119 -0
  306. package/neural-interface/lib/sqlite-integrity-worker.js +22 -0
  307. package/neural-interface/lib/synabun-version.js +1 -1
  308. package/neural-interface/lib/system-activity.js +1 -1
  309. package/neural-interface/lib/tool-lists.js +124 -0
  310. package/neural-interface/lib/whatsapp/brain.js +7 -3
  311. package/neural-interface/lib/whatsapp/bridge.js +6 -3
  312. package/neural-interface/lib/whatsapp/cards.js +22 -9
  313. package/neural-interface/lib/whatsapp/config.js +1 -1
  314. package/neural-interface/lib/worker-confinement.js +1268 -0
  315. package/neural-interface/lib/worker-guard-setting.js +56 -0
  316. package/neural-interface/lib/worker-mcp-channel.js +175 -0
  317. package/neural-interface/lib/worker-question.js +74 -0
  318. package/neural-interface/lib/worker-relay-ports.js +227 -0
  319. package/neural-interface/lib/worker-resume.js +238 -0
  320. package/neural-interface/lib/worker-sandbox.js +329 -0
  321. package/neural-interface/package-lock.json +34 -12
  322. package/neural-interface/package.json +3 -2
  323. package/neural-interface/public/index.html +3 -1
  324. package/neural-interface/public/index2d.html +19 -11
  325. package/neural-interface/public/onboarding.html +3 -3
  326. package/neural-interface/public/shared/account-menu.js +8 -5
  327. package/neural-interface/public/shared/agent-runtime-options.js +125 -31
  328. package/neural-interface/public/shared/agy/agy-bypass.js +47 -0
  329. package/neural-interface/public/shared/agy/agy-client.js +266 -0
  330. package/neural-interface/public/shared/agy/agy-commands.js +120 -0
  331. package/neural-interface/public/shared/agy/agy-copy.js +326 -0
  332. package/neural-interface/public/shared/agy/agy-diff.js +161 -0
  333. package/neural-interface/public/shared/agy/agy-events.js +469 -0
  334. package/neural-interface/public/shared/agy/agy-greeting.js +26 -0
  335. package/neural-interface/public/shared/agy/agy-options.js +232 -0
  336. package/neural-interface/public/shared/agy/agy-panel.js +2543 -0
  337. package/neural-interface/public/shared/agy/agy-render.js +392 -0
  338. package/neural-interface/public/shared/agy/agy-setup.js +149 -0
  339. package/neural-interface/public/shared/agy/agy-styles.js +604 -0
  340. package/neural-interface/public/shared/agy/agy-tabs-logic.js +181 -0
  341. package/neural-interface/public/shared/api.js +1527 -1336
  342. package/neural-interface/public/shared/app-frame.js +192 -0
  343. package/neural-interface/public/shared/assistant/asst-agents-dock.js +36 -6
  344. package/neural-interface/public/shared/assistant/asst-api.js +1 -1
  345. package/neural-interface/public/shared/assistant/asst-ask-styles.js +270 -0
  346. package/neural-interface/public/shared/assistant/asst-ask-tail.js +118 -0
  347. package/neural-interface/public/shared/assistant/asst-ask.js +397 -0
  348. package/neural-interface/public/shared/assistant/asst-brain-picker.js +105 -44
  349. package/neural-interface/public/shared/assistant/asst-budget.js +5 -4
  350. package/neural-interface/public/shared/assistant/asst-control.js +205 -26
  351. package/neural-interface/public/shared/assistant/asst-followups-card.js +383 -0
  352. package/neural-interface/public/shared/assistant/asst-followups.js +417 -0
  353. package/neural-interface/public/shared/assistant/asst-hosts.js +16 -7
  354. package/neural-interface/public/shared/assistant/asst-limit-dock.js +45 -0
  355. package/neural-interface/public/shared/assistant/asst-limit-wait.js +327 -0
  356. package/neural-interface/public/shared/assistant/asst-limit.js +168 -0
  357. package/neural-interface/public/shared/assistant/asst-mascot.js +49 -12
  358. package/neural-interface/public/shared/assistant/asst-menu.js +7 -6
  359. package/neural-interface/public/shared/assistant/asst-panel.js +151 -19
  360. package/neural-interface/public/shared/assistant/asst-rack-styles.js +66 -49
  361. package/neural-interface/public/shared/assistant/asst-render.js +445 -82
  362. package/neural-interface/public/shared/assistant/asst-route.js +304 -53
  363. package/neural-interface/public/shared/assistant/asst-routes-editor.js +9 -8
  364. package/neural-interface/public/shared/assistant/asst-scenes.js +56 -1
  365. package/neural-interface/public/shared/assistant/asst-sidepanel.js +117 -10
  366. package/neural-interface/public/shared/assistant/asst-slash.js +17 -9
  367. package/neural-interface/public/shared/assistant/asst-state.js +87 -24
  368. package/neural-interface/public/shared/assistant/asst-stop.js +127 -0
  369. package/neural-interface/public/shared/assistant/asst-styles.js +93 -21
  370. package/neural-interface/public/shared/assistant/asst-tool-kinds.js +190 -23
  371. package/neural-interface/public/shared/assistant/asst-usage.js +2 -1
  372. package/neural-interface/public/shared/automation-context.js +5 -2
  373. package/neural-interface/public/shared/cc-dropdown.js +89 -19
  374. package/neural-interface/public/shared/cdx/cdx-context-menu.js +10 -9
  375. package/neural-interface/public/shared/cdx/cdx-copy.js +16 -0
  376. package/neural-interface/public/shared/cdx/cdx-output-model.js +22 -1
  377. package/neural-interface/public/shared/cdx/cdx-output.js +34 -10
  378. package/neural-interface/public/shared/cdx/cdx-panel.js +59 -151
  379. package/neural-interface/public/shared/cdx/cdx-preview.js +12 -0
  380. package/neural-interface/public/shared/cdx/cdx-protocol.js +39 -11
  381. package/neural-interface/public/shared/cdx/cdx-render.js +57 -38
  382. package/neural-interface/public/shared/cdx/cdx-requests.js +14 -4
  383. package/neural-interface/public/shared/cdx/cdx-skill-hints.js +54 -0
  384. package/neural-interface/public/shared/cdx/cdx-styles.js +15 -3
  385. package/neural-interface/public/shared/cdx/cdx-tabs.js +127 -44
  386. package/neural-interface/public/shared/cdx/cdx-ws.js +0 -1
  387. package/neural-interface/public/shared/cli-install-controller.js +218 -0
  388. package/neural-interface/public/shared/cli-install-view.js +11 -12
  389. package/neural-interface/public/shared/cli-status.js +11 -10
  390. package/neural-interface/public/shared/constants.js +5 -4
  391. package/neural-interface/public/shared/context-menu-areas.js +34 -0
  392. package/neural-interface/public/shared/context-menu-logic.js +243 -0
  393. package/neural-interface/public/shared/context-menu-model.js +367 -0
  394. package/neural-interface/public/shared/context-menu.js +372 -0
  395. package/neural-interface/public/shared/cp/cp-context-menu.js +10 -9
  396. package/neural-interface/public/shared/cp/cp-restore.js +162 -0
  397. package/neural-interface/public/shared/cp/cp-tool-cards.js +10 -3
  398. package/neural-interface/public/shared/crash-notice-view.js +168 -0
  399. package/neural-interface/public/shared/desktop-bridge.js +17 -0
  400. package/neural-interface/public/shared/desktop-titlebar-view.js +12 -0
  401. package/neural-interface/public/shared/desktop-titlebar.js +13 -4
  402. package/neural-interface/public/shared/desktop-update-view.js +21 -0
  403. package/neural-interface/public/shared/editor/editor-panel-logic.js +2 -2
  404. package/neural-interface/public/shared/explorer/fx-api.js +4 -4
  405. package/neural-interface/public/shared/explorer/fx-links.js +3 -3
  406. package/neural-interface/public/shared/explorer/fx-logic.js +1 -1
  407. package/neural-interface/public/shared/explorer/fx-main.js +48 -16
  408. package/neural-interface/public/shared/explorer/fx-tree.js +1 -0
  409. package/neural-interface/public/shared/greeting/greeting-card.js +2 -2
  410. package/neural-interface/public/shared/greeting/greeting-view.js +1 -0
  411. package/neural-interface/public/shared/html-shell.js +30 -15
  412. package/neural-interface/public/shared/i18n.js +2 -0
  413. package/neural-interface/public/shared/ocp-v2/ocp-v2-approvals.js +7 -7
  414. package/neural-interface/public/shared/ocp-v2/ocp-v2-binding.js +4 -4
  415. package/neural-interface/public/shared/ocp-v2/ocp-v2-changes-logic.js +3 -101
  416. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +2 -0
  417. package/neural-interface/public/shared/ocp-v2/ocp-v2-compact-button.js +1 -3
  418. package/neural-interface/public/shared/ocp-v2/ocp-v2-composer-logic.js +2 -6
  419. package/neural-interface/public/shared/ocp-v2/ocp-v2-context-menu.js +11 -11
  420. package/neural-interface/public/shared/ocp-v2/ocp-v2-context-model.js +0 -3
  421. package/neural-interface/public/shared/ocp-v2/ocp-v2-events.js +8 -4
  422. package/neural-interface/public/shared/ocp-v2/ocp-v2-manager.js +7 -7
  423. package/neural-interface/public/shared/ocp-v2/ocp-v2-outcome-notifications.js +16 -0
  424. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +74 -259
  425. package/neural-interface/public/shared/ocp-v2/ocp-v2-plan.js +1 -2
  426. package/neural-interface/public/shared/ocp-v2/ocp-v2-projectbar.js +95 -202
  427. package/neural-interface/public/shared/ocp-v2/ocp-v2-rehydrate.js +13 -4
  428. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +13 -22
  429. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +4 -4
  430. package/neural-interface/public/shared/ocp-v2/ocp-v2-session-actions.js +8 -61
  431. package/neural-interface/public/shared/ocp-v2/ocp-v2-sessions-logic.js +2 -91
  432. package/neural-interface/public/shared/ocp-v2/ocp-v2-slash-hints.js +16 -15
  433. package/neural-interface/public/shared/ocp-v2/ocp-v2-state.js +8 -21
  434. package/neural-interface/public/shared/ocp-v2/ocp-v2-status-logic.js +1 -99
  435. package/neural-interface/public/shared/ocp-v2/ocp-v2-status.js +5 -35
  436. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +17 -21
  437. package/neural-interface/public/shared/ocp-v2/{ocp-v2-profile-inheritance.js → ocp-v2-tools-inheritance.js} +16 -16
  438. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +60 -50
  439. package/neural-interface/public/shared/page-session.js +96 -0
  440. package/neural-interface/public/shared/provider-icons.js +4 -2
  441. package/neural-interface/public/shared/registry.js +5 -3
  442. package/neural-interface/public/shared/retired-2d.js +3 -0
  443. package/neural-interface/public/shared/schedules-view.js +195 -61
  444. package/neural-interface/public/shared/settings/settings-claude-hooks.js +135 -0
  445. package/neural-interface/public/shared/settings/settings-data.js +3 -1
  446. package/neural-interface/public/shared/settings/settings-ia.js +6 -6
  447. package/neural-interface/public/shared/settings/settings-opencode.js +89 -0
  448. package/neural-interface/public/shared/settings/settings-rules.js +2 -2
  449. package/neural-interface/public/shared/settings/settings-skill-folders-pane.js +265 -0
  450. package/neural-interface/public/shared/settings/settings-skill-folders.js +130 -0
  451. package/neural-interface/public/shared/settings/settings-worker-guard.js +72 -0
  452. package/neural-interface/public/shared/showcase-privacy.js +6 -1
  453. package/neural-interface/public/shared/sidepanel-session-status.js +3 -3
  454. package/neural-interface/public/shared/skills/ss-args.js +96 -0
  455. package/neural-interface/public/shared/skills/ss-creation.js +222 -0
  456. package/neural-interface/public/shared/skills/ss-dialog.js +133 -0
  457. package/neural-interface/public/shared/skills/ss-doc.js +173 -0
  458. package/neural-interface/public/shared/skills/ss-eval-adapters.js +269 -0
  459. package/neural-interface/public/shared/skills/ss-eval-grade.js +359 -0
  460. package/neural-interface/public/shared/skills/ss-eval-optimise.js +153 -0
  461. package/neural-interface/public/shared/skills/ss-eval-plan.js +315 -0
  462. package/neural-interface/public/shared/skills/ss-evals.js +417 -0
  463. package/neural-interface/public/shared/skills/ss-form.js +159 -0
  464. package/neural-interface/public/shared/skills/ss-frontmatter.js +376 -0
  465. package/neural-interface/public/shared/skills/ss-ids.js +32 -0
  466. package/neural-interface/public/shared/skills/ss-lint.js +149 -0
  467. package/neural-interface/public/shared/skills/ss-presets.js +117 -0
  468. package/neural-interface/public/shared/skills/ss-profiles.js +513 -0
  469. package/neural-interface/public/shared/skills/ss-resources.js +165 -0
  470. package/neural-interface/public/shared/skills/ss-review-view.js +190 -0
  471. package/neural-interface/public/shared/skills/ss-tabs.js +79 -0
  472. package/neural-interface/public/shared/skills/ss-unicode.js +135 -0
  473. package/neural-interface/public/shared/skills/studio/sx-actions.js +222 -0
  474. package/neural-interface/public/shared/skills/studio/sx-cm.js +101 -0
  475. package/neural-interface/public/shared/skills/studio/sx-create-ai-view.js +749 -0
  476. package/neural-interface/public/shared/skills/studio/sx-create-ai.js +237 -0
  477. package/neural-interface/public/shared/skills/studio/sx-create-view.js +254 -0
  478. package/neural-interface/public/shared/skills/studio/sx-create.js +126 -0
  479. package/neural-interface/public/shared/skills/studio/sx-diff-view.js +43 -0
  480. package/neural-interface/public/shared/skills/studio/sx-diff.js +133 -0
  481. package/neural-interface/public/shared/skills/studio/sx-docs.js +480 -0
  482. package/neural-interface/public/shared/skills/studio/sx-dom.js +230 -0
  483. package/neural-interface/public/shared/skills/studio/sx-edit-view.js +848 -0
  484. package/neural-interface/public/shared/skills/studio/sx-editor-model.js +193 -0
  485. package/neural-interface/public/shared/skills/studio/sx-errors.js +22 -0
  486. package/neural-interface/public/shared/skills/studio/sx-fixes-view.js +359 -0
  487. package/neural-interface/public/shared/skills/studio/sx-fixes.js +327 -0
  488. package/neural-interface/public/shared/skills/studio/sx-geometry.js +82 -0
  489. package/neural-interface/public/shared/skills/studio/sx-import.js +262 -0
  490. package/neural-interface/public/shared/skills/studio/sx-inspector.js +308 -0
  491. package/neural-interface/public/shared/skills/studio/sx-install-flow.js +282 -0
  492. package/neural-interface/public/shared/skills/studio/sx-install.js +313 -0
  493. package/neural-interface/public/shared/skills/studio/sx-library-view.js +748 -0
  494. package/neural-interface/public/shared/skills/studio/sx-library.js +479 -0
  495. package/neural-interface/public/shared/skills/studio/sx-main.js +690 -0
  496. package/neural-interface/public/shared/skills/studio/sx-operations-view.js +98 -0
  497. package/neural-interface/public/shared/skills/studio/sx-operations.js +11 -0
  498. package/neural-interface/public/shared/skills/studio/sx-plan.js +158 -0
  499. package/neural-interface/public/shared/skills/studio/sx-publish-view.js +621 -0
  500. package/neural-interface/public/shared/skills/studio/sx-review-view.js +369 -0
  501. package/neural-interface/public/shared/skills/studio/sx-review.js +148 -0
  502. package/neural-interface/public/shared/skills/studio/sx-scan.js +35 -0
  503. package/neural-interface/public/shared/skills/studio/sx-snippets.js +96 -0
  504. package/neural-interface/public/shared/skills/studio/sx-subject.js +249 -0
  505. package/neural-interface/public/shared/skills/studio/sx-test-view.js +994 -0
  506. package/neural-interface/public/shared/skills/studio/sx-test.js +328 -0
  507. package/neural-interface/public/shared/skills/studio/sx-versions-view.js +241 -0
  508. package/neural-interface/public/shared/skills/studio/sx-workspace-view.js +426 -0
  509. package/neural-interface/public/shared/state.js +1 -1
  510. package/neural-interface/public/shared/streaming-markdown.js +58 -0
  511. package/neural-interface/public/shared/styleguide/sg-panel.js +4 -2
  512. package/neural-interface/public/shared/styles.css +1822 -1756
  513. package/neural-interface/public/shared/synabun-mascot.js +1115 -9
  514. package/neural-interface/public/shared/text-cache.js +29 -0
  515. package/neural-interface/public/shared/tool-list-picker.js +55 -0
  516. package/neural-interface/public/shared/tool-list-storage.js +24 -0
  517. package/neural-interface/public/shared/tool-list-view.js +112 -0
  518. package/neural-interface/public/shared/topright-overflow-logic.js +54 -0
  519. package/neural-interface/public/shared/tour/tour-actor.js +268 -0
  520. package/neural-interface/public/shared/tour/tour-beats.js +336 -0
  521. package/neural-interface/public/shared/tour/tour-engine.js +884 -0
  522. package/neural-interface/public/shared/tour/tour-ink.js +390 -0
  523. package/neural-interface/public/shared/tour/tour-stage.js +440 -0
  524. package/neural-interface/public/shared/tour/tour-state.js +40 -0
  525. package/neural-interface/public/shared/tour/tour-tips.js +99 -0
  526. package/neural-interface/public/shared/tour/tour.css +144 -0
  527. package/neural-interface/public/shared/ui-antigravity-panel.js +19 -0
  528. package/neural-interface/public/shared/ui-assistant.js +2 -0
  529. package/neural-interface/public/shared/ui-automation-studio.js +111 -140
  530. package/neural-interface/public/shared/ui-claude-panel.js +376 -82
  531. package/neural-interface/public/shared/ui-cli-banner.js +23 -167
  532. package/neural-interface/public/shared/ui-command-runner.js +6 -3
  533. package/neural-interface/public/shared/ui-context-menu.js +295 -0
  534. package/neural-interface/public/shared/ui-cost-widget.js +57 -59
  535. package/neural-interface/public/shared/ui-crash-notice.js +187 -0
  536. package/neural-interface/public/shared/ui-detail.js +15 -3
  537. package/neural-interface/public/shared/ui-file-explorer.js +48 -6
  538. package/neural-interface/public/shared/ui-file-links.js +2 -2
  539. package/neural-interface/public/shared/ui-games.js +2 -2
  540. package/neural-interface/public/shared/ui-image-gallery.js +50 -47
  541. package/neural-interface/public/shared/ui-judgments.js +1 -1
  542. package/neural-interface/public/shared/ui-keybinds.js +33 -2
  543. package/neural-interface/public/shared/ui-link.js +3 -3
  544. package/neural-interface/public/shared/ui-menubar.js +10 -20
  545. package/neural-interface/public/shared/ui-native-loop-router-state.js +5 -0
  546. package/neural-interface/public/shared/ui-native-loop-router.js +11 -1
  547. package/neural-interface/public/shared/ui-navbar.js +49 -24
  548. package/neural-interface/public/shared/ui-notifications.js +1 -0
  549. package/neural-interface/public/shared/ui-panels.js +96 -65
  550. package/neural-interface/public/shared/ui-resume.js +9 -7
  551. package/neural-interface/public/shared/ui-schedules-studio.js +546 -431
  552. package/neural-interface/public/shared/ui-scroll-follow.js +16 -5
  553. package/neural-interface/public/shared/ui-sessions.js +27 -15
  554. package/neural-interface/public/shared/ui-settings.js +683 -526
  555. package/neural-interface/public/shared/ui-sidepanel-session-entry.js +9 -2
  556. package/neural-interface/public/shared/ui-sidepanel-sessions.js +4 -4
  557. package/neural-interface/public/shared/ui-sidepanel-state.js +23 -12
  558. package/neural-interface/public/shared/ui-sidepanel-tray.js +48 -7
  559. package/neural-interface/public/shared/ui-sidepanel-windows.js +18 -13
  560. package/neural-interface/public/shared/ui-skills.js +33 -2830
  561. package/neural-interface/public/shared/ui-styleguide.js +5 -2
  562. package/neural-interface/public/shared/ui-sync.js +15 -5
  563. package/neural-interface/public/shared/ui-terminal.js +270 -190
  564. package/neural-interface/public/shared/ui-tool-updates.js +6 -4
  565. package/neural-interface/public/shared/ui-tooltip.js +81 -51
  566. package/neural-interface/public/shared/ui-topright-overflow.js +233 -0
  567. package/neural-interface/public/shared/ui-trash.js +6 -4
  568. package/neural-interface/public/shared/ui-tutorial-layout.js +3 -117
  569. package/neural-interface/public/shared/ui-tutorial-preview.js +103 -0
  570. package/neural-interface/public/shared/ui-tutorial-steps.js +3 -65
  571. package/neural-interface/public/shared/ui-tutorial.css +47 -100
  572. package/neural-interface/public/shared/ui-tutorial.js +285 -509
  573. package/neural-interface/public/shared/ui-update.js +38 -2
  574. package/neural-interface/public/shared/ui-whiteboard.js +312 -59
  575. package/neural-interface/public/shared/ui-worker-end.js +5 -1
  576. package/neural-interface/public/shared/ui-workspaces.js +3 -2
  577. package/neural-interface/public/shared/view-variant.js +66 -0
  578. package/neural-interface/public/shared/whatsapp/wa-view.js +3 -3
  579. package/neural-interface/public/shared/whiteboard-list.js +172 -0
  580. package/neural-interface/public/shared/worker-end.js +10 -3
  581. package/neural-interface/public/shared/worker-questions.js +34 -0
  582. package/neural-interface/public/shared/worker-restart.js +12 -0
  583. package/neural-interface/public/sidepanel-session.html +2 -2
  584. package/neural-interface/public/variant/2d/background.js +2 -1
  585. package/neural-interface/public/variant/2d/context-menu.js +8 -2
  586. package/neural-interface/public/variant/2d/graph.js +42 -20
  587. package/neural-interface/public/variant/2d/hulls.js +14 -9
  588. package/neural-interface/public/variant/2d/lasso.js +11 -7
  589. package/neural-interface/public/variant/2d/main.js +32 -1
  590. package/neural-interface/public/variant/2d/minimap.js +11 -13
  591. package/neural-interface/public/variant/3d/main.js +69 -6
  592. package/neural-interface/public/variant/3d/map-controls.js +24 -9
  593. package/neural-interface/public/vendor/caveat/OFL.txt +93 -0
  594. package/neural-interface/public/vendor/caveat/caveat-latin-400-normal.woff2 +0 -0
  595. package/neural-interface/public/vendor/caveat/caveat-latin-700-normal.woff2 +0 -0
  596. package/neural-interface/public/vendor/caveat/caveat.css +17 -0
  597. package/neural-interface/public/vendor/caveat/manifest.json +12 -0
  598. package/neural-interface/public/vendor/yaml/LICENSE +13 -0
  599. package/neural-interface/public/vendor/yaml/manifest.json +4 -0
  600. package/neural-interface/public/vendor/yaml/yaml-2.9.1.min.js +137 -0
  601. package/neural-interface/scripts/build-vendor.mjs +32 -0
  602. package/neural-interface/server.js +4553 -2855
  603. package/neural-interface/templates/rulesets/README.md +1 -1
  604. package/neural-interface/templates/rulesets/core.md +1 -1
  605. package/neural-interface/templates/rulesets/legacy-hashes.json +23 -5
  606. package/neural-interface/templates/rulesets/manifest.json +8 -8
  607. package/npm-shrinkwrap.json +34 -12
  608. package/package.json +17 -3
  609. package/postinstall.js +9 -4
  610. package/preuninstall.js +1 -1
  611. package/setup.js +97 -322
  612. package/skills/skill-forge/SKILL.md +177 -0
  613. package/skills/skill-forge/evals/evals.json +54 -0
  614. package/skills/skill-forge/references/package.md +105 -0
  615. package/skills/skill-forge/references/review-and-recovery.md +63 -0
  616. package/skills/skill-forge/references/runtime-profiles.md +20 -0
  617. package/skills/skill-forge/references/sources-and-capabilities.md +51 -0
  618. package/skills/synabun/SKILL.md +10 -7
  619. package/skills/synabun/modules/audit.md +1 -5
  620. package/skills/synabun/modules/changelog.md +1 -5
  621. package/skills/synabun/modules/idea.md +1 -1
  622. package/skills/synabun/modules/memorize.md +6 -9
  623. package/skills/synabun/modules/schedule.md +6 -6
  624. package/mcp-server/dist/services/profiles.d.ts +0 -120
  625. package/mcp-server/dist/services/profiles.d.ts.map +0 -1
  626. package/mcp-server/dist/services/profiles.js +0 -387
  627. package/mcp-server/dist/services/profiles.js.map +0 -1
  628. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts +0 -97
  629. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts.map +0 -1
  630. package/mcp-server/dist/tools/leonardo-browser-tools.js +0 -228
  631. package/mcp-server/dist/tools/leonardo-browser-tools.js.map +0 -1
  632. package/mcp-server/dist/tools/leonardo.d.ts +0 -13
  633. package/mcp-server/dist/tools/leonardo.d.ts.map +0 -1
  634. package/mcp-server/dist/tools/leonardo.js +0 -21
  635. package/mcp-server/dist/tools/leonardo.js.map +0 -1
  636. package/mcp-server/dist/tools/profile.d.ts +0 -17
  637. package/mcp-server/dist/tools/profile.d.ts.map +0 -1
  638. package/mcp-server/dist/tools/profile.js +0 -114
  639. package/mcp-server/dist/tools/profile.js.map +0 -1
  640. package/mcp-server/src/services/profiles.ts +0 -428
  641. package/mcp-server/src/tools/leonardo-browser-tools.ts +0 -291
  642. package/mcp-server/src/tools/leonardo.ts +0 -30
  643. package/mcp-server/src/tools/profile.ts +0 -122
  644. package/neural-interface/lib/assistant-brains/opencode-browser-policy.js +0 -47
  645. package/neural-interface/lib/assistant-brains/opencode-route-gate.js +0 -62
  646. package/neural-interface/lib/mcp-profile-servers.js +0 -120
  647. package/neural-interface/public/shared/ui-tutorial-draw.js +0 -311
  648. package/skills/leonardo/SKILL.md +0 -168
  649. package/skills/leonardo/icon.svg +0 -1
  650. package/skills/leonardo/modules/browser-guide.md +0 -378
  651. package/skills/leonardo/modules/image-prompter.md +0 -283
  652. package/skills/leonardo/modules/model-advisor.md +0 -100
  653. package/skills/leonardo/modules/prompt-library.md +0 -110
  654. package/skills/leonardo/modules/style-guide.md +0 -186
  655. package/skills/leonardo/modules/video-prompter.md +0 -317
  656. /package/neural-interface/templates/rulesets/{gemini.md → antigravity.md} +0 -0
@@ -30,6 +30,19 @@
30
30
  // conversation opened again learns its route from the journal like any other
31
31
  // event (no rack of the history moves: none is live) and lends it to the
32
32
  // resumed work once its turn runs (lendRouteScene).
33
+ // What a turn leaves for the user to click sits under the row it belongs to:
34
+ // the follow-ups a message offers (its ```synabun-followups block, or best
35
+ // effort its prose: asst-followups.js) and, under an error that is a usage
36
+ // limit, the waiting card with its countdown (asst-limit-wait.js); a limited
37
+ // run's card shows the same waiting card. The user's next prompt answers
38
+ // them or leaves them stale, and so does a new turn (settleOffers). A card
39
+ // that asks and is still open is lifted to the transcript's bottom while it
40
+ // waits (asst-ask-tail.js), so text and tool output streaming in after it
41
+ // never bury it; once answered it goes back under its row.
42
+ // A follow-ups card and the question cards the panel appends (appendNode with
43
+ // an `_ask`) are presented by the mascot (asst-ask.js): the newest one still
44
+ // open is the character on screen, its face a live rig (liveAsk,
45
+ // activateRigs); every other card's face is a painted still.
33
46
 
34
47
  import { assistantEventKey, fmtCost, fmtElapsed, isRemovableRun, isTerminalRunStatus, modelShortName, providerShortLabel, routeTargetLabel, runTone, summarizeMailbox } from './asst-state.js';
35
48
  import { createRouteLine } from './asst-route.js';
@@ -37,14 +50,21 @@ import { closeMenu, isMenuOpen } from './asst-menu.js';
37
50
  import { computerToolName, describeComputerAction, frameUrls, markerPercent, openFrameLightbox, parseComputerResult, swapImage } from './asst-computer.js';
38
51
  import * as mascotLib from '../synabun-mascot.js';
39
52
  import { isReducedMotion, subscribe } from '../synabun-ticker.js';
40
- import { KIND_SPECS, callOutcome, commandOf, editDiff, editPaths, fmtDuration, groupSubject, joinCut, kindOf, maskEcho, mcpServerOf, middleEllipsis, planState, plural, prettyServer, receiptSentence, redactInput, resultDetail, sayCall, shouldFoldNarration, stationAggregate, stationKey, targetKey, toolWords, verbText } from './asst-tool-kinds.js';
53
+ import { KIND_SPECS, callOutcome, canonicalToolInput, commandOf, editDiff, editPaths, fmtDuration, groupSubject, headEllipsis, kindOf, maskEcho, mcpServerOf, middleEllipsis, planState, plural, prettyServer, receiptSentence, redactInput, resultDetail, sayCall, shouldFoldNarration, stationAggregate, stationKey, targetKey, toolWords, verbText, wrapRuns } from './asst-tool-kinds.js';
41
54
  import { injectRackStyles } from './asst-rack-styles.js';
42
55
  import { injectSceneStyles } from './asst-scene-styles.js';
43
56
  import { activityScene, compactActivity, isLivePhase, runChip, sceneOfClass, sceneSvg, stagePhase } from './asst-scenes.js';
44
57
  import { STAGE_TIMING, createWorkerStage } from './asst-run-stage.js';
45
58
  import { formatRunTokens } from './asst-usage.js';
46
59
  import { sanitizeHtmlString, sanitizeInto } from './asst-sanitize.js';
60
+ import { renderStreamingMarkdown } from '../streaming-markdown.js';
47
61
  import { automationView } from './asst-automation.js';
62
+ import { guessFollowups, splitFollowups } from './asst-followups.js';
63
+ import { createFollowupsCard } from './asst-followups-card.js';
64
+ import { createAskTail } from './asst-ask-tail.js';
65
+ import { limitFromText } from './asst-limit.js';
66
+ import { createLimitDock } from './asst-limit-dock.js';
67
+ import { createLimitWait } from './asst-limit-wait.js';
48
68
 
49
69
  // ── Markdown (lazy marked, the vendored build the sidepanels use too) ───────────────
50
70
  let _marked = null;
@@ -259,12 +279,15 @@ export function updateRunCard(card, run, opts = {}) {
259
279
  const readBtn = card.querySelector('[data-action="read"]');
260
280
  if (readBtn) readBtn.disabled = run.status === 'queued';
261
281
 
282
+ // A provider's usage limit stopped it: the waiting card, whose Retry now stands in for "Retry on" the same model.
283
+ const limitShown = syncRunLimit(card, run, opts, t);
284
+
262
285
  // Escalation offer ("Escalate to ‹label›", or "Retry on ‹label›" after a
263
286
  // temporary failure) when the server proposes one; a note without a button
264
287
  // when a stronger model would hit the same wall (access, the user's input).
265
288
  let esc8 = card.querySelector('.asst-run-escalate');
266
289
  const escalationRaw = run.escalation && typeof run.escalation === 'object' ? run.escalation : null;
267
- const escalation = escalationRaw?.to ? escalationRaw : null;
290
+ const escalation = escalationRaw?.to && !(limitShown && escalationRaw.kind === 'retry') ? escalationRaw : null;
268
291
  const needsNote = escalationRaw && escalationRaw.kind === 'none' ? escalationRaw : null;
269
292
  const onEscalate = opts.onEscalate || card._runOpts?.onEscalate;
270
293
  if (esc8 && esc8.dataset.kind !== (needsNote ? 'note' : 'offer')) { esc8.remove(); esc8 = null; card._escalation = null; }
@@ -322,6 +345,49 @@ export function updateRunCard(card, run, opts = {}) {
322
345
  updateRunMedia(card, run, { t, lightboxT: opts.t || card._runOpts?.t });
323
346
  }
324
347
 
348
+ /** What Retry now runs a limited run on: the server's same-model retry, else the run's own provider and model. */
349
+ function limitRetryTarget(run) {
350
+ const esc = run?.escalation && typeof run.escalation === 'object' ? run.escalation : null;
351
+ if (esc?.kind === 'retry' && esc.to?.provider) return esc.to;
352
+ return { provider: run.provider || 'claude-code', model: run.model || null, effort: run.effort || null, accountId: run.accountId || null };
353
+ }
354
+
355
+ /**
356
+ * A run a provider's usage limit stopped (run.limit, lib/assistant-dispatch.js): the waiting card
357
+ * is registered with the conversation's single bottom dock; a standalone card can own its wait.
358
+ * Retry now runs it again on the same model. Never while running or in the compact agents tray.
359
+ */
360
+ function syncRunLimit(card, run, opts, t) {
361
+ const limit = run.limit && typeof run.limit === 'object' ? run.limit : null;
362
+ const tone = runTone(run);
363
+ const show = !opts.compact && !!limit && tone !== 'running' && tone !== 'queued' && !run.removed;
364
+ let wait = card._limitWait || null;
365
+ if (!show) {
366
+ (opts.onLimitWait || card._runOpts?.onLimitWait)?.(run, null);
367
+ if (wait) { wait.destroy(); wait.el.remove(); card._limitWait = null; }
368
+ return false;
369
+ }
370
+ const shared = opts.onLimitWait || card._runOpts?.onLimitWait;
371
+ if (shared) { shared(run, show && !limit.retriedAt && !run.escalation?.started ? limit : null); return show; }
372
+ if (!wait || !wait.el.isConnected) {
373
+ const onEscalate = opts.onEscalate || card._runOpts?.onEscalate;
374
+ wait = createLimitWait({
375
+ t,
376
+ resetsAt: limit.resetsAt || null,
377
+ since: Date.parse(limit.detectedAt || run.completedAt || '') || Date.now(),
378
+ provider: providerShortLabel(run.provider || 'claude-code'),
379
+ onRetry: typeof onEscalate === 'function' ? () => onEscalate(card.dataset.runId, limitRetryTarget(card._usageRun || run)) : null,
380
+ });
381
+ card._limitWait = wait;
382
+ const meta = card.querySelector('.asst-run-meta');
383
+ if (meta) meta.after(wait.el); else card.appendChild(wait.el);
384
+ } else if (limit.resetsAt) {
385
+ wait.update({ resetsAt: limit.resetsAt });
386
+ }
387
+ if (limit.retriedAt || run.escalation?.started) wait.retried();
388
+ return true;
389
+ }
390
+
325
391
  const MAX_RUN_MEDIA = 8;
326
392
 
327
393
  /**
@@ -424,7 +490,7 @@ const PREVIEW_LINES = 5;
424
490
  const THINK_PREVIEW_LINES = 10;
425
491
  const MAX_DIFF_LINES = 400; // an edit's mini diff draws this many lines; "Show full input" draws the rest
426
492
  // The empty state's suggestions (asst-panel.js ids) and the kind each one is about.
427
- const SUGGESTION_KINDS = { computer: 'computer', web: 'web', fix: 'edit', schedule: 'plan', remember: 'memory' };
493
+ const SUGGESTION_KINDS = { computer: 'computer', web: 'web', fix: 'edit', skill: 'skill', schedule: 'plan', remember: 'memory' };
428
494
 
429
495
  /**
430
496
  * createRenderer(transcriptEl, hooks)
@@ -433,7 +499,13 @@ const SUGGESTION_KINDS = { computer: 'computer', web: 'web', fix: 'edit', schedu
433
499
  * emptySuggestions() → [{ id, label, template }], onSuggestion(template),
434
500
  * brainLabel() → string, onChangeRoutes(), onRouteEvent(ev) → bool (true = handled by an open card),
435
501
  * heroOrigin() → DOMRect | null (where the empty state's character flies in from: the host's placeholder),
436
- * onMascotChange() (the character on screen changed: mascot()) }
502
+ * onMascotChange() (the character on screen changed: mascot()),
503
+ * onFollowup(text, { set, indices }) (a follow-up was clicked: send `text` as the user's message),
504
+ * onFollowupOther() ("Something else…": the composer), limitInfo() → the session's limit notice
505
+ * ({ status, resetsAt } | null), brainProvider() → the brain's provider id, onLimitRetry(text)
506
+ * (Retry now on the Assistant's own limit card: send `text`), onSwitchProvider(anchorEl, { excludeProvider, retryText })
507
+ * (Continue with another provider on the Assistant's own limit card: the host opens its own
508
+ * provider picker anchored on anchorEl and decides what a pick does) }
437
509
  */
438
510
  export function createRenderer(transcriptEl, hooks = {}) {
439
511
  loadMarked();
@@ -449,7 +521,11 @@ export function createRenderer(transcriptEl, hooks = {}) {
449
521
  const autoRows = new Map(); // traceId → the turn's automation trace row
450
522
  let working = null;
451
523
  let workingFace = null; // the "Thinking" row's character: { still, rig?, el, target? } (syncWorkingFace)
524
+ // The open card's slot under every row (asst-ask-tail.js): a card that waits on the user is drawn
525
+ // there, above the "Working…" row, until it is answered; rows appended meanwhile go above it.
526
+ const askTail = createAskTail($msgs, { anchor: () => (working?.parentNode === $msgs ? working : null) });
452
527
  let followTail = true;
528
+ let scrollFrame = 0;
453
529
  let emptyEl = null;
454
530
  let hero = null; // the empty state's character: { rig, el, box, parked, leaving, held, dock, egg, target }
455
531
  const mcpWarned = new Set();
@@ -470,6 +546,15 @@ export function createRenderer(transcriptEl, hooks = {}) {
470
546
  let lastDirect = null; // the last one approved since the user's prompt: a "do it here" continuation turn takes it again
471
547
  let turnSeq = 0; // turns this renderer has seen start (showWorking)
472
548
  let reattachRunning = null; // what the last reattach said: a turn runs (true), none (false), not asked yet (null)
549
+ // What a turn left for the user to click: follow-up cards under its messages (asst-followups-card.js).
550
+ // The user's next prompt answers them or they go
551
+ // stale, and so does a new turn; live and from the journal alike, so a reload draws the same states.
552
+ const offers = new Set(); // { el, set, settle(text) → answered, stale() }
553
+ // The cards the mascot presents (asst-ask.js): follow-ups cards and the question cards the panel appends.
554
+ // The newest one still open is the character on screen (above any stage); see liveAsk / activateRigs.
555
+ const askCards = new Set(); // { el, presenter, isOpen(), onChange, seq }
556
+ let askSeq = 0;
557
+ let lastTextRow = null; // the newest root message since the user's last prompt: the one a guess reads
473
558
 
474
559
  // ── Activity racks ──
475
560
  // Every burst of agent work (tool calls, thinking, short narration) is one
@@ -539,6 +624,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
539
624
  if (step.ghost && step.type === 'call' && !Object.keys(step.input || {}).length) { say.text = '…'; say.detail = '…'; say.segments = null; say.mono = false; say.meta = []; }
540
625
  return say;
541
626
  };
627
+ const limitDock = createLimitDock($msgs, { t });
542
628
  const nowMs = () => (clockAt ?? Date.now());
543
629
  const isTimed = () => !muted || clockAt != null;
544
630
  const locale = () => (typeof document !== 'undefined' && document.documentElement?.lang) || 'en';
@@ -555,7 +641,8 @@ export function createRenderer(transcriptEl, hooks = {}) {
555
641
 
556
642
  function scrollEnd(force = false) {
557
643
  if (!force && !followTail) return;
558
- requestAnimationFrame(() => { $msgs.scrollTop = $msgs.scrollHeight; });
644
+ if (scrollFrame) return;
645
+ scrollFrame = requestAnimationFrame(() => { scrollFrame = 0; $msgs.scrollTop = $msgs.scrollHeight; });
559
646
  }
560
647
 
561
648
  function root() { return { container: $msgs, stream: null, key: null, msgId: null, lastAt: 0 }; }
@@ -739,12 +826,46 @@ export function createRenderer(transcriptEl, hooks = {}) {
739
826
  return best;
740
827
  }
741
828
 
742
- /** The mascot on screen: the hero, else the newest stage, else the "Thinking" row's, else none. */
829
+ /** The mascot on screen: the hero, else the newest open card's, else the newest stage, else the "Thinking" row's, else none. */
743
830
  function visibleMascot() {
744
831
  if (hero?.rig) return hero.target;
832
+ const ask = liveAsk();
833
+ if (ask?.presenter?.isLive?.()) return ask.presenter.target;
745
834
  return newestStage()?.target || workingFace?.target || null;
746
835
  }
747
836
 
837
+ // ── Cards the mascot presents ──
838
+ /**
839
+ * A follow-ups card or a question card (its `_ask`): its face may be the character on screen while it is
840
+ * open. Every ask — a route card too, which has no face — waits at the transcript's bottom until answered.
841
+ */
842
+ function registerAsk(handle) {
843
+ if (!handle?.el || askCards.has(handle)) return;
844
+ handle.seq = (askSeq += 1);
845
+ handle.onChange = () => { askTail.sync(); mascotChanged(); };
846
+ askCards.add(handle);
847
+ askTail.pin(handle);
848
+ mascotChanged();
849
+ }
850
+
851
+ /** The newest card with a face that is still open (answers can still be picked): the one whose face is live. */
852
+ function liveAsk() {
853
+ let best = null;
854
+ for (const handle of [...askCards]) {
855
+ if (!handle.el?.isConnected) { askCards.delete(handle); handle.presenter?.destroy?.(); continue; }
856
+ // A route card asks, but has no face of its own: it waits at the bottom without taking the character.
857
+ if (!handle.presenter || !handle.isOpen() || (best && best.seq > handle.seq)) continue;
858
+ best = handle;
859
+ }
860
+ return best;
861
+ }
862
+
863
+ function dropAsks() {
864
+ for (const handle of askCards) { try { handle.presenter?.destroy?.(); } catch { /* gone */ } }
865
+ askCards.clear();
866
+ askTail.clear();
867
+ }
868
+
748
869
  function mascotChanged() {
749
870
  activateRigs();
750
871
  try { hooks.onMascotChange?.(); } catch { /* the director is gone */ }
@@ -755,12 +876,12 @@ export function createRenderer(transcriptEl, hooks = {}) {
755
876
  while (rows.length > MAX_MESSAGES && rows[0] !== working) rows[0].remove();
756
877
  }
757
878
 
758
- /** Insert a row before "Working…" so the indicator stays the last row. */
879
+ /** Insert a row before the open cards (if any) and before "Working…", so the indicator stays the last row. */
759
880
  function append(node, scope = rootScope, { park = false } = {}) {
760
881
  hideEmpty({ park });
761
- if (scope.container === $msgs && working && working.parentNode === $msgs) $msgs.insertBefore(node, working);
882
+ if (scope.container === $msgs) askTail.insert(node);
762
883
  else scope.container.appendChild(node);
763
- if (scope === rootScope) prune();
884
+ if (scope === rootScope) { prune(); askTail.sync(); }
764
885
  // Text, a card, a notice…: the parked hero would sit on it. The prompt and a rack leave it to settleHero().
765
886
  if (hero?.parked && scope === rootScope && !node._rack && !node.classList.contains('msg-user')) dismissHero();
766
887
  if (racks.size) syncRacks();
@@ -825,6 +946,127 @@ export function createRenderer(transcriptEl, hooks = {}) {
825
946
  linkify(body);
826
947
  }
827
948
 
949
+ /**
950
+ * A message's text without its follow-ups block (asst-followups.js). While it streams, a block
951
+ * being typed stays hidden; once the text is final, the block becomes the card under the row
952
+ * (root messages only: a subagent's offers are not the user's to answer).
953
+ */
954
+ function paintMessage(row, body, text, { streaming = false, scope = rootScope } = {}) {
955
+ const split = splitFollowups(text, { streaming });
956
+ renderStreamingMarkdown(body, split.body, renderMarkdownInto, { streaming });
957
+ if (!streaming && split.block && scope === rootScope) attachFollowups(row, split.block);
958
+ return split;
959
+ }
960
+ /** What a streaming text shows: '' while it is nothing but the start of a block. */
961
+ const shownText = (text) => splitFollowups(text, { streaming: true }).body;
962
+
963
+ // ── Offers ──
964
+ /**
965
+ * The follow-ups card under a message row: once per row, registered as an open offer, and presented by
966
+ * the mascot (its face is live while it is the newest open card: activateRigs).
967
+ */
968
+ function attachFollowups(row, set) {
969
+ if (!row || !set?.items?.length) return null;
970
+ if (row._followups?.el.isConnected) return row._followups;
971
+ // One turn, one card: a set offered earlier in this turn (another text segment between tool calls, a
972
+ // streamed row the final message replaced) is superseded by this one. `offers` holds only this turn's:
973
+ // the user's prompt and a new turn settle and drop the earlier ones (settleOffers), which stay as history.
974
+ supersedeFollowups(row);
975
+ const card = createFollowupsCard(set, {
976
+ t,
977
+ // A card drawn from the journal comes in quietly; a live one with a nod.
978
+ entrance: calm() ? null : 'nod',
979
+ onSend: (text, meta) => hooks.onFollowup?.(text, meta),
980
+ onOther: () => hooks.onFollowupOther?.(),
981
+ onFocusComposer: () => hooks.onFollowupOther?.(),
982
+ });
983
+ row._followups = card;
984
+ card._row = row;
985
+ (row.querySelector('.asst-msg-content') || row).appendChild(card.el);
986
+ offers.add(card);
987
+ registerAsk(card);
988
+ enterRow(card.el);
989
+ scrollEnd();
990
+ return card;
991
+ }
992
+
993
+ /** Take out this turn's open follow-ups cards that belong to a row other than `row`. */
994
+ function supersedeFollowups(row) {
995
+ for (const offer of [...offers]) {
996
+ if (!offer._row || offer._row === row) continue;
997
+ offers.delete(offer);
998
+ askCards.delete(offer);
999
+ try { offer.presenter?.destroy?.(); } catch { /* gone */ }
1000
+ if (offer._row._followups === offer) offer._row._followups = null;
1001
+ offer.el?.remove();
1002
+ }
1003
+ askTail.sync();
1004
+ }
1005
+
1006
+ /**
1007
+ * A finished turn whose last message still ends in a list of options or an "I can X, or Y" offer,
1008
+ * with no block: best-effort items read from the prose. Once per message.
1009
+ */
1010
+ function guessOffer() {
1011
+ const row = lastTextRow;
1012
+ if (!row?.isConnected || row._followups || row._guessed || row.classList.contains('asst-msg-error')) return;
1013
+ // The turn already offered a set from its block: a guess from the prose would be a second card.
1014
+ for (const offer of offers) if (offer._row && offer.el?.isConnected) return;
1015
+ row._guessed = true;
1016
+ const set = guessFollowups(row._text || '');
1017
+ if (set) attachFollowups(row, set);
1018
+ }
1019
+
1020
+ /**
1021
+ * The user's next prompt reached the transcript (`text`), or a new turn started (no text): every open
1022
+ * offer is answered by it or goes stale. A guessed set nobody took leaves: its message says it in words.
1023
+ */
1024
+ function settleOffers(text = null) {
1025
+ for (const offer of [...offers]) {
1026
+ offers.delete(offer);
1027
+ if (!offer.el?.isConnected) continue;
1028
+ const answered = text !== null ? offer.settle(text) : (offer.stale(), false);
1029
+ if (!answered && offer.set?.source === 'heuristic') offer.el.remove();
1030
+ }
1031
+ if (text !== null) lastTextRow = null;
1032
+ }
1033
+
1034
+ const sameText = (a, b) => String(a ?? '').replace(/\s+/g, ' ').trim().toLowerCase() === String(b ?? '').replace(/\s+/g, ' ').trim().toLowerCase();
1035
+
1036
+ /**
1037
+ * A provider's usage limit (asst-limit.js) updates the conversation's single bottom countdown.
1038
+ * The reset time is the session's own limit report while live (hooks.limitInfo: the Claude SDK's
1039
+ * rate_limit_info), else the one the message names, read from when it was written. Retry now sends one
1040
+ * message that retries the blocked work.
1041
+ */
1042
+ function offerLimitWait(text, { brainError = false, limit = null } = {}) {
1043
+ const read = limitFromText(text, { now: nowMs() });
1044
+ let info = limit;
1045
+ if (!info && !muted) { try { info = hooks.limitInfo?.() || null; } catch { info = null; } }
1046
+ const reported = info?.status === 'rejected';
1047
+ // The brain's own error while the session's report says the limit holds is that limit, whatever its words.
1048
+ if (!read && !(brainError && reported)) return null;
1049
+ const resetsAt = reported ? info.resetsAt : read?.resetsAt;
1050
+ const retryText = t('assistant.limitWait.continue', 'The usage limit has reset. Retry what was blocked and continue where you left off.');
1051
+ let providerId = '';
1052
+ try { providerId = hooks.brainProvider?.() || ''; } catch { /* unknown */ }
1053
+ const provider = providerId ? providerShortLabel(providerId) : '';
1054
+ return limitDock.set('brain', {
1055
+ resetsAt, since: nowMs(), provider,
1056
+ onRetry: () => { limitDock.set('brain', null); hooks.onLimitRetry?.(retryText); },
1057
+ onSwitchProvider: typeof hooks.onSwitchProvider === 'function'
1058
+ ? (anchor) => hooks.onSwitchProvider(anchor, { excludeProvider: providerId, retryText })
1059
+ : null,
1060
+ onClose: () => limitDock.set('brain', null),
1061
+ });
1062
+ }
1063
+
1064
+ /** The session's limit report changed (assistant:limit): this turn's waiting card counts to its reset time. */
1065
+ function noteLimit(limit) {
1066
+ if (limit?.status === 'rejected') offerLimitWait('', { brainError: true, limit });
1067
+ else if (limit?.status && limit.status !== 'rejected') limitDock.set('brain', null);
1068
+ }
1069
+
828
1070
  function copyText(text) {
829
1071
  const value = String(text ?? '');
830
1072
  if (!value) return;
@@ -857,10 +1099,18 @@ export function createRenderer(transcriptEl, hooks = {}) {
857
1099
  if (!s) return;
858
1100
  if (s.mdTimer) { clearTimeout(s.mdTimer); s.mdTimer = null; }
859
1101
  if (s.thinkTimer) { clearTimeout(s.thinkTimer); s.thinkTimer = null; }
860
- if (s.bodyEl && s.textBuf.trim()) renderMarkdownInto(s.bodyEl, s.textBuf);
1102
+ if (s.bodyEl && s.textBuf.trim()) paintMessage(s.row, s.bodyEl, s.textBuf, { streaming: true, scope });
861
1103
  if (s.think) refreshStep(s.think);
862
1104
  }
863
1105
 
1106
+ /** A streamed row that stays as the message (no final message replaced it): its text is final now. */
1107
+ function sealStreamRow(scope) {
1108
+ const s = scope.stream;
1109
+ if (!s?.row || !s.bodyEl || !s.textBuf.trim()) return;
1110
+ s.row.classList.remove('streaming');
1111
+ paintMessage(s.row, s.bodyEl, s.textBuf, { scope });
1112
+ }
1113
+
864
1114
  /** The stream's text row goes (the final message renders the text again). */
865
1115
  function dropStreamText(scope) {
866
1116
  const s = scope.stream;
@@ -930,10 +1180,12 @@ export function createRenderer(transcriptEl, hooks = {}) {
930
1180
  if (!s.thinkTimer) s.thinkTimer = setTimeout(() => { s.thinkTimer = null; refreshStep(step); if (step.rack.live) paintStage(step.rack); }, STREAM_TICK_MS);
931
1181
  } else if (delta.type === 'text_delta' && delta.text != null) {
932
1182
  s.textBuf += delta.text;
933
- if (!s.bodyEl && s.textBuf.trim()) {
1183
+ // A row once there is something to show: a follow-ups block on its own is the card, later.
1184
+ if (!s.bodyEl && shownText(s.textBuf).trim()) {
934
1185
  const { row, wrap } = assistantRow(scope, { _text: s.textBuf, _msgId: s.msgId, _claude: s.claude });
935
1186
  row.classList.add('streaming');
936
1187
  noteAnswer(scope, row);
1188
+ if (scope === rootScope) lastTextRow = row;
937
1189
  s.row = row;
938
1190
  s.wrap = wrap;
939
1191
  s.bodyEl = el('div', 'asst-md');
@@ -944,7 +1196,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
944
1196
  s.mdTimer = setTimeout(() => {
945
1197
  s.mdTimer = null;
946
1198
  if (!s.bodyEl) return;
947
- renderMarkdownInto(s.bodyEl, s.textBuf);
1199
+ paintMessage(s.row, s.bodyEl, s.textBuf, { streaming: true, scope });
948
1200
  scrollEnd();
949
1201
  }, STREAM_TICK_MS);
950
1202
  }
@@ -953,7 +1205,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
953
1205
  }
954
1206
  if (type === 'content_block_stop') {
955
1207
  const s = scope.stream;
956
- if (s?.bodyEl && s.mdTimer) { clearTimeout(s.mdTimer); s.mdTimer = null; renderMarkdownInto(s.bodyEl, s.textBuf); }
1208
+ if (s?.bodyEl && s.mdTimer) { clearTimeout(s.mdTimer); s.mdTimer = null; paintMessage(s.row, s.bodyEl, s.textBuf, { streaming: true, scope }); }
957
1209
  if (s?.think && s.thinkTimer) { clearTimeout(s.thinkTimer); s.thinkTimer = null; refreshStep(s.think); }
958
1210
  }
959
1211
  }
@@ -1023,9 +1275,10 @@ export function createRenderer(transcriptEl, hooks = {}) {
1023
1275
  function textRow(scope, text, ctx = {}) {
1024
1276
  const { row, wrap } = assistantRow(scope, { _text: text, _msgId: ctx.msgId || null, _claude: !!ctx.claude });
1025
1277
  const body = el('div', 'asst-md');
1026
- renderMarkdownInto(body, text);
1027
1278
  wrap.appendChild(body);
1279
+ paintMessage(row, body, text, { scope });
1028
1280
  noteAnswer(scope, row);
1281
+ if (scope === rootScope) lastTextRow = row;
1029
1282
  return row;
1030
1283
  }
1031
1284
 
@@ -1052,7 +1305,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
1052
1305
  else if (s.row) {
1053
1306
  s.row.classList.remove('streaming');
1054
1307
  if (!s.wrap.children.length) s.row.remove();
1055
- else if (s.textBuf.trim()) (scope.kept ||= new Set()).add(textKey(s.textBuf));
1308
+ else if (s.textBuf.trim()) { (scope.kept ||= new Set()).add(textKey(s.textBuf)); sealStreamRow(scope); }
1056
1309
  }
1057
1310
  if (streamedThink && !thinks.length) finishThink(streamedThink);
1058
1311
  scope.stream = null;
@@ -1148,7 +1401,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
1148
1401
  const kind = kindOf(block.name, block.input);
1149
1402
  const step = {
1150
1403
  type: 'call', id: block.id || `call-${++stepSeq}`, seq: ++stepSeq, name: block.name || 'tool', kind, key: stationKey(kind, block.name),
1151
- input: block.input || {}, state: 'running', result: '', detail: '', code: null, startedAt: nowMs(), endedAt: 0, progress: 0,
1404
+ input: canonicalToolInput(block.input), state: 'running', result: '', detail: '', code: null, startedAt: nowMs(), endedAt: 0, progress: 0,
1152
1405
  timed: isTimed(), ghost, stale: false, rack, station: null, card: null, parent: parentOf(rack.scope), bodyBuilt: false,
1153
1406
  ...(extra || {}),
1154
1407
  };
@@ -1163,7 +1416,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
1163
1416
  /** The final block of a streamed call, or the same block replayed: the step learns its input, nothing is added. */
1164
1417
  function updateCall(step, block) {
1165
1418
  if (block.name) step.name = block.name;
1166
- if (block.input && typeof block.input === 'object' && Object.keys(block.input).length) step.input = block.input;
1419
+ if (block.input && typeof block.input === 'object' && Object.keys(block.input).length) step.input = canonicalToolInput(block.input);
1167
1420
  step.ghost = false;
1168
1421
  if (step.card?._computer) { step.card._computer.input = step.input; step.card._computer.description = describeComputerAction(step.input, step.name); }
1169
1422
  refreshStep(step);
@@ -1213,10 +1466,10 @@ export function createRenderer(transcriptEl, hooks = {}) {
1213
1466
  return prev;
1214
1467
  }
1215
1468
 
1216
- /** The scope's last row, skipping "Working…" and rows folding into a rack. */
1469
+ /** The scope's last row, skipping "Working…", the open card's slot and rows folding into a rack. */
1217
1470
  function tailRow(scope) {
1218
1471
  let node = scope.container.lastElementChild;
1219
- while (node && (node === working || node.classList.contains('asst-folding'))) node = node.previousElementSibling;
1472
+ while (node && (node === working || node === askTail.slot || node.classList.contains('asst-folding'))) node = node.previousElementSibling;
1220
1473
  return node;
1221
1474
  }
1222
1475
 
@@ -1326,7 +1579,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
1326
1579
  row.innerHTML = `<div class="asst-rack-stage">
1327
1580
  <div class="asst-rack-set"><div class="asst-rack-face" aria-hidden="true"></div><div class="asst-rack-scene" aria-hidden="true"></div></div>
1328
1581
  <div class="asst-rack-caption"></div>
1329
- <div class="asst-bubble" aria-live="off" hidden><span class="asst-bubble-verb"></span><span class="asst-bubble-subject" role="button" tabindex="-1" aria-expanded="false"><span class="asst-bubble-text"></span><span class="asst-bubble-caret" aria-hidden="true"></span></span><span class="asst-bubble-meta"></span><span class="asst-bubble-err" hidden></span></div>
1582
+ <div class="asst-bubble" aria-live="off" hidden><span class="asst-bubble-line"><span class="asst-bubble-verb"></span><span class="asst-bubble-subject" role="button" tabindex="-1" aria-expanded="false"><span class="asst-bubble-text"></span></span><span class="asst-bubble-meta"></span></span><span class="asst-bubble-err" hidden></span></div>
1330
1583
  <div class="asst-rack-sweep" aria-hidden="true"></div>
1331
1584
  </div>
1332
1585
  <button type="button" class="asst-rack-receipt" aria-expanded="false" aria-controls="${ledgerId}"><span class="asst-rack-receipt-face" aria-hidden="true"></span><span class="asst-rack-receipt-text"></span><span class="asst-rack-receipt-failed" hidden></span><span class="asst-rack-receipt-clock"></span><span class="asst-rack-chev" aria-hidden="true">&#x203A;</span></button>
@@ -1340,7 +1593,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
1340
1593
  // The direct route's class whose scene the stage shows: set here, or once when a reopened conversation's route
1341
1594
  // resumes (lendRouteScene); a rack never loses its scene.
1342
1595
  sceneClass: nested ? '' : directSceneClass(), sceneId: '', sceneAt: -Infinity,
1343
- stage: { el: q('.asst-rack-stage'), set: q('.asst-rack-set'), face: q('.asst-rack-face'), scene: q('.asst-rack-scene'), caption: q('.asst-rack-caption'), bubble: q('.asst-bubble'), verb: q('.asst-bubble-verb'), subject: q('.asst-bubble-subject'), text: q('.asst-bubble-text'), meta: q('.asst-bubble-meta'), err: q('.asst-bubble-err') },
1596
+ stage: { el: q('.asst-rack-stage'), set: q('.asst-rack-set'), face: q('.asst-rack-face'), scene: q('.asst-rack-scene'), caption: q('.asst-rack-caption'), bubble: q('.asst-bubble'), line: q('.asst-bubble-line'), verb: q('.asst-bubble-verb'), subject: q('.asst-bubble-subject'), text: q('.asst-bubble-text'), meta: q('.asst-bubble-meta'), err: q('.asst-bubble-err') },
1344
1597
  receipt: { btn: q('.asst-rack-receipt'), face: q('.asst-rack-receipt-face'), text: q('.asst-rack-receipt-text'), failed: q('.asst-rack-receipt-failed'), clock: q('.asst-rack-receipt-clock'), faceBuilt: false, pose: '', svg: null },
1345
1598
  ledger: q('.asst-rack-ledger'), statusEl: q('.asst-rack-status'), alertEl: q('.asst-rack-alert'), timelineEl: null, timelineSig: '',
1346
1599
  rig: null, target: null, held: false, holdUntil: 0, holdTimer: 0, pose: null, awake: null, awakeKey: null,
@@ -1631,6 +1884,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
1631
1884
  s.text.textContent = '';
1632
1885
  s.subject.removeAttribute('title');
1633
1886
  s.subject.className = 'asst-bubble-subject';
1887
+ s.subject.hidden = false;
1634
1888
  s.subject.setAttribute('aria-expanded', 'false');
1635
1889
  s.meta.textContent = '';
1636
1890
  s.err.textContent = '';
@@ -1640,15 +1894,21 @@ export function createRenderer(transcriptEl, hooks = {}) {
1640
1894
  }
1641
1895
 
1642
1896
  /**
1643
- * One rig of this transcript animates: the hero, else the newest stage, else
1644
- * the "Thinking" row's. The rule ends at this renderer: every other panel
1645
- * runs its own, so N live sessions show N moving characters.
1897
+ * One rig of this transcript animates: the hero, else the newest open card
1898
+ * the mascot presents (a follow-ups card, a question card: its face is a rig
1899
+ * only then, and never while calm), else the newest stage, else the
1900
+ * "Thinking" row's. The rule ends at this renderer: every other panel runs
1901
+ * its own, so N live sessions show N moving characters.
1646
1902
  */
1647
1903
  function activateRigs() {
1648
- const newest = hero?.rig ? null : newestStage();
1904
+ const ask = hero?.rig ? null : liveAsk();
1905
+ const askLive = !!ask && !calm();
1906
+ for (const handle of askCards) handle.presenter?.setLive?.(askLive && handle === ask);
1907
+ const asking = askLive && !!ask.presenter?.isLive?.();
1908
+ const newest = hero?.rig || asking ? null : newestStage();
1649
1909
  for (const rack of racks) if (rack.rig) rack.rig.setActive?.(rack === newest);
1650
1910
  hero?.rig?.setActive?.(true);
1651
- workingFace?.rig?.setActive?.(!hero?.rig && !newest);
1911
+ workingFace?.rig?.setActive?.(!hero?.rig && !asking && !newest);
1652
1912
  }
1653
1913
 
1654
1914
  /** The bubble's call: the awake one, else the newest; it holds 900 ms (3 s for thinking) before the next wins. */
@@ -1758,29 +2018,38 @@ export function createRenderer(transcriptEl, hooks = {}) {
1758
2018
  const held = rack.waiting && step === waitingStep(rack);
1759
2019
  const verb = held ? t('assistant.rack.waiting', 'Waiting for you') : verbText(say.verb, running, t);
1760
2020
  const grouped = groupedSubject(rack, step);
1761
- const full = grouped || say.text;
2021
+ // A thought with no words yet says only "Thinking" (the mascot's thought dots are the one busy sign):
2022
+ // the subject's "…" placeholder stays for a call whose input is still streaming.
2023
+ const bare = step.type === 'think' && say.text === '…';
2024
+ // A command as the stage says it: one "…" however many paths its summary shortens (say.stage).
2025
+ const full = bare ? '' : grouped || say.stage || say.text;
1762
2026
  const literal = !grouped && say.full ? say.full : full; // the command as written: the title and the opened bubble
1763
2027
  const open = rack.bubble.open;
1764
- // Narrow stages cut from the head, so the file name (the tail) survives. A small budget keeps a
1765
- // shorter tail (24 chars of it would not fit beside the verb).
1766
- const cut = subjectCut(rack);
1767
- const cutTo = (n) => (open ? literal : cut === 'head' ? headEllipsis(full, n) : middleEllipsis(full, n, Math.min(24, Math.max(8, n - 6))));
1768
- const shownText = cutTo(subjectBudget(rack, verb, cut));
1769
- const key = `${step.id}|${verb}|${shownText}|${say.muted ? 1 : 0}`;
1770
2028
  s.bubble.hidden = !full && !verb;
1771
2029
  s.bubble.dataset.state = held ? 'waiting' : running ? 'running' : step.state;
1772
2030
  s.bubble.dataset.kind = step.kind;
2031
+ // The numbers first: they share the subject's line, so the cut below measures the room they leave.
2032
+ paintBubbleMeta(rack, step, say, now);
2033
+ // Narrow stages cut from the head, so the file name (the tail) survives. A small budget keeps a
2034
+ // shorter tail (24 chars of it would not fit beside the verb). A subject already cut from its head
2035
+ // (a streamed thought's tail) is cut from the head again: one "…", never two.
2036
+ const cut = full.startsWith('…') ? 'head' : subjectCut(rack);
2037
+ const cutTo = (n) => (open ? literal : cut === 'head' ? headEllipsis(full, n) : middleEllipsis(full, n, Math.min(24, Math.max(8, n - 6))));
2038
+ const shownText = bare ? '' : cutTo(subjectBudget(rack, verb, cut));
2039
+ const key = `${step.id}|${verb}|${shownText}|${say.muted ? 1 : 0}|${bare ? 1 : 0}`;
1773
2040
  if (key !== rack.bubble.key) {
1774
2041
  const changedStep = !rack.bubble.key.startsWith(`${step.id}|`);
1775
2042
  rack.bubble.key = key;
1776
2043
  const write = () => {
1777
2044
  s.verb.textContent = verb;
1778
2045
  s.subject.className = `asst-bubble-subject${say.mono ? ' is-mono' : ''}${say.muted ? ' is-muted' : ''}`;
2046
+ s.subject.hidden = bare;
1779
2047
  const segments = grouped || open ? null : say.segments;
1780
2048
  writeSubject(s.text, segments, shownText);
1781
2049
  // The budget is an estimate (a long token wraps whole to the next line): what the two-line
1782
2050
  // clamp would hide is cut here instead, so the subject is never cut a second time.
1783
- const fitted = open ? shownText : fitSubject(s, shownText, cutTo, segments);
2051
+ const fitted = open || bare ? shownText : fitSubject(s, shownText, cutTo, segments);
2052
+ s.subject.classList.toggle('is-cut', fitted.startsWith('…'));
1784
2053
  if (literal !== fitted || open) s.subject.title = literal; else s.subject.removeAttribute('title');
1785
2054
  };
1786
2055
  if (swap && changedStep && !calm() && typeof s.subject.animate === 'function' && s.text.textContent) {
@@ -1793,7 +2062,6 @@ export function createRenderer(transcriptEl, hooks = {}) {
1793
2062
  };
1794
2063
  } else write();
1795
2064
  }
1796
- paintBubbleMeta(rack, step, say, now);
1797
2065
  const err = step.state === 'error' ? (step.detail || exitText(step)) : '';
1798
2066
  s.err.hidden = !err;
1799
2067
  if (s.err.textContent !== err) s.err.textContent = err;
@@ -1804,10 +2072,11 @@ export function createRenderer(transcriptEl, hooks = {}) {
1804
2072
  }
1805
2073
 
1806
2074
  /**
1807
- * How many characters of subject fit the bubble: two lines of the column
1808
- * beside the verb, measured on the laid-out stage. The text is cut in the
1809
- * middle so its tail (the file, the last argument) stays; on a narrow stage
1810
- * from the head (subjectCut).
2075
+ * How many characters of subject fit the bubble: two lines of its one
2076
+ * text flow, less the verb at the head and the numbers at the end,
2077
+ * measured on the laid-out stage. The text is cut in the middle so its
2078
+ * tail (the file, the last argument) stays; on a narrow stage from the
2079
+ * head (subjectCut).
1811
2080
  */
1812
2081
  function subjectBudget(rack, verb, cut = 'middle') {
1813
2082
  const s = rack.stage;
@@ -1817,11 +2086,18 @@ export function createRenderer(transcriptEl, hooks = {}) {
1817
2086
  const bubble = s.bubble.hidden ? null : s.bubble.getBoundingClientRect();
1818
2087
  const beside = !bubble || bubble.left >= face.right - 1;
1819
2088
  const verbPx = Math.max(s.verb.getBoundingClientRect().width, verb.length * 7);
1820
- const column = width - 28 - 22 - (beside ? face.width + 8 : 0) - verbPx - 7;
2089
+ // The numbers at the end of the line (painted first): a running call keeps a clock's room before it shows.
2090
+ const metaPx = s.meta.textContent ? s.meta.getBoundingClientRect().width : 0;
2091
+ const numbersPx = Math.max(metaPx, rack.bubble.step?.state === 'running' ? 28 : 0);
2092
+ // The bubble's text column: the stage's 14px sides, the notch's 8px, the bubble's 10px sides.
2093
+ const column = width - 28 - 8 - 20 - (beside ? face.width + 8 : 0);
1821
2094
  const perLine = Math.max(12, Math.floor(column / 7));
1822
- // Two lines, less what wrapping at token boundaries wastes (a head cut wraps anywhere: two full lines).
1823
- const lines = getComputedStyle(s.subject).whiteSpace === 'nowrap' ? 1 : cut === 'head' ? 2 : 1.7;
1824
- return Math.max(24, Math.min(240, Math.floor(perLine * lines) - 1));
2095
+ // Two lines, less what wrapping at token boundaries wastes (a head cut is one tail: close to two full
2096
+ // lines, a path's pieces wrapping at its separators); a guess on the generous side, since fitSubject
2097
+ // cuts what the clamp would hide or a piece would push past the edge.
2098
+ const lines = getComputedStyle(s.line).whiteSpace === 'nowrap' ? 1 : cut === 'head' ? 2 : 1.85;
2099
+ const taken = Math.ceil((verbPx + 7 + (numbersPx ? numbersPx + 7 : 0)) / 7);
2100
+ return Math.max(24, Math.min(240, Math.floor(perLine * lines) - taken - 1));
1825
2101
  }
1826
2102
 
1827
2103
  /** 'head' where the stylesheet says so (≤320 px: keep the tail), else 'middle'. */
@@ -1830,30 +2106,53 @@ export function createRenderer(transcriptEl, hooks = {}) {
1830
2106
  }
1831
2107
 
1832
2108
  /**
1833
- * "…ender-events.browser.mjs": the last `max` chars, from a token boundary when one is near. One "…":
1834
- * a token the subject already shortened at the start of that tail goes with the cut ("……/x.js").
2109
+ * The subject's text, in its segments' spans (a read's dim folder, its
2110
+ * bold name). A URL or path in it is one `.asst-bubble-piece` (nowrap) per
2111
+ * piece with a <wbr> between them (wrapRuns): it wraps after "/" and at
2112
+ * its other separators, never mid-word.
1835
2113
  */
1836
- function headEllipsis(text, max) {
1837
- const s = String(text ?? '');
1838
- if (s.length <= max) return s;
1839
- let tail = s.slice(s.length - Math.max(8, max - 1));
1840
- const cut = tail.search(/[\s/]/);
1841
- if (cut > 0 && cut < tail.length * 0.4) tail = tail.slice(tail[cut] === '/' ? cut : cut + 1);
1842
- return joinCut('', tail);
1843
- }
1844
-
1845
2114
  function writeSubject(node, segments, text) {
1846
2115
  node.textContent = '';
1847
- if (Array.isArray(segments) && segments.length && segments.map(x => x.text).join('') === text) {
1848
- for (const seg of segments) node.appendChild(el('span', seg.cls ? `is-${seg.cls}` : '', seg.text));
1849
- } else node.textContent = text;
2116
+ const segs = Array.isArray(segments) && segments.length && segments.map(x => x.text).join('') === text ? segments : null;
2117
+ let end = 0;
2118
+ const bounds = segs ? segs.map(seg => [end += seg.text.length, seg.cls]) : [[text.length, null]];
2119
+ // text[from, to) into `host`, one span per segment it crosses.
2120
+ const put = (host, from, to) => {
2121
+ for (const [stop, cls] of bounds) {
2122
+ if (from >= to) break;
2123
+ if (stop <= from) continue;
2124
+ const slice = text.slice(from, Math.min(stop, to));
2125
+ host.appendChild(segs ? el('span', cls ? `is-${cls}` : '', slice) : document.createTextNode(slice));
2126
+ from += slice.length;
2127
+ }
2128
+ };
2129
+ let at = 0;
2130
+ for (const run of wrapRuns(text)) {
2131
+ if (!run.pieces) { put(node, at, at += run.text.length); continue; }
2132
+ run.pieces.forEach((piece, i) => {
2133
+ if (i) node.appendChild(document.createElement('wbr'));
2134
+ put(node.appendChild(el('span', 'asst-bubble-piece')), at, at += piece.length);
2135
+ });
2136
+ }
1850
2137
  }
1851
2138
 
1852
- /** The bubble's subject cut shorter (`cutTo`, six steps at most) until its clamp hides none of it; the text it shows. */
2139
+ /** A path piece wider than what is left of its line: it runs past the line's edge, where the line's overflow hides it. */
2140
+ function pieceOverflows(s) {
2141
+ const pieces = s.text.getElementsByClassName('asst-bubble-piece');
2142
+ if (!pieces.length) return false;
2143
+ const right = s.line.getBoundingClientRect().right + 0.5;
2144
+ for (const piece of pieces) for (const r of piece.getClientRects()) if (r.right > right) return true;
2145
+ return false;
2146
+ }
2147
+
2148
+ /**
2149
+ * The bubble's subject cut shorter (`cutTo`, six steps at most) until the line's clamp hides none of it
2150
+ * (nor the numbers after it) and no path piece runs past its edge; the text it shows.
2151
+ */
1853
2152
  function fitSubject(s, text, cutTo, segments) {
1854
2153
  let shown = text;
1855
2154
  let budget = shown.length;
1856
- for (let i = 0; i < 6 && budget > 12 && s.subject.scrollHeight > s.subject.clientHeight + 1; i += 1) {
2155
+ for (let i = 0; i < 6 && budget > 12 && (s.line.scrollHeight > s.line.clientHeight + 1 || pieceOverflows(s)); i += 1) {
1857
2156
  budget = Math.floor(budget * 0.85);
1858
2157
  shown = cutTo(budget);
1859
2158
  writeSubject(s.text, segments, shown);
@@ -1882,7 +2181,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
1882
2181
  // A subagent's bubble follows its child: the step the agent is on now.
1883
2182
  if (step.kind === 'subagent' && step.children?.length) {
1884
2183
  const child = step.children.filter(c => c.type === 'call' || (c.state === 'running' && !c.stale)).reduce((a, b) => (!a || b.startedAt >= a.startedAt ? b : a), null);
1885
- if (child) parts.push(['m', middleEllipsis(stepSentence(child, child.state === 'running' && !child.stale), 60, 24)]);
2184
+ if (child) parts.push(['m', middleEllipsis(stepSentence(child, child.state === 'running' && !child.stale, { stage: true }), 60, 24)]);
1886
2185
  }
1887
2186
  // ×N only for the same target again (a re-run, a re-read): a number the ledger agrees with.
1888
2187
  // Other calls in the same 800 ms burst read as a sentence instead.
@@ -2176,10 +2475,10 @@ export function createRenderer(transcriptEl, hooks = {}) {
2176
2475
  return t('assistant.rack.state.done', 'done');
2177
2476
  }
2178
2477
 
2179
- /** "Running npm test" / "Ran npm test". */
2180
- function stepSentence(step, running) {
2478
+ /** "Running npm test" / "Ran npm test"; `stage`: as the stage bubble says it (say.stage). */
2479
+ function stepSentence(step, running, { stage = false } = {}) {
2181
2480
  const say = sayOf(step);
2182
- return `${verbText(say.verb, running, t)} ${String(say.text || '').replace(/^\$ /, '')}`.trim();
2481
+ return `${verbText(say.verb, running, t)} ${String((stage && say.stage) || say.text || '').replace(/^\$ /, '')}`.trim();
2183
2482
  }
2184
2483
 
2185
2484
  /** The receipt: one call says what it did; a burst says it in one sentence. */
@@ -2681,6 +2980,9 @@ export function createRenderer(transcriptEl, hooks = {}) {
2681
2980
  }
2682
2981
 
2683
2982
  function appendUser(text, { images = [], files = [], memories = [], origin = null, imageCount = 0 } = {}) {
2983
+ // The prompt answers what the last turn offered (one of its follow-ups, a retry), or it leaves it behind.
2984
+ settleOffers(String(text ?? ''));
2985
+ limitDock.set('brain', null);
2684
2986
  turnFrames = [];
2685
2987
  directRoute = null; // a new prompt is a new task: its own route decides
2686
2988
  lastDirect = null;
@@ -2720,17 +3022,21 @@ export function createRenderer(transcriptEl, hooks = {}) {
2720
3022
  }
2721
3023
 
2722
3024
  function appendError(text) {
2723
- return appendStatus(text || t('assistant.errorGeneric', 'Something went wrong'), 'error');
3025
+ const row = appendStatus(text || t('assistant.errorGeneric', 'Something went wrong'), 'error');
3026
+ if (text) offerLimitWait(text); // a usage limit: the waiting card under it
3027
+ return row;
2724
3028
  }
2725
3029
 
2726
3030
  function appendDivider(label) {
2727
3031
  return append(el('div', 'asst-divider', label || ''));
2728
3032
  }
2729
3033
 
2730
- /** Place an arbitrary node (control / route card) as a transcript row. */
3034
+ /** Place an arbitrary node (control / route card) as a transcript row; a card that asks is presented by the mascot. */
2731
3035
  function appendNode(node) {
2732
3036
  if (!node) return null;
2733
- return append(node);
3037
+ const row = append(node);
3038
+ if (node._ask) registerAsk(node._ask);
3039
+ return row;
2734
3040
  }
2735
3041
 
2736
3042
  function showWorking(label) {
@@ -2744,6 +3050,9 @@ export function createRenderer(transcriptEl, hooks = {}) {
2744
3050
  hideEmpty({ park: true });
2745
3051
  $msgs.appendChild(working);
2746
3052
  if (!turnActive) {
3053
+ // A new turn: what the last one offered and nobody took is no longer clickable.
3054
+ settleOffers();
3055
+ limitDock.set('brain', null);
2747
3056
  turn = { calls: 0, last: null, errored: false, answerRow: null };
2748
3057
  turnSeq += 1;
2749
3058
  if (directRoute && directRoute.turn == null) { // a route picked while no turn ran, or one the journal left running, is this turn's
@@ -2831,6 +3140,12 @@ export function createRenderer(transcriptEl, hooks = {}) {
2831
3140
  onStop: hooks.onStopRun,
2832
3141
  onRead: hooks.onReadRun,
2833
3142
  onEscalate: hooks.onEscalateRun,
3143
+ onLimitWait: (run, limit) => limitDock.set(`run:${run.runId}`, limit ? {
3144
+ resetsAt: limit.resetsAt || null,
3145
+ since: Date.parse(limit.detectedAt || run.completedAt || '') || nowMs(),
3146
+ provider: providerShortLabel(run.provider || 'claude-code'),
3147
+ onRetry: () => { limitDock.set(`run:${run.runId}`, null); hooks.onEscalateRun?.(run.runId, limitRetryTarget(run)); },
3148
+ } : null),
2834
3149
  result,
2835
3150
  keepSummary: true,
2836
3151
  };
@@ -2876,7 +3191,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
2876
3191
  if (!run?.runId) return null;
2877
3192
  noteRun(run);
2878
3193
  const card = runCards.get(run.runId);
2879
- if (!card?.isConnected) return null;
3194
+ if (!card?.isConnected) { limitDock.set(`run:${run.runId}`, null); return null; }
2880
3195
  updateRunCard(card, run, runCardOpts()); // fresh options: the run's own lastResult, never a result the card was built with
2881
3196
  syncStage(run.runId, card, run);
2882
3197
  return card;
@@ -3216,8 +3531,8 @@ export function createRenderer(transcriptEl, hooks = {}) {
3216
3531
  // Under the reply once the turn has ended; a later snapshot (learning) never moves it again.
3217
3532
  const ended = ev.phase !== 'sent';
3218
3533
  if (fresh || (ended && !row.atEnd)) {
3219
- // Placed like any row (before "Working…"), but a status line of the harness never dismisses the hero.
3220
- if (working && working.parentNode === $msgs) $msgs.insertBefore(row.el, working);
3534
+ // Placed like any row (above the open cards and before "Working…"), but a status line of the harness never dismisses the hero.
3535
+ if (askTail.slot || (working && working.parentNode === $msgs)) askTail.insert(row.el);
3221
3536
  else $msgs.appendChild(row.el);
3222
3537
  if (ended) row.atEnd = true;
3223
3538
  prune();
@@ -3231,13 +3546,14 @@ export function createRenderer(transcriptEl, hooks = {}) {
3231
3546
  function showEmpty() {
3232
3547
  if (emptyEl || $msgs.querySelector(':scope > :not(.asst-working)')) return;
3233
3548
  emptyEl = el('div', 'asst-empty');
3549
+ // One column, one hierarchy: the character and the heading, the greeting when there is one,
3550
+ // the starters, then the context line (Project · Budget). No lead: the starters say what it can do.
3234
3551
  emptyEl.innerHTML = `
3235
3552
  <div class="asst-empty-mascot" aria-hidden="true"></div>
3236
3553
  <div class="asst-empty-title" role="heading" aria-level="2">${esc(t('assistant.empty.heading', 'What should we do?'))}</div>
3237
- <div class="asst-empty-body">${esc(t('assistant.empty.lead', 'I can use your Mac, look things up on the web, edit files and run commands, and remember or schedule things.'))}</div>
3238
3554
  <div class="asst-empty-greet"></div>
3239
- <div class="asst-empty-project" hidden></div>
3240
- <div class="asst-suggestions" role="list"></div>`;
3555
+ <div class="asst-suggestions" role="list" aria-label="${esc(t('assistant.empty.starters', 'Starters'))}"></div>
3556
+ <div class="asst-empty-project" role="group" aria-label="${esc(t('assistant.empty.context', 'Context'))}" hidden></div>`;
3241
3557
  const box = emptyEl.querySelector('.asst-empty-mascot');
3242
3558
  try {
3243
3559
  const rig = mascotLib.createMascot(box, { width: 120, height: 60 });
@@ -3245,8 +3561,8 @@ export function createRenderer(transcriptEl, hooks = {}) {
3245
3561
  hero.target = heroTarget(hero);
3246
3562
  wireEgg(hero);
3247
3563
  } catch { hero = null; }
3248
- // The project the conversation works in, under the lead: the host draws the picker (the brain
3249
- // picker's project field), so it and the toolbar's Working project are one control.
3564
+ // The project the conversation works in, on the context line under the starters: the host draws
3565
+ // the picker (the brain picker's project field), so it and the toolbar's Working project are one control.
3250
3566
  const projectSlot = emptyEl.querySelector('.asst-empty-project');
3251
3567
  let picker = null;
3252
3568
  try { picker = typeof hooks.mountEmptyProject === 'function' ? hooks.mountEmptyProject(projectSlot) : null; } catch { picker = null; }
@@ -3263,8 +3579,8 @@ export function createRenderer(transcriptEl, hooks = {}) {
3263
3579
  }
3264
3580
 
3265
3581
  /**
3266
- * The greeting SynaBun prepared for this return (public/shared/greeting/) takes the lead's place.
3267
- * The panel calls it once it knows the conversation is new: a restored one is never greeted.
3582
+ * The greeting SynaBun prepared for this return (public/shared/greeting/) sits between the heading
3583
+ * and the starters. The panel calls it once it knows the conversation is new: a restored one is never greeted.
3268
3584
  */
3269
3585
  function offerGreeting() {
3270
3586
  const owner = emptyEl;
@@ -3282,12 +3598,16 @@ export function createRenderer(transcriptEl, hooks = {}) {
3282
3598
  host.innerHTML = '';
3283
3599
  let list = [];
3284
3600
  try { list = typeof hooks.emptySuggestions === 'function' ? (hooks.emptySuggestions() || []) : []; } catch { list = []; }
3601
+ let i = 0;
3285
3602
  for (const s of list) {
3286
- // glyph | seam | label: the kind's prop, and the character acts it out while the suggestion is hovered or focused.
3603
+ // A ghost row: the kind's prop and the label (no capsule, no seam); it comes in staggered by --i,
3604
+ // and the character acts the kind out while the suggestion is hovered or focused.
3287
3605
  const chip = el('button', 'asst-chip asst-suggestion');
3288
3606
  chip.type = 'button';
3289
3607
  chip.setAttribute('role', 'listitem');
3290
3608
  chip.dataset.suggestion = s.id || '';
3609
+ chip.style.setProperty('--i', String(i + 1));
3610
+ i += 1;
3291
3611
  const kind = s.kind || SUGGESTION_KINDS[s.id] || null;
3292
3612
  if (kind) {
3293
3613
  chip.dataset.kind = kind;
@@ -3301,10 +3621,28 @@ export function createRenderer(transcriptEl, hooks = {}) {
3301
3621
  chip.addEventListener('blur', () => previewHero(null));
3302
3622
  }
3303
3623
  chip.appendChild(el('span', 'asst-chip-text', s.label));
3304
- chip.addEventListener('click', () => hooks.onSuggestion?.(s.template, s));
3624
+ if (s.meta) chip.appendChild(el('span', 'asst-chip-meta', s.meta));
3625
+ chip.addEventListener('click', () => {
3626
+ // The picked starter keeps the gold on-state while its text is the draft (setDrafting clears it);
3627
+ // a row with an action (the Workspace tour) fills nothing, so it is never "picked".
3628
+ if (!s.action) {
3629
+ for (const other of host.querySelectorAll('.is-picked')) other.classList.remove('is-picked');
3630
+ chip.classList.add('is-picked');
3631
+ }
3632
+ hooks.onSuggestion?.(s.template, s);
3633
+ });
3305
3634
  host.appendChild(chip);
3306
3635
  }
3307
3636
  host.hidden = !list.length;
3637
+ // The context line comes in after the last starter.
3638
+ emptyEl.querySelector('.asst-empty-project')?.style.setProperty('--i', String(list.length + 1));
3639
+ }
3640
+
3641
+ /** The composer holds a draft: the starters recede (the picked one stays lit) until it is cleared. */
3642
+ function setDrafting(on) {
3643
+ if (!emptyEl) return;
3644
+ emptyEl.classList.toggle('is-drafting', !!on);
3645
+ if (!on) for (const chip of emptyEl.querySelectorAll('.asst-suggestion.is-picked')) chip.classList.remove('is-picked');
3308
3646
  }
3309
3647
 
3310
3648
  /** The empty state's character tries a suggestion on (null: back to idle); never over a pose the director holds. */
@@ -3509,6 +3847,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
3509
3847
  const content = Array.isArray(ev.message.content) ? ev.message.content : [];
3510
3848
  lastErrorText = typeof ev.message.content === 'string' ? ev.message.content.trim()
3511
3849
  : content.filter((b) => b?.type === 'text').map((b) => b.text).join('\n').trim();
3850
+ if (!parentId) offerLimitWait(lastErrorText, { brainError: true });
3512
3851
  }
3513
3852
  return true;
3514
3853
  }
@@ -3527,8 +3866,8 @@ export function createRenderer(transcriptEl, hooks = {}) {
3527
3866
  }
3528
3867
  case 'result': {
3529
3868
  flushStream(scope);
3530
- // Codex/OpenCode also send an `error` packet; the SDK reports only here.
3531
- const errorText = !parentId && ev.brain !== 'codex' && ev.brain !== 'opencode' ? resultErrorText(ev, t) : '';
3869
+ // Codex/OpenCode/Antigravity also send an `error` packet; the SDK reports only here.
3870
+ const errorText = !parentId && ev.brain !== 'codex' && ev.brain !== 'opencode' && ev.brain !== 'antigravity' ? resultErrorText(ev, t) : '';
3532
3871
  if (errorText && !muted) turn.errored = true;
3533
3872
  // The stage on screen waits for the turn's verdict (onResult → finishTurn) before it folds.
3534
3873
  if (!parentId && !muted) grace = newestStage();
@@ -3536,6 +3875,8 @@ export function createRenderer(transcriptEl, hooks = {}) {
3536
3875
  if (!parentId) endBursts(rootScope);
3537
3876
  // A journal's or a replay's turn ended here (live, finishTurn ends it): so did the route it ran.
3538
3877
  if (!parentId && muted) directRoute = null;
3878
+ // A turn that ended well: its last message's options in prose become items (no block of its own).
3879
+ if (!parentId && !ev.is_error && !String(ev.subtype || '').startsWith('error')) guessOffer();
3539
3880
  hooks.onResult?.(ev);
3540
3881
  if (errorText && errorText !== lastErrorText) appendError(errorText);
3541
3882
  if (!parentId) { lastErrorText = ''; rootScope.kept = null; releaseGrace(); }
@@ -3642,10 +3983,14 @@ export function createRenderer(transcriptEl, hooks = {}) {
3642
3983
  /**
3643
3984
  * The turn ended. `holdMs`: the panel plays a one-shot (success, error) on
3644
3985
  * the character on screen, so its stage stays up that long before it folds.
3986
+ * `guess`: the turn ended well, so options its last message offers in prose may become items.
3645
3987
  */
3646
- function finishTurn({ holdMs = 0 } = {}) {
3988
+ function finishTurn({ holdMs = 0, guess = true } = {}) {
3647
3989
  flushStream(rootScope);
3990
+ sealStreamRow(rootScope);
3648
3991
  if (rootScope.stream?.row) rootScope.stream.row.classList.remove('streaming');
3992
+ // The `result` usually guessed already; a turn that ended well without one gets it here (never a stopped or failed one).
3993
+ if (guess && !turn.errored) guessOffer();
3649
3994
  for (const scope of scopes.values()) flushStream(scope);
3650
3995
  closeStreamThinks();
3651
3996
  hideWorking();
@@ -3686,11 +4031,14 @@ export function createRenderer(transcriptEl, hooks = {}) {
3686
4031
  }
3687
4032
  if (failed) console.warn(`[assistant] ${failed} of ${list.length} transcript entries could not be drawn`, firstError);
3688
4033
  flushStream(rootScope);
4034
+ sealStreamRow(rootScope);
3689
4035
  if (rootScope.stream?.row) rootScope.stream.row.classList.remove('streaming');
3690
4036
  for (const scope of scopes.values()) flushStream(scope);
3691
4037
  hideWorking();
3692
4038
  turnActive = false;
3693
4039
  for (const rack of [...racks]) renderRack(rack);
4040
+ // Drawn quietly: a card the history leaves open (nobody answered it yet) comes to life now.
4041
+ mascotChanged();
3694
4042
  scrollEnd(true);
3695
4043
  }
3696
4044
 
@@ -3728,11 +4076,21 @@ export function createRenderer(transcriptEl, hooks = {}) {
3728
4076
  }
3729
4077
 
3730
4078
  function clear() {
4079
+ if (scrollFrame) { cancelAnimationFrame(scrollFrame); scrollFrame = 0; }
4080
+ clearTimeout(recutTimer);
4081
+ recutTimer = 0;
4082
+ toRecut.clear();
3731
4083
  for (const scope of scopes.values()) { if (scope.stream?.mdTimer) clearTimeout(scope.stream.mdTimer); if (scope.stream?.thinkTimer) clearTimeout(scope.stream.thinkTimer); }
3732
4084
  if (rootScope.stream?.mdTimer) clearTimeout(rootScope.stream.mdTimer);
3733
4085
  if (rootScope.stream?.thinkTimer) clearTimeout(rootScope.stream.thinkTimer);
3734
4086
  scopes.clear();
3735
4087
  dropStages();
4088
+ limitDock.clear();
4089
+ for (const offer of offers) { try { offer.wait?.destroy(); } catch { /* gone */ } }
4090
+ offers.clear();
4091
+ dropAsks();
4092
+ lastTextRow = null;
4093
+ for (const card of runCards.values()) { try { card._limitWait?.destroy(); } catch { /* gone */ } }
3736
4094
  runActivity.clear();
3737
4095
  runCards.clear();
3738
4096
  routeLines.clear();
@@ -3764,7 +4122,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
3764
4122
  return {
3765
4123
  el: $msgs,
3766
4124
  clear,
3767
- destroy: () => { hideWorking(); dropHero(); dropAll(); dropStages(); },
4125
+ destroy: () => { clear(); stageResizes?.disconnect(); limitDock.destroy(); },
3768
4126
  appendUser,
3769
4127
  appendAssistantMarkdown,
3770
4128
  appendStatus,
@@ -3803,6 +4161,10 @@ export function createRenderer(transcriptEl, hooks = {}) {
3803
4161
  forgetSeen: () => { seen.clear(); snapshot = null; },
3804
4162
  /** A card waits on the user: `toolUseId` names the call it holds (the newest live stage when unknown); false clears it. */
3805
4163
  setWaiting,
4164
+ /** The session's usage-limit report changed: this turn's waiting card counts to its reset time. */
4165
+ noteLimit,
4166
+ /** Dismiss the Assistant's own limit-wait card (a provider switch already decided what follows). */
4167
+ clearLimitWait: () => limitDock.set('brain', null),
3806
4168
  finishTurn,
3807
4169
  renderTranscript,
3808
4170
  showWorking,
@@ -3811,6 +4173,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
3811
4173
  showEmpty,
3812
4174
  offerGreeting,
3813
4175
  refreshEmpty: renderSuggestions,
4176
+ setDrafting,
3814
4177
  isEmpty: () => !!emptyEl,
3815
4178
  scrollEnd,
3816
4179
  isNearBottom,