synabun 1.3.0 → 2.0.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 (1163) hide show
  1. package/CHANGELOG.md +205 -662
  2. package/LICENSE-COMMERCIAL.md +33 -0
  3. package/NOTICE +17 -0
  4. package/README.md +175 -853
  5. package/THIRD-PARTY-LICENSES.md +136 -0
  6. package/benchmarks/browser-live-results.json +79 -0
  7. package/benchmarks/browser-v2-results.json +258 -0
  8. package/benchmarks/fixtures/browser/README.md +57 -0
  9. package/benchmarks/fixtures/browser/page-state.json +132 -0
  10. package/benchmarks/fixtures/browser/social.json +92 -0
  11. package/benchmarks/fixtures/browser/target.json +319 -0
  12. package/benchmarks/fixtures/desktop/README.md +58 -0
  13. package/benchmarks/fixtures/desktop/abstention.json +59 -0
  14. package/benchmarks/fixtures/desktop/adversarial.json +22 -0
  15. package/benchmarks/fixtures/desktop/forbidden.json +93 -0
  16. package/benchmarks/fixtures/desktop/generate.mjs +1023 -0
  17. package/benchmarks/fixtures/desktop/target.json +136 -0
  18. package/bin/synabun.js +30 -0
  19. package/docs/whatsapp.md +325 -0
  20. package/hooks/claude-code/post-plan.mjs +613 -391
  21. package/hooks/claude-code/post-remember.mjs +246 -122
  22. package/hooks/claude-code/pre-compact.mjs +171 -110
  23. package/hooks/claude-code/pre-task.mjs +133 -0
  24. package/hooks/claude-code/pre-websearch.mjs +15 -4
  25. package/hooks/claude-code/prompt-origin.mjs +94 -0
  26. package/hooks/claude-code/prompt-submit.mjs +1244 -861
  27. package/hooks/claude-code/session-start.mjs +114 -156
  28. package/hooks/claude-code/shared.mjs +1228 -368
  29. package/hooks/claude-code/state.mjs +167 -0
  30. package/hooks/claude-code/stop.mjs +515 -140
  31. package/hooks/claude-code/subagent-start.mjs +154 -0
  32. package/hooks/claude-code/subagent-stop.mjs +102 -0
  33. package/hooks/claude-code/task-gate.mjs +150 -0
  34. package/launcher.mjs +40 -0
  35. package/lib/claude-hooks.js +137 -0
  36. package/lib/claude-usage-audit.js +141 -0
  37. package/lib/client-config-repair.js +145 -0
  38. package/lib/data-home-migration.js +502 -0
  39. package/lib/desktop-pwa.js +206 -0
  40. package/lib/external-tools.js +221 -0
  41. package/lib/first-launch-protection.js +245 -0
  42. package/lib/packaged-runtime.js +110 -0
  43. package/lib/paths.d.ts +21 -0
  44. package/lib/paths.js +103 -0
  45. package/lib/start-launcher.js +1309 -0
  46. package/mcp-server/dist/config.d.ts +22 -0
  47. package/mcp-server/dist/config.d.ts.map +1 -0
  48. package/mcp-server/dist/config.js +109 -0
  49. package/mcp-server/dist/config.js.map +1 -0
  50. package/mcp-server/dist/http.d.ts +30 -0
  51. package/mcp-server/dist/http.d.ts.map +1 -0
  52. package/mcp-server/dist/http.js +276 -0
  53. package/mcp-server/dist/http.js.map +1 -0
  54. package/mcp-server/dist/index.d.ts +48 -0
  55. package/mcp-server/dist/index.d.ts.map +1 -0
  56. package/mcp-server/dist/index.js +538 -0
  57. package/mcp-server/dist/index.js.map +1 -0
  58. package/mcp-server/dist/preload-http.d.ts +3 -0
  59. package/mcp-server/dist/preload-http.d.ts.map +1 -0
  60. package/mcp-server/dist/preload-http.js +28 -0
  61. package/mcp-server/dist/preload-http.js.map +1 -0
  62. package/mcp-server/dist/preload.d.ts +3 -0
  63. package/mcp-server/dist/preload.d.ts.map +1 -0
  64. package/mcp-server/dist/preload.js +32 -0
  65. package/mcp-server/dist/preload.js.map +1 -0
  66. package/mcp-server/dist/scripts/migrate-qdrant-to-sqlite.d.ts +19 -0
  67. package/mcp-server/dist/scripts/migrate-qdrant-to-sqlite.d.ts.map +1 -0
  68. package/mcp-server/dist/scripts/migrate-qdrant-to-sqlite.js +403 -0
  69. package/mcp-server/dist/scripts/migrate-qdrant-to-sqlite.js.map +1 -0
  70. package/mcp-server/dist/scripts/setup-embeddings.d.ts +3 -0
  71. package/mcp-server/dist/scripts/setup-embeddings.d.ts.map +1 -0
  72. package/mcp-server/dist/scripts/setup-embeddings.js +15 -0
  73. package/mcp-server/dist/scripts/setup-embeddings.js.map +1 -0
  74. package/mcp-server/dist/services/assistant-judgments.d.ts +79 -0
  75. package/mcp-server/dist/services/assistant-judgments.d.ts.map +1 -0
  76. package/mcp-server/dist/services/assistant-judgments.js +128 -0
  77. package/mcp-server/dist/services/assistant-judgments.js.map +1 -0
  78. package/mcp-server/dist/services/browser-assist-gate.d.ts +314 -0
  79. package/mcp-server/dist/services/browser-assist-gate.d.ts.map +1 -0
  80. package/mcp-server/dist/services/browser-assist-gate.js +344 -0
  81. package/mcp-server/dist/services/browser-assist-gate.js.map +1 -0
  82. package/mcp-server/dist/services/browser-assist.d.ts +104 -0
  83. package/mcp-server/dist/services/browser-assist.d.ts.map +1 -0
  84. package/mcp-server/dist/services/browser-assist.js +282 -0
  85. package/mcp-server/dist/services/browser-assist.js.map +1 -0
  86. package/mcp-server/dist/services/browser-bench.d.ts +772 -0
  87. package/mcp-server/dist/services/browser-bench.d.ts.map +1 -0
  88. package/mcp-server/dist/services/browser-bench.js +424 -0
  89. package/mcp-server/dist/services/browser-bench.js.map +1 -0
  90. package/mcp-server/dist/services/browser-judgments.d.ts +196 -0
  91. package/mcp-server/dist/services/browser-judgments.d.ts.map +1 -0
  92. package/mcp-server/dist/services/browser-judgments.js +363 -0
  93. package/mcp-server/dist/services/browser-judgments.js.map +1 -0
  94. package/mcp-server/dist/services/browser-risk.d.ts +166 -0
  95. package/mcp-server/dist/services/browser-risk.d.ts.map +1 -0
  96. package/mcp-server/dist/services/browser-risk.js +458 -0
  97. package/mcp-server/dist/services/browser-risk.js.map +1 -0
  98. package/mcp-server/dist/services/categories.d.ts +61 -0
  99. package/mcp-server/dist/services/categories.d.ts.map +1 -0
  100. package/mcp-server/dist/services/categories.js +377 -0
  101. package/mcp-server/dist/services/categories.js.map +1 -0
  102. package/mcp-server/dist/services/codex-config-heal.d.ts +49 -0
  103. package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -0
  104. package/mcp-server/dist/services/codex-config-heal.js +346 -0
  105. package/mcp-server/dist/services/codex-config-heal.js.map +1 -0
  106. package/mcp-server/dist/services/desktop-assist-gate.d.ts +198 -0
  107. package/mcp-server/dist/services/desktop-assist-gate.d.ts.map +1 -0
  108. package/mcp-server/dist/services/desktop-assist-gate.js +202 -0
  109. package/mcp-server/dist/services/desktop-assist-gate.js.map +1 -0
  110. package/mcp-server/dist/services/desktop-assist.d.ts +104 -0
  111. package/mcp-server/dist/services/desktop-assist.d.ts.map +1 -0
  112. package/mcp-server/dist/services/desktop-assist.js +266 -0
  113. package/mcp-server/dist/services/desktop-assist.js.map +1 -0
  114. package/mcp-server/dist/services/desktop-bench.d.ts +553 -0
  115. package/mcp-server/dist/services/desktop-bench.d.ts.map +1 -0
  116. package/mcp-server/dist/services/desktop-bench.js +311 -0
  117. package/mcp-server/dist/services/desktop-bench.js.map +1 -0
  118. package/mcp-server/dist/services/desktop-client.d.ts +23 -0
  119. package/mcp-server/dist/services/desktop-client.d.ts.map +1 -0
  120. package/mcp-server/dist/services/desktop-client.js +79 -0
  121. package/mcp-server/dist/services/desktop-client.js.map +1 -0
  122. package/mcp-server/dist/services/desktop-judgments.d.ts +125 -0
  123. package/mcp-server/dist/services/desktop-judgments.d.ts.map +1 -0
  124. package/mcp-server/dist/services/desktop-judgments.js +177 -0
  125. package/mcp-server/dist/services/desktop-judgments.js.map +1 -0
  126. package/mcp-server/dist/services/desktop-risk.d.ts +283 -0
  127. package/mcp-server/dist/services/desktop-risk.d.ts.map +1 -0
  128. package/mcp-server/dist/services/desktop-risk.js +605 -0
  129. package/mcp-server/dist/services/desktop-risk.js.map +1 -0
  130. package/mcp-server/dist/services/discord.d.ts +112 -0
  131. package/mcp-server/dist/services/discord.d.ts.map +1 -0
  132. package/mcp-server/dist/services/discord.js +477 -0
  133. package/mcp-server/dist/services/discord.js.map +1 -0
  134. package/mcp-server/dist/services/edit-stale.d.ts +81 -0
  135. package/mcp-server/dist/services/edit-stale.d.ts.map +1 -0
  136. package/mcp-server/dist/services/edit-stale.js +358 -0
  137. package/mcp-server/dist/services/edit-stale.js.map +1 -0
  138. package/mcp-server/dist/services/embedding-service.d.ts +16 -0
  139. package/mcp-server/dist/services/embedding-service.d.ts.map +1 -0
  140. package/mcp-server/dist/services/embedding-service.js +126 -0
  141. package/mcp-server/dist/services/embedding-service.js.map +1 -0
  142. package/mcp-server/dist/services/embedding-worker.d.ts +2 -0
  143. package/mcp-server/dist/services/embedding-worker.d.ts.map +1 -0
  144. package/mcp-server/dist/services/embedding-worker.js +52 -0
  145. package/mcp-server/dist/services/embedding-worker.js.map +1 -0
  146. package/mcp-server/dist/services/fb-regions.d.ts +71 -0
  147. package/mcp-server/dist/services/fb-regions.d.ts.map +1 -0
  148. package/mcp-server/dist/services/fb-regions.js +211 -0
  149. package/mcp-server/dist/services/fb-regions.js.map +1 -0
  150. package/mcp-server/dist/services/file-checksums.d.ts +22 -0
  151. package/mcp-server/dist/services/file-checksums.d.ts.map +1 -0
  152. package/mcp-server/dist/services/file-checksums.js +55 -0
  153. package/mcp-server/dist/services/file-checksums.js.map +1 -0
  154. package/mcp-server/dist/services/file-excerpt.d.ts +24 -0
  155. package/mcp-server/dist/services/file-excerpt.d.ts.map +1 -0
  156. package/mcp-server/dist/services/file-excerpt.js +65 -0
  157. package/mcp-server/dist/services/file-excerpt.js.map +1 -0
  158. package/mcp-server/dist/services/identity.d.ts +124 -0
  159. package/mcp-server/dist/services/identity.d.ts.map +1 -0
  160. package/mcp-server/dist/services/identity.js +241 -0
  161. package/mcp-server/dist/services/identity.js.map +1 -0
  162. package/mcp-server/dist/services/local-embeddings.d.ts +2 -0
  163. package/mcp-server/dist/services/local-embeddings.d.ts.map +1 -0
  164. package/mcp-server/dist/services/local-embeddings.js +3 -0
  165. package/mcp-server/dist/services/local-embeddings.js.map +1 -0
  166. package/mcp-server/dist/services/memory-client.d.ts +4 -0
  167. package/mcp-server/dist/services/memory-client.d.ts.map +1 -0
  168. package/mcp-server/dist/services/memory-client.js +27 -0
  169. package/mcp-server/dist/services/memory-client.js.map +1 -0
  170. package/mcp-server/dist/services/memory-judge-backfill.d.ts +135 -0
  171. package/mcp-server/dist/services/memory-judge-backfill.d.ts.map +1 -0
  172. package/mcp-server/dist/services/memory-judge-backfill.js +510 -0
  173. package/mcp-server/dist/services/memory-judge-backfill.js.map +1 -0
  174. package/mcp-server/dist/services/memory-judgments.d.ts +362 -0
  175. package/mcp-server/dist/services/memory-judgments.d.ts.map +1 -0
  176. package/mcp-server/dist/services/memory-judgments.js +674 -0
  177. package/mcp-server/dist/services/memory-judgments.js.map +1 -0
  178. package/mcp-server/dist/services/memory-maintenance.d.ts +108 -0
  179. package/mcp-server/dist/services/memory-maintenance.d.ts.map +1 -0
  180. package/mcp-server/dist/services/memory-maintenance.js +403 -0
  181. package/mcp-server/dist/services/memory-maintenance.js.map +1 -0
  182. package/mcp-server/dist/services/memory-retrieval.d.ts +66 -0
  183. package/mcp-server/dist/services/memory-retrieval.d.ts.map +1 -0
  184. package/mcp-server/dist/services/memory-retrieval.js +412 -0
  185. package/mcp-server/dist/services/memory-retrieval.js.map +1 -0
  186. package/mcp-server/dist/services/memory-schema.d.ts +16 -0
  187. package/mcp-server/dist/services/memory-schema.d.ts.map +1 -0
  188. package/mcp-server/dist/services/memory-schema.js +282 -0
  189. package/mcp-server/dist/services/memory-schema.js.map +1 -0
  190. package/mcp-server/dist/services/memory-sessions.d.ts +24 -0
  191. package/mcp-server/dist/services/memory-sessions.d.ts.map +1 -0
  192. package/mcp-server/dist/services/memory-sessions.js +110 -0
  193. package/mcp-server/dist/services/memory-sessions.js.map +1 -0
  194. package/mcp-server/dist/services/memory-triage.d.ts +60 -0
  195. package/mcp-server/dist/services/memory-triage.d.ts.map +1 -0
  196. package/mcp-server/dist/services/memory-triage.js +189 -0
  197. package/mcp-server/dist/services/memory-triage.js.map +1 -0
  198. package/mcp-server/dist/services/memory-verification.d.ts +24 -0
  199. package/mcp-server/dist/services/memory-verification.d.ts.map +1 -0
  200. package/mcp-server/dist/services/memory-verification.js +92 -0
  201. package/mcp-server/dist/services/memory-verification.js.map +1 -0
  202. package/mcp-server/dist/services/memory-writer.d.ts +26 -0
  203. package/mcp-server/dist/services/memory-writer.d.ts.map +1 -0
  204. package/mcp-server/dist/services/memory-writer.js +126 -0
  205. package/mcp-server/dist/services/memory-writer.js.map +1 -0
  206. package/mcp-server/dist/services/neural-interface.d.ts +439 -0
  207. package/mcp-server/dist/services/neural-interface.d.ts.map +1 -0
  208. package/mcp-server/dist/services/neural-interface.js +1135 -0
  209. package/mcp-server/dist/services/neural-interface.js.map +1 -0
  210. package/mcp-server/dist/services/profiles.d.ts +120 -0
  211. package/mcp-server/dist/services/profiles.d.ts.map +1 -0
  212. package/mcp-server/dist/services/profiles.js +387 -0
  213. package/mcp-server/dist/services/profiles.js.map +1 -0
  214. package/mcp-server/dist/services/secret-gate.d.ts +29 -0
  215. package/mcp-server/dist/services/secret-gate.d.ts.map +1 -0
  216. package/mcp-server/dist/services/secret-gate.js +43 -0
  217. package/mcp-server/dist/services/secret-gate.js.map +1 -0
  218. package/mcp-server/dist/services/server-instructions.d.ts +10 -0
  219. package/mcp-server/dist/services/server-instructions.d.ts.map +1 -0
  220. package/mcp-server/dist/services/server-instructions.js +101 -0
  221. package/mcp-server/dist/services/server-instructions.js.map +1 -0
  222. package/mcp-server/dist/services/sqlite.d.ts +235 -0
  223. package/mcp-server/dist/services/sqlite.d.ts.map +1 -0
  224. package/mcp-server/dist/services/sqlite.js +1185 -0
  225. package/mcp-server/dist/services/sqlite.js.map +1 -0
  226. package/mcp-server/dist/services/typesafe-bench.d.ts +99 -0
  227. package/mcp-server/dist/services/typesafe-bench.d.ts.map +1 -0
  228. package/mcp-server/dist/services/typesafe-bench.js +218 -0
  229. package/mcp-server/dist/services/typesafe-bench.js.map +1 -0
  230. package/mcp-server/dist/services/typesafe-config.d.ts +342 -0
  231. package/mcp-server/dist/services/typesafe-config.d.ts.map +1 -0
  232. package/mcp-server/dist/services/typesafe-config.js +746 -0
  233. package/mcp-server/dist/services/typesafe-config.js.map +1 -0
  234. package/mcp-server/dist/services/typesafe.d.ts +213 -0
  235. package/mcp-server/dist/services/typesafe.d.ts.map +1 -0
  236. package/mcp-server/dist/services/typesafe.js +330 -0
  237. package/mcp-server/dist/services/typesafe.js.map +1 -0
  238. package/mcp-server/dist/services/vector-cache.d.ts +68 -0
  239. package/mcp-server/dist/services/vector-cache.d.ts.map +1 -0
  240. package/mcp-server/dist/services/vector-cache.js +271 -0
  241. package/mcp-server/dist/services/vector-cache.js.map +1 -0
  242. package/mcp-server/dist/services/whiteboard-geometry.d.ts +305 -0
  243. package/mcp-server/dist/services/whiteboard-geometry.d.ts.map +1 -0
  244. package/mcp-server/dist/services/whiteboard-geometry.js +666 -0
  245. package/mcp-server/dist/services/whiteboard-geometry.js.map +1 -0
  246. package/mcp-server/dist/tools/agents.d.ts +212 -0
  247. package/mcp-server/dist/tools/agents.d.ts.map +1 -0
  248. package/mcp-server/dist/tools/agents.js +407 -0
  249. package/mcp-server/dist/tools/agents.js.map +1 -0
  250. package/mcp-server/dist/tools/bluesky.d.ts +39 -0
  251. package/mcp-server/dist/tools/bluesky.d.ts.map +1 -0
  252. package/mcp-server/dist/tools/bluesky.js +1144 -0
  253. package/mcp-server/dist/tools/bluesky.js.map +1 -0
  254. package/mcp-server/dist/tools/browser-advanced.d.ts +58 -0
  255. package/mcp-server/dist/tools/browser-advanced.d.ts.map +1 -0
  256. package/mcp-server/dist/tools/browser-advanced.js +106 -0
  257. package/mcp-server/dist/tools/browser-advanced.js.map +1 -0
  258. package/mcp-server/dist/tools/browser-batch.d.ts +99 -0
  259. package/mcp-server/dist/tools/browser-batch.d.ts.map +1 -0
  260. package/mcp-server/dist/tools/browser-batch.js +187 -0
  261. package/mcp-server/dist/tools/browser-batch.js.map +1 -0
  262. package/mcp-server/dist/tools/browser-cheatsheet.d.ts +21 -0
  263. package/mcp-server/dist/tools/browser-cheatsheet.d.ts.map +1 -0
  264. package/mcp-server/dist/tools/browser-cheatsheet.js +144 -0
  265. package/mcp-server/dist/tools/browser-cheatsheet.js.map +1 -0
  266. package/mcp-server/dist/tools/browser-interact.d.ts +239 -0
  267. package/mcp-server/dist/tools/browser-interact.d.ts.map +1 -0
  268. package/mcp-server/dist/tools/browser-interact.js +300 -0
  269. package/mcp-server/dist/tools/browser-interact.js.map +1 -0
  270. package/mcp-server/dist/tools/browser-navigate.d.ts +90 -0
  271. package/mcp-server/dist/tools/browser-navigate.d.ts.map +1 -0
  272. package/mcp-server/dist/tools/browser-navigate.js +142 -0
  273. package/mcp-server/dist/tools/browser-navigate.js.map +1 -0
  274. package/mcp-server/dist/tools/browser-observe.d.ts +561 -0
  275. package/mcp-server/dist/tools/browser-observe.d.ts.map +1 -0
  276. package/mcp-server/dist/tools/browser-observe.js +2009 -0
  277. package/mcp-server/dist/tools/browser-observe.js.map +1 -0
  278. package/mcp-server/dist/tools/browser.d.ts +34 -0
  279. package/mcp-server/dist/tools/browser.d.ts.map +1 -0
  280. package/mcp-server/dist/tools/browser.js +112 -0
  281. package/mcp-server/dist/tools/browser.js.map +1 -0
  282. package/mcp-server/dist/tools/card-tools.d.ts +82 -0
  283. package/mcp-server/dist/tools/card-tools.d.ts.map +1 -0
  284. package/mcp-server/dist/tools/card-tools.js +137 -0
  285. package/mcp-server/dist/tools/card-tools.js.map +1 -0
  286. package/mcp-server/dist/tools/card.d.ts +8 -0
  287. package/mcp-server/dist/tools/card.d.ts.map +1 -0
  288. package/mcp-server/dist/tools/card.js +16 -0
  289. package/mcp-server/dist/tools/card.js.map +1 -0
  290. package/mcp-server/dist/tools/category.d.ts +32 -0
  291. package/mcp-server/dist/tools/category.d.ts.map +1 -0
  292. package/mcp-server/dist/tools/category.js +290 -0
  293. package/mcp-server/dist/tools/category.js.map +1 -0
  294. package/mcp-server/dist/tools/choice.d.ts +160 -0
  295. package/mcp-server/dist/tools/choice.d.ts.map +1 -0
  296. package/mcp-server/dist/tools/choice.js +80 -0
  297. package/mcp-server/dist/tools/choice.js.map +1 -0
  298. package/mcp-server/dist/tools/computer.d.ts +71 -0
  299. package/mcp-server/dist/tools/computer.d.ts.map +1 -0
  300. package/mcp-server/dist/tools/computer.js +174 -0
  301. package/mcp-server/dist/tools/computer.js.map +1 -0
  302. package/mcp-server/dist/tools/discord-channel.d.ts +44 -0
  303. package/mcp-server/dist/tools/discord-channel.d.ts.map +1 -0
  304. package/mcp-server/dist/tools/discord-channel.js +236 -0
  305. package/mcp-server/dist/tools/discord-channel.js.map +1 -0
  306. package/mcp-server/dist/tools/discord-guild.d.ts +20 -0
  307. package/mcp-server/dist/tools/discord-guild.d.ts.map +1 -0
  308. package/mcp-server/dist/tools/discord-guild.js +138 -0
  309. package/mcp-server/dist/tools/discord-guild.js.map +1 -0
  310. package/mcp-server/dist/tools/discord-member.d.ts +26 -0
  311. package/mcp-server/dist/tools/discord-member.d.ts.map +1 -0
  312. package/mcp-server/dist/tools/discord-member.js +122 -0
  313. package/mcp-server/dist/tools/discord-member.js.map +1 -0
  314. package/mcp-server/dist/tools/discord-message.d.ts +36 -0
  315. package/mcp-server/dist/tools/discord-message.d.ts.map +1 -0
  316. package/mcp-server/dist/tools/discord-message.js +184 -0
  317. package/mcp-server/dist/tools/discord-message.js.map +1 -0
  318. package/mcp-server/dist/tools/discord-onboarding.d.ts +32 -0
  319. package/mcp-server/dist/tools/discord-onboarding.d.ts.map +1 -0
  320. package/mcp-server/dist/tools/discord-onboarding.js +179 -0
  321. package/mcp-server/dist/tools/discord-onboarding.js.map +1 -0
  322. package/mcp-server/dist/tools/discord-role.d.ts +34 -0
  323. package/mcp-server/dist/tools/discord-role.d.ts.map +1 -0
  324. package/mcp-server/dist/tools/discord-role.js +178 -0
  325. package/mcp-server/dist/tools/discord-role.js.map +1 -0
  326. package/mcp-server/dist/tools/discord-thread.d.ts +28 -0
  327. package/mcp-server/dist/tools/discord-thread.d.ts.map +1 -0
  328. package/mcp-server/dist/tools/discord-thread.js +88 -0
  329. package/mcp-server/dist/tools/discord-thread.js.map +1 -0
  330. package/mcp-server/dist/tools/discord-webhook.d.ts +32 -0
  331. package/mcp-server/dist/tools/discord-webhook.d.ts.map +1 -0
  332. package/mcp-server/dist/tools/discord-webhook.js +154 -0
  333. package/mcp-server/dist/tools/discord-webhook.js.map +1 -0
  334. package/mcp-server/dist/tools/discord.d.ts +7 -0
  335. package/mcp-server/dist/tools/discord.d.ts.map +1 -0
  336. package/mcp-server/dist/tools/discord.js +25 -0
  337. package/mcp-server/dist/tools/discord.js.map +1 -0
  338. package/mcp-server/dist/tools/fb-groups.d.ts +98 -0
  339. package/mcp-server/dist/tools/fb-groups.d.ts.map +1 -0
  340. package/mcp-server/dist/tools/fb-groups.js +369 -0
  341. package/mcp-server/dist/tools/fb-groups.js.map +1 -0
  342. package/mcp-server/dist/tools/forget.d.ts +14 -0
  343. package/mcp-server/dist/tools/forget.d.ts.map +1 -0
  344. package/mcp-server/dist/tools/forget.js +26 -0
  345. package/mcp-server/dist/tools/forget.js.map +1 -0
  346. package/mcp-server/dist/tools/git-tools.d.ts +24 -0
  347. package/mcp-server/dist/tools/git-tools.d.ts.map +1 -0
  348. package/mcp-server/dist/tools/git-tools.js +107 -0
  349. package/mcp-server/dist/tools/git-tools.js.map +1 -0
  350. package/mcp-server/dist/tools/git.d.ts +7 -0
  351. package/mcp-server/dist/tools/git.d.ts.map +1 -0
  352. package/mcp-server/dist/tools/git.js +11 -0
  353. package/mcp-server/dist/tools/git.js.map +1 -0
  354. package/mcp-server/dist/tools/gsc/experience.d.ts +135 -0
  355. package/mcp-server/dist/tools/gsc/experience.d.ts.map +1 -0
  356. package/mcp-server/dist/tools/gsc/experience.js +351 -0
  357. package/mcp-server/dist/tools/gsc/experience.js.map +1 -0
  358. package/mcp-server/dist/tools/gsc/helpers.d.ts +78 -0
  359. package/mcp-server/dist/tools/gsc/helpers.d.ts.map +1 -0
  360. package/mcp-server/dist/tools/gsc/helpers.js +258 -0
  361. package/mcp-server/dist/tools/gsc/helpers.js.map +1 -0
  362. package/mcp-server/dist/tools/gsc/indexing.d.ts +121 -0
  363. package/mcp-server/dist/tools/gsc/indexing.d.ts.map +1 -0
  364. package/mcp-server/dist/tools/gsc/indexing.js +357 -0
  365. package/mcp-server/dist/tools/gsc/indexing.js.map +1 -0
  366. package/mcp-server/dist/tools/gsc/inspect.d.ts +78 -0
  367. package/mcp-server/dist/tools/gsc/inspect.d.ts.map +1 -0
  368. package/mcp-server/dist/tools/gsc/inspect.js +321 -0
  369. package/mcp-server/dist/tools/gsc/inspect.js.map +1 -0
  370. package/mcp-server/dist/tools/gsc/navigate.d.ts +44 -0
  371. package/mcp-server/dist/tools/gsc/navigate.d.ts.map +1 -0
  372. package/mcp-server/dist/tools/gsc/navigate.js +197 -0
  373. package/mcp-server/dist/tools/gsc/navigate.js.map +1 -0
  374. package/mcp-server/dist/tools/gsc/performance.d.ts +84 -0
  375. package/mcp-server/dist/tools/gsc/performance.d.ts.map +1 -0
  376. package/mcp-server/dist/tools/gsc/performance.js +274 -0
  377. package/mcp-server/dist/tools/gsc/performance.js.map +1 -0
  378. package/mcp-server/dist/tools/gsc/settings.d.ts +153 -0
  379. package/mcp-server/dist/tools/gsc/settings.d.ts.map +1 -0
  380. package/mcp-server/dist/tools/gsc/settings.js +425 -0
  381. package/mcp-server/dist/tools/gsc/settings.js.map +1 -0
  382. package/mcp-server/dist/tools/gsc.d.ts +7 -0
  383. package/mcp-server/dist/tools/gsc.d.ts.map +1 -0
  384. package/mcp-server/dist/tools/gsc.js +51 -0
  385. package/mcp-server/dist/tools/gsc.js.map +1 -0
  386. package/mcp-server/dist/tools/image-tools.d.ts +24 -0
  387. package/mcp-server/dist/tools/image-tools.d.ts.map +1 -0
  388. package/mcp-server/dist/tools/image-tools.js +80 -0
  389. package/mcp-server/dist/tools/image-tools.js.map +1 -0
  390. package/mcp-server/dist/tools/image.d.ts +10 -0
  391. package/mcp-server/dist/tools/image.d.ts.map +1 -0
  392. package/mcp-server/dist/tools/image.js +14 -0
  393. package/mcp-server/dist/tools/image.js.map +1 -0
  394. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts +97 -0
  395. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts.map +1 -0
  396. package/mcp-server/dist/tools/leonardo-browser-tools.js +228 -0
  397. package/mcp-server/dist/tools/leonardo-browser-tools.js.map +1 -0
  398. package/mcp-server/dist/tools/leonardo.d.ts +13 -0
  399. package/mcp-server/dist/tools/leonardo.d.ts.map +1 -0
  400. package/mcp-server/dist/tools/leonardo.js +21 -0
  401. package/mcp-server/dist/tools/leonardo.js.map +1 -0
  402. package/mcp-server/dist/tools/loop.d.ts +30 -0
  403. package/mcp-server/dist/tools/loop.d.ts.map +1 -0
  404. package/mcp-server/dist/tools/loop.js +401 -0
  405. package/mcp-server/dist/tools/loop.js.map +1 -0
  406. package/mcp-server/dist/tools/memories.d.ts +56 -0
  407. package/mcp-server/dist/tools/memories.d.ts.map +1 -0
  408. package/mcp-server/dist/tools/memories.js +171 -0
  409. package/mcp-server/dist/tools/memories.js.map +1 -0
  410. package/mcp-server/dist/tools/morelogin.d.ts +24 -0
  411. package/mcp-server/dist/tools/morelogin.d.ts.map +1 -0
  412. package/mcp-server/dist/tools/morelogin.js +79 -0
  413. package/mcp-server/dist/tools/morelogin.js.map +1 -0
  414. package/mcp-server/dist/tools/profile.d.ts +17 -0
  415. package/mcp-server/dist/tools/profile.d.ts.map +1 -0
  416. package/mcp-server/dist/tools/profile.js +114 -0
  417. package/mcp-server/dist/tools/profile.js.map +1 -0
  418. package/mcp-server/dist/tools/recall.d.ts +71 -0
  419. package/mcp-server/dist/tools/recall.d.ts.map +1 -0
  420. package/mcp-server/dist/tools/recall.js +147 -0
  421. package/mcp-server/dist/tools/recall.js.map +1 -0
  422. package/mcp-server/dist/tools/reflect.d.ts +56 -0
  423. package/mcp-server/dist/tools/reflect.d.ts.map +1 -0
  424. package/mcp-server/dist/tools/reflect.js +157 -0
  425. package/mcp-server/dist/tools/reflect.js.map +1 -0
  426. package/mcp-server/dist/tools/remember.d.ts +62 -0
  427. package/mcp-server/dist/tools/remember.d.ts.map +1 -0
  428. package/mcp-server/dist/tools/remember.js +201 -0
  429. package/mcp-server/dist/tools/remember.js.map +1 -0
  430. package/mcp-server/dist/tools/response.d.ts +23 -0
  431. package/mcp-server/dist/tools/response.d.ts.map +1 -0
  432. package/mcp-server/dist/tools/response.js +24 -0
  433. package/mcp-server/dist/tools/response.js.map +1 -0
  434. package/mcp-server/dist/tools/restore.d.ts +14 -0
  435. package/mcp-server/dist/tools/restore.d.ts.map +1 -0
  436. package/mcp-server/dist/tools/restore.js +23 -0
  437. package/mcp-server/dist/tools/restore.js.map +1 -0
  438. package/mcp-server/dist/tools/style-guide-tools.d.ts +51 -0
  439. package/mcp-server/dist/tools/style-guide-tools.d.ts.map +1 -0
  440. package/mcp-server/dist/tools/style-guide-tools.js +231 -0
  441. package/mcp-server/dist/tools/style-guide-tools.js.map +1 -0
  442. package/mcp-server/dist/tools/style-guide.d.ts +8 -0
  443. package/mcp-server/dist/tools/style-guide.d.ts.map +1 -0
  444. package/mcp-server/dist/tools/style-guide.js +12 -0
  445. package/mcp-server/dist/tools/style-guide.js.map +1 -0
  446. package/mcp-server/dist/tools/sync.d.ts +20 -0
  447. package/mcp-server/dist/tools/sync.d.ts.map +1 -0
  448. package/mcp-server/dist/tools/sync.js +156 -0
  449. package/mcp-server/dist/tools/sync.js.map +1 -0
  450. package/mcp-server/dist/tools/tictactoe-tools.d.ts +18 -0
  451. package/mcp-server/dist/tools/tictactoe-tools.d.ts.map +1 -0
  452. package/mcp-server/dist/tools/tictactoe-tools.js +78 -0
  453. package/mcp-server/dist/tools/tictactoe-tools.js.map +1 -0
  454. package/mcp-server/dist/tools/tictactoe.d.ts +7 -0
  455. package/mcp-server/dist/tools/tictactoe.d.ts.map +1 -0
  456. package/mcp-server/dist/tools/tictactoe.js +11 -0
  457. package/mcp-server/dist/tools/tictactoe.js.map +1 -0
  458. package/mcp-server/dist/tools/utils.d.ts +19 -0
  459. package/mcp-server/dist/tools/utils.d.ts.map +1 -0
  460. package/mcp-server/dist/tools/utils.js +54 -0
  461. package/mcp-server/dist/tools/utils.js.map +1 -0
  462. package/mcp-server/dist/tools/whiteboard-format.d.ts +88 -0
  463. package/mcp-server/dist/tools/whiteboard-format.d.ts.map +1 -0
  464. package/mcp-server/dist/tools/whiteboard-format.js +337 -0
  465. package/mcp-server/dist/tools/whiteboard-format.js.map +1 -0
  466. package/mcp-server/dist/tools/whiteboard-tools.d.ts +326 -0
  467. package/mcp-server/dist/tools/whiteboard-tools.d.ts.map +1 -0
  468. package/mcp-server/dist/tools/whiteboard-tools.js +222 -0
  469. package/mcp-server/dist/tools/whiteboard-tools.js.map +1 -0
  470. package/mcp-server/dist/tools/whiteboard.d.ts +8 -0
  471. package/mcp-server/dist/tools/whiteboard.d.ts.map +1 -0
  472. package/mcp-server/dist/tools/whiteboard.js +16 -0
  473. package/mcp-server/dist/tools/whiteboard.js.map +1 -0
  474. package/mcp-server/dist/tools/youtube/analytics.d.ts +46 -0
  475. package/mcp-server/dist/tools/youtube/analytics.d.ts.map +1 -0
  476. package/mcp-server/dist/tools/youtube/analytics.js +83 -0
  477. package/mcp-server/dist/tools/youtube/analytics.js.map +1 -0
  478. package/mcp-server/dist/tools/youtube/dedup.d.ts +35 -0
  479. package/mcp-server/dist/tools/youtube/dedup.d.ts.map +1 -0
  480. package/mcp-server/dist/tools/youtube/dedup.js +56 -0
  481. package/mcp-server/dist/tools/youtube/dedup.js.map +1 -0
  482. package/mcp-server/dist/tools/youtube/download.d.ts +31 -0
  483. package/mcp-server/dist/tools/youtube/download.d.ts.map +1 -0
  484. package/mcp-server/dist/tools/youtube/download.js +30 -0
  485. package/mcp-server/dist/tools/youtube/download.js.map +1 -0
  486. package/mcp-server/dist/tools/youtube/helpers.d.ts +66 -0
  487. package/mcp-server/dist/tools/youtube/helpers.d.ts.map +1 -0
  488. package/mcp-server/dist/tools/youtube/helpers.js +143 -0
  489. package/mcp-server/dist/tools/youtube/helpers.js.map +1 -0
  490. package/mcp-server/dist/tools/youtube/media.d.ts +43 -0
  491. package/mcp-server/dist/tools/youtube/media.d.ts.map +1 -0
  492. package/mcp-server/dist/tools/youtube/media.js +97 -0
  493. package/mcp-server/dist/tools/youtube/media.js.map +1 -0
  494. package/mcp-server/dist/tools/youtube/metadata.d.ts +33 -0
  495. package/mcp-server/dist/tools/youtube/metadata.d.ts.map +1 -0
  496. package/mcp-server/dist/tools/youtube/metadata.js +106 -0
  497. package/mcp-server/dist/tools/youtube/metadata.js.map +1 -0
  498. package/mcp-server/dist/tools/youtube/navigate.d.ts +30 -0
  499. package/mcp-server/dist/tools/youtube/navigate.d.ts.map +1 -0
  500. package/mcp-server/dist/tools/youtube/navigate.js +71 -0
  501. package/mcp-server/dist/tools/youtube/navigate.js.map +1 -0
  502. package/mcp-server/dist/tools/youtube/playlist.d.ts +29 -0
  503. package/mcp-server/dist/tools/youtube/playlist.d.ts.map +1 -0
  504. package/mcp-server/dist/tools/youtube/playlist.js +128 -0
  505. package/mcp-server/dist/tools/youtube/playlist.js.map +1 -0
  506. package/mcp-server/dist/tools/youtube/schedule.d.ts +31 -0
  507. package/mcp-server/dist/tools/youtube/schedule.d.ts.map +1 -0
  508. package/mcp-server/dist/tools/youtube/schedule.js +147 -0
  509. package/mcp-server/dist/tools/youtube/schedule.js.map +1 -0
  510. package/mcp-server/dist/tools/youtube/source.d.ts +36 -0
  511. package/mcp-server/dist/tools/youtube/source.d.ts.map +1 -0
  512. package/mcp-server/dist/tools/youtube/source.js +85 -0
  513. package/mcp-server/dist/tools/youtube/source.js.map +1 -0
  514. package/mcp-server/dist/tools/youtube/upload.d.ts +53 -0
  515. package/mcp-server/dist/tools/youtube/upload.d.ts.map +1 -0
  516. package/mcp-server/dist/tools/youtube/upload.js +255 -0
  517. package/mcp-server/dist/tools/youtube/upload.js.map +1 -0
  518. package/mcp-server/dist/tools/youtube.d.ts +12 -0
  519. package/mcp-server/dist/tools/youtube.d.ts.map +1 -0
  520. package/mcp-server/dist/tools/youtube.js +38 -0
  521. package/mcp-server/dist/tools/youtube.js.map +1 -0
  522. package/mcp-server/dist/tui.d.ts +3 -0
  523. package/mcp-server/dist/tui.d.ts.map +1 -0
  524. package/mcp-server/dist/tui.js +806 -0
  525. package/mcp-server/dist/tui.js.map +1 -0
  526. package/mcp-server/dist/types.d.ts +77 -0
  527. package/mcp-server/dist/types.d.ts.map +1 -0
  528. package/mcp-server/dist/types.js +2 -0
  529. package/mcp-server/dist/types.js.map +1 -0
  530. package/mcp-server/package-lock.json +4798 -0
  531. package/mcp-server/package.json +55 -38
  532. package/mcp-server/run.mjs +12 -2
  533. package/mcp-server/src/config.ts +48 -3
  534. package/mcp-server/src/http.ts +296 -93
  535. package/mcp-server/src/index.ts +546 -203
  536. package/mcp-server/src/preload-http.ts +19 -18
  537. package/mcp-server/src/preload.ts +19 -18
  538. package/mcp-server/src/scripts/setup-embeddings.ts +9 -0
  539. package/mcp-server/src/services/assistant-judgments.ts +157 -0
  540. package/mcp-server/src/services/browser-assist-gate.ts +383 -0
  541. package/mcp-server/src/services/browser-assist.ts +267 -0
  542. package/mcp-server/src/services/browser-bench.ts +477 -0
  543. package/mcp-server/src/services/browser-judgments.ts +414 -0
  544. package/mcp-server/src/services/browser-risk.ts +484 -0
  545. package/mcp-server/src/services/categories.ts +36 -19
  546. package/mcp-server/src/services/codex-config-heal.ts +403 -0
  547. package/mcp-server/src/services/desktop-assist-gate.ts +251 -0
  548. package/mcp-server/src/services/desktop-assist.ts +308 -0
  549. package/mcp-server/src/services/desktop-bench.ts +372 -0
  550. package/mcp-server/src/services/desktop-client.ts +76 -0
  551. package/mcp-server/src/services/desktop-judgments.ts +218 -0
  552. package/mcp-server/src/services/desktop-risk.ts +743 -0
  553. package/mcp-server/src/services/edit-stale.ts +299 -0
  554. package/mcp-server/src/services/embedding-service.ts +108 -0
  555. package/mcp-server/src/services/embedding-worker.ts +46 -0
  556. package/mcp-server/src/services/fb-regions.ts +221 -0
  557. package/mcp-server/src/services/file-checksums.ts +26 -9
  558. package/mcp-server/src/services/file-excerpt.ts +59 -0
  559. package/mcp-server/src/services/identity.ts +291 -0
  560. package/mcp-server/src/services/local-embeddings.ts +2 -77
  561. package/mcp-server/src/services/memory-client.ts +23 -0
  562. package/mcp-server/src/services/memory-judge-backfill.ts +462 -0
  563. package/mcp-server/src/services/memory-judgments.ts +901 -0
  564. package/mcp-server/src/services/memory-maintenance.ts +360 -0
  565. package/mcp-server/src/services/memory-retrieval.ts +349 -0
  566. package/mcp-server/src/services/memory-schema.ts +280 -0
  567. package/mcp-server/src/services/memory-sessions.ts +87 -0
  568. package/mcp-server/src/services/memory-triage.ts +182 -0
  569. package/mcp-server/src/services/memory-verification.ts +68 -0
  570. package/mcp-server/src/services/memory-writer.ts +125 -0
  571. package/mcp-server/src/services/neural-interface.ts +1217 -73
  572. package/mcp-server/src/services/profiles.ts +428 -0
  573. package/mcp-server/src/services/secret-gate.ts +52 -0
  574. package/mcp-server/src/services/server-instructions.ts +122 -0
  575. package/mcp-server/src/services/sqlite.ts +1456 -823
  576. package/mcp-server/src/services/typesafe-bench.ts +247 -0
  577. package/mcp-server/src/services/typesafe-config.ts +790 -0
  578. package/mcp-server/src/services/typesafe.ts +390 -0
  579. package/mcp-server/src/services/vector-cache.ts +255 -0
  580. package/mcp-server/src/services/whiteboard-geometry.ts +711 -0
  581. package/mcp-server/src/tools/agents.ts +539 -0
  582. package/mcp-server/src/tools/bluesky.ts +1186 -0
  583. package/mcp-server/src/tools/browser-advanced.ts +15 -6
  584. package/mcp-server/src/tools/browser-batch.ts +178 -0
  585. package/mcp-server/src/tools/browser-cheatsheet.ts +155 -0
  586. package/mcp-server/src/tools/browser-interact.ts +362 -255
  587. package/mcp-server/src/tools/browser-navigate.ts +164 -77
  588. package/mcp-server/src/tools/browser-observe.ts +2222 -1330
  589. package/mcp-server/src/tools/browser.ts +181 -107
  590. package/mcp-server/src/tools/card.ts +7 -5
  591. package/mcp-server/src/tools/choice.ts +103 -0
  592. package/mcp-server/src/tools/computer.ts +190 -0
  593. package/mcp-server/src/tools/discord.ts +10 -23
  594. package/mcp-server/src/tools/fb-groups.ts +387 -0
  595. package/mcp-server/src/tools/forget.ts +1 -1
  596. package/mcp-server/src/tools/git.ts +3 -1
  597. package/mcp-server/src/tools/gsc/experience.ts +440 -0
  598. package/mcp-server/src/tools/gsc/helpers.ts +288 -0
  599. package/mcp-server/src/tools/gsc/indexing.ts +495 -0
  600. package/mcp-server/src/tools/gsc/inspect.ts +391 -0
  601. package/mcp-server/src/tools/gsc/navigate.ts +236 -0
  602. package/mcp-server/src/tools/gsc/performance.ts +352 -0
  603. package/mcp-server/src/tools/gsc/settings.ts +564 -0
  604. package/mcp-server/src/tools/gsc.ts +95 -0
  605. package/mcp-server/src/tools/image.ts +3 -1
  606. package/mcp-server/src/tools/leonardo-browser-tools.ts +47 -37
  607. package/mcp-server/src/tools/leonardo.ts +7 -5
  608. package/mcp-server/src/tools/loop.ts +118 -25
  609. package/mcp-server/src/tools/memories.ts +65 -5
  610. package/mcp-server/src/tools/morelogin.ts +81 -0
  611. package/mcp-server/src/tools/profile.ts +122 -0
  612. package/mcp-server/src/tools/recall.ts +105 -261
  613. package/mcp-server/src/tools/reflect.ts +171 -145
  614. package/mcp-server/src/tools/remember.ts +223 -115
  615. package/mcp-server/src/tools/style-guide-tools.ts +250 -0
  616. package/mcp-server/src/tools/style-guide.ts +16 -0
  617. package/mcp-server/src/tools/sync.ts +66 -9
  618. package/mcp-server/src/tools/tictactoe.ts +3 -1
  619. package/mcp-server/src/tools/utils.ts +22 -0
  620. package/mcp-server/src/tools/whiteboard-format.ts +394 -0
  621. package/mcp-server/src/tools/whiteboard-tools.ts +191 -183
  622. package/mcp-server/src/tools/whiteboard.ts +7 -5
  623. package/mcp-server/src/tools/youtube/analytics.ts +106 -0
  624. package/mcp-server/src/tools/youtube/dedup.ts +57 -0
  625. package/mcp-server/src/tools/youtube/download.ts +41 -0
  626. package/mcp-server/src/tools/youtube/helpers.ts +183 -0
  627. package/mcp-server/src/tools/youtube/media.ts +109 -0
  628. package/mcp-server/src/tools/youtube/metadata.ts +112 -0
  629. package/mcp-server/src/tools/youtube/navigate.ts +81 -0
  630. package/mcp-server/src/tools/youtube/playlist.ts +134 -0
  631. package/mcp-server/src/tools/youtube/schedule.ts +158 -0
  632. package/mcp-server/src/tools/youtube/source.ts +101 -0
  633. package/mcp-server/src/tools/youtube/upload.ts +296 -0
  634. package/mcp-server/src/tools/youtube.ts +47 -0
  635. package/mcp-server/src/tui.ts +8 -9
  636. package/neural-interface/games/TicTacToe/1.svg +1 -0
  637. package/neural-interface/games/TicTacToe/2.svg +1 -0
  638. package/neural-interface/games/TicTacToe/3.svg +1 -0
  639. package/neural-interface/games/TicTacToe/4.svg +1 -0
  640. package/neural-interface/games/TicTacToe/5.svg +1 -0
  641. package/neural-interface/games/TicTacToe/6.svg +1 -0
  642. package/neural-interface/games/TicTacToe/7.svg +1 -0
  643. package/neural-interface/games/TicTacToe/8.svg +1 -0
  644. package/neural-interface/games/TicTacToe/9.svg +1 -0
  645. package/neural-interface/games/TicTacToe/Board.svg +1 -0
  646. package/neural-interface/games/TicTacToe/Circle.svg +1 -0
  647. package/neural-interface/games/TicTacToe/Cross.svg +1 -0
  648. package/neural-interface/i18n/en.json +7022 -0
  649. package/neural-interface/i18n/pt-BR.json +7026 -0
  650. package/neural-interface/lib/assistant-api.js +1005 -0
  651. package/neural-interface/lib/assistant-attachments.js +121 -0
  652. package/neural-interface/lib/assistant-brains/claude.js +207 -0
  653. package/neural-interface/lib/assistant-brains/codex-browser-policy-hook.mjs +43 -0
  654. package/neural-interface/lib/assistant-brains/codex-route-gate-hook.mjs +56 -0
  655. package/neural-interface/lib/assistant-brains/codex.js +213 -0
  656. package/neural-interface/lib/assistant-brains/opencode-browser-policy.js +47 -0
  657. package/neural-interface/lib/assistant-brains/opencode-route-gate.js +62 -0
  658. package/neural-interface/lib/assistant-brains/opencode.js +320 -0
  659. package/neural-interface/lib/assistant-budget.js +318 -0
  660. package/neural-interface/lib/assistant-catalog.js +731 -0
  661. package/neural-interface/lib/assistant-clarify.js +664 -0
  662. package/neural-interface/lib/assistant-config.js +232 -0
  663. package/neural-interface/lib/assistant-dispatch.js +2744 -0
  664. package/neural-interface/lib/assistant-envelope.js +582 -0
  665. package/neural-interface/lib/assistant-evidence.js +236 -0
  666. package/neural-interface/lib/assistant-hidden-models.js +272 -0
  667. package/neural-interface/lib/assistant-jev.js +117 -0
  668. package/neural-interface/lib/assistant-judgments.js +111 -0
  669. package/neural-interface/lib/assistant-media.js +335 -0
  670. package/neural-interface/lib/assistant-memory.js +398 -0
  671. package/neural-interface/lib/assistant-persona.js +444 -0
  672. package/neural-interface/lib/assistant-plan-permissions.js +448 -0
  673. package/neural-interface/lib/assistant-playbooks/design.md +87 -0
  674. package/neural-interface/lib/assistant-playbooks.js +72 -0
  675. package/neural-interface/lib/assistant-pricing.js +159 -0
  676. package/neural-interface/lib/assistant-route-gate-codex.js +163 -0
  677. package/neural-interface/lib/assistant-route-gate.js +212 -0
  678. package/neural-interface/lib/assistant-router.js +1324 -0
  679. package/neural-interface/lib/assistant-runtime.js +3337 -0
  680. package/neural-interface/lib/assistant-task-prompt.js +472 -0
  681. package/neural-interface/lib/assistant-usage.js +1688 -0
  682. package/neural-interface/lib/augmented-path.js +70 -0
  683. package/neural-interface/lib/automation-storage-migration.js +54 -0
  684. package/neural-interface/lib/backup-service.js +851 -0
  685. package/neural-interface/lib/backup-zip-reader.js +262 -0
  686. package/neural-interface/lib/browser-assist-api.js +41 -0
  687. package/neural-interface/lib/browser-console.js +176 -0
  688. package/neural-interface/lib/browser-execution.js +241 -0
  689. package/neural-interface/lib/browser-extraction.js +191 -0
  690. package/neural-interface/lib/browser-language.js +83 -0
  691. package/neural-interface/lib/browser-screenshot.js +399 -0
  692. package/neural-interface/lib/browser-semantic-context.js +639 -0
  693. package/neural-interface/lib/browser-snapshots.js +192 -0
  694. package/neural-interface/lib/browser-tab-recovery.js +119 -0
  695. package/neural-interface/lib/browser-targets.js +70 -0
  696. package/neural-interface/lib/browser-tool-policy.js +926 -0
  697. package/neural-interface/lib/claude-accounts.js +230 -0
  698. package/neural-interface/lib/claude-agent-bridge.js +3203 -0
  699. package/neural-interface/lib/claude-bypass-policy.js +99 -0
  700. package/neural-interface/lib/claude-executable.js +241 -0
  701. package/neural-interface/lib/claude-history.js +429 -0
  702. package/neural-interface/lib/claude-model-catalog.js +444 -0
  703. package/neural-interface/lib/claude-panel-requests.js +165 -0
  704. package/neural-interface/lib/claude-panel-session.js +272 -0
  705. package/neural-interface/lib/claude-permission-rules.js +153 -0
  706. package/neural-interface/lib/claude-session-cleanup.js +71 -0
  707. package/neural-interface/lib/claude-session-list.js +82 -0
  708. package/neural-interface/lib/claude-session-ops.js +140 -0
  709. package/neural-interface/lib/claude-settings-view.js +51 -0
  710. package/neural-interface/lib/claude-skin-connect.js +79 -0
  711. package/neural-interface/lib/claude-stall-policy.js +7 -0
  712. package/neural-interface/lib/claude-temporary.js +435 -0
  713. package/neural-interface/lib/cli-update-planner.js +312 -0
  714. package/neural-interface/lib/codex-capabilities.d.ts +41 -0
  715. package/neural-interface/lib/codex-capabilities.js +148 -0
  716. package/neural-interface/lib/codex-collaboration-mode.js +17 -0
  717. package/neural-interface/lib/codex-compat-rpc.js +241 -0
  718. package/neural-interface/lib/codex-mcp-approvals.js +56 -0
  719. package/neural-interface/lib/codex-mcp-apps.js +47 -0
  720. package/neural-interface/lib/codex-model-context.js +202 -0
  721. package/neural-interface/lib/codex-plan-store.js +71 -0
  722. package/neural-interface/lib/codex-request-journal.js +242 -0
  723. package/neural-interface/lib/codex-runtime-path.js +213 -0
  724. package/neural-interface/lib/codex-session-catalog.js +231 -0
  725. package/neural-interface/lib/codex-session-settings.js +46 -0
  726. package/neural-interface/lib/codex-sidepanel-permissions.js +110 -0
  727. package/neural-interface/lib/codex-thread-history.js +165 -0
  728. package/neural-interface/lib/codex-user-verification.js +34 -0
  729. package/neural-interface/lib/codex-writer-lifecycle.js +152 -0
  730. package/neural-interface/lib/confined-fs.js +256 -0
  731. package/neural-interface/lib/db.js +922 -0
  732. package/neural-interface/lib/design-md.js +9 -0
  733. package/neural-interface/lib/desktop/api.js +92 -0
  734. package/neural-interface/lib/desktop/audit.js +62 -0
  735. package/neural-interface/lib/desktop/build.js +423 -0
  736. package/neural-interface/lib/desktop/config.js +606 -0
  737. package/neural-interface/lib/desktop/coords.js +76 -0
  738. package/neural-interface/lib/desktop/fake-helper.js +1192 -0
  739. package/neural-interface/lib/desktop/frames.js +77 -0
  740. package/neural-interface/lib/desktop/grants.js +84 -0
  741. package/neural-interface/lib/desktop/guards.js +170 -0
  742. package/neural-interface/lib/desktop/helper/SynabunDesktop.swift +3056 -0
  743. package/neural-interface/lib/desktop/lease.js +51 -0
  744. package/neural-interface/lib/desktop/manager.js +545 -0
  745. package/neural-interface/lib/desktop/press-contexts.js +103 -0
  746. package/neural-interface/lib/desktop/protocol.js +495 -0
  747. package/neural-interface/lib/desktop/service.js +1202 -0
  748. package/neural-interface/lib/effort-levels.js +148 -0
  749. package/neural-interface/lib/event-loop-monitor.js +67 -0
  750. package/neural-interface/lib/exec-loop-command.js +209 -0
  751. package/neural-interface/lib/folder-picker.ps1 +77 -0
  752. package/neural-interface/lib/http-guards.js +287 -0
  753. package/neural-interface/lib/jsonl-lines.js +94 -0
  754. package/neural-interface/lib/loop-logger.js +127 -0
  755. package/neural-interface/lib/mcp-client-config.js +133 -0
  756. package/neural-interface/lib/mcp-installer.js +487 -0
  757. package/neural-interface/lib/mcp-profile-refresh-coordinator.js +78 -0
  758. package/neural-interface/lib/mcp-profile-servers.js +120 -0
  759. package/neural-interface/lib/memo-cache.js +84 -0
  760. package/neural-interface/lib/memory-api.js +758 -0
  761. package/neural-interface/lib/memory-map-api.js +1150 -0
  762. package/neural-interface/lib/memory-map-layout.js +872 -0
  763. package/neural-interface/lib/memory-map-worker.js +165 -0
  764. package/neural-interface/lib/memory-session-capture.js +81 -0
  765. package/neural-interface/lib/memory-timers.js +62 -0
  766. package/neural-interface/lib/morelogin.js +301 -0
  767. package/neural-interface/lib/native-binary-runtime.js +382 -0
  768. package/neural-interface/lib/native-loop-goal.js +33 -0
  769. package/neural-interface/lib/native-loop-providers.js +1002 -0
  770. package/neural-interface/lib/native-loop-runtime.js +747 -0
  771. package/neural-interface/lib/offline-page.js +54 -0
  772. package/neural-interface/lib/opencode-hidden-models.js +113 -0
  773. package/neural-interface/lib/opencode-history.js +253 -0
  774. package/neural-interface/lib/opencode-runtime-config.js +58 -0
  775. package/neural-interface/lib/opencode-v2-client.js +486 -0
  776. package/neural-interface/lib/opencode-v2-ws-requests.js +955 -0
  777. package/neural-interface/lib/path-confine.js +213 -0
  778. package/neural-interface/lib/plugin-installer.js +264 -0
  779. package/neural-interface/lib/project-storage.js +766 -0
  780. package/neural-interface/lib/pty-host/core.js +603 -0
  781. package/neural-interface/lib/pty-host/flow.js +43 -0
  782. package/neural-interface/lib/pty-host/host.js +47 -0
  783. package/neural-interface/lib/pty-host/manager.js +360 -0
  784. package/neural-interface/lib/pty-host/protocol.js +81 -0
  785. package/neural-interface/lib/pty-host/snapshot.js +133 -0
  786. package/neural-interface/lib/remote-policy.js +582 -0
  787. package/neural-interface/lib/rulesets/api.js +175 -0
  788. package/neural-interface/lib/rulesets/fs-safe.js +245 -0
  789. package/neural-interface/lib/rulesets/installer.js +1453 -0
  790. package/neural-interface/lib/rulesets/json-edit.js +186 -0
  791. package/neural-interface/lib/rulesets/lock.js +170 -0
  792. package/neural-interface/lib/rulesets/managed-text.js +314 -0
  793. package/neural-interface/lib/rulesets/normalise.js +24 -0
  794. package/neural-interface/lib/rulesets/render.js +84 -0
  795. package/neural-interface/lib/schedule-launch-intent.js +40 -0
  796. package/neural-interface/lib/session-cache-builder.js +524 -0
  797. package/neural-interface/lib/session-chunker.js +344 -0
  798. package/neural-interface/lib/session-indexer.js +327 -0
  799. package/neural-interface/lib/session-title-generator.js +232 -0
  800. package/neural-interface/lib/skill-prompt-bridge.js +11 -0
  801. package/neural-interface/lib/style-guide/api.js +375 -0
  802. package/neural-interface/lib/style-guide/color.js +352 -0
  803. package/neural-interface/lib/style-guide/export-css.js +99 -0
  804. package/neural-interface/lib/style-guide/export-dtcg.js +209 -0
  805. package/neural-interface/lib/style-guide/export-tailwind.js +102 -0
  806. package/neural-interface/lib/style-guide/fonts.json +227 -0
  807. package/neural-interface/lib/style-guide/import.js +1268 -0
  808. package/neural-interface/lib/style-guide/pointers.js +103 -0
  809. package/neural-interface/lib/style-guide/presets.js +232 -0
  810. package/neural-interface/lib/style-guide/render-design-md.js +319 -0
  811. package/neural-interface/lib/style-guide/schema.js +848 -0
  812. package/neural-interface/lib/style-guide/security.js +43 -0
  813. package/neural-interface/lib/style-guide/store.js +491 -0
  814. package/neural-interface/lib/style-guide/summary.js +106 -0
  815. package/neural-interface/lib/style-guide/tokens.js +170 -0
  816. package/neural-interface/lib/synabun-update-plan.js +324 -0
  817. package/neural-interface/lib/synabun-version.js +179 -0
  818. package/neural-interface/lib/system-restore.js +99 -0
  819. package/neural-interface/lib/terminal-env.js +86 -0
  820. package/neural-interface/lib/ui-blob-store.js +106 -0
  821. package/neural-interface/lib/vector-cache.js +2 -0
  822. package/neural-interface/lib/virtual-socket.js +60 -0
  823. package/neural-interface/lib/whatsapp/api.js +146 -0
  824. package/neural-interface/lib/whatsapp/auth-store.js +389 -0
  825. package/neural-interface/lib/whatsapp/baileys-adapter.js +457 -0
  826. package/neural-interface/lib/whatsapp/brain.js +128 -0
  827. package/neural-interface/lib/whatsapp/bridge.js +1499 -0
  828. package/neural-interface/lib/whatsapp/cards.js +519 -0
  829. package/neural-interface/lib/whatsapp/commands.js +92 -0
  830. package/neural-interface/lib/whatsapp/config.js +291 -0
  831. package/neural-interface/lib/whatsapp/connector/entry.mjs +25 -0
  832. package/neural-interface/lib/whatsapp/connector/manifest.json +6 -0
  833. package/neural-interface/lib/whatsapp/connector/npm-shrinkwrap.json +717 -0
  834. package/neural-interface/lib/whatsapp/connector/package.json +8 -0
  835. package/neural-interface/lib/whatsapp/fake-baileys.js +444 -0
  836. package/neural-interface/lib/whatsapp/format.js +552 -0
  837. package/neural-interface/lib/whatsapp/host-core.js +1225 -0
  838. package/neural-interface/lib/whatsapp/host.js +119 -0
  839. package/neural-interface/lib/whatsapp/identity.js +288 -0
  840. package/neural-interface/lib/whatsapp/inbound.js +149 -0
  841. package/neural-interface/lib/whatsapp/installer.js +562 -0
  842. package/neural-interface/lib/whatsapp/manager.js +638 -0
  843. package/neural-interface/lib/whatsapp/paths.js +81 -0
  844. package/neural-interface/lib/whatsapp/protocol.js +327 -0
  845. package/neural-interface/lib/whatsapp/qr-svg.js +57 -0
  846. package/neural-interface/lib/whatsapp/redact.js +130 -0
  847. package/neural-interface/lib/whatsapp/service.js +1305 -0
  848. package/neural-interface/lib/whatsapp/vendor/qrcodegen.js +848 -0
  849. package/neural-interface/package-lock.json +4431 -0
  850. package/neural-interface/package.json +58 -27
  851. package/neural-interface/public/apple-touch-icon.png +0 -0
  852. package/neural-interface/public/category-logos/synabun.png +0 -0
  853. package/neural-interface/public/claude.svg +1 -0
  854. package/neural-interface/public/favicon-16x16.png +0 -0
  855. package/neural-interface/public/favicon-32x32.png +0 -0
  856. package/neural-interface/public/icon-192.png +0 -0
  857. package/neural-interface/public/icon-512.png +0 -0
  858. package/neural-interface/public/index.html +32 -182
  859. package/neural-interface/public/index2d.html +2 -2
  860. package/neural-interface/public/install-app.html +31 -0
  861. package/neural-interface/public/logo-synabun.png +0 -0
  862. package/neural-interface/public/logoHD.png +0 -0
  863. package/neural-interface/public/manifest.json +2 -0
  864. package/neural-interface/public/mcp.svg +4 -0
  865. package/neural-interface/public/offline.html +670 -299
  866. package/neural-interface/public/onboarding.html +804 -161
  867. package/neural-interface/public/opencode-logo-dark-square.svg +1 -0
  868. package/neural-interface/public/shared/agent-runtime-options.js +596 -0
  869. package/neural-interface/public/shared/api.js +339 -28
  870. package/neural-interface/public/shared/assistant/asst-accounts.js +318 -0
  871. package/neural-interface/public/shared/assistant/asst-agents-dock.js +296 -0
  872. package/neural-interface/public/shared/assistant/asst-api.js +270 -0
  873. package/neural-interface/public/shared/assistant/asst-brain-picker.js +744 -0
  874. package/neural-interface/public/shared/assistant/asst-budget.js +333 -0
  875. package/neural-interface/public/shared/assistant/asst-composer.js +656 -0
  876. package/neural-interface/public/shared/assistant/asst-computer.js +803 -0
  877. package/neural-interface/public/shared/assistant/asst-control.js +1018 -0
  878. package/neural-interface/public/shared/assistant/asst-hosts.js +262 -0
  879. package/neural-interface/public/shared/assistant/asst-mascot.js +313 -0
  880. package/neural-interface/public/shared/assistant/asst-menu.js +375 -0
  881. package/neural-interface/public/shared/assistant/asst-panel.js +1845 -0
  882. package/neural-interface/public/shared/assistant/asst-rack-styles.js +586 -0
  883. package/neural-interface/public/shared/assistant/asst-render.js +3305 -0
  884. package/neural-interface/public/shared/assistant/asst-route.js +1653 -0
  885. package/neural-interface/public/shared/assistant/asst-roving.js +75 -0
  886. package/neural-interface/public/shared/assistant/asst-sanitize.js +117 -0
  887. package/neural-interface/public/shared/assistant/asst-sidepanel-styles.js +488 -0
  888. package/neural-interface/public/shared/assistant/asst-sidepanel.js +675 -0
  889. package/neural-interface/public/shared/assistant/asst-slash.js +258 -0
  890. package/neural-interface/public/shared/assistant/asst-state.js +727 -0
  891. package/neural-interface/public/shared/assistant/asst-styles.js +1686 -0
  892. package/neural-interface/public/shared/assistant/asst-tool-kinds.js +1017 -0
  893. package/neural-interface/public/shared/assistant/asst-usage.js +444 -0
  894. package/neural-interface/public/shared/assistant/asst-ws.js +175 -0
  895. package/neural-interface/public/shared/automation-context.js +75 -0
  896. package/neural-interface/public/shared/blob-store.js +99 -0
  897. package/neural-interface/public/shared/cdx/cdx-capabilities.js +118 -0
  898. package/neural-interface/public/shared/cdx/cdx-cards.js +37 -0
  899. package/neural-interface/public/shared/cdx/cdx-commands.js +171 -0
  900. package/neural-interface/public/shared/cdx/cdx-context-menu.js +402 -0
  901. package/neural-interface/public/shared/cdx/cdx-context-model.js +220 -0
  902. package/neural-interface/public/shared/cdx/cdx-features.js +408 -0
  903. package/neural-interface/public/shared/cdx/cdx-icons.js +89 -0
  904. package/neural-interface/public/shared/cdx/cdx-mcp-app-host.js +414 -0
  905. package/neural-interface/public/shared/cdx/cdx-mcp-app-proxy.html +6 -0
  906. package/neural-interface/public/shared/cdx/cdx-mcp-app-proxy.js +56 -0
  907. package/neural-interface/public/shared/cdx/cdx-mcp-app.js +178 -0
  908. package/neural-interface/public/shared/cdx/cdx-model-catalog.js +120 -0
  909. package/neural-interface/public/shared/cdx/cdx-output-model.js +261 -0
  910. package/neural-interface/public/shared/cdx/cdx-output.js +382 -0
  911. package/neural-interface/public/shared/cdx/cdx-panel.js +1372 -0
  912. package/neural-interface/public/shared/cdx/cdx-plan.js +266 -0
  913. package/neural-interface/public/shared/cdx/cdx-protocol.js +682 -0
  914. package/neural-interface/public/shared/cdx/cdx-render.js +3381 -0
  915. package/neural-interface/public/shared/cdx/cdx-requests.js +1136 -0
  916. package/neural-interface/public/shared/cdx/cdx-styles.js +3251 -0
  917. package/neural-interface/public/shared/cdx/cdx-tabs.js +7992 -0
  918. package/neural-interface/public/shared/cdx/cdx-ws.js +363 -0
  919. package/neural-interface/public/shared/cli-status.js +169 -0
  920. package/neural-interface/public/shared/command-runner-model.js +30 -0
  921. package/neural-interface/public/shared/constants.js +22 -5
  922. package/neural-interface/public/shared/cp/cp-agents.js +186 -0
  923. package/neural-interface/public/shared/cp/cp-bash.js +252 -0
  924. package/neural-interface/public/shared/cp/cp-compose.js +159 -0
  925. package/neural-interface/public/shared/cp/cp-context-menu.js +309 -0
  926. package/neural-interface/public/shared/cp/cp-context-model.js +157 -0
  927. package/neural-interface/public/shared/cp/cp-context-window.js +80 -0
  928. package/neural-interface/public/shared/cp/cp-ctx.js +11 -0
  929. package/neural-interface/public/shared/cp/cp-diff.js +255 -0
  930. package/neural-interface/public/shared/cp/cp-event-rows.js +637 -0
  931. package/neural-interface/public/shared/cp/cp-events.js +846 -0
  932. package/neural-interface/public/shared/cp/cp-markdown.js +104 -0
  933. package/neural-interface/public/shared/cp/cp-permission-model.js +905 -0
  934. package/neural-interface/public/shared/cp/cp-permissions.js +626 -0
  935. package/neural-interface/public/shared/cp/cp-rehydrate.js +113 -0
  936. package/neural-interface/public/shared/cp/cp-restore.js +224 -0
  937. package/neural-interface/public/shared/cp/cp-session-model.js +338 -0
  938. package/neural-interface/public/shared/cp/cp-session-settings.js +254 -0
  939. package/neural-interface/public/shared/cp/cp-sessions.js +227 -0
  940. package/neural-interface/public/shared/cp/cp-statusline.js +153 -0
  941. package/neural-interface/public/shared/cp/cp-styles.js +349 -0
  942. package/neural-interface/public/shared/cp/cp-tasks-model.js +282 -0
  943. package/neural-interface/public/shared/cp/cp-tasks.js +107 -0
  944. package/neural-interface/public/shared/cp/cp-temporary-view.js +56 -0
  945. package/neural-interface/public/shared/cp/cp-temporary.js +231 -0
  946. package/neural-interface/public/shared/cp/cp-tool-cards.js +326 -0
  947. package/neural-interface/public/shared/cp/cp-tool-results.js +618 -0
  948. package/neural-interface/public/shared/cp/cp-usage-model.js +124 -0
  949. package/neural-interface/public/shared/graph-activity.js +68 -0
  950. package/neural-interface/public/shared/html-shell.js +116 -17
  951. package/neural-interface/public/shared/i18n.js +132 -34
  952. package/neural-interface/public/shared/node-positions-store.js +44 -0
  953. package/neural-interface/public/shared/ocp-hidden-models.js +116 -0
  954. package/neural-interface/public/shared/ocp-v2/ocp-v2-approvals.js +355 -0
  955. package/neural-interface/public/shared/ocp-v2/ocp-v2-autoaccept-button.js +35 -0
  956. package/neural-interface/public/shared/ocp-v2/ocp-v2-automation-ownership.js +72 -0
  957. package/neural-interface/public/shared/ocp-v2/ocp-v2-binding.js +280 -0
  958. package/neural-interface/public/shared/ocp-v2/ocp-v2-caps.js +67 -0
  959. package/neural-interface/public/shared/ocp-v2/ocp-v2-changes-logic.js +179 -0
  960. package/neural-interface/public/shared/ocp-v2/ocp-v2-changes.js +164 -0
  961. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +725 -0
  962. package/neural-interface/public/shared/ocp-v2/ocp-v2-compact-button.js +75 -0
  963. package/neural-interface/public/shared/ocp-v2/ocp-v2-composer-logic.js +1204 -0
  964. package/neural-interface/public/shared/ocp-v2/ocp-v2-confirm-logic.js +117 -0
  965. package/neural-interface/public/shared/ocp-v2/ocp-v2-confirm.js +59 -0
  966. package/neural-interface/public/shared/ocp-v2/ocp-v2-context-gauge.js +252 -0
  967. package/neural-interface/public/shared/ocp-v2/ocp-v2-context-menu.js +478 -0
  968. package/neural-interface/public/shared/ocp-v2/ocp-v2-context-model.js +179 -0
  969. package/neural-interface/public/shared/ocp-v2/ocp-v2-events.js +233 -0
  970. package/neural-interface/public/shared/ocp-v2/ocp-v2-icons.js +30 -0
  971. package/neural-interface/public/shared/ocp-v2/ocp-v2-manager.js +1065 -0
  972. package/neural-interface/public/shared/ocp-v2/ocp-v2-mention-hints.js +182 -0
  973. package/neural-interface/public/shared/ocp-v2/ocp-v2-modelpicker.js +655 -0
  974. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +2636 -0
  975. package/neural-interface/public/shared/ocp-v2/ocp-v2-plan.js +601 -0
  976. package/neural-interface/public/shared/ocp-v2/ocp-v2-profile-inheritance.js +78 -0
  977. package/neural-interface/public/shared/ocp-v2/ocp-v2-projectbar.js +649 -0
  978. package/neural-interface/public/shared/ocp-v2/ocp-v2-providers.js +35 -0
  979. package/neural-interface/public/shared/ocp-v2/ocp-v2-rehydrate.js +357 -0
  980. package/neural-interface/public/shared/ocp-v2/ocp-v2-render-logic.js +189 -0
  981. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +1869 -0
  982. package/neural-interface/public/shared/ocp-v2/ocp-v2-select.js +42 -0
  983. package/neural-interface/public/shared/ocp-v2/ocp-v2-send-logic.js +117 -0
  984. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +1714 -0
  985. package/neural-interface/public/shared/ocp-v2/ocp-v2-session-actions.js +468 -0
  986. package/neural-interface/public/shared/ocp-v2/ocp-v2-session-widgets.js +63 -0
  987. package/neural-interface/public/shared/ocp-v2/ocp-v2-sessions-logic.js +366 -0
  988. package/neural-interface/public/shared/ocp-v2/ocp-v2-settings-link.js +19 -0
  989. package/neural-interface/public/shared/ocp-v2/ocp-v2-slash-hints.js +343 -0
  990. package/neural-interface/public/shared/ocp-v2/ocp-v2-state.js +746 -0
  991. package/neural-interface/public/shared/ocp-v2/ocp-v2-status-logic.js +347 -0
  992. package/neural-interface/public/shared/ocp-v2/ocp-v2-status.js +338 -0
  993. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +3543 -0
  994. package/neural-interface/public/shared/ocp-v2/ocp-v2-subagents-logic.js +482 -0
  995. package/neural-interface/public/shared/ocp-v2/ocp-v2-tools-logic.js +351 -0
  996. package/neural-interface/public/shared/ocp-v2/ocp-v2-tools.js +168 -0
  997. package/neural-interface/public/shared/ocp-v2/ocp-v2-variantpicker.js +315 -0
  998. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +545 -0
  999. package/neural-interface/public/shared/provider-icons.js +30 -0
  1000. package/neural-interface/public/shared/pwa-install-page.js +34 -0
  1001. package/neural-interface/public/shared/pwa-install.css +12 -0
  1002. package/neural-interface/public/shared/pwa-install.js +28 -0
  1003. package/neural-interface/public/shared/session-title.js +100 -0
  1004. package/neural-interface/public/shared/settings/settings-ia.js +389 -0
  1005. package/neural-interface/public/shared/settings/settings-kit.js +234 -0
  1006. package/neural-interface/public/shared/settings/settings-rules.js +160 -0
  1007. package/neural-interface/public/shared/settings/settings-search.js +463 -0
  1008. package/neural-interface/public/shared/settings/settings-shell.js +570 -0
  1009. package/neural-interface/public/shared/start-bridge.js +583 -0
  1010. package/neural-interface/public/shared/state.js +5 -0
  1011. package/neural-interface/public/shared/storage.js +50 -7
  1012. package/neural-interface/public/shared/styleguide/sg-agents.js +42 -0
  1013. package/neural-interface/public/shared/styleguide/sg-brand.js +4 -0
  1014. package/neural-interface/public/shared/styleguide/sg-color-client.js +388 -0
  1015. package/neural-interface/public/shared/styleguide/sg-colors.js +151 -0
  1016. package/neural-interface/public/shared/styleguide/sg-components.js +35 -0
  1017. package/neural-interface/public/shared/styleguide/sg-history.js +41 -0
  1018. package/neural-interface/public/shared/styleguide/sg-icons-logo.js +38 -0
  1019. package/neural-interface/public/shared/styleguide/sg-imagery.js +4 -0
  1020. package/neural-interface/public/shared/styleguide/sg-import-export.js +48 -0
  1021. package/neural-interface/public/shared/styleguide/sg-kit.js +284 -0
  1022. package/neural-interface/public/shared/styleguide/sg-labels.js +318 -0
  1023. package/neural-interface/public/shared/styleguide/sg-layout.js +4 -0
  1024. package/neural-interface/public/shared/styleguide/sg-motion.js +5 -0
  1025. package/neural-interface/public/shared/styleguide/sg-panel.js +237 -0
  1026. package/neural-interface/public/shared/styleguide/sg-preview.js +138 -0
  1027. package/neural-interface/public/shared/styleguide/sg-rules.js +9 -0
  1028. package/neural-interface/public/shared/styleguide/sg-shape.js +24 -0
  1029. package/neural-interface/public/shared/styleguide/sg-state.js +132 -0
  1030. package/neural-interface/public/shared/styleguide/sg-typography.js +125 -0
  1031. package/neural-interface/public/shared/styles.css +8614 -1477
  1032. package/neural-interface/public/shared/synabun-mascot.js +1625 -0
  1033. package/neural-interface/public/shared/synabun-ticker.js +376 -0
  1034. package/neural-interface/public/shared/term-ready.js +307 -0
  1035. package/neural-interface/public/shared/term-renderer.js +343 -0
  1036. package/neural-interface/public/shared/ui-assistant-panel.js +10 -0
  1037. package/neural-interface/public/shared/ui-assistant.js +77 -0
  1038. package/neural-interface/public/shared/ui-automation-studio.js +5105 -3573
  1039. package/neural-interface/public/shared/ui-claude-panel.js +11685 -6333
  1040. package/neural-interface/public/shared/ui-codex-panel.js +12 -0
  1041. package/neural-interface/public/shared/ui-command-runner.js +251 -468
  1042. package/neural-interface/public/shared/ui-cost-widget.js +55 -34
  1043. package/neural-interface/public/shared/ui-detail.js +77 -14
  1044. package/neural-interface/public/shared/ui-explorer.js +360 -181
  1045. package/neural-interface/public/shared/ui-file-explorer.js +485 -107
  1046. package/neural-interface/public/shared/ui-help.js +2 -2
  1047. package/neural-interface/public/shared/ui-image-gallery.js +45 -21
  1048. package/neural-interface/public/shared/ui-judgments.js +1346 -0
  1049. package/neural-interface/public/shared/ui-keybinds.js +3 -2
  1050. package/neural-interface/public/shared/ui-link.js +105 -51
  1051. package/neural-interface/public/shared/ui-loading.js +222 -11
  1052. package/neural-interface/public/shared/ui-memory-maintenance.js +111 -0
  1053. package/neural-interface/public/shared/ui-menubar.js +100 -31
  1054. package/neural-interface/public/shared/ui-native-loop-router-state.js +94 -0
  1055. package/neural-interface/public/shared/ui-native-loop-router.js +493 -0
  1056. package/neural-interface/public/shared/ui-native-window-id.js +7 -0
  1057. package/neural-interface/public/shared/ui-navbar.js +357 -222
  1058. package/neural-interface/public/shared/ui-notifications.js +58 -12
  1059. package/neural-interface/public/shared/ui-opencode-panel-v2.js +17 -0
  1060. package/neural-interface/public/shared/ui-panels.js +15 -7
  1061. package/neural-interface/public/shared/ui-resume.js +823 -653
  1062. package/neural-interface/public/shared/ui-schedule-queue.js +172 -0
  1063. package/neural-interface/public/shared/ui-schedules-studio.js +1882 -0
  1064. package/neural-interface/public/shared/ui-scroll-follow.js +194 -0
  1065. package/neural-interface/public/shared/ui-sessions.js +789 -34
  1066. package/neural-interface/public/shared/ui-settings.js +5774 -2194
  1067. package/neural-interface/public/shared/ui-sidepanel-layout.js +109 -0
  1068. package/neural-interface/public/shared/ui-sidepanel-runtime.js +19 -0
  1069. package/neural-interface/public/shared/ui-sidepanel-session-entry.js +40 -0
  1070. package/neural-interface/public/shared/ui-sidepanel-sessions.js +221 -0
  1071. package/neural-interface/public/shared/ui-sidepanel-state.js +131 -0
  1072. package/neural-interface/public/shared/ui-sidepanel-tray.js +547 -0
  1073. package/neural-interface/public/shared/ui-sidepanel-windows.js +387 -0
  1074. package/neural-interface/public/shared/ui-skills.js +18 -32
  1075. package/neural-interface/public/shared/ui-styleguide.js +35 -0
  1076. package/neural-interface/public/shared/ui-sync.js +87 -0
  1077. package/neural-interface/public/shared/ui-terminal.js +7863 -6385
  1078. package/neural-interface/public/shared/ui-tool-updates.js +292 -0
  1079. package/neural-interface/public/shared/ui-tooltip.js +156 -15
  1080. package/neural-interface/public/shared/ui-tutorial-draw.js +119 -683
  1081. package/neural-interface/public/shared/ui-tutorial-layout.js +135 -0
  1082. package/neural-interface/public/shared/ui-tutorial-steps.js +59 -2209
  1083. package/neural-interface/public/shared/ui-tutorial.css +102 -0
  1084. package/neural-interface/public/shared/ui-tutorial.js +557 -1076
  1085. package/neural-interface/public/shared/ui-update.js +420 -68
  1086. package/neural-interface/public/shared/ui-whatsapp.js +941 -0
  1087. package/neural-interface/public/shared/ui-whiteboard.js +1076 -233
  1088. package/neural-interface/public/shared/ui-workspaces.js +2 -1
  1089. package/neural-interface/public/shared/utils.js +58 -0
  1090. package/neural-interface/public/shared/vterm-buffer.js +15 -5
  1091. package/neural-interface/public/shared/whatsapp/wa-phone.js +65 -0
  1092. package/neural-interface/public/shared/whatsapp/wa-view.js +411 -0
  1093. package/neural-interface/public/sidepanel-session.html +18 -0
  1094. package/neural-interface/public/sw.js +10 -2
  1095. package/neural-interface/public/synabun.png +0 -0
  1096. package/neural-interface/public/variant/2d/graph.js +1707 -1635
  1097. package/neural-interface/public/variant/2d/lasso.js +2 -1
  1098. package/neural-interface/public/variant/2d/layout.js +595 -594
  1099. package/neural-interface/public/variant/2d/main.js +557 -521
  1100. package/neural-interface/public/variant/2d/settings-gfx.js +18 -24
  1101. package/neural-interface/public/variant/3d/main.js +517 -500
  1102. package/neural-interface/public/variant/3d/map-controls.js +144 -0
  1103. package/neural-interface/public/variant/3d/map-data.js +251 -0
  1104. package/neural-interface/public/variant/3d/map-labels.js +241 -0
  1105. package/neural-interface/public/variant/3d/map-pick.js +90 -0
  1106. package/neural-interface/public/variant/3d/map-renderer.js +521 -0
  1107. package/neural-interface/public/variant/3d/map-shaders.js +158 -0
  1108. package/neural-interface/public/variant/3d/map-view.js +618 -0
  1109. package/neural-interface/public/vendor/three/manifest.json +3 -0
  1110. package/neural-interface/public/vendor/three/three-0.169.0.min.js +3844 -0
  1111. package/neural-interface/public/vendor/xterm/manifest.json +3 -0
  1112. package/neural-interface/public/vendor/xterm/xterm-6.0.0.css +285 -0
  1113. package/neural-interface/public/vendor/xterm/xterm-6.0.0.min.js +192 -0
  1114. package/neural-interface/scripts/build-vendor.mjs +64 -0
  1115. package/neural-interface/scripts/codex-protocol-fixture.mjs +83 -0
  1116. package/neural-interface/scripts/rebuild-pty.js +39 -22
  1117. package/neural-interface/scripts/vendor-three-entry.js +24 -0
  1118. package/neural-interface/scripts/vendor-xterm-entry.js +8 -0
  1119. package/neural-interface/server.js +33983 -15000
  1120. package/neural-interface/skins/default/skin.css +90 -0
  1121. package/neural-interface/skins/default/skin.json +8 -0
  1122. package/neural-interface/templates/rulesets/README.md +9 -0
  1123. package/neural-interface/templates/rulesets/claude.md +3 -0
  1124. package/neural-interface/templates/rulesets/codex.md +2 -0
  1125. package/neural-interface/templates/rulesets/coexistence.md +6 -0
  1126. package/neural-interface/templates/rulesets/core.md +26 -0
  1127. package/neural-interface/templates/rulesets/cursor.md +2 -0
  1128. package/neural-interface/templates/rulesets/gemini.md +2 -0
  1129. package/neural-interface/templates/rulesets/legacy-hashes.json +227 -0
  1130. package/neural-interface/templates/rulesets/manifest.json +12 -0
  1131. package/neural-interface/templates/rulesets/opencode.md +2 -0
  1132. package/package.json +131 -53
  1133. package/postinstall.js +55 -26
  1134. package/preuninstall.js +187 -0
  1135. package/scripts/restore-data-home-from-backup.mjs +239 -0
  1136. package/setup.js +872 -260
  1137. package/skills/leonardo/SKILL.md +19 -10
  1138. package/skills/leonardo/modules/image-prompter.md +18 -12
  1139. package/skills/leonardo/modules/video-prompter.md +19 -13
  1140. package/skills/synabun/SKILL.md +209 -185
  1141. package/skills/synabun/icon.png +0 -0
  1142. package/skills/synabun/modules/audit.md +380 -374
  1143. package/skills/synabun/modules/changelog.md +15 -6
  1144. package/skills/synabun/modules/idea.md +148 -144
  1145. package/skills/synabun/modules/memorize.md +13 -4
  1146. package/skills/synabun/modules/schedule.md +198 -0
  1147. package/skills/synabun/synabunicon.png +0 -0
  1148. package/updater.cmd +31 -0
  1149. package/updater.mjs +382 -0
  1150. package/updater.sh +29 -0
  1151. package/hooks/claude-code/test-user-learning.mjs +0 -531
  1152. package/neural-interface/public/claude-chat.css +0 -878
  1153. package/neural-interface/public/claude-chat.html +0 -92
  1154. package/neural-interface/public/claude-chat.js +0 -1168
  1155. package/neural-interface/public/index-legacy.html +0 -14677
  1156. package/neural-interface/public/index2d-legacy.html +0 -8193
  1157. package/neural-interface/public/shared/html-term-renderer.js +0 -1022
  1158. package/neural-interface/public/variant/3d/background.js +0 -884
  1159. package/neural-interface/public/variant/3d/camera.js +0 -661
  1160. package/neural-interface/public/variant/3d/gfx.js +0 -116
  1161. package/neural-interface/public/variant/3d/graph.js +0 -2399
  1162. package/neural-interface/public/variant/3d/settings-gfx.js +0 -228
  1163. package/neural-interface/templates/CLAUDE-template.md +0 -211
@@ -0,0 +1,1845 @@
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun Assistant — panel (mount / lifecycle)
3
+ // ═══════════════════════════════════════════
4
+ // mountAssistant(viewport, { sessionId, brain, host }) builds the assistant
5
+ // UI inside a viewport owned by a host and returns the controller the host
6
+ // talks to. Two hosts mount this same component — the terminal tab
7
+ // (ui-terminal.js, `.term-viewport.assistant-viewport`) and the sidepanel
8
+ // (asst-sidepanel.js, `.asp-viewport`) — so every feature exists in both.
9
+ // Never branch on which host this is: `host.id` only rides along on the
10
+ // assistant:new / assistant:resume / assistant:focused events so New and
11
+ // History open in the same host (ui-assistant.js routes them), and
12
+ // `host.notifySource` picks the Settings → Notifications source.
13
+ // Layout: a flush toolbar (route mode · context · computer | agents · cost ·
14
+ // history · new · "⋯") · banners · transcript (role=log, not a live region:
15
+ // each rack speaks through its own status/alert, and the turn's answer and
16
+ // errors through one panel status) · bottom card
17
+ // (computer tray, agents tray, composer: input · attach · model · effort ·
18
+ // permission mode / send). `host.toolbarSlot` is placement only: a host that
19
+ // has its own header (the sidepanel) passes an element and the same toolbar,
20
+ // with the same wiring, lives there instead of at the top of the root. The tab is
21
+ // always called SynaBun unless the user renames it; the session's own title
22
+ // (first prompt) only names it in the history menu and in notifications.
23
+ // Notifies through ui-notifications when hidden. The mascot on screen (the
24
+ // empty state's hero, the live rack's stage) wears the panel's state through
25
+ // asst-mascot.js: watching you type, blocked on a card, offline, asleep, and a
26
+ // success or error when a turn that did real work ends.
27
+
28
+ import { emit, on, state as appState } from '../state.js';
29
+ import { storage } from '../storage.js';
30
+ import { KEYS } from '../constants.js';
31
+ import { t } from '../i18n.js';
32
+ import { notify, NOTIF_TYPE } from '../ui-notifications.js';
33
+ import { getProviderMeta } from '../provider-icons.js';
34
+ import { isGuest, hasPermission } from '../ui-sync.js';
35
+ import { getWhiteboardElementById } from '../ui-whiteboard.js';
36
+ import { focusNativeLoopRun } from '../ui-native-loop-router.js';
37
+ import { injectAssistantStyles } from './asst-styles.js';
38
+ import { createAssistantSocket } from './asst-ws.js';
39
+ import { createRenderer, ICON_ASSISTANT, mdInto } from './asst-render.js';
40
+ import { createMascotDirector, MASCOT_TIMING, turnEndPose } from './asst-mascot.js';
41
+ import { lastInputAt, onKey, onPointer } from '../synabun-ticker.js';
42
+ import { buildControlResponse, closedControlState, normalizeControlRequest, renderControlCard } from './asst-control.js';
43
+ import { buildPromptText, createComposer } from './asst-composer.js';
44
+ import { buildDispatchSpec, parseSlashCommand, slashHelpMarkdown } from './asst-slash.js';
45
+ import { createBrainPicker } from './asst-brain-picker.js';
46
+ import { createRovingToolbar } from './asst-roving.js';
47
+ import { openAccountsManager } from './asst-accounts.js';
48
+ import { createAgentsDock } from './asst-agents-dock.js';
49
+ import { createUsageGauge } from './asst-usage.js';
50
+ import { closeMenu, isMenuOpen, openMenu } from './asst-menu.js';
51
+ import { createRouteModeControl, openModelsManager, openRoutesEditor, refreshModelsManager, renderRouteCard, routeModeLabel } from './asst-route.js';
52
+ import { budgetChipText, budgetTone, openBudgetEditor } from './asst-budget.js';
53
+ import { computerRemoteView, createComputerPanel, createComputerToggle, normalizeComputerRemote, openFrameLightbox, closeFrameLightbox, requestDesktopStop } from './asst-computer.js';
54
+ import {
55
+ applyLimit, applyLimitPacket, applyUsagePacket, brainLabel, createTabState, DEFAULT_ROUTE_MODE, deserializeTabState, fmtCost, fmtRunLine, limitExpiresIn, limitNoticeText, modelShortName,
56
+ brainModePatch, normalizeBrain, normalizeComputerMode, permissionModeLabel, normalizeRouteMode, providerShortLabel, readStoredBrain, runFromPayload,
57
+ serializeTabState, tabStorageKey,
58
+ } from './asst-state.js';
59
+ import {
60
+ closeAssistantSession, dispatchAssistantRun, escalateAssistantRun, focusAssistantRun, getAssistantRun,
61
+ fetchManageCatalog, getAssistantRunResult, getAssistantSession, getAssistantSessionUsage, getDesktopStatus, getRouting, killAllAssistantRuns, removeAssistantRun, clearFinishedAssistantRuns,
62
+ listAssistantSessions, patchAssistantSession, patchHiddenModels, stopAssistantRun, uploadAssistantAttachment,
63
+ } from './asst-api.js';
64
+
65
+ const ICON_SESSIONS = '<svg viewBox="0 0 24 24"><path d="M3 6h18M3 12h18M3 18h12"/></svg>';
66
+ const ICON_AGENTS = '<svg viewBox="0 0 24 24"><circle cx="9" cy="8" r="3"/><circle cx="17" cy="9" r="2.5"/><path d="M3 19a6 6 0 0 1 12 0M14.5 19a4.5 4.5 0 0 1 7-3.7"/></svg>';
67
+ const ICON_NEW = '<svg viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></svg>';
68
+ const ICON_COG = '<svg viewBox="0 0 24 24"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/></svg>';
69
+ const ICON_ROUTE = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7h13l-3-3M20 17H7l3 3"/></svg>';
70
+ const ICON_COMPUTER = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="12" rx="2"/><path d="M8 20h8M12 16v4"/></svg>';
71
+ const ICON_CLOSE = '<svg viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>';
72
+ const REPLAY_WAIT_MS = 1500; // a replay that stops short of its count is drawn after this long
73
+
74
+ function el(tag, className, text) {
75
+ const node = document.createElement(tag);
76
+ if (className) node.className = className;
77
+ if (text != null) node.textContent = text;
78
+ return node;
79
+ }
80
+
81
+ function esc(value) {
82
+ return String(value ?? '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
83
+ }
84
+
85
+ function tt(key, fallback, params) {
86
+ const v = t(key, params);
87
+ if (v && v !== key && typeof v === 'string') return v;
88
+ return params ? String(fallback).replace(/\{(\w+)\}/g, (_, k) => (params[k] != null ? params[k] : `{${k}}`)) : fallback;
89
+ }
90
+
91
+ function num(value) {
92
+ const n = Number(value);
93
+ return value != null && value !== '' && Number.isFinite(n) ? n : null;
94
+ }
95
+
96
+ function basename(path) {
97
+ return String(path || '').split(/[\\/]/).filter(Boolean).pop() || String(path || '');
98
+ }
99
+
100
+ function relativeTime(value) {
101
+ const ms = new Date(value || 0).getTime();
102
+ if (!ms) return '';
103
+ const diff = Math.max(0, Date.now() - ms);
104
+ const mins = Math.floor(diff / 60000);
105
+ if (mins < 1) return tt('assistant.time.justNow', 'just now');
106
+ if (mins < 60) return `${mins}m`;
107
+ const hrs = Math.floor(mins / 60);
108
+ if (hrs < 24) return `${hrs}h`;
109
+ return `${Math.floor(hrs / 24)}d`;
110
+ }
111
+
112
+ function clipCopy(text) {
113
+ if (navigator.clipboard?.writeText) navigator.clipboard.writeText(text).catch(() => {});
114
+ }
115
+
116
+ /**
117
+ * mountAssistant(viewport, { sessionId, brain, host, session })
118
+ * host: { id: 'terminal'|'sidepanel', notifySource: 'cli'|'panel',
119
+ * setLabel(text), setStatus('idle'|'working'|'action'|'done'), setBrain(brain),
120
+ * isVisible() → bool, toolbarSlot?: Element (where the toolbar goes; default the root),
121
+ * takeHeroOrigin?() → DOMRect | null (a character the host showed while starting: the empty state's flies in from it) }
122
+ * The hooks may fire during mount, before this returns.
123
+ * The tab label is always "SynaBun"; a user rename arrives via controller.setTitle.
124
+ */
125
+ export function mountAssistant(viewport, { sessionId, brain, host = {}, session = null } = {}) {
126
+ injectAssistantStyles();
127
+ const saved = deserializeTabState(storage.getItem(tabStorageKey(sessionId)));
128
+ const st = createTabState(sessionId, brain || session?.brain || saved?.brain || readStoredBrain(storage), {
129
+ title: tt('assistant.defaultTitle', 'SynaBun'),
130
+ // The agents tray starts collapsed; it only expands on request.
131
+ dockOpen: saved ? saved.dockOpen : storage.getItem(KEYS.ASSISTANT_DOCK_OPEN) === '1',
132
+ dockShowAll: saved ? saved.dockShowAll : storage.getItem(KEYS.ASSISTANT_DOCK_SHOW_ALL) === '1',
133
+ draft: saved?.draft || '',
134
+ costUsd: Number(session?.costUsd) || saved?.costUsd || 0,
135
+ providerSessionId: session?.providerSessionId || null,
136
+ routeMode: saved?.routeMode ?? null,
137
+ computerUse: saved?.computerUse ?? null,
138
+ usageView: saved?.usageView || null,
139
+ });
140
+ Object.assign(st, {
141
+ status: 'idle',
142
+ dispatchUsd: 0,
143
+ serverCost: false,
144
+ engine: '',
145
+ sdkVersion: '',
146
+ routing: null, // server view { mode, effectiveMode, defaultMode, askBelow }
147
+ features: { routing: null, computer: null }, // null = unknown yet
148
+ brainCapabilities: null,
149
+ computerEffective: st.computerUse === true,
150
+ desktopSupported: null,
151
+ setupInfo: null,
152
+ desktopActive: false,
153
+ desktopProbe: false,
154
+ routingProbe: false,
155
+ sessionTitle: String(session?.title || ''), // the server's name (first prompt): history + notifications, never the tab
156
+ });
157
+ const controls = new Map(); // requestId → { card, origin, kind }
158
+ // Cards answered here (requestId → card): one the server closed before the answer
159
+ // arrived (control_cancelled after it) says so instead of keeping "Allowed".
160
+ const answeredCards = new Map();
161
+ const routeCards = new Map(); // routeId → route card entry
162
+ const clarifyCards = new Set(); // requestIds of clarify cards shown here (the card records their outcome)
163
+ // Prompts this panel sent (request_id → { payload, row }): one the server refuses comes back to the composer.
164
+ const sentQueries = new Map();
165
+ let querySeq = 0;
166
+ // A reattach's buffered replay: the packets that went out while this panel was away. They are
167
+ // collected (reattach_result says how many), filtered against what is on screen (renderer.planReplay:
168
+ // the journal snapshot drew most of them) and drawn like the journal; the reattach_result, not a
169
+ // replayed `done`, `aborted` or `turn_started`, says whether a turn runs now.
170
+ let replay = null; // { left, packets, timer }
171
+ let replaying = false;
172
+ const unsubs = [];
173
+ let destroyed = false;
174
+ let persistTimer = null;
175
+ let helpModal = null;
176
+ let director = null; // the mascot on screen (asst-mascot.js), made once the renderer and the composer exist
177
+
178
+ // ── DOM ──
179
+ const root = el('div', 'asst-root');
180
+ root.dataset.status = 'idle';
181
+ root.innerHTML = `
182
+ <div class="asst-bar" role="toolbar" aria-label="${esc(tt('assistant.toolbar.label', 'Assistant controls'))}">
183
+ <div class="asst-bar-left">
184
+ <div class="asst-bar-group" data-group="route"></div>
185
+ <div class="asst-bar-group" data-group="context"></div>
186
+ <div class="asst-bar-group" data-group="computer"></div>
187
+ </div>
188
+ <div class="asst-bar-right">
189
+ <button type="button" class="asst-iconbtn asst-act-agents" hidden aria-pressed="false" data-tooltip="${esc(tt('assistant.topbar.agents', 'Dispatched agents'))}" aria-label="${esc(tt('assistant.topbar.agents', 'Dispatched agents'))}">${ICON_AGENTS}<span class="asst-count">0</span></button>
190
+ <button type="button" class="asst-cost" hidden aria-label="${esc(tt('assistant.budget.title', 'Budget'))}"></button>
191
+ <button type="button" class="asst-iconbtn asst-act-sessions" data-tooltip="${esc(tt('assistant.topbar.sessions', 'Sessions'))}" aria-label="${esc(tt('assistant.topbar.sessions', 'Sessions'))}" aria-haspopup="menu" aria-expanded="false">${ICON_SESSIONS}</button>
192
+ <button type="button" class="asst-iconbtn asst-act-new" data-tooltip="${esc(tt('assistant.topbar.new', 'New assistant session'))}" aria-label="${esc(tt('assistant.topbar.new', 'New assistant session'))}">${ICON_NEW}</button>
193
+ <button type="button" class="asst-iconbtn asst-act-more" data-tooltip="${esc(tt('assistant.toolbar.more', 'More options'))}" aria-label="${esc(tt('assistant.toolbar.more', 'More options'))}" aria-haspopup="menu" aria-expanded="false">${ICON_COG}</button>
194
+ </div>
195
+ </div>
196
+ <div class="asst-banners" aria-live="polite"></div>
197
+ <div class="asst-transcript" role="log" aria-live="off" tabindex="0" aria-label="${esc(tt('assistant.transcript', 'Conversation'))}"></div>
198
+ <div class="asst-announce" role="status" aria-live="polite" aria-atomic="true"></div>
199
+ <div class="asst-bottom">
200
+ <div class="asst-computer-host"></div>
201
+ <div class="asst-dock-host"></div>
202
+ <div class="asst-usage-host" hidden></div>
203
+ <div class="asst-composer-host"></div>
204
+ </div>
205
+ `;
206
+ viewport.appendChild(root);
207
+
208
+ const barEl = root.querySelector('.asst-bar');
209
+ const barGroups = Object.fromEntries([...barEl.querySelectorAll('.asst-bar-group')].map((g) => [g.dataset.group, g]));
210
+ const costEl = root.querySelector('.asst-cost');
211
+ const bannersEl = root.querySelector('.asst-banners');
212
+ const transcriptEl = root.querySelector('.asst-transcript');
213
+ const announcer = root.querySelector('.asst-announce');
214
+ const agentsBtn = root.querySelector('.asst-act-agents');
215
+ const agentsCount = agentsBtn.querySelector('.asst-count');
216
+ const sessionsBtn = root.querySelector('.asst-act-sessions');
217
+ const newBtn = root.querySelector('.asst-act-new');
218
+ const moreBtn = root.querySelector('.asst-act-more');
219
+ // Every bar element is held above: from here on the bar may live outside the root.
220
+ if (host.toolbarSlot) host.toolbarSlot.appendChild(barEl);
221
+
222
+ // ── Helpers ──
223
+ const providerIcon = (p) => getProviderMeta(p).icon;
224
+ const providerColor = (p) => getProviderMeta(p).color;
225
+ const visible = () => { try { return host.isVisible ? !!host.isVisible() : true; } catch { return true; } };
226
+ const hostId = host.id || null; // routing only (assistant:new / resume / focused) — never a feature switch
227
+
228
+ function persist() {
229
+ if (destroyed) return;
230
+ if (persistTimer) clearTimeout(persistTimer);
231
+ persistTimer = setTimeout(() => {
232
+ persistTimer = null;
233
+ try {
234
+ storage.setItem(tabStorageKey(sessionId), JSON.stringify(serializeTabState(st)));
235
+ storage.setItem(KEYS.ASSISTANT_BRAIN, JSON.stringify(st.brain));
236
+ storage.setItem(KEYS.ASSISTANT_DOCK_OPEN, st.dockOpen ? '1' : '0');
237
+ storage.setItem(KEYS.ASSISTANT_DOCK_SHOW_ALL, st.dockShowAll ? '1' : '0');
238
+ } catch { /* storage unavailable */ }
239
+ }, 300);
240
+ }
241
+
242
+ /** The tab's name: "SynaBun", or what the user renamed it to (controller.setTitle). */
243
+ function setTitle(text) {
244
+ const next = String(text || '').trim();
245
+ if (!next) return;
246
+ st.title = next;
247
+ syncTabLabel();
248
+ persist();
249
+ }
250
+
251
+ /** Push the name to the tab; the host ignores it for renamed tabs and unchanged labels. */
252
+ function syncTabLabel() {
253
+ try { host.setLabel?.(st.title); } catch { /* ignore */ }
254
+ }
255
+
256
+ /** Notifications say which conversation: the rename, else the session's first-prompt title. */
257
+ function notifyLabel() {
258
+ return st.userRenamed ? st.title : (st.sessionTitle || st.title);
259
+ }
260
+
261
+ /** Through the host's Settings source ('cli' terminal / 'panel' sidepanel); a click routes to assistant:show. */
262
+ function notifyUser(type) {
263
+ notify(host.notifySource || 'cli', type, notifyLabel(), { sessionId, provider: 'assistant', panel: 'assistant' });
264
+ }
265
+
266
+ /** root[data-status] and data-computer drive status styling (the live dot, the Computer switch). */
267
+ function renderStatus() {
268
+ root.dataset.status = st.status || 'idle';
269
+ if (st.desktopActive) root.dataset.computer = 'active';
270
+ else delete root.dataset.computer;
271
+ }
272
+
273
+ function setStatus(status) {
274
+ st.status = status || 'idle';
275
+ renderStatus();
276
+ syncBlocked();
277
+ try { host.setStatus?.(st.status); } catch { /* ignore */ }
278
+ }
279
+
280
+ /**
281
+ * The newest card waiting on the user (permission, question, plan, clarify, route): the mascot's eyes
282
+ * go to it, and the rack whose call it holds says "Waiting for you" (clock stopped, no gold).
283
+ */
284
+ function syncBlocked() {
285
+ if (!director) return; // mounting: the renderer and the director come first
286
+ let card = null;
287
+ let own = null; // the newest card of this session's own brain (a worker's relayed card holds no call here)
288
+ for (const entry of controls.values()) {
289
+ if (!entry.card?.el?.isConnected) continue;
290
+ card = entry.card.el;
291
+ if (!entry.origin?.runId) own = entry;
292
+ }
293
+ renderer.setWaiting(!!own, own?.toolUseId || null);
294
+ director.setBlocked(card ? () => card.getBoundingClientRect() : null);
295
+ }
296
+
297
+ /** The call a control card holds, when its packet names it (Claude tool_use_id, Codex item, OpenCode call). */
298
+ function controlToolUseId(normalized) {
299
+ const req = normalized?.raw?.request || normalized?.raw || {};
300
+ const native = req.brain_native || {};
301
+ const id = req.tool_use_id ?? req.toolUseId ?? req.toolUseID ?? req.callID ?? native.toolCallId ?? native.params?.itemId ?? req.input?.tool_use_id ?? null;
302
+ return id != null && id !== '' ? String(id) : null;
303
+ }
304
+
305
+ let announceTimer = 0;
306
+ /** One polite line for screen readers (the transcript itself is not a live region). */
307
+ function announce(text) {
308
+ const line = String(text || '').replace(/\s+/g, ' ').trim();
309
+ if (!line) return;
310
+ clearTimeout(announceTimer);
311
+ announcer.textContent = '';
312
+ announceTimer = setTimeout(() => { if (!destroyed) announcer.textContent = line.length > 220 ? `${line.slice(0, 219).replace(/\s+\S*$/, '')}…` : line; }, 60);
313
+ }
314
+
315
+ function totalCost() {
316
+ return (Number(st.costUsd) || 0) + (Number(st.dispatchUsd) || 0);
317
+ }
318
+
319
+ function renderCost() {
320
+ // The server's budget view (brain + agents, the caps) when it sends one.
321
+ const budget = st.budget && Number.isFinite(Number(st.budget.totalUsd)) ? st.budget : null;
322
+ const total = budget ? Number(budget.totalUsd) : totalCost();
323
+ costEl.textContent = fmtCost(total);
324
+ costEl.hidden = !(total > 0);
325
+ const tone = budgetTone(budget);
326
+ if (tone && tone !== 'ok') costEl.dataset.tone = tone;
327
+ else delete costEl.dataset.tone;
328
+ const tip = budget
329
+ ? budgetChipText(budget, { t, brainMetered: st.brainMetered !== false })
330
+ : Number(st.dispatchUsd) > 0
331
+ ? tt('assistant.topbar.costSplit', 'Session {session} · agents {agents}', { session: fmtCost(st.costUsd || 0), agents: fmtCost(st.dispatchUsd) })
332
+ : tt('assistant.topbar.cost', 'Session cost');
333
+ costEl.setAttribute('data-tooltip', tip);
334
+ }
335
+
336
+ function brainModelLabel() {
337
+ return st.brainCapabilities?.label || picker?.modelLabel?.() || modelShortName(st.brain.model) || providerShortLabel(st.brain.provider);
338
+ }
339
+
340
+ function setRunning(running, { label } = {}) {
341
+ const was = st.running;
342
+ st.running = !!running;
343
+ if (st.running) st.turnStartedAt = st.turnStartedAt || Date.now();
344
+ else st.turnStartedAt = 0;
345
+ composer.setRunning(st.running);
346
+ if (st.running) { renderer.showWorking(label); setStatus('working'); if (!was) director?.turnStarted(); }
347
+ else { renderer.hideWorking(); }
348
+ }
349
+
350
+ function banner(id, text, { tone = '', action = null } = {}) {
351
+ let node = bannersEl.querySelector(`[data-banner="${id}"]`);
352
+ if (!text) { node?.remove(); return; }
353
+ if (!node) { node = el('div', 'asst-banner'); node.dataset.banner = id; bannersEl.appendChild(node); }
354
+ node.className = `asst-banner${tone ? ` ${tone}` : ''}`;
355
+ node.innerHTML = `<span class="asst-banner-text">${esc(text)}</span>`;
356
+ if (action) {
357
+ const btn = el('button', 'asst-btn asst-btn-secondary', action.label);
358
+ btn.type = 'button';
359
+ btn.addEventListener('click', () => action.onClick?.());
360
+ node.appendChild(btn);
361
+ }
362
+ }
363
+
364
+ function toast(text) {
365
+ if (!text) return;
366
+ renderer.appendStatus(text, 'info');
367
+ }
368
+
369
+ function guestBlocked() {
370
+ return isGuest() && !hasPermission('terminal');
371
+ }
372
+
373
+ function computerAvailable() {
374
+ return st.features.computer === true && st.desktopSupported !== false;
375
+ }
376
+
377
+ function routingAvailable() {
378
+ return st.features.routing === true;
379
+ }
380
+
381
+ // ── Renderer ──
382
+ const renderer = createRenderer(transcriptEl, {
383
+ t,
384
+ providerIcon,
385
+ providerColor,
386
+ copy: clipCopy,
387
+ onFocusRun: (runId) => focusRun(runId),
388
+ onStopRun: (runId) => stopRun(runId),
389
+ onReadRun: (runId) => readRun(runId),
390
+ onEscalateRun: (runId, target) => escalateRun(runId, target),
391
+ onMemoryClick: (id) => { if (id) emit('memory:open', { id }); },
392
+ brainLabel: () => brainModelLabel(),
393
+ onChangeRoutes: () => openRoutes(),
394
+ onRouteEvent: (ev) => handleRouteCardEvent(ev),
395
+ emptySuggestions: () => emptySuggestions(),
396
+ onSuggestion: (template) => composer.fillTemplate(template),
397
+ heroOrigin: () => { try { return host.takeHeroOrigin?.() || null; } catch { return null; } },
398
+ onMascotChange: () => director?.retarget(),
399
+ onInit: (ev) => {
400
+ if (ev.session_id) st.providerSessionId = ev.session_id;
401
+ if (ev.model && !st.brain.model) { st.brain = { ...st.brain, model: ev.model }; picker.render(); }
402
+ if (ev.permissionMode) { const patch = brainModePatch(st.brain, ev.permissionMode); if (Object.entries(patch).some(([k, v]) => st.brain[k] !== v)) { st.brain = { ...st.brain, ...patch }; picker.render(); } }
403
+ },
404
+ onResult: (ev) => {
405
+ // A replayed result belongs to a turn the reattach_result already accounted for.
406
+ if (replaying) return;
407
+ // The runtime's assistant:cost packet is authoritative; this only covers older servers.
408
+ if (!st.serverCost && Number.isFinite(Number(ev.total_cost_usd))) { st.costUsd = (Number(st.costUsd) || 0) + Number(ev.total_cost_usd); renderCost(); persist(); }
409
+ if (ev.session_id) st.providerSessionId = ev.session_id;
410
+ finishTurn({ aborted: false });
411
+ },
412
+ onStatus: (status) => { if (st.running) renderer.showWorking(status === 'compacting' ? tt('assistant.compacting', 'compacting…') : undefined); },
413
+ // An older server forwards the provider's limit report with every response: same notice, same upsert.
414
+ onLimit: (info) => { if (applyLimit(st, info)) renderLimit(); },
415
+ onModeChanged: (mode, planMode) => {
416
+ // 'plan' is the plan flag; leaving it (a Claude plan approved) keeps the approval mode.
417
+ const patch = brainModePatch(st.brain, mode, planMode);
418
+ if (!Object.entries(patch).some(([k, v]) => st.brain[k] !== v)) return;
419
+ st.brain = { ...st.brain, ...patch };
420
+ picker.render();
421
+ renderer.appendStatus(tt('assistant.status.mode', 'Permission mode: {mode}', { mode: permissionModeLabel(st.brain, (id, fallback) => tt(`assistant.brain.modes.${id}`, fallback)) }));
422
+ persist();
423
+ },
424
+ });
425
+
426
+ function emptySuggestions() {
427
+ const project = st.brain.project ? basename(st.brain.project) : tt('assistant.empty.thisProject', 'this project');
428
+ const list = [];
429
+ if (computerAvailable()) list.push({ id: 'computer', label: tt('assistant.empty.suggestComputer', 'Use my Mac to…'), template: tt('assistant.empty.templateComputer', 'Use my Mac to {caret}') });
430
+ list.push({ id: 'web', label: tt('assistant.empty.suggestWeb', 'Look up … on the web'), template: tt('assistant.empty.templateWeb', 'Look up {caret} on the web') });
431
+ list.push({ id: 'fix', label: tt('assistant.empty.suggestFix', 'Fix … in {project}', { project }), template: tt('assistant.empty.templateFix', 'Fix {caret} in {project}', { project }) });
432
+ list.push({ id: 'schedule', label: tt('assistant.empty.suggestSchedule', 'Schedule …'), template: tt('assistant.empty.templateSchedule', 'Schedule {caret}') });
433
+ list.push({ id: 'remember', label: tt('assistant.empty.suggestRemember', 'Remember …'), template: tt('assistant.empty.templateRemember', 'Remember {caret}') });
434
+ return list;
435
+ }
436
+
437
+ // ── Composer ──
438
+ const composer = createComposer(root.querySelector('.asst-composer-host'), {
439
+ t,
440
+ describeSlash: (cmd) => tt(cmd.descKey, cmd.desc),
441
+ resolveMemory: (id) => appState.allNodes?.find(n => n.id === id) || null,
442
+ resolveWhiteboardImage: (id) => { const wb = getWhiteboardElementById(id); return wb?.type === 'image' && wb.dataUrl ? { dataUrl: wb.dataUrl } : null; },
443
+ onDraftChange: (text) => { st.draft = text; persist(); director?.type(!!String(text || '').trim()); },
444
+ onFocusChange: (focused) => { if (focused) director?.touch(); else director?.stopTyping(); },
445
+ onAbort: () => abort(),
446
+ onToast: toast,
447
+ onRemoveQueued: (idx) => { if (idx >= 0) { st.queue.splice(idx, 1); composer.setQueue(st.queue); } },
448
+ onSubmit: (payload) => submit(payload),
449
+ // Files that do not go inline: saved on the server, the prompt carries the path.
450
+ uploadFile: (file, opts) => uploadAssistantAttachment(file, { ...opts, sessionId }),
451
+ });
452
+ if (st.draft) composer.setText(st.draft);
453
+
454
+ // ── The mascot on screen wears the panel's state (asst-mascot.js) ──
455
+ director = createMascotDirector({
456
+ getTarget: () => renderer.mascot(),
457
+ caret: () => composer.caretPoint(),
458
+ setEnabled: (on) => renderer.setRigsEnabled(on),
459
+ isVisible: () => visible(),
460
+ env: {
461
+ input: (fn) => { const offPointer = onPointer(fn); const offKey = onKey(fn); return () => { offPointer(); offKey(); }; },
462
+ lastInputAt,
463
+ },
464
+ });
465
+
466
+ // ── Toolbar (flush under the tabs): brain · route · context · computer ──
467
+ const routeChip = createRouteModeControl(barGroups.route, {
468
+ t,
469
+ getMode: () => routeModeView(),
470
+ brainName: () => brainModelLabel(),
471
+ onChange: (mode) => setRouteMode(mode),
472
+ onOpenModels: () => openModels(),
473
+ onOpenEditor: () => openRoutes(),
474
+ menuPlacement: 'below',
475
+ // Pending routes the server counts but no open card shows: one click replays them.
476
+ getWaiting: () => Math.max(0, (Number(st.pendingRoutes) || 0) - [...routeCards.values()].filter((card) => !card.collapsed).length),
477
+ onShowWaiting: () => sock.send({ type: 'reattach' }),
478
+ });
479
+
480
+ const accountHooks = () => ({ t, onChanged: () => picker.refresh(true), onSelect: (id) => applyBrain({ ...st.brain, accountId: id }, { requiresSwitch: true, field: 'account' }) });
481
+ // Model · effort · permission mode · account sit in the composer row next to attach; project and MCP in the toolbar.
482
+ const picker = createBrainPicker({ brain: composer.el.querySelector('.asst-composer-brain'), context: barGroups.context }, {
483
+ t,
484
+ getBrain: () => st.brain,
485
+ onChange: (next, meta) => applyBrain(next, meta),
486
+ onAddAccount: (provider) => openAccountsManager({ provider, ...accountHooks() }).openAdd(),
487
+ onManageAccounts: (provider) => openAccountsManager({ provider, ...accountHooks() }),
488
+ onToast: toast,
489
+ onManageModels: () => openModels(),
490
+ getMenuAnchor: () => moreBtn,
491
+ // Composer fields open upward, toolbar fields downward.
492
+ menuPlacement: 'auto',
493
+ });
494
+
495
+ const computerToggle = createComputerToggle(barGroups.computer, {
496
+ t,
497
+ onToggle: (enabled) => setComputerUse(enabled),
498
+ onRemoteInfo: () => explainRemoteComputer(),
499
+ });
500
+ // The composer row's fields lead the toolbar's arrow order (model first, "⋯" last).
501
+ const roving = createRovingToolbar(barEl, { lead: composer.el.querySelector('.asst-composer-brain') });
502
+
503
+ // ── Computer use (setup tray + live activity) ──
504
+ const computerPanel = createComputerPanel(root.querySelector('.asst-computer-host'), {
505
+ t,
506
+ sessionId,
507
+ isEnabled: () => st.computerEffective && computerAvailable(),
508
+ onStatus: (status, info) => {
509
+ st.setupInfo = info;
510
+ if (status && typeof status === 'object' && 'supported' in status) st.desktopSupported = status.supported === true;
511
+ if (st.features.computer == null && status?.supported === true) st.features.computer = true;
512
+ applyFeatures();
513
+ },
514
+ onActivity: (active) => {
515
+ if (st.desktopActive === active) return;
516
+ st.desktopActive = active;
517
+ renderStatus();
518
+ computerToggle.set({ active });
519
+ },
520
+ onOpenFrame: ({ src, marker, caption, returnFocus }) => openFrameLightbox({ src, marker, caption, t, returnFocus }),
521
+ });
522
+
523
+ // ── Agents tray ──
524
+ const dock = createAgentsDock(root.querySelector('.asst-dock-host'), {
525
+ t,
526
+ sessionId,
527
+ open: st.dockOpen,
528
+ showAll: st.dockShowAll,
529
+ onFocus: (runId) => focusRun(runId),
530
+ onStop: (runId) => stopRun(runId),
531
+ onRead: (runId) => readRun(runId),
532
+ onEscalate: (runId, target) => escalateRun(runId, target),
533
+ onStopAll: () => stopAll(),
534
+ onRemove: (runId) => removeRun(runId),
535
+ onClearFinished: () => clearFinished(),
536
+ onToggle: (open) => { st.dockOpen = open; agentsBtn.setAttribute('aria-pressed', open ? 'true' : 'false'); persist(); },
537
+ onShowAllChange: (showAll) => { st.dockShowAll = showAll; persist(); },
538
+ onCountChange: ({ total, active, attention, visible: shown }) => {
539
+ agentsCount.textContent = String(active || total);
540
+ agentsCount.classList.toggle('attention', attention > 0);
541
+ agentsBtn.hidden = !shown;
542
+ agentsBtn.classList.toggle('attention', attention > 0);
543
+ },
544
+ });
545
+ agentsBtn.setAttribute('aria-pressed', st.dockOpen ? 'true' : 'false');
546
+
547
+ const usage = createUsageGauge(root.querySelector('.asst-usage-host'), { t, sessionId });
548
+ if (st.usageView) { usage.apply({ type: 'assistant:usage', ...st.usageView }); usage.setConnection('reconnecting'); }
549
+ let usageFetchSeq = 0;
550
+ let usageSocketOpened = false;
551
+ let usageWarned = false;
552
+ function fetchUsage() {
553
+ const seq = ++usageFetchSeq;
554
+ getAssistantSessionUsage(sessionId).then((data) => {
555
+ if (!destroyed && seq === usageFetchSeq && data?.ok && data.usage) applyUsage(data.usage);
556
+ }).catch(() => { /* older servers do not expose usage */ });
557
+ }
558
+ function applyUsage(packet) {
559
+ if (packet?.sessionId === sessionId && (!packet.task?.id || !Number.isSafeInteger(packet.task?.tokens?.total) || packet.task.tokens.total < 0)) {
560
+ if (!usageWarned) { console.warn('[assistant] invalid usage packet'); usageWarned = true; }
561
+ return;
562
+ }
563
+ const was = st.usageView;
564
+ if (!applyUsagePacket(st, packet)) return;
565
+ if (!usage.apply(st.usageView)) { st.usageView = was; return; }
566
+ for (const agent of st.usageView.task.agents || []) {
567
+ if (agent.scope !== 'run' || !agent.runId) continue;
568
+ const tokens = agent.tokens || null;
569
+ const fidelity = agent.fidelity || st.usageView.task.fidelity;
570
+ dock.patchUsageRun(agent.runId, tokens, fidelity);
571
+ renderer.patchRunUsage(agent.runId, tokens, fidelity);
572
+ }
573
+ persist();
574
+ }
575
+
576
+ // The provider's usage limit: one line under the usage gauge, replaced in place (st.limit is
577
+ // one notice or null). It is never a transcript row, and it goes when the provider says the
578
+ // limit is no longer near or its reset time passes.
579
+ const limitEl = el('div', 'asst-limit');
580
+ limitEl.hidden = true;
581
+ limitEl.setAttribute('role', 'status');
582
+ limitEl.setAttribute('aria-live', 'polite');
583
+ root.querySelector('.asst-usage-host').after(limitEl);
584
+ let limitTimer = null;
585
+ function renderLimit() {
586
+ clearTimeout(limitTimer);
587
+ limitTimer = null;
588
+ const limit = st.limit;
589
+ limitEl.hidden = !limit;
590
+ limitEl.dataset.status = limit?.status || '';
591
+ const text = limitNoticeText(limit, tt);
592
+ if (limitEl.textContent !== text) limitEl.textContent = text;
593
+ const wait = limitExpiresIn(limit);
594
+ if (wait == null) return;
595
+ limitTimer = setTimeout(() => {
596
+ if (destroyed) return;
597
+ if (limitExpiresIn(st.limit) > 0) { renderLimit(); return; } // a wait longer than one timer can hold
598
+ st.limit = null;
599
+ renderLimit();
600
+ }, Math.min(wait + 250, 2 ** 31 - 1));
601
+ }
602
+
603
+ // ── Socket ──
604
+ const sock = createAssistantSocket(sessionId, {
605
+ onPacket: (packet) => handlePacket(packet),
606
+ onStatus: (status, extra) => {
607
+ st.connected = status === 'open';
608
+ usage.setConnection(status);
609
+ if (status === 'open') { if (usageSocketOpened) fetchUsage(); usageSocketOpened = true; }
610
+ if (status === 'open') { banner('conn', ''); st.attachedElsewhere = false; banner('elsewhere', ''); director.setOffline(false); }
611
+ else if (status === 'reconnecting') { banner('conn', tt('assistant.banner.reconnecting', 'Reconnecting to the assistant…'), { tone: 'info' }); director.setOffline(true); }
612
+ else if (status === 'attached-elsewhere') {
613
+ st.attachedElsewhere = true;
614
+ banner('elsewhere', tt('assistant.banner.attachedElsewhere', 'This session is open in another window.'), {
615
+ action: { label: tt('assistant.banner.takeOver', 'Take over'), onClick: () => { sock.takeOver(); banner('elsewhere', ''); } },
616
+ });
617
+ }
618
+ if (extra?.message && status === 'attached-elsewhere') { /* message already shown by the error handler */ }
619
+ },
620
+ });
621
+
622
+ // ── Packet handling ──
623
+ /** A reattach replays `count` buffered packets next: collect them, then draw what is not on screen yet. */
624
+ function startReplay(count) {
625
+ if (replay) flushReplay();
626
+ if (!(count > 0)) { renderer.planReplay([]); return; } // nothing buffered: the snapshot has no replay to meet
627
+ replay = { left: count, packets: [], timer: setTimeout(() => flushReplay(), REPLAY_WAIT_MS) };
628
+ }
629
+
630
+ function flushReplay() {
631
+ const batch = replay;
632
+ replay = null;
633
+ if (!batch || destroyed) return;
634
+ clearTimeout(batch.timer);
635
+ const packets = renderer.planReplay(batch.packets);
636
+ replaying = true;
637
+ try { for (const packet of packets) handlePacket(packet); }
638
+ finally { replaying = false; }
639
+ }
640
+
641
+ function handlePacket(packet) {
642
+ if (destroyed || !packet?.type) return;
643
+ if (replay && packet.type !== 'reattach_result') {
644
+ replay.packets.push(packet);
645
+ if ((replay.left -= 1) <= 0) flushReplay();
646
+ return;
647
+ }
648
+ // Replayed history: the reattach_result already said whether a turn runs now.
649
+ if (replaying && (packet.type === 'done' || packet.type === 'aborted' || packet.type === 'turn_started')) return;
650
+ if (replaying && packet.type === 'error') { if (!packet.request_id) renderer.appendError(packet.message || tt('assistant.errorGeneric', 'Something went wrong')); return; }
651
+ switch (packet.type) {
652
+ case 'engine':
653
+ st.engine = packet.engine || '';
654
+ st.sdkVersion = packet.sdkVersion || '';
655
+ return;
656
+ case 'event':
657
+ handleEvent(packet.event);
658
+ return;
659
+ case 'control_request':
660
+ showControl(packet, null);
661
+ return;
662
+ case 'control_cancelled': {
663
+ // A plan card a newer message replaced, or that timed out, reads Expired. A card answered
664
+ // here whose request closed before the answer arrived says so too: that answer did nothing.
665
+ const id = String(packet.request_id);
666
+ const answered = controls.has(id) ? null : answeredCards.get(id);
667
+ const kind = controls.get(id)?.kind || answered?.el?.dataset?.kind;
668
+ const label = closedControlState(packet.reason, kind) === 'expired' ? tt('assistant.control.state.expired', 'Expired') : tt('assistant.control.state.cancelled', 'Cancelled');
669
+ if (answered) { answered.expire(label); answeredCards.delete(id); return; }
670
+ lockControl(packet.request_id, label);
671
+ return;
672
+ }
673
+ case 'control_resolved':
674
+ // Answered somewhere else (WhatsApp, another window): this card is done.
675
+ lockControl(packet.request_id, resolvedLabel(packet.origin, tt('assistant.control.state.answered', 'Answered')));
676
+ return;
677
+ case 'reattach_result':
678
+ if (packet.ok) {
679
+ if (packet.sessionId) st.providerSessionId = packet.sessionId;
680
+ if (packet.resynced) rehydrate({ force: true });
681
+ if (packet.running) setRunning(true);
682
+ else if (st.running) finishTurn({ aborted: false, silent: true });
683
+ startReplay(Number(packet.replayed) || 0);
684
+ } else if (st.running) {
685
+ finishTurn({ aborted: false, silent: true });
686
+ }
687
+ return;
688
+ case 'rewind_result':
689
+ renderer.appendStatus(packet.ok ? tt('assistant.status.rewound', 'Files rewound to checkpoint') : `${tt('assistant.status.rewindFailed', 'Rewind failed')}: ${packet.error || ''}`);
690
+ return;
691
+ case 'stderr':
692
+ if (packet.text?.trim()) renderer.appendStatus(packet.text.trim());
693
+ return;
694
+ case 'turn_started':
695
+ // The CLI began a turn on its own (a background agent finished, a
696
+ // scheduled wakeup fired); its `done` finishes it.
697
+ if (!st.running) setRunning(true);
698
+ return;
699
+ case 'done':
700
+ finishTurn({ aborted: false });
701
+ return;
702
+ case 'aborted':
703
+ finishTurn({ aborted: true });
704
+ return;
705
+ case 'error': {
706
+ // A rejected route answer keeps its card open with the message.
707
+ const routeCard = packet.request_id ? routeCardByRequest(packet.request_id) : null;
708
+ if (routeCard) { routeCard.fail(packet.message); return; }
709
+ if (packet.code === 'ROUTE_INVALID') { renderer.appendError(packet.message || tt('assistant.routes.invalid', 'That route is not available. Pick another option.')); return; }
710
+ // A prompt of this panel that did not run (the approved plan goes first, the brain is mid-turn…):
711
+ // its text goes back into the composer, and the reattach_result after it says what runs now.
712
+ const refused = packet.request_id ? sentQueries.get(String(packet.request_id)) : null;
713
+ if (refused) {
714
+ sentQueries.delete(String(packet.request_id));
715
+ refused.row?.remove?.();
716
+ composer.restore(refused.payload);
717
+ renderer.appendError(`${packet.message || tt('assistant.errorGeneric', 'Something went wrong')} ${tt('assistant.status.notSent', 'Your message is back in the input box.')}`);
718
+ return;
719
+ }
720
+ renderer.appendError(packet.message || tt('assistant.errorGeneric', 'Something went wrong'));
721
+ announce(tt('assistant.announce.error', 'Error: {text}', { text: packet.message || tt('assistant.errorGeneric', 'Something went wrong') }));
722
+ if (st.running) finishTurn({ aborted: true, silent: true, errored: true });
723
+ notifyUser(NOTIF_TYPE.ERROR);
724
+ return;
725
+ }
726
+ case 'mode_changed':
727
+ handleEvent({ type: 'mode_changed', mode: packet.mode, planMode: packet.planMode });
728
+ return;
729
+ case 'assistant:session':
730
+ applySessionMeta(packet.session || packet);
731
+ return;
732
+ case 'assistant:dispatch': {
733
+ const run = runFromPayload(packet);
734
+ if (run) { dock.upsertRun(run); renderer.upsertRunCard(dock.getRun(run.runId) || run); }
735
+ return;
736
+ }
737
+ case 'assistant:permission-request':
738
+ showControl({ request_id: packet.request_id || packet.requestId || packet.request?.requestId, request: packet.request, provider: packet.provider, runId: packet.runId }, { runId: packet.runId, provider: packet.provider || dock.getRun(packet.runId)?.provider });
739
+ return;
740
+ case 'assistant:mailbox':
741
+ renderer.appendMailbox(packet.items || packet.events || packet.mailbox || []);
742
+ return;
743
+ case 'assistant:cost':
744
+ applyCost(packet);
745
+ return;
746
+ case 'assistant:usage':
747
+ ++usageFetchSeq;
748
+ applyUsage(packet);
749
+ return;
750
+ case 'assistant:limit':
751
+ if (applyLimitPacket(st, packet)) renderLimit();
752
+ return;
753
+ case 'assistant:sessions':
754
+ showSessionsMenu(packet.sessions || []);
755
+ return;
756
+ case 'assistant:desktop':
757
+ computerPanel.applyDesktop(packet);
758
+ return;
759
+ case 'assistant:desktop-setup':
760
+ computerPanel.applySetup(packet);
761
+ return;
762
+ default:
763
+ return;
764
+ }
765
+ }
766
+
767
+ function handleEvent(ev) {
768
+ if (!ev?.type) return;
769
+ // One registry for what is on screen (the renderer's): the journal snapshot registers there too.
770
+ if (!renderer.claimEvent(ev)) return;
771
+ const draw = (event) => renderer.handleEvent(event, replaying ? { replay: true } : undefined);
772
+ switch (ev.type) {
773
+ case 'synabun.dispatch': {
774
+ const run = runFromPayload(ev);
775
+ if (run) { dock.upsertRun(run); renderer.upsertRunCard(dock.getRun(run.runId) || run); }
776
+ return;
777
+ }
778
+ case 'synabun.dispatch_result': {
779
+ const run = runFromPayload(ev);
780
+ if (run) { dock.upsertRun(run); renderer.upsertRunCard(dock.getRun(run.runId) || run, { result: ev.result ?? null }); }
781
+ return;
782
+ }
783
+ case 'synabun.dispatch_control_request':
784
+ showControl({ request_id: ev.request_id || ev.request?.requestId, request: ev.request, provider: ev.provider, runId: ev.runId }, { runId: ev.runId, provider: ev.provider || dock.getRun(ev.runId)?.provider });
785
+ return;
786
+ case 'synabun.user_prompt':
787
+ // A prompt from WhatsApp started a turn this panel did not send (a replayed one is history).
788
+ if (!replaying && ev.origin && ev.origin !== 'ui' && !st.running) setRunning(true);
789
+ draw(ev);
790
+ return;
791
+ case 'synabun.dispatch_control_resolved':
792
+ lockControl(ev.request_id, resolvedLabel(ev.resolvedBy));
793
+ if (ev.runId) dock.patchRun(ev.runId, { turnState: 'running' });
794
+ return;
795
+ case 'synabun.mailbox':
796
+ renderer.appendMailbox(ev.items || ev.events || []);
797
+ return;
798
+ case 'synabun.clarify':
799
+ if (settleClarifyCard(ev)) return;
800
+ draw(ev);
801
+ return;
802
+ case 'synabun.cost':
803
+ applyCost(ev);
804
+ return;
805
+ default:
806
+ draw(ev);
807
+ }
808
+ }
809
+
810
+ function applyCost(packet) {
811
+ const session = num(packet?.sessionUsd ?? packet?.sessionCostUsd ?? packet?.totalUsd);
812
+ if (session != null) st.costUsd = session;
813
+ else if (!packet?.runId && num(packet?.costUsd) != null) st.costUsd = num(packet.costUsd);
814
+ if (num(packet?.dispatchUsd) != null) st.dispatchUsd = num(packet.dispatchUsd);
815
+ if (packet?.budget && typeof packet.budget === 'object') st.budget = packet.budget;
816
+ if (typeof packet?.brainMetered === 'boolean') st.brainMetered = packet.brainMetered;
817
+ if (packet?.runId && num(packet.costUsd) != null) dock.patchRun(packet.runId, { costUsd: num(packet.costUsd) });
818
+ if (session != null) st.serverCost = true;
819
+ renderCost();
820
+ persist();
821
+ }
822
+
823
+ function applySessionMeta(meta) {
824
+ if (!meta || typeof meta !== 'object') return;
825
+ if (meta.id && meta.id !== sessionId) return;
826
+ // The server's title names the conversation (history, notifications), never the tab.
827
+ if (meta.title) st.sessionTitle = String(meta.title);
828
+ if (meta.brain && typeof meta.brain === 'object') {
829
+ const next = normalizeBrain({ ...st.brain, ...meta.brain, provider: meta.brain.provider || meta.brain.brain || st.brain.provider });
830
+ if (JSON.stringify(next) !== JSON.stringify(st.brain)) { st.brain = next; picker.render(); host.setBrain?.(next); persist(); }
831
+ } else if (typeof meta.brain === 'string') {
832
+ const next = normalizeBrain({ ...st.brain, provider: meta.brain, model: meta.model ?? st.brain.model, effort: meta.effort ?? st.brain.effort, accountId: meta.accountId ?? st.brain.accountId, mcpProfile: meta.mcpProfile ?? st.brain.mcpProfile, project: meta.cwd ?? st.brain.project });
833
+ if (JSON.stringify(next) !== JSON.stringify(st.brain)) { st.brain = next; picker.render(); host.setBrain?.(next); persist(); }
834
+ }
835
+ if (meta.budget && typeof meta.budget === 'object') st.budget = meta.budget;
836
+ if (Number.isFinite(Number(meta.costUsd))) { st.costUsd = Number(meta.costUsd); renderCost(); }
837
+ if (meta.providerSessionId) st.providerSessionId = meta.providerSessionId;
838
+ if (meta.routing && typeof meta.routing === 'object') {
839
+ st.routing = {
840
+ mode: normalizeRouteMode(meta.routing.mode, null),
841
+ effectiveMode: normalizeRouteMode(meta.routing.effectiveMode, null),
842
+ defaultMode: normalizeRouteMode(meta.routing.defaultMode, null),
843
+ askBelow: meta.routing.askBelow ?? null,
844
+ };
845
+ st.routeMode = st.routing.mode;
846
+ }
847
+ if (meta.features && typeof meta.features === 'object') {
848
+ if (typeof meta.features.routing === 'boolean') st.features.routing = meta.features.routing;
849
+ if (typeof meta.features.computer === 'boolean') st.features.computer = meta.features.computer;
850
+ }
851
+ if (typeof meta.computerUse === 'boolean') st.computerEffective = meta.computerUse;
852
+ if ('computerUseExplicit' in meta) st.computerUse = normalizeComputerMode(meta.computerUseExplicit, null);
853
+ // A WhatsApp conversation: what is in effect and why (not a per-conversation switch). null on the desktop.
854
+ if ('computerRemote' in meta) st.computerRemote = normalizeComputerRemote(meta.computerRemote);
855
+ if (meta.brainCapabilities && typeof meta.brainCapabilities === 'object') st.brainCapabilities = meta.brainCapabilities;
856
+ if (Array.isArray(meta.pendingRoutes)) {
857
+ for (const pending of meta.pendingRoutes) {
858
+ if (pending && typeof pending === 'object' && pending.request && (pending.request_id || pending.requestId)) showControl(pending, null);
859
+ }
860
+ } else if (typeof meta.pendingRoutes === 'number') {
861
+ // The session view carries a count; the route chip shows any the panel is missing.
862
+ st.pendingRoutes = Math.max(0, meta.pendingRoutes);
863
+ routeChip.render();
864
+ }
865
+ applyFeatures();
866
+ // Every attach sends meta: this also heals a restored float whose saved label came back.
867
+ syncTabLabel();
868
+ persist();
869
+ }
870
+
871
+ function applyFeatures() {
872
+ if (destroyed) return;
873
+ routeChip.setVisible(routingAvailable());
874
+ routeChip.render();
875
+ const showComputer = st.features.computer === true || st.desktopSupported === true;
876
+ computerToggle.set({
877
+ visible: showComputer,
878
+ enabled: st.computerEffective,
879
+ supported: st.desktopSupported !== false,
880
+ setupState: st.setupInfo?.state || 'loading',
881
+ active: st.desktopActive,
882
+ remote: st.computerRemote || null,
883
+ });
884
+ if (showComputer && !st.desktopProbe) {
885
+ st.desktopProbe = true;
886
+ computerPanel.refresh().catch(() => {});
887
+ }
888
+ computerPanel.render();
889
+ renderer.refreshEmpty();
890
+ roving.sync();
891
+ }
892
+
893
+ /** Features unknown after the first meta (older server)? Probe the endpoints once. */
894
+ function probeFeatures() {
895
+ if (st.features.routing == null && !st.routingProbe) {
896
+ st.routingProbe = true;
897
+ getRouting()
898
+ .then((data) => {
899
+ st.features.routing = true;
900
+ if (!st.routing) {
901
+ const def = normalizeRouteMode(data?.routing?.defaultMode, DEFAULT_ROUTE_MODE);
902
+ st.routing = { mode: st.routeMode, effectiveMode: st.routeMode || def, defaultMode: def, askBelow: data?.routing?.askBelow ?? null };
903
+ }
904
+ applyFeatures();
905
+ })
906
+ .catch(() => { st.features.routing = false; applyFeatures(); });
907
+ }
908
+ if (st.features.computer == null && !st.desktopProbe) {
909
+ st.desktopProbe = true;
910
+ getDesktopStatus()
911
+ .then((status) => {
912
+ st.desktopSupported = status?.supported === true;
913
+ st.features.computer = status?.supported === true;
914
+ computerPanel.applyStatus(status);
915
+ applyFeatures();
916
+ })
917
+ .catch(() => { st.features.computer = false; applyFeatures(); });
918
+ }
919
+ }
920
+
921
+ function routeModeView() {
922
+ const r = st.routing || {};
923
+ return { mode: normalizeRouteMode(st.routeMode, null) || r.mode || null, effectiveMode: r.effectiveMode, defaultMode: r.defaultMode };
924
+ }
925
+
926
+ function setRouteMode(mode, { quiet = false } = {}) {
927
+ const next = normalizeRouteMode(mode, null);
928
+ st.routeMode = next;
929
+ const def = st.routing?.defaultMode || DEFAULT_ROUTE_MODE;
930
+ st.routing = { ...(st.routing || {}), mode: next, effectiveMode: next || def, defaultMode: def };
931
+ routeChip.render();
932
+ persist();
933
+ sock.send({ type: 'set_route_mode', mode: next });
934
+ try { if (next) storage.setItem(KEYS.ASSISTANT_ROUTE_MODE, next); } catch { /* storage unavailable */ }
935
+ if (!quiet) renderer.appendStatus(tt('assistant.status.routeMode', 'Model routing: {mode}', { mode: routeModeLabel(next || def, t) }));
936
+ }
937
+
938
+ /** Fresh tabs inherit the last-used route mode (like the brain). */
939
+ function applyLastUsedRouteMode() {
940
+ if (saved || !routingAvailable() || !st.routing || st.routing.mode != null) return;
941
+ let last = null;
942
+ try { last = normalizeRouteMode(storage.getItem(KEYS.ASSISTANT_ROUTE_MODE), null); } catch { last = null; }
943
+ if (last && last !== st.routing.effectiveMode) setRouteMode(last, { quiet: true });
944
+ }
945
+
946
+ /** A WhatsApp conversation's Computer switch says why it is what it is (the status line of the transcript). */
947
+ function explainRemoteComputer() {
948
+ const view = computerRemoteView(st.computerRemote, t);
949
+ if (!view) return false;
950
+ renderer.appendStatus(tt('assistant.status.computerRemote', 'Computer use in this WhatsApp conversation: {why}.', { why: view.tip }));
951
+ return true;
952
+ }
953
+
954
+ function setComputerUse(enabled) {
955
+ // Not a per-conversation switch in a WhatsApp conversation: nothing is sent, nothing bounces.
956
+ if (st.computerRemote) { explainRemoteComputer(); return; }
957
+ const next = enabled === true;
958
+ const wasActive = st.desktopActive;
959
+ st.computerUse = next;
960
+ st.computerEffective = next;
961
+ persist();
962
+ sock.send({ type: 'set_computer_use', enabled: next });
963
+ if (!next && wasActive) {
964
+ sock.send({ type: 'desktop_stop' });
965
+ if (!sock.isOpen()) requestDesktopStop({ scope: 'session', assistantSessionId: sessionId });
966
+ }
967
+ // Permissions can be revoked while the toggle was off: re-read the status.
968
+ if (next) computerPanel.refresh().catch(() => {});
969
+ applyFeatures();
970
+ }
971
+
972
+ function finishTurn({ aborted, silent = false, errored = false } = {}) {
973
+ const wasRunning = st.running;
974
+ // Once per turn (a result and its `done` both land here): success after real work, error when it failed.
975
+ const stats = renderer.turnStats();
976
+ const pose = wasRunning ? turnEndPose({
977
+ aborted: aborted && !errored,
978
+ errored: errored || stats.errored,
979
+ lastFailed: stats.lastFailed,
980
+ calls: stats.calls,
981
+ elapsedMs: st.turnStartedAt ? Date.now() - st.turnStartedAt : 0,
982
+ }) : null;
983
+ const answer = wasRunning && !aborted ? renderer.turnAnswer() : '';
984
+ renderer.finishTurn({ holdMs: pose ? MASCOT_TIMING.oneShotMs[pose] : 0 });
985
+ if (wasRunning) director.turnEnded(pose);
986
+ if (answer && !silent) announce(tt('assistant.announce.replied', 'SynaBun replied: {text}', { text: answer }));
987
+ st.running = false;
988
+ st.turnStartedAt = 0;
989
+ composer.setRunning(false);
990
+ if (aborted) {
991
+ if (!silent) renderer.appendStatus(tt('assistant.status.aborted', 'Aborted.'));
992
+ setStatus('idle');
993
+ } else if (wasRunning) {
994
+ setStatus('done');
995
+ if (!visible() && !silent) notifyUser(NOTIF_TYPE.DONE);
996
+ } else {
997
+ setStatus('idle');
998
+ }
999
+ if (controls.size) setStatus('action');
1000
+ if (st.queue.length) {
1001
+ const next = st.queue.shift();
1002
+ composer.setQueue(st.queue);
1003
+ setTimeout(() => { if (!destroyed && !st.running) sendQuery(next); }, 250);
1004
+ }
1005
+ }
1006
+
1007
+ // ── Controls ──
1008
+ function routeCardByRequest(requestId) {
1009
+ for (const card of routeCards.values()) if (card.requestId === String(requestId)) return card;
1010
+ return null;
1011
+ }
1012
+
1013
+ function showControl(packet, origin) {
1014
+ const normalized = normalizeControlRequest({ ...packet, provider: packet.provider || (origin ? origin.provider : st.brain.provider) }, { origin });
1015
+ if (!normalized) return;
1016
+ if (controls.has(normalized.requestId)) return;
1017
+ renderer.hideWorking();
1018
+ const entry = renderControlCard(null, normalized, {
1019
+ t,
1020
+ md: renderer.md,
1021
+ providerIcon,
1022
+ scrollEnd: () => renderer.scrollEnd(),
1023
+ onRespond: (n, decision) => respondControl(n, decision),
1024
+ renderRoute: (_container, n) => mountRouteCard(n),
1025
+ });
1026
+ if (!entry) return;
1027
+ if (normalized.kind !== 'route') renderer.appendNode(entry.el);
1028
+ controls.set(normalized.requestId, { card: entry, origin, kind: normalized.kind, toolUseId: origin?.runId ? null : controlToolUseId(normalized) });
1029
+ if (normalized.kind === 'clarify') clarifyCards.add(normalized.requestId);
1030
+ setStatus('action');
1031
+ if (origin?.runId) dock.patchRun(origin.runId, { turnState: 'awaiting_permission' });
1032
+ if (!visible()) notifyUser(NOTIF_TYPE.ASK);
1033
+ renderer.scrollEnd(true);
1034
+ syncBlocked();
1035
+ }
1036
+
1037
+ function mountRouteCard(n) {
1038
+ const card = renderRouteCard(null, n, {
1039
+ t,
1040
+ providerIcon,
1041
+ brainLabel: () => brainModelLabel(),
1042
+ getBrain: () => st.brain,
1043
+ shouldFocus: () => visible() && !composer.hasContent() && !isMenuOpen(),
1044
+ onRespond: (response) => {
1045
+ sock.send({ type: 'control_response', request_id: card.requestId, response });
1046
+ if (st.running) renderer.showWorking();
1047
+ },
1048
+ onCancel: (packet) => sock.send(packet),
1049
+ onCollapse: (routeId, line) => {
1050
+ renderer.registerRouteLine(routeId, line);
1051
+ controls.delete(card.requestId);
1052
+ if (!controls.size) setStatus(st.running ? 'working' : 'idle');
1053
+ routeChip.render();
1054
+ },
1055
+ onDone: () => composer.focus(),
1056
+ onToast: toast,
1057
+ onChangeRoutes: () => openRoutes(),
1058
+ scrollEnd: () => renderer.scrollEnd(),
1059
+ });
1060
+ if (!card) return null;
1061
+ renderer.removeRouteLine(card.routeId);
1062
+ renderer.appendNode(card.el);
1063
+ routeCards.set(card.routeId, card);
1064
+ routeChip.render();
1065
+ requestAnimationFrame(() => { if (!destroyed && !card.collapsed) card.focusIfIdle(); });
1066
+ return card;
1067
+ }
1068
+
1069
+ /** `synabun.route` for a route that has a card: the card represents it until it settles. */
1070
+ function handleRouteCardEvent(ev) {
1071
+ const routeId = ev?.route?.routeId;
1072
+ const card = routeId ? routeCards.get(routeId) : null;
1073
+ if (!card) return false;
1074
+ const phase = ev.phase || 'decided';
1075
+ if (phase === 'card' || phase === 'pending') return true;
1076
+ card.settle(phase, ev.route);
1077
+ card.destroy();
1078
+ routeCards.delete(routeId);
1079
+ controls.delete(card.requestId);
1080
+ routeChip.render();
1081
+ if (!controls.size) setStatus(st.running ? 'working' : 'idle');
1082
+ return true;
1083
+ }
1084
+
1085
+ /**
1086
+ * `synabun.clarify` for a clarify card this window showed: the card is the
1087
+ * record (locked with the outcome when it was answered elsewhere or in chat),
1088
+ * so no second line. Without a card (another window, a reload) the renderer
1089
+ * draws the outcome line.
1090
+ */
1091
+ function settleClarifyCard(ev) {
1092
+ const phase = ev?.phase || '';
1093
+ if (phase === 'card') return true;
1094
+ const requestId = ev?.brief?.round?.requestId ? String(ev.brief.round.requestId) : '';
1095
+ if (!requestId || !clarifyCards.has(requestId)) return false;
1096
+ const label = phase === 'chat' ? tt('assistant.control.state.answeredInChat', 'Answered in chat')
1097
+ : phase === 'answered' ? tt('assistant.control.state.answered', 'Answered')
1098
+ : phase === 'declined' ? tt('assistant.control.state.skipped', 'Skipped')
1099
+ : tt('assistant.control.state.cancelled', 'Cancelled');
1100
+ lockControl(requestId, label);
1101
+ return true;
1102
+ }
1103
+
1104
+ function respondControl(n, decision) {
1105
+ const response = buildControlResponse(n, decision);
1106
+ if (n.kind === 'clarify') {
1107
+ // The server's clarifier owns it: a brain waiting in agent_clarify continues; an idle one gets a mailbox turn.
1108
+ sock.send({ type: 'control_response', request_id: n.requestId, response });
1109
+ if (st.running) renderer.showWorking();
1110
+ controls.delete(n.requestId);
1111
+ if (!controls.size) setStatus(st.running ? 'working' : 'idle');
1112
+ return;
1113
+ }
1114
+ if (n.origin?.runId) {
1115
+ sock.send({ type: 'dispatch_control_response', runId: n.origin.runId, request_id: n.requestId, response });
1116
+ dock.patchRun(n.origin.runId, { turnState: 'running' });
1117
+ } else {
1118
+ sock.send({ type: 'control_response', request_id: n.requestId, response });
1119
+ if (!st.running) setRunning(true);
1120
+ else renderer.showWorking();
1121
+ const card = controls.get(n.requestId)?.card;
1122
+ if (card?.expire) answeredCards.set(n.requestId, card);
1123
+ while (answeredCards.size > 20) answeredCards.delete(answeredCards.keys().next().value);
1124
+ }
1125
+ controls.delete(n.requestId);
1126
+ if (!controls.size) setStatus(st.running ? 'working' : 'idle');
1127
+ syncBlocked();
1128
+ }
1129
+
1130
+ /** The locked-card label for who answered: WhatsApp by name, the panel as `fallback`. */
1131
+ function resolvedLabel(who, fallback = null) {
1132
+ if (who === 'whatsapp') return tt('assistant.control.state.answeredOnWhatsApp', 'Answered on WhatsApp');
1133
+ if (who && who !== 'ui') return tt('assistant.control.state.resolvedBy', 'Resolved by {who}', { who });
1134
+ return fallback || tt('assistant.control.state.resolved', 'Resolved');
1135
+ }
1136
+
1137
+ function lockControl(requestId, label) {
1138
+ const entry = controls.get(String(requestId));
1139
+ const routeCard = routeCardByRequest(requestId);
1140
+ if (routeCard) {
1141
+ routeCard.lock(label);
1142
+ routeCard.destroy();
1143
+ routeCards.delete(routeCard.routeId);
1144
+ routeChip.render();
1145
+ } else if (entry) {
1146
+ entry.card.lock(label);
1147
+ }
1148
+ if (!entry && !routeCard) return;
1149
+ controls.delete(String(requestId));
1150
+ if (!controls.size) setStatus(st.running ? 'working' : 'idle');
1151
+ syncBlocked();
1152
+ }
1153
+
1154
+ // ── Sending ──
1155
+ function sendQuery(payload) {
1156
+ if (guestBlocked()) return false;
1157
+ const prompt = buildPromptText(payload);
1158
+ if (!prompt && !payload.images?.length) return false;
1159
+ const b = st.brain;
1160
+ const msg = {
1161
+ type: 'query',
1162
+ prompt,
1163
+ cwd: b.project || undefined,
1164
+ model: b.model || undefined,
1165
+ effort: b.effort || undefined,
1166
+ permissionMode: b.permissionMode || 'default',
1167
+ planMode: b.planMode === true,
1168
+ brain: { ...b },
1169
+ // A refusal carries it back to this panel alone (the 'error' case restores the text).
1170
+ request_id: `q-${Date.now().toString(36)}-${(querySeq += 1)}`,
1171
+ };
1172
+ if (payload.images?.length) msg.images = payload.images.map(i => ({ base64: i.base64, mediaType: i.mediaType }));
1173
+ const row = renderer.appendUser(payload.display ?? payload.text, { images: payload.images, files: [...(payload.files || []), ...(payload.uploads || [])], memories: payload.memories });
1174
+ director.sent();
1175
+ sentQueries.set(msg.request_id, { payload, row });
1176
+ while (sentQueries.size > 20) sentQueries.delete(sentQueries.keys().next().value);
1177
+ st.turnStartedAt = Date.now();
1178
+ setRunning(true);
1179
+ sock.send(msg);
1180
+ // Name the conversation like the server will (its first prompt) — not the tab.
1181
+ if (!st.sessionTitle || /^(?:SynaBun|Assistant(?: \d+)?)$/.test(st.sessionTitle)) {
1182
+ const guess = String(payload.text || '').split('\n')[0].trim().slice(0, 60);
1183
+ if (guess) st.sessionTitle = guess;
1184
+ }
1185
+ return true;
1186
+ }
1187
+
1188
+ async function submit(payload) {
1189
+ if (guestBlocked()) return false;
1190
+ const parsed = parseSlashCommand(payload.text);
1191
+ if (parsed && !parsed.passthrough) {
1192
+ const handled = await runSlash(parsed, payload);
1193
+ return handled === false ? false : 'sent';
1194
+ }
1195
+ if (st.running) {
1196
+ st.queue.push(payload);
1197
+ composer.setQueue(st.queue);
1198
+ return 'queued';
1199
+ }
1200
+ return sendQuery(payload) ? 'sent' : false;
1201
+ }
1202
+
1203
+ function clearTranscript() {
1204
+ renderer.clear(); // what was on screen is forgotten with it
1205
+ renderer.showEmpty();
1206
+ }
1207
+
1208
+ async function runSlash(parsed, payload) {
1209
+ switch (parsed.name) {
1210
+ case 'help':
1211
+ renderer.appendUser(payload.text);
1212
+ renderer.appendAssistantMarkdown(slashHelpMarkdown((c) => tt(c.descKey, c.desc)));
1213
+ return true;
1214
+ case 'clear':
1215
+ clearTranscript();
1216
+ return true;
1217
+ case 'compact':
1218
+ compact();
1219
+ return true;
1220
+ case 'new':
1221
+ newSession(parsed.provider || null);
1222
+ return true;
1223
+ case 'resume':
1224
+ if (parsed.sessionId) resume(parsed.sessionId);
1225
+ else sock.send({ type: 'list_sessions' });
1226
+ return true;
1227
+ case 'stop':
1228
+ if (parsed.all) await stopAll();
1229
+ else if (parsed.target) await stopRun(parsed.target);
1230
+ else abort();
1231
+ return true;
1232
+ case 'agents': {
1233
+ const runs = dock.getRuns();
1234
+ const lines = runs.length ? runs.map((r, i) => `- ${fmtRunLine(r, { index: i + 1 })}`) : [`_${tt('assistant.dock.emptyShort', 'No dispatched agents yet.')}_`];
1235
+ renderer.appendUser(payload.text);
1236
+ renderer.appendAssistantMarkdown(`**${tt('assistant.dock.title', 'Agents')}**\n\n${lines.join('\n')}`);
1237
+ if (!parsed.all && runs.length) dock.setOpen(true);
1238
+ return true;
1239
+ }
1240
+ case 'recall':
1241
+ if (parsed.error) { toast(tt('assistant.slash.needsQuery', 'Usage: /recall <query>')); return false; }
1242
+ return queueOrSend({ ...payload, display: payload.text, text: tt('assistant.prompts.recall', 'Use the SynaBun recall tool to search memory for: "{query}". Summarize the most relevant results with their IDs.', { query: parsed.query }) });
1243
+ case 'remember':
1244
+ if (parsed.error) { toast(tt('assistant.slash.needsText', 'Usage: /remember <text>')); return false; }
1245
+ return queueOrSend({ ...payload, display: payload.text, text: tt('assistant.prompts.remember', 'Store this in SynaBun memory with the remember tool (pick a fitting category, project, tags and importance): {text}', { text: parsed.text }) });
1246
+ case 'model': {
1247
+ if (!parsed.model && !parsed.provider) { picker.openField('model'); return true; }
1248
+ const next = { ...st.brain };
1249
+ let requiresSwitch = false;
1250
+ if (parsed.provider && parsed.provider !== st.brain.provider) { next.provider = parsed.provider; next.model = ''; next.effort = null; next.agent = null; next.accountId = null; requiresSwitch = true; }
1251
+ if (parsed.model) next.model = parsed.model;
1252
+ if (parsed.effort) next.effort = parsed.effort === 'off' ? null : parsed.effort;
1253
+ applyBrain(normalizeBrain(next), { requiresSwitch, field: 'model' });
1254
+ picker.refresh().catch(() => {});
1255
+ return true;
1256
+ }
1257
+ case 'project':
1258
+ if (!parsed.path) { picker.openField('project'); return true; }
1259
+ applyBrain({ ...st.brain, project: parsed.path === 'none' ? null : parsed.path }, { requiresSwitch: false, field: 'project' });
1260
+ return true;
1261
+ case 'models':
1262
+ if (parsed.error) { toast(tt('assistant.slash.modelsUsage', 'Usage: /models [list | hide <id…> | show <id…>]')); return false; }
1263
+ if (parsed.action === 'open') { openModels(parsed.tab); return true; }
1264
+ return runModelsSlash(parsed, payload);
1265
+ case 'budget':
1266
+ openBudget();
1267
+ return true;
1268
+ case 'routes':
1269
+ if (parsed.error) { toast(tt('assistant.slash.routesUsage', 'Usage: /routes [always|unsure|never]')); return false; }
1270
+ if (!routingAvailable()) { toast(tt('assistant.slash.routesUnavailable', 'Model routing is not available on this server.')); return true; }
1271
+ if (parsed.mode) setRouteMode(parsed.mode);
1272
+ else openRoutes();
1273
+ return true;
1274
+ case 'computer':
1275
+ if (parsed.error) { toast(tt('assistant.slash.computerUsage', 'Usage: /computer [on|off]')); return false; }
1276
+ if (!computerAvailable()) { toast(tt('assistant.slash.computerUnavailable', 'Computer use is not available here.')); return true; }
1277
+ // A WhatsApp conversation: the command answers with what is in effect and why.
1278
+ if (st.computerRemote) { explainRemoteComputer(); return true; }
1279
+ setComputerUse(parsed.mode ? parsed.mode === 'on' : !st.computerEffective);
1280
+ renderer.appendStatus(st.computerEffective ? tt('assistant.status.computerOn', 'Computer use is on.') : tt('assistant.status.computerOff', 'Computer use is off.'));
1281
+ return true;
1282
+ case 'dispatch': {
1283
+ if (parsed.error) {
1284
+ toast(dispatchErrorText(parsed));
1285
+ return false;
1286
+ }
1287
+ await dispatch(parsed, payload);
1288
+ return true;
1289
+ }
1290
+ default:
1291
+ return queueOrSend(payload);
1292
+ }
1293
+ }
1294
+
1295
+ function dispatchErrorText(parsed) {
1296
+ switch (parsed.error) {
1297
+ case 'missing_provider': return tt('assistant.slash.dispatchProvider', 'Usage: /dispatch <claude|codex|opencode> [flags] <task>');
1298
+ case 'invalid_provider': return tt('assistant.slash.dispatchInvalidProvider', 'Unknown provider "{provider}". Use claude, codex or opencode.', { provider: parsed.provider });
1299
+ case 'unknown_flag': return tt('assistant.slash.dispatchUnknownFlag', 'Unknown flag --{flag}', { flag: parsed.flag });
1300
+ case 'missing_flag_value': return tt('assistant.slash.dispatchFlagValue', 'Flag --{flag} needs a value', { flag: parsed.flag });
1301
+ case 'missing_task': return tt('assistant.slash.dispatchTask', 'Add the task text after the flags');
1302
+ default: return tt('assistant.slash.dispatchBad', 'Could not parse /dispatch');
1303
+ }
1304
+ }
1305
+
1306
+ function queueOrSend(payload) {
1307
+ if (st.running) { st.queue.push(payload); composer.setQueue(st.queue); return true; }
1308
+ return sendQuery(payload);
1309
+ }
1310
+
1311
+ async function dispatch(parsed, payload) {
1312
+ const spec = buildDispatchSpec(parsed, st.brain, { assistantSessionId: sessionId });
1313
+ renderer.appendUser(payload.text);
1314
+ try {
1315
+ const res = await dispatchAssistantRun(spec);
1316
+ const run = runFromPayload(res) || res?.run;
1317
+ if (run) {
1318
+ dock.upsertRun(run);
1319
+ renderer.upsertRunCard(dock.getRun(run.runId) || run);
1320
+ if (res.queued) renderer.appendStatus(tt('assistant.status.queued', 'Queued at position {position} (concurrency limit).', { position: res.position ?? '?' }));
1321
+ } else {
1322
+ renderer.appendStatus(tt('assistant.status.dispatched', 'Dispatched.'));
1323
+ }
1324
+ } catch (err) {
1325
+ renderer.appendError(`${tt('assistant.status.dispatchFailed', 'Dispatch failed')}: ${err?.message || err}`);
1326
+ }
1327
+ }
1328
+
1329
+ function abort() {
1330
+ if (!st.running && !controls.size) return;
1331
+ sock.send({ type: 'abort' });
1332
+ renderer.appendStatus(tt('assistant.status.aborting', 'Aborting…'));
1333
+ }
1334
+
1335
+ function compact() {
1336
+ sock.send({ type: 'compact' });
1337
+ renderer.appendDivider(tt('assistant.compacting', 'compacting…'));
1338
+ if (!st.running) setRunning(true, { label: tt('assistant.compacting', 'compacting…') });
1339
+ }
1340
+
1341
+ function newSession(provider) {
1342
+ const b = provider ? normalizeBrain({ ...st.brain, provider, model: '', effort: null, agent: null, accountId: null }) : st.brain;
1343
+ emit('assistant:new', { brain: b, host: hostId });
1344
+ }
1345
+
1346
+ function resume(id) {
1347
+ if (!id) return;
1348
+ if (String(id).startsWith('assistant-')) { emit('assistant:resume', { sessionId: id, host: hostId }); return; }
1349
+ sock.send({ type: 'resume', providerSessionId: id });
1350
+ renderer.appendDivider(tt('assistant.status.resuming', 'resuming {id}', { id: String(id).slice(0, 8) }));
1351
+ }
1352
+
1353
+ function applyBrain(next, meta = {}) {
1354
+ const before = st.brain;
1355
+ st.brain = normalizeBrain(next);
1356
+ const applied = st.brain;
1357
+ picker.render();
1358
+ host.setBrain?.(st.brain);
1359
+ persist();
1360
+ if (meta.silent) return;
1361
+ if (meta.requiresSwitch) {
1362
+ const b = st.brain;
1363
+ sock.send({ type: 'switch_brain', brain: b.provider, model: b.model || undefined, effort: b.effort || undefined, accountId: b.accountId || undefined, mcpProfile: b.mcpProfile || undefined, cwd: b.project || undefined, agent: b.agent || undefined, permissionMode: b.permissionMode, planMode: b.planMode === true });
1364
+ renderer.appendDivider(`${tt('assistant.status.brain', 'brain')} → ${brainLabel(b)}`);
1365
+ renderer.forgetSeen(); // the new brain's ids start over
1366
+ } else if (before.permissionMode !== st.brain.permissionMode || !!before.planMode !== !!st.brain.planMode) {
1367
+ sock.send({ type: 'set_permission_mode', mode: st.brain.permissionMode, planMode: st.brain.planMode === true });
1368
+ }
1369
+ if (before.project !== st.brain.project) renderer.refreshEmpty();
1370
+ patchAssistantSession(sessionId, { brain: st.brain }).catch((err) => {
1371
+ // Refused (a model disabled in Assistant → Models): undo, so the refused
1372
+ // brain is neither shown nor saved as the last used one — the next start
1373
+ // would ask for it again. A brain switch already got the error on the socket.
1374
+ if (err?.code !== 'MODEL_DISABLED' || destroyed || st.brain !== applied) return;
1375
+ st.brain = before;
1376
+ picker.render();
1377
+ host.setBrain?.(before);
1378
+ persist();
1379
+ if (!meta.requiresSwitch) renderer.appendError(err.message || String(err));
1380
+ });
1381
+ }
1382
+
1383
+ // ── Runs ──
1384
+ async function focusRun(runId) {
1385
+ if (!runId) return;
1386
+ focusAssistantRun(runId).catch(() => {});
1387
+ try { await focusNativeLoopRun(runId); } catch { /* router not ready */ }
1388
+ }
1389
+
1390
+ async function stopRun(runId) {
1391
+ if (!runId) return;
1392
+ try { await stopAssistantRun(runId); toast(tt('assistant.status.stopRequested', 'Stop requested for {id}', { id: String(runId).slice(0, 8) })); }
1393
+ catch (err) { renderer.appendError(`${tt('assistant.status.stopFailed', 'Stop failed')}: ${err?.message || err}`); }
1394
+ }
1395
+
1396
+ async function removeRun(runId) {
1397
+ if (!runId) return;
1398
+ try {
1399
+ const res = await removeAssistantRun(runId, sessionId);
1400
+ dock.dropRuns(res?.removed?.length ? res.removed : [runId]);
1401
+ } catch (err) {
1402
+ renderer.appendError(`${tt('assistant.run.removeFailed', 'Could not remove the run')}: ${err?.message || err}`);
1403
+ }
1404
+ }
1405
+
1406
+ async function clearFinished() {
1407
+ try {
1408
+ const res = await clearFinishedAssistantRuns(sessionId);
1409
+ dock.dropRuns(res?.removed || []);
1410
+ } catch (err) {
1411
+ renderer.appendError(`${tt('assistant.run.removeFailed', 'Could not remove the run')}: ${err?.message || err}`);
1412
+ }
1413
+ }
1414
+
1415
+ async function stopAll() {
1416
+ try { await killAllAssistantRuns(sessionId); toast(tt('assistant.status.stopAllRequested', 'Stopping all dispatched agents…')); }
1417
+ catch (err) { renderer.appendError(`${tt('assistant.status.stopFailed', 'Stop failed')}: ${err?.message || err}`); }
1418
+ }
1419
+
1420
+ async function escalateRun(runId, target) {
1421
+ if (!runId) return;
1422
+ try {
1423
+ const res = await escalateAssistantRun(runId, target || undefined);
1424
+ const run = runFromPayload(res) || res?.run;
1425
+ if (run) { dock.upsertRun(run); renderer.upsertRunCard(dock.getRun(run.runId) || run); }
1426
+ renderer.appendStatus(tt('assistant.status.escalated', 'Escalated {id}.', { id: String(runId).slice(0, 8) }));
1427
+ const current = dock.getRun(runId);
1428
+ if (current?.escalation) {
1429
+ const patched = dock.patchRun(runId, { escalation: { ...current.escalation, started: true } });
1430
+ if (patched) renderer.upsertRunCard(patched);
1431
+ }
1432
+ } catch (err) {
1433
+ renderer.appendError(`${tt('assistant.status.escalateFailed', 'Escalation failed')}: ${err?.message || err}`);
1434
+ }
1435
+ }
1436
+
1437
+ async function readRun(runId) {
1438
+ if (!runId) return;
1439
+ try {
1440
+ let result = null;
1441
+ try { result = await getAssistantRunResult(runId); } catch { /* not finished yet */ }
1442
+ const payload = result?.result ?? result;
1443
+ const run = dock.getRun(runId) || runFromPayload(await getAssistantRun(runId).catch(() => null));
1444
+ const head = `**${tt('assistant.run.resultOf', 'Result of')} ${run?.title || String(runId).slice(0, 8)}**`;
1445
+ let body = '';
1446
+ if (payload && typeof payload === 'object') {
1447
+ const bits = [];
1448
+ if (payload.status) bits.push(`_${payload.status}_`);
1449
+ if (payload.summary) bits.push(payload.summary);
1450
+ if (Array.isArray(payload.files) && payload.files.length) bits.push(`${tt('assistant.run.files', 'Files')}: ${payload.files.map(f => `\`${f}\``).join(', ')}`);
1451
+ // Image / video creation: each generated file, images shown inline, with its path.
1452
+ const media = (Array.isArray(payload.media) ? payload.media : []).filter(m => m?.url && /^\/api\/assistant\/runs\//.test(String(m.url)));
1453
+ if (media.length) bits.push(media.map(m => (m.kind === 'image' ? `![](${m.url})\n\`${m.path}\`` : `[${tt('assistant.run.mediaVideoLink', 'Video')}](${m.url}) · \`${m.path}\``)).join('\n\n'));
1454
+ if (Array.isArray(payload.follow_ups) && payload.follow_ups.length) bits.push(`${tt('assistant.run.followUps', 'Follow-ups')}:\n${payload.follow_ups.map(f => `- ${f}`).join('\n')}`);
1455
+ if (payload.question) bits.push(`**${tt('assistant.run.question', 'Question')}:** ${payload.question}`);
1456
+ if (!bits.length && payload.raw) bits.push(String(payload.raw).slice(0, 4000));
1457
+ if (!bits.length && payload.text) bits.push(String(payload.text).slice(0, 4000));
1458
+ body = bits.join('\n\n');
1459
+ } else if (typeof payload === 'string') body = payload.slice(0, 4000);
1460
+ if (!body) body = `_${tt('assistant.run.noResult', 'No result yet')}_ · ${run ? fmtRunLine(run) : runId}`;
1461
+ renderer.appendAssistantMarkdown(`${head}\n\n${body}`);
1462
+ if (run) renderer.upsertRunCard(run, { result: payload && typeof payload === 'object' ? payload : null });
1463
+ } catch (err) {
1464
+ renderer.appendError(`${tt('assistant.run.readFailed', 'Could not read result')}: ${err?.message || err}`);
1465
+ }
1466
+ }
1467
+
1468
+ // ── Menus ──
1469
+ function showSessionsMenu(list) {
1470
+ const sessions = (Array.isArray(list) ? list : []).filter(s => s?.id);
1471
+ const items = [
1472
+ { id: 'new', label: tt('assistant.topbar.new', 'New assistant session'), desc: brainLabel(st.brain), icon: ICON_NEW, onSelect: () => newSession(null) },
1473
+ { kind: 'separator' },
1474
+ ];
1475
+ if (!sessions.length) items.push({ kind: 'info', label: tt('assistant.sessions.empty', 'No other sessions') });
1476
+ else items.push({ kind: 'header', label: tt('assistant.sessions.recent', 'Recent') });
1477
+ for (const s of sessions.slice(0, 30)) {
1478
+ const isCurrent = s.id === sessionId;
1479
+ const b = s.brain && typeof s.brain === 'object' ? s.brain : { provider: s.brain, model: s.model, effort: s.effort };
1480
+ items.push({
1481
+ kind: 'radio',
1482
+ id: s.id,
1483
+ label: s.title || s.label || s.id,
1484
+ desc: [brainLabel(b), relativeTime(s.updatedAt || s.createdAt), s.status].filter(Boolean).join(' · '),
1485
+ selected: isCurrent,
1486
+ onSelect: () => { if (!isCurrent) emit('assistant:resume', { sessionId: s.id, host: hostId }); },
1487
+ });
1488
+ }
1489
+ openMenu(sessionsBtn, {
1490
+ title: tt('assistant.topbar.sessions', 'Sessions'),
1491
+ items,
1492
+ role: 'menu',
1493
+ placement: 'below',
1494
+ width: 300,
1495
+ filter: sessions.length > 8,
1496
+ filterPlaceholder: tt('assistant.sessions.filter', 'Filter sessions…'),
1497
+ });
1498
+ }
1499
+
1500
+ function engineText() {
1501
+ if (!st.engine) return '';
1502
+ return `${st.engine}${st.sdkVersion ? ` ${st.sdkVersion}` : ''}`;
1503
+ }
1504
+
1505
+ /** Shown in the bar right now (container queries hide controls without touching attributes). */
1506
+ function isRendered(node) {
1507
+ return !!node && !node.hidden && node.isConnected && node.getClientRects().length > 0;
1508
+ }
1509
+
1510
+ /** The toolbar "⋯": whatever the bar cannot show at this width, then conversation actions. */
1511
+ function openMore() {
1512
+ const fields = picker.fieldSummary().filter((f) => !f.hidden && !isRendered(picker.buttons[f.field]));
1513
+ const routeHidden = routeChip.isVisible() && !isRendered(routeChip.el);
1514
+ const computerHidden = !computerToggle.el.hidden && !isRendered(computerToggle.el);
1515
+ const agentsHidden = !agentsBtn.hidden && !isRendered(agentsBtn);
1516
+ const items = [];
1517
+ if (fields.length || routeHidden || computerHidden || agentsHidden) {
1518
+ items.push({ kind: 'header', label: tt('assistant.toolbar.conversation', 'This conversation') });
1519
+ for (const f of fields) {
1520
+ items.push({ id: `field-${f.field}`, label: f.label, desc: f.value, icon: f.icon, color: f.color, onSelect: () => picker.openField(f.field, moreBtn) });
1521
+ }
1522
+ if (routeHidden) {
1523
+ items.push({ id: 'route-mode', label: tt('assistant.routes.title', 'Model routing'), desc: routeModeLabel(routeChip.mode(), t), icon: ICON_ROUTE, onSelect: () => routeChip.open(moreBtn) });
1524
+ }
1525
+ if (computerHidden) {
1526
+ items.push({
1527
+ kind: 'check',
1528
+ id: 'computer',
1529
+ label: tt('assistant.computer.use', 'Computer use'),
1530
+ // A WhatsApp conversation: the item shows what is in effect and why; picking it explains, never toggles.
1531
+ ...(st.computerRemote ? { desc: computerRemoteView(st.computerRemote, t)?.tip || '' } : {}),
1532
+ icon: ICON_COMPUTER,
1533
+ checked: st.computerRemote ? computerRemoteView(st.computerRemote, t)?.on === true : st.computerEffective,
1534
+ disabled: st.desktopSupported === false,
1535
+ onSelect: () => setComputerUse(!st.computerEffective),
1536
+ });
1537
+ }
1538
+ if (agentsHidden) {
1539
+ items.push({ kind: 'check', id: 'agents', label: tt('assistant.topbar.agents', 'Dispatched agents'), desc: agentsCount.textContent, icon: ICON_AGENTS, checked: dock.isOpen(), onSelect: () => toggleDock() });
1540
+ }
1541
+ items.push({ kind: 'separator' });
1542
+ }
1543
+ items.push({ id: 'attach', label: tt('assistant.composer.attach', 'Attach files'), onSelect: () => composer.openFilePicker() });
1544
+ items.push({ id: 'compact', label: tt('assistant.topbar.compact', 'Compact context'), onSelect: () => compact() });
1545
+ if (routingAvailable()) items.push({ id: 'routes', label: tt('assistant.routes.editor', 'Model routes…'), icon: ICON_ROUTE, onSelect: () => openRoutes() });
1546
+ items.push({ id: 'models', label: tt('assistant.models.menu', 'Models…'), onSelect: () => openModels() });
1547
+ items.push({ id: 'budget', label: tt('assistant.budget.menu', 'Budget…'), desc: st.budget ? `${fmtCost(st.budget.totalUsd) || '$0.00'} / ${fmtCost(st.budget.hardUsd)}` : '', onSelect: () => openBudget() });
1548
+ items.push({ id: 'clear', label: tt('assistant.topbar.clear', 'Clear transcript'), onSelect: () => clearTranscript() });
1549
+ items.push({ id: 'help', label: tt('assistant.topbar.helpShortcuts', 'Help & shortcuts'), onSelect: () => openHelp() });
1550
+ items.push({ kind: 'separator' });
1551
+ if (engineText()) items.push({ kind: 'info', label: tt('assistant.topbar.engine', 'Engine'), value: engineText() });
1552
+ items.push({ kind: 'info', label: tt('assistant.topbar.cost', 'Session cost'), value: fmtCost(totalCost()) || '$0.00' });
1553
+ if (Number(st.dispatchUsd) > 0) items.push({ kind: 'info', label: tt('assistant.topbar.agentsCost', 'Agents'), value: fmtCost(st.dispatchUsd) });
1554
+ openMenu(moreBtn, { label: tt('assistant.toolbar.more', 'More options'), items, role: 'menu', placement: 'below', width: 280 });
1555
+ }
1556
+
1557
+ // Assistant → Models: switch models off (hidden from routing, dispatch and every model menu).
1558
+ function openModels(tab = 'available') {
1559
+ openModelsManager({
1560
+ t,
1561
+ tab,
1562
+ providerIcon,
1563
+ getBrain: () => st.brain,
1564
+ onToast: toast,
1565
+ onChanged: () => picker.refresh(true).catch(() => {}),
1566
+ }).catch(() => {});
1567
+ }
1568
+
1569
+ /** /models list|hide|show: one PATCH, then a short report (the server resolves ids and providers). */
1570
+ async function runModelsSlash(parsed, payload) {
1571
+ renderer.appendUser(payload.text);
1572
+ try {
1573
+ if (parsed.action === 'list') {
1574
+ // Per provider: how many are on, then every archived id (context variants
1575
+ // are their own ids; "not listed" = no provider lists it any more).
1576
+ const data = await fetchManageCatalog();
1577
+ const lines = Object.entries(data?.models || {}).map(([provider, rows]) => {
1578
+ const list = Array.isArray(rows) ? rows : [];
1579
+ const archived = Array.isArray(data?.archived?.[provider]) ? data.archived[provider] : [];
1580
+ const ids = archived.slice(0, 60).map((a) => `\`${a.id}\`${a.listed ? '' : ` (${tt('assistant.models.notListed', 'not listed')})`}`);
1581
+ if (archived.length > ids.length) ids.push(tt('assistant.models.moreArchived', '+{count} more — /models archived', { count: String(archived.length - ids.length) }));
1582
+ const on = tt('assistant.models.countOn', '{on} of {total} on', { on: String(list.filter((r) => !r.hidden).length), total: String(list.length) });
1583
+ return `- **${provider}** — ${on}; ${tt('assistant.models.tabArchived', 'Archived')}: ${ids.length ? ids.join(', ') : tt('assistant.models.noneHidden', 'none off')}`;
1584
+ });
1585
+ renderer.appendAssistantMarkdown(`**${tt('assistant.models.title', 'Models')}**\n\n${lines.join('\n')}`);
1586
+ return true;
1587
+ }
1588
+ const res = await patchHiddenModels({ [parsed.action]: parsed.ids });
1589
+ const done = (res?.applied || []).map((a) => `\`${a.provider}/${a.id}\``).join(', ');
1590
+ renderer.appendStatus(parsed.action === 'hide'
1591
+ ? tt('assistant.models.hiddenNow', 'Switched off: {models}', { models: done })
1592
+ : tt('assistant.models.shownNow', 'Switched on: {models}', { models: done }));
1593
+ picker.refresh(true).catch(() => {});
1594
+ return true;
1595
+ } catch (err) {
1596
+ renderer.appendError(`${tt('assistant.models.saveFailed', 'Could not update the model')}: ${err?.message || err}`);
1597
+ return true;
1598
+ }
1599
+ }
1600
+
1601
+ // Assistant → Budget: the money caps (applied live by the server).
1602
+ function openBudget() {
1603
+ openBudgetEditor({
1604
+ t,
1605
+ sessionId,
1606
+ brainMetered: st.brainMetered !== false && st.brain?.provider !== 'codex',
1607
+ onToast: toast,
1608
+ onSaved: (res) => { if (res?.session) { st.budget = res.session; renderCost(); } },
1609
+ }).catch(() => {});
1610
+ }
1611
+
1612
+ function openRoutes() {
1613
+ openRoutesEditor({
1614
+ t,
1615
+ providerIcon,
1616
+ getBrain: () => st.brain,
1617
+ brainName: () => brainModelLabel(),
1618
+ onToast: toast,
1619
+ onSaved: (routing) => {
1620
+ if (routing?.defaultMode) {
1621
+ const def = normalizeRouteMode(routing.defaultMode, DEFAULT_ROUTE_MODE);
1622
+ st.routing = { ...(st.routing || {}), defaultMode: def, effectiveMode: st.routeMode || def };
1623
+ routeChip.render();
1624
+ }
1625
+ toast(tt('assistant.routes.saved', 'Model routes saved.'));
1626
+ },
1627
+ }).catch(() => {});
1628
+ }
1629
+
1630
+ // ── Help ──
1631
+ function openHelp() {
1632
+ if (helpModal) { helpModal.remove(); helpModal = null; return; }
1633
+ const overlay = el('div', 'asst-modal-overlay');
1634
+ overlay.setAttribute('role', 'dialog');
1635
+ overlay.setAttribute('aria-modal', 'true');
1636
+ overlay.setAttribute('aria-label', tt('assistant.help.title', 'Assistant help'));
1637
+ const modal = el('div', 'asst-modal');
1638
+ modal.innerHTML = `<div class="asst-modal-head"><span class="asst-icon">${ICON_ASSISTANT}</span><span>${esc(tt('assistant.help.title', 'Assistant help'))}</span><button type="button" class="asst-iconbtn asst-modal-close" aria-label="${esc(tt('common.close', 'Close'))}">${ICON_CLOSE}</button></div><div class="asst-modal-body asst-md"></div>`;
1639
+ const body = modal.querySelector('.asst-modal-body');
1640
+ const keys = [
1641
+ `- \`Enter\` — ${tt('assistant.help.keySend', 'send')} · \`Shift+Enter\` — ${tt('assistant.help.keyNewline', 'new line')} · \`Esc\` — ${tt('assistant.help.keyAbort', 'abort the running turn')}`,
1642
+ `- ${tt('assistant.help.keyRoute', 'Route card: `1`–`4` choose · `↑`/`↓` move · `Enter` runs · `Esc` cancels')}`,
1643
+ `- ${tt('assistant.help.keyComputer', '`Esc` anywhere stops computer control immediately')}`,
1644
+ `- ${tt('assistant.help.keyToolbar', 'Toolbar: `←`/`→` move between controls · `Home`/`End` jump to the ends')}`,
1645
+ `- ${tt('assistant.help.keyDrop', 'Drop files, memories from the graph or images from the whiteboard onto the composer to attach them.')}`,
1646
+ ];
1647
+ mdInto(body, `${tt('assistant.help.intro', 'The assistant thinks with the selected brain (Claude, Codex or OpenCode), recalls memories on every turn and can dispatch work to the sidepanel agents.')}\n\n${slashHelpMarkdown((c) => tt(c.descKey, c.desc))}\n\n**${tt('assistant.help.keys', 'Keys')}**\n\n${keys.join('\n')}`);
1648
+ overlay.appendChild(modal);
1649
+ const returnTo = document.activeElement;
1650
+ const close = () => {
1651
+ overlay.remove();
1652
+ helpModal = null;
1653
+ document.removeEventListener('keydown', onKey, true);
1654
+ if (returnTo?.isConnected) { try { returnTo.focus({ preventScroll: true }); } catch { /* ignore */ } }
1655
+ };
1656
+ const onKey = (e) => { if (e.key === 'Escape') { e.preventDefault(); e.stopPropagation(); close(); } else e.stopPropagation(); };
1657
+ document.addEventListener('keydown', onKey, true);
1658
+ overlay.addEventListener('mousedown', (e) => { if (e.target === overlay) close(); });
1659
+ modal.querySelector('.asst-modal-close').addEventListener('click', close);
1660
+ document.body.appendChild(overlay);
1661
+ helpModal = overlay;
1662
+ modal.querySelector('.asst-modal-close').focus();
1663
+ }
1664
+
1665
+ // ── Toolbar wiring (right group) ──
1666
+ sessionsBtn.addEventListener('click', async (e) => {
1667
+ e.stopPropagation();
1668
+ if (isMenuOpen(sessionsBtn)) { closeMenu(); return; }
1669
+ let list = [];
1670
+ try { list = await listAssistantSessions(); } catch { /* server not ready */ }
1671
+ if (!destroyed) showSessionsMenu(list);
1672
+ });
1673
+ function toggleDock() {
1674
+ dock.setOpen(!dock.isOpen());
1675
+ agentsBtn.setAttribute('aria-pressed', dock.isOpen() ? 'true' : 'false');
1676
+ }
1677
+ agentsBtn.addEventListener('click', () => toggleDock());
1678
+ newBtn.addEventListener('click', () => newSession(null));
1679
+ moreBtn.addEventListener('click', (e) => {
1680
+ e.stopPropagation();
1681
+ if (isMenuOpen(moreBtn)) { closeMenu(); return; }
1682
+ openMore();
1683
+ });
1684
+ // The spend chip opens the Budget tab.
1685
+ costEl.addEventListener('click', () => openBudget());
1686
+
1687
+ // Esc stops computer control from anywhere, before any other handler runs.
1688
+ const onEscCapture = (e) => {
1689
+ if (e.key !== 'Escape' || destroyed) return;
1690
+ if (computerPanel.isActive()) computerPanel.stop('esc');
1691
+ };
1692
+ document.addEventListener('keydown', onEscCapture, true);
1693
+
1694
+ // Esc inside the assistant aborts a running turn — after route cards, menus,
1695
+ // the slash list and the composer had their turn (they preventDefault), so
1696
+ // the same key cancels a card instead of the turn. Both hosts rely on this.
1697
+ const onRootKey = (e) => {
1698
+ if (e.key !== 'Escape' || e.defaultPrevented || destroyed || !st.running) return;
1699
+ e.preventDefault();
1700
+ e.stopPropagation();
1701
+ abort();
1702
+ };
1703
+ // A toolbar placed in the host's header is still part of the assistant.
1704
+ const surfaces = root.contains(barEl) ? [root] : [root, barEl];
1705
+ surfaces.forEach((node) => node.addEventListener('keydown', onRootKey));
1706
+
1707
+ // Last-used tracking for Apps → Assistant / `a` / Ctrl+A (ui-assistant.js).
1708
+ const onFocusIn = () => { if (!destroyed) { emit('assistant:focused', { sessionId, host: hostId }); director.touch(); } };
1709
+ surfaces.forEach((node) => node.addEventListener('focusin', onFocusIn));
1710
+
1711
+ // ── Bus ──
1712
+ unsubs.push(on('sync:assistant:session-updated', (msg) => { const s = msg?.session || msg; if (s?.id === sessionId) applySessionMeta(s); }));
1713
+ unsubs.push(on('sync:assistant:cost', (msg) => { if (!msg?.assistantSessionId || msg.assistantSessionId === sessionId) applyCost(msg); }));
1714
+ unsubs.push(on('sync:assistant:mailbox', (msg) => { if (msg?.assistantSessionId === sessionId) renderer.appendMailbox(msg.items || msg.events || []); }));
1715
+ unsubs.push(on('sync:assistant:permission-request', (msg) => {
1716
+ const run = runFromPayload(msg);
1717
+ const runId = msg?.runId || run?.runId || null;
1718
+ const owned = (msg?.assistantSessionId || run?.assistantSessionId) === sessionId || (runId && dock.getRun(runId));
1719
+ if (!owned) return;
1720
+ const requestId = msg.request_id || msg.requestId || msg.request?.requestId;
1721
+ if (!requestId || controls.has(String(requestId))) return;
1722
+ const provider = msg.provider || dock.getRun(runId)?.provider || null;
1723
+ showControl({ request_id: requestId, request: msg.request, provider, runId }, { runId, provider });
1724
+ }));
1725
+ unsubs.push(on('sync:assistant:permission-resolved', (msg) => {
1726
+ const requestId = msg?.request_id || msg?.requestId;
1727
+ if (requestId) lockControl(requestId, resolvedLabel(msg.resolvedBy));
1728
+ }));
1729
+ unsubs.push(on('sync:assistant:desktop', (msg) => computerPanel.applyDesktop(msg)));
1730
+ unsubs.push(on('sync:assistant:desktop-setup', (msg) => computerPanel.applySetup(msg)));
1731
+ // Models switched on/off elsewhere: the open Models manager reloads (the menus drop their cache on their own).
1732
+ unsubs.push(on('sync:assistant:hidden-models-changed', () => refreshModelsManager()));
1733
+ unsubs.push(on('sync:opencode:hidden-models-changed', () => refreshModelsManager()));
1734
+ unsubs.push(on('sync:assistant:routing-changed', (msg) => {
1735
+ const routing = msg?.routing || msg;
1736
+ const def = normalizeRouteMode(routing?.defaultMode, null);
1737
+ if (!def) return;
1738
+ st.routing = { ...(st.routing || {}), defaultMode: def, effectiveMode: st.routeMode || def };
1739
+ routeChip.render();
1740
+ }));
1741
+ unsubs.push(on('session:info', () => updateGuest()));
1742
+ unsubs.push(on('permissions:changed', () => updateGuest()));
1743
+
1744
+ function updateGuest() {
1745
+ composer.setDisabled(guestBlocked(), tt('assistant.composer.guestBlocked', 'Assistant access is disabled by the host'));
1746
+ }
1747
+
1748
+ // ── Rehydration ──
1749
+ async function rehydrate({ force = false } = {}) {
1750
+ try {
1751
+ const data = await getAssistantSession(sessionId);
1752
+ const meta = data?.session || data;
1753
+ applySessionMeta(meta);
1754
+ const transcript = data?.transcript || data?.messages || meta?.transcript || [];
1755
+ if (force) renderer.clear();
1756
+ if (Array.isArray(transcript) && transcript.length) renderer.renderTranscript(transcript);
1757
+ renderer.showEmpty(); // no-op once the transcript has rows
1758
+ for (const run of (Array.isArray(data?.runs) ? data.runs : [])) dock.upsertRun(run);
1759
+ } catch (err) {
1760
+ console.warn('[assistant] could not restore the conversation', err);
1761
+ renderer.showEmpty();
1762
+ }
1763
+ }
1764
+
1765
+ // ── Boot ──
1766
+ syncTabLabel(); // legacy "Assistant 11" / first-prompt tab labels become "SynaBun"
1767
+ renderCost();
1768
+ renderStatus();
1769
+ updateGuest();
1770
+ host.setBrain?.(st.brain);
1771
+ if (session && typeof session === 'object') applySessionMeta(session);
1772
+ else applyFeatures();
1773
+ rehydrate().finally(() => {
1774
+ if (destroyed) return;
1775
+ probeFeatures();
1776
+ applyLastUsedRouteMode();
1777
+ sock.connect();
1778
+ });
1779
+ fetchUsage();
1780
+
1781
+ const controller = {
1782
+ sessionId,
1783
+ el: root,
1784
+ getBrain: () => st.brain,
1785
+ setBrain: (b, meta) => applyBrain(b, meta || { requiresSwitch: false, field: 'external' }),
1786
+ getTitle: () => st.title,
1787
+ setTitle: (text) => { st.userRenamed = true; setTitle(text); },
1788
+ isRunning: () => st.running,
1789
+ isConnected: () => st.connected,
1790
+ send: (text, extra = {}) => submit({ text: String(text ?? ''), images: extra.images || [], files: extra.files || [], uploads: extra.uploads || [], memories: extra.memories || [] }),
1791
+ abort,
1792
+ compact,
1793
+ newSession,
1794
+ resume,
1795
+ setRouteMode: (mode) => setRouteMode(mode),
1796
+ setComputerUse: (enabled) => setComputerUse(enabled),
1797
+ attachMemory: (node) => composer.addMemory(node),
1798
+ attachImageDataUrl: (dataUrl, name) => composer.addImageDataUrl(dataUrl, name),
1799
+ insertText: (text) => composer.insertText(text),
1800
+ /** A file path from the explorer ("send to panel") → the composer, as inline code. */
1801
+ attachPath: (path) => { if (path) { composer.insertText(`\`${path}\` `); composer.focus(); } },
1802
+ /** The server's name for the conversation (first prompt) — tooltips, never the tab label. */
1803
+ getConversationTitle: () => st.sessionTitle || '',
1804
+ getStatus: () => st.status || 'idle',
1805
+ focus: () => composer.focus(),
1806
+ onShown: () => { syncTabLabel(); renderer.scrollEnd(true); if (visible()) composer.focus(); director.touch(); director.visibilityChanged(); },
1807
+ onHidden: () => { closeMenu(); picker.close(); director.stopTyping(); director.visibilityChanged(); },
1808
+ counts: () => dock.counts(),
1809
+ destroy: ({ closeServer = false } = {}) => {
1810
+ if (destroyed) return;
1811
+ destroyed = true;
1812
+ if (persistTimer) { clearTimeout(persistTimer); persistTimer = null; }
1813
+ clearTimeout(announceTimer);
1814
+ if (replay) { clearTimeout(replay.timer); replay = null; }
1815
+ director.destroy();
1816
+ usage.destroy();
1817
+ clearTimeout(limitTimer);
1818
+ try { storage.setItem(tabStorageKey(sessionId), JSON.stringify(serializeTabState(st))); } catch { /* ignore */ }
1819
+ unsubs.forEach(off => { try { off(); } catch { /* ignore */ } });
1820
+ document.removeEventListener('keydown', onEscCapture, true);
1821
+ surfaces.forEach((node) => { node.removeEventListener('keydown', onRootKey); node.removeEventListener('focusin', onFocusIn); });
1822
+ closeMenu();
1823
+ closeFrameLightbox();
1824
+ if (helpModal) { helpModal.remove(); helpModal = null; }
1825
+ for (const card of routeCards.values()) card.destroy();
1826
+ routeCards.clear();
1827
+ sock.close();
1828
+ roving.destroy();
1829
+ picker.destroy();
1830
+ routeChip.destroy();
1831
+ computerToggle.destroy();
1832
+ computerPanel.destroy();
1833
+ dock.destroy();
1834
+ composer.destroy();
1835
+ renderer.destroy();
1836
+ barEl.remove(); // it may live in the host's header
1837
+ root.remove();
1838
+ if (closeServer) {
1839
+ closeAssistantSession(sessionId).catch(() => {});
1840
+ try { storage.removeItem(tabStorageKey(sessionId)); } catch { /* ignore */ }
1841
+ }
1842
+ },
1843
+ };
1844
+ return controller;
1845
+ }