@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
@@ -0,0 +1,136 @@
1
+ // Board columns: ONE catalog, shared by every project; each project picks which
2
+ // of them it shows.
3
+ //
4
+ // This settles a real tension in how one person uses one tool. Dev work wants
5
+ // pending → running → in review → blocked; a personal list wants pending and
6
+ // done and nothing else. "Every project the same" makes the second list carry
7
+ // four columns it never uses; "every project its own" means `qa` on one board
8
+ // is a different thing from `qa` on the next, and an agent told to "move it to
9
+ // QA" has to ask which QA. So: the VOCABULARY is global and editable, and the
10
+ // SUBSET is per project.
11
+ //
12
+ // A column is a task `status`. That is why this is cheap: nothing in the daemon
13
+ // branches on the value of `status` — it is a validation whitelist, a label, and
14
+ // something to group by. Making the whitelist configurable is the whole feature.
15
+ //
16
+ // `done` is never in the catalog. It is a task's `state`, not its status, and it
17
+ // is the one column every board ends with — dragging a card there closes the
18
+ // task, dragging it out reopens it. Making it configurable would let someone
19
+ // build a board with no way to finish anything.
20
+ import { TASK_STATUSES, DEFAULT_TASK_STATUS } from "#core/stores/tasks.js";
21
+
22
+ /** The terminal column every board gets, appended after the configured ones. */
23
+ export const DONE_COLUMN = "done";
24
+
25
+ /** Shipped catalog — exactly the statuses that existed before this was configurable. */
26
+ export const DEFAULT_TASK_COLUMNS = Object.freeze(
27
+ TASK_STATUSES.map((id) => Object.freeze({ id, label: null })),
28
+ );
29
+
30
+ const ID_RE = /^[a-z0-9][a-z0-9_-]{0,31}$/;
31
+
32
+ /**
33
+ * The on-enter automation, or nothing. An `agent` is required — an instruction
34
+ * with nobody to carry it out is not a half-configured hook, it is no hook.
35
+ */
36
+ function normalizeHook(value) {
37
+ const agent = String(value?.agent || "").trim().toLowerCase();
38
+ if (!agent || !ID_RE.test(agent)) return {};
39
+ const instruction = typeof value?.instruction === "string" ? value.instruction.trim() : "";
40
+ return { on_enter: { agent, instruction: instruction || null } };
41
+ }
42
+
43
+ /** A usable column id: slug-shaped, and never the reserved terminal one. */
44
+ export function isColumnId(value) {
45
+ const id = String(value || "").trim().toLowerCase();
46
+ return ID_RE.test(id) && id !== DONE_COLUMN;
47
+ }
48
+
49
+ /**
50
+ * Clean a catalog coming from config or from an HTTP body. Anything unusable is
51
+ * dropped rather than rejected: this reads stored data, and one bad row must not
52
+ * blank out a board.
53
+ *
54
+ * `label: null` means "use the built-in translation for this id" — that is how
55
+ * the four shipped columns stay bilingual while a custom one keeps the exact
56
+ * words its author typed.
57
+ *
58
+ * `on_enter` is the automation: `{ agent, instruction }` means "when a task
59
+ * lands in this column, hand it to that agent". The slug is resolved against
60
+ * the TASK'S OWN project, which is what lets one global "QA" column put each
61
+ * project's own qa agent to work. A project without that agent simply does
62
+ * nothing — a card that silently summoned a stranger would be worse.
63
+ */
64
+ export function normalizeColumns(value) {
65
+ if (!Array.isArray(value)) return [...DEFAULT_TASK_COLUMNS];
66
+ const seen = new Set();
67
+ const out = [];
68
+ for (const raw of value) {
69
+ const id = String((typeof raw === "string" ? raw : raw?.id) || "").trim().toLowerCase();
70
+ if (!isColumnId(id) || seen.has(id)) continue;
71
+ seen.add(id);
72
+ const label = typeof raw?.label === "string" && raw.label.trim() ? raw.label.trim() : null;
73
+ out.push({ id, label, ...normalizeHook(raw?.on_enter) });
74
+ }
75
+ // A board with no columns is not a board. Falling back beats showing nothing.
76
+ return out.length ? out : [...DEFAULT_TASK_COLUMNS];
77
+ }
78
+
79
+ /** The automation configured on a column, or null. */
80
+ export function columnHook(columns, id) {
81
+ return columns.find((c) => c.id === id)?.on_enter || null;
82
+ }
83
+
84
+ /** The global catalog: every column name that exists anywhere. */
85
+ export function readColumnCatalog(globalConfig) {
86
+ return normalizeColumns(globalConfig?.tasks?.columns);
87
+ }
88
+
89
+ /**
90
+ * The columns ONE project shows, in order, with `done` appended.
91
+ *
92
+ * A project that has chosen nothing shows the whole catalog — that is the old
93
+ * behaviour, and a board that starts empty would look broken.
94
+ *
95
+ * @param {object} globalConfig
96
+ * @param {object} projectConfig the project's own `.apc/config.json`
97
+ * @returns {{id: string, label: string|null}[]}
98
+ */
99
+ export function projectColumns(globalConfig, projectConfig) {
100
+ const catalog = readColumnCatalog(globalConfig);
101
+ const picked = projectConfig?.tasks?.columns;
102
+ if (!Array.isArray(picked) || picked.length === 0) {
103
+ return [...catalog, { id: DONE_COLUMN, label: null }];
104
+ }
105
+ const byId = new Map(catalog.map((c) => [c.id, c]));
106
+ const out = [];
107
+ const seen = new Set();
108
+ for (const raw of picked) {
109
+ const id = String(raw?.id || raw || "").trim().toLowerCase();
110
+ // A project can only pick from the catalog: that is what keeps "QA" the
111
+ // same thing everywhere. An id removed from the catalog quietly disappears
112
+ // from the boards that used it, rather than resurrecting a dead column.
113
+ if (!byId.has(id) || seen.has(id)) continue;
114
+ seen.add(id);
115
+ out.push(byId.get(id));
116
+ }
117
+ if (!out.length) out.push(...catalog);
118
+ return [...out, { id: DONE_COLUMN, label: null }];
119
+ }
120
+
121
+ /**
122
+ * Where a task sits on the board.
123
+ *
124
+ * `done` finishes in the done column. `dropped` does NOT: it was abandoned, not
125
+ * completed, and it stays in the column it was abandoned in — which is the
126
+ * useful thing to see when you go looking at what you gave up on and where.
127
+ * Filing it under "done" would claim it was finished.
128
+ *
129
+ * An open task whose status is not a column on THIS board falls into the first
130
+ * one, so a card is never invisible.
131
+ */
132
+ export function columnFor(task, columns) {
133
+ if (task?.state === "done") return DONE_COLUMN;
134
+ const status = task?.status || DEFAULT_TASK_STATUS;
135
+ return columns.some((c) => c.id === status) ? status : (columns[0]?.id || DEFAULT_TASK_STATUS);
136
+ }
@@ -0,0 +1,258 @@
1
+ // Comments on a task that can summon an agent.
2
+ //
3
+ // A comment thread is the one place where "who should do this" and "what is
4
+ // this" sit together, so @-mentioning an agent there is the cheapest way to
5
+ // hand work over: drop a card in QA, tag @qa, and it does the QA and writes
6
+ // back what it found. The reply lands as another comment on the same task.
7
+ //
8
+ // TURN-TAKING IS THE GROUP CHAT'S, ON PURPOSE. `parseMentions` is imported from
9
+ // agent/group/turn-resolver.js rather than copied, and the cascade below is the
10
+ // same shape: an agent's reply is scanned for mentions, whoever is named gets a
11
+ // turn, and a ceiling stops the ping-pong. What is NOT reused is the seeding —
12
+ // a group with no mention hands the turn to the first agent in the room, and a
13
+ // comment with no mention must summon nobody. A note to self is a note to self.
14
+ //
15
+ // The ceiling is lower than the group's ten. A room is watched live by the
16
+ // person who opened it; a task thread runs unattended — that is the entire
17
+ // point of leaving work for QA — so the blast radius of a mention loop is a
18
+ // number nobody is sitting there to stop.
19
+ import { resolveAgentModel } from "#core/agent/agent-model.js";
20
+ import { buildAgentSystem } from "#core/agent/build-agent-system.js";
21
+ import { groupToolIters } from "#core/agent/constants.js";
22
+ import { parseMentions } from "#core/agent/group/turn-resolver.js";
23
+ import { runAgentTurn } from "#core/agent/run-turn.js";
24
+ import { readAgents } from "#core/apc/parser.js";
25
+ import { CHANNELS } from "#core/constants/channels.js";
26
+ import { OWNER_ACTOR_ID } from "#core/constants/actors.js";
27
+ import { emitMessageEvent } from "#core/events/bus.js";
28
+ import { addComment, getTask } from "#core/stores/tasks.js";
29
+ import { nowIso } from "#core/util/time.js";
30
+
31
+ /** One comment can produce at most this many agent replies, cascade included. */
32
+ export const MAX_COMMENT_TURNS = 4;
33
+
34
+ /** Keep a reply readable in a side panel — see the "no huge deploy" rule below. */
35
+ const REPLY_CHAR_HINT = 700;
36
+
37
+ const displayName = (a) => a.fields?.Name || a.slug;
38
+
39
+ /**
40
+ * Everyone addressable on a task in this project: the owner (who can be named
41
+ * but never summoned) plus every agent the project declares.
42
+ */
43
+ export function taskParticipants(projectPath) {
44
+ let agents = [];
45
+ try { agents = readAgents(projectPath); } catch { agents = []; }
46
+ return [
47
+ { slug: OWNER_ACTOR_ID, name: "Owner", kind: "owner" },
48
+ ...agents.map((a) => ({ slug: a.slug, name: displayName(a), kind: "agent" })),
49
+ ];
50
+ }
51
+
52
+ /**
53
+ * Agent slugs a comment addresses. Exported so the API can resolve them at
54
+ * WRITE time and store them on the comment — the roster can change, and the
55
+ * thread should keep saying who was actually pulled in that day.
56
+ */
57
+ export function mentionedAgents(text, projectPath, authorSlug = OWNER_ACTOR_ID) {
58
+ return parseMentions(text || "", taskParticipants(projectPath), authorSlug);
59
+ }
60
+
61
+ /** The task, rendered for a model that has never seen it. */
62
+ function taskBlock(task, projectName) {
63
+ const lines = [
64
+ "## Task",
65
+ `**${task.title}**`,
66
+ `id: ${task.id} · project: ${projectName} · state: ${task.state} · status: ${task.status}`,
67
+ ];
68
+ if (task.due) lines.push(`due: ${task.due}`);
69
+ if (task.agent) lines.push(`assigned: @${task.agent}`);
70
+ if (task.tags?.length) lines.push(`tags: ${task.tags.map((t) => `#${t}`).join(" ")}`);
71
+ if (task.subtask_count) lines.push(`subtasks: ${task.subtask_done}/${task.subtask_count} done`);
72
+ if (task.description) lines.push("", "### Description", task.description);
73
+ // The agent prompt is instructions for whoever RUNS the task, so a summoned
74
+ // agent needs it. It is not the description and must not be read as one.
75
+ if (task.body) lines.push("", "### Agent prompt (instructions for running this task)", task.body);
76
+ return lines.join("\n");
77
+ }
78
+
79
+ /** The thread so far, oldest first, prefixed by speaker. */
80
+ function threadBlock(task, nameFor) {
81
+ if (!task.comments?.length) return "";
82
+ return [
83
+ "",
84
+ "## Comments",
85
+ ...task.comments.map((c) => `${nameFor(c.by)}: ${c.text}`),
86
+ ].join("\n");
87
+ }
88
+
89
+ function systemBlock({ me, others, taskTitle }) {
90
+ const roster = others.length ? others.map((o) => `${o.name} (@${o.slug})`).join(", ") : "(no other agents)";
91
+ return [
92
+ "## Task comments",
93
+ `You were @-mentioned in the comment thread of the task **${taskTitle}**.`,
94
+ `Reply ONLY as yourself (**${me}**). Your reply is posted as a comment on that task — it is not a chat message.`,
95
+ "**Do the work first.** You have your real tools. If the comment asks you to check, review, test or fix something, do it and report what you actually found. Do not describe what you would do.",
96
+ `**Be short.** A comment lives in a side panel next to the task. Aim for a few lines and stay under ~${REPLY_CHAR_HINT} characters. If the detail is long, put the conclusion in the comment and the detail where it belongs (a file, a PR, the task's description).`,
97
+ `**Handing work over:** the only way another agent gets a turn is writing their exact handle with an @ — ${roster}. Writing just their name does NOT reach them. Mention someone ONLY if they genuinely need to act; if you can close it yourself, cite nobody and the thread ends.`,
98
+ ].join("\n");
99
+ }
100
+
101
+ /**
102
+ * Record an agent→agent handover on the a2a ledger, so a crossing that happened
103
+ * inside a task still shows up where every other agent-to-agent exchange does.
104
+ * The work itself already ran through the real turn engine — this is the paper
105
+ * trail, not the delivery mechanism (an a2a `--deliver` reply has no tools).
106
+ */
107
+ function logHandover(p, { from, to, taskId, text, model, usage }) {
108
+ const ts = nowIso();
109
+ // Attribution rides along: a row filed without `model`/`usage` reopens in the
110
+ // viewer as "0 tok, no model", which is the regression tests/message-
111
+ // attribution.test.js exists to stop. The numbers are the summoned agent's
112
+ // own turn — the one whose reply this row mirrors.
113
+ const meta = {
114
+ task: taskId, via: "task_comment",
115
+ ...(model ? { model } : {}), ...(usage ? { usage } : {}),
116
+ };
117
+ try {
118
+ p.logMessage({
119
+ agent_slug: from, channel: CHANNELS.A2A, direction: "out",
120
+ author: from, body: text, meta: { ...meta, to, final: true }, ts,
121
+ });
122
+ p.logMessage({
123
+ agent_slug: to, channel: CHANNELS.A2A, direction: "in",
124
+ author: from, body: text, meta: { ...meta, from }, ts,
125
+ });
126
+ } catch {
127
+ // The comment is the source of truth; a missing mirror row must never fail
128
+ // the turn that produced it.
129
+ }
130
+ }
131
+
132
+ /** The real thing: one summoned agent's turn, with its own tools. */
133
+ async function runRealTurn({
134
+ p, agent, slug, from, task, participants, nameFor, projectName,
135
+ cfg, projects, plugins, registries, signal,
136
+ }) {
137
+ const modelId = await resolveAgentModel({ agent, config: cfg });
138
+ if (!modelId) throw new Error(`no model for agent ${slug}`);
139
+ const me = displayName(agent);
140
+ const others = participants.filter((x) => x.kind === "agent" && x.slug !== slug);
141
+ const system = buildAgentSystem(p, agent, {
142
+ invocation: "engine",
143
+ caller: from === OWNER_ACTOR_ID ? "user" : from,
144
+ globalConfig: cfg,
145
+ extraParts: [systemBlock({ me, others, taskTitle: task.title })],
146
+ });
147
+ const directive = from === OWNER_ACTOR_ID
148
+ ? `The owner tagged you (@${slug}) on this task. Act on the last comment, then reply.`
149
+ : `${nameFor(from)} tagged you (@${slug}) on this task. Act on the last comment, then reply.`;
150
+
151
+ const result = await runAgentTurn({
152
+ p, agent, modelId, system,
153
+ prompt: `${taskBlock(task, projectName)}${threadBlock(task, nameFor)}\n\n---\n${directive}`,
154
+ previousMessages: [],
155
+ channel: CHANNELS.WEB,
156
+ // Same reasoning as the group: the channel picks the PROMPT, but one comment
157
+ // can fan out into several of these runs, so the budget is the fan-out-aware
158
+ // one.
159
+ maxIters: groupToolIters(cfg),
160
+ tools: true,
161
+ projects, plugins, registries, config: cfg,
162
+ signal,
163
+ });
164
+ return { text: result.text || "", model: result.model || modelId, usage: result.usage };
165
+ }
166
+
167
+ /**
168
+ * Run the mention cascade for the newest comment on a task.
169
+ *
170
+ * Fire-and-forget from a route: every reply is persisted as it lands, so a
171
+ * caller that never awaits still gets the full thread on its next read.
172
+ *
173
+ * @param {object} args
174
+ * @param {object} args.p resolved project (path, storagePath, config, logMessage, id)
175
+ * @param {string} args.taskId
176
+ * @param {string[]} args.seed agent slugs the triggering comment mentioned
177
+ * @param {string} args.author who wrote the triggering comment (owner | agent slug)
178
+ * @param {AbortSignal} [args.signal]
179
+ * @param {Function} [args.runTurn]
180
+ * Injectable model call, same trick the group resolver uses: the cascade
181
+ * and its ceiling are the part worth testing, and neither needs an
182
+ * engine. Production always uses the real turn.
183
+ * @returns {Promise<Array<{slug, text}>>}
184
+ */
185
+ export async function runCommentMentions({
186
+ p, taskId, seed, author = OWNER_ACTOR_ID,
187
+ projects, plugins, registries, config, signal = null,
188
+ maxTurns = MAX_COMMENT_TURNS,
189
+ runTurn = runRealTurn,
190
+ }) {
191
+ const participants = taskParticipants(p.path);
192
+ const agents = new Map();
193
+ try {
194
+ for (const a of readAgents(p.path)) agents.set(a.slug, a);
195
+ } catch { /* no roster → nothing to summon */ }
196
+
197
+ const nameFor = (who) =>
198
+ !who || who === OWNER_ACTOR_ID ? "Owner" : (agents.get(who) ? displayName(agents.get(who)) : who);
199
+
200
+ const cfg = p.config || config;
201
+ const projectName = p.name || p.path || String(p.id);
202
+ const said = [];
203
+ const queue = (seed || []).filter((s) => agents.has(s)).map((slug) => ({ slug, from: author }));
204
+
205
+ while (queue.length && said.length < maxTurns) {
206
+ if (signal?.aborted) break;
207
+ const { slug, from } = queue.shift();
208
+ const agent = agents.get(slug);
209
+ if (!agent) continue;
210
+
211
+ // Re-read every hop: an earlier speaker's comment is already persisted, so
212
+ // the next one sees what was just said.
213
+ const task = getTask(p.storagePath, taskId);
214
+ if (!task) break;
215
+
216
+ let reply = "";
217
+ let model = null;
218
+ let usage = null;
219
+ try {
220
+ // A test's stub may hand back a bare string; the real one returns what
221
+ // answered and what it spent.
222
+ const out = await runTurn({
223
+ p, agent, slug, from, task, participants, nameFor, projectName,
224
+ cfg, projects, plugins, registries, signal,
225
+ });
226
+ if (typeof out === "string") reply = out.trim();
227
+ else { reply = (out?.text || "").trim(); model = out?.model || null; usage = out?.usage || null; }
228
+ } catch (e) {
229
+ // A failed summon is written into the thread rather than swallowed: a
230
+ // comment that says "@qa" and is met with silence looks like the feature
231
+ // is broken, which is worse than reading why it did not run.
232
+ reply = `⚠️ No pude atender la mención: ${e?.message || String(e)}`;
233
+ }
234
+
235
+ if (!reply) reply = "…";
236
+ const next = parseMentions(reply, participants, slug);
237
+ addComment(p.storagePath, taskId, { by: slug, text: reply, mentions: next });
238
+ said.push({ slug, text: reply });
239
+ // attribution-exempt: this is the live-feed SIGNAL, not a ledger insertion.
240
+ // It carries "the thread moved" and nothing else — the row it announces was
241
+ // written by addComment above, and the a2a mirror below is where the model
242
+ // and usage are recorded.
243
+ emitMessageEvent({
244
+ scope: "project", channel: CHANNELS.A2A, project_root: p.storagePath,
245
+ agent_slug: slug, direction: "out", type: "agent", thread: taskId, final: true,
246
+ ts: nowIso(),
247
+ });
248
+
249
+ for (const to of next) {
250
+ if (!agents.has(to)) continue;
251
+ if (queue.some((q) => q.slug === to)) continue;
252
+ logHandover(p, { from: slug, to, taskId, text: reply, model, usage });
253
+ queue.push({ slug: to, from: slug });
254
+ }
255
+ }
256
+
257
+ return said;
258
+ }
@@ -7,3 +7,16 @@ export function nowIso() {
7
7
  export function isoToMs(iso) {
8
8
  return iso ? Date.parse(iso) : 0;
9
9
  }
10
+
11
+ // The two shapes the dated note files use (see core/memory/dated-log.js): a
12
+ // "YYYY-MM-DD" day heading and an "HH:MM" bullet tag. UTC on purpose — the
13
+ // notebook, project memory and routine memory have always been stamped in UTC
14
+ // and they are read against each other, so a per-file timezone would make two
15
+ // notes written a minute apart land on different days.
16
+ export function dayStamp(d = new Date()) {
17
+ return d.toISOString().slice(0, 10);
18
+ }
19
+
20
+ export function hourStamp(d = new Date()) {
21
+ return d.toISOString().slice(11, 16);
22
+ }
@@ -63,7 +63,8 @@ function ttsConfig(globalConfig) {
63
63
  return globalConfig?.voice?.tts || {};
64
64
  }
65
65
 
66
- function providerConfig(globalConfig, provider) {
66
+ /** The stored config block for one engine id (custom ids included). */
67
+ export function providerConfig(globalConfig, provider) {
67
68
  const tts = ttsConfig(globalConfig);
68
69
  if (isCustomId(provider)) return tts?.custom?.[slugOf(provider)] || {};
69
70
  return tts?.[provider] || {};
@@ -100,6 +101,58 @@ export function resolveChainOrder(ttsCfg) {
100
101
  return full;
101
102
  }
102
103
 
104
+ /**
105
+ * Every engine that could handle a synthesize() call, in the order they should
106
+ * be tried. `selectTtsEngine` returns the first of these; `synthesize()` walks
107
+ * the whole list so an engine that *looks* usable but fails at request time
108
+ * (a local server that isn't running) hands off to the next one instead of
109
+ * sinking the turn.
110
+ *
111
+ * WHY THIS EXISTS. isAvailable() is a config probe, not a reachability probe —
112
+ * a custom endpoint reports itself available on the strength of having a
113
+ * base_url. That is the right call for the settings UI (it must list an engine
114
+ * the user configured even while its server is down) but it made "chain" mode
115
+ * a misnomer: selection stopped at the first *configured* engine and a failure
116
+ * there never reached the second. Falling back on the real error is both more
117
+ * accurate than any probe and free.
118
+ *
119
+ * @returns {Promise<Array<{provider, adapter, engineConfig}>>}
120
+ */
121
+ export async function resolveTtsCandidates({ globalConfig, provider }) {
122
+ const ttsCfg = ttsConfig(globalConfig);
123
+ const entry = (id) => ({
124
+ provider: id,
125
+ adapter: getTtsAdapter(id),
126
+ engineConfig: providerConfig(globalConfig, id),
127
+ });
128
+
129
+ // An explicit provider means the caller wants THAT engine — the voice
130
+ // tester, or a retry. No fallback: silently speaking in a different voice
131
+ // than the one under test would be worse than failing.
132
+ if (provider && provider !== "auto") return [entry(provider)];
133
+
134
+ if (resolveMode(ttsCfg) === "single") {
135
+ const id = ttsCfg?.provider;
136
+ if (id && id !== "auto") return [entry(id)];
137
+ // Misconfigured single mode (no concrete provider) → fall through to chain.
138
+ }
139
+
140
+ const out = [];
141
+ for (const id of resolveChainOrder(ttsCfg)) {
142
+ if (id !== "mock" && !isEnabled(ttsCfg, id)) continue;
143
+ const cfg = providerConfig(globalConfig, id);
144
+ try {
145
+ if (id !== "mock" && !(await getTtsAdapter(id).isAvailable(cfg, globalConfig?.engines))) continue;
146
+ } catch { continue; }
147
+ out.push(entry(id));
148
+ }
149
+ // mock closes the list so synthesize() never throws for want of an engine.
150
+ // It returns silence, so callers that can tell the user "no voice" should
151
+ // check the returned `provider` rather than treat it as speech.
152
+ if (!out.some((c) => c.provider === "mock")) out.push(entry("mock"));
153
+ return out;
154
+ }
155
+
103
156
  /**
104
157
  * Resolve which engine should handle a synthesize() call.
105
158
  * Returns { provider, adapter, engineConfig }.
@@ -8,9 +8,15 @@
8
8
  // OpenAI-compatible speech server (e.g. a Qwen3-TTS / QVox daemon at
9
9
  // http://127.0.0.1:5111/v1). When base_url is set we additionally forward the
10
10
  // non-OpenAI fields that server understands — `instruct` (the base voice, from
11
- // the `style` arg), `language` and `temperature`. These extras are NEVER sent
12
- // to stock OpenAI (only when base_url is present), so the standard path stays
13
- // byte-for-byte compatible.
11
+ // the `style` arg), `language`, `clone`/`ref_text` and `temperature`. These
12
+ // extras are NEVER sent to stock OpenAI (only when base_url is present), so the
13
+ // standard path stays byte-for-byte compatible.
14
+ //
15
+ // Which of `clone` / `voice` / `instruct` is set decides HOW the server speaks,
16
+ // and they are not additive — QVox reads them in that order and the first one
17
+ // wins. A named speaker carries its own accent, so picking one leaves `style`
18
+ // describing only the delivery; only a cloned reference can carry an accent no
19
+ // preset speaker has.
14
20
 
15
21
  import fs from "node:fs";
16
22
  import path from "node:path";
@@ -59,6 +65,34 @@ export default {
59
65
  return Boolean(config.base_url) || Boolean(getKey(config, parentEnginesCfg));
60
66
  },
61
67
 
68
+ // Only local endpoints have anything to warm. A cloud API is as ready as it
69
+ // will ever be, and a hosted server is not ours to spin up — so a stock
70
+ // OpenAI config skips instead of sending a pointless request.
71
+ async warmup(config = {}) {
72
+ if (!config.base_url) return { ok: true, skipped: "not a local endpoint" };
73
+ const url = config.base_url.replace(/\/+$/, "") + "/warmup";
74
+ const res = await fetch(url, {
75
+ method: "POST",
76
+ headers: {
77
+ "content-type": "application/json",
78
+ ...(config.api_key ? { authorization: `Bearer ${config.api_key}` } : {}),
79
+ },
80
+ // Say which voice, so the endpoint warms the model that will answer
81
+ // rather than a sibling of it — these are different multi-gigabyte
82
+ // checkpoints, and warming one leaves the other exactly as cold. The
83
+ // clone is named for the same reason: a cloned reference is served by a
84
+ // third checkpoint again, so a warmup that only ever mentions `voice`
85
+ // warms nothing the next request will use.
86
+ body: JSON.stringify({
87
+ ...(config.voice ? { voice: config.voice } : {}),
88
+ ...(config.clone ? { clone: config.clone, ref_text: config.ref_text } : {}),
89
+ }),
90
+ signal: AbortSignal.timeout(120_000),
91
+ });
92
+ if (!res.ok) throw new Error(`warmup ${res.status}`);
93
+ return await res.json().catch(() => ({ ok: true }));
94
+ },
95
+
62
96
  async synthesize({ text, voice, language, style, outDir, config = {}, format, signal, parentEnginesCfg }) {
63
97
  if (!text) throw new Error("openai-tts: empty text");
64
98
  const isCustom = Boolean(config.base_url);
@@ -79,7 +113,19 @@ export default {
79
113
  if (isCustom) {
80
114
  // QVox / Qwen3-TTS extras (ignored by stock OpenAI, so only sent here).
81
115
  if (styleHint) body.instruct = styleHint;
82
- if (language) body.language = language;
116
+ // A caller that names a language wins; otherwise the configured one.
117
+ // Nothing in the speaking path (Telegram voice notes, the desktop, a
118
+ // routine) passes one, so without this the server was left to guess from
119
+ // its own default — right by luck here, wrong for anyone whose local
120
+ // server defaults elsewhere.
121
+ const lang = language || config.language;
122
+ if (lang && lang !== "auto") body.language = lang;
123
+ // Voice cloning: the endpoint reads the reference itself, so this is a
124
+ // path on ITS filesystem, not ours. `ref_text` is what the recording
125
+ // says — optional, and worth setting: telling the model that measured
126
+ // 15.8 chars/s against 12.9 without it.
127
+ if (config.clone) body.clone = config.clone;
128
+ if (config.ref_text) body.ref_text = config.ref_text;
83
129
  if (config.temperature != null) body.temperature = config.temperature;
84
130
  } else if (styleHint && /gpt-4o.*tts/i.test(model || "")) {
85
131
  // Stock OpenAI's newer TTS models accept a natural-language `instructions`.
@@ -0,0 +1,67 @@
1
+ // Re-encode synthesized speech into the one container Telegram will render as
2
+ // a VOICE NOTE.
3
+ //
4
+ // This is not a cosmetic detail. `sendVoice` accepts OGG/Opus and nothing
5
+ // else; hand it the WAV that Gemini TTS returns (or the MP3 from OpenAI /
6
+ // ElevenLabs) and the Bot API either rejects the upload or Telegram shows a
7
+ // grey file attachment — which on a phone mounted in a car is unplayable
8
+ // without taking your hand off the wheel. So: one conversion, in one place,
9
+ // and callers that need a voice note ask for one instead of guessing at
10
+ // formats.
11
+ //
12
+ // ffmpeg is the same dependency the transcription path already relies on, and
13
+ // it is reached through envWithPath() for the same reason: a daemon booted by
14
+ // launchd inherits a PATH with no Homebrew in it, and every conversion would
15
+ // otherwise fail with ENOENT (see core/util/path-env.js).
16
+ import fs from "node:fs";
17
+ import path from "node:path";
18
+ import { execFile } from "node:child_process";
19
+ import { promisify } from "node:util";
20
+ import { envWithPath } from "#core/util/path-env.js";
21
+ import { ensureTtsTmpDir } from "./tts.js";
22
+
23
+ const run = promisify(execFile);
24
+
25
+ // Telegram voice notes are mono and low-bitrate by convention; Opus at 32 kbps
26
+ // is transparent for speech and keeps a 20-second note around 80 KB.
27
+ const VOICE_BITRATE = "32k";
28
+ const VOICE_SAMPLE_RATE = "48000";
29
+
30
+ /**
31
+ * Convert `sourcePath` to OGG/Opus and return the new path. A file that is
32
+ * already .ogg is returned untouched — every engine that emits Opus natively
33
+ * (ElevenLabs' ogg formats, Gemini when it answers audio/ogg) skips the
34
+ * re-encode entirely.
35
+ *
36
+ * Returns `null` when ffmpeg is unavailable or fails, so callers can fall back
37
+ * to plain text rather than dropping the reply. A voice note is an upgrade;
38
+ * losing the message because the upgrade failed is not acceptable.
39
+ */
40
+ export async function toVoiceNote(sourcePath) {
41
+ if (!sourcePath || !fs.existsSync(sourcePath)) return null;
42
+ if (path.extname(sourcePath).toLowerCase() === ".ogg") return sourcePath;
43
+
44
+ const target = path.join(
45
+ ensureTtsTmpDir(),
46
+ `${path.basename(sourcePath, path.extname(sourcePath))}.ogg`
47
+ );
48
+ try {
49
+ await run(
50
+ "ffmpeg",
51
+ [
52
+ "-hide_banner", "-loglevel", "error", "-y",
53
+ "-i", sourcePath,
54
+ "-vn",
55
+ "-ac", "1",
56
+ "-ar", VOICE_SAMPLE_RATE,
57
+ "-c:a", "libopus",
58
+ "-b:a", VOICE_BITRATE,
59
+ target,
60
+ ],
61
+ { env: envWithPath(), timeout: 60_000 }
62
+ );
63
+ } catch {
64
+ return null;
65
+ }
66
+ return fs.existsSync(target) && fs.statSync(target).size > 0 ? target : null;
67
+ }
@@ -0,0 +1,43 @@
1
+ // Text that is about to be spoken aloud, cleaned of what a voice cannot say.
2
+ //
3
+ // A TTS model has to produce *something* for every token it is given, and an
4
+ // emoji has no pronunciation — so it improvises, which comes out as a couple of
5
+ // seconds of humming in the middle of a sentence. Roby ending a reply with 👍
6
+ // is pleasant to read and unlistenable.
7
+ //
8
+ // This is deliberately about speech, not about display: a channel that shows
9
+ // the text as well decides separately whether the reader keeps the emoji.
10
+
11
+ // Pictographs, plus the pieces that glue them together: variation selectors,
12
+ // zero-width joiners, skin-tone modifiers, keycap marks and the regional
13
+ // indicators that spell out flags.
14
+ // Written as alternatives rather than one character class on purpose: the
15
+ // joiners below are combining marks, and a class that mixes them with the
16
+ // pictographs they attach to is both misleading to read and flagged by lint.
17
+ const EMOJI = new RegExp(
18
+ [
19
+ "\\p{Extended_Pictographic}", // the pictographs themselves
20
+ "\\p{Regional_Indicator}", // the letter pairs that spell out flags
21
+ "[\\u{1F3FB}-\\u{1F3FF}]", // skin-tone modifiers
22
+ "\\uFE0F", // variation selector (the "render as emoji" flag)
23
+ "\\u200D", // zero-width joiner, glues 👨‍👩‍👧 together
24
+ "\\u20E3", // the enclosing box of a keycap like 1️⃣
25
+ ].join("|"),
26
+ "gu"
27
+ );
28
+
29
+ /**
30
+ * Remove emoji from text meant to be spoken, and tidy the space they leave.
31
+ * Returns "" when nothing sayable is left, which lets a caller drop a segment
32
+ * that was only an emoji instead of synthesizing a shrug.
33
+ */
34
+ export function stripEmoji(text) {
35
+ return String(text ?? "")
36
+ .replace(EMOJI, "")
37
+ // An emoji sitting between words leaves a double space; one that trailed a
38
+ // sentence leaves a space before the period.
39
+ .replace(/[ \t]{2,}/g, " ")
40
+ .replace(/[ \t]+([,.;:!?…])/g, "$1")
41
+ .replace(/^[ \t]+|[ \t]+$/gm, "")
42
+ .trim();
43
+ }