@agentprojectcontext/apx 1.81.0 → 1.89.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 (838) hide show
  1. package/package.json +9 -8
  2. package/skills/apc-context/SKILL.md +14 -7
  3. package/skills/apx/SKILL.md +62 -2
  4. package/skills/apx-mcp/SKILL.md +42 -15
  5. package/src/core/agent/a2a/peers.js +161 -0
  6. package/src/core/agent/a2a/reply.js +379 -27
  7. package/src/core/agent/abort.js +25 -0
  8. package/src/core/agent/agent-model.js +58 -0
  9. package/src/core/agent/agent-tools.js +101 -0
  10. package/src/core/agent/author-line.js +110 -0
  11. package/src/core/agent/build-agent-system.js +52 -18
  12. package/src/core/agent/channels/voice-context.js +17 -2
  13. package/src/core/agent/closing-floor.js +117 -0
  14. package/src/core/agent/constants.js +119 -11
  15. package/src/core/agent/engine-call.js +56 -0
  16. package/src/core/agent/group/run-group-turn.js +218 -0
  17. package/src/core/agent/group/turn-resolver.js +165 -0
  18. package/src/core/agent/judge.js +138 -11
  19. package/src/core/agent/loop/side-effects.js +106 -3
  20. package/src/core/agent/memory.js +31 -18
  21. package/src/core/agent/model-capabilities.js +47 -0
  22. package/src/core/agent/model-router.js +11 -40
  23. package/src/core/agent/prompt-builder.js +47 -0
  24. package/src/core/agent/prompts/channels/a2a.md +12 -0
  25. package/src/core/agent/prompts/channels/code.md +3 -1
  26. package/src/core/agent/prompts/channels/telegram.md +2 -0
  27. package/src/core/agent/prompts/channels/web_code.md +1 -1
  28. package/src/core/agent/prompts/channels/whatsapp.md +37 -0
  29. package/src/core/agent/prompts/core/agent-base.md +2 -0
  30. package/src/core/agent/prompts/discipline/action.md +1 -0
  31. package/src/core/agent/prompts/discipline/two-segment.md +2 -1
  32. package/src/core/agent/prompts/modes/mobility.md +36 -0
  33. package/src/core/agent/retry.js +42 -0
  34. package/src/core/agent/run-agent.js +322 -25
  35. package/src/core/agent/run-turn.js +253 -0
  36. package/src/core/agent/self-memory.js +18 -52
  37. package/src/core/agent/skills/agent-skills.js +118 -0
  38. package/src/core/agent/skills/catalog.js +1 -1
  39. package/src/core/agent/skills/declared.js +13 -0
  40. package/src/core/agent/skills/index-store.js +8 -1
  41. package/src/core/agent/skills/inspector.js +20 -3
  42. package/src/core/agent/skills/loader.js +18 -3
  43. package/src/core/agent/skills/media.js +121 -0
  44. package/src/core/agent/skills/origin.js +61 -0
  45. package/src/core/agent/skills/sections.js +136 -0
  46. package/src/core/agent/super-agent.js +54 -14
  47. package/src/core/agent/tools/handlers/_calendar.js +50 -0
  48. package/src/core/agent/tools/handlers/add-mcp.js +103 -0
  49. package/src/core/agent/tools/handlers/asana-create-task.js +3 -1
  50. package/src/core/agent/tools/handlers/attach-media.js +56 -0
  51. package/src/core/agent/tools/handlers/calendar-create-event.js +38 -0
  52. package/src/core/agent/tools/handlers/calendar-find-slot.js +39 -0
  53. package/src/core/agent/tools/handlers/calendar-list-events.js +39 -0
  54. package/src/core/agent/tools/handlers/calendar-update-event.js +35 -0
  55. package/src/core/agent/tools/handlers/call-agent.js +14 -6
  56. package/src/core/agent/tools/handlers/call-runtime.js +27 -49
  57. package/src/core/agent/tools/handlers/comment-task.js +78 -0
  58. package/src/core/agent/tools/handlers/complete-task.js +60 -0
  59. package/src/core/agent/tools/handlers/configure-agent.js +70 -0
  60. package/src/core/agent/tools/handlers/create-agent.js +68 -0
  61. package/src/core/agent/tools/handlers/create-task.js +41 -3
  62. package/src/core/agent/tools/handlers/import-agent.js +5 -5
  63. package/src/core/agent/tools/handlers/list-mcp-tools.js +83 -0
  64. package/src/core/agent/tools/handlers/list-routines.js +46 -0
  65. package/src/core/agent/tools/handlers/list-tasks.js +8 -1
  66. package/src/core/agent/tools/handlers/mark-commitment.js +57 -0
  67. package/src/core/agent/tools/handlers/read-skill.js +64 -0
  68. package/src/core/agent/tools/handlers/remember-routine.js +2 -8
  69. package/src/core/agent/tools/handlers/remember.js +82 -6
  70. package/src/core/agent/tools/handlers/remove-agent.js +42 -0
  71. package/src/core/agent/tools/handlers/run-routine.js +94 -0
  72. package/src/core/agent/tools/handlers/run-shell.js +44 -2
  73. package/src/core/agent/tools/handlers/send-telegram.js +41 -19
  74. package/src/core/agent/tools/handlers/set-agent-prompt.js +44 -0
  75. package/src/core/agent/tools/handlers/view-media.js +69 -0
  76. package/src/core/agent/tools/handlers/write-agent-memory.js +52 -0
  77. package/src/core/agent/tools/helpers.js +36 -4
  78. package/src/core/agent/tools/names.js +64 -0
  79. package/src/core/agent/tools/registry-bridge.js +24 -1
  80. package/src/core/agent/tools/registry.js +79 -0
  81. package/src/core/agent/tools/tool-call-parser.js +136 -13
  82. package/src/core/agent/vision-bridge.js +69 -0
  83. package/src/core/apc/agent-identity.js +74 -0
  84. package/src/core/apc/agent-rename-refs.js +187 -0
  85. package/src/core/apc/agent-write.js +312 -0
  86. package/src/core/apc/agents-vault.js +1 -1
  87. package/src/core/apc/blob-keys.js +42 -0
  88. package/src/core/apc/frontmatter.js +166 -10
  89. package/src/core/apc/parser.js +33 -83
  90. package/src/core/apc/paths.js +4 -6
  91. package/src/core/apc/project-memory.js +44 -0
  92. package/src/core/apc/projects-helpers.js +33 -0
  93. package/src/core/apc/scaffold.js +36 -16
  94. package/src/core/channels/telegram/ask-callbacks.js +103 -0
  95. package/src/core/channels/telegram/dispatch.js +154 -30
  96. package/src/core/channels/telegram/helpers.js +59 -7
  97. package/src/core/channels/telegram/inbound/audio.js +26 -30
  98. package/src/core/channels/telegram/inbound/file.js +22 -32
  99. package/src/core/channels/telegram/inbound/photo.js +24 -28
  100. package/src/core/channels/telegram/progress-gate.js +76 -0
  101. package/src/core/channels/telegram/reply.js +151 -43
  102. package/src/core/channels/telegram/voice-note.js +150 -0
  103. package/src/core/config/index.js +53 -54
  104. package/src/core/config/paths.js +117 -31
  105. package/src/core/config/redact.js +89 -4
  106. package/src/core/config/secret-values.js +2 -4
  107. package/src/core/constants/actors.js +5 -0
  108. package/src/core/constants/channels.js +3 -1
  109. package/src/core/constants/task-categories.js +73 -0
  110. package/src/core/constants/task-fields.js +28 -0
  111. package/src/core/engines/_globs.js +33 -0
  112. package/src/core/engines/anthropic.js +19 -1
  113. package/src/core/engines/catalog.js +20 -0
  114. package/src/core/engines/gemini.js +84 -16
  115. package/src/core/engines/index.js +19 -3
  116. package/src/core/engines/mock.js +145 -1
  117. package/src/core/engines/openai-compatible.js +150 -15
  118. package/src/core/engines/presets.js +36 -9
  119. package/src/core/engines/zen.js +131 -0
  120. package/src/core/events/bus.js +140 -0
  121. package/src/core/events/mascot-notify.js +156 -0
  122. package/src/core/http-tools/browser.js +366 -36
  123. package/src/core/http-tools/catalog.js +206 -22
  124. package/src/core/http-tools/grep.js +62 -7
  125. package/src/core/http-tools/registry.js +3 -1
  126. package/src/core/i18n/en.js +34 -3
  127. package/src/core/i18n/es.js +36 -4
  128. package/src/core/i18n/index.js +5 -5
  129. package/src/core/i18n/pt.js +32 -3
  130. package/src/core/identity/self.js +14 -1
  131. package/src/core/images/engines/a1111.js +219 -0
  132. package/src/core/images/engines/index.js +187 -0
  133. package/src/core/images/engines/mock.js +74 -0
  134. package/src/core/images/engines/openai.js +130 -0
  135. package/src/core/images/engines/sdcpp.js +204 -0
  136. package/src/core/images/engines/shared.js +218 -0
  137. package/src/core/images/generate.js +260 -0
  138. package/src/core/integrations/catalog.js +8 -3
  139. package/src/core/integrations/plugins/_google-oauth.js +179 -0
  140. package/src/core/integrations/plugins/asana.js +7 -3
  141. package/src/core/integrations/plugins/calendar.js +404 -0
  142. package/src/core/integrations/store.js +2 -0
  143. package/src/core/memory/broker.js +16 -1
  144. package/src/core/memory/compactor.js +19 -3
  145. package/src/core/memory/consolidate.js +2 -2
  146. package/src/core/memory/dated-log.js +66 -0
  147. package/src/core/memory/embed-engines/custom.js +59 -0
  148. package/src/core/memory/embed-engines/gemini.js +9 -3
  149. package/src/core/memory/embed-engines/index.js +87 -15
  150. package/src/core/memory/embeddings.js +52 -24
  151. package/src/core/memory/index.js +13 -3
  152. package/src/core/memory/indexer.js +83 -27
  153. package/src/core/memory/prune.js +182 -0
  154. package/src/core/memory/store.js +22 -0
  155. package/src/core/memory/summarizer.js +72 -8
  156. package/src/core/mobility/answer.js +80 -0
  157. package/src/core/mobility/geofence.js +667 -0
  158. package/src/core/mobility/osm-route.js +282 -0
  159. package/src/core/mobility/preferences.js +91 -0
  160. package/src/core/mobility/state.js +245 -0
  161. package/src/core/mobility/trip-event.js +384 -0
  162. package/src/core/net/tailscale.js +168 -0
  163. package/src/core/profiles/bundled/secretary/PROFILE.md +32 -42
  164. package/src/core/profiles/bundled/secretary/config.schema.es.json +68 -0
  165. package/src/core/profiles/bundled/secretary/config.schema.json +30 -2
  166. package/src/core/profiles/bundled/secretary/profile.json +3 -1
  167. package/src/core/profiles/bundled/secretary/routines/a2a-sweep.json +20 -0
  168. package/src/core/profiles/bundled/secretary/routines/day-close.json +4 -1
  169. package/src/core/profiles/bundled/secretary/routines/day-open.json +7 -1
  170. package/src/core/profiles/bundled/secretary/routines/watch.json +13 -2
  171. package/src/core/profiles/lifecycle.js +243 -19
  172. package/src/core/profiles/paths.js +12 -0
  173. package/src/core/profiles/store.js +47 -0
  174. package/src/core/routines/active-runs.js +213 -0
  175. package/src/core/routines/delivery.js +612 -0
  176. package/src/core/routines/header.js +88 -0
  177. package/src/core/routines/index.js +26 -0
  178. package/src/core/routines/run-log.js +78 -0
  179. package/src/core/routines/runner.js +629 -66
  180. package/src/core/routines/signals.js +75 -0
  181. package/src/core/runtime-skills/apc-context/SKILL.md +14 -7
  182. package/src/core/runtime-skills/apx/SKILL.md +93 -1
  183. package/src/core/runtime-skills/apx-agency-agents/SKILL.md +2 -2
  184. package/src/core/runtime-skills/apx-agent/SKILL.md +134 -19
  185. package/src/core/runtime-skills/apx-commitment/SKILL.md +21 -11
  186. package/src/core/runtime-skills/apx-image/SKILL.md +60 -0
  187. package/src/core/runtime-skills/apx-integrations/SKILL.md +82 -0
  188. package/src/core/runtime-skills/apx-mcp/SKILL.md +45 -14
  189. package/src/core/runtime-skills/apx-mcp-builder/SKILL.md +4 -4
  190. package/src/core/runtime-skills/apx-profile/SKILL.md +27 -2
  191. package/src/core/runtime-skills/apx-project/SKILL.md +23 -2
  192. package/src/core/runtime-skills/apx-routine/SKILL.md +40 -17
  193. package/src/core/runtime-skills/apx-runtime/SKILL.md +124 -14
  194. package/src/core/runtime-skills/apx-sessions/SKILL.md +7 -6
  195. package/src/core/runtime-skills/apx-skill-builder/SKILL.md +15 -13
  196. package/src/core/runtime-skills/apx-task/SKILL.md +79 -26
  197. package/src/core/runtime-skills/apx-telegram/SKILL.md +12 -12
  198. package/src/core/runtime-skills/apx-transcribe/SKILL.md +56 -0
  199. package/src/core/runtime-skills/apx-voice/SKILL.md +27 -8
  200. package/src/core/runtime-skills/claude-code/SKILL.md +8 -0
  201. package/src/core/runtime-skills/openrouter/SKILL.md +8 -2
  202. package/src/core/runtimes/_spawn.js +9 -1
  203. package/src/core/runtimes/claude-code.js +11 -2
  204. package/src/core/runtimes/codex.js +44 -10
  205. package/src/core/runtimes/index.js +17 -2
  206. package/src/core/runtimes/opencode.js +88 -9
  207. package/src/core/runtimes/outcome.js +119 -0
  208. package/src/core/sessions/index.js +193 -1
  209. package/src/core/stores/agent-inbox.js +109 -23
  210. package/src/core/stores/code-sessions.js +21 -0
  211. package/src/core/stores/commitments.js +27 -3
  212. package/src/core/stores/conversations.js +220 -20
  213. package/src/core/stores/deliveries.js +177 -0
  214. package/src/core/stores/media-archive.js +228 -0
  215. package/src/core/stores/messages.js +1096 -112
  216. package/src/core/stores/organization.js +34 -0
  217. package/src/core/stores/project-memory.js +71 -0
  218. package/src/core/stores/routine-memory.js +16 -8
  219. package/src/core/stores/routines.js +27 -49
  220. package/src/core/stores/sessions.js +4 -8
  221. package/src/core/stores/tasks.js +212 -18
  222. package/src/core/stores/turn-record.js +209 -0
  223. package/src/core/tasks/columns.js +136 -0
  224. package/src/core/tasks/comment-turn.js +258 -0
  225. package/src/core/util/time.js +13 -0
  226. package/src/core/voice/engines/index.js +54 -1
  227. package/src/core/voice/engines/openai.js +50 -4
  228. package/src/core/voice/opus.js +67 -0
  229. package/src/core/voice/pronounceable.js +43 -0
  230. package/src/core/voice/transcription.js +4 -0
  231. package/src/core/voice/tts.js +114 -24
  232. package/src/host/daemon/active-turns.js +188 -0
  233. package/src/host/daemon/api/admin-config.js +9 -0
  234. package/src/host/daemon/api/agents.js +99 -90
  235. package/src/host/daemon/api/artifact-preview.js +7 -6
  236. package/src/host/daemon/api/artifacts.js +3 -2
  237. package/src/host/daemon/api/code.js +55 -9
  238. package/src/host/daemon/api/commitments.js +11 -0
  239. package/src/host/daemon/api/confirm.js +3 -2
  240. package/src/host/daemon/api/conversations.js +543 -35
  241. package/src/host/daemon/api/deck.js +9 -8
  242. package/src/host/daemon/api/deliveries.js +33 -0
  243. package/src/host/daemon/api/desktop.js +5 -4
  244. package/src/host/daemon/api/embeddings.js +15 -6
  245. package/src/host/daemon/api/engines.js +59 -8
  246. package/src/host/daemon/api/exec.js +437 -121
  247. package/src/host/daemon/api/groups.js +215 -0
  248. package/src/host/daemon/api/images.js +107 -0
  249. package/src/host/daemon/api/inbox.js +123 -2
  250. package/src/host/daemon/api/integrations.js +64 -2
  251. package/src/host/daemon/api/mcps.js +7 -6
  252. package/src/host/daemon/api/media.js +283 -0
  253. package/src/host/daemon/api/mobility.js +177 -0
  254. package/src/host/daemon/api/net.js +182 -0
  255. package/src/host/daemon/api/pairing.js +38 -5
  256. package/src/host/daemon/api/profiles.js +30 -2
  257. package/src/host/daemon/api/routines.js +36 -4
  258. package/src/host/daemon/api/runtimes.js +74 -39
  259. package/src/host/daemon/api/sessions-search.js +7 -2
  260. package/src/host/daemon/api/sessions.js +44 -2
  261. package/src/host/daemon/api/shared.js +127 -20
  262. package/src/host/daemon/api/skills.js +11 -4
  263. package/src/host/daemon/api/super-agent.js +384 -53
  264. package/src/host/daemon/api/tasks.js +180 -10
  265. package/src/host/daemon/api/telegram.js +11 -10
  266. package/src/host/daemon/api/thread-faces.js +159 -0
  267. package/src/host/daemon/api/top-level.js +3 -3
  268. package/src/host/daemon/api/transcribe.js +11 -10
  269. package/src/host/daemon/api/tts.js +97 -4
  270. package/src/host/daemon/api/turn-abort.js +23 -0
  271. package/src/host/daemon/api/turns.js +49 -0
  272. package/src/host/daemon/api/voice.js +5 -4
  273. package/src/host/daemon/api/web.js +98 -0
  274. package/src/host/daemon/api.js +39 -0
  275. package/src/host/daemon/delivery-sweep.js +58 -0
  276. package/src/host/daemon/desktop-ws.js +5 -23
  277. package/src/host/daemon/events-ws.js +273 -0
  278. package/src/host/daemon/index.js +59 -15
  279. package/src/host/daemon/plugins/desktop/index.js +17 -4
  280. package/src/host/daemon/plugins/telegram/index.js +78 -40
  281. package/src/host/daemon/pty-bridge.py +99 -0
  282. package/src/host/daemon/routines-scheduler.js +1 -0
  283. package/src/host/daemon/smoke.js +1 -2
  284. package/src/host/daemon/terminal-ws.js +132 -0
  285. package/src/host/daemon/tts-keepwarm.js +87 -0
  286. package/src/host/daemon/wakeup.js +11 -6
  287. package/src/host/daemon/whisper-server.js +44 -0
  288. package/src/host/daemon/whisper-server.py +70 -13
  289. package/src/host/daemon/ws-auth.js +28 -0
  290. package/src/interfaces/android/app/build.gradle +42 -0
  291. package/src/interfaces/android/app/proguard-rules.pro +4 -0
  292. package/src/interfaces/android/app/src/main/AndroidManifest.xml +139 -0
  293. package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/AlertScreen.java +139 -0
  294. package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/AndroidAutoDetector.java +106 -0
  295. package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/ApxCarAppService.java +57 -0
  296. package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/ApxPreferences.java +165 -0
  297. package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/CarAlertStore.java +84 -0
  298. package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/CarMessageActionReceiver.java +47 -0
  299. package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/CarMessageNotification.java +152 -0
  300. package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/DaemonAddress.java +35 -0
  301. package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/DaemonClient.java +339 -0
  302. package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/DeviceLocation.java +38 -0
  303. package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/ErrandScreen.java +127 -0
  304. package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/MainActivity.java +872 -0
  305. package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/MapsNavigationDetector.java +63 -0
  306. package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/MapsNavigationListenerService.java +453 -0
  307. package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/MapsShareIntentParser.java +93 -0
  308. package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/MascotBlobCatalog.java +66 -0
  309. package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/MascotOverlayService.java +448 -0
  310. package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/MascotView.java +249 -0
  311. package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/MessageFrameParser.java +240 -0
  312. package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/NotifyChannels.java +69 -0
  313. package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/ProximityActionReceiver.java +78 -0
  314. package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/ProximityNotification.java +161 -0
  315. package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/StartupReceiver.java +57 -0
  316. package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/TravelEventGate.java +62 -0
  317. package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/TravelStatusBanner.java +116 -0
  318. package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/TripLocationService.java +272 -0
  319. package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/TripPlacesSheet.java +203 -0
  320. package/src/interfaces/android/app/src/main/java/dev/agentprojectcontext/apx/TripScreen.java +182 -0
  321. package/src/interfaces/android/app/src/main/res/drawable/app_background.xml +12 -0
  322. package/src/interfaces/android/app/src/main/res/drawable/ic_apx_app.xml +10 -0
  323. package/src/interfaces/android/app/src/main/res/drawable/ic_apx_launcher_foreground.xml +12 -0
  324. package/src/interfaces/android/app/src/main/res/drawable/ic_apx_notification.xml +22 -0
  325. package/src/interfaces/android/app/src/main/res/drawable/ic_trip_car.xml +37 -0
  326. package/src/interfaces/android/app/src/main/res/drawable-nodpi/apx_logo.png +0 -0
  327. package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_amatista.png +0 -0
  328. package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_aqua.png +0 -0
  329. package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_brasa.png +0 -0
  330. package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_campana.png +0 -0
  331. package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_caracol.png +0 -0
  332. package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_carbon.png +0 -0
  333. package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_cobalto.png +0 -0
  334. package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_cometa.png +0 -0
  335. package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_coral.png +0 -0
  336. package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_cubi.png +0 -0
  337. package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_eclipse.png +0 -0
  338. package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_faro.png +0 -0
  339. package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_gajo.png +0 -0
  340. package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_iris.png +0 -0
  341. package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_kiwi.png +0 -0
  342. package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_laguna.png +0 -0
  343. package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_lila.png +0 -0
  344. package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_menta.png +0 -0
  345. package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_nimbo.png +0 -0
  346. package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_noche.png +0 -0
  347. package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_onyx.png +0 -0
  348. package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_orbita.png +0 -0
  349. package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_ovni.png +0 -0
  350. package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_papa.png +0 -0
  351. package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_parche.png +0 -0
  352. package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_peon.png +0 -0
  353. package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_perla.png +0 -0
  354. package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_pino.png +0 -0
  355. package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_quad.png +0 -0
  356. package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_rubi.png +0 -0
  357. package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_saturno.png +0 -0
  358. package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_trebol.png +0 -0
  359. package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_trino.png +0 -0
  360. package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_volcan.png +0 -0
  361. package/src/interfaces/android/app/src/main/res/drawable-nodpi/mascot_zafiro.png +0 -0
  362. package/src/interfaces/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +5 -0
  363. package/src/interfaces/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +5 -0
  364. package/src/interfaces/android/app/src/main/res/raw/apx_notification.mp3 +0 -0
  365. package/src/interfaces/android/app/src/main/res/values/colors.xml +4 -0
  366. package/src/interfaces/android/app/src/main/res/values/strings.xml +25 -0
  367. package/src/interfaces/android/app/src/main/res/values/styles.xml +10 -0
  368. package/src/interfaces/android/app/src/main/res/values-v31/styles.xml +14 -0
  369. package/src/interfaces/android/app/src/main/res/xml/automotive_app_desc.xml +9 -0
  370. package/src/interfaces/android/app/src/main/res/xml/network_security_config.xml +10 -0
  371. package/src/interfaces/android/app/src/test/java/dev/agentprojectcontext/apx/AndroidAutoDetectorTest.java +85 -0
  372. package/src/interfaces/android/app/src/test/java/dev/agentprojectcontext/apx/CarAlertStoreTest.java +104 -0
  373. package/src/interfaces/android/app/src/test/java/dev/agentprojectcontext/apx/DaemonAddressTest.java +23 -0
  374. package/src/interfaces/android/app/src/test/java/dev/agentprojectcontext/apx/MapsNavigationDetectorTest.java +69 -0
  375. package/src/interfaces/android/app/src/test/java/dev/agentprojectcontext/apx/MapsShareIntentParserTest.java +31 -0
  376. package/src/interfaces/android/app/src/test/java/dev/agentprojectcontext/apx/MessageFrameParserTest.java +180 -0
  377. package/src/interfaces/android/app/src/test/java/dev/agentprojectcontext/apx/TravelEventGateTest.java +64 -0
  378. package/src/interfaces/android/app/src/test/java/dev/agentprojectcontext/apx/TripPlacesSheetTest.java +66 -0
  379. package/src/interfaces/android/build.gradle +3 -0
  380. package/src/interfaces/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  381. package/src/interfaces/android/gradle/wrapper/gradle-wrapper.properties +9 -0
  382. package/src/interfaces/android/gradle.properties +3 -0
  383. package/src/interfaces/android/gradlew +248 -0
  384. package/src/interfaces/android/gradlew.bat +82 -0
  385. package/src/interfaces/android/settings.gradle +18 -0
  386. package/src/interfaces/cli/commands/a2a.js +145 -4
  387. package/src/interfaces/cli/commands/agent.js +247 -22
  388. package/src/interfaces/cli/commands/code.js +1 -1
  389. package/src/interfaces/cli/commands/commitment.js +5 -0
  390. package/src/interfaces/cli/commands/config.js +85 -6
  391. package/src/interfaces/cli/commands/exec.js +90 -1
  392. package/src/interfaces/cli/commands/image.js +314 -0
  393. package/src/interfaces/cli/commands/mcp.js +87 -23
  394. package/src/interfaces/cli/commands/memory.js +35 -22
  395. package/src/interfaces/cli/commands/panel.js +94 -0
  396. package/src/interfaces/cli/commands/profile.js +14 -0
  397. package/src/interfaces/cli/commands/project.js +29 -0
  398. package/src/interfaces/cli/commands/routine.js +45 -10
  399. package/src/interfaces/cli/commands/runtime.js +30 -7
  400. package/src/interfaces/cli/commands/session.js +90 -38
  401. package/src/interfaces/cli/commands/sessions.js +35 -2
  402. package/src/interfaces/cli/commands/skills.js +15 -9
  403. package/src/interfaces/cli/commands/status.js +2 -3
  404. package/src/interfaces/cli/commands/task.js +54 -5
  405. package/src/interfaces/cli/commands/telegram.js +12 -4
  406. package/src/interfaces/cli/commands/transcribe.js +177 -0
  407. package/src/interfaces/cli/help/index.js +311 -72
  408. package/src/interfaces/cli/http.js +61 -13
  409. package/src/interfaces/cli/index.js +1 -1
  410. package/src/interfaces/cli/routes/agent.js +5 -4
  411. package/src/interfaces/cli/routes/commitment.js +3 -2
  412. package/src/interfaces/cli/routes/config.js +7 -2
  413. package/src/interfaces/cli/routes/image.js +11 -0
  414. package/src/interfaces/cli/routes/index.js +2 -0
  415. package/src/interfaces/cli/routes/log.js +1 -1
  416. package/src/interfaces/cli/routes/memory.js +3 -2
  417. package/src/interfaces/cli/routes/panel.js +15 -2
  418. package/src/interfaces/cli/routes/profile.js +3 -2
  419. package/src/interfaces/cli/routes/transcribe.js +10 -0
  420. package/src/interfaces/cli/stdin.js +29 -0
  421. package/src/interfaces/desktop/assets/blobs/amatista.png +0 -0
  422. package/src/interfaces/desktop/assets/blobs/aqua.png +0 -0
  423. package/src/interfaces/desktop/assets/blobs/brasa.png +0 -0
  424. package/src/interfaces/desktop/assets/blobs/campana.png +0 -0
  425. package/src/interfaces/desktop/assets/blobs/caracol.png +0 -0
  426. package/src/interfaces/desktop/assets/blobs/carbon.png +0 -0
  427. package/src/interfaces/desktop/assets/blobs/cobalto.png +0 -0
  428. package/src/interfaces/desktop/assets/blobs/cometa.png +0 -0
  429. package/src/interfaces/desktop/assets/blobs/coral.png +0 -0
  430. package/src/interfaces/desktop/assets/blobs/cubi.png +0 -0
  431. package/src/interfaces/desktop/assets/blobs/eclipse.png +0 -0
  432. package/src/interfaces/desktop/assets/blobs/faro.png +0 -0
  433. package/src/interfaces/desktop/assets/blobs/gajo.png +0 -0
  434. package/src/interfaces/desktop/assets/blobs/iris.png +0 -0
  435. package/src/interfaces/desktop/assets/blobs/kiwi.png +0 -0
  436. package/src/interfaces/desktop/assets/blobs/laguna.png +0 -0
  437. package/src/interfaces/desktop/assets/blobs/lila.png +0 -0
  438. package/src/interfaces/desktop/assets/blobs/menta.png +0 -0
  439. package/src/interfaces/desktop/assets/blobs/nimbo.png +0 -0
  440. package/src/interfaces/desktop/assets/blobs/noche.png +0 -0
  441. package/src/interfaces/desktop/assets/blobs/onyx.png +0 -0
  442. package/src/interfaces/desktop/assets/blobs/orbita.png +0 -0
  443. package/src/interfaces/desktop/assets/blobs/ovni.png +0 -0
  444. package/src/interfaces/desktop/assets/blobs/papa.png +0 -0
  445. package/src/interfaces/desktop/assets/blobs/parche.png +0 -0
  446. package/src/interfaces/desktop/assets/blobs/peon.png +0 -0
  447. package/src/interfaces/desktop/assets/blobs/perla.png +0 -0
  448. package/src/interfaces/desktop/assets/blobs/pino.png +0 -0
  449. package/src/interfaces/desktop/assets/blobs/quad.png +0 -0
  450. package/src/interfaces/desktop/assets/blobs/rubi.png +0 -0
  451. package/src/interfaces/desktop/assets/blobs/saturno.png +0 -0
  452. package/src/interfaces/desktop/assets/blobs/trebol.png +0 -0
  453. package/src/interfaces/desktop/assets/blobs/trino.png +0 -0
  454. package/src/interfaces/desktop/assets/blobs/volcan.png +0 -0
  455. package/src/interfaces/desktop/assets/blobs/zafiro.png +0 -0
  456. package/src/interfaces/desktop/assets/notification.mp3 +0 -0
  457. package/src/interfaces/desktop/main.js +473 -29
  458. package/src/interfaces/desktop/mascot-blobs.js +41 -0
  459. package/src/interfaces/desktop/mascot-preload.js +32 -0
  460. package/src/interfaces/desktop/mascot.css +186 -0
  461. package/src/interfaces/desktop/mascot.html +40 -0
  462. package/src/interfaces/desktop/mascot.js +163 -0
  463. package/src/interfaces/desktop/preload.js +10 -0
  464. package/src/interfaces/desktop/renderer.js +308 -48
  465. package/src/interfaces/desktop/style.css +34 -0
  466. package/src/interfaces/mcp-server/index.js +87 -0
  467. package/src/interfaces/tui/_shims/util-locale.ts +0 -2
  468. package/src/interfaces/tui/app.tsx +0 -6
  469. package/src/interfaces/tui/attach.ts +0 -1
  470. package/src/interfaces/tui/context/sdk-apx.tsx +0 -4
  471. package/src/interfaces/tui/plugin/api.tsx +0 -3
  472. package/src/interfaces/tui/plugin/runtime.ts +0 -3
  473. package/src/interfaces/tui/thread.ts +0 -16
  474. package/src/interfaces/web/README.md +22 -14
  475. package/src/interfaces/web/dist/assets/claude-XtpLmGtF.webp +0 -0
  476. package/src/interfaces/web/dist/assets/codex-DExPUgvh.webp +0 -0
  477. package/src/interfaces/web/dist/assets/index-BEoRgIqd.css +32 -0
  478. package/src/interfaces/web/dist/assets/index-BEoRgIqd.css.br +0 -0
  479. package/src/interfaces/web/dist/assets/index-BEoRgIqd.css.gz +0 -0
  480. package/src/interfaces/web/dist/assets/index-C6fXHc1p.js +1074 -0
  481. package/src/interfaces/web/dist/assets/index-C6fXHc1p.js.br +0 -0
  482. package/src/interfaces/web/dist/assets/index-C6fXHc1p.js.gz +0 -0
  483. package/src/interfaces/web/dist/assets/index-C6fXHc1p.js.map +1 -0
  484. package/src/interfaces/web/dist/assets/index-C6fXHc1p.js.map.br +0 -0
  485. package/src/interfaces/web/dist/assets/index-C6fXHc1p.js.map.gz +0 -0
  486. package/src/interfaces/web/dist/favicon/dark/site.webmanifest +12 -6
  487. package/src/interfaces/web/dist/favicon/white/site.webmanifest +12 -6
  488. package/src/interfaces/web/dist/index.html +22 -6
  489. package/src/interfaces/web/dist/index.html.br +0 -0
  490. package/src/interfaces/web/dist/index.html.gz +0 -0
  491. package/src/interfaces/web/dist/manifest.webmanifest +25 -0
  492. package/src/interfaces/web/dist/modules/blobs/amatista.png +0 -0
  493. package/src/interfaces/web/dist/modules/blobs/aqua.png +0 -0
  494. package/src/interfaces/web/dist/modules/blobs/brasa.png +0 -0
  495. package/src/interfaces/web/dist/modules/blobs/campana.png +0 -0
  496. package/src/interfaces/web/dist/modules/blobs/caracol.png +0 -0
  497. package/src/interfaces/web/dist/modules/blobs/carbon.png +0 -0
  498. package/src/interfaces/web/dist/modules/blobs/cobalto.png +0 -0
  499. package/src/interfaces/web/dist/modules/blobs/cometa.png +0 -0
  500. package/src/interfaces/web/dist/modules/blobs/coral.png +0 -0
  501. package/src/interfaces/web/dist/modules/blobs/cubi.png +0 -0
  502. package/src/interfaces/web/dist/modules/blobs/eclipse.png +0 -0
  503. package/src/interfaces/web/dist/modules/blobs/faro.png +0 -0
  504. package/src/interfaces/web/dist/modules/blobs/gajo.png +0 -0
  505. package/src/interfaces/web/dist/modules/blobs/iris.png +0 -0
  506. package/src/interfaces/web/dist/modules/blobs/kiwi.png +0 -0
  507. package/src/interfaces/web/dist/modules/blobs/laguna.png +0 -0
  508. package/src/interfaces/web/dist/modules/blobs/lila.png +0 -0
  509. package/src/interfaces/web/dist/modules/blobs/menta.png +0 -0
  510. package/src/interfaces/web/dist/modules/blobs/nimbo.png +0 -0
  511. package/src/interfaces/web/dist/modules/blobs/noche.png +0 -0
  512. package/src/interfaces/web/dist/modules/blobs/onyx.png +0 -0
  513. package/src/interfaces/web/dist/modules/blobs/orbita.png +0 -0
  514. package/src/interfaces/web/dist/modules/blobs/ovni.png +0 -0
  515. package/src/interfaces/web/dist/modules/blobs/papa.png +0 -0
  516. package/src/interfaces/web/dist/modules/blobs/parche.png +0 -0
  517. package/src/interfaces/web/dist/modules/blobs/peon.png +0 -0
  518. package/src/interfaces/web/dist/modules/blobs/perla.png +0 -0
  519. package/src/interfaces/web/dist/modules/blobs/pino.png +0 -0
  520. package/src/interfaces/web/dist/modules/blobs/quad.png +0 -0
  521. package/src/interfaces/web/dist/modules/blobs/rubi.png +0 -0
  522. package/src/interfaces/web/dist/modules/blobs/saturno.png +0 -0
  523. package/src/interfaces/web/dist/modules/blobs/trebol.png +0 -0
  524. package/src/interfaces/web/dist/modules/blobs/trino.png +0 -0
  525. package/src/interfaces/web/dist/modules/blobs/volcan.png +0 -0
  526. package/src/interfaces/web/dist/modules/blobs/zafiro.png +0 -0
  527. package/src/interfaces/web/dist/sw.js +106 -0
  528. package/src/interfaces/web/dist/sw.js.br +0 -0
  529. package/src/interfaces/web/dist/sw.js.gz +0 -0
  530. package/src/interfaces/web/eslint.config.js +83 -0
  531. package/src/interfaces/web/index.html +20 -4
  532. package/src/interfaces/web/package-lock.json +1651 -1280
  533. package/src/interfaces/web/package.json +9 -10
  534. package/src/interfaces/web/pnpm-lock.yaml +867 -869
  535. package/src/interfaces/web/public/favicon/dark/site.webmanifest +12 -6
  536. package/src/interfaces/web/public/favicon/white/site.webmanifest +12 -6
  537. package/src/interfaces/web/public/manifest.webmanifest +25 -0
  538. package/src/interfaces/web/public/modules/blobs/amatista.png +0 -0
  539. package/src/interfaces/web/public/modules/blobs/aqua.png +0 -0
  540. package/src/interfaces/web/public/modules/blobs/brasa.png +0 -0
  541. package/src/interfaces/web/public/modules/blobs/campana.png +0 -0
  542. package/src/interfaces/web/public/modules/blobs/caracol.png +0 -0
  543. package/src/interfaces/web/public/modules/blobs/carbon.png +0 -0
  544. package/src/interfaces/web/public/modules/blobs/cobalto.png +0 -0
  545. package/src/interfaces/web/public/modules/blobs/cometa.png +0 -0
  546. package/src/interfaces/web/public/modules/blobs/coral.png +0 -0
  547. package/src/interfaces/web/public/modules/blobs/cubi.png +0 -0
  548. package/src/interfaces/web/public/modules/blobs/eclipse.png +0 -0
  549. package/src/interfaces/web/public/modules/blobs/faro.png +0 -0
  550. package/src/interfaces/web/public/modules/blobs/gajo.png +0 -0
  551. package/src/interfaces/web/public/modules/blobs/iris.png +0 -0
  552. package/src/interfaces/web/public/modules/blobs/kiwi.png +0 -0
  553. package/src/interfaces/web/public/modules/blobs/laguna.png +0 -0
  554. package/src/interfaces/web/public/modules/blobs/lila.png +0 -0
  555. package/src/interfaces/web/public/modules/blobs/menta.png +0 -0
  556. package/src/interfaces/web/public/modules/blobs/nimbo.png +0 -0
  557. package/src/interfaces/web/public/modules/blobs/noche.png +0 -0
  558. package/src/interfaces/web/public/modules/blobs/onyx.png +0 -0
  559. package/src/interfaces/web/public/modules/blobs/orbita.png +0 -0
  560. package/src/interfaces/web/public/modules/blobs/ovni.png +0 -0
  561. package/src/interfaces/web/public/modules/blobs/papa.png +0 -0
  562. package/src/interfaces/web/public/modules/blobs/parche.png +0 -0
  563. package/src/interfaces/web/public/modules/blobs/peon.png +0 -0
  564. package/src/interfaces/web/public/modules/blobs/perla.png +0 -0
  565. package/src/interfaces/web/public/modules/blobs/pino.png +0 -0
  566. package/src/interfaces/web/public/modules/blobs/quad.png +0 -0
  567. package/src/interfaces/web/public/modules/blobs/rubi.png +0 -0
  568. package/src/interfaces/web/public/modules/blobs/saturno.png +0 -0
  569. package/src/interfaces/web/public/modules/blobs/trebol.png +0 -0
  570. package/src/interfaces/web/public/modules/blobs/trino.png +0 -0
  571. package/src/interfaces/web/public/modules/blobs/volcan.png +0 -0
  572. package/src/interfaces/web/public/modules/blobs/zafiro.png +0 -0
  573. package/src/interfaces/web/public/sw.js +106 -0
  574. package/src/interfaces/web/src/App.tsx +106 -10
  575. package/src/interfaces/web/src/assets/cli/claude.webp +0 -0
  576. package/src/interfaces/web/src/assets/cli/codex.webp +0 -0
  577. package/src/interfaces/web/src/assets/cli/cursor.svg +1 -0
  578. package/src/interfaces/web/src/assets/cli/opencode.png +0 -0
  579. package/src/interfaces/web/src/components/Combobox.tsx +201 -0
  580. package/src/interfaces/web/src/components/MobileHint.tsx +95 -0
  581. package/src/interfaces/web/src/components/MobileLinkDialog.tsx +156 -0
  582. package/src/interfaces/web/src/components/ModelCombobox.tsx +21 -113
  583. package/src/interfaces/web/src/components/{routines/ReadOnlyBlock.tsx → ReadOnlyBlock.tsx} +13 -4
  584. package/src/interfaces/web/src/components/Roby.tsx +5 -3
  585. package/src/interfaces/web/src/components/RobyBubble.tsx +23 -5
  586. package/src/interfaces/web/src/components/RowMenu.tsx +46 -0
  587. package/src/interfaces/web/src/components/TimezoneSelect.tsx +4 -1
  588. package/src/interfaces/web/src/components/Toast.tsx +50 -14
  589. package/src/interfaces/web/src/components/UiSelect.tsx +12 -5
  590. package/src/interfaces/web/src/components/agents/AgentAvatar.tsx +171 -0
  591. package/src/interfaces/web/src/components/agents/AgentFormFields.tsx +42 -11
  592. package/src/interfaces/web/src/components/agents/AgentModelBadge.tsx +157 -0
  593. package/src/interfaces/web/src/components/agents/AgentModelSelect.tsx +57 -0
  594. package/src/interfaces/web/src/components/agents/AgentSkillsPicker.tsx +115 -0
  595. package/src/interfaces/web/src/components/agents/BlobAvatar.tsx +50 -0
  596. package/src/interfaces/web/src/components/agents/blobPresets.ts +50 -0
  597. package/src/interfaces/web/src/components/agents/modelCatalog.ts +66 -0
  598. package/src/interfaces/web/src/components/chat/ActionGroup.tsx +162 -0
  599. package/src/interfaces/web/src/components/chat/AskAnswersCard.tsx +4 -3
  600. package/src/interfaces/web/src/components/chat/AskQuestionsCard.tsx +1 -1
  601. package/src/interfaces/web/src/components/chat/Attachment.tsx +220 -0
  602. package/src/interfaces/web/src/components/chat/ChatList.tsx +226 -51
  603. package/src/interfaces/web/src/components/chat/ChatRowActivity.tsx +45 -0
  604. package/src/interfaces/web/src/components/chat/Composer.tsx +672 -23
  605. package/src/interfaces/web/src/components/chat/ComposerCommands.tsx +175 -0
  606. package/src/interfaces/web/src/components/chat/ComposerSuggest.tsx +215 -0
  607. package/src/interfaces/web/src/components/chat/ContextBar.tsx +133 -65
  608. package/src/interfaces/web/src/components/chat/InlineAskPanel.tsx +107 -47
  609. package/src/interfaces/web/src/components/chat/MessageBubble.tsx +460 -119
  610. package/src/interfaces/web/src/components/chat/MessageList.tsx +165 -18
  611. package/src/interfaces/web/src/components/chat/ReasoningBlock.tsx +58 -0
  612. package/src/interfaces/web/src/components/chat/SessionPicker.tsx +182 -0
  613. package/src/interfaces/web/src/components/chat/SkillTrace.tsx +183 -0
  614. package/src/interfaces/web/src/components/chat/ToolCall.tsx +11 -11
  615. package/src/interfaces/web/src/components/code/CodeContextTab.tsx +87 -53
  616. package/src/interfaces/web/src/components/code/CodeFileTree.tsx +11 -52
  617. package/src/interfaces/web/src/components/code/CodeFileViewer.tsx +5 -4
  618. package/src/interfaces/web/src/components/code/CodeProjectPicker.tsx +31 -7
  619. package/src/interfaces/web/src/components/code/CodeSessionList.tsx +32 -11
  620. package/src/interfaces/web/src/components/code/CodeSidePanel.tsx +33 -3
  621. package/src/interfaces/web/src/components/code/CodeTerminal.tsx +5 -4
  622. package/src/interfaces/web/src/components/code/NewCodeSessionDialog.tsx +164 -0
  623. package/src/interfaces/web/src/components/commitments/CommitmentDetail.tsx +162 -0
  624. package/src/interfaces/web/src/components/commitments/CommitmentFormDialog.tsx +158 -0
  625. package/src/interfaces/web/src/components/commitments/CommitmentList.tsx +194 -0
  626. package/src/interfaces/web/src/components/commitments/commitmentState.tsx +46 -0
  627. package/src/interfaces/web/src/components/common/BulkActionBar.tsx +60 -0
  628. package/src/interfaces/web/src/components/common/ConfirmDialog.tsx +4 -1
  629. package/src/interfaces/web/src/components/common/SelectCheckbox.tsx +40 -0
  630. package/src/interfaces/web/src/components/common/TabNav.tsx +1 -1
  631. package/src/interfaces/web/src/components/cron/CronPicker.tsx +1 -1
  632. package/src/interfaces/web/src/components/files/FileBrowser.tsx +1 -1
  633. package/src/interfaces/web/src/components/files/FileTree.tsx +6 -4
  634. package/src/interfaces/web/src/components/files/FileViewer.tsx +9 -5
  635. package/src/interfaces/web/src/components/files/MarkdownPreview.tsx +45 -9
  636. package/src/interfaces/web/src/components/images/ImageDefaultsCard.tsx +93 -0
  637. package/src/interfaces/web/src/components/images/ImageProviderList.tsx +142 -0
  638. package/src/interfaces/web/src/components/images/ImageProviderModal.tsx +263 -0
  639. package/src/interfaces/web/src/components/images/ImageTestCard.tsx +142 -0
  640. package/src/interfaces/web/src/components/inbox/ChannelFilter.tsx +178 -0
  641. package/src/interfaces/web/src/components/inbox/InboxList.tsx +77 -81
  642. package/src/interfaces/web/src/components/inbox/InboxRowItem.tsx +140 -0
  643. package/src/interfaces/web/src/components/integrations/ComingSoonPlugin.tsx +2 -2
  644. package/src/interfaces/web/src/components/integrations/PluginConnect.tsx +208 -30
  645. package/src/interfaces/web/src/components/integrations/PluginToolsSection.tsx +1 -1
  646. package/src/interfaces/web/src/components/layout/ProjectAvatar.tsx +39 -24
  647. package/src/interfaces/web/src/components/layout/ProjectSidebar.tsx +253 -58
  648. package/src/interfaces/web/src/components/memory/MemoryBrowser.tsx +94 -47
  649. package/src/interfaces/web/src/components/routines/ExecutionsList.tsx +202 -90
  650. package/src/interfaces/web/src/components/routines/RoutineDetail.tsx +28 -6
  651. package/src/interfaces/web/src/components/routines/RoutineEditor.tsx +16 -5
  652. package/src/interfaces/web/src/components/routines/RoutineList.tsx +49 -9
  653. package/src/interfaces/web/src/components/routines/VarTextarea.tsx +1 -1
  654. package/src/interfaces/web/src/components/routines/runChat.ts +121 -0
  655. package/src/interfaces/web/src/components/sessions/SessionDetail.tsx +148 -0
  656. package/src/interfaces/web/src/components/sessions/SessionTerminal.tsx +98 -0
  657. package/src/interfaces/web/src/components/settings/AccessPanel.tsx +205 -0
  658. package/src/interfaces/web/src/components/settings/DefaultRouterCard.tsx +246 -101
  659. package/src/interfaces/web/src/components/settings/DesktopSettingsPanel.tsx +36 -2
  660. package/src/interfaces/web/src/components/settings/DevicesPanel.tsx +19 -6
  661. package/src/interfaces/web/src/components/settings/EmbedProviderList.tsx +274 -0
  662. package/src/interfaces/web/src/components/settings/EnginesPanel.tsx +26 -6
  663. package/src/interfaces/web/src/components/settings/MemoryPanel.tsx +118 -119
  664. package/src/interfaces/web/src/components/settings/NudgePanel.tsx +4 -3
  665. package/src/interfaces/web/src/components/settings/PairDeviceDialog.tsx +15 -4
  666. package/src/interfaces/web/src/components/settings/PanelPrefs.tsx +374 -0
  667. package/src/interfaces/web/src/components/settings/ProfilePanel.tsx +70 -2
  668. package/src/interfaces/web/src/components/settings/RoutingPanel.tsx +2 -1
  669. package/src/interfaces/web/src/components/settings/SkillsInspectorPanel.tsx +102 -43
  670. package/src/interfaces/web/src/components/settings/SkillsManager.tsx +76 -21
  671. package/src/interfaces/web/src/components/settings/SkillsSettings.tsx +10 -3
  672. package/src/interfaces/web/src/components/settings/SuperAgentPanel.tsx +8 -0
  673. package/src/interfaces/web/src/components/settings/TelegramChannelsPanel.tsx +11 -2
  674. package/src/interfaces/web/src/components/settings/TelegramContactsPanel.tsx +19 -8
  675. package/src/interfaces/web/src/components/settings/TelegramRolesPanel.tsx +12 -2
  676. package/src/interfaces/web/src/components/settings/WebPanel.tsx +22 -31
  677. package/src/interfaces/web/src/components/settings/providers/ProviderCard.tsx +15 -3
  678. package/src/interfaces/web/src/components/settings/providers/ProviderModal.tsx +97 -66
  679. package/src/interfaces/web/src/components/settings/providers/ProviderTestDialog.tsx +150 -0
  680. package/src/interfaces/web/src/components/settings/providers/typeStyles.ts +67 -25
  681. package/src/interfaces/web/src/components/settings/providers/types.ts +3 -0
  682. package/src/interfaces/web/src/components/tasks/TaskBoard.tsx +201 -0
  683. package/src/interfaces/web/src/components/tasks/TaskColumnsDialog.tsx +243 -0
  684. package/src/interfaces/web/src/components/tasks/TaskComments.tsx +167 -0
  685. package/src/interfaces/web/src/components/tasks/TaskDetail.tsx +335 -0
  686. package/src/interfaces/web/src/components/tasks/TaskFormDialog.tsx +406 -0
  687. package/src/interfaces/web/src/components/tasks/TaskList.tsx +258 -0
  688. package/src/interfaces/web/src/components/tasks/TaskSubtasks.tsx +126 -0
  689. package/src/interfaces/web/src/components/tasks/columns.ts +51 -0
  690. package/src/interfaces/web/src/components/tasks/taskStatus.tsx +159 -34
  691. package/src/interfaces/web/src/components/tasks/taskViewPrefs.ts +73 -0
  692. package/src/interfaces/web/src/components/tasks/useTaskColumns.ts +31 -0
  693. package/src/interfaces/web/src/components/ui/chat-input.tsx +136 -12
  694. package/src/interfaces/web/src/components/ui/context-menu.tsx +15 -198
  695. package/src/interfaces/web/src/components/ui/dialog.tsx +3 -2
  696. package/src/interfaces/web/src/components/ui/filter-chips.tsx +4 -0
  697. package/src/interfaces/web/src/components/ui/popover.tsx +14 -49
  698. package/src/interfaces/web/src/components/ui/sheet.tsx +2 -1
  699. package/src/interfaces/web/src/components/ui/spinner.tsx +2 -1
  700. package/src/interfaces/web/src/components/ui.tsx +62 -23
  701. package/src/interfaces/web/src/components/voice/VoiceProviderList.tsx +2 -1
  702. package/src/interfaces/web/src/components/voice/VoiceProviderModal.tsx +66 -8
  703. package/src/interfaces/web/src/components/voice/VoiceQvoxInstallCard.tsx +63 -0
  704. package/src/interfaces/web/src/components/voice/VoiceQvoxStatusCard.tsx +51 -0
  705. package/src/interfaces/web/src/components/voice/VoiceSttCard.tsx +4 -3
  706. package/src/interfaces/web/src/components/voice/VoiceTestCard.tsx +38 -4
  707. package/src/interfaces/web/src/constants/index.ts +1 -0
  708. package/src/interfaces/web/src/hooks/useChannelPrefs.ts +39 -0
  709. package/src/interfaces/web/src/hooks/useChat.ts +1092 -85
  710. package/src/interfaces/web/src/hooks/useChatActivity.ts +24 -0
  711. package/src/interfaces/web/src/hooks/useInbox.ts +26 -4
  712. package/src/interfaces/web/src/hooks/useLiveMessages.ts +16 -0
  713. package/src/interfaces/web/src/hooks/useOllamaModels.ts +79 -0
  714. package/src/interfaces/web/src/hooks/useRecorder.ts +198 -0
  715. package/src/interfaces/web/src/hooks/useTokenBootstrap.ts +23 -6
  716. package/src/interfaces/web/src/i18n/en.ts +832 -175
  717. package/src/interfaces/web/src/i18n/es.ts +841 -188
  718. package/src/interfaces/web/src/lib/api/agents.ts +23 -4
  719. package/src/interfaces/web/src/lib/api/code.ts +17 -0
  720. package/src/interfaces/web/src/lib/api/commitments.ts +7 -1
  721. package/src/interfaces/web/src/lib/api/conversations.ts +38 -11
  722. package/src/interfaces/web/src/lib/api/embeddings.ts +3 -0
  723. package/src/interfaces/web/src/lib/api/engines.ts +14 -0
  724. package/src/interfaces/web/src/lib/api/groups.ts +93 -0
  725. package/src/interfaces/web/src/lib/api/images.ts +193 -0
  726. package/src/interfaces/web/src/lib/api/inbox.ts +29 -5
  727. package/src/interfaces/web/src/lib/api/integrations.ts +9 -0
  728. package/src/interfaces/web/src/lib/api/media.ts +126 -0
  729. package/src/interfaces/web/src/lib/api/net.ts +44 -0
  730. package/src/interfaces/web/src/lib/api/profiles.ts +12 -0
  731. package/src/interfaces/web/src/lib/api/projects.ts +9 -1
  732. package/src/interfaces/web/src/lib/api/routines.ts +9 -1
  733. package/src/interfaces/web/src/lib/api/sessions.ts +18 -0
  734. package/src/interfaces/web/src/lib/api/skills.ts +8 -0
  735. package/src/interfaces/web/src/lib/api/super_agent.ts +3 -0
  736. package/src/interfaces/web/src/lib/api/tasks.ts +46 -6
  737. package/src/interfaces/web/src/lib/api/tools.ts +2 -0
  738. package/src/interfaces/web/src/lib/api/transcribe.ts +31 -0
  739. package/src/interfaces/web/src/lib/api/turns.ts +20 -0
  740. package/src/interfaces/web/src/lib/api/voice.ts +72 -2
  741. package/src/interfaces/web/src/lib/api.ts +3 -0
  742. package/src/interfaces/web/src/lib/channels.ts +214 -0
  743. package/src/interfaces/web/src/lib/chat-activity.ts +175 -0
  744. package/src/interfaces/web/src/lib/chat-prefs.ts +48 -0
  745. package/src/interfaces/web/src/lib/code-context.ts +0 -2
  746. package/src/interfaces/web/src/lib/http.ts +51 -17
  747. package/src/interfaces/web/src/lib/inbox-selection.ts +29 -0
  748. package/src/interfaces/web/src/lib/live.ts +252 -0
  749. package/src/interfaces/web/src/lib/net.ts +211 -0
  750. package/src/interfaces/web/src/lib/notify.ts +331 -0
  751. package/src/interfaces/web/src/lib/projectNav.ts +40 -0
  752. package/src/interfaces/web/src/lib/pwa.ts +115 -0
  753. package/src/interfaces/web/src/lib/qvox.ts +24 -0
  754. package/src/interfaces/web/src/lib/slug.ts +3 -0
  755. package/src/interfaces/web/src/lib/tone.ts +150 -0
  756. package/src/interfaces/web/src/main.tsx +15 -4
  757. package/src/interfaces/web/src/screens/ApxAdminScreen.tsx +33 -11
  758. package/src/interfaces/web/src/screens/InboxScreen.tsx +126 -71
  759. package/src/interfaces/web/src/screens/ProjectScreen.tsx +8 -4
  760. package/src/interfaces/web/src/screens/SettingsScreen.tsx +7 -4
  761. package/src/interfaces/web/src/screens/base/AgentDefaultsTab.tsx +30 -9
  762. package/src/interfaces/web/src/screens/base/CommitmentsTab.tsx +150 -168
  763. package/src/interfaces/web/src/screens/base/LogsTab.tsx +8 -7
  764. package/src/interfaces/web/src/screens/base/SessionsTab.tsx +99 -37
  765. package/src/interfaces/web/src/screens/base/WorkspacesTab.tsx +1 -1
  766. package/src/interfaces/web/src/screens/mobile/MobileChat.tsx +85 -0
  767. package/src/interfaces/web/src/screens/mobile/MobileChatList.tsx +244 -0
  768. package/src/interfaces/web/src/screens/mobile/MobileCommitments.tsx +350 -0
  769. package/src/interfaces/web/src/screens/mobile/MobileScreen.tsx +138 -0
  770. package/src/interfaces/web/src/screens/mobile/MobileTabBar.tsx +105 -0
  771. package/src/interfaces/web/src/screens/mobile/MobileTasks.tsx +407 -0
  772. package/src/interfaces/web/src/screens/mobile/NewChatSheet.tsx +313 -0
  773. package/src/interfaces/web/src/screens/mobile/mobileList.tsx +194 -0
  774. package/src/interfaces/web/src/screens/mobile/routes.ts +239 -0
  775. package/src/interfaces/web/src/screens/modules/CodeScreen.tsx +135 -64
  776. package/src/interfaces/web/src/screens/modules/DeckScreen.tsx +0 -1
  777. package/src/interfaces/web/src/screens/modules/ImagesScreen.tsx +172 -0
  778. package/src/interfaces/web/src/screens/modules/VoiceScreen.tsx +36 -4
  779. package/src/interfaces/web/src/screens/project/AgentBrainGraph.tsx +117 -30
  780. package/src/interfaces/web/src/screens/project/AgentDetailScreen.tsx +537 -92
  781. package/src/interfaces/web/src/screens/project/AgentsTab.tsx +344 -93
  782. package/src/interfaces/web/src/screens/project/ArtifactsTab.tsx +1 -1
  783. package/src/interfaces/web/src/screens/project/ChatTab.tsx +1160 -94
  784. package/src/interfaces/web/src/screens/project/IntegrationsTab.tsx +3 -2
  785. package/src/interfaces/web/src/screens/project/McpsTab.tsx +62 -14
  786. package/src/interfaces/web/src/screens/project/Overview.tsx +36 -15
  787. package/src/interfaces/web/src/screens/project/RoutinesTab.tsx +93 -8
  788. package/src/interfaces/web/src/screens/project/SkillsTab.tsx +4 -1
  789. package/src/interfaces/web/src/screens/project/StructureTab.tsx +5 -4
  790. package/src/interfaces/web/src/screens/project/TasksTab.tsx +351 -129
  791. package/src/interfaces/web/src/screens/project/VarsTab.tsx +17 -5
  792. package/src/interfaces/web/src/styles.css +106 -31
  793. package/src/interfaces/web/src/types/daemon.ts +246 -2
  794. package/src/interfaces/web/src/vite-env.d.ts +20 -0
  795. package/src/interfaces/tui/plugin/command-shim.ts +0 -109
  796. package/src/interfaces/web/components.json +0 -25
  797. package/src/interfaces/web/dist/assets/index-CvEoGtTf.js +0 -849
  798. package/src/interfaces/web/dist/assets/index-CvEoGtTf.js.map +0 -1
  799. package/src/interfaces/web/dist/assets/index-DzBBXFaO.css +0 -1
  800. package/src/interfaces/web/src/components/tasks/TaskDetailPanel.tsx +0 -142
  801. package/src/interfaces/web/src/components/ui/accordion.tsx +0 -72
  802. package/src/interfaces/web/src/components/ui/alert-dialog.tsx +0 -187
  803. package/src/interfaces/web/src/components/ui/alert.tsx +0 -76
  804. package/src/interfaces/web/src/components/ui/aspect-ratio.tsx +0 -22
  805. package/src/interfaces/web/src/components/ui/avatar.tsx +0 -107
  806. package/src/interfaces/web/src/components/ui/breadcrumb.tsx +0 -125
  807. package/src/interfaces/web/src/components/ui/button-group.tsx +0 -87
  808. package/src/interfaces/web/src/components/ui/calendar.tsx +0 -221
  809. package/src/interfaces/web/src/components/ui/card.tsx +0 -103
  810. package/src/interfaces/web/src/components/ui/carousel.tsx +0 -242
  811. package/src/interfaces/web/src/components/ui/chart.tsx +0 -371
  812. package/src/interfaces/web/src/components/ui/checkbox.tsx +0 -29
  813. package/src/interfaces/web/src/components/ui/collapsible.tsx +0 -19
  814. package/src/interfaces/web/src/components/ui/combobox.tsx +0 -295
  815. package/src/interfaces/web/src/components/ui/command.tsx +0 -196
  816. package/src/interfaces/web/src/components/ui/direction.tsx +0 -4
  817. package/src/interfaces/web/src/components/ui/drawer.tsx +0 -134
  818. package/src/interfaces/web/src/components/ui/empty.tsx +0 -104
  819. package/src/interfaces/web/src/components/ui/field.tsx +0 -236
  820. package/src/interfaces/web/src/components/ui/hover-card.tsx +0 -51
  821. package/src/interfaces/web/src/components/ui/input-otp.tsx +0 -85
  822. package/src/interfaces/web/src/components/ui/item.tsx +0 -201
  823. package/src/interfaces/web/src/components/ui/menubar.tsx +0 -280
  824. package/src/interfaces/web/src/components/ui/native-select.tsx +0 -61
  825. package/src/interfaces/web/src/components/ui/navigation-menu.tsx +0 -168
  826. package/src/interfaces/web/src/components/ui/pagination.tsx +0 -130
  827. package/src/interfaces/web/src/components/ui/progress.tsx +0 -83
  828. package/src/interfaces/web/src/components/ui/radio-group.tsx +0 -36
  829. package/src/interfaces/web/src/components/ui/resizable.tsx +0 -50
  830. package/src/interfaces/web/src/components/ui/scroll-area.tsx +0 -53
  831. package/src/interfaces/web/src/components/ui/sidebar.tsx +0 -726
  832. package/src/interfaces/web/src/components/ui/slider.tsx +0 -52
  833. package/src/interfaces/web/src/components/ui/sonner.tsx +0 -49
  834. package/src/interfaces/web/src/components/ui/table.tsx +0 -116
  835. package/src/interfaces/web/src/components/ui/toggle-group.tsx +0 -87
  836. package/src/interfaces/web/src/components/ui/toggle.tsx +0 -45
  837. package/src/interfaces/web/src/hooks/use-mobile.ts +0 -19
  838. package/src/interfaces/web/src/screens/base/GlobalTasksTab.tsx +0 -161
@@ -59,6 +59,7 @@
59
59
  let pausePreviewed = false; // a preview decode fired for the current pause
60
60
  let reuseLiveOnStop = false; // commit should reuse pendingUserText, not re-decode
61
61
  let livePromise = null; // in-flight preview decode (awaited on reuse)
62
+ let previewFlushTs = 0; // audio cut-off the pending preview actually saw
62
63
 
63
64
  // Web Audio analyser — drives the live capsule wave from real mic amplitude
64
65
  let audioCtx = null;
@@ -67,7 +68,7 @@
67
68
  let timeData = null;
68
69
  let waveRaf = null;
69
70
 
70
- let streamingAgentEntry = null; // legacy single-bubble streaming (kept dormant)
71
+ let streamingAgentEntry = null; // the pending "thinking" bubble; also renders turn errors
71
72
  let toolPillsByName = {}; // active tool pills, by tool name, for the live turn
72
73
  let ttsAudio = null; // <audio> currently playing
73
74
 
@@ -92,6 +93,7 @@
92
93
  // requestTts / second finalize on the same in-flight bubble.
93
94
  let doneHandled = false;
94
95
  let ttsTimer = null;
96
+ let prewarmedTurn = false; // this turn already kicked a speak-ahead
95
97
 
96
98
  // ── Inline SVG icons (mirrors the design's I.* set) ──────────────────────
97
99
  const SVG = (path, attrs = {}) => {
@@ -108,6 +110,7 @@
108
110
  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" }),
109
111
  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" }),
110
112
  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" }),
113
+ muted: () => SVG('<path d="M11 5 6.5 9H3v6h3.5L11 19V5z"/><path d="M16 9.5l4 5M20 9.5l-4 5"/>', { width: 12, height: 12, viewBox: "0 0 24 24" }),
111
114
  check: () => SVG('<path d="M5 12l4.5 4.5L19 7"/>', { width: 13, height: 13, viewBox: "0 0 24 24", "stroke-width": "2" }),
112
115
  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>`,
113
116
  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>`,
@@ -448,7 +451,11 @@
448
451
  <button class="chip btn-regen">${ICON.refresh()} Regenerar</button>
449
452
  </div>
450
453
  `;
451
- if (m.audio && m.dur) {
454
+ if (m.audio) {
455
+ // A missing duration is no reason to withhold the player — the <audio>
456
+ // element reports its own once metadata loads. Requiring m.dur here is
457
+ // what limited playback to the engines that happen to measure their
458
+ // output, which in practice meant the silent mock and nothing else.
452
459
  // Insert scrubber before turn-actions
453
460
  const scrubberHtml = buildScrubberHtml(m);
454
461
  const actions = t.querySelector(".turn-actions");
@@ -536,6 +543,8 @@
536
543
  $convScroll?.querySelector("[data-pending='user']")?.remove();
537
544
  }
538
545
 
546
+ // The placeholder bubble shown while the turn is in flight. Segments replace
547
+ // it with real bubbles; a failed turn overwrites it with the error text.
539
548
  function ensureStreamingAgentBubble() {
540
549
  if (!$convScroll) return;
541
550
  if (streamingAgentEntry?.el && document.body.contains(streamingAgentEntry.el)) return;
@@ -561,16 +570,14 @@
561
570
  scrollConvToBottom();
562
571
  }
563
572
 
564
- function appendStreamingToken(chunk) {
565
- ensureStreamingAgentBubble();
566
- if (!streamingAgentEntry.started) {
567
- streamingAgentEntry.started = true;
568
- streamingAgentEntry.msgEl.innerHTML = ""; // clear the dots placeholder
569
- }
570
- streamingAgentEntry.text += chunk;
571
- // Re-render with the word-in animation: split into spans on word boundaries.
572
- streamingAgentEntry.msgEl.innerHTML = formatWordsHtml(streamingAgentEntry.text) + `<span class="caret"></span>`;
573
- scrollConvToBottom();
573
+ // Drop the provisional bubble without recording anything. Streamed deltas are
574
+ // a preview of a segment that is still coming; the `segment` event is what
575
+ // becomes a message, gets spoken, and enters history. Finalizing here instead
576
+ // would file the same reply twice.
577
+ function discardStreamingAgent() {
578
+ if (!streamingAgentEntry) return;
579
+ streamingAgentEntry.el.remove();
580
+ streamingAgentEntry = null;
574
581
  }
575
582
 
576
583
  function finalizeStreamingAgent({ audio, dur } = {}) {
@@ -613,11 +620,52 @@
613
620
  }
614
621
 
615
622
  // ── Audio scrubber ───────────────────────────────────────────────────────
623
+ // m:ss. The old formatter hardcoded the "0:" minute, so anything past a
624
+ // minute read as 0:73.
625
+ function fmtDur(s) {
626
+ const total = Math.max(0, Math.round(s || 0));
627
+ return `${Math.floor(total / 60)}:${String(total % 60).padStart(2, "0")}`;
628
+ }
629
+ // A bubble's text lands as soon as the turn resolves, but its voice is still
630
+ // being generated — so the reply sat there looking finished and mute, and
631
+ // then a player appeared out of nowhere seconds later. This placeholder fills
632
+ // that gap. Same bar geometry and padding as the real scrubber, so swapping
633
+ // one for the other doesn't move anything on screen.
634
+ function buildPendingAudioHtml() {
635
+ const N = 38;
636
+ const bars = waveShape(N, 13);
637
+ return `
638
+ <div class="audio pending" role="status" aria-label="Generando audio…">
639
+ <span class="play"></span>
640
+ <div class="wavebar">
641
+ ${bars.map((h, i) => `<i style="height:${Math.round(h * 24)}px;--i:${i}"></i>`).join("")}
642
+ </div>
643
+ <span class="dur">···</span>
644
+ </div>
645
+ `;
646
+ }
647
+
648
+ // Only ever called for a turn that is live and has asked for speech: a past
649
+ // bubble that was never voiced must not grow a spinner when it is rebuilt.
650
+ function showPendingAudio(turnId) {
651
+ const turnEl = $convScroll?.querySelector(`[data-id="${turnId}"]`);
652
+ if (!turnEl || turnEl.querySelector(".audio")) return;
653
+ turnEl.querySelector(".turn-actions")?.insertAdjacentHTML("beforebegin", buildPendingAudioHtml());
654
+ }
655
+
656
+ // The placeholder promises audio is coming, so every path that ends that
657
+ // promise — audio arrived, TTS failed, the watchdog gave up — has to take it
658
+ // down. One left spinning forever is worse than never having shown it.
659
+ function clearPendingAudio(turnId) {
660
+ const turnEl = $convScroll?.querySelector(`[data-id="${turnId}"]`);
661
+ turnEl?.querySelector(".audio.pending")?.remove();
662
+ }
663
+
616
664
  function buildScrubberHtml(m) {
617
665
  const N = 38;
618
666
  const bars = waveShape(N, 13);
619
667
  const dur = m.dur || 0;
620
- const fmt = (s) => `0:${String(Math.round(s)).padStart(2, "0")}`;
668
+ const fmt = fmtDur;
621
669
  return `
622
670
  <div class="audio" data-bars="${N}">
623
671
  <button class="play" aria-label="Reproducir respuesta">${ICON.play()}</button>
@@ -628,19 +676,37 @@
628
676
  </div>
629
677
  `;
630
678
  }
679
+ // One player per bubble, fed by a playlist that is still being written.
680
+ //
681
+ // The reply is synthesized a sentence at a time, so parts arrive while
682
+ // earlier ones are already playing. The element plays them back to back and
683
+ // reports one continuous position across the whole list, which is what the
684
+ // listener perceives — they hear one answer, not three clips.
631
685
  function wireScrubber(turnEl, m) {
632
686
  const N = 38;
633
687
  const audioEl = turnEl.querySelector(".audio");
634
- if (!audioEl || !m.audio) return;
688
+ // A bubble rebuilt from state carries only the finished url; treat it as a
689
+ // one-item playlist so replay works the same on both paths.
690
+ if (!m._parts?.length && m.audio) m._parts = [{ url: m.audio, dur: m.dur || 0 }];
691
+ if (!audioEl || !m._parts?.length) return;
635
692
  const $play = audioEl.querySelector(".play");
636
693
  const $bar = audioEl.querySelector(".wavebar");
637
694
  const bars = $bar.querySelectorAll("i");
638
695
  const $dur = audioEl.querySelector(".dur");
639
- const dur = m.dur || 1;
640
- const fmt = (s) => `0:${String(Math.round(s)).padStart(2, "0")}`;
641
- const audio = new Audio(m.audio);
642
- m._audioEl = audio; // the audio queue drives sequential playback
696
+ const fmt = fmtDur;
697
+ const audio = new Audio(m._parts[0].url);
698
+ m._audioEl = audio; // the audio queue drives sequential playback
699
+ m._partIdx = 0;
643
700
  let raf = null;
701
+ let waiting = false; // played everything that has arrived so far
702
+
703
+ const partDur = (i) => {
704
+ if (i === m._partIdx && audio.duration > 0 && isFinite(audio.duration)) return audio.duration;
705
+ return m._parts[i]?.dur || 0;
706
+ };
707
+ const total = () => m._parts.reduce((a, _, i) => a + partDur(i), 0);
708
+ const before = () => m._parts.slice(0, m._partIdx).reduce((a, _, i) => a + partDur(i), 0);
709
+ const elapsed = () => before() + (audio.currentTime || 0);
644
710
 
645
711
  const setProgress = (p) => {
646
712
  p = Math.max(0, Math.min(1, p));
@@ -649,15 +715,40 @@
649
715
  b.classList.toggle("on", i <= cur);
650
716
  b.classList.toggle("cur", i === cur && !audio.paused);
651
717
  });
652
- $dur.textContent = p > 0 || !audio.paused ? fmt(p * dur) : fmt(dur);
718
+ const t = total();
719
+ $dur.textContent = p > 0 || !audio.paused ? fmt(p * t) : fmt(t);
653
720
  };
654
- const tick = () => {
655
- if (audio.duration > 0) setProgress(audio.currentTime / audio.duration);
656
- raf = requestAnimationFrame(tick);
721
+ const paint = () => { const t = total(); setProgress(t > 0 ? elapsed() / t : 0); };
722
+ const tick = () => { paint(); raf = requestAnimationFrame(tick); };
723
+
724
+ // Load a part and keep playing. Used both when one ends and when a part
725
+ // the player was waiting for finally shows up.
726
+ const playPart = (i) => {
727
+ m._partIdx = i;
728
+ waiting = false;
729
+ audio.src = m._parts[i].url;
730
+ audio.play().catch(() => onSegmentEnded(m));
657
731
  };
732
+ // Called from attachAudioPart when the list grows.
733
+ m._onPartArrived = () => {
734
+ const t = total();
735
+ if (waiting && m._parts[m._partIdx + 1]) playPart(m._partIdx + 1);
736
+ else if (audio.paused && audio.currentTime === 0) $dur.textContent = fmt(t);
737
+ };
738
+ // Called from the tts-end event: nothing more is coming.
739
+ m._onPartsDone = () => { if (waiting) { waiting = false; onSegmentEnded(m); } };
740
+
741
+ audio.addEventListener("loadedmetadata", paint);
658
742
  audio.addEventListener("play", () => { $play.innerHTML = ICON.pause(); raf = requestAnimationFrame(tick); if (mode !== "speaking") { mode = "speaking"; render(); } });
659
743
  audio.addEventListener("pause", () => { $play.innerHTML = ICON.play(); if (raf) cancelAnimationFrame(raf); });
660
- audio.addEventListener("ended", () => { $play.innerHTML = ICON.play(); if (raf) cancelAnimationFrame(raf); setProgress(1); onSegmentEnded(m); });
744
+ audio.addEventListener("ended", () => {
745
+ if (m._parts[m._partIdx + 1]) return playPart(m._partIdx + 1);
746
+ if (!m._partsDone) { waiting = true; return; } // the next sentence is still being made
747
+ $play.innerHTML = ICON.play();
748
+ if (raf) cancelAnimationFrame(raf);
749
+ setProgress(1);
750
+ onSegmentEnded(m);
751
+ });
661
752
  // 404 / decode error / autoplay block: don't hang — advance the queue.
662
753
  audio.addEventListener("error", () => onSegmentEnded(m));
663
754
 
@@ -676,19 +767,40 @@
676
767
  $bar.addEventListener("click", (e) => {
677
768
  const r = $bar.getBoundingClientRect();
678
769
  const p = Math.max(0, Math.min(1, (e.clientX - r.left) / r.width));
679
- if (audio.duration > 0) audio.currentTime = p * audio.duration;
770
+ // Map one position on the bar onto (part, offset) the bar spans the
771
+ // whole reply, not the sentence that happens to be playing.
772
+ let target = p * total();
773
+ for (let i = 0; i < m._parts.length; i++) {
774
+ const d = partDur(i);
775
+ if (target <= d || i === m._parts.length - 1) {
776
+ if (i !== m._partIdx) {
777
+ const wasPlaying = !audio.paused;
778
+ m._partIdx = i;
779
+ audio.src = m._parts[i].url;
780
+ if (wasPlaying) audio.play().catch(() => {});
781
+ }
782
+ audio.currentTime = Math.max(0, Math.min(target, Math.max(0, d - 0.05)));
783
+ break;
784
+ }
785
+ target -= d;
786
+ }
680
787
  setProgress(p);
681
788
  });
682
789
  }
683
790
 
684
- // Post-finalize hook: add a scrubber to an already-rendered agent turn
685
- // when its TTS audio arrives. Called from the `tts-ready` daemon event.
686
- function attachAudioToTurn(turnId, { url, dur }) {
791
+ // Post-finalize hook: a sentence of this turn's speech just arrived. The
792
+ // first one mounts the player; later ones extend the playlist under it.
793
+ function attachAudioPart(turnId, { url, dur }) {
687
794
  const m = messages.find((x) => x.id === turnId);
688
795
  if (!m) return;
689
- m.audio = url;
690
- m.dur = dur || 0;
796
+ (m._parts = m._parts || []).push({ url, dur: dur || 0 });
797
+ m.audio = m._parts[0].url; // kept for rebuilds
798
+ m.dur = m._parts.reduce((a, p) => a + (p.dur || 0), 0);
799
+
800
+ if (m._parts.length > 1) { m._onPartArrived?.(); return; }
801
+
691
802
  const turnEl = $convScroll?.querySelector(`[data-id="${turnId}"]`);
803
+ turnEl?.querySelector(".audio.pending")?.remove();
692
804
  if (turnEl && !turnEl.querySelector(".audio")) {
693
805
  // Insert the scrubber HTML just before turn-actions (matches appendTurn).
694
806
  const actions = turnEl.querySelector(".turn-actions");
@@ -697,12 +809,34 @@
697
809
  else turnEl.insertAdjacentHTML("beforeend", html);
698
810
  wireScrubber(turnEl, m); // sets m._audioEl
699
811
  }
700
- // Audio is ready let the sequential queue play it when it's this
701
- // segment's turn (gapless auto-play across the turn's bubbles).
812
+ // The first sentence is enough to start: let the queue play it while the
813
+ // rest of the reply is still being spoken into existence.
702
814
  queueMarkReady(m);
703
815
  scrollConvToBottom();
704
816
  }
705
817
 
818
+ // No more sentences are coming for this bubble.
819
+ function markAudioComplete(turnId) {
820
+ const m = messages.find((x) => x.id === turnId);
821
+ if (!m) return;
822
+ m._partsDone = true;
823
+ m._onPartsDone?.();
824
+ }
825
+
826
+ // The other half of attachAudioPart: this segment produced no audio. Say so
827
+ // on the bubble instead of leaving the reply mutely text-only — "no voice
828
+ // this time" and "the voice is broken" look identical otherwise, and the
829
+ // second one is worth knowing about.
830
+ function markTurnUnvoiced(turnId, reason) {
831
+ const turnEl = $convScroll?.querySelector(`[data-id="${turnId}"]`);
832
+ if (!turnEl || turnEl.querySelector(".unvoiced")) return;
833
+ const el = document.createElement("span");
834
+ el.className = "chip unvoiced";
835
+ el.title = reason ? `Sin voz: ${reason}` : "Sin voz";
836
+ el.innerHTML = `${ICON.muted()} Sin voz`;
837
+ turnEl.querySelector(".turn-actions")?.prepend(el);
838
+ }
839
+
706
840
  function waveShape(n, seed = 7) {
707
841
  const out = []; let s = seed;
708
842
  for (let i = 0; i < n; i++) {
@@ -723,6 +857,7 @@
723
857
  currentTurn++;
724
858
  resetTurnAudio();
725
859
  doneHandled = false;
860
+ prewarmedTurn = false;
726
861
  if (ttsTimer) { clearTimeout(ttsTimer); ttsTimer = null; }
727
862
  }
728
863
  function resetTurnAudio() {
@@ -777,14 +912,23 @@
777
912
  // Called from a segment audio's `ended` (or `error`). Advances the queue.
778
913
  function onSegmentEnded(m) {
779
914
  const e = turnAudios.find((x) => x.m === m);
780
- if (e) { if (e.played) return; e.played = true; }
781
- if (queuePlaying && ttsAudio === m._audioEl) {
915
+ const counted = !!e?.played; // the queue already moved past this one
916
+ if (e) e.played = true;
917
+ if (!counted && queuePlaying && ttsAudio === m._audioEl) {
782
918
  queuePlaying = false;
783
919
  audioCursor++;
784
920
  pumpAudioQueue();
785
- } else if (mode === "speaking") {
786
- mode = "idle"; render();
921
+ return;
787
922
  }
923
+ // Nothing is playing any more, so the capsule must not still say so.
924
+ //
925
+ // This used to hang on "Roby está hablando…" forever. When a segment's TTS
926
+ // outran the turn watchdog, the watchdog wrote the entry off as played;
927
+ // the audio then arrived anyway, the user pressed play by hand, and on
928
+ // `ended` the already-played guard returned early — before the line that
929
+ // puts the capsule back to idle. The guard is about not advancing the
930
+ // queue twice, so that is all it guards now.
931
+ if (!queuePlaying && mode === "speaking") { mode = "idle"; render(); }
788
932
  }
789
933
 
790
934
  // ── Recording flow ───────────────────────────────────────────────────────
@@ -799,10 +943,14 @@
799
943
  pausePreviewed = false;
800
944
  reuseLiveOnStop = false;
801
945
  livePromise = null;
946
+ previewFlushTs = 0;
802
947
  pendingUserText = "";
803
- // Warm the whisper model now (overlaps the mic warm-up), so the decode at
804
- // the end of this utterance doesn't pay a cold start.
948
+ // Warm both models now, while the user is about to speak. Whisper is needed
949
+ // in a few seconds, the voice engine a few seconds after that — and both
950
+ // cost far more when cold than the whole rest of the turn. Overlapping them
951
+ // with the utterance is free time that would otherwise be dead waiting.
805
952
  window.apx?.warmupStt?.();
953
+ window.apx?.warmupTts?.();
806
954
  mode = "listening";
807
955
  render();
808
956
  startMic();
@@ -889,12 +1037,18 @@
889
1037
  // in-flight preview if it hasn't settled yet.
890
1038
  if (reuseLiveOnStop) {
891
1039
  if (livePromise) { try { await livePromise; } catch {} }
892
- text = (pendingUserText || "").trim();
1040
+ // Only if the preview's audio actually covers everything said. If the
1041
+ // user resumed speaking after that cut-off (and the new pause couldn't
1042
+ // fire its own preview because one was still in flight), the stashed
1043
+ // text is a truncated utterance — decode again rather than send half
1044
+ // a sentence.
1045
+ if (previewFlushTs && lastVoiceTs <= previewFlushTs) text = (pendingUserText || "").trim();
893
1046
  }
894
1047
  // Manual send (Enviar / ⌘G release) or no preview yet → one fresh decode.
895
1048
  if (!text) text = (await transcribeBuffered()).trim();
896
1049
  recordedChunks = [];
897
1050
  reuseLiveOnStop = false;
1051
+ previewFlushTs = 0;
898
1052
  // Guard with .trim() — whisper occasionally returns a single space or
899
1053
  // newline for very short clips, which used to commit an empty bubble.
900
1054
  if (!text || isCancelled) {
@@ -1070,6 +1224,29 @@
1070
1224
  } catch {}
1071
1225
  return "";
1072
1226
  }
1227
+ // MediaRecorder only hands over a blob once per timeslice, so at any instant
1228
+ // the last second of speech is still inside the recorder. A preview decode
1229
+ // that skipped this would transcribe an utterance with its tail missing —
1230
+ // which is exactly what made the reuse below unsafe, and why every turn used
1231
+ // to pay a second full decode. requestData() forces the pending audio out now.
1232
+ function flushRecorder() {
1233
+ return new Promise((resolve) => {
1234
+ if (!mediaRecorder || mediaRecorder.state !== "recording") return resolve();
1235
+ let done = false;
1236
+ const finish = () => {
1237
+ if (done) return;
1238
+ done = true;
1239
+ mediaRecorder.removeEventListener("dataavailable", finish);
1240
+ resolve();
1241
+ };
1242
+ mediaRecorder.addEventListener("dataavailable", finish);
1243
+ try { mediaRecorder.requestData(); } catch { finish(); return; }
1244
+ // The event is the normal path; the timer only exists so a recorder that
1245
+ // never answers can't wedge the preview (and with it the auto-send).
1246
+ setTimeout(finish, 250);
1247
+ });
1248
+ }
1249
+
1073
1250
  // Decode what's been recorded so far (fired once per speech pause). The
1074
1251
  // result is stashed in pendingUserText and reused by the auto-send on stop,
1075
1252
  // so the same audio is never decoded twice. livePromise lets onstop await an
@@ -1079,8 +1256,17 @@
1079
1256
  liveBusy = true;
1080
1257
  livePromise = (async () => {
1081
1258
  try {
1259
+ await flushRecorder();
1260
+ // Stamp the cut-off BEFORE decoding: onstop reuses this text only if no
1261
+ // further speech arrived after this instant.
1262
+ previewFlushTs = Date.now();
1082
1263
  const text = await transcribeBuffered();
1083
- if (text && mode === "listening") pendingUserText = text;
1264
+ // Deliberately not gated on `mode === "listening"`: the auto-send flips
1265
+ // the mode to "transcribing" at SILENCE_MS, which lands *before* this
1266
+ // decode settles. Gating here threw the result away every single time
1267
+ // and made the reuse below dead code — one wasted decode per turn, plus
1268
+ // a second one on the critical path.
1269
+ if (text && !isCancelled) pendingUserText = text;
1084
1270
  } finally { liveBusy = false; }
1085
1271
  })();
1086
1272
  return livePromise;
@@ -1110,6 +1296,59 @@
1110
1296
  // mount the placeholder bubble, and ask main to grow the window now (not
1111
1297
  // one ResizeObserver tick later). Shared by commitUserMessage + regen so
1112
1298
  // both paths set up the daemon-event pipeline identically.
1299
+ // Emoji and reasoning are stripped daemon-side before a segment is spoken, so
1300
+ // a sentence carrying either would be pre-made under a key the real request
1301
+ // never uses. Cheaper to skip than to synthesize and throw away.
1302
+ const HAS_EMOJI = /\p{Extended_Pictographic}|\p{Regional_Indicator}/u;
1303
+ const HAS_REASONING = /[<[]\s*\/?\s*(think|thinking|reason|reasoning|scratchpad|analysis)/i;
1304
+ // The daemon can only hand the renderer a segment once the whole turn has
1305
+ // resolved, so the voice engine sits idle for the model's entire generation
1306
+ // and only then starts on sentence one. The tokens are already arriving,
1307
+ // though — as soon as they contain a finished sentence, that sentence is
1308
+ // almost certainly the first thing that will be spoken, so start making it
1309
+ // now. main.js keys the result by exact text: if the reply turns out
1310
+ // different, the pre-made audio is simply never claimed.
1311
+ function maybePrewarmTts() {
1312
+ if (prewarmedTurn || !streamingAgentEntry) return;
1313
+ const buf = streamingAgentEntry.text || "";
1314
+ // A long preamble with no sentence end is a model that isn't writing prose
1315
+ // (reasoning, a list, code) — stop watching rather than guess.
1316
+ if (buf.length > 400) { prewarmedTurn = true; return; }
1317
+ if (HAS_REASONING.test(buf)) { prewarmedTurn = true; return; }
1318
+ // Only look at whole sentences: a half-streamed one is not yet the chunk.
1319
+ const complete = buf.match(/^[\s\S]*[.!?…](?=\s)/);
1320
+ if (!complete) return;
1321
+ const first = firstSpeechChunk(complete[0].trim());
1322
+ if (!first) return; // no chunk has closed yet — keep watching
1323
+ prewarmedTurn = true;
1324
+ if (HAS_EMOJI.test(first)) return;
1325
+ window.apx?.prewarmTts?.(first);
1326
+ }
1327
+
1328
+ // Mirror of main.js splitForSpeech, for its first chunk only. It has to agree
1329
+ // exactly: a chunk built here that the real request never asks for is not just
1330
+ // wasted, it holds the voice engine's lock while the chunk that *is* wanted
1331
+ // waits behind it. Returns null while no chunk has closed yet — the splitter
1332
+ // packs short sentences together, so "Hola." on its own is not a chunk.
1333
+ function firstSpeechChunk(text, { min = 12, limit = 80 } = {}) {
1334
+ const pieces = [];
1335
+ for (const sentence of String(text).trim().split(/(?<=[.!?…])\s+/)) {
1336
+ if (sentence.length <= limit) { pieces.push(sentence); continue; }
1337
+ for (const clause of sentence.split(/(?<=[,;:—–])\s+/)) {
1338
+ if (clause.length <= limit) { pieces.push(clause); continue; }
1339
+ for (const word of clause.split(" ")) pieces.push(word);
1340
+ }
1341
+ }
1342
+ let cur = "";
1343
+ for (const piece of pieces) {
1344
+ if (!piece) continue;
1345
+ if (cur && cur.length + 1 + piece.length > limit) return cur;
1346
+ cur = cur ? `${cur} ${piece}` : piece;
1347
+ if (cur.length >= min) return cur;
1348
+ }
1349
+ return null;
1350
+ }
1351
+
1113
1352
  function startAgentTurn() {
1114
1353
  beginAgentTurn(); // bump currentTurn + reset the audio queue/guards
1115
1354
  mode = "thinking";
@@ -1164,16 +1403,25 @@
1164
1403
  }
1165
1404
  ensureConv();
1166
1405
  break;
1167
- case "token":
1168
- // Legacy path (backend no longer streams tokens for desktop). Kept so a
1169
- // mixed-version daemon doesn't break — accumulate into a single bubble.
1170
- appendStreamingToken(msg.text || "");
1171
- break;
1172
1406
  case "tool_start": addToolPill(msg.name); break;
1173
1407
  case "tool_done": updateToolPill(msg.name); break;
1408
+ case "delta": {
1409
+ // Tokens as they land. Purely visual: this bubble is thrown away the
1410
+ // moment the real segment arrives, so nothing here is spoken or kept.
1411
+ if (!msg.text) break;
1412
+ ensureConv();
1413
+ ensureStreamingAgentBubble();
1414
+ streamingAgentEntry.text += msg.text;
1415
+ streamingAgentEntry.msgEl.innerHTML = formatWordsHtml(streamingAgentEntry.text);
1416
+ maybePrewarmTts();
1417
+ requestWindowResize();
1418
+ scrollConvToBottom();
1419
+ break;
1420
+ }
1174
1421
  case "segment": {
1175
1422
  // Each segment is its own agent message bubble + its own audio.
1176
1423
  ensureConv();
1424
+ discardStreamingAgent(); // the streamed preview is now redundant
1177
1425
  const text = (msg.text || "").trim();
1178
1426
  if (!text) break;
1179
1427
  const id = nextId++;
@@ -1185,6 +1433,7 @@
1185
1433
  // it for gapless sequential playback. `speak` carries the inline emotion
1186
1434
  // tags (the bubble `text` has them stripped) so a tag-aware engine like
1187
1435
  // QVox can act on them; falls back to the visible text.
1436
+ showPendingAudio(id);
1188
1437
  window.apx?.requestTts?.(msg.speak || text, id);
1189
1438
  requestWindowResize();
1190
1439
  scrollConvToBottom();
@@ -1194,6 +1443,7 @@
1194
1443
  if (doneHandled) break;
1195
1444
  doneHandled = true;
1196
1445
  turnDone = true;
1446
+ discardStreamingAgent(); // nothing should outlive the turn as a preview
1197
1447
  // Record the whole turn as one assistant entry for conversation context.
1198
1448
  const full = (msg.text || "").trim();
1199
1449
  if (full) history.push({ role: "assistant", content: full });
@@ -1201,7 +1451,12 @@
1201
1451
  // the capsule can't get stuck in "Pensando…".
1202
1452
  if (turnWatchdog) clearTimeout(turnWatchdog);
1203
1453
  turnWatchdog = setTimeout(() => {
1204
- turnAudios.forEach((e) => { if (!e.ready) { e.ready = true; e.failed = true; e.played = true; } });
1454
+ turnAudios.forEach((e) => {
1455
+ if (e.ready) return;
1456
+ e.ready = true; e.failed = true; e.played = true;
1457
+ clearPendingAudio(e.m.id);
1458
+ markTurnUnvoiced(e.m.id, "la voz no respondió");
1459
+ });
1205
1460
  pumpAudioQueue();
1206
1461
  }, 12000);
1207
1462
  // Play whatever audio is already ready; flip to idle if there's nothing
@@ -1212,13 +1467,18 @@
1212
1467
  }
1213
1468
  break;
1214
1469
  }
1215
- case "tts-ready":
1216
- if (msg.seg != null) attachAudioToTurn(msg.seg, { url: msg.url, dur: msg.duration });
1470
+ case "tts-part":
1471
+ // One sentence of this bubble's speech. The first starts playback; the
1472
+ // rest queue up behind it while they are still being generated.
1473
+ if (msg.seg != null) attachAudioPart(msg.seg, { url: msg.url, dur: msg.duration });
1474
+ break;
1475
+ case "tts-end":
1476
+ if (msg.seg != null) markAudioComplete(msg.seg);
1217
1477
  break;
1218
1478
  case "tts-failed": {
1219
1479
  // No audio for this segment — skip it in the queue so playback advances.
1220
1480
  const m = (msg.seg != null) ? messages.find((x) => x.id === msg.seg) : null;
1221
- if (m) queueMarkFailed(m);
1481
+ if (m) { clearPendingAudio(m.id); queueMarkFailed(m); markTurnUnvoiced(m.id, msg.error); }
1222
1482
  break;
1223
1483
  }
1224
1484
  case "error": {
@@ -358,6 +358,35 @@ button { font-family: inherit; }
358
358
  .wavebar i.cur { transform: scaleY(1.25); }
359
359
  .audio .dur { font-size: 11px; color: var(--ink-3); font-variant-numeric: tabular-nums; min-width: 30px; text-align: right; }
360
360
 
361
+ /* Waiting for the voice. Same box as the real player so the swap is invisible:
362
+ the bars keep their shape and only their opacity moves, in a wave that runs
363
+ left to right — the reply is being spoken into existence, in order. */
364
+ .audio.pending { pointer-events: none; }
365
+ .audio.pending .play {
366
+ background: none; box-shadow: none;
367
+ border: 2px solid color-mix(in oklch, var(--ink-3) 30%, transparent);
368
+ border-top-color: var(--accent);
369
+ animation: audio-spin .7s linear infinite;
370
+ }
371
+ .audio.pending .wavebar { cursor: default; }
372
+ .audio.pending .wavebar i {
373
+ animation: audio-wait 1.15s ease-in-out infinite;
374
+ animation-delay: calc(var(--i) * 28ms);
375
+ }
376
+ .audio.pending .dur { letter-spacing: 1.5px; opacity: .7; }
377
+ @keyframes audio-spin { to { transform: rotate(360deg); } }
378
+ @keyframes audio-wait {
379
+ 0%, 100% { opacity: .28; }
380
+ 45% { opacity: .9; }
381
+ }
382
+ /* Motion is the whole signal here, so without it say so in the bars instead of
383
+ leaving a placeholder indistinguishable from a finished, silent player. */
384
+ @media (prefers-reduced-motion: reduce) {
385
+ .audio.pending .play,
386
+ .audio.pending .wavebar i { animation: none; }
387
+ .audio.pending .wavebar i { opacity: .4; }
388
+ }
389
+
361
390
  /* Continued agent messages (no repeated "Roby" header) hug the previous one. */
362
391
  .turn.cont { padding-top: 0; }
363
392
  .turn.cont .msg-agent { margin-top: 1px; }
@@ -390,6 +419,11 @@ button { font-family: inherit; }
390
419
  }
391
420
  .chip:hover { background: var(--glass-hairline); color: var(--ink); border-color: transparent; }
392
421
  .chip.done { color: oklch(0.6 0.15 150); }
422
+ /* "Sin voz": a passive marker, not a button — and it stays visible on older
423
+ turns too, which .turn-actions otherwise hides. */
424
+ .chip.unvoiced { cursor: default; opacity: .75; }
425
+ .chip.unvoiced:hover { background: transparent; color: var(--ink-2); border-color: var(--glass-hairline); }
426
+ .turn-actions:has(.unvoiced) { display: flex; opacity: 1; }
393
427
 
394
428
  /* system notice (e.g. no mic permission / dead mic) — not a chat message */
395
429
  .sys-notice {