@agentprojectcontext/apx 1.24.0 → 1.27.1

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 (718) hide show
  1. package/package.json +42 -12
  2. package/skills/apx/SKILL.md +50 -119
  3. package/skills/apx-agency-agents/SKILL.md +141 -0
  4. package/skills/apx-agent/SKILL.md +100 -0
  5. package/skills/apx-mcp/SKILL.md +114 -0
  6. package/skills/apx-mcp-builder/SKILL.md +186 -0
  7. package/skills/apx-project/SKILL.md +100 -0
  8. package/skills/apx-routine/SKILL.md +138 -0
  9. package/skills/apx-runtime/SKILL.md +115 -0
  10. package/skills/apx-sessions/SKILL.md +281 -0
  11. package/skills/apx-skill-builder/SKILL.md +149 -0
  12. package/skills/apx-task/SKILL.md +95 -0
  13. package/skills/apx-telegram/SKILL.md +115 -0
  14. package/skills/apx-voice/SKILL.md +135 -0
  15. package/src/core/agent/constants.js +3 -0
  16. package/src/core/agent/ghost-guard.js +24 -0
  17. package/src/core/agent/index.js +35 -0
  18. package/src/core/agent/model-router.js +257 -0
  19. package/src/core/agent/prompt-builder.js +313 -0
  20. package/src/core/agent/prompts/channels/api.md +7 -0
  21. package/src/core/agent/prompts/channels/cli.md +6 -0
  22. package/src/core/agent/prompts/channels/code.md +20 -0
  23. package/src/core/agent/prompts/channels/deck.md +6 -0
  24. package/src/core/agent/prompts/channels/desktop.md +24 -0
  25. package/src/core/agent/prompts/channels/routine.md +9 -0
  26. package/src/core/agent/prompts/channels/telegram.md +9 -0
  27. package/src/core/agent/prompts/channels/terminal.md +16 -0
  28. package/src/core/agent/prompts/channels/web.md +7 -0
  29. package/src/core/agent/prompts/channels/web_sidebar.md +7 -0
  30. package/src/core/agent/prompts/modes/voice.md +4 -0
  31. package/src/core/agent/prompts/super-agent-base.md +42 -0
  32. package/src/core/agent/pseudo-tools.js +40 -0
  33. package/src/core/agent/retry.js +85 -0
  34. package/src/core/agent/run-agent.js +423 -0
  35. package/src/core/agent/self-memory.js +155 -0
  36. package/src/{daemon → core/agent}/tool-call-parser.js +83 -10
  37. package/src/core/agent/tools-overlap.js +66 -0
  38. package/src/core/apc-skill-sync.js +97 -0
  39. package/src/core/code-sessions-store.js +150 -0
  40. package/src/core/config.js +473 -11
  41. package/src/core/desktop/autostart.js +162 -0
  42. package/src/core/engines/_health.js +63 -0
  43. package/src/{daemon → core}/engines/anthropic.js +16 -0
  44. package/src/core/engines/gemini.js +168 -0
  45. package/src/core/engines/groq.js +8 -0
  46. package/src/{daemon → core}/engines/index.js +3 -1
  47. package/src/{daemon → core}/engines/mock.js +22 -0
  48. package/src/{daemon → core}/engines/ollama.js +35 -0
  49. package/src/core/engines/openai-compatible.js +145 -0
  50. package/src/core/engines/openai.js +8 -0
  51. package/src/core/engines/openrouter.js +8 -0
  52. package/src/core/git-baseline.js +147 -0
  53. package/src/core/identity.js +21 -0
  54. package/src/core/logging.js +1 -1
  55. package/src/core/mcp/index.js +14 -0
  56. package/src/{daemon/mcp-runner.js → core/mcp/runner.js} +18 -6
  57. package/src/core/mcp/sources.js +246 -0
  58. package/src/core/memory/active-threads.js +124 -0
  59. package/src/core/memory/broker.js +144 -0
  60. package/src/core/memory/compactor.js +186 -0
  61. package/src/core/memory/embed-engines/gemini.js +62 -0
  62. package/src/core/memory/embed-engines/index.js +148 -0
  63. package/src/core/memory/embed-engines/ollama.js +55 -0
  64. package/src/core/memory/embed-engines/openai.js +64 -0
  65. package/src/core/memory/embed-engines/tf.js +20 -0
  66. package/src/core/memory/embeddings.js +132 -0
  67. package/src/core/memory/index.js +161 -0
  68. package/src/core/memory/indexer.js +257 -0
  69. package/src/core/memory/store.js +231 -0
  70. package/src/core/messages-store.js +143 -25
  71. package/src/core/parser.js +78 -16
  72. package/src/core/scaffold.js +175 -79
  73. package/src/core/tasks-store.js +264 -0
  74. package/src/core/telegram-identity.js +126 -0
  75. package/src/core/tools/index.js +6 -0
  76. package/src/core/voice/engines/elevenlabs.js +96 -0
  77. package/src/core/voice/engines/gemini.js +148 -0
  78. package/src/core/voice/engines/index.js +144 -0
  79. package/src/core/voice/engines/mock.js +59 -0
  80. package/src/core/voice/engines/openai.js +82 -0
  81. package/src/core/voice/engines/piper.js +93 -0
  82. package/src/core/voice/index.js +3 -0
  83. package/src/core/voice/tts.js +89 -0
  84. package/src/{daemon → host/daemon}/apc-runtime-context.js +1 -1
  85. package/src/host/daemon/api/admin-config.js +159 -0
  86. package/src/host/daemon/api/admin.js +72 -0
  87. package/src/host/daemon/api/agents.js +284 -0
  88. package/src/host/daemon/api/artifacts.js +52 -0
  89. package/src/host/daemon/api/code.js +351 -0
  90. package/src/host/daemon/api/config.js +104 -0
  91. package/src/host/daemon/api/connections.js +42 -0
  92. package/src/host/daemon/api/conversations.js +161 -0
  93. package/src/host/daemon/api/deck.js +511 -0
  94. package/src/host/daemon/api/desktop.js +71 -0
  95. package/src/host/daemon/api/embeddings.js +65 -0
  96. package/src/host/daemon/api/engines.js +80 -0
  97. package/src/host/daemon/api/exec.js +193 -0
  98. package/src/host/daemon/api/health.js +10 -0
  99. package/src/host/daemon/api/identity.js +36 -0
  100. package/src/host/daemon/api/mcps.js +205 -0
  101. package/src/host/daemon/api/messages.js +83 -0
  102. package/src/host/daemon/api/pairing.js +194 -0
  103. package/src/host/daemon/api/plugins.js +19 -0
  104. package/src/host/daemon/api/projects.js +35 -0
  105. package/src/host/daemon/api/routines.js +84 -0
  106. package/src/host/daemon/api/run.js +55 -0
  107. package/src/host/daemon/api/runtimes.js +219 -0
  108. package/src/host/daemon/api/sessions-search.js +177 -0
  109. package/src/host/daemon/api/sessions.js +115 -0
  110. package/src/host/daemon/api/shared.js +217 -0
  111. package/src/host/daemon/api/super-agent.js +208 -0
  112. package/src/host/daemon/api/tasks.js +118 -0
  113. package/src/host/daemon/api/telegram.js +325 -0
  114. package/src/host/daemon/api/tools.js +21 -0
  115. package/src/host/daemon/api/top-level.js +131 -0
  116. package/src/host/daemon/api/transcribe.js +35 -0
  117. package/src/host/daemon/api/tts.js +44 -0
  118. package/src/host/daemon/api/voice.js +519 -0
  119. package/src/host/daemon/api/web.js +123 -0
  120. package/src/host/daemon/api.js +152 -0
  121. package/src/{daemon → host/daemon}/compact.js +1 -1
  122. package/src/{daemon → host/daemon}/db.js +19 -5
  123. package/src/{daemon/overlay-ws.js → host/daemon/desktop-ws.js} +7 -7
  124. package/src/host/daemon/engine-sessions.js +245 -0
  125. package/src/{daemon → host/daemon}/index.js +27 -10
  126. package/src/{daemon/plugins/overlay.js → host/daemon/plugins/desktop.js} +36 -28
  127. package/src/{daemon → host/daemon}/plugins/index.js +2 -2
  128. package/src/{daemon → host/daemon}/plugins/telegram.js +165 -33
  129. package/src/{daemon → host/daemon}/project-config.js +31 -7
  130. package/src/{daemon → host/daemon}/routines.js +27 -8
  131. package/src/{daemon → host/daemon}/skills-loader.js +4 -2
  132. package/src/{daemon → host/daemon}/smoke.js +9 -5
  133. package/src/{daemon → host/daemon}/super-agent-tools/helpers.js +1 -1
  134. package/src/host/daemon/super-agent-tools/index.js +157 -0
  135. package/src/{daemon → host/daemon}/super-agent-tools/registry-bridge.js +1 -1
  136. package/src/{daemon → host/daemon}/super-agent-tools/tools/add-project.js +2 -2
  137. package/src/{daemon → host/daemon}/super-agent-tools/tools/ask-questions.js +4 -0
  138. package/src/{daemon → host/daemon}/super-agent-tools/tools/call-agent.js +5 -2
  139. package/src/{daemon → host/daemon}/super-agent-tools/tools/call-runtime.js +117 -8
  140. package/src/host/daemon/super-agent-tools/tools/create-task.js +52 -0
  141. package/src/{daemon → host/daemon}/super-agent-tools/tools/import-agent.js +2 -3
  142. package/src/{daemon → host/daemon}/super-agent-tools/tools/list-agents.js +1 -1
  143. package/src/host/daemon/super-agent-tools/tools/list-tasks.js +52 -0
  144. package/src/{daemon → host/daemon}/super-agent-tools/tools/list-vault-agents.js +1 -1
  145. package/src/host/daemon/super-agent-tools/tools/read-self-memory.js +21 -0
  146. package/src/host/daemon/super-agent-tools/tools/remember.js +40 -0
  147. package/src/{daemon → host/daemon}/super-agent-tools/tools/search-messages.js +1 -1
  148. package/src/host/daemon/super-agent-tools/tools/search-sessions.js +134 -0
  149. package/src/{daemon → host/daemon}/super-agent-tools/tools/send-telegram.js +2 -2
  150. package/src/{daemon → host/daemon}/super-agent-tools/tools/set-identity.js +1 -1
  151. package/src/{daemon → host/daemon}/super-agent-tools/tools/set-permission-mode.js +1 -1
  152. package/src/{daemon → host/daemon}/super-agent-tools/tools/tail-messages.js +1 -1
  153. package/src/host/daemon/super-agent.js +129 -0
  154. package/src/host/daemon/token-store.js +118 -0
  155. package/src/host/daemon/tool-call-parser.js +2 -0
  156. package/src/{daemon → host/daemon}/transcription.js +1 -1
  157. package/src/{daemon → host/daemon}/wakeup.js +2 -2
  158. package/src/interfaces/cli/claude-permissions.js +33 -0
  159. package/src/{cli → interfaces/cli}/commands/agent.js +43 -8
  160. package/src/{cli → interfaces/cli}/commands/command.js +1 -1
  161. package/src/{cli → interfaces/cli}/commands/config.js +1 -1
  162. package/src/{cli → interfaces/cli}/commands/daemon.js +67 -0
  163. package/src/interfaces/cli/commands/desktop.js +335 -0
  164. package/src/interfaces/cli/commands/exec.js +92 -0
  165. package/src/{cli → interfaces/cli}/commands/identity.js +6 -63
  166. package/src/{cli → interfaces/cli}/commands/init.js +1 -1
  167. package/src/{cli → interfaces/cli}/commands/mcp.js +69 -10
  168. package/src/{cli → interfaces/cli}/commands/memory.js +2 -2
  169. package/src/interfaces/cli/commands/model.js +136 -0
  170. package/src/interfaces/cli/commands/pair.js +170 -0
  171. package/src/interfaces/cli/commands/project-config.js +131 -0
  172. package/src/{cli → interfaces/cli}/commands/project.js +1 -1
  173. package/src/{cli → interfaces/cli}/commands/search.js +1 -1
  174. package/src/interfaces/cli/commands/session.js +892 -0
  175. package/src/interfaces/cli/commands/sessions.js +997 -0
  176. package/src/{cli → interfaces/cli}/commands/setup.js +98 -4
  177. package/src/{cli → interfaces/cli}/commands/skills.js +117 -9
  178. package/src/{cli → interfaces/cli}/commands/status.js +9 -1
  179. package/src/{cli → interfaces/cli}/commands/sys.js +96 -17
  180. package/src/interfaces/cli/commands/task.js +179 -0
  181. package/src/interfaces/cli/commands/telegram.js +366 -0
  182. package/src/{cli → interfaces/cli}/commands/update.js +1 -1
  183. package/src/interfaces/cli/commands/voice.js +258 -0
  184. package/src/{cli → interfaces/cli}/http.js +6 -2
  185. package/src/{cli → interfaces/cli}/index.js +955 -63
  186. package/src/interfaces/cli/postinstall.js +34 -0
  187. package/src/interfaces/desktop/assets/app-icon-180.png +0 -0
  188. package/src/interfaces/desktop/assets/app-icon-32.png +0 -0
  189. package/src/interfaces/desktop/assets/app-icon.png +0 -0
  190. package/src/interfaces/desktop/assets/apx-logo.png +0 -0
  191. package/src/interfaces/desktop/assets/superagent.png +0 -0
  192. package/src/interfaces/desktop/assets/tray-icon.png +0 -0
  193. package/src/interfaces/desktop/index.html +18 -0
  194. package/src/interfaces/desktop/main.js +652 -0
  195. package/src/interfaces/desktop/preload.js +48 -0
  196. package/src/interfaces/desktop/renderer.js +1006 -0
  197. package/src/interfaces/desktop/style.css +400 -0
  198. package/src/{mcp → interfaces/mcp-server}/index.js +2 -2
  199. package/src/interfaces/tui/_shims/util-which.ts +53 -0
  200. package/src/{tui → interfaces/tui}/app.tsx +2 -2
  201. package/src/{tui → interfaces/tui}/component/prompt/index.tsx +4 -1
  202. package/src/{tui → interfaces/tui}/context/sdk-apx.tsx +84 -16
  203. package/src/interfaces/tui/context/sync-apx.tsx +398 -0
  204. package/src/interfaces/tui/routes/session/index.tsx +368 -0
  205. package/src/interfaces/tui/routes/session/message-actions.tsx +58 -0
  206. package/src/interfaces/tui/routes/session/sidebar-apx.tsx +114 -0
  207. package/src/{tui → interfaces/tui}/tsconfig.json +1 -0
  208. package/src/{tui → interfaces/tui}/util/clipboard.ts +1 -1
  209. package/src/interfaces/web/README.md +102 -0
  210. package/src/interfaces/web/coming-soon.html +65 -0
  211. package/src/interfaces/web/components.json +25 -0
  212. package/src/interfaces/web/dist/assets/index-BDUsA6L6.css +1 -0
  213. package/src/interfaces/web/dist/assets/index-CfWyjPBa.js +548 -0
  214. package/src/interfaces/web/dist/assets/index-CfWyjPBa.js.map +1 -0
  215. package/src/interfaces/web/dist/favicon/dark/android-chrome-192x192.png +0 -0
  216. package/src/interfaces/web/dist/favicon/dark/android-chrome-512x512.png +0 -0
  217. package/src/interfaces/web/dist/favicon/dark/apple-touch-icon.png +0 -0
  218. package/src/interfaces/web/dist/favicon/dark/favicon-16x16.png +0 -0
  219. package/src/interfaces/web/dist/favicon/dark/favicon-32x32.png +0 -0
  220. package/src/interfaces/web/dist/favicon/dark/favicon-48x48.png +0 -0
  221. package/src/interfaces/web/dist/favicon/dark/favicon.ico +0 -0
  222. package/src/interfaces/web/dist/favicon/dark/favicon.webp +0 -0
  223. package/src/interfaces/web/dist/favicon/dark/site.webmanifest +18 -0
  224. package/src/interfaces/web/dist/favicon/white/android-chrome-192x192.png +0 -0
  225. package/src/interfaces/web/dist/favicon/white/android-chrome-512x512.png +0 -0
  226. package/src/interfaces/web/dist/favicon/white/apple-touch-icon.png +0 -0
  227. package/src/interfaces/web/dist/favicon/white/favicon-16x16.png +0 -0
  228. package/src/interfaces/web/dist/favicon/white/favicon-32x32.png +0 -0
  229. package/src/interfaces/web/dist/favicon/white/favicon-48x48.png +0 -0
  230. package/src/interfaces/web/dist/favicon/white/favicon.ico +0 -0
  231. package/src/interfaces/web/dist/favicon/white/favicon.webp +0 -0
  232. package/src/interfaces/web/dist/favicon/white/site.webmanifest +18 -0
  233. package/src/interfaces/web/dist/index.html +27 -0
  234. package/src/interfaces/web/dist/logo/logo_dark.webp +0 -0
  235. package/src/interfaces/web/dist/logo/logo_only_dark.webp +0 -0
  236. package/src/interfaces/web/dist/logo/logo_only_white.webp +0 -0
  237. package/src/interfaces/web/dist/logo/logo_vertical_dark.webp +0 -0
  238. package/src/interfaces/web/dist/logo/logo_vertical_white.webp +0 -0
  239. package/src/interfaces/web/dist/logo/logo_white.webp +0 -0
  240. package/src/interfaces/web/dist/modules/superagent.png +0 -0
  241. package/src/interfaces/web/index.html +26 -0
  242. package/src/interfaces/web/package-lock.json +4253 -0
  243. package/src/interfaces/web/package.json +55 -0
  244. package/src/interfaces/web/playwright.config.ts +45 -0
  245. package/src/interfaces/web/pnpm-lock.yaml +2946 -0
  246. package/src/interfaces/web/public/favicon/dark/android-chrome-192x192.png +0 -0
  247. package/src/interfaces/web/public/favicon/dark/android-chrome-512x512.png +0 -0
  248. package/src/interfaces/web/public/favicon/dark/apple-touch-icon.png +0 -0
  249. package/src/interfaces/web/public/favicon/dark/favicon-16x16.png +0 -0
  250. package/src/interfaces/web/public/favicon/dark/favicon-32x32.png +0 -0
  251. package/src/interfaces/web/public/favicon/dark/favicon-48x48.png +0 -0
  252. package/src/interfaces/web/public/favicon/dark/favicon.ico +0 -0
  253. package/src/interfaces/web/public/favicon/dark/favicon.webp +0 -0
  254. package/src/interfaces/web/public/favicon/dark/site.webmanifest +18 -0
  255. package/src/interfaces/web/public/favicon/white/android-chrome-192x192.png +0 -0
  256. package/src/interfaces/web/public/favicon/white/android-chrome-512x512.png +0 -0
  257. package/src/interfaces/web/public/favicon/white/apple-touch-icon.png +0 -0
  258. package/src/interfaces/web/public/favicon/white/favicon-16x16.png +0 -0
  259. package/src/interfaces/web/public/favicon/white/favicon-32x32.png +0 -0
  260. package/src/interfaces/web/public/favicon/white/favicon-48x48.png +0 -0
  261. package/src/interfaces/web/public/favicon/white/favicon.ico +0 -0
  262. package/src/interfaces/web/public/favicon/white/favicon.webp +0 -0
  263. package/src/interfaces/web/public/favicon/white/site.webmanifest +18 -0
  264. package/src/interfaces/web/public/logo/logo_dark.webp +0 -0
  265. package/src/interfaces/web/public/logo/logo_only_dark.webp +0 -0
  266. package/src/interfaces/web/public/logo/logo_only_white.webp +0 -0
  267. package/src/interfaces/web/public/logo/logo_vertical_dark.webp +0 -0
  268. package/src/interfaces/web/public/logo/logo_vertical_white.webp +0 -0
  269. package/src/interfaces/web/public/logo/logo_white.webp +0 -0
  270. package/src/interfaces/web/public/modules/superagent.png +0 -0
  271. package/src/interfaces/web/src/App.tsx +199 -0
  272. package/src/interfaces/web/src/components/AddProjectDialog.tsx +121 -0
  273. package/src/interfaces/web/src/components/ModelCombobox.tsx +96 -0
  274. package/src/interfaces/web/src/components/RobyBubble.tsx +213 -0
  275. package/src/interfaces/web/src/components/Section.tsx +44 -0
  276. package/src/interfaces/web/src/components/TelegramChannelDialog.tsx +97 -0
  277. package/src/interfaces/web/src/components/TelegramSendDialog.tsx +48 -0
  278. package/src/interfaces/web/src/components/Toast.tsx +84 -0
  279. package/src/interfaces/web/src/components/UiSelect.tsx +74 -0
  280. package/src/interfaces/web/src/components/chat/Composer.tsx +43 -0
  281. package/src/interfaces/web/src/components/chat/ContextBar.tsx +111 -0
  282. package/src/interfaces/web/src/components/chat/MessageBubble.tsx +95 -0
  283. package/src/interfaces/web/src/components/chat/MessageList.tsx +35 -0
  284. package/src/interfaces/web/src/components/chat/ModelPicker.tsx +145 -0
  285. package/src/interfaces/web/src/components/chat/ToolCall.tsx +141 -0
  286. package/src/interfaces/web/src/components/code/CodeChangesTab.tsx +87 -0
  287. package/src/interfaces/web/src/components/code/CodeComposer.tsx +87 -0
  288. package/src/interfaces/web/src/components/code/CodeContextTab.tsx +83 -0
  289. package/src/interfaces/web/src/components/code/CodeProjectPicker.tsx +39 -0
  290. package/src/interfaces/web/src/components/code/CodeSessionList.tsx +97 -0
  291. package/src/interfaces/web/src/components/code/CodeSidePanel.tsx +44 -0
  292. package/src/interfaces/web/src/components/code/CodeToolTrail.tsx +29 -0
  293. package/src/interfaces/web/src/components/code/DiffView.tsx +67 -0
  294. package/src/interfaces/web/src/components/common/Qr.tsx +27 -0
  295. package/src/interfaces/web/src/components/common/TabLayout.tsx +46 -0
  296. package/src/interfaces/web/src/components/common/TabNav.tsx +113 -0
  297. package/src/interfaces/web/src/components/config/ConfigTabsEditor.tsx +202 -0
  298. package/src/interfaces/web/src/components/config/GlobalConfigEditor.tsx +42 -0
  299. package/src/interfaces/web/src/components/config/global-config-sections.ts +60 -0
  300. package/src/interfaces/web/src/components/config/project-config-sections.ts +58 -0
  301. package/src/interfaces/web/src/components/deck/DaemonCard.tsx +58 -0
  302. package/src/interfaces/web/src/components/deck/DesktopGroup.tsx +33 -0
  303. package/src/interfaces/web/src/components/deck/WidgetRow.tsx +100 -0
  304. package/src/interfaces/web/src/components/layout/Logo.tsx +59 -0
  305. package/src/interfaces/web/src/components/layout/ProjectAvatar.tsx +116 -0
  306. package/src/interfaces/web/src/components/layout/ProjectSidebar.tsx +151 -0
  307. package/src/interfaces/web/src/components/settings/AdvancedPanel.tsx +45 -0
  308. package/src/interfaces/web/src/components/settings/AppearancePanel.tsx +72 -0
  309. package/src/interfaces/web/src/components/settings/DefaultRouterCard.tsx +232 -0
  310. package/src/interfaces/web/src/components/settings/DevicesPanel.tsx +60 -0
  311. package/src/interfaces/web/src/components/settings/EnginesPanel.tsx +127 -0
  312. package/src/interfaces/web/src/components/settings/IdentityPanel.tsx +69 -0
  313. package/src/interfaces/web/src/components/settings/MemoryPanel.tsx +226 -0
  314. package/src/interfaces/web/src/components/settings/PairDeviceDialog.tsx +175 -0
  315. package/src/interfaces/web/src/components/settings/SuperAgentPanel.tsx +93 -0
  316. package/src/interfaces/web/src/components/settings/TelegramChannelsPanel.tsx +90 -0
  317. package/src/interfaces/web/src/components/settings/TelegramContactsPanel.tsx +101 -0
  318. package/src/interfaces/web/src/components/settings/TelegramGlobalPanel.tsx +100 -0
  319. package/src/interfaces/web/src/components/settings/TelegramRolesPanel.tsx +108 -0
  320. package/src/interfaces/web/src/components/settings/TelegramSettingsTabs.tsx +55 -0
  321. package/src/interfaces/web/src/components/settings/providers/ProviderCard.tsx +95 -0
  322. package/src/interfaces/web/src/components/settings/providers/ProviderModal.tsx +405 -0
  323. package/src/interfaces/web/src/components/settings/providers/typeStyles.ts +155 -0
  324. package/src/interfaces/web/src/components/settings/providers/types.ts +26 -0
  325. package/src/interfaces/web/src/components/ui/accordion.tsx +72 -0
  326. package/src/interfaces/web/src/components/ui/alert-dialog.tsx +187 -0
  327. package/src/interfaces/web/src/components/ui/alert.tsx +76 -0
  328. package/src/interfaces/web/src/components/ui/aspect-ratio.tsx +22 -0
  329. package/src/interfaces/web/src/components/ui/avatar.tsx +107 -0
  330. package/src/interfaces/web/src/components/ui/badge.tsx +52 -0
  331. package/src/interfaces/web/src/components/ui/breadcrumb.tsx +125 -0
  332. package/src/interfaces/web/src/components/ui/button-group.tsx +87 -0
  333. package/src/interfaces/web/src/components/ui/button.tsx +58 -0
  334. package/src/interfaces/web/src/components/ui/calendar.tsx +221 -0
  335. package/src/interfaces/web/src/components/ui/card.tsx +103 -0
  336. package/src/interfaces/web/src/components/ui/carousel.tsx +242 -0
  337. package/src/interfaces/web/src/components/ui/chart.tsx +371 -0
  338. package/src/interfaces/web/src/components/ui/chat-input.tsx +122 -0
  339. package/src/interfaces/web/src/components/ui/checkbox.tsx +29 -0
  340. package/src/interfaces/web/src/components/ui/collapsible.tsx +19 -0
  341. package/src/interfaces/web/src/components/ui/combobox.tsx +295 -0
  342. package/src/interfaces/web/src/components/ui/command.tsx +196 -0
  343. package/src/interfaces/web/src/components/ui/context-menu.tsx +271 -0
  344. package/src/interfaces/web/src/components/ui/dialog.tsx +158 -0
  345. package/src/interfaces/web/src/components/ui/direction.tsx +4 -0
  346. package/src/interfaces/web/src/components/ui/drawer.tsx +134 -0
  347. package/src/interfaces/web/src/components/ui/dropdown-menu.tsx +266 -0
  348. package/src/interfaces/web/src/components/ui/empty.tsx +104 -0
  349. package/src/interfaces/web/src/components/ui/field.tsx +236 -0
  350. package/src/interfaces/web/src/components/ui/hover-card.tsx +51 -0
  351. package/src/interfaces/web/src/components/ui/input-group.tsx +158 -0
  352. package/src/interfaces/web/src/components/ui/input-otp.tsx +85 -0
  353. package/src/interfaces/web/src/components/ui/input.tsx +20 -0
  354. package/src/interfaces/web/src/components/ui/item.tsx +201 -0
  355. package/src/interfaces/web/src/components/ui/kbd.tsx +26 -0
  356. package/src/interfaces/web/src/components/ui/label.tsx +20 -0
  357. package/src/interfaces/web/src/components/ui/menubar.tsx +280 -0
  358. package/src/interfaces/web/src/components/ui/native-select.tsx +61 -0
  359. package/src/interfaces/web/src/components/ui/navigation-menu.tsx +168 -0
  360. package/src/interfaces/web/src/components/ui/pagination.tsx +130 -0
  361. package/src/interfaces/web/src/components/ui/popover.tsx +88 -0
  362. package/src/interfaces/web/src/components/ui/progress.tsx +83 -0
  363. package/src/interfaces/web/src/components/ui/radio-group.tsx +36 -0
  364. package/src/interfaces/web/src/components/ui/resizable.tsx +50 -0
  365. package/src/interfaces/web/src/components/ui/scroll-area.tsx +53 -0
  366. package/src/interfaces/web/src/components/ui/select.tsx +201 -0
  367. package/src/interfaces/web/src/components/ui/separator.tsx +23 -0
  368. package/src/interfaces/web/src/components/ui/sheet.tsx +138 -0
  369. package/src/interfaces/web/src/components/ui/sidebar.tsx +723 -0
  370. package/src/interfaces/web/src/components/ui/skeleton.tsx +13 -0
  371. package/src/interfaces/web/src/components/ui/slider.tsx +52 -0
  372. package/src/interfaces/web/src/components/ui/sonner.tsx +49 -0
  373. package/src/interfaces/web/src/components/ui/spinner.tsx +10 -0
  374. package/src/interfaces/web/src/components/ui/switch.tsx +30 -0
  375. package/src/interfaces/web/src/components/ui/table.tsx +116 -0
  376. package/src/interfaces/web/src/components/ui/tabs.tsx +72 -0
  377. package/src/interfaces/web/src/components/ui/textarea.tsx +18 -0
  378. package/src/interfaces/web/src/components/ui/tip.tsx +21 -0
  379. package/src/interfaces/web/src/components/ui/toggle-group.tsx +87 -0
  380. package/src/interfaces/web/src/components/ui/toggle.tsx +45 -0
  381. package/src/interfaces/web/src/components/ui/tooltip.tsx +64 -0
  382. package/src/interfaces/web/src/components/ui.tsx +211 -0
  383. package/src/interfaces/web/src/components/voice/VoiceProviderList.tsx +197 -0
  384. package/src/interfaces/web/src/components/voice/VoiceProviderModal.tsx +213 -0
  385. package/src/interfaces/web/src/components/voice/VoiceSttCard.tsx +72 -0
  386. package/src/interfaces/web/src/components/voice/VoiceTestCard.tsx +112 -0
  387. package/src/interfaces/web/src/components/voice/useTtsPlayer.ts +59 -0
  388. package/src/interfaces/web/src/constants/index.ts +91 -0
  389. package/src/interfaces/web/src/hooks/use-mobile.ts +19 -0
  390. package/src/interfaces/web/src/hooks/useChat.ts +276 -0
  391. package/src/interfaces/web/src/hooks/useDaemonStatus.ts +12 -0
  392. package/src/interfaces/web/src/hooks/useDevices.ts +12 -0
  393. package/src/interfaces/web/src/hooks/useEngines.ts +10 -0
  394. package/src/interfaces/web/src/hooks/useGlobalConfig.ts +24 -0
  395. package/src/interfaces/web/src/hooks/useIdentity.ts +16 -0
  396. package/src/interfaces/web/src/hooks/useProjects.ts +27 -0
  397. package/src/interfaces/web/src/hooks/useTelegram.ts +35 -0
  398. package/src/interfaces/web/src/hooks/useTheme.tsx +57 -0
  399. package/src/interfaces/web/src/hooks/useTokenBootstrap.ts +122 -0
  400. package/src/interfaces/web/src/i18n/en.ts +767 -0
  401. package/src/interfaces/web/src/i18n/es.ts +770 -0
  402. package/src/interfaces/web/src/i18n/index.ts +86 -0
  403. package/src/interfaces/web/src/lib/api/admin.ts +30 -0
  404. package/src/interfaces/web/src/lib/api/agents.ts +46 -0
  405. package/src/interfaces/web/src/lib/api/code.ts +122 -0
  406. package/src/interfaces/web/src/lib/api/conversations.ts +16 -0
  407. package/src/interfaces/web/src/lib/api/deck.ts +106 -0
  408. package/src/interfaces/web/src/lib/api/desktop.ts +54 -0
  409. package/src/interfaces/web/src/lib/api/embeddings.ts +44 -0
  410. package/src/interfaces/web/src/lib/api/engines.ts +17 -0
  411. package/src/interfaces/web/src/lib/api/filesystem.ts +12 -0
  412. package/src/interfaces/web/src/lib/api/health.ts +6 -0
  413. package/src/interfaces/web/src/lib/api/identity.ts +7 -0
  414. package/src/interfaces/web/src/lib/api/mcps.ts +29 -0
  415. package/src/interfaces/web/src/lib/api/messages.ts +24 -0
  416. package/src/interfaces/web/src/lib/api/projects.ts +29 -0
  417. package/src/interfaces/web/src/lib/api/routines.ts +14 -0
  418. package/src/interfaces/web/src/lib/api/sessions.ts +16 -0
  419. package/src/interfaces/web/src/lib/api/super_agent.ts +29 -0
  420. package/src/interfaces/web/src/lib/api/tasks.ts +19 -0
  421. package/src/interfaces/web/src/lib/api/telegram.ts +57 -0
  422. package/src/interfaces/web/src/lib/api/tools.ts +13 -0
  423. package/src/interfaces/web/src/lib/api/voice.ts +169 -0
  424. package/src/interfaces/web/src/lib/api.ts +48 -0
  425. package/src/interfaces/web/src/lib/cn.ts +6 -0
  426. package/src/interfaces/web/src/lib/code-context.ts +83 -0
  427. package/src/interfaces/web/src/lib/config-values.ts +29 -0
  428. package/src/interfaces/web/src/lib/device.ts +10 -0
  429. package/src/interfaces/web/src/lib/http.ts +104 -0
  430. package/src/interfaces/web/src/lib/secrets.ts +15 -0
  431. package/src/interfaces/web/src/lib/utils.ts +6 -0
  432. package/src/interfaces/web/src/main.tsx +16 -0
  433. package/src/interfaces/web/src/screens/ApxAdminScreen.tsx +174 -0
  434. package/src/interfaces/web/src/screens/PairingScreen.tsx +105 -0
  435. package/src/interfaces/web/src/screens/ProjectScreen.tsx +178 -0
  436. package/src/interfaces/web/src/screens/SettingsScreen.tsx +111 -0
  437. package/src/interfaces/web/src/screens/base/AgentDefaultsTab.tsx +274 -0
  438. package/src/interfaces/web/src/screens/base/ComingSoon.tsx +16 -0
  439. package/src/interfaces/web/src/screens/base/GlobalTasksTab.tsx +53 -0
  440. package/src/interfaces/web/src/screens/base/LogsTab.tsx +188 -0
  441. package/src/interfaces/web/src/screens/base/ModelsTab.tsx +13 -0
  442. package/src/interfaces/web/src/screens/base/SessionsTab.tsx +58 -0
  443. package/src/interfaces/web/src/screens/base/WorkspacesTab.tsx +49 -0
  444. package/src/interfaces/web/src/screens/modules/CodeScreen.tsx +295 -0
  445. package/src/interfaces/web/src/screens/modules/DeckScreen.tsx +173 -0
  446. package/src/interfaces/web/src/screens/modules/DesktopScreen.tsx +304 -0
  447. package/src/interfaces/web/src/screens/modules/VoiceScreen.tsx +174 -0
  448. package/src/interfaces/web/src/screens/project/AgentBrainGraph.tsx +152 -0
  449. package/src/interfaces/web/src/screens/project/AgentDetailScreen.tsx +455 -0
  450. package/src/interfaces/web/src/screens/project/AgentsTab.tsx +364 -0
  451. package/src/interfaces/web/src/screens/project/ChatTab.tsx +198 -0
  452. package/src/interfaces/web/src/screens/project/ConfigTab.tsx +94 -0
  453. package/src/interfaces/web/src/screens/project/McpsTab.tsx +149 -0
  454. package/src/interfaces/web/src/screens/project/MemoriesTab.tsx +134 -0
  455. package/src/interfaces/web/src/screens/project/Overview.tsx +37 -0
  456. package/src/interfaces/web/src/screens/project/RoutinesTab.tsx +386 -0
  457. package/src/interfaces/web/src/screens/project/TasksTab.tsx +116 -0
  458. package/src/interfaces/web/src/screens/project/TelegramTab.tsx +126 -0
  459. package/src/interfaces/web/src/screens/project/ThreadsTab.tsx +100 -0
  460. package/src/interfaces/web/src/styles.css +128 -0
  461. package/src/interfaces/web/src/types/daemon.ts +289 -0
  462. package/src/interfaces/web/tailwind.config.js +53 -0
  463. package/src/interfaces/web/tsconfig.json +24 -0
  464. package/src/interfaces/web/vite.config.ts +50 -0
  465. package/src/cli/commands/exec.js +0 -56
  466. package/src/cli/commands/overlay.js +0 -253
  467. package/src/cli/commands/session.js +0 -395
  468. package/src/cli/commands/sessions.js +0 -517
  469. package/src/cli/commands/telegram.js +0 -77
  470. package/src/cli/postinstall.js +0 -75
  471. package/src/cli-ts/commands/agent.ts +0 -173
  472. package/src/cli-ts/commands/chat.ts +0 -119
  473. package/src/cli-ts/commands/daemon.ts +0 -112
  474. package/src/cli-ts/commands/exec.ts +0 -109
  475. package/src/cli-ts/commands/mcp.ts +0 -235
  476. package/src/cli-ts/commands/session.ts +0 -224
  477. package/src/cli-ts/commands/status.ts +0 -61
  478. package/src/cli-ts/http.ts +0 -36
  479. package/src/cli-ts/index.ts +0 -73
  480. package/src/cli-ts/ui.ts +0 -107
  481. package/src/daemon/api.js +0 -1558
  482. package/src/daemon/engines/gemini.js +0 -56
  483. package/src/daemon/engines/openai.js +0 -79
  484. package/src/daemon/mcp-sources.js +0 -114
  485. package/src/daemon/super-agent-tools/index.js +0 -84
  486. package/src/daemon/super-agent-tools.js +0 -1
  487. package/src/daemon/super-agent.js +0 -541
  488. package/src/overlay/index.html +0 -44
  489. package/src/overlay/main.js +0 -480
  490. package/src/overlay/preload.js +0 -34
  491. package/src/overlay/renderer.js +0 -371
  492. package/src/overlay/style.css +0 -250
  493. package/src/tui/context/sync-apx.tsx +0 -284
  494. package/src/tui/routes/session/index.tsx +0 -274
  495. package/src/tui/routes/session/sidebar-apx.tsx +0 -90
  496. /package/src/{daemon → core}/tools/browser.js +0 -0
  497. /package/src/{daemon → core}/tools/fetch.js +0 -0
  498. /package/src/{daemon → core}/tools/glob.js +0 -0
  499. /package/src/{daemon → core}/tools/grep.js +0 -0
  500. /package/src/{daemon → core}/tools/registry.js +0 -0
  501. /package/src/{daemon → core}/tools/search.js +0 -0
  502. /package/src/{daemon → host/daemon}/conversations.js +0 -0
  503. /package/src/{daemon → host/daemon}/env-detect.js +0 -0
  504. /package/src/{daemon → host/daemon}/runtimes/_spawn.js +0 -0
  505. /package/src/{daemon → host/daemon}/runtimes/aider.js +0 -0
  506. /package/src/{daemon → host/daemon}/runtimes/claude-code.js +0 -0
  507. /package/src/{daemon → host/daemon}/runtimes/codex.js +0 -0
  508. /package/src/{daemon → host/daemon}/runtimes/cursor-agent.js +0 -0
  509. /package/src/{daemon → host/daemon}/runtimes/gemini-cli.js +0 -0
  510. /package/src/{daemon → host/daemon}/runtimes/index.js +0 -0
  511. /package/src/{daemon → host/daemon}/runtimes/opencode.js +0 -0
  512. /package/src/{daemon → host/daemon}/runtimes/qwen-code.js +0 -0
  513. /package/src/{daemon → host/daemon}/super-agent-tools/tools/call-mcp.js +0 -0
  514. /package/src/{daemon → host/daemon}/super-agent-tools/tools/edit-file.js +0 -0
  515. /package/src/{daemon → host/daemon}/super-agent-tools/tools/list-files.js +0 -0
  516. /package/src/{daemon → host/daemon}/super-agent-tools/tools/list-mcps.js +0 -0
  517. /package/src/{daemon → host/daemon}/super-agent-tools/tools/list-projects.js +0 -0
  518. /package/src/{daemon → host/daemon}/super-agent-tools/tools/list-skills.js +0 -0
  519. /package/src/{daemon → host/daemon}/super-agent-tools/tools/load-skill.js +0 -0
  520. /package/src/{daemon → host/daemon}/super-agent-tools/tools/read-agent-memory.js +0 -0
  521. /package/src/{daemon → host/daemon}/super-agent-tools/tools/read-file.js +0 -0
  522. /package/src/{daemon → host/daemon}/super-agent-tools/tools/run-shell.js +0 -0
  523. /package/src/{daemon → host/daemon}/super-agent-tools/tools/search-files.js +0 -0
  524. /package/src/{daemon → host/daemon}/super-agent-tools/tools/transcribe-audio.js +0 -0
  525. /package/src/{daemon → host/daemon}/super-agent-tools/tools/write-file.js +0 -0
  526. /package/src/{daemon → host/daemon}/thinking.js +0 -0
  527. /package/src/{daemon → host/daemon}/whisper-server.py +0 -0
  528. /package/src/{daemon → host/daemon}/whisper-transcribe.py +0 -0
  529. /package/src/{cli → interfaces/cli}/commands/a2a.js +0 -0
  530. /package/src/{cli → interfaces/cli}/commands/artifact.js +0 -0
  531. /package/src/{cli → interfaces/cli}/commands/chat.js +0 -0
  532. /package/src/{cli → interfaces/cli}/commands/log.js +0 -0
  533. /package/src/{cli → interfaces/cli}/commands/messages.js +0 -0
  534. /package/src/{cli → interfaces/cli}/commands/plugins.js +0 -0
  535. /package/src/{cli → interfaces/cli}/commands/routine.js +0 -0
  536. /package/src/{cli → interfaces/cli}/commands/runtime.js +0 -0
  537. /package/src/{cli → interfaces/cli}/terminal-chat/renderer.js +0 -0
  538. /package/src/{overlay → interfaces/desktop}/package.json +0 -0
  539. /package/src/{tui → interfaces/tui}/_shims/cli-error.ts +0 -0
  540. /package/src/{tui → interfaces/tui}/_shims/cli-logo.ts +0 -0
  541. /package/src/{tui → interfaces/tui}/_shims/cli-ui.ts +0 -0
  542. /package/src/{tui → interfaces/tui}/_shims/config-console-state.ts +0 -0
  543. /package/src/{tui → interfaces/tui}/_shims/core-any.ts +0 -0
  544. /package/src/{tui → interfaces/tui}/_shims/core-binary.ts +0 -0
  545. /package/src/{tui → interfaces/tui}/_shims/core-flag.ts +0 -0
  546. /package/src/{tui → interfaces/tui}/_shims/core-log.ts +0 -0
  547. /package/src/{tui → interfaces/tui}/_shims/lsp-language.ts +0 -0
  548. /package/src/{tui → interfaces/tui}/_shims/opencode-any.ts +0 -0
  549. /package/src/{tui → interfaces/tui}/_shims/opencode-sdk-v2.ts +0 -0
  550. /package/src/{tui → interfaces/tui}/_shims/plugin-tui.ts +0 -0
  551. /package/src/{tui → interfaces/tui}/_shims/prompt-display.ts +0 -0
  552. /package/src/{tui → interfaces/tui}/_shims/provider-provider.ts +0 -0
  553. /package/src/{tui → interfaces/tui}/_shims/session-retry.ts +0 -0
  554. /package/src/{tui → interfaces/tui}/_shims/session-schema.ts +0 -0
  555. /package/src/{tui → interfaces/tui}/_shims/session-session.ts +0 -0
  556. /package/src/{tui → interfaces/tui}/_shims/snapshot.ts +0 -0
  557. /package/src/{tui → interfaces/tui}/_shims/tool-any.ts +0 -0
  558. /package/src/{tui → interfaces/tui}/_shims/util-error.ts +0 -0
  559. /package/src/{tui → interfaces/tui}/_shims/util-filesystem.ts +0 -0
  560. /package/src/{tui → interfaces/tui}/_shims/util-format.ts +0 -0
  561. /package/src/{tui → interfaces/tui}/_shims/util-iife.ts +0 -0
  562. /package/src/{tui → interfaces/tui}/_shims/util-locale.ts +0 -0
  563. /package/src/{tui → interfaces/tui}/_shims/util-process.ts +0 -0
  564. /package/src/{tui → interfaces/tui}/asset/charge.wav +0 -0
  565. /package/src/{tui → interfaces/tui}/asset/pulse-a.wav +0 -0
  566. /package/src/{tui → interfaces/tui}/asset/pulse-b.wav +0 -0
  567. /package/src/{tui → interfaces/tui}/asset/pulse-c.wav +0 -0
  568. /package/src/{tui → interfaces/tui}/attach.ts +0 -0
  569. /package/src/{tui → interfaces/tui}/component/bg-pulse-render.ts +0 -0
  570. /package/src/{tui → interfaces/tui}/component/bg-pulse.tsx +0 -0
  571. /package/src/{tui → interfaces/tui}/component/border.tsx +0 -0
  572. /package/src/{tui → interfaces/tui}/component/dialog-agent.tsx +0 -0
  573. /package/src/{tui → interfaces/tui}/component/dialog-console-org.tsx +0 -0
  574. /package/src/{tui → interfaces/tui}/component/dialog-mcp.tsx +0 -0
  575. /package/src/{tui → interfaces/tui}/component/dialog-model.tsx +0 -0
  576. /package/src/{tui → interfaces/tui}/component/dialog-provider.tsx +0 -0
  577. /package/src/{tui → interfaces/tui}/component/dialog-retry-action.tsx +0 -0
  578. /package/src/{tui → interfaces/tui}/component/dialog-session-delete-failed.tsx +0 -0
  579. /package/src/{tui → interfaces/tui}/component/dialog-session-list.tsx +0 -0
  580. /package/src/{tui → interfaces/tui}/component/dialog-session-rename.tsx +0 -0
  581. /package/src/{tui → interfaces/tui}/component/dialog-skill.tsx +0 -0
  582. /package/src/{tui → interfaces/tui}/component/dialog-stash.tsx +0 -0
  583. /package/src/{tui → interfaces/tui}/component/dialog-status.tsx +0 -0
  584. /package/src/{tui → interfaces/tui}/component/dialog-tag.tsx +0 -0
  585. /package/src/{tui → interfaces/tui}/component/dialog-theme-list.tsx +0 -0
  586. /package/src/{tui → interfaces/tui}/component/dialog-variant.tsx +0 -0
  587. /package/src/{tui → interfaces/tui}/component/dialog-workspace-create.tsx +0 -0
  588. /package/src/{tui → interfaces/tui}/component/dialog-workspace-file-changes.tsx +0 -0
  589. /package/src/{tui → interfaces/tui}/component/dialog-workspace-unavailable.tsx +0 -0
  590. /package/src/{tui → interfaces/tui}/component/error-component.tsx +0 -0
  591. /package/src/{tui → interfaces/tui}/component/logo.tsx +0 -0
  592. /package/src/{tui → interfaces/tui}/component/plugin-route-missing.tsx +0 -0
  593. /package/src/{tui → interfaces/tui}/component/prompt/autocomplete.tsx +0 -0
  594. /package/src/{tui → interfaces/tui}/component/prompt/cwd.ts +0 -0
  595. /package/src/{tui → interfaces/tui}/component/prompt/frecency.tsx +0 -0
  596. /package/src/{tui → interfaces/tui}/component/prompt/history.tsx +0 -0
  597. /package/src/{tui → interfaces/tui}/component/prompt/part.ts +0 -0
  598. /package/src/{tui → interfaces/tui}/component/prompt/stash.tsx +0 -0
  599. /package/src/{tui → interfaces/tui}/component/prompt/traits.ts +0 -0
  600. /package/src/{tui → interfaces/tui}/component/spinner.tsx +0 -0
  601. /package/src/{tui → interfaces/tui}/component/startup-loading.tsx +0 -0
  602. /package/src/{tui → interfaces/tui}/component/todo-item.tsx +0 -0
  603. /package/src/{tui → interfaces/tui}/component/use-connected.tsx +0 -0
  604. /package/src/{tui → interfaces/tui}/component/workspace-label.tsx +0 -0
  605. /package/src/{tui → interfaces/tui}/config/cwd.ts +0 -0
  606. /package/src/{tui → interfaces/tui}/config/keybind.ts +0 -0
  607. /package/src/{tui → interfaces/tui}/config/tui-migrate.ts +0 -0
  608. /package/src/{tui → interfaces/tui}/config/tui-schema.ts +0 -0
  609. /package/src/{tui → interfaces/tui}/config/tui.ts +0 -0
  610. /package/src/{tui → interfaces/tui}/context/aggregate-failures.ts +0 -0
  611. /package/src/{tui → interfaces/tui}/context/args.tsx +0 -0
  612. /package/src/{tui → interfaces/tui}/context/command-palette.tsx +0 -0
  613. /package/src/{tui → interfaces/tui}/context/directory.ts +0 -0
  614. /package/src/{tui → interfaces/tui}/context/editor-zed.ts +0 -0
  615. /package/src/{tui → interfaces/tui}/context/editor.ts +0 -0
  616. /package/src/{tui → interfaces/tui}/context/event-apx.ts +0 -0
  617. /package/src/{tui → interfaces/tui}/context/event.ts +0 -0
  618. /package/src/{tui → interfaces/tui}/context/exit.tsx +0 -0
  619. /package/src/{tui → interfaces/tui}/context/helper.tsx +0 -0
  620. /package/src/{tui → interfaces/tui}/context/kv.tsx +0 -0
  621. /package/src/{tui → interfaces/tui}/context/local.tsx +0 -0
  622. /package/src/{tui → interfaces/tui}/context/path-format.tsx +0 -0
  623. /package/src/{tui → interfaces/tui}/context/project-apx.tsx +0 -0
  624. /package/src/{tui → interfaces/tui}/context/project.tsx +0 -0
  625. /package/src/{tui → interfaces/tui}/context/prompt.tsx +0 -0
  626. /package/src/{tui → interfaces/tui}/context/route.tsx +0 -0
  627. /package/src/{tui → interfaces/tui}/context/sdk.tsx +0 -0
  628. /package/src/{tui → interfaces/tui}/context/sync-v2.tsx +0 -0
  629. /package/src/{tui → interfaces/tui}/context/sync.tsx +0 -0
  630. /package/src/{tui → interfaces/tui}/context/theme/aura.json +0 -0
  631. /package/src/{tui → interfaces/tui}/context/theme/ayu.json +0 -0
  632. /package/src/{tui → interfaces/tui}/context/theme/carbonfox.json +0 -0
  633. /package/src/{tui → interfaces/tui}/context/theme/catppuccin-frappe.json +0 -0
  634. /package/src/{tui → interfaces/tui}/context/theme/catppuccin-macchiato.json +0 -0
  635. /package/src/{tui → interfaces/tui}/context/theme/catppuccin.json +0 -0
  636. /package/src/{tui → interfaces/tui}/context/theme/cobalt2.json +0 -0
  637. /package/src/{tui → interfaces/tui}/context/theme/cursor.json +0 -0
  638. /package/src/{tui → interfaces/tui}/context/theme/dracula.json +0 -0
  639. /package/src/{tui → interfaces/tui}/context/theme/everforest.json +0 -0
  640. /package/src/{tui → interfaces/tui}/context/theme/flexoki.json +0 -0
  641. /package/src/{tui → interfaces/tui}/context/theme/github.json +0 -0
  642. /package/src/{tui → interfaces/tui}/context/theme/gruvbox.json +0 -0
  643. /package/src/{tui → interfaces/tui}/context/theme/kanagawa.json +0 -0
  644. /package/src/{tui → interfaces/tui}/context/theme/lucent-orng.json +0 -0
  645. /package/src/{tui → interfaces/tui}/context/theme/material.json +0 -0
  646. /package/src/{tui → interfaces/tui}/context/theme/matrix.json +0 -0
  647. /package/src/{tui → interfaces/tui}/context/theme/mercury.json +0 -0
  648. /package/src/{tui → interfaces/tui}/context/theme/monokai.json +0 -0
  649. /package/src/{tui → interfaces/tui}/context/theme/nightowl.json +0 -0
  650. /package/src/{tui → interfaces/tui}/context/theme/nord.json +0 -0
  651. /package/src/{tui → interfaces/tui}/context/theme/one-dark.json +0 -0
  652. /package/src/{tui → interfaces/tui}/context/theme/opencode.json +0 -0
  653. /package/src/{tui → interfaces/tui}/context/theme/orng.json +0 -0
  654. /package/src/{tui → interfaces/tui}/context/theme/osaka-jade.json +0 -0
  655. /package/src/{tui → interfaces/tui}/context/theme/palenight.json +0 -0
  656. /package/src/{tui → interfaces/tui}/context/theme/rosepine.json +0 -0
  657. /package/src/{tui → interfaces/tui}/context/theme/solarized.json +0 -0
  658. /package/src/{tui → interfaces/tui}/context/theme/synthwave84.json +0 -0
  659. /package/src/{tui → interfaces/tui}/context/theme/tokyonight.json +0 -0
  660. /package/src/{tui → interfaces/tui}/context/theme/vercel.json +0 -0
  661. /package/src/{tui → interfaces/tui}/context/theme/vesper.json +0 -0
  662. /package/src/{tui → interfaces/tui}/context/theme/zenburn.json +0 -0
  663. /package/src/{tui → interfaces/tui}/context/theme.tsx +0 -0
  664. /package/src/{tui → interfaces/tui}/context/tui-config.tsx +0 -0
  665. /package/src/{tui → interfaces/tui}/event.ts +0 -0
  666. /package/src/{tui → interfaces/tui}/feature-plugins/home/footer.tsx +0 -0
  667. /package/src/{tui → interfaces/tui}/feature-plugins/home/tips-view.tsx +0 -0
  668. /package/src/{tui → interfaces/tui}/feature-plugins/home/tips.tsx +0 -0
  669. /package/src/{tui → interfaces/tui}/feature-plugins/sidebar/context.tsx +0 -0
  670. /package/src/{tui → interfaces/tui}/feature-plugins/sidebar/files.tsx +0 -0
  671. /package/src/{tui → interfaces/tui}/feature-plugins/sidebar/footer.tsx +0 -0
  672. /package/src/{tui → interfaces/tui}/feature-plugins/sidebar/lsp.tsx +0 -0
  673. /package/src/{tui → interfaces/tui}/feature-plugins/sidebar/mcp.tsx +0 -0
  674. /package/src/{tui → interfaces/tui}/feature-plugins/sidebar/todo.tsx +0 -0
  675. /package/src/{tui → interfaces/tui}/feature-plugins/system/plugins.tsx +0 -0
  676. /package/src/{tui → interfaces/tui}/feature-plugins/system/session-v2.tsx +0 -0
  677. /package/src/{tui → interfaces/tui}/feature-plugins/system/which-key.tsx +0 -0
  678. /package/src/{tui → interfaces/tui}/keymap.tsx +0 -0
  679. /package/src/{tui → interfaces/tui}/layer.ts +0 -0
  680. /package/src/{tui → interfaces/tui}/plugin/api.tsx +0 -0
  681. /package/src/{tui → interfaces/tui}/plugin/command-shim.ts +0 -0
  682. /package/src/{tui → interfaces/tui}/plugin/internal.ts +0 -0
  683. /package/src/{tui → interfaces/tui}/plugin/runtime.ts +0 -0
  684. /package/src/{tui → interfaces/tui}/plugin/slots.tsx +0 -0
  685. /package/src/{tui → interfaces/tui}/routes/home.tsx +0 -0
  686. /package/src/{tui → interfaces/tui}/routes/session/dialog-fork-from-timeline.tsx +0 -0
  687. /package/src/{tui → interfaces/tui}/routes/session/dialog-message.tsx +0 -0
  688. /package/src/{tui → interfaces/tui}/routes/session/dialog-subagent.tsx +0 -0
  689. /package/src/{tui → interfaces/tui}/routes/session/dialog-timeline.tsx +0 -0
  690. /package/src/{tui → interfaces/tui}/routes/session/footer.tsx +0 -0
  691. /package/src/{tui → interfaces/tui}/routes/session/permission.tsx +0 -0
  692. /package/src/{tui → interfaces/tui}/routes/session/question.tsx +0 -0
  693. /package/src/{tui → interfaces/tui}/routes/session/sidebar.tsx +0 -0
  694. /package/src/{tui → interfaces/tui}/routes/session/subagent-footer.tsx +0 -0
  695. /package/src/{tui → interfaces/tui}/run.ts +0 -0
  696. /package/src/{tui → interfaces/tui}/thread.ts +0 -0
  697. /package/src/{tui → interfaces/tui}/ui/dialog-alert.tsx +0 -0
  698. /package/src/{tui → interfaces/tui}/ui/dialog-confirm.tsx +0 -0
  699. /package/src/{tui → interfaces/tui}/ui/dialog-export-options.tsx +0 -0
  700. /package/src/{tui → interfaces/tui}/ui/dialog-help.tsx +0 -0
  701. /package/src/{tui → interfaces/tui}/ui/dialog-prompt.tsx +0 -0
  702. /package/src/{tui → interfaces/tui}/ui/dialog-select.tsx +0 -0
  703. /package/src/{tui → interfaces/tui}/ui/dialog.tsx +0 -0
  704. /package/src/{tui → interfaces/tui}/ui/link.tsx +0 -0
  705. /package/src/{tui → interfaces/tui}/ui/spinner.ts +0 -0
  706. /package/src/{tui → interfaces/tui}/ui/toast.tsx +0 -0
  707. /package/src/{tui → interfaces/tui}/util/editor.ts +0 -0
  708. /package/src/{tui → interfaces/tui}/util/model.ts +0 -0
  709. /package/src/{tui → interfaces/tui}/util/provider-origin.ts +0 -0
  710. /package/src/{tui → interfaces/tui}/util/revert-diff.ts +0 -0
  711. /package/src/{tui → interfaces/tui}/util/scroll.ts +0 -0
  712. /package/src/{tui → interfaces/tui}/util/selection.ts +0 -0
  713. /package/src/{tui → interfaces/tui}/util/signal.ts +0 -0
  714. /package/src/{tui → interfaces/tui}/util/sound.ts +0 -0
  715. /package/src/{tui → interfaces/tui}/util/transcript.ts +0 -0
  716. /package/src/{tui → interfaces/tui}/validate-session.ts +0 -0
  717. /package/src/{tui → interfaces/tui}/win32.ts +0 -0
  718. /package/src/{tui → interfaces/tui}/worker.ts +0 -0
@@ -0,0 +1,1006 @@
1
+ // APX Desktop renderer — vanilla port of the v2 floating-capsule design.
2
+ //
3
+ // The capsule is always visible (a Siri/Spotlight-style bar). When there is
4
+ // a conversation in flight (or any past turn) a glass card appears below it
5
+ // with the transcript, and a session bar below that. State machine:
6
+ //
7
+ // idle → input ready, mic button, no live wave
8
+ // listening → mic recording, capsule shows live wave + cancel/send
9
+ // transcribing → blob being decoded, status "Transcribiendo…"
10
+ // thinking → super-agent producing tokens, status "Pensando…"
11
+ // speaking → TTS playing back, status "Superagente está hablando…"
12
+ //
13
+ // MediaRecorder webm chunks are buffered and the CUMULATIVE blob is sent on
14
+ // every tick (live partial) and again on stop (authoritative) — single chunks
15
+ // lack the EBML header and are undecodable on their own.
16
+
17
+ (() => {
18
+ "use strict";
19
+
20
+ // ── State ─────────────────────────────────────────────────────────────────
21
+ let mode = "idle"; // idle | listening | transcribing | thinking | speaking
22
+ let messages = []; // [{id, role:'user'|'agent', text, t, via, dur?, audio?}]
23
+ let nextId = 1;
24
+ let pendingUserText = ""; // live partial during transcribing
25
+ let isCancelled = false;
26
+
27
+ let mediaRecorder = null;
28
+ let audioStream = null;
29
+ let recordedChunks = [];
30
+ let recorderMime = "";
31
+ let recorderFormat = "webm";
32
+ let liveBusy = false;
33
+
34
+ // Web Audio analyser — drives the live capsule wave from real mic amplitude
35
+ let audioCtx = null;
36
+ let analyser = null;
37
+ let freqData = null;
38
+ let waveRaf = null;
39
+
40
+ let streamingAgentEntry = null; // { id, role:'agent', el, ... } during thinking/speaking
41
+ let toolPillsByName = {}; // active tool pills inside the streaming bubble row
42
+ let ttsAudio = null; // <audio> playing the agent reply
43
+
44
+ let history = []; // [{role:'user'|'assistant', content}] sent to daemon for context
45
+ let theme = "light";
46
+ let position = "right";
47
+ let agentName = "Superagente"; // overwritten from config on first render
48
+
49
+ // Guard so a duplicate `done` event from the daemon never spawns a second
50
+ // requestTts / second finalize on the same in-flight bubble.
51
+ let doneHandled = false;
52
+ let ttsTimer = null;
53
+ // Which agent turn (by message id) is waiting for its TTS audio to attach.
54
+ // We finalize the bubble immediately on `done`, then post-attach the
55
+ // scrubber when (or if) tts-ready arrives.
56
+ let pendingTtsTurnId = null;
57
+
58
+ // ── Inline SVG icons (mirrors the design's I.* set) ──────────────────────
59
+ const SVG = (path, attrs = {}) => {
60
+ const a = Object.entries(attrs).map(([k, v]) => `${k}="${v}"`).join(" ");
61
+ return `<svg ${a} fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">${path}</svg>`;
62
+ };
63
+ const ICON = {
64
+ mic: () => SVG('<rect x="9" y="3" width="6" height="11" rx="3"/><path d="M5 11a7 7 0 0 0 14 0M12 18v3"/>', { width: 18, height: 18, viewBox: "0 0 24 24" }),
65
+ text: () => SVG('<path d="M5 7h14M5 12h14M5 17h9"/>', { width: 17, height: 17, viewBox: "0 0 24 24" }),
66
+ send: () => SVG('<path d="M5 12h13M12 5l7 7-7 7"/>', { width: 18, height: 18, viewBox: "0 0 24 24" }),
67
+ x: () => SVG('<path d="M6 6l12 12M18 6L6 18"/>', { width: 17, height: 17, viewBox: "0 0 24 24", "stroke-width": "1.9" }),
68
+ close: () => SVG('<path d="M6 6l12 12M18 6L6 18"/>', { width: 15, height: 15, viewBox: "0 0 24 24" }),
69
+ plus: () => SVG('<path d="M12 5v14M5 12h14"/>', { width: 14, height: 14, viewBox: "0 0 24 24", "stroke-width": "1.9" }),
70
+ person: () => SVG('<circle cx="12" cy="8" r="3.6"/><path d="M5.5 20c0-3.6 2.9-6 6.5-6s6.5 2.4 6.5 6"/>', { width: 12, height: 12, viewBox: "0 0 24 24" }),
71
+ refresh:() => SVG('<path d="M3 12a9 9 0 0 1 15.5-6.2L21 8M21 3v5h-5M21 12a9 9 0 0 1-15.5 6.2L3 16M3 21v-5h5"/>', { width: 13, height: 13, viewBox: "0 0 24 24", "stroke-width": "1.9" }),
72
+ copy: () => SVG('<rect x="9" y="9" width="11" height="11" rx="2.5"/><path d="M5 15V5a2 2 0 0 1 2-2h8"/>', { width: 13, height: 13, viewBox: "0 0 24 24" }),
73
+ check: () => SVG('<path d="M5 12l4.5 4.5L19 7"/>', { width: 13, height: 13, viewBox: "0 0 24 24", "stroke-width": "2" }),
74
+ play: () => `<svg width="13" height="13" viewBox="0 0 24 24" fill="currentColor"><path d="M8 5.2v13.6c0 .8.9 1.3 1.6.9l10.5-6.8c.6-.4.6-1.3 0-1.7L9.6 4.3C8.9 3.9 8 4.4 8 5.2z"/></svg>`,
75
+ pause: () => `<svg width="13" height="13" viewBox="0 0 24 24" fill="currentColor"><rect x="6.5" y="5" width="4" height="14" rx="1.3"/><rect x="13.5" y="5" width="4" height="14" rx="1.3"/></svg>`,
76
+ stop: () => `<svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="6" width="12" height="12" rx="3"/></svg>`,
77
+ };
78
+
79
+ // ── DOM scaffolding (built once) ─────────────────────────────────────────
80
+ const $root = document.getElementById("root");
81
+ const $connBadge = document.getElementById("conn-badge");
82
+
83
+ $root.className = "float-root enter pos-right";
84
+ $root.innerHTML = `
85
+ <div class="cap" id="cap">
86
+ <div class="cap-badge" id="cap-badge" style="display:none">
87
+ <span class="g mic" id="badge-mic"></span>
88
+ <span class="g txt" id="badge-txt"></span>
89
+ </div>
90
+ <div class="center" id="cap-center"></div>
91
+ <div class="cap-actions" id="cap-actions"></div>
92
+ </div>
93
+ <div id="caption-slot"></div>
94
+ <div id="conv-slot"></div>
95
+ <div id="session-slot"></div>
96
+ `;
97
+ const $cap = $root.querySelector("#cap");
98
+ const $capBadge = $root.querySelector("#cap-badge");
99
+ const $badgeMic = $root.querySelector("#badge-mic");
100
+ const $badgeTxt = $root.querySelector("#badge-txt");
101
+ const $capCenter = $root.querySelector("#cap-center");
102
+ const $capActions = $root.querySelector("#cap-actions");
103
+ const $captionSlot = $root.querySelector("#caption-slot");
104
+ const $convSlot = $root.querySelector("#conv-slot");
105
+ const $sessionSlot = $root.querySelector("#session-slot");
106
+
107
+ $badgeMic.innerHTML = ICON.mic();
108
+ $badgeTxt.innerHTML = ICON.text();
109
+ $capActions.style.cssText = "display:flex; align-items:center; gap:6px;";
110
+
111
+ // ── Initial config from main (theme, position, shortcut, agent name) ─────
112
+ // Don't render() before this resolves — otherwise the first paint creates
113
+ // the input with the default "Hablá o escribí a Superagente…" placeholder
114
+ // and the render() guard ("if (!existingInput)") never recreates it, so
115
+ // the agent name stays wrong until the user changes mode.
116
+ let configReady = false;
117
+ Promise.all([
118
+ window.apx?.getTheme?.() ?? "light",
119
+ window.apx?.getPosition?.() ?? "right",
120
+ window.apx?.getShortcut?.() ?? "CommandOrControl+G",
121
+ window.apx?.getAgentName?.() ?? "Superagente",
122
+ ]).then(([th, pos, shortcut, name]) => {
123
+ theme = th || "light";
124
+ position = pos || "right";
125
+ agentName = (name && String(name).trim()) || "Superagente";
126
+ document.documentElement.setAttribute("data-theme", theme);
127
+ setPosition(position);
128
+ initialCaption(shortcut);
129
+ configReady = true;
130
+ // If render() already fired the bootstrap paint (because the IPC was
131
+ // slow), the existing input has the stale placeholder. Patch it in
132
+ // place so the user sees the real agent name on the very first frame
133
+ // they can interact with.
134
+ const input = $capCenter.querySelector("input");
135
+ if (input) input.placeholder = `Hablá o escribí a ${agentName}…`;
136
+ render();
137
+ }).catch(() => {
138
+ document.documentElement.setAttribute("data-theme", "light");
139
+ setPosition("right");
140
+ initialCaption("CommandOrControl+G");
141
+ configReady = true;
142
+ render();
143
+ });
144
+
145
+ function setPosition(p) {
146
+ $root.classList.remove("pos-left", "pos-center", "pos-right");
147
+ $root.classList.add("pos-" + p);
148
+ }
149
+
150
+ function formatShortcut(s) {
151
+ if (!s) return "⌘G";
152
+ const isMac = (window.apx?.platform || (navigator.platform || "").toLowerCase()).indexOf("mac") >= 0
153
+ || (window.apx?.platform === "darwin");
154
+ return s
155
+ .replace("CommandOrControl", isMac ? "⌘" : "Ctrl")
156
+ .replace("Command", "⌘")
157
+ .replace("Control", "Ctrl")
158
+ .replace("Shift", "⇧")
159
+ .replace("Option", "⌥")
160
+ .replace("Alt", "⌥")
161
+ .replace(/\+/g, "");
162
+ }
163
+
164
+ function initialCaption(shortcut) {
165
+ const sc = formatShortcut(shortcut);
166
+ $captionSlot.innerHTML = `
167
+ <div class="caption">Mantené <span class="kbd">${sc}</span> para hablar
168
+ <span class="kbd">⌥ /</span> para escribir</div>
169
+ `;
170
+ }
171
+
172
+ // ── Render: capsule center + actions vary by mode ────────────────────────
173
+ //
174
+ // CRITICAL: the idle <input> must NOT be re-created on every keystroke,
175
+ // or it loses focus. We keep the input element across re-renders and only
176
+ // tear it down when mode changes. Badge + right-side actions rebuild
177
+ // freely (they have no focus state to preserve).
178
+ function render() {
179
+ $cap.classList.toggle("listening", mode === "listening");
180
+ $cap.classList.toggle("busy", mode === "transcribing" || mode === "thinking" || mode === "speaking");
181
+ const existingInput = $capCenter.querySelector("input");
182
+ const currentInputText = existingInput ? existingInput.value : "";
183
+
184
+ // badge visibility: shown while typing or listening
185
+ const typing = mode === "idle" && currentInputText.trim() !== "";
186
+ const showBadge = typing || mode === "listening";
187
+ $capBadge.style.display = showBadge ? "" : "none";
188
+ if (showBadge) {
189
+ $badgeMic.classList.toggle("show", !typing);
190
+ $badgeMic.classList.toggle("hide", typing);
191
+ $badgeTxt.classList.toggle("show", typing);
192
+ $badgeTxt.classList.toggle("hide", !typing);
193
+ }
194
+
195
+ // center — only rebuild when there's a real change
196
+ if (mode === "idle") {
197
+ if (!existingInput) {
198
+ // Mode just transitioned to idle (or first render). Create the input
199
+ // once; subsequent renders will hit the `existingInput` branch and
200
+ // leave focus/selection alone.
201
+ $capCenter.innerHTML = "";
202
+ const el = document.createElement("input");
203
+ el.type = "text";
204
+ el.placeholder = `Hablá o escribí a ${agentName}…`;
205
+ el.addEventListener("input", () => render());
206
+ el.addEventListener("keydown", (e) => {
207
+ if (e.key === "Enter" && el.value.trim()) {
208
+ const text = el.value.trim();
209
+ el.value = "";
210
+ sendText(text);
211
+ }
212
+ });
213
+ $capCenter.appendChild(el);
214
+ if (window._focusOnNext) {
215
+ window._focusOnNext = false;
216
+ setTimeout(() => el.focus(), 30);
217
+ }
218
+ }
219
+ // else: input already there → leave it alone (preserves focus + caret)
220
+ } else if (mode === "listening") {
221
+ // Only rebuild the wave if it's not already there (avoids restarting
222
+ // CSS animations / Web Audio binding every render).
223
+ let wave = $capCenter.querySelector(".cap-wave");
224
+ if (!wave) {
225
+ $capCenter.innerHTML = "";
226
+ wave = document.createElement("div");
227
+ wave.className = "cap-wave reactive"; // JS drives the bar heights from analyser
228
+ for (let i = 0; i < 26; i++) {
229
+ const b = document.createElement("i");
230
+ b.style.height = "4px";
231
+ wave.appendChild(b);
232
+ }
233
+ $capCenter.appendChild(wave);
234
+ }
235
+ } else if (mode === "transcribing" || mode === "thinking" || mode === "speaking") {
236
+ // Only swap innerHTML when the rendered mode actually changes — keeps
237
+ // the shimmer/dots animations from restarting on every render() call.
238
+ if ($capCenter.dataset.mode !== mode) {
239
+ $capCenter.dataset.mode = mode;
240
+ if (mode === "transcribing") {
241
+ $capCenter.innerHTML = `<span class="status"><span class="dots"><i></i><i></i><i></i></span><span class="shimmer">Transcribiendo…</span></span>`;
242
+ } else if (mode === "thinking") {
243
+ $capCenter.innerHTML = `<span class="status"><span class="dots"><i></i><i></i><i></i></span><span class="shimmer">Pensando…</span></span>`;
244
+ } else if (mode === "speaking") {
245
+ $capCenter.innerHTML = `<span class="status"><img class="sa-glyph" src="assets/superagent.png" alt=""/><span class="shimmer">${escapeHtml(agentName)} está hablando…</span></span>`;
246
+ }
247
+ }
248
+ }
249
+ // Clear data-mode when we're back to idle/listening so a future busy mode
250
+ // re-renders correctly.
251
+ if (mode === "idle" || mode === "listening") $capCenter.dataset.mode = "";
252
+
253
+ // actions
254
+ $capActions.innerHTML = "";
255
+ const addBtn = (cls, label, icon, onClick) => {
256
+ const b = document.createElement("button");
257
+ b.className = "act" + (cls ? " " + cls : "");
258
+ b.setAttribute("aria-label", label);
259
+ b.title = label;
260
+ b.innerHTML = icon;
261
+ b.addEventListener("click", onClick);
262
+ $capActions.appendChild(b);
263
+ return b;
264
+ };
265
+ if (mode === "idle") {
266
+ if (currentInputText.trim()) {
267
+ addBtn("", "Enviar", ICON.send(), () => {
268
+ const text = $capCenter.querySelector("input")?.value.trim();
269
+ if (text) { $capCenter.querySelector("input").value = ""; sendText(text); }
270
+ });
271
+ } else {
272
+ addBtn("", "Hablar", ICON.mic(), () => startListening());
273
+ }
274
+ } else if (mode === "listening") {
275
+ addBtn("ghost", "Cancelar", ICON.x(), () => cancel());
276
+ addBtn("", "Enviar", ICON.send(), () => stopListening(/* commit */ true));
277
+ } else if (mode === "transcribing") {
278
+ addBtn("ghost", "Cancelar", ICON.x(), () => cancel());
279
+ } else if (mode === "thinking") {
280
+ addBtn("ghost", "Cancelar", ICON.x(), () => cancel());
281
+ } else if (mode === "speaking") {
282
+ addBtn("ghost", "Detener", ICON.stop(), () => stopSpeaking());
283
+ }
284
+
285
+ // caption visible only when idle AND no messages yet
286
+ $captionSlot.style.display = (mode === "idle" && messages.length === 0) ? "" : "none";
287
+
288
+ // session bar visible when there are messages
289
+ if (messages.length > 0 || mode !== "idle") {
290
+ renderSessionBar();
291
+ } else {
292
+ $sessionSlot.innerHTML = "";
293
+ }
294
+
295
+ // conv card visible when there's any content
296
+ const wantConv = messages.length > 0 || mode === "transcribing" || mode === "thinking" || mode === "speaking";
297
+ if (wantConv) ensureConv();
298
+ else $convSlot.innerHTML = "";
299
+
300
+ requestWindowResize();
301
+ }
302
+
303
+ function renderSessionBar() {
304
+ if ($sessionSlot.querySelector(".session-bar")) return; // keep DOM stable
305
+ $sessionSlot.innerHTML = `
306
+ <div class="session-bar">
307
+ <button class="sbtn new" id="btn-new"><span class="ic">${ICON.plus()}</span> Nueva sesión</button>
308
+ <button class="sbtn close" id="btn-close"><span class="ic">${ICON.close()}</span> Cerrar</button>
309
+ </div>
310
+ `;
311
+ $sessionSlot.querySelector("#btn-new").addEventListener("click", newSession);
312
+ $sessionSlot.querySelector("#btn-close").addEventListener("click", closeWindow);
313
+ }
314
+
315
+ // ── Conversation card ────────────────────────────────────────────────────
316
+ let $convScroll = null;
317
+ function ensureConv() {
318
+ if (!$convSlot.firstChild) {
319
+ $convSlot.innerHTML = `<div class="conv"><div class="conv-scroll" id="conv-scroll"></div></div>`;
320
+ $convScroll = $convSlot.querySelector("#conv-scroll");
321
+ // Re-render all existing turns
322
+ messages.forEach((m, i) => appendTurn(m, i === messages.length - 1));
323
+ if (mode === "transcribing") renderPendingUserPartial();
324
+ if (mode === "thinking" || mode === "speaking") ensureStreamingAgentBubble();
325
+ }
326
+ }
327
+
328
+ function appendTurn(m, isLast) {
329
+ if (!$convScroll) return;
330
+ if (isLast) clearLastClass();
331
+ const t = document.createElement("div");
332
+ t.className = "turn" + (isLast ? " last" : "");
333
+ t.dataset.id = m.id;
334
+ if (m.role === "user") {
335
+ const viaIcon = m.via === "voice" ? `<span class="via-mic" title="Mensaje de voz">${ICON.mic()}</span>` : "";
336
+ t.innerHTML = `
337
+ <div class="role user">
338
+ <span class="ava">${ICON.person()}</span>
339
+ <span class="who">Vos</span>
340
+ <span class="time">${m.t}</span>
341
+ </div>
342
+ <div class="bubble-user">${escapeHtml(m.text)}${viaIcon}</div>
343
+ `;
344
+ } else {
345
+ t.innerHTML = `
346
+ <div class="role agent">
347
+ <span class="ava sa"><img src="assets/superagent.png" alt=""/></span>
348
+ <span class="who">${escapeHtml(agentName)}</span>
349
+ <span class="time">${m.t || ""}</span>
350
+ </div>
351
+ <div class="msg-agent">${formatWordsHtml(m.text)}</div>
352
+ ${m.audio ? "" /* scrubber added separately */ : ""}
353
+ <div class="turn-actions">
354
+ <button class="chip btn-regen">${ICON.refresh()} Regenerar</button>
355
+ <button class="chip btn-copy">${ICON.copy()} Copiar</button>
356
+ </div>
357
+ `;
358
+ if (m.audio && m.dur) {
359
+ // Insert scrubber before turn-actions
360
+ const scrubberHtml = buildScrubberHtml(m);
361
+ const actions = t.querySelector(".turn-actions");
362
+ actions.insertAdjacentHTML("beforebegin", scrubberHtml);
363
+ wireScrubber(t, m);
364
+ }
365
+ // copy
366
+ t.querySelector(".btn-copy")?.addEventListener("click", (e) => {
367
+ navigator.clipboard?.writeText(m.text).catch(() => {});
368
+ const btn = e.currentTarget;
369
+ btn.classList.add("done");
370
+ btn.innerHTML = `${ICON.check()} Copiado`;
371
+ setTimeout(() => { btn.classList.remove("done"); btn.innerHTML = `${ICON.copy()} Copiar`; }, 1400);
372
+ });
373
+ // regen: only the LAST agent turn can be regenerated. Past turns
374
+ // can't because we'd have to re-issue the user prompt that came right
375
+ // before THEM, then re-thread the entire suffix of the conversation,
376
+ // and that gets semantically confusing fast. CSS hides .btn-regen on
377
+ // older turns; this guard catches anyone who routes around the CSS.
378
+ t.querySelector(".btn-regen")?.addEventListener("click", () => {
379
+ const lastMsg = messages[messages.length - 1];
380
+ if (!lastMsg || lastMsg.id !== m.id) {
381
+ console.warn("desktop renderer: regen only works on the last turn");
382
+ return;
383
+ }
384
+ const lastUser = [...messages].reverse().find((x) => x.role === "user");
385
+ if (!lastUser) return;
386
+ // Drop the matching assistant entry from `history` so the daemon
387
+ // gets the same conversation it had right before producing `m`.
388
+ if (history.length && history[history.length - 1].role === "assistant") {
389
+ history.pop();
390
+ }
391
+ messages = messages.filter((x) => x.id !== m.id);
392
+ rebuildConvFromState();
393
+ startAgentTurn();
394
+ sendToDaemon(lastUser.text);
395
+ });
396
+ }
397
+ $convScroll.appendChild(t);
398
+ scrollConvToBottom();
399
+ }
400
+
401
+ // Strip the `last` modifier from every turn currently in the scroll. Called
402
+ // right before we mount a new "last" turn so the previous one stops being
403
+ // styled as the freshest reply (and its Regenerate button hides).
404
+ function clearLastClass() {
405
+ if (!$convScroll) return;
406
+ $convScroll.querySelectorAll(".turn.last").forEach((el) => el.classList.remove("last"));
407
+ }
408
+
409
+ function rebuildConvFromState() {
410
+ if (!$convScroll) return;
411
+ $convScroll.innerHTML = "";
412
+ streamingAgentEntry = null;
413
+ toolPillsByName = {};
414
+ messages.forEach((m, i) => appendTurn(m, i === messages.length - 1));
415
+ }
416
+
417
+ function renderPendingUserPartial() {
418
+ if (!$convScroll) return;
419
+ const existing = $convScroll.querySelector("[data-pending='user']");
420
+ if (existing) {
421
+ const bub = existing.querySelector(".bubble-user");
422
+ bub.innerHTML = `${escapeHtml(pendingUserText)}<span class="caret"></span>`;
423
+ return;
424
+ }
425
+ const t = document.createElement("div");
426
+ t.className = "turn last";
427
+ t.dataset.pending = "user";
428
+ t.innerHTML = `
429
+ <div class="role user">
430
+ <span class="ava">${ICON.person()}</span>
431
+ <span class="who">Vos</span>
432
+ <span class="time">${nowHHMM()}</span>
433
+ </div>
434
+ <div class="bubble-user">${escapeHtml(pendingUserText)}<span class="caret"></span></div>
435
+ `;
436
+ $convScroll.appendChild(t);
437
+ scrollConvToBottom();
438
+ }
439
+ function removePendingUserPartial() {
440
+ $convScroll?.querySelector("[data-pending='user']")?.remove();
441
+ }
442
+
443
+ function ensureStreamingAgentBubble() {
444
+ if (!$convScroll) return;
445
+ if (streamingAgentEntry?.el && document.body.contains(streamingAgentEntry.el)) return;
446
+ clearLastClass();
447
+ const id = nextId++;
448
+ const t = document.createElement("div");
449
+ t.className = "turn last";
450
+ t.dataset.id = id;
451
+ // Placeholder while we wait for the first token: just the dots — the
452
+ // word "Pensando" already appears in the capsule, no need to repeat it
453
+ // inside the bubble. See feedback on doble-"Pensando" in 2026-05-30.
454
+ t.innerHTML = `
455
+ <div class="role agent">
456
+ <span class="ava sa"><img src="assets/superagent.png" alt=""/></span>
457
+ <span class="who">${escapeHtml(agentName)}</span>
458
+ </div>
459
+ <div class="msg-agent">
460
+ <span class="status-line"><span class="dots"><i></i><i></i><i></i></span></span>
461
+ </div>
462
+ `;
463
+ $convScroll.appendChild(t);
464
+ streamingAgentEntry = { id, el: t, msgEl: t.querySelector(".msg-agent"), text: "", started: false };
465
+ scrollConvToBottom();
466
+ }
467
+
468
+ function appendStreamingToken(chunk) {
469
+ ensureStreamingAgentBubble();
470
+ if (!streamingAgentEntry.started) {
471
+ streamingAgentEntry.started = true;
472
+ streamingAgentEntry.msgEl.innerHTML = ""; // clear the dots placeholder
473
+ }
474
+ streamingAgentEntry.text += chunk;
475
+ // Re-render with the word-in animation: split into spans on word boundaries.
476
+ streamingAgentEntry.msgEl.innerHTML = formatWordsHtml(streamingAgentEntry.text) + `<span class="caret"></span>`;
477
+ scrollConvToBottom();
478
+ }
479
+
480
+ function finalizeStreamingAgent({ audio, dur } = {}) {
481
+ if (!streamingAgentEntry) return;
482
+ const m = {
483
+ id: streamingAgentEntry.id,
484
+ role: "agent",
485
+ text: streamingAgentEntry.text || "",
486
+ t: nowHHMM(),
487
+ audio: audio || null,
488
+ dur: dur || null,
489
+ fresh: true,
490
+ };
491
+ messages.push(m);
492
+ history.push({ role: "assistant", content: m.text });
493
+ // Replace the streaming placeholder with the finished turn
494
+ streamingAgentEntry.el.remove();
495
+ streamingAgentEntry = null;
496
+ appendTurn(m, true);
497
+ // Force resize so the window grows to fit the agent reply right away.
498
+ requestWindowResize();
499
+ }
500
+
501
+ function addToolPill(name) {
502
+ ensureStreamingAgentBubble();
503
+ if (toolPillsByName[name]) return;
504
+ const pill = document.createElement("div");
505
+ pill.className = "tool-pill";
506
+ pill.innerHTML = `<div class="spinner"></div><span>${escapeHtml(name)}</span>`;
507
+ $convScroll.insertBefore(pill, streamingAgentEntry.el);
508
+ toolPillsByName[name] = pill;
509
+ scrollConvToBottom();
510
+ }
511
+ function updateToolPill(name) {
512
+ const pill = toolPillsByName[name];
513
+ if (!pill) return;
514
+ pill.innerHTML = `<span class="check">✓</span><span>${escapeHtml(name)}</span>`;
515
+ }
516
+
517
+ // ── Audio scrubber ───────────────────────────────────────────────────────
518
+ function buildScrubberHtml(m) {
519
+ const N = 38;
520
+ const bars = waveShape(N, 13);
521
+ const dur = m.dur || 0;
522
+ const fmt = (s) => `0:${String(Math.round(s)).padStart(2, "0")}`;
523
+ return `
524
+ <div class="audio" data-bars="${N}">
525
+ <button class="play" aria-label="Reproducir respuesta">${ICON.play()}</button>
526
+ <div class="wavebar">
527
+ ${bars.map((h) => `<i style="height:${Math.round(h * 24)}px"></i>`).join("")}
528
+ </div>
529
+ <span class="dur">${fmt(dur)}</span>
530
+ </div>
531
+ `;
532
+ }
533
+ function wireScrubber(turnEl, m) {
534
+ const N = 38;
535
+ const audioEl = turnEl.querySelector(".audio");
536
+ if (!audioEl || !m.audio) return;
537
+ const $play = audioEl.querySelector(".play");
538
+ const $bar = audioEl.querySelector(".wavebar");
539
+ const bars = $bar.querySelectorAll("i");
540
+ const $dur = audioEl.querySelector(".dur");
541
+ const dur = m.dur || 1;
542
+ const fmt = (s) => `0:${String(Math.round(s)).padStart(2, "0")}`;
543
+ const audio = new Audio(m.audio);
544
+ let raf = null;
545
+ let progress = 0;
546
+
547
+ const setProgress = (p) => {
548
+ progress = Math.max(0, Math.min(1, p));
549
+ const cur = Math.floor(progress * N);
550
+ bars.forEach((b, i) => {
551
+ b.classList.toggle("on", i <= cur);
552
+ b.classList.toggle("cur", i === cur && !audio.paused);
553
+ });
554
+ $dur.textContent = progress > 0 || !audio.paused ? fmt(progress * dur) : fmt(dur);
555
+ };
556
+
557
+ const tick = () => {
558
+ if (audio.duration > 0) setProgress(audio.currentTime / audio.duration);
559
+ raf = requestAnimationFrame(tick);
560
+ };
561
+ audio.addEventListener("play", () => { $play.innerHTML = ICON.pause(); raf = requestAnimationFrame(tick); mode = "speaking"; render(); });
562
+ audio.addEventListener("pause", () => { $play.innerHTML = ICON.play(); if (raf) cancelAnimationFrame(raf); if (mode === "speaking") { mode = "idle"; render(); } });
563
+ audio.addEventListener("ended", () => { setProgress(1); if (mode === "speaking") { mode = "idle"; render(); } });
564
+
565
+ $play.addEventListener("click", () => audio.paused ? audio.play() : audio.pause());
566
+ $bar.addEventListener("click", (e) => {
567
+ const r = $bar.getBoundingClientRect();
568
+ const p = Math.max(0, Math.min(1, (e.clientX - r.left) / r.width));
569
+ if (audio.duration > 0) audio.currentTime = p * audio.duration;
570
+ setProgress(p);
571
+ });
572
+
573
+ // If the audio errors out (404, decode error, autoplay block, etc) make
574
+ // sure the capsule doesn't stay stuck in "está hablando…".
575
+ audio.addEventListener("error", () => {
576
+ if (mode === "speaking") { mode = "idle"; render(); }
577
+ });
578
+
579
+ // autoplay if it's the fresh reply
580
+ if (m.fresh) {
581
+ m.fresh = false;
582
+ ttsAudio?.pause?.();
583
+ ttsAudio = audio;
584
+ audio.play().catch(() => {
585
+ // Autoplay block (rare in Electron with user-gesture but possible
586
+ // when the window has never been focused). Bail out so the capsule
587
+ // returns to idle and the user can still tap "play" on the scrubber.
588
+ if (mode === "speaking" || mode === "thinking") { mode = "idle"; render(); }
589
+ });
590
+ }
591
+ }
592
+
593
+ // Post-finalize hook: add a scrubber to an already-rendered agent turn
594
+ // when its TTS audio arrives. Called from the `tts-ready` daemon event.
595
+ function attachAudioToTurn(turnId, { url, dur }) {
596
+ const m = messages.find((x) => x.id === turnId);
597
+ if (!m) return;
598
+ m.audio = url;
599
+ m.dur = dur || 0;
600
+ m.fresh = true; // autoplay the freshly-arrived reply
601
+ const turnEl = $convScroll?.querySelector(`[data-id="${turnId}"]`);
602
+ if (!turnEl) return;
603
+ // Insert the scrubber HTML just before turn-actions (matches appendTurn order).
604
+ const actions = turnEl.querySelector(".turn-actions");
605
+ const html = buildScrubberHtml(m);
606
+ if (actions) actions.insertAdjacentHTML("beforebegin", html);
607
+ else turnEl.insertAdjacentHTML("beforeend", html);
608
+ wireScrubber(turnEl, m);
609
+ scrollConvToBottom();
610
+ }
611
+
612
+ function waveShape(n, seed = 7) {
613
+ const out = []; let s = seed;
614
+ for (let i = 0; i < n; i++) {
615
+ s = (s * 9301 + 49297) % 233280;
616
+ const r = s / 233280;
617
+ const env = Math.sin((i / n) * Math.PI);
618
+ out.push(0.25 + (0.35 + r * 0.65) * (0.55 + env * 0.6));
619
+ }
620
+ return out;
621
+ }
622
+
623
+ // ── Recording flow ───────────────────────────────────────────────────────
624
+ function startListening() {
625
+ if (mode !== "idle") return;
626
+ isCancelled = false;
627
+ mode = "listening";
628
+ render();
629
+ startMic();
630
+ }
631
+
632
+ // commit=true → stop and send the recording; false → cancel
633
+ function stopListening(commit) {
634
+ if (mode !== "listening") return;
635
+ isCancelled = !commit;
636
+ if (!commit) {
637
+ mode = "idle";
638
+ stopMic();
639
+ render();
640
+ return;
641
+ }
642
+ mode = "transcribing";
643
+ render();
644
+ stopMic(); // onstop will resolve, send to daemon
645
+ }
646
+
647
+ function cancel() {
648
+ isCancelled = true;
649
+ if (mode === "listening") { stopMic(); }
650
+ if (mode === "thinking" || mode === "speaking") { window.apx?.cancel?.(); }
651
+ removePendingUserPartial();
652
+ if (streamingAgentEntry) {
653
+ streamingAgentEntry.el.remove();
654
+ streamingAgentEntry = null;
655
+ }
656
+ mode = "idle";
657
+ render();
658
+ }
659
+
660
+ function stopSpeaking() {
661
+ try { ttsAudio?.pause?.(); } catch {}
662
+ if (mode === "speaking") { mode = "idle"; render(); }
663
+ }
664
+
665
+ // ── Mic capture (buffer chunks, cumulative blob = always has header) ─────
666
+ async function startMic() {
667
+ try {
668
+ audioStream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
669
+
670
+ // Web Audio analyser → real-time amplitude for the capsule wave.
671
+ try {
672
+ audioCtx = new (window.AudioContext || window.webkitAudioContext)();
673
+ const src = audioCtx.createMediaStreamSource(audioStream);
674
+ analyser = audioCtx.createAnalyser();
675
+ analyser.fftSize = 128; // → 64 frequency bins
676
+ analyser.smoothingTimeConstant = 0.72; // analyser-side temporal smoothing
677
+ analyser.minDecibels = -85; // floor (silence)
678
+ analyser.maxDecibels = -15; // ceiling (loud speech)
679
+ src.connect(analyser);
680
+ freqData = new Uint8Array(analyser.frequencyBinCount);
681
+ startWaveLoop();
682
+ } catch (e) {
683
+ console.warn("desktop renderer: AnalyserNode init failed", e);
684
+ }
685
+
686
+ const mimeType = MediaRecorder.isTypeSupported("audio/webm;codecs=opus")
687
+ ? "audio/webm;codecs=opus"
688
+ : "audio/ogg;codecs=opus";
689
+ recorderFormat = mimeType.includes("webm") ? "webm" : "ogg";
690
+ recorderMime = mimeType;
691
+ recordedChunks = [];
692
+ mediaRecorder = new MediaRecorder(audioStream, { mimeType, audioBitsPerSecond: 32000 });
693
+ mediaRecorder.ondataavailable = (e) => {
694
+ if (e.data && e.data.size > 0) recordedChunks.push(e.data);
695
+ runLivePartial();
696
+ };
697
+ mediaRecorder.onstop = async () => {
698
+ if (isCancelled) { recordedChunks = []; if (mode !== "idle") { mode = "idle"; render(); } return; }
699
+ const raw = await transcribeBuffered();
700
+ const text = (raw || "").trim();
701
+ recordedChunks = [];
702
+ // Guard with .trim() — whisper occasionally returns a single space or
703
+ // newline for very short clips, which used to commit an empty bubble.
704
+ if (!text || isCancelled) {
705
+ mode = "idle";
706
+ pendingUserText = "";
707
+ removePendingUserPartial();
708
+ render();
709
+ return;
710
+ }
711
+ pendingUserText = text;
712
+ commitUserMessage(text, /* via */ "voice");
713
+ };
714
+ mediaRecorder.start(2000);
715
+ } catch (e) {
716
+ console.error("desktop renderer: mic error", e);
717
+ mode = "idle";
718
+ render();
719
+ }
720
+ }
721
+ function stopMic() {
722
+ try { mediaRecorder?.stop(); } catch {}
723
+ try { audioStream?.getTracks().forEach((t) => t.stop()); } catch {}
724
+ mediaRecorder = null;
725
+ audioStream = null;
726
+ stopWaveLoop();
727
+ try { audioCtx?.close(); } catch {}
728
+ audioCtx = null;
729
+ analyser = null;
730
+ freqData = null;
731
+ }
732
+
733
+ // ── Reactive wave: amplitude-driven bar heights (runs while mode === listening)
734
+ function startWaveLoop() {
735
+ stopWaveLoop();
736
+ // Per-bar smoothed amplitude so heights don't twitch frame-to-frame.
737
+ let smoothed = null;
738
+ const tick = () => {
739
+ if (mode !== "listening" || !analyser) { waveRaf = null; return; }
740
+ analyser.getByteFrequencyData(freqData);
741
+ const wave = $capCenter.querySelector(".cap-wave");
742
+ if (wave) {
743
+ const bars = wave.children;
744
+ const n = bars.length;
745
+ if (!smoothed || smoothed.length !== n) smoothed = new Float32Array(n);
746
+ // Only use the lower ~60% of the spectrum — voice energy lives there;
747
+ // upper bins are mostly noise hiss that would make bars jiggle uselessly.
748
+ const usable = Math.floor(freqData.length * 0.6);
749
+ const binsPerBar = Math.max(1, Math.floor(usable / n));
750
+ for (let i = 0; i < n; i++) {
751
+ let sum = 0;
752
+ const start = i * binsPerBar;
753
+ const end = Math.min(start + binsPerBar, usable);
754
+ for (let j = start; j < end; j++) sum += freqData[j];
755
+ const raw = sum / Math.max(1, (end - start)) / 255; // 0..1
756
+ // ease curve — small inputs stay small, loud inputs reach ~1
757
+ const v = Math.pow(raw, 0.65);
758
+ // exponential smoothing: snappy attack, slower decay (feels punchier)
759
+ const k = v > smoothed[i] ? 0.55 : 0.18;
760
+ smoothed[i] = smoothed[i] + (v - smoothed[i]) * k;
761
+ // map to height: 3px floor (silence) → 28px peak
762
+ const h = 3 + smoothed[i] * 25;
763
+ bars[i].style.height = h.toFixed(1) + "px";
764
+ }
765
+ }
766
+ waveRaf = requestAnimationFrame(tick);
767
+ };
768
+ waveRaf = requestAnimationFrame(tick);
769
+ }
770
+ function stopWaveLoop() {
771
+ if (waveRaf != null) cancelAnimationFrame(waveRaf);
772
+ waveRaf = null;
773
+ }
774
+ async function transcribeBuffered() {
775
+ if (!recordedChunks.length) return "";
776
+ const blob = new Blob(recordedChunks, { type: recorderMime });
777
+ const buf = await blob.arrayBuffer();
778
+ try {
779
+ const r = await window.apx.transcribeChunk(buf, recorderFormat, "auto");
780
+ if (r?.ok && r.text?.trim()) return r.text.trim();
781
+ } catch {}
782
+ return "";
783
+ }
784
+ async function runLivePartial() {
785
+ if (liveBusy || mode !== "listening" || !recordedChunks.length) return;
786
+ liveBusy = true;
787
+ try {
788
+ const text = await transcribeBuffered();
789
+ if (text && mode === "listening") {
790
+ pendingUserText = text;
791
+ // No visible live preview in the capsule wave mode; update is mostly
792
+ // useful for the conv pending-user partial during transcribing.
793
+ }
794
+ } finally { liveBusy = false; }
795
+ }
796
+
797
+ // ── Send: text path + post-transcription commit path ─────────────────────
798
+ function sendText(text) {
799
+ const t = (text || "").trim();
800
+ if (!t) return;
801
+ commitUserMessage(t, /* via */ "text");
802
+ }
803
+ function commitUserMessage(text, via) {
804
+ const clean = (text || "").trim();
805
+ if (!clean) { console.warn("desktop renderer: refused to commit empty user message"); return; }
806
+ const m = { id: nextId++, role: "user", text: clean, t: nowHHMM(), via };
807
+ messages.push(m);
808
+ history.push({ role: "user", content: clean });
809
+ pendingUserText = "";
810
+ removePendingUserPartial();
811
+ ensureConv();
812
+ appendTurn(m, true);
813
+ startAgentTurn();
814
+ sendToDaemon(clean);
815
+ }
816
+
817
+ // Begin a fresh agent turn: reset per-turn flags, switch to thinking,
818
+ // mount the placeholder bubble, and ask main to grow the window now (not
819
+ // one ResizeObserver tick later). Shared by commitUserMessage + regen so
820
+ // both paths set up the daemon-event pipeline identically.
821
+ function startAgentTurn() {
822
+ doneHandled = false;
823
+ pendingTtsTurnId = null;
824
+ if (ttsTimer) { clearTimeout(ttsTimer); ttsTimer = null; }
825
+ mode = "thinking";
826
+ render();
827
+ ensureStreamingAgentBubble();
828
+ requestWindowResize();
829
+ }
830
+
831
+ function sendToDaemon(text) {
832
+ const prev = history.slice(0, -1).slice(-20);
833
+ window.apx?.sendMessage?.(text, prev).catch?.((e) => {
834
+ finalizeStreamingAgentError(e?.message || String(e));
835
+ });
836
+ }
837
+
838
+ function finalizeStreamingAgentError(message) {
839
+ if (!streamingAgentEntry) ensureStreamingAgentBubble();
840
+ streamingAgentEntry.text = "Error: " + message;
841
+ streamingAgentEntry.msgEl.innerHTML = `<span style="color:oklch(0.6 0.2 22)">${escapeHtml(streamingAgentEntry.text)}</span>`;
842
+ finalizeStreamingAgent();
843
+ mode = "idle"; render();
844
+ }
845
+
846
+ function newSession() {
847
+ cancel();
848
+ messages = [];
849
+ history = [];
850
+ streamingAgentEntry = null;
851
+ toolPillsByName = {};
852
+ pendingUserText = "";
853
+ $convSlot.innerHTML = "";
854
+ $sessionSlot.innerHTML = "";
855
+ mode = "idle";
856
+ render();
857
+ }
858
+ function closeWindow() { window.apx?.close?.(); }
859
+
860
+ // ── Daemon event router ──────────────────────────────────────────────────
861
+ window.apx?.onDaemonEvent?.((msg) => {
862
+ switch (msg.type) {
863
+ case "thinking":
864
+ if (mode !== "thinking" && mode !== "speaking") { mode = "thinking"; render(); }
865
+ ensureStreamingAgentBubble();
866
+ break;
867
+ case "token":
868
+ appendStreamingToken(msg.text || "");
869
+ break;
870
+ case "tool_start": addToolPill(msg.name); break;
871
+ case "tool_done": updateToolPill(msg.name); break;
872
+ case "done": {
873
+ // Daemon may emit `done` twice (retry/race). Process only once per turn.
874
+ if (doneHandled) break;
875
+ doneHandled = true;
876
+ const finalText = msg.text || streamingAgentEntry?.text || "";
877
+ // CRITICAL: many models (gemini-flash, groq-fast tier) don't stream
878
+ // tokens — they send the whole reply in `done`. Without this branch
879
+ // the bubble stays with just the dots placeholder until TTS resolves
880
+ // (or 6s timeout), which feels broken. Inject the text NOW so the
881
+ // user sees the reply immediately.
882
+ if (streamingAgentEntry) {
883
+ streamingAgentEntry.text = finalText;
884
+ if (!streamingAgentEntry.started && finalText) {
885
+ streamingAgentEntry.started = true;
886
+ streamingAgentEntry.msgEl.innerHTML = formatWordsHtml(finalText);
887
+ scrollConvToBottom();
888
+ }
889
+ }
890
+ // Finalize and return to idle right away so the capsule frees up.
891
+ // TTS runs in the background; tts-ready will attach the scrubber to
892
+ // the already-rendered turn (see attachAudioToLastAgentTurn below).
893
+ const finalizedTurnId = streamingAgentEntry?.id;
894
+ finalizeStreamingAgent();
895
+ mode = "idle"; render();
896
+ // Fire-and-forget TTS request. If it returns audio, attach it to
897
+ // the turn we just rendered; if it errors / times out / never replies,
898
+ // no big deal — the user already has the text. Guard with a 6s soft
899
+ // timeout so a stuck request doesn't hold ttsTimer state.
900
+ const handled = window.apx?.requestTts?.(finalText);
901
+ if (handled) {
902
+ if (ttsTimer) clearTimeout(ttsTimer);
903
+ ttsTimer = setTimeout(() => { ttsTimer = null; }, 6000);
904
+ // Remember which turn the next tts-ready/failed belongs to.
905
+ pendingTtsTurnId = finalizedTurnId || null;
906
+ }
907
+ break;
908
+ }
909
+ case "tts-ready": {
910
+ if (ttsTimer) { clearTimeout(ttsTimer); ttsTimer = null; }
911
+ if (pendingTtsTurnId != null) {
912
+ attachAudioToTurn(pendingTtsTurnId, { url: msg.url, dur: msg.duration });
913
+ pendingTtsTurnId = null;
914
+ }
915
+ break;
916
+ }
917
+ case "tts-failed":
918
+ // The text is already on screen; just clean up the timer + pending id.
919
+ if (ttsTimer) { clearTimeout(ttsTimer); ttsTimer = null; }
920
+ pendingTtsTurnId = null;
921
+ break;
922
+ case "error":
923
+ finalizeStreamingAgentError(msg.message || "Unknown error");
924
+ break;
925
+ case "cancelled":
926
+ if (streamingAgentEntry) {
927
+ if (!streamingAgentEntry.text) streamingAgentEntry.el.remove();
928
+ else finalizeStreamingAgent();
929
+ streamingAgentEntry = null;
930
+ }
931
+ mode = "idle"; render();
932
+ break;
933
+ }
934
+ });
935
+
936
+ window.apx?.onDaemonConnected?.(() => $connBadge.classList.remove("show"));
937
+ window.apx?.onDaemonDisconnected?.(() => $connBadge.classList.add("show"));
938
+
939
+ // ── Main-process IPC: recording-start/stop (global hotkey), focus-input ──
940
+ window.apx?.onRecordingStart?.(() => { if (mode === "idle") startListening(); });
941
+ window.apx?.onRecordingStop?.(() => { if (mode === "listening") stopListening(true); });
942
+ window.apx?.onFocusInput?.(() => {
943
+ if (mode !== "idle") return;
944
+ window._focusOnNext = true;
945
+ render();
946
+ });
947
+
948
+ // ── Keyboard ─────────────────────────────────────────────────────────────
949
+ document.addEventListener("keydown", (e) => {
950
+ if (e.key === "Escape") {
951
+ e.preventDefault();
952
+ if (mode === "listening" || mode === "transcribing" || mode === "thinking" || mode === "speaking") cancel();
953
+ else closeWindow();
954
+ }
955
+ });
956
+
957
+ // ── Window-size hint to main (collapse to capsule when empty) ────────────
958
+ // ResizeObserver fires whenever the rendered height of #root changes —
959
+ // more reliable than a setTimeout poll (used to under-report by one frame
960
+ // and clip the session bar). 24px bottom padding so the buttons breathe.
961
+ let lastH = 0;
962
+ function requestWindowResize() {
963
+ if (!$root) return;
964
+ const h = Math.ceil($root.getBoundingClientRect().height) + 24;
965
+ if (h !== lastH) {
966
+ lastH = h;
967
+ window.apx?.resize?.(h);
968
+ }
969
+ }
970
+ try {
971
+ const ro = new ResizeObserver(() => requestWindowResize());
972
+ ro.observe($root);
973
+ } catch {
974
+ // Older runtimes without ResizeObserver: fall back to a 250ms poll.
975
+ setInterval(requestWindowResize, 250);
976
+ }
977
+
978
+ // ── Helpers ──────────────────────────────────────────────────────────────
979
+ function nowHHMM() {
980
+ const d = new Date();
981
+ return d.getHours() + ":" + String(d.getMinutes()).padStart(2, "0");
982
+ }
983
+ function escapeHtml(s) {
984
+ return String(s ?? "").replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[c]));
985
+ }
986
+ // Wrap each word in a span.w so wordIn animates fresh tokens
987
+ function formatWordsHtml(text) {
988
+ const escaped = escapeHtml(text);
989
+ // Split on spaces but keep newlines as <br>
990
+ return escaped
991
+ .split("\n")
992
+ .map((line) => line.split(/(\s+)/).map((tok) => tok.match(/^\s+$/) ? tok : `<span class="w">${tok}</span>`).join(""))
993
+ .join("<br>");
994
+ }
995
+ function scrollConvToBottom() {
996
+ if (!$convScroll) return;
997
+ requestAnimationFrame(() => { $convScroll.scrollTop = $convScroll.scrollHeight; });
998
+ }
999
+
1000
+ // ── First paint ──────────────────────────────────────────────────────────
1001
+ // Wait briefly for the config Promise (theme/position/shortcut/agentName).
1002
+ // If it doesn't resolve in 400ms, paint anyway so the window doesn't stay
1003
+ // blank on a wedged daemon — the .then() will patch the placeholder when
1004
+ // it finally resolves.
1005
+ setTimeout(() => { if (!configReady) render(); }, 400);
1006
+ })();