@agentprojectcontext/apx 1.80.2 → 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 +134 -0
  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
@@ -1,28 +1,121 @@
1
- import { Bot, Copy, User, Info, Sparkles } from "lucide-react";
1
+ import { useState } from "react";
2
+ import { Bot, Clock, CornerDownRight, Copy, Info, Pencil, RefreshCw, X } from "lucide-react";
2
3
  import { cn } from "../../lib/cn";
4
+ import { AgentAvatar, type AgentFace } from "../agents/AgentAvatar";
3
5
  import { ToolCall } from "./ToolCall";
6
+ import { ActionGroup, segmentTurnParts, countTurnTools, type TurnSegment } from "./ActionGroup";
7
+ import { SkillTrace } from "./SkillTrace";
8
+ import { ReasoningBlock } from "./ReasoningBlock";
4
9
  import { AskQuestionsCard } from "./AskQuestionsCard";
5
10
  import { AskAnswersCard, parseAskAnswerText } from "./AskAnswersCard";
6
- import { textOf, type ChatMsg } from "../../hooks/useChat";
11
+ import { AttachmentGroup, stripMediaMarker } from "./Attachment";
12
+ import { MarkdownPreview, renderMentions } from "../files/MarkdownPreview";
13
+ import { textOf, type ChatMsg, type ChatPart } from "../../hooks/useChat";
7
14
  import { Tip } from "../ui/tip";
8
15
  import { t } from "../../i18n";
9
16
 
10
17
  interface Props {
11
18
  msg: ChatMsg;
12
- /** True when this is the last message in the list. Used to detect if an
13
- * ask_questions tool call is still waiting for the user vs already answered
14
- * (a later user message would push this assistant turn off the bottom). */
15
- isLast?: boolean;
19
+ /** True when THIS turn's ask_questions call is the one still waiting for an
20
+ * answer. Decided by the list, which can see whether a user message came
21
+ * after it; a turn cannot tell on its own. */
22
+ askPending?: boolean;
16
23
  /** True when this user message is the reply to a preceding `ask_questions`
17
24
  * call. Renders as a full-width centered card instead of the user bubble. */
18
25
  isAskAnswer?: boolean;
19
26
  onCopy?: (text: string) => void;
27
+ /** Who said it. Absent → the neutral glyph (surfaces that don't know the
28
+ * cast). The user side draws no avatar at all: you know who you are, and a
29
+ * generic silhouette on every second bubble is pure noise. */
30
+ face?: AgentFace;
31
+ /** Phone shaping: drop the avatar column and let the bubble have the width.
32
+ * On a 390px screen the face costs 36px on EVERY assistant turn to repeat
33
+ * the name already standing in the header, three inches above. */
34
+ compact?: boolean;
35
+ /** Written while the previous turn was still running: it is in the thread but
36
+ * has not left yet. Drawn at half strength, and it says so. */
37
+ queued?: boolean;
38
+ /** Take it back before it goes. Only meaningful alongside `queued`. */
39
+ onUnqueue?: () => void;
40
+ /** Re-run this assistant turn (drop it and everything after, ask again).
41
+ * Absent → no button (super-agent threads / previews don't offer it). */
42
+ onRegenerate?: () => void;
43
+ /** Edit this user turn and re-send, dropping everything after it. Absent → no
44
+ * edit affordance. */
45
+ onEdit?: (text: string) => void;
46
+ /** Group style: name each speaker in a header ABOVE the bubble (with a "traído
47
+ * por X" tag when a mention pulled them in), the way a group chat reads. Off
48
+ * in a 1:1, where the single agent is already named in the header. */
49
+ showSpeaker?: boolean;
50
+ /** Resolve an agent slug to its display name, for the "traído por X" tag. */
51
+ nameOf?: (slug: string) => string;
52
+ /** Full view: tool calls collapse into an ActionGroup. Simple ("pelado") view:
53
+ * tools hide and the narration lines that lived inside the group render as
54
+ * normal bubbles in order — same parts list, different layout. Default on. */
55
+ showTools?: boolean;
20
56
  }
21
57
 
22
- export function MessageBubble({ msg, isLast, isAskAnswer, onCopy }: Props) {
58
+ export function MessageBubble({ msg, askPending, isAskAnswer, onCopy, face, compact, queued, onUnqueue, onRegenerate, onEdit, showSpeaker, nameOf, showTools = true }: Props) {
59
+ // Hooks before any early return. The group-notice branch below returns without
60
+ // rendering a bubble, and these two used to sit after it — so a notice arriving
61
+ // mid-thread ("X joined the chat") changed the hook count for that row and React
62
+ // threw "rendered more hooks than during the previous render". Group chat is
63
+ // exactly where notices appear, so the crash was on the feature's own path.
64
+ const [editing, setEditing] = useState(false);
65
+ const [draft, setDraft] = useState("");
66
+
67
+ // A group system notice ("… se sumó / salió del chat") is a centred line, not
68
+ // a bubble, so it reads as something the room did rather than someone saying it.
69
+ if (msg.event) {
70
+ const who = (nameOf ? nameOf(msg.who || "") : msg.who) || msg.who || "";
71
+ return (
72
+ <div className="flex justify-center py-1">
73
+ <span className="rounded-full bg-muted/50 px-3 py-0.5 text-[11px] text-muted-fg">
74
+ {msg.event === "left" ? t("project.groups.left_chat", { name: who }) : t("project.groups.joined_chat", { name: who })}
75
+ </span>
76
+ </div>
77
+ );
78
+ }
23
79
  const mine = msg.role === "user";
24
- const copyText = textOf(msg);
80
+ // A turn that carried a file shows the file; its text is the marker the agent
81
+ // was handed, so only what the user actually wrote (caption, or the voice
82
+ // transcript) stays as text — copy included.
83
+ //
84
+ // Both directions. This was `mine ? msg.media : undefined`, which threw away
85
+ // every attachment the AGENT sent: the photo it pushed to Telegram, the image
86
+ // a routine delivered, a skill diagram it attached. The row on disk had the
87
+ // file and the bubble dropped it on the floor — so "the agent shared a photo"
88
+ // rendered as a bare caption, or as the placeholder text where a caption was
89
+ // never written.
90
+ const media = msg.media;
91
+ const copyText = media?.length ? stripMediaMarker(textOf(msg), media.length) : textOf(msg);
25
92
  const hasTools = msg.parts.some((p) => p.kind === "tool");
93
+ // A user turn is never grouped — it has no tools, and the segmenter would
94
+ // return one plain part per message anyway; blocks only shape the agent side.
95
+ //
96
+ // The turn keeps ITS OWN count across however many blocks it happened in:
97
+ // three tools, a sentence, three more tools is one turn of six actions, not
98
+ // two turns of three.
99
+ const segments = mine ? [] : segmentTurnParts(msg.parts);
100
+ const toolTotal = mine ? 0 : countTurnTools(msg.parts);
101
+ const blockCount = segments.filter((seg) => seg.kind === "work").length;
102
+ // The last block is the one still working; the earlier ones already finished.
103
+ const lastWorkAt = segments.map((seg) => seg.kind).lastIndexOf("work");
104
+ // Simple view: same parts, no ActionGroup. Narration that lived inside the
105
+ // work block comes out as regular bubbles; tools stay hidden (ask_questions
106
+ // still shows — it is a control the reader must reach).
107
+ const simpleParts = !mine && !showTools
108
+ ? msg.parts.filter((p) =>
109
+ p.kind === "text" ||
110
+ p.kind === "reasoning" ||
111
+ (p.kind === "tool" && p.tool === "ask_questions"),
112
+ )
113
+ : null;
114
+ // What actually gets drawn, in order. Only the agent's full view has blocks;
115
+ // the user's own turn and the simple view are flat lists of parts.
116
+ const renderList: TurnSegment[] = showTools && !mine
117
+ ? segments
118
+ : (simpleParts || msg.parts).map((part) => ({ kind: "part", part }) as const);
26
119
 
27
120
  if (mine && isAskAnswer) {
28
121
  const text = textOf(msg);
@@ -31,148 +124,396 @@ export function MessageBubble({ msg, isLast, isAskAnswer, onCopy }: Props) {
31
124
  }
32
125
  }
33
126
 
127
+ const startEdit = () => { setDraft(copyText); setEditing(true); };
128
+ const submitEdit = () => {
129
+ const v = draft.trim();
130
+ setEditing(false);
131
+ if (!onEdit) return;
132
+ // Caption may be empty when the turn still has a file — editing only the
133
+ // text of a photo message must not refuse the save.
134
+ if (v || media?.length) onEdit(v);
135
+ };
136
+
137
+ // One part of the turn, outside any block: the agent's own words, its
138
+ // thinking, or a card that must stay in the open.
139
+ const renderPart = (part: ChatPart, i: number) =>
140
+ part.kind === "reasoning" ? (
141
+ <ReasoningBlock key={i} text={part.text} streaming={part.streaming} />
142
+ ) : part.kind === "tool" ? (
143
+ part.tool === "ask_questions" && !mine ? (
144
+ <AskQuestionsCard key={`${part.id}-${i}`} part={part} pending={!!askPending} />
145
+ ) : (
146
+ <ToolCall key={`${part.id}-${i}`} part={part} />
147
+ )
148
+ ) : textOfPart(part.text, media) ? (
149
+ <div
150
+ key={i}
151
+ className={cn(
152
+ // max-w-full AND overflow-wrap:anywhere. The column caps at 85%, but
153
+ // a flex child is free to exceed its parent unless it is told not to,
154
+ // so one unbroken string still pushed the bubble past both edges of a
155
+ // phone with the text cut off on the left. `anywhere` rather than
156
+ // Tailwind's `break-words` (overflow-wrap: break-word) because only
157
+ // `anywhere` also shrinks the element's MIN-CONTENT width:
158
+ // break-word wraps the glyphs but still reports the whole URL as the
159
+ // narrowest the box can be, so any ancestor that sizes to content — a
160
+ // flex item, a grid cell — is laid out around the unbroken string and
161
+ // the overflow comes back.
162
+ "max-w-full [overflow-wrap:anywhere] rounded-2xl px-3 py-2 text-sm leading-relaxed",
163
+ mine
164
+ ? "whitespace-pre-wrap rounded-br-sm bg-bubble-mine text-foreground"
165
+ // Agent bubble — the softer muted fill the group v1 used, now
166
+ // shared by every chat bubble so 1:1 and group read the same.
167
+ : "w-full rounded-bl-sm bg-muted/50 text-foreground",
168
+ )}
169
+ >
170
+ {/* The agent writes markdown — **bold**, lists, `code`, links — so its
171
+ turns render through the (dependency-free, no
172
+ dangerouslySetInnerHTML) markdown component. The user's own bubble
173
+ stays literal: they typed it, and reflowing their text as markdown
174
+ would eat their asterisks and line breaks. The first/last child
175
+ margins are zeroed so the block spacing does not double up with the
176
+ bubble's own py-2. */}
177
+ {mine ? (
178
+ renderMentions(textOfPart(part.text, media))
179
+ ) : (
180
+ <MarkdownPreview
181
+ content={textOfPart(part.text, media)}
182
+ mentions
183
+ className="text-sm text-foreground [&>*:first-child]:mt-0 [&>*:last-child]:mb-0"
184
+ />
185
+ )}
186
+ </div>
187
+ ) : null;
188
+
34
189
  return (
35
- <div className={cn("group flex items-start gap-2", mine ? "justify-end" : "justify-start")}>
36
- {!mine && (
190
+ <div
191
+ className={cn(
192
+ "group flex items-start gap-2",
193
+ mine ? "justify-end" : "justify-start",
194
+ // Not sent yet, and it should not read as if it were: the same bubble
195
+ // at half strength, the way a message in flight looks everywhere else.
196
+ queued && "opacity-55",
197
+ )}
198
+ >
199
+ {!mine && !compact && (face ? (
200
+ <AgentAvatar {...face} size={28} className="mt-0.5" />
201
+ ) : (
37
202
  <span className="mt-0.5 grid size-7 shrink-0 place-items-center rounded-full bg-muted text-muted-foreground">
38
203
  <Bot size={14} />
39
204
  </span>
40
- )}
41
- <div className={cn("flex min-w-0 flex-col gap-1.5", mine ? "items-end" : "w-full max-w-[85%]")}>
205
+ ))}
206
+ {/* Both sides are capped. The user's own column used to have no max-width
207
+ at all, so one unbroken string — a Google Docs URL pasted into a
208
+ message — stretched the bubble past the viewport and the text ran off
209
+ the left edge. Invisible on a wide screen, unreadable on a phone. */}
210
+ <div
211
+ className={cn(
212
+ "flex min-w-0 flex-col gap-1.5",
213
+ compact ? "max-w-[92%]" : "max-w-[85%]",
214
+ mine ? "items-end" : "w-full",
215
+ )}
216
+ >
217
+ {/* Group style: name the speaker ABOVE the bubble, with a "traído por X"
218
+ tag when a mention pulled them in — the way a group chat reads. */}
219
+ {showSpeaker && !mine && (msg.agent || msg.agentId) && (
220
+ <div className="flex items-center gap-1.5 text-[11px] leading-none">
221
+ <span className="font-semibold text-foreground/90">{msg.agent}</span>
222
+ {msg.reason && (
223
+ <span className="inline-flex items-center gap-0.5 text-muted-fg/70">
224
+ <CornerDownRight size={11} /> {t("project.groups.pulled_by", { name: nameOf ? nameOf(msg.reason) : msg.reason })}
225
+ </span>
226
+ )}
227
+ </div>
228
+ )}
229
+
230
+ {/* What was actually sent: the voice note plays, the photo is the photo,
231
+ the document opens. */}
232
+ {media?.length ? <AttachmentGroup media={media} /> : null}
233
+
42
234
  {/* Operational notes (engine fallbacks, retries, suppressed tools). */}
43
235
  {!mine && msg.notes && msg.notes.length > 0 && (
44
236
  <div className="flex flex-col gap-0.5">
45
237
  {msg.notes.map((n, i) => (
46
- <span key={i} className="flex items-center gap-1 text-[10px] text-amber-400/80">
238
+ <span key={i} className="flex items-center gap-1 text-[10px] text-amber-600 dark:text-amber-400/80">
47
239
  <Info size={10} /> {n}
48
240
  </span>
49
241
  ))}
50
242
  </div>
51
243
  )}
52
244
 
53
- {/* Skill Inspector: which skills the per-turn RAG injected for this turn. */}
54
- {!mine && msg.inspector && (msg.inspector.loaded?.length || msg.inspector.hinted?.length) ? (
55
- <Tip content={t("shared_ui.skill_inspector_title", { embedder: msg.inspector.embedder || "RAG" })}>
56
- <div
57
- className="flex flex-wrap items-center gap-1 text-[10px] text-sky-400/90"
58
- >
59
- <Sparkles size={10} />
60
- {msg.inspector.loaded?.map((s) => (
61
- <span key={`l-${s}`} className="rounded bg-sky-500/15 px-1 py-0.5 font-mono">
62
- {s}
63
- </span>
64
- ))}
65
- {msg.inspector.hinted?.map((s) => (
66
- <span key={`h-${s}`} className="rounded border border-sky-500/30 px-1 py-0.5 font-mono opacity-70">
67
- {s}?
68
- </span>
69
- ))}
70
- </div>
71
- </Tip>
72
- ) : null}
73
-
74
- {/* Ordered parts: interleaved assistant text + tool calls. */}
75
- {msg.parts.map((part, i) =>
76
- part.kind === "tool" ? (
77
- part.tool === "ask_questions" && !mine ? (
78
- <AskQuestionsCard
79
- key={`${part.id}-${i}`}
80
- part={part}
81
- pending={!!isLast}
82
- />
83
- ) : (
84
- <ToolCall key={`${part.id}-${i}`} part={part} />
85
- )
86
- ) : part.text ? (
87
- <div
88
- key={i}
89
- className={cn(
90
- "whitespace-pre-wrap rounded-2xl px-3 py-2 text-sm leading-relaxed shadow-sm",
91
- mine
92
- ? "rounded-br-sm border border-emerald-500/30 bg-emerald-500/10 text-foreground dark:bg-emerald-500/15"
93
- : "w-full rounded-bl-sm border border-border bg-card text-foreground",
94
- )}
95
- >
96
- {part.text}
97
- </div>
98
- ) : null,
99
- )}
245
+ {/* Skill Inspector: which skills the per-turn RAG injected for this turn.
246
+ Each badge opens the skill it names. */}
247
+ {!mine && msg.inspector && <SkillTrace inspector={msg.inspector} />}
100
248
 
101
- {/* Pending placeholder before any part has arrived. */}
102
- {!mine && msg.pending && msg.parts.length === 0 && (
103
- <div className="rounded-2xl rounded-bl-sm border border-border bg-card px-3 py-2 text-sm text-muted-foreground">
104
-
249
+ {/* Editing your own turn in place: on save it re-sends and everything
250
+ below is dropped and re-answered. Enter saves, Esc/Cancel backs out. */}
251
+ {mine && editing && (
252
+ <div className={cn("flex w-full flex-col gap-1.5", compact ? "max-w-[92%]" : "max-w-[85%]")}>
253
+ <textarea
254
+ autoFocus
255
+ value={draft}
256
+ onChange={(e) => setDraft(e.target.value)}
257
+ onKeyDown={(e) => {
258
+ if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); submitEdit(); }
259
+ if (e.key === "Escape") setEditing(false);
260
+ }}
261
+ rows={Math.min(8, Math.max(2, draft.split("\n").length))}
262
+ className="w-full resize-y rounded-2xl rounded-br-sm border border-primary/40 bg-bubble-mine px-3 py-2 text-sm leading-relaxed text-foreground outline-none focus:border-primary"
263
+ />
264
+ <div className="flex items-center justify-end gap-2 text-xs">
265
+ <button type="button" onClick={() => setEditing(false)} className="rounded px-2 py-1 text-muted-foreground hover:text-foreground">
266
+ {t("common.cancel")}
267
+ </button>
268
+ <button type="button" onClick={submitEdit} disabled={!draft.trim()} className="rounded bg-primary px-2 py-1 font-medium text-primary-foreground disabled:opacity-50">
269
+ {t("chat_ui.edit_resend")}
270
+ </button>
271
+ </div>
105
272
  </div>
106
273
  )}
274
+ {/* The turn in the order it happened: a run of tool calls collapses into
275
+ one block, and what the agent SAID between the runs stands outside
276
+ them, as the message it is. A 24-step turn is still a log rather
277
+ than a screenful of cards — but the reader can now see WHERE in the
278
+ turn each thing was said, instead of only the closing line with
279
+ everything before it inside one box.
107
280
 
108
- {/* Attribution: who answered and on which engine. Always visible (not
109
- hover-gated) in a thread where several agents/models take turns,
110
- this is the only way to tell them apart at a glance. */}
111
- {!mine && (msg.agent || msg.model) && (
112
- <div className="flex flex-wrap items-center gap-1 text-[10px]">
113
- {msg.agent && (
114
- <span className="rounded bg-emerald-500/15 px-1 py-0.5 font-medium text-emerald-300">
115
- {msg.agent}
116
- </span>
117
- )}
118
- {msg.model && (
119
- <span className="rounded border border-border px-1 py-0.5 font-mono text-muted-foreground">
120
- {msg.model}
121
- </span>
122
- )}
123
- </div>
281
+ Simple view has no blocks at all: every text/reasoning part in
282
+ original order, tools hidden (ask_questions still shows it is a
283
+ control the reader must reach). */}
284
+ {!(mine && editing) && renderList.map((seg, si) =>
285
+ seg.kind === "work" ? (
286
+ <ActionGroup
287
+ key={`work-${si}`}
288
+ parts={seg.parts}
289
+ // Only the LAST block is still working; the ones above it have
290
+ // already finished, and a spinner on each would say otherwise.
291
+ running={!!msg.pending && si === lastWorkAt}
292
+ // Numbered against the TURN, not the block: a second block
293
+ // starting over at "1 action" would read as a second turn. A turn
294
+ // with one block just says how many — "1–4 of 4" is noise.
295
+ range={blockCount > 1 ? { from: seg.from, to: seg.to, total: toolTotal } : undefined}
296
+ />
297
+ ) : (
298
+ renderPart(seg.part, si)
299
+ ),
300
+ )}
301
+ {/* Still going. Not just before the first part arrives: a turn that has
302
+ been running shell commands for two minutes shows a list of finished
303
+ steps and nothing that says more is coming, so it reads as an answer
304
+ that stopped mid-thought. The pill stays for the whole turn and goes
305
+ when the turn does. */}
306
+ {!mine && msg.pending && (
307
+ <Typing label={
308
+ (() => {
309
+ const name = face?.name || msg.agent || "";
310
+ const key = msg.parts.length === 0 ? "chat_ui.typing" : "chat_ui.working";
311
+ // In a group the speaker header already names them right above, so
312
+ // the pill drops the name ("está escribiendo…"). In a 1:1 the pill
313
+ // is the only place it's named, so keep it ("Candela está escribiendo…").
314
+ return showSpeaker || !name
315
+ ? t(`${key}_generic` as "chat_ui.typing_generic")
316
+ : t(key, { name });
317
+ })()
318
+ } />
124
319
  )}
125
320
 
126
- <div className="flex items-center gap-2 text-[10px] text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100">
127
- <span>{formatTs(msg.ts)}</span>
128
- {!mine && msg.usage && (msg.usage.input_tokens || msg.usage.output_tokens) ? (
129
- <span className="font-mono">
130
- · {(msg.usage.input_tokens || 0) + (msg.usage.output_tokens || 0)} tok
321
+ {/* One line under the bubble: who answered on the left, when it did and
322
+ what it cost on the right. They used to be two stacked rows with the
323
+ second one hover-only, which on a phone means it does not exist.
324
+ Nothing at all while the turn is still running: a lone timestamp
325
+ under "escribiendo…" is a receipt for a message that has not
326
+ arrived yet. It appears with the answer. */}
327
+ {!(!mine && msg.pending) && <div
328
+ className={cn(
329
+ "flex w-full items-center gap-x-2 gap-y-1 text-[10px]",
330
+ // On a phone the row may not wrap: the whole point is that the meta
331
+ // stands BESIDE the attribution, and "zen:deepseek-v4-flash-free"
332
+ // alone fills 390px, so letting it wrap puts the data back under the
333
+ // bubble where it started. The model gives up the room instead — it
334
+ // is the one field here also spelled out in the context panel.
335
+ compact ? "flex-nowrap" : "flex-wrap",
336
+ mine && "justify-end",
337
+ )}
338
+ >
339
+ {/* In a group the speaker is named ABOVE the bubble, so the footer drops
340
+ the green name chip and keeps only model/time/cost. */}
341
+ {!mine && msg.agent && !showSpeaker && (
342
+ <span className="shrink-0 rounded bg-emerald-500/15 px-1 py-0.5 font-medium text-emerald-700 dark:text-emerald-300">
343
+ {msg.agent}
131
344
  </span>
132
- ) : null}
133
- {!mine && hasTools && (
134
- <span>· {t("shared_ui.tools_count", { n: msg.parts.filter((p) => p.kind === "tool").length })}</span>
135
345
  )}
136
- {/* Replayed turns have no tool parts the live events are gone — but
137
- they do carry the summary recorded at the time. Show that instead,
138
- so history does not look like the agent just answered from
139
- nothing. Failures are named: "it tried and could not" is the half
140
- worth surfacing. */}
141
- {!mine && !hasTools && msg.toolSummary?.tools?.length ? (
142
- <span title={msg.toolSummary.tools.map((x) => `${x.name}×${x.count}`).join(", ")}>
143
- · {t("shared_ui.tools_count", { n: msg.toolSummary.total })}
144
- {msg.toolSummary.failed
145
- ? ` (${t("shared_ui.tools_failed", { n: msg.toolSummary.failed })})`
146
- : ""}
346
+ {/* Half-strength surface: the attribution sits under the bubble and
347
+ should read as quieter than it, never as a second chip competing
348
+ with the agent's name. The token flips per theme, so one value
349
+ covers light and dark. */}
350
+ {!mine && msg.model && (
351
+ <span className="min-w-0 truncate rounded bg-surface-soft/50 px-1 py-0.5 font-mono text-muted-foreground">
352
+ {msg.model}
147
353
  </span>
148
- ) : null}
149
- {onCopy && copyText && (
150
- <Tip content={t("chat_ui.copy")}>
151
- <button
152
- type="button"
153
- onClick={() => onCopy(copyText)}
154
- className="inline-flex items-center gap-1 hover:text-foreground"
155
- aria-label={t("chat_ui.copy")}
156
- >
157
- <Copy size={10} /> {t("chat_ui.copy")}
158
- </button>
159
- </Tip>
160
354
  )}
161
- </div>
355
+ <div
356
+ className={cn(
357
+ "ml-auto flex shrink-0 items-center gap-2 text-muted-foreground",
358
+ // The phone has no hover, so there it is simply always on. So is
359
+ // the queue line — "waiting its turn, here is how to take it
360
+ // back" is not something to hide behind a hover.
361
+ !compact && !queued && "opacity-0 transition-opacity group-hover:opacity-100",
362
+ )}
363
+ >
364
+ {/* A timestamp on a turn that has not gone out yet is a receipt for
365
+ something that did not happen. What it is waiting for takes the
366
+ slot instead, until it leaves and gets a real one. */}
367
+ {queued ? (
368
+ <span className="inline-flex items-center gap-1">
369
+ <Clock size={10} /> {t("chat_ui.queued")}
370
+ </span>
371
+ ) : (
372
+ <span>{formatTs(msg.ts, compact)}</span>
373
+ )}
374
+ {queued && onUnqueue && (
375
+ <Tip content={t("chat_ui.queued_cancel")}>
376
+ <button
377
+ type="button"
378
+ onClick={onUnqueue}
379
+ className="inline-flex items-center hover:text-foreground"
380
+ aria-label={t("chat_ui.queued_cancel")}
381
+ >
382
+ <X size={11} />
383
+ </button>
384
+ </Tip>
385
+ )}
386
+ {!mine && msg.usage && (msg.usage.input_tokens || msg.usage.output_tokens) ? (
387
+ <span className="font-mono">
388
+ · {fmtTok((msg.usage.input_tokens || 0) + (msg.usage.output_tokens || 0))} tok
389
+ </span>
390
+ ) : null}
391
+ {showTools && !mine && hasTools && (
392
+ <span>· {t("shared_ui.tools_count", { n: msg.parts.filter((p) => p.kind === "tool").length })}</span>
393
+ )}
394
+ {/* Replayed turns have no tool parts — the live events are gone —
395
+ but they do carry the summary recorded at the time. Show that
396
+ instead, so history does not look like the agent just answered
397
+ from nothing. Failures are named: "it tried and could not" is
398
+ the half worth surfacing. Hidden in simple view with the rest. */}
399
+ {showTools && !mine && !hasTools && msg.toolSummary?.tools?.length ? (
400
+ <span title={msg.toolSummary.tools.map((x) => `${x.name}×${x.count}`).join(", ")}>
401
+ · {t("shared_ui.tools_count", { n: msg.toolSummary.total })}
402
+ {msg.toolSummary.failed
403
+ ? ` (${t("shared_ui.tools_failed", { n: msg.toolSummary.failed })})`
404
+ : ""}
405
+ </span>
406
+ ) : null}
407
+ {onCopy && copyText && (
408
+ <Tip content={t("chat_ui.copy")}>
409
+ <button
410
+ type="button"
411
+ onClick={() => onCopy(copyText)}
412
+ className="inline-flex items-center gap-1 hover:text-foreground"
413
+ aria-label={t("chat_ui.copy")}
414
+ >
415
+ <Copy size={10} /> {!compact && t("chat_ui.copy")}
416
+ </button>
417
+ </Tip>
418
+ )}
419
+ {/* Edit your turn and re-ask (drops everything below). */}
420
+ {mine && onEdit && !queued && !editing && (
421
+ <Tip content={t("chat_ui.edit")}>
422
+ <button
423
+ type="button"
424
+ onClick={startEdit}
425
+ className="inline-flex items-center gap-1 hover:text-foreground"
426
+ aria-label={t("chat_ui.edit")}
427
+ >
428
+ <Pencil size={10} /> {!compact && t("chat_ui.edit")}
429
+ </button>
430
+ </Tip>
431
+ )}
432
+ {/* Re-run this answer (drops it and everything below). */}
433
+ {!mine && onRegenerate && !queued && (
434
+ <Tip content={t("chat_ui.regenerate")}>
435
+ <button
436
+ type="button"
437
+ onClick={onRegenerate}
438
+ className="inline-flex items-center gap-1 hover:text-foreground"
439
+ aria-label={t("chat_ui.regenerate")}
440
+ >
441
+ <RefreshCw size={10} /> {!compact && t("chat_ui.regenerate")}
442
+ </button>
443
+ </Tip>
444
+ )}
445
+ </div>
446
+ </div>}
162
447
  </div>
163
- {mine && (
164
- <span className="mt-0.5 grid size-7 shrink-0 place-items-center rounded-full bg-muted text-muted-foreground">
165
- <User size={14} />
166
- </span>
167
- )}
168
448
  </div>
169
449
  );
170
450
  }
171
451
 
172
- function formatTs(iso: string): string {
452
+ /** "escribiendo…" / "trabajando…" — the word in the reader's language, with the
453
+ * dots doing the waiting. Three spans on staggered delays rather than a CSS
454
+ * animation of the text itself, so a screen reader gets one stable label
455
+ * instead of a glyph that changes three times a second. */
456
+ function Typing({ label }: { label: string }) {
457
+ return (
458
+ // w-fit, not the stretched full-width bubble every other assistant turn
459
+ // gets: a two-word status painted across the whole column reads as a
460
+ // message that arrived empty.
461
+ <div className="flex w-fit items-center gap-1.5 self-start rounded-2xl rounded-bl-sm bg-surface-soft px-3 py-2 text-sm text-muted-foreground">
462
+ <span>{label}</span>
463
+ <span aria-hidden className="flex items-center gap-[3px]">
464
+ {[0, 1, 2].map((i) => (
465
+ <span
466
+ key={i}
467
+ className="size-1 animate-bounce rounded-full bg-current"
468
+ style={{ animationDelay: `${i * 140}ms`, animationDuration: "1s" }}
469
+ />
470
+ ))}
471
+ </span>
472
+ </div>
473
+ );
474
+ }
475
+
476
+ /** The visible text of a part: with attachments, the machine-facing markers
477
+ * are dropped and what is left (a caption, or the voice transcript) is shown. */
478
+ function textOfPart(text: string | undefined, media: unknown[] | undefined): string {
479
+ if (!text) return "";
480
+ return media?.length ? stripMediaMarker(text, media.length) : text;
481
+ }
482
+
483
+ /** 1219686 → "1.2M". The raw count fit while the row was hover-only and had the
484
+ * bubble's whole width; now it shares a line with the agent and the model on a
485
+ * 390px screen, where seven digits are what pushes the row onto two. */
486
+ function fmtTok(n: number): string {
487
+ if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
488
+ if (n >= 1_000) return `${(n / 1_000).toFixed(1)}k`;
489
+ return String(n);
490
+ }
491
+
492
+ function formatTs(iso: string, compact?: boolean): string {
173
493
  try {
174
494
  const d = new Date(iso);
175
- return d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" });
495
+ if (Number.isNaN(d.getTime())) return iso;
496
+ // Seconds are for reading a live stream on a desktop; on the phone they are
497
+ // three more characters competing with the model's name for the same line.
498
+ const time = d.toLocaleTimeString([], {
499
+ hour: "2-digit",
500
+ minute: "2-digit",
501
+ ...(compact ? {} : { second: "2-digit" }),
502
+ });
503
+ // Today → time only. Older (or future) days keep the date so a scrolled
504
+ // thread is readable without opening a calendar.
505
+ const now = new Date();
506
+ const sameDay =
507
+ d.getFullYear() === now.getFullYear() &&
508
+ d.getMonth() === now.getMonth() &&
509
+ d.getDate() === now.getDate();
510
+ if (sameDay) return time;
511
+ const date = d.toLocaleDateString([], {
512
+ day: "2-digit",
513
+ month: "2-digit",
514
+ ...(d.getFullYear() !== now.getFullYear() ? { year: "2-digit" } : {}),
515
+ });
516
+ return `${date} ${time}`;
176
517
  } catch {
177
518
  return iso;
178
519
  }