@clawpump/claw-agent 0.1.4 → 0.1.6

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 (1214) hide show
  1. package/agent/.dockerignore +67 -0
  2. package/agent/.envrc +1 -1
  3. package/agent/.gitattributes +8 -0
  4. package/agent/AGENTS.md +216 -4
  5. package/agent/CONTRIBUTING.md +46 -8
  6. package/agent/Dockerfile +78 -35
  7. package/agent/MANIFEST.in +2 -0
  8. package/agent/README.md +12 -5
  9. package/agent/README.ur-pk.md +261 -0
  10. package/agent/README.zh-CN.md +11 -8
  11. package/agent/SECURITY.md +5 -4
  12. package/agent/acp_adapter/provenance.py +127 -0
  13. package/agent/acp_adapter/server.py +112 -5
  14. package/agent/acp_adapter/session.py +1 -6
  15. package/agent/acp_registry/agent.json +2 -2
  16. package/agent/agent/account_usage.py +313 -1
  17. package/agent/agent/agent_init.py +140 -37
  18. package/agent/agent/agent_runtime_helpers.py +342 -83
  19. package/agent/agent/anthropic_adapter.py +320 -33
  20. package/agent/agent/auxiliary_client.py +525 -105
  21. package/agent/agent/background_review.py +157 -19
  22. package/agent/agent/bedrock_adapter.py +71 -6
  23. package/agent/agent/billing_view.py +295 -0
  24. package/agent/agent/chat_completion_helpers.py +229 -4
  25. package/agent/agent/codex_responses_adapter.py +86 -10
  26. package/agent/agent/codex_runtime.py +153 -1
  27. package/agent/agent/coding_context.py +738 -0
  28. package/agent/agent/context_compressor.py +392 -44
  29. package/agent/agent/context_references.py +34 -1
  30. package/agent/agent/conversation_compression.py +159 -22
  31. package/agent/agent/conversation_loop.py +643 -908
  32. package/agent/agent/copilot_acp_client.py +4 -11
  33. package/agent/agent/credential_pool.py +5 -3
  34. package/agent/agent/credits_tracker.py +794 -0
  35. package/agent/agent/curator.py +91 -18
  36. package/agent/agent/curator_backup.py +26 -10
  37. package/agent/agent/display.py +42 -1
  38. package/agent/agent/error_classifier.py +52 -3
  39. package/agent/agent/errors.py +3 -0
  40. package/agent/agent/file_safety.py +0 -17
  41. package/agent/agent/gemini_native_adapter.py +31 -1
  42. package/agent/agent/i18n.py +48 -4
  43. package/agent/agent/image_gen_provider.py +74 -5
  44. package/agent/agent/image_routing.py +29 -0
  45. package/agent/agent/insights.py +8 -17
  46. package/agent/agent/lsp/install.py +3 -0
  47. package/agent/agent/memory_manager.py +326 -31
  48. package/agent/agent/message_content.py +50 -0
  49. package/agent/agent/model_metadata.py +214 -3
  50. package/agent/agent/moonshot_schema.py +8 -1
  51. package/agent/agent/onboarding.py +60 -0
  52. package/agent/agent/prompt_builder.py +327 -37
  53. package/agent/agent/redact.py +1 -0
  54. package/agent/agent/runtime_cwd.py +34 -5
  55. package/agent/agent/secret_scope.py +205 -0
  56. package/agent/agent/secret_sources/bitwarden.py +34 -2
  57. package/agent/agent/skill_commands.py +90 -1
  58. package/agent/agent/skill_preprocessing.py +1 -0
  59. package/agent/agent/skill_utils.py +209 -36
  60. package/agent/agent/ssl_guard.py +94 -0
  61. package/agent/agent/system_prompt.py +133 -5
  62. package/agent/agent/tool_executor.py +496 -70
  63. package/agent/agent/transports/anthropic.py +83 -21
  64. package/agent/agent/transports/chat_completions.py +94 -5
  65. package/agent/agent/transports/codex.py +67 -2
  66. package/agent/agent/transports/codex_app_server.py +1 -0
  67. package/agent/agent/transports/codex_app_server_session.py +30 -0
  68. package/agent/agent/transports/types.py +12 -0
  69. package/agent/agent/turn_context.py +408 -0
  70. package/agent/agent/turn_finalizer.py +428 -0
  71. package/agent/agent/turn_retry_state.py +68 -0
  72. package/agent/agent/usage_pricing.py +3 -0
  73. package/agent/apps/bootstrap-installer/package.json +6 -5
  74. package/agent/apps/bootstrap-installer/src/routes/failure.tsx +12 -5
  75. package/agent/apps/bootstrap-installer/src/routes/progress.tsx +1 -3
  76. package/agent/apps/bootstrap-installer/src/store.ts +3 -2
  77. package/agent/apps/bootstrap-installer/src-tauri/src/bootstrap.rs +172 -7
  78. package/agent/apps/bootstrap-installer/src-tauri/src/events.rs +14 -1
  79. package/agent/apps/bootstrap-installer/src-tauri/src/paths.rs +29 -0
  80. package/agent/apps/bootstrap-installer/src-tauri/src/powershell.rs +93 -3
  81. package/agent/apps/bootstrap-installer/src-tauri/src/update.rs +695 -39
  82. package/agent/apps/bootstrap-installer/tsconfig.json +3 -4
  83. package/agent/apps/desktop/DESIGN.md +167 -0
  84. package/agent/apps/desktop/README.md +20 -16
  85. package/agent/apps/desktop/assets/icon.icns +0 -0
  86. package/agent/apps/desktop/assets/icon.ico +0 -0
  87. package/agent/apps/desktop/assets/icon.png +0 -0
  88. package/agent/apps/desktop/electron/backend-env.cjs +112 -0
  89. package/agent/apps/desktop/electron/backend-env.test.cjs +111 -0
  90. package/agent/apps/desktop/electron/backend-probes.test.cjs +3 -1
  91. package/agent/apps/desktop/electron/backend-ready.cjs +66 -0
  92. package/agent/apps/desktop/electron/bootstrap-platform.cjs +52 -0
  93. package/agent/apps/desktop/electron/bootstrap-platform.test.cjs +59 -1
  94. package/agent/apps/desktop/electron/bootstrap-runner.cjs +176 -38
  95. package/agent/apps/desktop/electron/bootstrap-runner.test.cjs +112 -1
  96. package/agent/apps/desktop/electron/connection-config.cjs +288 -0
  97. package/agent/apps/desktop/electron/connection-config.test.cjs +396 -0
  98. package/agent/apps/desktop/electron/dashboard-token.cjs +99 -0
  99. package/agent/apps/desktop/electron/dashboard-token.test.cjs +142 -0
  100. package/agent/apps/desktop/electron/desktop-uninstall.cjs +232 -0
  101. package/agent/apps/desktop/electron/desktop-uninstall.test.cjs +246 -0
  102. package/agent/apps/desktop/electron/entitlements.mac.inherit.plist +2 -0
  103. package/agent/apps/desktop/electron/fs-read-dir.cjs +109 -0
  104. package/agent/apps/desktop/electron/fs-read-dir.test.cjs +364 -0
  105. package/agent/apps/desktop/electron/gateway-ws-probe.cjs +188 -0
  106. package/agent/apps/desktop/electron/gateway-ws-probe.test.cjs +122 -0
  107. package/agent/apps/desktop/electron/git-root.cjs +54 -0
  108. package/agent/apps/desktop/electron/git-root.test.cjs +40 -0
  109. package/agent/apps/desktop/electron/git-worktrees.cjs +174 -0
  110. package/agent/apps/desktop/electron/hardening.cjs +123 -28
  111. package/agent/apps/desktop/electron/hardening.test.cjs +163 -0
  112. package/agent/apps/desktop/electron/main.cjs +3121 -331
  113. package/agent/apps/desktop/electron/oauth-net-request.cjs +20 -0
  114. package/agent/apps/desktop/electron/oauth-net-request.test.cjs +34 -0
  115. package/agent/apps/desktop/electron/preload.cjs +52 -2
  116. package/agent/apps/desktop/electron/session-windows.cjs +124 -0
  117. package/agent/apps/desktop/electron/session-windows.test.cjs +199 -0
  118. package/agent/apps/desktop/electron/update-rebuild.cjs +29 -0
  119. package/agent/apps/desktop/electron/update-rebuild.test.cjs +55 -0
  120. package/agent/apps/desktop/electron/update-remote.cjs +56 -0
  121. package/agent/apps/desktop/electron/update-remote.test.cjs +78 -0
  122. package/agent/apps/desktop/electron/vscode-marketplace.cjs +331 -0
  123. package/agent/apps/desktop/electron/vscode-marketplace.test.cjs +113 -0
  124. package/agent/apps/desktop/electron/windows-child-process.test.cjs +57 -0
  125. package/agent/apps/desktop/electron/windows-user-env.cjs +76 -0
  126. package/agent/apps/desktop/electron/windows-user-env.test.cjs +90 -0
  127. package/agent/apps/desktop/electron/workspace-cwd.cjs +38 -0
  128. package/agent/apps/desktop/electron/workspace-cwd.test.cjs +45 -0
  129. package/agent/apps/desktop/eslint.config.mjs +0 -3
  130. package/agent/apps/desktop/index.html +27 -2
  131. package/agent/apps/desktop/package.json +31 -11
  132. package/agent/apps/desktop/pr-assets/session-source-folders.png +0 -0
  133. package/agent/apps/desktop/public/apple-touch-icon.png +0 -0
  134. package/agent/apps/desktop/public/nous-girl.jpg +0 -0
  135. package/agent/apps/desktop/scripts/assert-dist-built.cjs +70 -0
  136. package/agent/apps/desktop/scripts/assert-dist-built.test.cjs +84 -0
  137. package/agent/apps/desktop/scripts/before-pack.cjs +78 -0
  138. package/agent/apps/desktop/scripts/before-pack.test.cjs +53 -0
  139. package/agent/apps/desktop/scripts/diag-scroll-reset.mjs +229 -0
  140. package/agent/apps/desktop/scripts/patch-electron-builder-mac-binary.cjs +64 -0
  141. package/agent/apps/desktop/scripts/run-electron-builder.cjs +57 -0
  142. package/agent/apps/desktop/src/app/agents/index.tsx +53 -45
  143. package/agent/apps/desktop/src/app/artifacts/index.tsx +102 -83
  144. package/agent/apps/desktop/src/app/chat/chat-drop-overlay.tsx +29 -8
  145. package/agent/apps/desktop/src/app/chat/chat-swap-overlay.tsx +47 -0
  146. package/agent/apps/desktop/src/app/chat/composer/attachments.tsx +81 -45
  147. package/agent/apps/desktop/src/app/chat/composer/completion-drawer.tsx +13 -24
  148. package/agent/apps/desktop/src/app/chat/composer/context-menu.tsx +138 -88
  149. package/agent/apps/desktop/src/app/chat/composer/controls.tsx +138 -90
  150. package/agent/apps/desktop/src/app/chat/composer/enter-submit-dom-race.test.tsx +218 -0
  151. package/agent/apps/desktop/src/app/chat/composer/focus.ts +32 -0
  152. package/agent/apps/desktop/src/app/chat/composer/help-hint.tsx +38 -25
  153. package/agent/apps/desktop/src/app/chat/composer/hooks/use-live-completion-adapter.ts +7 -0
  154. package/agent/apps/desktop/src/app/chat/composer/hooks/use-mic-recorder.ts +22 -12
  155. package/agent/apps/desktop/src/app/chat/composer/hooks/use-slash-completions.ts +142 -14
  156. package/agent/apps/desktop/src/app/chat/composer/hooks/use-voice-conversation.ts +14 -11
  157. package/agent/apps/desktop/src/app/chat/composer/hooks/use-voice-recorder.ts +9 -6
  158. package/agent/apps/desktop/src/app/chat/composer/ime-composition-dom-repro.test.tsx +108 -0
  159. package/agent/apps/desktop/src/app/chat/composer/index.tsx +930 -180
  160. package/agent/apps/desktop/src/app/chat/composer/inline-refs.ts +136 -32
  161. package/agent/apps/desktop/src/app/chat/composer/model-pill.tsx +86 -0
  162. package/agent/apps/desktop/src/app/chat/composer/queue-panel.tsx +54 -75
  163. package/agent/apps/desktop/src/app/chat/composer/rich-editor.test.ts +117 -1
  164. package/agent/apps/desktop/src/app/chat/composer/rich-editor.ts +117 -6
  165. package/agent/apps/desktop/src/app/chat/composer/slash-nav-dom-repro.test.tsx +186 -0
  166. package/agent/apps/desktop/src/app/chat/composer/status-stack/index.tsx +202 -0
  167. package/agent/apps/desktop/src/app/chat/composer/status-stack/status-row.tsx +155 -0
  168. package/agent/apps/desktop/src/app/chat/composer/text-utils.test.ts +104 -0
  169. package/agent/apps/desktop/src/app/chat/composer/text-utils.ts +37 -9
  170. package/agent/apps/desktop/src/app/chat/composer/trigger-popover.test.tsx +50 -0
  171. package/agent/apps/desktop/src/app/chat/composer/trigger-popover.tsx +105 -40
  172. package/agent/apps/desktop/src/app/chat/composer/types.ts +5 -0
  173. package/agent/apps/desktop/src/app/chat/composer/url-dialog.tsx +11 -15
  174. package/agent/apps/desktop/src/app/chat/composer/voice-activity.tsx +8 -4
  175. package/agent/apps/desktop/src/app/chat/hooks/use-composer-actions.test.ts +57 -0
  176. package/agent/apps/desktop/src/app/chat/hooks/use-composer-actions.ts +70 -16
  177. package/agent/apps/desktop/src/app/chat/hooks/use-file-drop-zone.ts +52 -16
  178. package/agent/apps/desktop/src/app/chat/index.tsx +234 -81
  179. package/agent/apps/desktop/src/app/chat/perf-probe.tsx +69 -21
  180. package/agent/apps/desktop/src/app/chat/right-rail/preview-console.tsx +44 -40
  181. package/agent/apps/desktop/src/app/chat/right-rail/preview-file.tsx +71 -25
  182. package/agent/apps/desktop/src/app/chat/right-rail/preview-pane.test.tsx +40 -1
  183. package/agent/apps/desktop/src/app/chat/right-rail/preview-pane.tsx +55 -53
  184. package/agent/apps/desktop/src/app/chat/right-rail/preview.tsx +35 -17
  185. package/agent/apps/desktop/src/app/chat/scroll-to-bottom-button.test.tsx +67 -0
  186. package/agent/apps/desktop/src/app/chat/scroll-to-bottom-button.tsx +74 -0
  187. package/agent/apps/desktop/src/app/chat/sidebar/cron-jobs-section.tsx +356 -0
  188. package/agent/apps/desktop/src/app/chat/sidebar/index.tsx +1189 -364
  189. package/agent/apps/desktop/src/app/chat/sidebar/load-more-row.tsx +30 -0
  190. package/agent/apps/desktop/src/app/chat/sidebar/order.test.ts +21 -0
  191. package/agent/apps/desktop/src/app/chat/sidebar/order.ts +17 -0
  192. package/agent/apps/desktop/src/app/chat/sidebar/profile-switcher.tsx +524 -0
  193. package/agent/apps/desktop/src/app/chat/sidebar/session-actions-menu.tsx +80 -45
  194. package/agent/apps/desktop/src/app/chat/sidebar/session-row.tsx +120 -25
  195. package/agent/apps/desktop/src/app/chat/sidebar/virtual-session-list.tsx +7 -13
  196. package/agent/apps/desktop/src/app/chat/sidebar/workspace-groups.test.ts +149 -0
  197. package/agent/apps/desktop/src/app/chat/sidebar/workspace-groups.ts +326 -0
  198. package/agent/apps/desktop/src/app/chat/thread-loading.ts +7 -2
  199. package/agent/apps/desktop/src/app/command-center/index.tsx +320 -581
  200. package/agent/apps/desktop/src/app/command-palette/index.tsx +681 -0
  201. package/agent/apps/desktop/src/app/command-palette/marketplace-theme-page.tsx +157 -0
  202. package/agent/apps/desktop/src/app/cron/index.tsx +392 -324
  203. package/agent/apps/desktop/src/app/cron/job-state.ts +29 -0
  204. package/agent/apps/desktop/src/app/desktop-controller.tsx +618 -123
  205. package/agent/apps/desktop/src/app/floating-hud.ts +22 -0
  206. package/agent/apps/desktop/src/app/gateway/hooks/use-gateway-boot.test.tsx +265 -0
  207. package/agent/apps/desktop/src/app/gateway/hooks/use-gateway-boot.ts +260 -14
  208. package/agent/apps/desktop/src/app/gateway/hooks/use-gateway-request.ts +48 -4
  209. package/agent/apps/desktop/src/app/hooks/use-keybinds.ts +270 -0
  210. package/agent/apps/desktop/src/app/hooks/use-refresh-hotkey.ts +45 -0
  211. package/agent/apps/desktop/src/app/layout-constants.ts +19 -0
  212. package/agent/apps/desktop/src/app/messaging/index.tsx +136 -241
  213. package/agent/apps/desktop/src/app/messaging/platform-icon.tsx +95 -0
  214. package/agent/apps/desktop/src/app/model-visibility-overlay.tsx +31 -0
  215. package/agent/apps/desktop/src/app/overlays/overlay-search-input.tsx +18 -62
  216. package/agent/apps/desktop/src/app/overlays/overlay-split-layout.tsx +59 -7
  217. package/agent/apps/desktop/src/app/overlays/overlay-view.tsx +9 -5
  218. package/agent/apps/desktop/src/app/page-search-shell.tsx +42 -20
  219. package/agent/apps/desktop/src/app/profiles/create-profile-dialog.tsx +165 -0
  220. package/agent/apps/desktop/src/app/profiles/delete-profile-dialog.tsx +65 -0
  221. package/agent/apps/desktop/src/app/profiles/index.tsx +174 -199
  222. package/agent/apps/desktop/src/app/profiles/rename-profile-dialog.tsx +125 -0
  223. package/agent/apps/desktop/src/app/right-sidebar/files/dnd-manager.ts +27 -0
  224. package/agent/apps/desktop/src/app/right-sidebar/files/ipc.test.ts +100 -0
  225. package/agent/apps/desktop/src/app/right-sidebar/files/ipc.ts +12 -18
  226. package/agent/apps/desktop/src/app/right-sidebar/files/remote-picker.tsx +177 -0
  227. package/agent/apps/desktop/src/app/right-sidebar/files/tree.tsx +35 -21
  228. package/agent/apps/desktop/src/app/right-sidebar/files/use-project-tree.test.ts +75 -3
  229. package/agent/apps/desktop/src/app/right-sidebar/files/use-project-tree.ts +152 -5
  230. package/agent/apps/desktop/src/app/right-sidebar/index.test.tsx +75 -0
  231. package/agent/apps/desktop/src/app/right-sidebar/index.tsx +166 -129
  232. package/agent/apps/desktop/src/app/right-sidebar/store.ts +19 -4
  233. package/agent/apps/desktop/src/app/right-sidebar/terminal/buffer.ts +65 -0
  234. package/agent/apps/desktop/src/app/right-sidebar/terminal/index.tsx +29 -34
  235. package/agent/apps/desktop/src/app/right-sidebar/terminal/persistent.tsx +18 -6
  236. package/agent/apps/desktop/src/app/right-sidebar/terminal/selection.ts +93 -32
  237. package/agent/apps/desktop/src/app/right-sidebar/terminal/use-terminal-session.ts +381 -119
  238. package/agent/apps/desktop/src/app/routes.ts +9 -0
  239. package/agent/apps/desktop/src/app/session/hooks/use-cwd-actions.ts +17 -7
  240. package/agent/apps/desktop/src/app/session/hooks/use-message-stream.ts +365 -47
  241. package/agent/apps/desktop/src/app/session/hooks/use-model-controls.test.tsx +198 -0
  242. package/agent/apps/desktop/src/app/session/hooks/use-model-controls.ts +70 -34
  243. package/agent/apps/desktop/src/app/session/hooks/use-prompt-actions.test.tsx +1061 -0
  244. package/agent/apps/desktop/src/app/session/hooks/use-prompt-actions.ts +1143 -165
  245. package/agent/apps/desktop/src/app/session/hooks/use-route-resume.test.tsx +341 -2
  246. package/agent/apps/desktop/src/app/session/hooks/use-route-resume.ts +176 -5
  247. package/agent/apps/desktop/src/app/session/hooks/use-session-actions.test.tsx +259 -0
  248. package/agent/apps/desktop/src/app/session/hooks/use-session-actions.ts +452 -149
  249. package/agent/apps/desktop/src/app/session/hooks/use-session-state-cache.test.tsx +327 -0
  250. package/agent/apps/desktop/src/app/session/hooks/use-session-state-cache.ts +133 -4
  251. package/agent/apps/desktop/src/app/session-picker-overlay.tsx +32 -0
  252. package/agent/apps/desktop/src/app/session-switcher.tsx +107 -0
  253. package/agent/apps/desktop/src/app/settings/about-settings.tsx +45 -36
  254. package/agent/apps/desktop/src/app/settings/appearance-settings.tsx +243 -162
  255. package/agent/apps/desktop/src/app/settings/config-settings.tsx +86 -66
  256. package/agent/apps/desktop/src/app/settings/constants.ts +459 -122
  257. package/agent/apps/desktop/src/app/settings/credential-key-ui.tsx +373 -0
  258. package/agent/apps/desktop/src/app/settings/env-credentials.tsx +198 -0
  259. package/agent/apps/desktop/src/app/settings/env-var-actions-menu.tsx +136 -0
  260. package/agent/apps/desktop/src/app/settings/field-copy.ts +56 -0
  261. package/agent/apps/desktop/src/app/settings/gateway-settings.tsx +385 -72
  262. package/agent/apps/desktop/src/app/settings/helpers.test.ts +156 -1
  263. package/agent/apps/desktop/src/app/settings/helpers.ts +30 -2
  264. package/agent/apps/desktop/src/app/settings/index.tsx +118 -84
  265. package/agent/apps/desktop/src/app/settings/keys-settings.tsx +62 -419
  266. package/agent/apps/desktop/src/app/settings/mcp-settings.tsx +65 -60
  267. package/agent/apps/desktop/src/app/settings/model-settings.test.tsx +129 -5
  268. package/agent/apps/desktop/src/app/settings/model-settings.tsx +370 -65
  269. package/agent/apps/desktop/src/app/settings/notifications-settings.tsx +150 -0
  270. package/agent/apps/desktop/src/app/settings/primitives.tsx +5 -11
  271. package/agent/apps/desktop/src/app/settings/provider-config-panel.test.tsx +142 -0
  272. package/agent/apps/desktop/src/app/settings/provider-config-panel.tsx +182 -0
  273. package/agent/apps/desktop/src/app/settings/providers-settings.test.tsx +171 -0
  274. package/agent/apps/desktop/src/app/settings/providers-settings.tsx +471 -0
  275. package/agent/apps/desktop/src/app/settings/sessions-settings.tsx +183 -71
  276. package/agent/apps/desktop/src/app/settings/toolset-config-panel.test.tsx +135 -1
  277. package/agent/apps/desktop/src/app/settings/toolset-config-panel.tsx +180 -57
  278. package/agent/apps/desktop/src/app/settings/types.ts +9 -6
  279. package/agent/apps/desktop/src/app/settings/uninstall-section.tsx +185 -0
  280. package/agent/apps/desktop/src/app/settings/use-deep-link-highlight.ts +60 -0
  281. package/agent/apps/desktop/src/app/shell/app-shell.tsx +59 -13
  282. package/agent/apps/desktop/src/app/shell/gateway-menu-panel.tsx +37 -32
  283. package/agent/apps/desktop/src/app/shell/hooks/use-overlay-routing.ts +6 -3
  284. package/agent/apps/desktop/src/app/shell/hooks/use-statusbar-items.tsx +212 -53
  285. package/agent/apps/desktop/src/app/shell/keybind-panel.tsx +215 -0
  286. package/agent/apps/desktop/src/app/shell/model-edit-submenu.test.tsx +84 -0
  287. package/agent/apps/desktop/src/app/shell/model-edit-submenu.tsx +244 -0
  288. package/agent/apps/desktop/src/app/shell/model-menu-panel.tsx +392 -0
  289. package/agent/apps/desktop/src/app/shell/statusbar-controls.tsx +23 -33
  290. package/agent/apps/desktop/src/app/shell/titlebar-controls.tsx +79 -95
  291. package/agent/apps/desktop/src/app/shell/titlebar.ts +8 -2
  292. package/agent/apps/desktop/src/app/skills/index.test.tsx +11 -0
  293. package/agent/apps/desktop/src/app/skills/index.tsx +79 -64
  294. package/agent/apps/desktop/src/app/types.ts +85 -0
  295. package/agent/apps/desktop/src/app/updates-overlay.tsx +110 -105
  296. package/agent/apps/desktop/src/components/assistant-ui/ansi-text.tsx +34 -0
  297. package/agent/apps/desktop/src/components/assistant-ui/block-direction.test.tsx +129 -0
  298. package/agent/apps/desktop/src/components/assistant-ui/clarify-tool.tsx +102 -81
  299. package/agent/apps/desktop/src/components/assistant-ui/directive-text.tsx +92 -15
  300. package/agent/apps/desktop/src/components/assistant-ui/markdown-text.test.ts +38 -0
  301. package/agent/apps/desktop/src/components/assistant-ui/markdown-text.tsx +304 -45
  302. package/agent/apps/desktop/src/components/assistant-ui/message-render-boundary.test.tsx +80 -0
  303. package/agent/apps/desktop/src/components/assistant-ui/message-render-boundary.tsx +48 -0
  304. package/agent/apps/desktop/src/components/assistant-ui/streaming.test.tsx +142 -90
  305. package/agent/apps/desktop/src/components/assistant-ui/thread-list.tsx +337 -0
  306. package/agent/apps/desktop/src/components/assistant-ui/thread.tsx +667 -190
  307. package/agent/apps/desktop/src/components/assistant-ui/tool-approval-group.test.tsx +299 -0
  308. package/agent/apps/desktop/src/components/assistant-ui/tool-approval.test.tsx +133 -0
  309. package/agent/apps/desktop/src/components/assistant-ui/tool-approval.tsx +239 -0
  310. package/agent/apps/desktop/src/components/assistant-ui/tool-fallback-model.test.ts +31 -0
  311. package/agent/apps/desktop/src/components/assistant-ui/tool-fallback-model.ts +152 -134
  312. package/agent/apps/desktop/src/components/assistant-ui/tool-fallback.tsx +142 -150
  313. package/agent/apps/desktop/src/components/assistant-ui/tooltip-icon-button.tsx +14 -12
  314. package/agent/apps/desktop/src/components/assistant-ui/user-message-edit.test.tsx +141 -0
  315. package/agent/apps/desktop/src/components/assistant-ui/user-message-text.tsx +152 -0
  316. package/agent/apps/desktop/src/components/boot-failure-overlay.tsx +150 -33
  317. package/agent/apps/desktop/src/components/boot-failure-reauth.test.ts +100 -0
  318. package/agent/apps/desktop/src/components/boot-failure-reauth.ts +81 -0
  319. package/agent/apps/desktop/src/components/brand-mark.tsx +19 -0
  320. package/agent/apps/desktop/src/components/chat/code-card.tsx +1 -1
  321. package/agent/apps/desktop/src/components/chat/composer-dock.ts +31 -0
  322. package/agent/apps/desktop/src/components/chat/diff-lines.tsx +1 -1
  323. package/agent/apps/desktop/src/components/chat/disclosure-row.tsx +13 -3
  324. package/agent/apps/desktop/src/components/chat/expandable-block.tsx +52 -0
  325. package/agent/apps/desktop/src/components/chat/generated-image-result.tsx +174 -0
  326. package/agent/apps/desktop/src/components/chat/image-generation-placeholder.tsx +70 -37
  327. package/agent/apps/desktop/src/components/chat/intro.tsx +8 -7
  328. package/agent/apps/desktop/src/components/chat/preview-attachment.tsx +4 -2
  329. package/agent/apps/desktop/src/components/chat/shiki-highlighter.test.ts +37 -0
  330. package/agent/apps/desktop/src/components/chat/shiki-highlighter.tsx +96 -22
  331. package/agent/apps/desktop/src/components/chat/status-row.tsx +70 -0
  332. package/agent/apps/desktop/src/components/chat/status-section.tsx +42 -0
  333. package/agent/apps/desktop/src/components/chat/terminal-output.tsx +54 -0
  334. package/agent/apps/desktop/src/components/chat/zoomable-image.tsx +70 -109
  335. package/agent/apps/desktop/src/components/desktop-install-overlay.tsx +154 -84
  336. package/agent/apps/desktop/src/components/desktop-onboarding-overlay.test.tsx +38 -8
  337. package/agent/apps/desktop/src/components/desktop-onboarding-overlay.tsx +789 -233
  338. package/agent/apps/desktop/src/components/error-boundary.tsx +77 -0
  339. package/agent/apps/desktop/src/components/gateway-connecting-overlay.test.tsx +144 -0
  340. package/agent/apps/desktop/src/components/gateway-connecting-overlay.tsx +7 -1
  341. package/agent/apps/desktop/src/components/haptics-provider.tsx +24 -0
  342. package/agent/apps/desktop/src/components/language-switcher.test.tsx +53 -0
  343. package/agent/apps/desktop/src/components/language-switcher.tsx +175 -0
  344. package/agent/apps/desktop/src/components/model-picker.tsx +42 -40
  345. package/agent/apps/desktop/src/components/model-visibility-dialog.tsx +166 -0
  346. package/agent/apps/desktop/src/components/notifications.tsx +48 -27
  347. package/agent/apps/desktop/src/components/pane-shell/index.ts +1 -1
  348. package/agent/apps/desktop/src/components/pane-shell/pane-shell.tsx +146 -9
  349. package/agent/apps/desktop/src/components/prompt-overlays.tsx +234 -0
  350. package/agent/apps/desktop/src/components/session-picker.tsx +108 -0
  351. package/agent/apps/desktop/src/components/ui/action-status.tsx +25 -0
  352. package/agent/apps/desktop/src/components/ui/badge.tsx +35 -0
  353. package/agent/apps/desktop/src/components/ui/button.tsx +37 -13
  354. package/agent/apps/desktop/src/components/ui/confirm-dialog.tsx +109 -0
  355. package/agent/apps/desktop/src/components/ui/control.ts +25 -0
  356. package/agent/apps/desktop/src/components/ui/copy-button.test.tsx +36 -0
  357. package/agent/apps/desktop/src/components/ui/copy-button.tsx +38 -27
  358. package/agent/apps/desktop/src/components/ui/dialog.tsx +39 -11
  359. package/agent/apps/desktop/src/components/ui/dropdown-menu.tsx +98 -24
  360. package/agent/apps/desktop/src/components/ui/error-state.tsx +50 -0
  361. package/agent/apps/desktop/src/components/ui/fade-text.tsx +9 -2
  362. package/agent/apps/desktop/src/components/ui/{braille-spinner.tsx → glyph-spinner.tsx} +15 -13
  363. package/agent/apps/desktop/src/components/ui/input.tsx +5 -2
  364. package/agent/apps/desktop/src/components/ui/kbd.tsx +83 -12
  365. package/agent/apps/desktop/src/components/ui/log-view.tsx +19 -0
  366. package/agent/apps/desktop/src/components/ui/pagination.tsx +12 -5
  367. package/agent/apps/desktop/src/components/ui/popover.tsx +44 -0
  368. package/agent/apps/desktop/src/components/ui/search-field.tsx +80 -0
  369. package/agent/apps/desktop/src/components/ui/segmented-control.tsx +51 -0
  370. package/agent/apps/desktop/src/components/ui/select.tsx +10 -3
  371. package/agent/apps/desktop/src/components/ui/sheet.tsx +8 -2
  372. package/agent/apps/desktop/src/components/ui/sidebar.tsx +18 -25
  373. package/agent/apps/desktop/src/components/ui/switch.tsx +38 -15
  374. package/agent/apps/desktop/src/components/ui/textarea.tsx +4 -11
  375. package/agent/apps/desktop/src/components/ui/tool-icon.tsx +65 -0
  376. package/agent/apps/desktop/src/components/ui/tooltip.tsx +31 -4
  377. package/agent/apps/desktop/src/fonts/JetBrainsMono-Bold.woff2 +0 -0
  378. package/agent/apps/desktop/src/fonts/JetBrainsMono-Italic.woff2 +0 -0
  379. package/agent/apps/desktop/src/fonts/JetBrainsMono-Regular.woff2 +0 -0
  380. package/agent/apps/desktop/src/global.d.ts +181 -4
  381. package/agent/apps/desktop/src/hermes.test.ts +60 -0
  382. package/agent/apps/desktop/src/hermes.ts +190 -13
  383. package/agent/apps/desktop/src/hooks/use-image-download.ts +85 -0
  384. package/agent/apps/desktop/src/hooks/use-resize-observer.ts +13 -4
  385. package/agent/apps/desktop/src/hooks/use-worktree-info.ts +68 -0
  386. package/agent/apps/desktop/src/i18n/catalog.ts +12 -0
  387. package/agent/apps/desktop/src/i18n/context.test.tsx +232 -0
  388. package/agent/apps/desktop/src/i18n/context.tsx +183 -0
  389. package/agent/apps/desktop/src/i18n/define-locale.ts +41 -0
  390. package/agent/apps/desktop/src/i18n/en.ts +1921 -0
  391. package/agent/apps/desktop/src/i18n/index.ts +20 -0
  392. package/agent/apps/desktop/src/i18n/ja.ts +2053 -0
  393. package/agent/apps/desktop/src/i18n/languages.test.ts +43 -0
  394. package/agent/apps/desktop/src/i18n/languages.ts +86 -0
  395. package/agent/apps/desktop/src/i18n/runtime.test.ts +75 -0
  396. package/agent/apps/desktop/src/i18n/runtime.ts +53 -0
  397. package/agent/apps/desktop/src/i18n/types.ts +1559 -0
  398. package/agent/apps/desktop/src/i18n/zh-hant.ts +1992 -0
  399. package/agent/apps/desktop/src/i18n/zh.ts +2099 -0
  400. package/agent/apps/desktop/src/lib/ansi.test.ts +123 -0
  401. package/agent/apps/desktop/src/lib/ansi.ts +186 -0
  402. package/agent/apps/desktop/src/lib/chat-messages.test.ts +79 -0
  403. package/agent/apps/desktop/src/lib/chat-messages.ts +68 -29
  404. package/agent/apps/desktop/src/lib/chat-runtime.test.ts +65 -1
  405. package/agent/apps/desktop/src/lib/chat-runtime.ts +39 -3
  406. package/agent/apps/desktop/src/lib/completion-sound.ts +519 -0
  407. package/agent/apps/desktop/src/lib/desktop-fs.test.ts +116 -0
  408. package/agent/apps/desktop/src/lib/desktop-fs.ts +113 -0
  409. package/agent/apps/desktop/src/lib/desktop-slash-commands.test.ts +89 -6
  410. package/agent/apps/desktop/src/lib/desktop-slash-commands.ts +270 -131
  411. package/agent/apps/desktop/src/lib/external-link.test.tsx +27 -0
  412. package/agent/apps/desktop/src/lib/external-link.tsx +9 -2
  413. package/agent/apps/desktop/src/lib/gateway-events.test.ts +27 -0
  414. package/agent/apps/desktop/src/lib/gateway-events.ts +16 -0
  415. package/agent/apps/desktop/src/lib/gateway-ws-url.test.ts +78 -0
  416. package/agent/apps/desktop/src/lib/gateway-ws-url.ts +91 -0
  417. package/agent/apps/desktop/src/lib/generated-images.test.ts +97 -0
  418. package/agent/apps/desktop/src/lib/generated-images.ts +116 -0
  419. package/agent/apps/desktop/src/lib/haptics.ts +17 -0
  420. package/agent/apps/desktop/src/lib/icons.ts +10 -2
  421. package/agent/apps/desktop/src/lib/keybinds/actions.ts +137 -0
  422. package/agent/apps/desktop/src/lib/keybinds/combo.test.ts +86 -0
  423. package/agent/apps/desktop/src/lib/keybinds/combo.ts +195 -0
  424. package/agent/apps/desktop/src/lib/local-preview.ts +23 -2
  425. package/agent/apps/desktop/src/lib/markdown-preprocess.ts +20 -7
  426. package/agent/apps/desktop/src/lib/media.remote.test.ts +90 -0
  427. package/agent/apps/desktop/src/lib/media.ts +40 -1
  428. package/agent/apps/desktop/src/lib/model-status-label.test.ts +59 -0
  429. package/agent/apps/desktop/src/lib/model-status-label.ts +122 -0
  430. package/agent/apps/desktop/src/lib/mutable-ref.ts +6 -0
  431. package/agent/apps/desktop/src/lib/profile-color.ts +58 -0
  432. package/agent/apps/desktop/src/lib/query-client.ts +13 -0
  433. package/agent/apps/desktop/src/lib/remend-tail.test.ts +105 -0
  434. package/agent/apps/desktop/src/lib/remend-tail.ts +108 -0
  435. package/agent/apps/desktop/src/lib/session-export.ts +6 -3
  436. package/agent/apps/desktop/src/lib/session-ids.test.ts +44 -0
  437. package/agent/apps/desktop/src/lib/session-ids.ts +26 -0
  438. package/agent/apps/desktop/src/lib/session-search.test.ts +66 -0
  439. package/agent/apps/desktop/src/lib/session-search.ts +21 -0
  440. package/agent/apps/desktop/src/lib/session-source.ts +126 -0
  441. package/agent/apps/desktop/src/lib/storage.test.ts +25 -0
  442. package/agent/apps/desktop/src/lib/storage.ts +35 -1
  443. package/agent/apps/desktop/src/lib/todos.test.ts +46 -1
  444. package/agent/apps/desktop/src/lib/todos.ts +37 -0
  445. package/agent/apps/desktop/src/lib/tool-result-summary.ts +5 -1
  446. package/agent/apps/desktop/src/lib/update-copy.test.ts +38 -0
  447. package/agent/apps/desktop/src/lib/update-copy.ts +44 -0
  448. package/agent/apps/desktop/src/lib/use-enter-animation.ts +2 -2
  449. package/agent/apps/desktop/src/lib/yolo-session.ts +50 -0
  450. package/agent/apps/desktop/src/main.tsx +19 -19
  451. package/agent/apps/desktop/src/store/boot.ts +4 -3
  452. package/agent/apps/desktop/src/store/clarify.test.ts +81 -0
  453. package/agent/apps/desktop/src/store/clarify.ts +50 -13
  454. package/agent/apps/desktop/src/store/command-palette.ts +20 -0
  455. package/agent/apps/desktop/src/store/compaction.test.ts +53 -0
  456. package/agent/apps/desktop/src/store/compaction.ts +38 -0
  457. package/agent/apps/desktop/src/store/completion-sound.ts +32 -0
  458. package/agent/apps/desktop/src/store/composer-input-history.test.ts +147 -0
  459. package/agent/apps/desktop/src/store/composer-input-history.ts +158 -0
  460. package/agent/apps/desktop/src/store/composer-queue.test.ts +68 -0
  461. package/agent/apps/desktop/src/store/composer-queue.ts +76 -0
  462. package/agent/apps/desktop/src/store/composer-status.test.ts +99 -0
  463. package/agent/apps/desktop/src/store/composer-status.ts +277 -0
  464. package/agent/apps/desktop/src/store/composer.test.ts +106 -0
  465. package/agent/apps/desktop/src/store/composer.ts +116 -0
  466. package/agent/apps/desktop/src/store/cron.ts +19 -0
  467. package/agent/apps/desktop/src/store/gateway.ts +280 -6
  468. package/agent/apps/desktop/src/store/keybinds.ts +143 -0
  469. package/agent/apps/desktop/src/store/layout.ts +107 -9
  470. package/agent/apps/desktop/src/store/model-presets.test.ts +51 -0
  471. package/agent/apps/desktop/src/store/model-presets.ts +86 -0
  472. package/agent/apps/desktop/src/store/model-visibility.test.ts +99 -0
  473. package/agent/apps/desktop/src/store/model-visibility.ts +161 -0
  474. package/agent/apps/desktop/src/store/native-notifications.test.ts +192 -0
  475. package/agent/apps/desktop/src/store/native-notifications.ts +203 -0
  476. package/agent/apps/desktop/src/store/notifications.ts +10 -7
  477. package/agent/apps/desktop/src/store/onboarding.test.ts +271 -1
  478. package/agent/apps/desktop/src/store/onboarding.ts +268 -38
  479. package/agent/apps/desktop/src/store/preview.ts +10 -1
  480. package/agent/apps/desktop/src/store/profile.test.ts +89 -0
  481. package/agent/apps/desktop/src/store/profile.ts +395 -0
  482. package/agent/apps/desktop/src/store/prompts.test.ts +127 -0
  483. package/agent/apps/desktop/src/store/prompts.ts +117 -0
  484. package/agent/apps/desktop/src/store/session-switcher.test.ts +115 -0
  485. package/agent/apps/desktop/src/store/session-switcher.ts +128 -0
  486. package/agent/apps/desktop/src/store/session-sync.ts +25 -0
  487. package/agent/apps/desktop/src/store/session.test.ts +268 -2
  488. package/agent/apps/desktop/src/store/session.ts +392 -18
  489. package/agent/apps/desktop/src/store/subagents.ts +3 -0
  490. package/agent/apps/desktop/src/store/system-actions.ts +48 -0
  491. package/agent/apps/desktop/src/store/thread-scroll.ts +58 -5
  492. package/agent/apps/desktop/src/store/todos.test.ts +47 -0
  493. package/agent/apps/desktop/src/store/todos.ts +64 -0
  494. package/agent/apps/desktop/src/store/tool-dismiss.ts +45 -0
  495. package/agent/apps/desktop/src/store/translucency.ts +38 -0
  496. package/agent/apps/desktop/src/store/updates.test.ts +187 -2
  497. package/agent/apps/desktop/src/store/updates.ts +268 -18
  498. package/agent/apps/desktop/src/store/windows.test.ts +143 -0
  499. package/agent/apps/desktop/src/store/windows.ts +115 -0
  500. package/agent/apps/desktop/src/styles.css +510 -119
  501. package/agent/apps/desktop/src/themes/color.ts +142 -0
  502. package/agent/apps/desktop/src/themes/context.tsx +128 -75
  503. package/agent/apps/desktop/src/themes/install.test.ts +119 -0
  504. package/agent/apps/desktop/src/themes/install.ts +95 -0
  505. package/agent/apps/desktop/src/themes/presets.test.ts +33 -0
  506. package/agent/apps/desktop/src/themes/presets.ts +13 -4
  507. package/agent/apps/desktop/src/themes/profile-theme.test.ts +41 -0
  508. package/agent/apps/desktop/src/themes/types.ts +35 -0
  509. package/agent/apps/desktop/src/themes/user-themes.test.ts +63 -0
  510. package/agent/apps/desktop/src/themes/user-themes.ts +122 -0
  511. package/agent/apps/desktop/src/themes/vscode.test.ts +171 -0
  512. package/agent/apps/desktop/src/themes/vscode.ts +343 -0
  513. package/agent/apps/desktop/src/types/hermes.ts +138 -1
  514. package/agent/apps/desktop/tsconfig.json +2 -2
  515. package/agent/apps/desktop/vite.config.ts +18 -0
  516. package/agent/apps/shared/package.json +1 -1
  517. package/agent/apps/shared/src/json-rpc-gateway.ts +63 -2
  518. package/agent/apps/shared/tsconfig.json +2 -2
  519. package/agent/cli-config.yaml.example +78 -1
  520. package/agent/cli.py +2294 -3146
  521. package/agent/cron/blueprint_catalog.py +713 -0
  522. package/agent/cron/jobs.py +226 -110
  523. package/agent/cron/scheduler.py +468 -193
  524. package/agent/cron/scheduler_provider.py +177 -0
  525. package/agent/cron/scripts/__init__.py +1 -0
  526. package/agent/cron/scripts/classify_items.py +226 -0
  527. package/agent/cron/suggestion_catalog.py +154 -0
  528. package/agent/cron/suggestions.py +257 -0
  529. package/agent/docs/chronos-managed-cron-contract.md +196 -0
  530. package/agent/docs/design/profile-builder.md +146 -0
  531. package/agent/docs/middleware/README.md +260 -0
  532. package/agent/docs/observability/README.md +316 -0
  533. package/agent/docs/plans/2026-06-09-003-fix-telegram-stream-overflow-continuations-plan.md +240 -0
  534. package/agent/docs/rca-ssl-cacert-post-git-pull.md +54 -0
  535. package/agent/docs/relay-connector-contract.md +285 -0
  536. package/agent/gateway/authz_mixin.py +536 -0
  537. package/agent/gateway/channel_directory.py +65 -3
  538. package/agent/gateway/config.py +222 -12
  539. package/agent/gateway/display_config.py +10 -0
  540. package/agent/gateway/hooks.py +17 -0
  541. package/agent/gateway/kanban_watchers.py +1146 -0
  542. package/agent/gateway/message_timestamps.py +166 -0
  543. package/agent/gateway/platforms/ADDING_A_PLATFORM.md +29 -0
  544. package/agent/gateway/platforms/api_server.py +216 -38
  545. package/agent/gateway/platforms/base.py +210 -58
  546. package/agent/gateway/platforms/email.py +122 -12
  547. package/agent/gateway/platforms/feishu.py +80 -11
  548. package/agent/gateway/platforms/feishu_meeting_invite.py +212 -0
  549. package/agent/gateway/platforms/matrix.py +1498 -297
  550. package/agent/gateway/platforms/qqbot/adapter.py +6 -0
  551. package/agent/gateway/platforms/signal.py +8 -0
  552. package/agent/gateway/platforms/slack.py +308 -12
  553. package/agent/gateway/platforms/telegram.py +831 -24
  554. package/agent/gateway/platforms/webhook.py +109 -21
  555. package/agent/gateway/platforms/weixin.py +113 -2
  556. package/agent/gateway/platforms/whatsapp.py +94 -288
  557. package/agent/gateway/platforms/whatsapp_cloud.py +1956 -0
  558. package/agent/gateway/platforms/whatsapp_common.py +367 -0
  559. package/agent/gateway/platforms/yuanbao.py +608 -191
  560. package/agent/gateway/platforms/yuanbao_proto.py +232 -23
  561. package/agent/gateway/relay/__init__.py +375 -0
  562. package/agent/gateway/relay/adapter.py +222 -0
  563. package/agent/gateway/relay/auth.py +168 -0
  564. package/agent/gateway/relay/descriptor.py +118 -0
  565. package/agent/gateway/relay/transport.py +101 -0
  566. package/agent/gateway/relay/ws_transport.py +327 -0
  567. package/agent/gateway/response_filters.py +53 -0
  568. package/agent/gateway/rich_sent_store.py +80 -0
  569. package/agent/gateway/run.py +2940 -5001
  570. package/agent/gateway/session.py +109 -8
  571. package/agent/gateway/session_context.py +22 -4
  572. package/agent/gateway/slash_commands.py +3854 -0
  573. package/agent/gateway/status.py +141 -21
  574. package/agent/gateway/stream_consumer.py +288 -31
  575. package/agent/hermes-already-has-routines.md +1 -1
  576. package/agent/hermes_cli/__init__.py +62 -17
  577. package/agent/hermes_cli/_parser.py +30 -0
  578. package/agent/hermes_cli/_subprocess_compat.py +61 -0
  579. package/agent/hermes_cli/active_sessions.py +320 -0
  580. package/agent/hermes_cli/auth.py +707 -59
  581. package/agent/hermes_cli/auth_commands.py +39 -22
  582. package/agent/hermes_cli/backup.py +109 -7
  583. package/agent/hermes_cli/banner.py +88 -0
  584. package/agent/hermes_cli/blueprint_cmd.py +318 -0
  585. package/agent/hermes_cli/clawpump_cli.py +3 -3
  586. package/agent/hermes_cli/cli_agent_setup_mixin.py +684 -0
  587. package/agent/hermes_cli/cli_commands_mixin.py +2293 -0
  588. package/agent/hermes_cli/commands.py +216 -91
  589. package/agent/hermes_cli/config.py +967 -130
  590. package/agent/hermes_cli/container_boot.py +76 -11
  591. package/agent/hermes_cli/cron.py +5 -11
  592. package/agent/hermes_cli/curator.py +21 -0
  593. package/agent/hermes_cli/dashboard_auth/__init__.py +2 -0
  594. package/agent/hermes_cli/dashboard_auth/base.py +62 -0
  595. package/agent/hermes_cli/dashboard_auth/cookies.py +32 -19
  596. package/agent/hermes_cli/dashboard_auth/login_page.py +156 -6
  597. package/agent/hermes_cli/dashboard_auth/middleware.py +28 -4
  598. package/agent/hermes_cli/dashboard_auth/prefix.py +46 -2
  599. package/agent/hermes_cli/dashboard_auth/public_paths.py +6 -0
  600. package/agent/hermes_cli/dashboard_auth/routes.py +158 -2
  601. package/agent/hermes_cli/dashboard_auth/ws_tickets.py +85 -11
  602. package/agent/hermes_cli/dashboard_register.py +427 -0
  603. package/agent/hermes_cli/debug.py +155 -50
  604. package/agent/hermes_cli/distribution.py +227 -0
  605. package/agent/hermes_cli/doctor.py +255 -14
  606. package/agent/hermes_cli/dump.py +60 -6
  607. package/agent/hermes_cli/env_loader.py +33 -0
  608. package/agent/hermes_cli/gateway.py +755 -103
  609. package/agent/hermes_cli/gateway_enroll.py +250 -0
  610. package/agent/hermes_cli/gateway_windows.py +254 -11
  611. package/agent/hermes_cli/gui_uninstall.py +285 -0
  612. package/agent/hermes_cli/inventory.py +105 -4
  613. package/agent/hermes_cli/kanban.py +58 -71
  614. package/agent/hermes_cli/kanban_db.py +391 -14
  615. package/agent/hermes_cli/kanban_decompose.py +2 -2
  616. package/agent/hermes_cli/kanban_specify.py +3 -1
  617. package/agent/hermes_cli/logs.py +2 -0
  618. package/agent/hermes_cli/main.py +2889 -5287
  619. package/agent/hermes_cli/managed_scope.py +214 -0
  620. package/agent/hermes_cli/managed_uv.py +254 -0
  621. package/agent/hermes_cli/mcp_catalog.py +6 -3
  622. package/agent/hermes_cli/mcp_config.py +145 -21
  623. package/agent/hermes_cli/mcp_security.py +96 -0
  624. package/agent/hermes_cli/mcp_startup.py +32 -3
  625. package/agent/hermes_cli/memory_providers.py +149 -0
  626. package/agent/hermes_cli/memory_setup.py +97 -42
  627. package/agent/hermes_cli/middleware.py +313 -0
  628. package/agent/hermes_cli/model_catalog.py +31 -0
  629. package/agent/hermes_cli/model_cost_guard.py +134 -0
  630. package/agent/hermes_cli/model_normalize.py +2 -1
  631. package/agent/hermes_cli/model_setup_flows.py +2759 -0
  632. package/agent/hermes_cli/model_switch.py +242 -27
  633. package/agent/hermes_cli/models.py +284 -44
  634. package/agent/hermes_cli/nous_account.py +33 -6
  635. package/agent/hermes_cli/nous_billing.py +406 -0
  636. package/agent/hermes_cli/nous_subscription.py +202 -5
  637. package/agent/hermes_cli/platforms.py +1 -0
  638. package/agent/hermes_cli/plugins.py +218 -18
  639. package/agent/hermes_cli/plugins_cmd.py +249 -105
  640. package/agent/hermes_cli/portal_cli.py +56 -16
  641. package/agent/hermes_cli/profile_distribution.py +6 -1
  642. package/agent/hermes_cli/profiles.py +283 -32
  643. package/agent/hermes_cli/provider_catalog.py +170 -0
  644. package/agent/hermes_cli/providers.py +4 -1
  645. package/agent/hermes_cli/pty_bridge.py +53 -4
  646. package/agent/hermes_cli/runtime_provider.py +216 -34
  647. package/agent/hermes_cli/secret_prompt.py +4 -4
  648. package/agent/hermes_cli/secrets_cli.py +24 -0
  649. package/agent/hermes_cli/send_cmd.py +28 -2
  650. package/agent/hermes_cli/service_manager.py +166 -19
  651. package/agent/hermes_cli/session_listing.py +97 -0
  652. package/agent/hermes_cli/setup.py +158 -94
  653. package/agent/hermes_cli/setup_whatsapp_cloud.py +541 -0
  654. package/agent/hermes_cli/skills_config.py +8 -2
  655. package/agent/hermes_cli/skills_hub.py +149 -7
  656. package/agent/hermes_cli/status.py +2 -2
  657. package/agent/hermes_cli/subcommands/__init__.py +18 -0
  658. package/agent/hermes_cli/subcommands/_shared.py +29 -0
  659. package/agent/hermes_cli/subcommands/acp.py +52 -0
  660. package/agent/hermes_cli/subcommands/auth.py +109 -0
  661. package/agent/hermes_cli/subcommands/backup.py +38 -0
  662. package/agent/hermes_cli/subcommands/claw.py +92 -0
  663. package/agent/hermes_cli/subcommands/config.py +49 -0
  664. package/agent/hermes_cli/subcommands/cron.py +163 -0
  665. package/agent/hermes_cli/subcommands/dashboard.py +143 -0
  666. package/agent/hermes_cli/subcommands/debug.py +77 -0
  667. package/agent/hermes_cli/subcommands/doctor.py +35 -0
  668. package/agent/hermes_cli/subcommands/dump.py +28 -0
  669. package/agent/hermes_cli/subcommands/gateway.py +332 -0
  670. package/agent/hermes_cli/subcommands/gui.py +63 -0
  671. package/agent/hermes_cli/subcommands/hooks.py +77 -0
  672. package/agent/hermes_cli/subcommands/import_cmd.py +31 -0
  673. package/agent/hermes_cli/subcommands/insights.py +25 -0
  674. package/agent/hermes_cli/subcommands/login.py +78 -0
  675. package/agent/hermes_cli/subcommands/logout.py +28 -0
  676. package/agent/hermes_cli/subcommands/logs.py +78 -0
  677. package/agent/hermes_cli/subcommands/mcp.py +108 -0
  678. package/agent/hermes_cli/subcommands/memory.py +53 -0
  679. package/agent/hermes_cli/subcommands/model.py +72 -0
  680. package/agent/hermes_cli/subcommands/pairing.py +36 -0
  681. package/agent/hermes_cli/subcommands/plugins.py +94 -0
  682. package/agent/hermes_cli/subcommands/postinstall.py +23 -0
  683. package/agent/hermes_cli/subcommands/profile.py +203 -0
  684. package/agent/hermes_cli/subcommands/prompt_size.py +36 -0
  685. package/agent/hermes_cli/subcommands/security.py +62 -0
  686. package/agent/hermes_cli/subcommands/setup.py +58 -0
  687. package/agent/hermes_cli/subcommands/skills.py +298 -0
  688. package/agent/hermes_cli/subcommands/slack.py +60 -0
  689. package/agent/hermes_cli/subcommands/status.py +28 -0
  690. package/agent/hermes_cli/subcommands/tools.py +95 -0
  691. package/agent/hermes_cli/subcommands/uninstall.py +41 -0
  692. package/agent/hermes_cli/subcommands/update.py +70 -0
  693. package/agent/hermes_cli/subcommands/version.py +18 -0
  694. package/agent/hermes_cli/subcommands/webhook.py +76 -0
  695. package/agent/hermes_cli/subcommands/whatsapp.py +22 -0
  696. package/agent/hermes_cli/suggestions_cmd.py +153 -0
  697. package/agent/hermes_cli/telegram_managed_bot.py +358 -0
  698. package/agent/hermes_cli/tips.py +3 -4
  699. package/agent/hermes_cli/tools_config.py +155 -28
  700. package/agent/hermes_cli/uninstall.py +231 -35
  701. package/agent/hermes_cli/web_server.py +6188 -975
  702. package/agent/hermes_cli/win_pty_bridge.py +179 -0
  703. package/agent/hermes_cli/write_approval_commands.py +209 -0
  704. package/agent/hermes_constants.py +164 -33
  705. package/agent/hermes_logging.py +74 -2
  706. package/agent/hermes_state.py +919 -106
  707. package/agent/hermes_time.py +20 -0
  708. package/agent/locales/af.yaml +23 -0
  709. package/agent/locales/de.yaml +23 -0
  710. package/agent/locales/en.yaml +20 -0
  711. package/agent/locales/es.yaml +23 -0
  712. package/agent/locales/fr.yaml +23 -0
  713. package/agent/locales/ga.yaml +23 -0
  714. package/agent/locales/hu.yaml +23 -0
  715. package/agent/locales/it.yaml +23 -0
  716. package/agent/locales/ja.yaml +23 -0
  717. package/agent/locales/ko.yaml +23 -0
  718. package/agent/locales/pt.yaml +23 -0
  719. package/agent/locales/ru.yaml +23 -0
  720. package/agent/locales/tr.yaml +23 -0
  721. package/agent/locales/uk.yaml +23 -0
  722. package/agent/locales/zh-hant.yaml +23 -0
  723. package/agent/locales/zh.yaml +23 -0
  724. package/agent/model_tools.py +204 -40
  725. package/agent/optional-mcps/clawpump/manifest.yaml +15 -5
  726. package/agent/optional-mcps/clawpump-stdio/manifest.yaml +14 -4
  727. package/agent/optional-mcps/unreal-engine/manifest.yaml +54 -0
  728. package/agent/optional-skills/blockchain/hyperliquid/SKILL.md +2 -2
  729. package/agent/optional-skills/blockchain/hyperliquid/scripts/hyperliquid_client.py +1 -1
  730. package/agent/optional-skills/creative/kanban-video-orchestrator/SKILL.md +1 -1
  731. package/agent/optional-skills/creative/kanban-video-orchestrator/assets/setup.sh.tmpl +4 -3
  732. package/agent/optional-skills/creative/kanban-video-orchestrator/references/kanban-setup.md +6 -4
  733. package/agent/optional-skills/creative/kanban-video-orchestrator/references/tool-matrix.md +2 -2
  734. package/agent/{skills/software-development → optional-skills/devops}/hermes-s6-container-supervision/SKILL.md +2 -0
  735. package/agent/optional-skills/devops/watchers/SKILL.md +1 -1
  736. package/agent/optional-skills/devops/watchers/scripts/watch_github.py +2 -1
  737. package/agent/optional-skills/payments/mpp-agent/SKILL.md +124 -0
  738. package/agent/optional-skills/payments/stripe-link-cli/SKILL.md +184 -0
  739. package/agent/optional-skills/payments/stripe-projects/SKILL.md +120 -0
  740. package/agent/optional-skills/productivity/canvas/SKILL.md +1 -1
  741. package/agent/optional-skills/productivity/canvas/scripts/canvas_api.py +4 -1
  742. package/agent/optional-skills/productivity/shop/SKILL.md +224 -0
  743. package/agent/optional-skills/productivity/shop/references/catalog-mcp.md +236 -0
  744. package/agent/optional-skills/productivity/shop/references/direct-api.md +278 -0
  745. package/agent/optional-skills/productivity/shop/references/legal.md +3 -0
  746. package/agent/optional-skills/productivity/shop/references/safety.md +36 -0
  747. package/agent/optional-skills/productivity/shopify/SKILL.md +1 -1
  748. package/agent/optional-skills/productivity/siyuan/SKILL.md +1 -1
  749. package/agent/optional-skills/productivity/telephony/SKILL.md +4 -4
  750. package/agent/optional-skills/productivity/telephony/scripts/telephony.py +15 -15
  751. package/agent/optional-skills/security/1password/SKILL.md +1 -1
  752. package/agent/{skills/red-teaming → optional-skills/security}/godmode/SKILL.md +3 -4
  753. package/agent/{skills/red-teaming → optional-skills/security}/godmode/scripts/auto_jailbreak.py +3 -1
  754. package/agent/optional-skills/software-development/rest-graphql-debug/SKILL.md +1 -1
  755. package/agent/{skills → optional-skills}/software-development/subagent-driven-development/SKILL.md +5 -5
  756. package/agent/package-lock.json +4082 -7907
  757. package/agent/package.json +18 -3
  758. package/agent/plugins/browser/firecrawl/provider.py +4 -1
  759. package/agent/plugins/cron/__init__.py +344 -0
  760. package/agent/plugins/cron/chronos/__init__.py +241 -0
  761. package/agent/plugins/cron/chronos/_nas_client.py +123 -0
  762. package/agent/plugins/cron/chronos/plugin.yaml +9 -0
  763. package/agent/plugins/cron/chronos/verify.py +103 -0
  764. package/agent/plugins/dashboard_auth/basic/__init__.py +491 -0
  765. package/agent/plugins/dashboard_auth/basic/plugin.yaml +7 -0
  766. package/agent/plugins/dashboard_auth/nous/__init__.py +12 -14
  767. package/agent/plugins/dashboard_auth/self_hosted/__init__.py +736 -0
  768. package/agent/plugins/dashboard_auth/self_hosted/plugin.yaml +8 -0
  769. package/agent/plugins/disk-cleanup/disk_cleanup.py +100 -20
  770. package/agent/plugins/google_meet/audio_bridge.py +4 -0
  771. package/agent/plugins/google_meet/meet_bot.py +7 -1
  772. package/agent/plugins/hermes-achievements/dashboard/dist/index.js +9 -15
  773. package/agent/plugins/image_gen/fal/__init__.py +35 -6
  774. package/agent/plugins/image_gen/krea/__init__.py +56 -13
  775. package/agent/plugins/image_gen/openai/__init__.py +122 -24
  776. package/agent/plugins/image_gen/openai-codex/__init__.py +28 -2
  777. package/agent/plugins/image_gen/xai/__init__.py +92 -12
  778. package/agent/plugins/kanban/dashboard/dist/index.js +63 -48
  779. package/agent/plugins/kanban/dashboard/plugin_api.py +39 -35
  780. package/agent/plugins/memory/__init__.py +48 -5
  781. package/agent/plugins/memory/byterover/__init__.py +1 -0
  782. package/agent/plugins/memory/hindsight/README.md +1 -1
  783. package/agent/plugins/memory/hindsight/__init__.py +138 -24
  784. package/agent/plugins/memory/hindsight/plugin.yaml +1 -1
  785. package/agent/plugins/memory/honcho/README.md +13 -10
  786. package/agent/plugins/memory/honcho/cli.py +247 -122
  787. package/agent/plugins/memory/honcho/client.py +112 -102
  788. package/agent/plugins/memory/openviking/README.md +12 -1
  789. package/agent/plugins/memory/openviking/__init__.py +2281 -107
  790. package/agent/plugins/memory/openviking/plugin.yaml +1 -2
  791. package/agent/plugins/memory/supermemory/README.md +22 -10
  792. package/agent/plugins/memory/supermemory/__init__.py +142 -37
  793. package/agent/plugins/memory/supermemory/plugin.yaml +1 -1
  794. package/agent/plugins/model-providers/anthropic/__init__.py +1 -0
  795. package/agent/plugins/model-providers/bedrock/__init__.py +1 -0
  796. package/agent/plugins/model-providers/copilot-acp/__init__.py +1 -0
  797. package/agent/plugins/model-providers/custom/__init__.py +8 -2
  798. package/agent/plugins/model-providers/kimi-coding/__init__.py +16 -7
  799. package/agent/plugins/model-providers/minimax/__init__.py +60 -8
  800. package/agent/plugins/model-providers/opencode-zen/__init__.py +12 -3
  801. package/agent/plugins/model-providers/openrouter/__init__.py +75 -4
  802. package/agent/plugins/model-providers/xiaomi/__init__.py +2 -0
  803. package/agent/plugins/model-providers/zai/__init__.py +1 -0
  804. package/agent/plugins/observability/langfuse/__init__.py +147 -14
  805. package/agent/plugins/observability/nemo_relay/README.md +559 -0
  806. package/agent/plugins/observability/nemo_relay/__init__.py +962 -0
  807. package/agent/plugins/observability/nemo_relay/plugin.yaml +20 -0
  808. package/agent/plugins/platforms/discord/adapter.py +932 -61
  809. package/agent/plugins/platforms/discord/voice_mixer.py +379 -0
  810. package/agent/plugins/platforms/google_chat/adapter.py +9 -3
  811. package/agent/plugins/platforms/google_chat/oauth.py +1 -1
  812. package/agent/plugins/platforms/homeassistant/__init__.py +3 -0
  813. package/agent/{gateway/platforms/homeassistant.py → plugins/platforms/homeassistant/adapter.py} +128 -0
  814. package/agent/plugins/platforms/homeassistant/plugin.yaml +22 -0
  815. package/agent/plugins/platforms/irc/adapter.py +4 -1
  816. package/agent/plugins/platforms/line/adapter.py +16 -1
  817. package/agent/plugins/platforms/mattermost/adapter.py +100 -24
  818. package/agent/plugins/platforms/photon/README.md +179 -0
  819. package/agent/plugins/platforms/photon/__init__.py +4 -0
  820. package/agent/plugins/platforms/photon/adapter.py +1586 -0
  821. package/agent/plugins/platforms/photon/auth.py +1046 -0
  822. package/agent/plugins/platforms/photon/cli.py +439 -0
  823. package/agent/plugins/platforms/photon/plugin.yaml +88 -0
  824. package/agent/plugins/platforms/photon/sidecar/README.md +52 -0
  825. package/agent/plugins/platforms/photon/sidecar/index.mjs +720 -0
  826. package/agent/plugins/platforms/photon/sidecar/package-lock.json +1730 -0
  827. package/agent/plugins/platforms/photon/sidecar/package.json +25 -0
  828. package/agent/plugins/platforms/photon/sidecar/patch-spectrum-mixed-attachments.mjs +155 -0
  829. package/agent/plugins/platforms/raft/__init__.py +3 -0
  830. package/agent/plugins/platforms/raft/adapter.py +774 -0
  831. package/agent/plugins/platforms/raft/plugin.yaml +19 -0
  832. package/agent/plugins/platforms/simplex/adapter.py +777 -220
  833. package/agent/plugins/platforms/simplex/plugin.yaml +21 -2
  834. package/agent/plugins/platforms/teams/adapter.py +175 -5
  835. package/agent/plugins/plugin_utils.py +135 -0
  836. package/agent/plugins/video_gen/fal/__init__.py +10 -3
  837. package/agent/plugins/web/searxng/provider.py +15 -2
  838. package/agent/plugins/web/xai/provider.py +2 -2
  839. package/agent/providers/base.py +22 -3
  840. package/agent/pyproject.toml +115 -21
  841. package/agent/run_agent.py +733 -39
  842. package/agent/scripts/build_skills_index.py +51 -19
  843. package/agent/scripts/check_subprocess_stdin.py +177 -0
  844. package/agent/scripts/contributor_audit.py +2 -0
  845. package/agent/scripts/docker_config_migrate.py +67 -0
  846. package/agent/scripts/install.cmd +3 -3
  847. package/agent/scripts/install.ps1 +580 -154
  848. package/agent/scripts/install.sh +402 -185
  849. package/agent/scripts/lib/node-bootstrap.sh +39 -4
  850. package/agent/scripts/release.py +183 -0
  851. package/agent/scripts/run_tests.sh +1 -0
  852. package/agent/scripts/run_tests_parallel.py +18 -23
  853. package/agent/scripts/whatsapp-bridge/bridge.js +25 -4
  854. package/agent/setup.py +59 -0
  855. package/agent/skills/autonomous-ai-agents/codex/SKILL.md +19 -0
  856. package/agent/skills/autonomous-ai-agents/hermes-agent/SKILL.md +10 -3
  857. package/agent/skills/{mcp/native-mcp/SKILL.md → autonomous-ai-agents/hermes-agent/references/native-mcp.md} +0 -13
  858. package/agent/skills/{devops/webhook-subscriptions/SKILL.md → autonomous-ai-agents/hermes-agent/references/webhooks.md} +1 -11
  859. package/agent/skills/clawpump/SKILL.md +53 -5
  860. package/agent/skills/devops/kanban-orchestrator/SKILL.md +1 -0
  861. package/agent/skills/devops/kanban-worker/SKILL.md +1 -0
  862. package/agent/skills/github/github-auth/SKILL.md +2 -2
  863. package/agent/skills/github/github-auth/scripts/gh-env.sh +2 -2
  864. package/agent/skills/github/github-code-review/SKILL.md +2 -2
  865. package/agent/skills/github/github-issues/SKILL.md +2 -2
  866. package/agent/skills/github/github-pr-workflow/SKILL.md +2 -2
  867. package/agent/skills/github/github-repo-management/SKILL.md +2 -2
  868. package/agent/skills/media/gif-search/SKILL.md +1 -1
  869. package/agent/skills/media/youtube-content/SKILL.md +10 -7
  870. package/agent/skills/media/youtube-content/scripts/fetch_transcript.py +3 -3
  871. package/agent/skills/note-taking/obsidian/SKILL.md +1 -1
  872. package/agent/skills/productivity/airtable/SKILL.md +2 -2
  873. package/agent/skills/productivity/google-workspace/scripts/setup.py +33 -7
  874. package/agent/skills/productivity/notion/SKILL.md +2 -2
  875. package/agent/skills/productivity/teams-meeting-pipeline/SKILL.md +1 -1
  876. package/agent/skills/research/llm-wiki/SKILL.md +1 -1
  877. package/agent/skills/social-media/xurl/SKILL.md +9 -0
  878. package/agent/skills/software-development/hermes-agent-skill-authoring/SKILL.md +1 -1
  879. package/agent/skills/software-development/plan/SKILL.md +285 -5
  880. package/agent/skills/software-development/requesting-code-review/SKILL.md +2 -2
  881. package/agent/skills/software-development/simplify-code/SKILL.md +212 -0
  882. package/agent/skills/software-development/spike/SKILL.md +2 -2
  883. package/agent/skills/software-development/systematic-debugging/SKILL.md +1 -1
  884. package/agent/skills/software-development/test-driven-development/SKILL.md +1 -1
  885. package/agent/tools/approval.py +302 -4
  886. package/agent/tools/async_delegation.py +386 -0
  887. package/agent/tools/blueprints.py +325 -0
  888. package/agent/tools/browser_cdp_tool.py +3 -3
  889. package/agent/tools/browser_tool.py +34 -6
  890. package/agent/tools/checkpoint_manager.py +31 -1
  891. package/agent/tools/clarify_tool.py +55 -5
  892. package/agent/tools/code_execution_tool.py +31 -14
  893. package/agent/tools/computer_use/cua_backend.py +81 -3
  894. package/agent/tools/computer_use/tool.py +79 -5
  895. package/agent/tools/computer_use/vision_routing.py +55 -3
  896. package/agent/tools/credential_files.py +31 -12
  897. package/agent/tools/cronjob_tools.py +30 -20
  898. package/agent/tools/delegate_tool.py +356 -31
  899. package/agent/tools/env_probe.py +1 -0
  900. package/agent/tools/environments/docker.py +163 -8
  901. package/agent/tools/environments/file_sync.py +2 -1
  902. package/agent/tools/environments/local.py +74 -23
  903. package/agent/tools/environments/singularity.py +4 -1
  904. package/agent/tools/environments/ssh.py +78 -11
  905. package/agent/tools/file_operations.py +277 -41
  906. package/agent/tools/file_tools.py +166 -28
  907. package/agent/tools/image_generation_tool.py +515 -29
  908. package/agent/tools/kanban_tools.py +99 -0
  909. package/agent/tools/lazy_deps.py +33 -2
  910. package/agent/tools/mcp_oauth.py +5 -5
  911. package/agent/tools/mcp_oauth_manager.py +7 -5
  912. package/agent/tools/mcp_tool.py +840 -33
  913. package/agent/tools/memory_tool.py +335 -38
  914. package/agent/tools/osv_check.py +15 -1
  915. package/agent/tools/process_registry.py +155 -11
  916. package/agent/tools/read_extract.py +248 -0
  917. package/agent/tools/read_terminal_tool.py +93 -0
  918. package/agent/tools/schema_sanitizer.py +38 -0
  919. package/agent/tools/send_message_tool.py +163 -49
  920. package/agent/tools/session_search_tool.py +189 -7
  921. package/agent/tools/skill_manager_tool.py +202 -3
  922. package/agent/tools/skill_usage.py +52 -4
  923. package/agent/tools/skills_hub.py +184 -44
  924. package/agent/tools/skills_sync.py +232 -5
  925. package/agent/tools/skills_tool.py +125 -11
  926. package/agent/tools/terminal_tool.py +148 -26
  927. package/agent/tools/tirith_security.py +2 -0
  928. package/agent/tools/todo_tool.py +32 -1
  929. package/agent/tools/transcription_tools.py +13 -5
  930. package/agent/tools/tts_tool.py +332 -38
  931. package/agent/tools/url_safety.py +52 -1
  932. package/agent/tools/vision_tools.py +124 -39
  933. package/agent/tools/voice_mode.py +4 -3
  934. package/agent/tools/web_tools.py +45 -15
  935. package/agent/tools/write_approval.py +493 -0
  936. package/agent/toolsets.py +34 -10
  937. package/agent/trajectory_compressor.py +81 -10
  938. package/agent/tui_gateway/entry.py +43 -6
  939. package/agent/tui_gateway/server.py +3335 -330
  940. package/agent/tui_gateway/slash_worker.py +61 -0
  941. package/agent/tui_gateway/ws.py +67 -9
  942. package/agent/ui-tui/eslint.config.mjs +0 -4
  943. package/agent/ui-tui/package.json +6 -6
  944. package/agent/ui-tui/packages/hermes-ink/package.json +1 -1
  945. package/agent/ui-tui/packages/hermes-ink/src/ink/app-mouse.test.ts +34 -1
  946. package/agent/ui-tui/packages/hermes-ink/src/ink/app-rawmode-mouse.test.ts +91 -0
  947. package/agent/ui-tui/packages/hermes-ink/src/ink/components/App.tsx +35 -2
  948. package/agent/ui-tui/packages/hermes-ink/src/ink/events/input-event.ts +4 -11
  949. package/agent/ui-tui/packages/hermes-ink/src/ink/parse-keypress.test.ts +23 -57
  950. package/agent/ui-tui/packages/hermes-ink/src/ink/parse-keypress.ts +11 -135
  951. package/agent/ui-tui/packages/hermes-ink/src/ink/termio/tokenize.test.ts +185 -0
  952. package/agent/ui-tui/packages/hermes-ink/src/ink/termio/tokenize.ts +37 -3
  953. package/agent/ui-tui/packages/hermes-ink/src/utils/execFileNoThrow.ts +5 -5
  954. package/agent/ui-tui/src/__tests__/appChromeStatusRule.test.tsx +217 -0
  955. package/agent/ui-tui/src/__tests__/appChromeStatusRuleDevCredits.test.tsx +73 -0
  956. package/agent/ui-tui/src/__tests__/approvalAction.test.ts +11 -0
  957. package/agent/ui-tui/src/__tests__/billingCommand.test.ts +301 -0
  958. package/agent/ui-tui/src/__tests__/blockLayout.test.ts +122 -0
  959. package/agent/ui-tui/src/__tests__/brandingMcpCount.test.ts +111 -0
  960. package/agent/ui-tui/src/__tests__/completionApply.test.ts +51 -0
  961. package/agent/ui-tui/src/__tests__/createGatewayEventHandler.test.ts +487 -2
  962. package/agent/ui-tui/src/__tests__/createSlashHandler.test.ts +54 -0
  963. package/agent/ui-tui/src/__tests__/creditsCommand.test.ts +144 -0
  964. package/agent/ui-tui/src/__tests__/gatewayClient.test.ts +120 -99
  965. package/agent/ui-tui/src/__tests__/gracefulExit.test.ts +11 -0
  966. package/agent/ui-tui/src/__tests__/memoryMonitor.test.ts +102 -0
  967. package/agent/ui-tui/src/__tests__/paths.test.ts +41 -1
  968. package/agent/ui-tui/src/__tests__/terminalModes.test.ts +22 -0
  969. package/agent/ui-tui/src/__tests__/text.test.ts +23 -0
  970. package/agent/ui-tui/src/__tests__/textInputFastEcho.test.ts +37 -0
  971. package/agent/ui-tui/src/__tests__/turnControllerNotice.test.ts +43 -0
  972. package/agent/ui-tui/src/__tests__/useInputHandlers.test.ts +38 -1
  973. package/agent/ui-tui/src/__tests__/virtualHeights.test.ts +8 -0
  974. package/agent/ui-tui/src/app/createGatewayEventHandler.ts +102 -7
  975. package/agent/ui-tui/src/app/interfaces.ts +64 -1
  976. package/agent/ui-tui/src/app/overlayStore.ts +18 -2
  977. package/agent/ui-tui/src/app/slash/commands/billing.ts +332 -0
  978. package/agent/ui-tui/src/app/slash/commands/core.ts +31 -2
  979. package/agent/ui-tui/src/app/slash/commands/credits.ts +57 -0
  980. package/agent/ui-tui/src/app/slash/commands/ops.ts +28 -0
  981. package/agent/ui-tui/src/app/slash/commands/session.ts +32 -4
  982. package/agent/ui-tui/src/app/slash/registry.ts +4 -0
  983. package/agent/ui-tui/src/app/turnController.ts +145 -2
  984. package/agent/ui-tui/src/app/uiStore.ts +2 -0
  985. package/agent/ui-tui/src/app/useInputHandlers.ts +42 -4
  986. package/agent/ui-tui/src/app/useMainApp.ts +54 -8
  987. package/agent/ui-tui/src/app/useSessionLifecycle.ts +40 -31
  988. package/agent/ui-tui/src/app/useSubmission.ts +23 -31
  989. package/agent/ui-tui/src/components/appChrome.tsx +112 -5
  990. package/agent/ui-tui/src/components/appLayout.tsx +9 -0
  991. package/agent/ui-tui/src/components/appOverlays.tsx +25 -1
  992. package/agent/ui-tui/src/components/billingOverlay.tsx +684 -0
  993. package/agent/ui-tui/src/components/branding.tsx +15 -3
  994. package/agent/ui-tui/src/components/messageLine.tsx +25 -3
  995. package/agent/ui-tui/src/components/pluginsHub.tsx +238 -0
  996. package/agent/ui-tui/src/components/prompts.tsx +31 -17
  997. package/agent/ui-tui/src/components/streamingAssistant.tsx +63 -55
  998. package/agent/ui-tui/src/components/textInput.tsx +16 -0
  999. package/agent/ui-tui/src/config/env.ts +12 -0
  1000. package/agent/ui-tui/src/config/limits.ts +13 -0
  1001. package/agent/ui-tui/src/domain/blockLayout.ts +146 -0
  1002. package/agent/ui-tui/src/domain/paths.ts +24 -0
  1003. package/agent/ui-tui/src/domain/slash.ts +40 -0
  1004. package/agent/ui-tui/src/entry.tsx +35 -4
  1005. package/agent/ui-tui/src/gatewayClient.ts +22 -10
  1006. package/agent/ui-tui/src/gatewayTypes.ts +130 -1
  1007. package/agent/ui-tui/src/lib/gracefulExit.ts +24 -4
  1008. package/agent/ui-tui/src/lib/memory.test.ts +162 -0
  1009. package/agent/ui-tui/src/lib/memory.ts +60 -1
  1010. package/agent/ui-tui/src/lib/memoryMonitor.ts +79 -4
  1011. package/agent/ui-tui/src/lib/osc52.ts +1 -1
  1012. package/agent/ui-tui/src/lib/text.test.ts +32 -1
  1013. package/agent/ui-tui/src/lib/text.ts +29 -2
  1014. package/agent/ui-tui/src/lib/virtualHeights.ts +13 -0
  1015. package/agent/ui-tui/src/types.ts +5 -0
  1016. package/agent/ui-tui/tsconfig.build.json +0 -1
  1017. package/agent/ui-tui/tsconfig.json +2 -1
  1018. package/agent/utils.py +66 -2
  1019. package/agent/uv.lock +308 -696
  1020. package/agent/web/index.html +2 -2
  1021. package/agent/web/package.json +11 -6
  1022. package/agent/web/public/claw-bg.webp +0 -0
  1023. package/agent/web/public/claw-logo.webp +0 -0
  1024. package/agent/web/src/App.tsx +138 -48
  1025. package/agent/web/src/components/AutomationBlueprints.tsx +225 -0
  1026. package/agent/web/src/components/Backdrop.tsx +15 -0
  1027. package/agent/web/src/components/ChatSessionList.tsx +260 -0
  1028. package/agent/web/src/components/ChatSidebar.tsx +262 -78
  1029. package/agent/web/src/components/ConfirmDialog.tsx +122 -0
  1030. package/agent/web/src/components/ModelPickerDialog.tsx +111 -16
  1031. package/agent/web/src/components/ModelReloadConfirm.tsx +40 -0
  1032. package/agent/web/src/components/ProfileScopeBanner.tsx +30 -0
  1033. package/agent/web/src/components/ProfileSwitcher.tsx +67 -0
  1034. package/agent/web/src/components/ReasoningPicker.tsx +167 -0
  1035. package/agent/web/src/components/SkillEditorDialog.tsx +215 -0
  1036. package/agent/web/src/components/ThemeSwitcher.tsx +119 -4
  1037. package/agent/web/src/components/ToolsetConfigDrawer.tsx +457 -0
  1038. package/agent/web/src/contexts/PageHeaderProvider.tsx +7 -4
  1039. package/agent/web/src/contexts/ProfileProvider.tsx +137 -0
  1040. package/agent/web/src/contexts/SystemActions.tsx +6 -8
  1041. package/agent/web/src/contexts/profile-context.ts +19 -0
  1042. package/agent/web/src/contexts/useProfileScope.ts +6 -0
  1043. package/agent/web/src/i18n/af.ts +5 -4
  1044. package/agent/web/src/i18n/de.ts +5 -4
  1045. package/agent/web/src/i18n/en.ts +58 -4
  1046. package/agent/web/src/i18n/es.ts +5 -3
  1047. package/agent/web/src/i18n/fr.ts +5 -3
  1048. package/agent/web/src/i18n/ga.ts +5 -4
  1049. package/agent/web/src/i18n/hu.ts +5 -4
  1050. package/agent/web/src/i18n/it.ts +5 -4
  1051. package/agent/web/src/i18n/ja.ts +5 -4
  1052. package/agent/web/src/i18n/ko.ts +5 -4
  1053. package/agent/web/src/i18n/pt.ts +5 -3
  1054. package/agent/web/src/i18n/ru.ts +5 -4
  1055. package/agent/web/src/i18n/tr.ts +5 -4
  1056. package/agent/web/src/i18n/types.ts +59 -1
  1057. package/agent/web/src/i18n/uk.ts +5 -3
  1058. package/agent/web/src/i18n/zh-hant.ts +5 -4
  1059. package/agent/web/src/i18n/zh.ts +5 -4
  1060. package/agent/web/src/index.css +2 -2
  1061. package/agent/web/src/lib/api.ts +819 -52
  1062. package/agent/web/src/lib/dashboard-flags.ts +16 -7
  1063. package/agent/web/src/lib/reasoning-effort.test.ts +48 -0
  1064. package/agent/web/src/lib/reasoning-effort.ts +36 -0
  1065. package/agent/web/src/lib/session-refresh.test.ts +21 -0
  1066. package/agent/web/src/lib/session-refresh.ts +26 -0
  1067. package/agent/web/src/pages/ChannelsPage.tsx +529 -68
  1068. package/agent/web/src/pages/ChatPage.tsx +249 -56
  1069. package/agent/web/src/pages/ConfigPage.tsx +11 -1
  1070. package/agent/web/src/pages/CronPage.tsx +219 -31
  1071. package/agent/web/src/pages/EnvPage.tsx +25 -6
  1072. package/agent/web/src/pages/FilesPage.tsx +525 -0
  1073. package/agent/web/src/pages/McpPage.tsx +80 -3
  1074. package/agent/web/src/pages/ModelsPage.tsx +97 -12
  1075. package/agent/web/src/pages/PluginsPage.tsx +1 -1
  1076. package/agent/web/src/pages/ProfileBuilderPage.tsx +611 -0
  1077. package/agent/web/src/pages/ProfilesPage.tsx +1038 -172
  1078. package/agent/web/src/pages/SessionsPage.tsx +144 -13
  1079. package/agent/web/src/pages/SkillsPage.tsx +851 -70
  1080. package/agent/web/src/pages/SystemPage.tsx +340 -4
  1081. package/agent/web/src/pages/WalletPage.tsx +401 -0
  1082. package/agent/web/src/pages/WebhooksPage.tsx +145 -15
  1083. package/agent/web/src/pages/X402Page.tsx +207 -0
  1084. package/agent/web/src/plugins/registry.ts +28 -11
  1085. package/agent/web/src/plugins/sdk.d.ts +160 -0
  1086. package/agent/web/src/themes/context.tsx +112 -5
  1087. package/agent/web/src/themes/fonts.ts +167 -0
  1088. package/agent/web/src/themes/index.ts +7 -0
  1089. package/agent/web/tsconfig.app.json +0 -1
  1090. package/agent/web/vite.config.ts +1 -8
  1091. package/agent/web/vitest.config.ts +16 -0
  1092. package/package.json +1 -1
  1093. package/agent/apps/desktop/package-lock.json +0 -18363
  1094. package/agent/apps/desktop/src/app/chat/composer/skin-slash-popover.tsx +0 -56
  1095. package/agent/apps/desktop/src/components/assistant-ui/thread-virtualizer.tsx +0 -382
  1096. package/agent/apps/desktop/src/components/assistant-ui/todo-tool.tsx +0 -109
  1097. package/agent/apps/desktop/src/components/chat/generated-image-context.tsx +0 -19
  1098. package/agent/optional-skills/productivity/shop-app/SKILL.md +0 -340
  1099. package/agent/skills/autonomous-ai-agents/kanban-codex-lane/SKILL.md +0 -277
  1100. package/agent/skills/autonomous-ai-agents/kanban-codex-lane/templates/pmb-codex-lane-prompt.md +0 -57
  1101. package/agent/skills/diagramming/DESCRIPTION.md +0 -3
  1102. package/agent/skills/domain/DESCRIPTION.md +0 -24
  1103. package/agent/skills/gifs/DESCRIPTION.md +0 -3
  1104. package/agent/skills/inference-sh/DESCRIPTION.md +0 -19
  1105. package/agent/skills/mcp/DESCRIPTION.md +0 -3
  1106. package/agent/skills/media/spotify/SKILL.md +0 -135
  1107. package/agent/skills/mlops/training/DESCRIPTION.md +0 -3
  1108. package/agent/skills/mlops/vector-databases/DESCRIPTION.md +0 -3
  1109. package/agent/skills/productivity/linear/SKILL.md +0 -380
  1110. package/agent/skills/productivity/linear/scripts/linear_api.py +0 -445
  1111. package/agent/skills/software-development/debugging-hermes-tui-commands/SKILL.md +0 -152
  1112. package/agent/skills/software-development/writing-plans/SKILL.md +0 -297
  1113. package/agent/ui-tui/package-lock.json +0 -7449
  1114. package/agent/ui-tui/packages/hermes-ink/package-lock.json +0 -1289
  1115. package/agent/web/package-lock.json +0 -8887
  1116. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/PORT_NOTES.md +0 -0
  1117. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/SKILL.md +0 -0
  1118. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/prompts/system.md +0 -0
  1119. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/palettes/macaron.md +0 -0
  1120. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/palettes/mono-ink.md +0 -0
  1121. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/palettes/neon.md +0 -0
  1122. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/palettes/warm.md +0 -0
  1123. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/prompt-construction.md +0 -0
  1124. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/style-presets.md +0 -0
  1125. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/blueprint.md +0 -0
  1126. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/chalkboard.md +0 -0
  1127. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/editorial.md +0 -0
  1128. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/elegant.md +0 -0
  1129. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/fantasy-animation.md +0 -0
  1130. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/flat-doodle.md +0 -0
  1131. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/flat.md +0 -0
  1132. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/ink-notes.md +0 -0
  1133. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/intuition-machine.md +0 -0
  1134. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/minimal.md +0 -0
  1135. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/nature.md +0 -0
  1136. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/notion.md +0 -0
  1137. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/pixel-art.md +0 -0
  1138. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/playful.md +0 -0
  1139. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/retro.md +0 -0
  1140. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/scientific.md +0 -0
  1141. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/screen-print.md +0 -0
  1142. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/sketch-notes.md +0 -0
  1143. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/sketch.md +0 -0
  1144. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/vector-illustration.md +0 -0
  1145. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/vintage.md +0 -0
  1146. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/warm.md +0 -0
  1147. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/watercolor.md +0 -0
  1148. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles.md +0 -0
  1149. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/usage.md +0 -0
  1150. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/workflow.md +0 -0
  1151. /package/agent/{skills → optional-skills}/creative/baoyu-comic/PORT_NOTES.md +0 -0
  1152. /package/agent/{skills → optional-skills}/creative/baoyu-comic/SKILL.md +0 -0
  1153. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/analysis-framework.md +0 -0
  1154. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/chalk.md +0 -0
  1155. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/ink-brush.md +0 -0
  1156. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/ligne-claire.md +0 -0
  1157. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/manga.md +0 -0
  1158. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/minimalist.md +0 -0
  1159. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/realistic.md +0 -0
  1160. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/auto-selection.md +0 -0
  1161. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/base-prompt.md +0 -0
  1162. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/character-template.md +0 -0
  1163. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/cinematic.md +0 -0
  1164. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/dense.md +0 -0
  1165. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/four-panel.md +0 -0
  1166. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/mixed.md +0 -0
  1167. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/splash.md +0 -0
  1168. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/standard.md +0 -0
  1169. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/webtoon.md +0 -0
  1170. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/ohmsha-guide.md +0 -0
  1171. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/partial-workflows.md +0 -0
  1172. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/concept-story.md +0 -0
  1173. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/four-panel.md +0 -0
  1174. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/ohmsha.md +0 -0
  1175. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/shoujo.md +0 -0
  1176. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/wuxia.md +0 -0
  1177. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/storyboard-template.md +0 -0
  1178. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/action.md +0 -0
  1179. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/dramatic.md +0 -0
  1180. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/energetic.md +0 -0
  1181. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/neutral.md +0 -0
  1182. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/romantic.md +0 -0
  1183. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/vintage.md +0 -0
  1184. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/warm.md +0 -0
  1185. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/workflow.md +0 -0
  1186. /package/agent/{skills → optional-skills}/creative/creative-ideation/SKILL.md +0 -0
  1187. /package/agent/{skills → optional-skills}/creative/creative-ideation/references/full-prompt-library.md +0 -0
  1188. /package/agent/{skills → optional-skills}/creative/pixel-art/ATTRIBUTION.md +0 -0
  1189. /package/agent/{skills → optional-skills}/creative/pixel-art/SKILL.md +0 -0
  1190. /package/agent/{skills → optional-skills}/creative/pixel-art/references/palettes.md +0 -0
  1191. /package/agent/{skills → optional-skills}/creative/pixel-art/scripts/__init__.py +0 -0
  1192. /package/agent/{skills → optional-skills}/creative/pixel-art/scripts/palettes.py +0 -0
  1193. /package/agent/{skills → optional-skills}/creative/pixel-art/scripts/pixel_art.py +0 -0
  1194. /package/agent/{skills → optional-skills}/creative/pixel-art/scripts/pixel_art_video.py +0 -0
  1195. /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/SKILL.md +0 -0
  1196. /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/references/analysis-modules.md +0 -0
  1197. /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/references/methods-guide.md +0 -0
  1198. /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/templates/abliteration-config.yaml +0 -0
  1199. /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/templates/analysis-study.yaml +0 -0
  1200. /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/templates/batch-abliteration.yaml +0 -0
  1201. /package/agent/{skills → optional-skills}/mlops/research/DESCRIPTION.md +0 -0
  1202. /package/agent/{skills → optional-skills}/mlops/research/dspy/SKILL.md +0 -0
  1203. /package/agent/{skills → optional-skills}/mlops/research/dspy/references/examples.md +0 -0
  1204. /package/agent/{skills → optional-skills}/mlops/research/dspy/references/modules.md +0 -0
  1205. /package/agent/{skills → optional-skills}/mlops/research/dspy/references/optimizers.md +0 -0
  1206. /package/agent/{skills/red-teaming → optional-skills/security}/godmode/references/jailbreak-templates.md +0 -0
  1207. /package/agent/{skills/red-teaming → optional-skills/security}/godmode/references/refusal-detection.md +0 -0
  1208. /package/agent/{skills/red-teaming → optional-skills/security}/godmode/scripts/godmode_race.py +0 -0
  1209. /package/agent/{skills/red-teaming → optional-skills/security}/godmode/scripts/load_godmode.py +0 -0
  1210. /package/agent/{skills/red-teaming → optional-skills/security}/godmode/scripts/parseltongue.py +0 -0
  1211. /package/agent/{skills/red-teaming → optional-skills/security}/godmode/templates/prefill-subtle.json +0 -0
  1212. /package/agent/{skills/red-teaming → optional-skills/security}/godmode/templates/prefill.json +0 -0
  1213. /package/agent/{skills → optional-skills}/software-development/subagent-driven-development/references/context-budget-discipline.md +0 -0
  1214. /package/agent/{skills → optional-skills}/software-development/subagent-driven-development/references/gates-taxonomy.md +0 -0
@@ -9,6 +9,7 @@ Uses python-telegram-bot library for:
9
9
 
10
10
  import asyncio
11
11
  import dataclasses
12
+ import inspect
12
13
  import json
13
14
  import logging
14
15
  import os
@@ -181,6 +182,8 @@ def _strip_mdv2(text: str) -> str:
181
182
  """
182
183
  # Remove escape backslashes before special characters
183
184
  cleaned = re.sub(r'\\([_*\[\]()~`>#\+\-=|{}.!\\])', r'\1', text)
185
+ # Remove standard markdown bold (**text** → text) BEFORE MarkdownV2 bold
186
+ cleaned = re.sub(r'\*\*([^*]+)\*\*', r'\1', cleaned)
184
187
  # Remove MarkdownV2 bold markers that format_message converted from **bold**
185
188
  cleaned = re.sub(r'\*([^*]+)\*', r'\1', cleaned)
186
189
  # Remove MarkdownV2 italic markers that format_message converted from *italic*
@@ -344,6 +347,13 @@ class TelegramAdapter(BasePlatformAdapter):
344
347
 
345
348
  # Telegram message limits
346
349
  MAX_MESSAGE_LENGTH = 4096
350
+ supports_code_blocks = True # Telegram MarkdownV2 renders fenced code blocks
351
+ # Bot API 10.1 Rich Messages cap the raw markdown/html text at 32,768
352
+ # UTF-8 characters. Content above this is sent via the legacy chunking path.
353
+ RICH_MESSAGE_MAX_CHARS = 32768
354
+ # Backwards-compatible alias for tests/external callers that referenced the
355
+ # initial implementation name. The API limit is character-based, not bytes.
356
+ RICH_MESSAGE_MAX_BYTES = RICH_MESSAGE_MAX_CHARS
347
357
  # Threshold for detecting Telegram client-side message splits.
348
358
  # When a chunk is near this limit, a continuation is almost certain.
349
359
  _SPLIT_THRESHOLD = 4000
@@ -409,6 +419,18 @@ class TelegramAdapter(BasePlatformAdapter):
409
419
  self._mention_patterns = self._compile_mention_patterns()
410
420
  self._reply_to_mode: str = getattr(config, 'reply_to_mode', 'first') or 'first'
411
421
  self._disable_link_previews: bool = self._coerce_bool_extra("disable_link_previews", False)
422
+ # Bot API 10.1 Rich Messages: render constructs the legacy MarkdownV2
423
+ # path degrades (tables → bullet lists, task lists, <details>, block
424
+ # math) via sendRichMessage / editMessageText's rich_message param using
425
+ # the raw agent markdown. Enabled by default; users can opt out for
426
+ # clients that accept but render rich messages poorly via
427
+ # platforms.telegram.extra.rich_messages: false.
428
+ self._rich_messages_enabled: bool = self._coerce_bool_extra("rich_messages", True)
429
+ # Latched off after a capability failure on sendRichMessage /
430
+ # sendRichMessageDraft (e.g. older python-telegram-bot without the
431
+ # endpoint) so later sends skip the doomed rich attempt entirely.
432
+ self._rich_send_disabled: bool = False
433
+ self._rich_draft_disabled: bool = False
412
434
  # Buffer rapid/album photo updates so Telegram image bursts are handled
413
435
  # as a single MessageEvent instead of self-interrupting multiple turns.
414
436
  self._media_batch_delay_seconds = float(os.getenv("HERMES_TELEGRAM_MEDIA_BATCH_DELAY_SECONDS", "0.8"))
@@ -454,6 +476,23 @@ class TelegramAdapter(BasePlatformAdapter):
454
476
  self._forum_command_registered: set[int] = set()
455
477
  # Lock per la registrazione sicura dei comandi nei forum supergroup
456
478
  self._forum_lock = asyncio.Lock()
479
+ # Status indicator: when enabled, the bot's short description (the line
480
+ # shown under its name in the profile) is set to "Online" on connect and
481
+ # "Offline" on clean disconnect, so users can tell whether the gateway is
482
+ # up. Telegram bots have no real presence/online dot (that's a user-account
483
+ # feature), so the short description is the closest available surface.
484
+ # Off by default — this mutates the bot's GLOBAL profile, visible to all
485
+ # users. Opt in via gateway config: extra.status_indicator: true, or set
486
+ # custom strings via extra.status_online / extra.status_offline.
487
+ self._status_indicator_enabled: bool = bool(
488
+ self.config.extra.get("status_indicator", False)
489
+ )
490
+ self._status_online_text: str = str(
491
+ self.config.extra.get("status_online", "Online")
492
+ )
493
+ self._status_offline_text: str = str(
494
+ self.config.extra.get("status_offline", "Offline")
495
+ )
457
496
  # DM Topics config from extra.dm_topics
458
497
  self._dm_topics_config: List[Dict[str, Any]] = self.config.extra.get("dm_topics", [])
459
498
  # Precomputed chat_ids that have DM topics configured (for O(1) root-DM ignore check)
@@ -899,6 +938,456 @@ class TelegramAdapter(BasePlatformAdapter):
899
938
  return {"link_preview_options": LinkPreviewOptions(is_disabled=True)}
900
939
  return {"disable_web_page_preview": True}
901
940
 
941
+ # ------------------------------------------------------------------
942
+ # Bot API 10.1 Rich Messages (sendRichMessage)
943
+ #
944
+ # Final / new-message replies opportunistically use sendRichMessage with
945
+ # the RAW agent markdown so richer constructs (tables, task lists,
946
+ # collapsible details, math, ...) render natively. The legacy MarkdownV2
947
+ # send() path stays as the fallback for unsupported/oversized content and
948
+ # older PTB/clients. Streaming edits stay on Hermes' existing MarkdownV2
949
+ # edit path for now; finalization can re-send as rich and delete the stale
950
+ # preview until rich_message edit support is wired directly.
951
+ # ------------------------------------------------------------------
952
+ def _content_fits_rich_limits(self, content: str) -> bool:
953
+ """Cheap pre-check for the one hard rich limit we can count locally.
954
+
955
+ Only the 32,768 UTF-8 character text cap is enforced here. Other Bot API
956
+ rich limits (500 blocks, 16 nesting levels, 20 table columns, ...) are
957
+ not pre-counted; if exceeded Telegram returns a BadRequest, which
958
+ :meth:`_is_rich_fallback_error` classifies as permanent so the send
959
+ degrades to the legacy chunking path.
960
+ """
961
+ return len(content) <= self.RICH_MESSAGE_MAX_CHARS
962
+
963
+ def _bot_supports_rich(self) -> bool:
964
+ """True when the bound bot can issue raw ``sendRichMessage`` calls.
965
+
966
+ Gates on ``do_api_request`` being an *async* callable. The real
967
+ ``telegram.Bot.do_api_request`` is a coroutine function; test doubles
968
+ that opt into rich set it to an ``AsyncMock`` (also a coroutine
969
+ function). Plain ``MagicMock`` bots expose a *sync* auto-child and
970
+ ``SimpleNamespace`` bots lack the attribute entirely — both resolve to
971
+ ``False`` here, so the legacy path is used unchanged.
972
+ """
973
+ return inspect.iscoroutinefunction(getattr(self._bot, "do_api_request", None))
974
+
975
+ _RICH_DETAILS_RE = re.compile(r"<details\b[^>]*>.*?</details>", re.IGNORECASE | re.DOTALL)
976
+ _RICH_MATH_IN_DETAILS_RE = re.compile(
977
+ r"(\$\$.*?\$\$|"
978
+ r"\\\[.*?\\\]|"
979
+ r"\\\(.*?\\\)|"
980
+ r"\\(?:sum|frac|alpha|beta|gamma|delta|theta|lambda|mu|pi|sigma|"
981
+ r"int|prod|sqrt|lim|infty|begin\{(?:equation|align|matrix|cases)\}))",
982
+ re.IGNORECASE | re.DOTALL,
983
+ )
984
+
985
+ def _has_telegram_desktop_details_math_crash_shape(self, content: str) -> bool:
986
+ """Return True for rich-message details+math content that crashes TDesktop.
987
+
988
+ Telegram Desktop 6.9.1 can crash while rendering Bot API 10.1 rich
989
+ messages containing math inside a collapsible details block
990
+ (telegramdesktop/tdesktop#30808). The Bot API accepts the payload, so
991
+ Hermes must skip rich delivery up front and use the legacy MarkdownV2
992
+ path until affected Desktop clients age out.
993
+ """
994
+ if not content:
995
+ return False
996
+ for details_block in self._RICH_DETAILS_RE.findall(content):
997
+ if self._RICH_MATH_IN_DETAILS_RE.search(details_block):
998
+ return True
999
+ return False
1000
+
1001
+ def _needs_rich_rendering(self, content: str) -> bool:
1002
+ """Return True for markdown constructs that the legacy path degrades.
1003
+
1004
+ Keep ordinary replies on the pre-rich MarkdownV2 path so Telegram
1005
+ clients render a consistent font weight/spacing. The rich endpoint is
1006
+ reserved for constructs where raw markdown materially improves output:
1007
+ pipe tables (MarkdownV2 has no table syntax and rewrites them into
1008
+ bullet lists), GFM task lists, collapsible ``<details>`` blocks, and
1009
+ block math. Adapted from #45995 (@YonganZhang).
1010
+ """
1011
+ if not content:
1012
+ return False
1013
+ if any(_TABLE_SEPARATOR_RE.match(line) for line in content.splitlines()):
1014
+ return True
1015
+ if re.search(r"(?m)^\s*[-*]\s+\[[ xX]\]\s+", content):
1016
+ return True
1017
+ if re.search(r"(?m)^<details\b|^</details>|^<summary\b|^</summary>", content):
1018
+ return True
1019
+ if "$$" in content:
1020
+ return True
1021
+ return False
1022
+
1023
+ def _rich_eligible(self, content: str) -> bool:
1024
+ """Capability/content eligibility for rich, ignoring ``expect_edits``.
1025
+
1026
+ Shared core of :meth:`_should_attempt_rich` minus the per-call
1027
+ ``expect_edits`` metadata gate. The rich EDIT-finalize path
1028
+ (:meth:`_try_edit_rich`) needs this: a streamed preview is sent with
1029
+ ``expect_edits=True`` to stay on the editable path mid-stream, but the
1030
+ FINAL edit should still upgrade to rich when the content warrants it.
1031
+ """
1032
+ return bool(
1033
+ getattr(self, "_rich_messages_enabled", True)
1034
+ and not getattr(self, "_rich_send_disabled", False)
1035
+ and content
1036
+ and content.strip()
1037
+ and self._needs_rich_rendering(content)
1038
+ and not self._has_telegram_desktop_details_math_crash_shape(content)
1039
+ and self._content_fits_rich_limits(content)
1040
+ and self._bot_supports_rich()
1041
+ )
1042
+
1043
+ def _should_attempt_rich(
1044
+ self, content: str, metadata: Optional[Dict[str, Any]] = None
1045
+ ) -> bool:
1046
+ return bool(
1047
+ not (metadata or {}).get("expect_edits")
1048
+ and self._rich_eligible(content)
1049
+ )
1050
+
1051
+ def prefers_fresh_final_streaming(
1052
+ self, content: str, metadata: Optional[Dict[str, Any]] = None
1053
+ ) -> bool:
1054
+ """Whether to replace a streamed preview with a fresh rich final.
1055
+
1056
+ Disabled for Telegram. The fresh-final path briefly shows two copies of
1057
+ the final answer, then deletes the streaming preview after the rich send
1058
+ succeeds — it looks like duplicate delivery at the end of every streamed
1059
+ turn (the reason #46206 reverted it). Rich finalize is instead handled
1060
+ by editing the existing preview in place via Bot API 10.1's
1061
+ ``editMessageText`` ``rich_message`` parameter (see
1062
+ :meth:`_try_edit_rich`), so no fresh re-send / delete is needed.
1063
+ """
1064
+ return False
1065
+
1066
+ def streaming_overflow_limit(self) -> Optional[int]:
1067
+ """Allow the stream consumer to accumulate up to the rich-message cap
1068
+ before splitting, so a reply that fits one ``sendRichMessage`` /
1069
+ ``sendRichMessageDraft`` isn't fragmented at the 4,096 MarkdownV2 limit.
1070
+
1071
+ Gated on the same rich capability as the send path (minus the
1072
+ content-length check — raising that cap is the whole point): rich not
1073
+ latched off and the bot exposes an async ``do_api_request``. Returns
1074
+ ``None`` (→ legacy 4,096 limit) when rich isn't available, so non-rich
1075
+ streams split exactly as before.
1076
+ """
1077
+ if (
1078
+ getattr(self, "_rich_messages_enabled", True)
1079
+ and not getattr(self, "_rich_send_disabled", False)
1080
+ and self._bot_supports_rich()
1081
+ ):
1082
+ return self.RICH_MESSAGE_MAX_CHARS
1083
+ return None
1084
+
1085
+ def _rich_message_payload(
1086
+ self, content: str, *, skip_entity_detection: bool = False
1087
+ ) -> Dict[str, Any]:
1088
+ """Build the ``InputRichMessage`` object from RAW markdown.
1089
+
1090
+ Never pass ``format_message(content)`` here — that converts to
1091
+ MarkdownV2 and would escape/destroy rich syntax like table pipes.
1092
+ """
1093
+ payload: Dict[str, Any] = {"markdown": content}
1094
+ if skip_entity_detection:
1095
+ payload["skip_entity_detection"] = True
1096
+ return payload
1097
+
1098
+ def _is_rich_capability_error(self, exc: Exception) -> bool:
1099
+ """True ⇒ the rich endpoint itself is unavailable (old PTB/server).
1100
+
1101
+ These latch rich off for the rest of the adapter's life — retrying is
1102
+ pointless and would cost a failed roundtrip on every send. Per-message
1103
+ rejections (BadRequest from a parser/limit issue) are NOT capability
1104
+ errors: the next message may be fine.
1105
+ """
1106
+ name = exc.__class__.__name__.lower()
1107
+ if name in {"endpointnotfound", "invalidtoken"}:
1108
+ return True
1109
+ if isinstance(exc, (AttributeError, TypeError, NotImplementedError)):
1110
+ return True
1111
+ if getattr(exc, "error_code", None) == 404:
1112
+ return True
1113
+ s = str(exc).lower()
1114
+ if ("method" in s or "endpoint" in s) and (
1115
+ "not found" in s or "does not exist" in s
1116
+ ):
1117
+ return True
1118
+ return "no such method" in s
1119
+
1120
+ def _is_rich_fallback_error(self, exc: Exception) -> bool:
1121
+ """True ⇒ permanent/capability error ⇒ safe to fall back to legacy.
1122
+
1123
+ Conservative on purpose: only clearly-permanent failures (BadRequest,
1124
+ capability errors, unknown/unsupported endpoint) qualify. Everything
1125
+ else is treated as transient — the rich request may have reached
1126
+ Telegram, so we must NOT legacy-resend and risk a duplicate.
1127
+ """
1128
+ if self._is_bad_request_error(exc):
1129
+ return True
1130
+ if self._is_rich_capability_error(exc):
1131
+ return True
1132
+ s = str(exc).lower()
1133
+ return "unsupported" in s or "not implemented" in s
1134
+
1135
+ def _compute_single_send_routing(
1136
+ self,
1137
+ chat_id: str,
1138
+ reply_to: Optional[str],
1139
+ metadata: Optional[Dict[str, Any]],
1140
+ thread_id: Optional[str],
1141
+ ) -> Optional[tuple]:
1142
+ """Routing for a single (rich) send — mirrors send()'s index-0 block.
1143
+
1144
+ Returns ``(reply_to_id, thread_kwargs)``, or ``None`` to signal "skip
1145
+ rich, let the legacy path handle it" — used for the DM-topic fail-loud
1146
+ case so the legacy path stays the single source of the refuse result.
1147
+ """
1148
+ metadata_reply_to = self._metadata_reply_to_message_id(metadata)
1149
+ private_dm_topic_send = self._is_private_dm_topic_send(chat_id, thread_id, metadata)
1150
+ dm_topic_reply_to_off = (
1151
+ private_dm_topic_send
1152
+ and self._reply_to_mode == "off"
1153
+ and bool(metadata and metadata.get("telegram_dm_topic_reply_fallback"))
1154
+ )
1155
+ reply_to_source = reply_to or (
1156
+ str(metadata_reply_to)
1157
+ if private_dm_topic_send and metadata_reply_to is not None
1158
+ else None
1159
+ )
1160
+ if private_dm_topic_send:
1161
+ should_thread = reply_to_source is not None and self._reply_to_mode != "off"
1162
+ else:
1163
+ should_thread = self._should_thread_reply(reply_to_source, 0)
1164
+ reply_to_id = int(reply_to_source) if should_thread and reply_to_source else None
1165
+ if private_dm_topic_send and reply_to_id is None and not dm_topic_reply_to_off:
1166
+ # Refusing to send outside the requested DM topic — defer to the
1167
+ # legacy path, which returns the canonical fail-loud SendResult.
1168
+ return None
1169
+ thread_kwargs = self._thread_kwargs_for_send(
1170
+ chat_id,
1171
+ thread_id,
1172
+ metadata,
1173
+ reply_to_message_id=reply_to_id,
1174
+ reply_to_mode=self._reply_to_mode,
1175
+ )
1176
+ return reply_to_id, thread_kwargs
1177
+
1178
+ async def _try_send_rich(
1179
+ self,
1180
+ chat_id: str,
1181
+ content: str,
1182
+ reply_to: Optional[str],
1183
+ metadata: Optional[Dict[str, Any]],
1184
+ ) -> Optional[SendResult]:
1185
+ """Attempt a single ``sendRichMessage`` send.
1186
+
1187
+ Returns a :class:`SendResult` (success, or a transient failure that the
1188
+ caller must NOT legacy-resend), or ``None`` to signal "fall back to the
1189
+ legacy MarkdownV2 path" (permanent/capability error or DM-topic skip).
1190
+ """
1191
+ thread_id = self._metadata_thread_id(metadata)
1192
+ routing = self._compute_single_send_routing(chat_id, reply_to, metadata, thread_id)
1193
+ if routing is None:
1194
+ return None
1195
+ reply_to_id, thread_kwargs = routing
1196
+
1197
+ payload: Dict[str, Any] = {
1198
+ "chat_id": int(chat_id),
1199
+ "rich_message": self._rich_message_payload(content),
1200
+ }
1201
+ # Only forward non-None routing keys: when direct_messages_topic_id is
1202
+ # present _thread_kwargs_for_send pairs it with message_thread_id=None,
1203
+ # which must not be sent as a stray field on the raw endpoint.
1204
+ payload.update({k: v for k, v in thread_kwargs.items() if v is not None})
1205
+ payload.update(self._notification_kwargs(metadata))
1206
+ if getattr(self, "_disable_link_previews", False):
1207
+ payload["link_preview_options"] = {"is_disabled": True}
1208
+ if reply_to_id is not None:
1209
+ # Spec: sendRichMessage takes reply_parameters (ReplyParameters
1210
+ # object), NOT the legacy reply_to_message_id scalar. Unknown
1211
+ # params are silently ignored by the Bot API, so the scalar would
1212
+ # quietly drop the reply anchor instead of erroring.
1213
+ payload["reply_parameters"] = {"message_id": reply_to_id}
1214
+
1215
+ try:
1216
+ # Take the raw Bot API result (dict under real PTB). Passing
1217
+ # return_type=Message would make PTB deserialize a Bot API 10.1
1218
+ # response shape it does not fully model yet; a post-delivery parse
1219
+ # error must not be mistaken for a sendable failure.
1220
+ msg = await self._bot.do_api_request(
1221
+ "sendRichMessage", api_kwargs=payload
1222
+ )
1223
+ except Exception as exc:
1224
+ if self._is_rich_fallback_error(exc):
1225
+ if self._is_rich_capability_error(exc):
1226
+ # Endpoint missing (old PTB/server) — latch rich off so
1227
+ # every later send doesn't pay a doomed extra roundtrip.
1228
+ self._rich_send_disabled = True
1229
+ logger.debug(
1230
+ "[%s] sendRichMessage rejected (%s) — falling back to MarkdownV2",
1231
+ self.name, exc,
1232
+ )
1233
+ return None
1234
+ # Transient / network / unknown: the request may have reached
1235
+ # Telegram. Do NOT legacy-resend (duplicate risk); surface a
1236
+ # failure with retry semantics mirroring the legacy send() except.
1237
+ err_str = str(exc).lower()
1238
+ try:
1239
+ from telegram.error import TimedOut as _TimedOut
1240
+ except (ImportError, AttributeError):
1241
+ _TimedOut = None
1242
+ is_timeout = (_TimedOut and isinstance(exc, _TimedOut)) or "timed out" in err_str
1243
+ is_connect_timeout = self._looks_like_connect_timeout(exc)
1244
+ logger.warning(
1245
+ "[%s] sendRichMessage transient failure (no legacy resend): %s",
1246
+ self.name, exc,
1247
+ )
1248
+ return SendResult(
1249
+ success=False,
1250
+ error=str(exc),
1251
+ retryable=(is_connect_timeout or not is_timeout),
1252
+ )
1253
+
1254
+ message_id = None
1255
+ if isinstance(msg, dict):
1256
+ message_id = msg.get("message_id")
1257
+ if message_id is None:
1258
+ message_id = (msg.get("result") or {}).get("message_id")
1259
+ else:
1260
+ message_id = getattr(msg, "message_id", None)
1261
+ if message_id is not None:
1262
+ # Telegram won't echo rich content in reply_to_message, so remember
1263
+ # what we sent — replies to this message resolve via this index.
1264
+ try:
1265
+ from gateway import rich_sent_store
1266
+ rich_sent_store.record(str(chat_id), str(message_id), content)
1267
+ except Exception:
1268
+ pass
1269
+ return SendResult(
1270
+ success=True,
1271
+ message_id=str(message_id) if message_id is not None else None,
1272
+ )
1273
+
1274
+ async def _try_edit_rich(
1275
+ self,
1276
+ chat_id: str,
1277
+ message_id: str,
1278
+ content: str,
1279
+ ) -> Optional[SendResult]:
1280
+ """Edit an existing message in place as a rich message (Bot API 10.1).
1281
+
1282
+ Uses ``editMessageText`` with the ``rich_message`` parameter so a
1283
+ streamed preview can finalize as rich (tables/task lists/details/math)
1284
+ WITHOUT a fresh send + delete — no duplicate preview. Mirrors
1285
+ :meth:`_try_send_rich`'s error contract:
1286
+
1287
+ - success → ``SendResult(success=True, message_id=...)``
1288
+ - permanent / capability error → ``None`` (caller falls back to the
1289
+ legacy MarkdownV2 edit; capability errors latch rich off)
1290
+ - transient / unknown → ``SendResult(success=False)`` with retry
1291
+ semantics (the message may already be edited; do NOT legacy-resend)
1292
+ """
1293
+ payload: Dict[str, Any] = {
1294
+ "chat_id": int(chat_id),
1295
+ "message_id": int(message_id),
1296
+ "rich_message": self._rich_message_payload(content),
1297
+ }
1298
+ if getattr(self, "_disable_link_previews", False):
1299
+ payload["link_preview_options"] = {"is_disabled": True}
1300
+ try:
1301
+ # Raw Bot API result; do not request return_type=Message (PTB does
1302
+ # not fully model the 10.1 response shape yet — a post-edit parse
1303
+ # error must not be mistaken for a failed edit).
1304
+ await self._bot.do_api_request("editMessageText", api_kwargs=payload)
1305
+ except Exception as exc:
1306
+ if self._is_rich_fallback_error(exc):
1307
+ if self._is_rich_capability_error(exc):
1308
+ self._rich_send_disabled = True
1309
+ # "Message is not modified" — content identical to the current
1310
+ # rich message; treat as a successful no-op so the caller does
1311
+ # not fall through to a redundant legacy edit.
1312
+ if "not modified" in str(exc).lower():
1313
+ return SendResult(success=True, message_id=message_id)
1314
+ logger.debug(
1315
+ "[%s] rich editMessageText rejected (%s) — falling back to MarkdownV2 edit",
1316
+ self.name, exc,
1317
+ )
1318
+ return None
1319
+ if "not modified" in str(exc).lower():
1320
+ return SendResult(success=True, message_id=message_id)
1321
+ err_str = str(exc).lower()
1322
+ try:
1323
+ from telegram.error import TimedOut as _TimedOut
1324
+ except (ImportError, AttributeError):
1325
+ _TimedOut = None
1326
+ is_timeout = (_TimedOut and isinstance(exc, _TimedOut)) or "timed out" in err_str
1327
+ is_connect_timeout = self._looks_like_connect_timeout(exc)
1328
+ logger.warning(
1329
+ "[%s] rich editMessageText transient failure (no legacy resend): %s",
1330
+ self.name, exc,
1331
+ )
1332
+ return SendResult(
1333
+ success=False,
1334
+ error=str(exc),
1335
+ retryable=(is_connect_timeout or not is_timeout),
1336
+ )
1337
+ return SendResult(success=True, message_id=message_id)
1338
+
1339
+ def _should_attempt_rich_draft(self, content: str) -> bool:
1340
+ return bool(
1341
+ getattr(self, "_rich_messages_enabled", True)
1342
+ and not getattr(self, "_rich_send_disabled", False)
1343
+ and not getattr(self, "_rich_draft_disabled", False)
1344
+ and content
1345
+ and content.strip()
1346
+ and not self._has_telegram_desktop_details_math_crash_shape(content)
1347
+ and self._content_fits_rich_limits(content)
1348
+ and self._bot_supports_rich()
1349
+ )
1350
+
1351
+ async def _try_send_rich_draft(
1352
+ self,
1353
+ chat_id: str,
1354
+ draft_id: int,
1355
+ content: str,
1356
+ metadata: Optional[Dict[str, Any]],
1357
+ ) -> bool:
1358
+ """Emit one ``sendRichMessageDraft`` preview frame; True on success.
1359
+
1360
+ Draft frames are ephemeral and overwritten by the next frame / the
1361
+ final ``sendRichMessage``, so a duplicate or lost rich draft is
1362
+ harmless — any failure simply returns False and the caller renders the
1363
+ legacy plain-text draft. A permanent/capability failure additionally
1364
+ latches ``_rich_draft_disabled`` so later frames skip the rich attempt.
1365
+ """
1366
+ payload: Dict[str, Any] = {
1367
+ "chat_id": int(chat_id),
1368
+ "draft_id": int(draft_id),
1369
+ "rich_message": self._rich_message_payload(content),
1370
+ }
1371
+ thread_id = self._metadata_thread_id(metadata)
1372
+ if thread_id is not None:
1373
+ payload["message_thread_id"] = int(thread_id)
1374
+ try:
1375
+ ok = await self._bot.do_api_request("sendRichMessageDraft", api_kwargs=payload)
1376
+ return bool(ok)
1377
+ except Exception as exc:
1378
+ if self._is_rich_capability_error(exc):
1379
+ self._rich_draft_disabled = True
1380
+ logger.debug(
1381
+ "[%s] sendRichMessageDraft unsupported (%s) — using legacy drafts",
1382
+ self.name, exc,
1383
+ )
1384
+ else:
1385
+ logger.debug(
1386
+ "[%s] sendRichMessageDraft transient failure (%s) — legacy draft this frame",
1387
+ self.name, exc,
1388
+ )
1389
+ return False
1390
+
902
1391
  async def _drain_polling_connections(self) -> None:
903
1392
  """Reset the httpx connection pool used for getUpdates polling.
904
1393
 
@@ -1142,7 +1631,13 @@ class TelegramAdapter(BasePlatformAdapter):
1142
1631
  # gateway process is alive and reports "connected" but
1143
1632
  # no messages are received or sent.
1144
1633
  if self._polling_conflict_count < MAX_CONFLICT_RETRIES:
1145
- loop = asyncio.get_event_loop()
1634
+ # We are inside a running coroutine, so the running loop is
1635
+ # guaranteed to exist. asyncio.get_event_loop() is deprecated
1636
+ # and raises "RuntimeError: There is no current event loop in
1637
+ # thread 'MainThread'" on Python 3.10+ when invoked from a
1638
+ # context without an attached loop (which can happen when PTB
1639
+ # dispatches this error callback). Use get_running_loop().
1640
+ loop = asyncio.get_running_loop()
1146
1641
  self._polling_error_task = loop.create_task(
1147
1642
  self._handle_polling_conflict(retry_err)
1148
1643
  )
@@ -1767,6 +2262,13 @@ class TelegramAdapter(BasePlatformAdapter):
1767
2262
  mode = "webhook" if self._webhook_mode else "polling"
1768
2263
  logger.info("[%s] Connected to Telegram (%s mode)", self.name, mode)
1769
2264
 
2265
+ # Surface the gateway as "Online" in the bot's short description
2266
+ # (opt-in via extra.status_indicator). Non-fatal.
2267
+ try:
2268
+ await self._set_status_indicator(online=True)
2269
+ except Exception:
2270
+ pass
2271
+
1770
2272
  # Set up DM topics (Bot API 9.4 — Private Chat Topics)
1771
2273
  # Runs after connection is established so the bot can call createForumTopic.
1772
2274
  # Failures here are non-fatal — the bot works fine without topics.
@@ -1787,8 +2289,47 @@ class TelegramAdapter(BasePlatformAdapter):
1787
2289
  logger.error("[%s] Failed to connect to Telegram: %s", self.name, e, exc_info=True)
1788
2290
  return False
1789
2291
 
2292
+ async def _set_status_indicator(self, online: bool) -> None:
2293
+ """Set the bot's short description to the online/offline status text.
2294
+
2295
+ The short description is the line shown under the bot's name in its
2296
+ profile. It is the closest Bot API surface to a presence indicator —
2297
+ bots have no real online/offline dot (that's a user-account feature).
2298
+
2299
+ No-op unless ``extra.status_indicator`` is enabled. Best-effort: any
2300
+ failure is logged at debug and swallowed so it never blocks connect or
2301
+ disconnect. The default (no language_code) description applies to every
2302
+ user who doesn't have a language-specific one set.
2303
+ """
2304
+ if not getattr(self, "_status_indicator_enabled", False):
2305
+ return
2306
+ bot = self._bot
2307
+ if bot is None:
2308
+ return
2309
+ text = self._status_online_text if online else self._status_offline_text
2310
+ # Telegram caps short_description at 120 chars.
2311
+ text = text[:120]
2312
+ try:
2313
+ await bot.set_my_short_description(short_description=text)
2314
+ logger.info("[%s] Set bot status indicator to %r", self.name, text)
2315
+ except Exception as e:
2316
+ logger.debug(
2317
+ "[%s] Failed to set bot status indicator to %r: %s",
2318
+ self.name, text, e,
2319
+ )
2320
+
1790
2321
  async def disconnect(self) -> None:
1791
2322
  """Stop polling/webhook, cancel pending album flushes, and disconnect."""
2323
+ # Mark the bot "Offline" in its short description while the bot's HTTP
2324
+ # client is still alive (before app shutdown closes it). Opt-in via
2325
+ # extra.status_indicator. Non-fatal. This is the clean-shutdown path;
2326
+ # a hard crash leaves the last-known status, which is the expected
2327
+ # limitation of a profile-text indicator.
2328
+ try:
2329
+ await self._set_status_indicator(online=False)
2330
+ except Exception:
2331
+ pass
2332
+
1792
2333
  pending_media_group_tasks = list(self._media_group_tasks.values())
1793
2334
  for task in pending_media_group_tasks:
1794
2335
  task.cancel()
@@ -1860,6 +2401,22 @@ class TelegramAdapter(BasePlatformAdapter):
1860
2401
  return SendResult(success=True, message_id=None)
1861
2402
 
1862
2403
  try:
2404
+ # Bot API 10.1 rich fast-path: send the raw agent markdown via
2405
+ # sendRichMessage so tables/task lists/etc. render natively. Falls
2406
+ # through to the legacy MarkdownV2 path on permanent/capability
2407
+ # errors or DM-topic routing skips; returns directly on success or
2408
+ # on a transient failure (which must NOT be legacy-resent).
2409
+ if self._should_attempt_rich(content, metadata=metadata):
2410
+ rich_result = await self._try_send_rich(chat_id, content, reply_to, metadata)
2411
+ if rich_result is not None:
2412
+ if rich_result.success:
2413
+ # Re-trigger typing like the legacy success path does.
2414
+ try:
2415
+ await self.send_typing(chat_id, metadata=metadata)
2416
+ except Exception:
2417
+ pass # Typing failures are non-fatal
2418
+ return rich_result
2419
+
1863
2420
  # Format and split message if needed
1864
2421
  formatted = self.format_message(content)
1865
2422
  chunks = self.truncate_message(
@@ -2173,6 +2730,21 @@ class TelegramAdapter(BasePlatformAdapter):
2173
2730
  if not self._bot:
2174
2731
  return SendResult(success=False, error="Not connected")
2175
2732
 
2733
+ # Rich finalize (Bot API 10.1): when the completed content has
2734
+ # constructs the legacy MarkdownV2 edit degrades (tables → bullet
2735
+ # lists, task lists, <details>, block math) and rich is available,
2736
+ # edit the preview IN PLACE via editMessageText's rich_message param.
2737
+ # No fresh send + delete → no duplicate preview (the problem #46206
2738
+ # reverted the fresh-final path for). Attempted before the 4,096
2739
+ # overflow pre-flight because the rich text cap is 32,768 — a rich
2740
+ # table that exceeds the MarkdownV2 limit must not be split into legacy
2741
+ # chunks. Falls back to the legacy edit path (overflow split included)
2742
+ # on capability/permanent rejection.
2743
+ if finalize and self._rich_eligible(content):
2744
+ rich_result = await self._try_edit_rich(chat_id, message_id, content)
2745
+ if rich_result is not None:
2746
+ return rich_result
2747
+
2176
2748
  # Pre-flight: if content already exceeds the limit, split-and-deliver
2177
2749
  # without round-tripping a doomed edit.
2178
2750
  if utf16_len(content) > self.MAX_MESSAGE_LENGTH:
@@ -2201,11 +2773,17 @@ class TelegramAdapter(BasePlatformAdapter):
2201
2773
  # "Message is not modified" is a no-op, not an error
2202
2774
  if "not modified" in str(fmt_err).lower():
2203
2775
  return SendResult(success=True, message_id=message_id)
2204
- # Fallback: retry without markdown formatting
2776
+ # Fallback: strip MarkdownV2 escapes and retry as clean plain text
2777
+ logger.warning(
2778
+ "[%s] MarkdownV2 edit failed, falling back to plain text: %s",
2779
+ self.name,
2780
+ fmt_err,
2781
+ )
2782
+ _plain = _strip_mdv2(content) if content else content
2205
2783
  await self._bot.edit_message_text(
2206
2784
  chat_id=int(chat_id),
2207
2785
  message_id=int(message_id),
2208
- text=content,
2786
+ text=_plain,
2209
2787
  )
2210
2788
  return SendResult(success=True, message_id=message_id)
2211
2789
  except Exception as e:
@@ -2333,10 +2911,15 @@ class TelegramAdapter(BasePlatformAdapter):
2333
2911
  )
2334
2912
  except Exception as fmt_err:
2335
2913
  if "not modified" not in str(fmt_err).lower():
2914
+ logger.warning(
2915
+ "[%s] Overflow split: MarkdownV2 first-chunk edit "
2916
+ "failed, falling back to plain text: %s",
2917
+ self.name, fmt_err,
2918
+ )
2336
2919
  await self._bot.edit_message_text(
2337
2920
  chat_id=int(chat_id),
2338
2921
  message_id=int(message_id),
2339
- text=first_chunk,
2922
+ text=_strip_mdv2(first_chunk),
2340
2923
  )
2341
2924
  else:
2342
2925
  await self._bot.edit_message_text(
@@ -2364,6 +2947,7 @@ class TelegramAdapter(BasePlatformAdapter):
2364
2947
  # are already correctly sized). Best-effort MarkdownV2 with plain
2365
2948
  # fallback, mirroring send().
2366
2949
  continuation_ids: list[str] = []
2950
+ delivered_chunks = [first_chunk]
2367
2951
  prev_id = message_id
2368
2952
  thread_id = self._metadata_thread_id(metadata)
2369
2953
  for chunk in chunks[1:]:
@@ -2377,7 +2961,14 @@ class TelegramAdapter(BasePlatformAdapter):
2377
2961
  )
2378
2962
  for use_markdown in (True, False) if finalize else (False,):
2379
2963
  try:
2380
- text = self.format_message(chunk) if use_markdown else chunk
2964
+ if use_markdown:
2965
+ text = self.format_message(chunk)
2966
+ else:
2967
+ # Plain attempt: on finalize the MarkdownV2 attempt
2968
+ # failed, so degrade to clean stripped text, never
2969
+ # the raw chunk (raw ** / ``` markers would render
2970
+ # literally); streaming previews stay raw.
2971
+ text = _strip_mdv2(chunk) if finalize else chunk
2381
2972
  sent_msg = await self._bot.send_message(
2382
2973
  chat_id=int(chat_id),
2383
2974
  text=text,
@@ -2403,7 +2994,7 @@ class TelegramAdapter(BasePlatformAdapter):
2403
2994
  try:
2404
2995
  sent_msg = await self._bot.send_message(
2405
2996
  chat_id=int(chat_id),
2406
- text=chunk,
2997
+ text=_strip_mdv2(chunk) if finalize else chunk,
2407
2998
  **retry_thread_kwargs,
2408
2999
  **self._link_preview_kwargs(),
2409
3000
  **self._notification_kwargs(metadata),
@@ -2427,17 +3018,37 @@ class TelegramAdapter(BasePlatformAdapter):
2427
3018
  break
2428
3019
  if sent_msg is None:
2429
3020
  # Continuation failed — the user has chunk 1 + however many
2430
- # continuations succeeded. Report success with what we got
2431
- # so the stream consumer knows the edit landed; the
2432
- # remaining tail is lost on this attempt and the next
2433
- # streaming tick may retry.
3021
+ # continuations succeeded, but NOT the full response. Do not
3022
+ # report success: the stream consumer treats a successful edit
3023
+ # as final delivery on got_done, which would suppress fallback
3024
+ # delivery and leave the Telegram topic clipped after the last
3025
+ # delivered chunk.
2434
3026
  logger.warning(
2435
3027
  "[%s] Overflow split: stopped at %d/%d chunks delivered",
2436
3028
  self.name, 1 + len(continuation_ids), len(chunks),
2437
3029
  )
2438
- break
3030
+ delivered_prefix = "".join(
3031
+ re.sub(r" \(\d+/\d+\)$", "", delivered)
3032
+ for delivered in delivered_chunks
3033
+ )
3034
+ return SendResult(
3035
+ success=False,
3036
+ message_id=prev_id,
3037
+ error="overflow_continuation_failed",
3038
+ retryable=True,
3039
+ raw_response={
3040
+ "partial_overflow": True,
3041
+ "delivered_chunks": 1 + len(continuation_ids),
3042
+ "total_chunks": len(chunks),
3043
+ "last_message_id": prev_id,
3044
+ "delivered_prefix": delivered_prefix,
3045
+ "continuation_message_ids": tuple(continuation_ids),
3046
+ },
3047
+ continuation_message_ids=tuple(continuation_ids),
3048
+ )
2439
3049
  new_id = str(getattr(sent_msg, "message_id", "")) or prev_id
2440
3050
  continuation_ids.append(new_id)
3051
+ delivered_chunks.append(chunk)
2441
3052
  prev_id = new_id
2442
3053
 
2443
3054
  last_id = continuation_ids[-1] if continuation_ids else message_id
@@ -2502,17 +3113,30 @@ class TelegramAdapter(BasePlatformAdapter):
2502
3113
  content: str,
2503
3114
  metadata: Optional[Dict[str, Any]] = None,
2504
3115
  ) -> SendResult:
2505
- """Stream a partial message via Telegram's native sendMessageDraft.
2506
-
2507
- The Bot API animates the preview when the same ``draft_id`` is reused
2508
- across consecutive calls in the same chat. When the response
2509
- finishes, the caller sends the final text via the normal ``send``
2510
- path; the draft preview clears naturally on the client (Telegram has
2511
- no Bot API to "promote" a draft to a real message — the final
2512
- ``sendMessage`` is what the user receives in their history).
3116
+ """Stream a partial message via Telegram's native draft API.
3117
+
3118
+ Uses ``sendRichMessageDraft`` (Bot API 10.1) with the raw markdown when
3119
+ rich messages are enabled and supported, otherwise the plain-text
3120
+ ``sendMessageDraft``. The Bot API animates the preview when the same
3121
+ ``draft_id`` is reused across consecutive calls in the same chat. When
3122
+ the response finishes, the caller sends the final text via the normal
3123
+ ``send`` path; the draft preview clears naturally on the client
3124
+ (Telegram has no Bot API to "promote" a draft to a real message — the
3125
+ final ``sendMessage``/``sendRichMessage`` is what the user receives in
3126
+ their history).
2513
3127
  """
2514
3128
  if not self._bot:
2515
3129
  return SendResult(success=False, error="not_connected")
3130
+
3131
+ # Rich draft fast-path (Bot API 10.1 sendRichMessageDraft): render the
3132
+ # streaming preview with the same raw markdown the final
3133
+ # sendRichMessage will persist, so the animated draft matches the final
3134
+ # message. Any failure degrades to the legacy plain-text draft below.
3135
+ if self._should_attempt_rich_draft(content):
3136
+ if await self._try_send_rich_draft(chat_id, draft_id, content, metadata):
3137
+ # Drafts have no message_id; report success without one.
3138
+ return SendResult(success=True, message_id=None)
3139
+
2516
3140
  if not hasattr(self._bot, "send_message_draft"):
2517
3141
  return SendResult(success=False, error="api_unavailable")
2518
3142
 
@@ -3015,7 +3639,7 @@ class TelegramAdapter(BasePlatformAdapter):
3015
3639
  async def _handle_model_picker_callback(
3016
3640
  self, query, data: str, chat_id: str
3017
3641
  ) -> None:
3018
- """Handle model picker inline keyboard callbacks (mp:/mm:/mb:/mx:/mg:)."""
3642
+ """Handle model picker inline keyboard callbacks (mp:/mm:/mc:/mb:/mx:/mg:)."""
3019
3643
  state = self._model_picker_state.get(chat_id)
3020
3644
  if not state:
3021
3645
  await query.answer(text="Picker expired — use /model again.")
@@ -3100,6 +3724,55 @@ class TelegramAdapter(BasePlatformAdapter):
3100
3724
  )
3101
3725
  await query.answer()
3102
3726
 
3727
+ elif data.startswith("mc:"):
3728
+ # --- Expensive model confirmed: perform the switch ---
3729
+ try:
3730
+ idx = int(data[3:])
3731
+ except ValueError:
3732
+ await query.answer(text="Invalid selection.")
3733
+ return
3734
+
3735
+ model_list = state.get("model_list", [])
3736
+ if idx < 0 or idx >= len(model_list):
3737
+ await query.answer(text="Invalid model index.")
3738
+ return
3739
+
3740
+ model_id = model_list[idx]
3741
+ provider_slug = state.get("selected_provider", "")
3742
+ callback = state.get("on_model_selected")
3743
+
3744
+ if not callback:
3745
+ await query.answer(text="Picker expired.")
3746
+ return
3747
+
3748
+ switch_failed = False
3749
+ try:
3750
+ result_text = await callback(chat_id, model_id, provider_slug)
3751
+ except Exception as exc:
3752
+ logger.error("Model picker switch failed: %s", exc)
3753
+ result_text = f"Error switching model: {exc}"
3754
+ switch_failed = True
3755
+
3756
+ try:
3757
+ await query.edit_message_text(
3758
+ text=self.format_message(result_text),
3759
+ parse_mode=ParseMode.MARKDOWN_V2,
3760
+ reply_markup=None,
3761
+ )
3762
+ except Exception:
3763
+ try:
3764
+ await query.edit_message_text(
3765
+ text=result_text,
3766
+ parse_mode=None,
3767
+ reply_markup=None,
3768
+ )
3769
+ except Exception:
3770
+ pass
3771
+ await query.answer(
3772
+ text="Switch failed." if switch_failed else "Model switched!"
3773
+ )
3774
+ self._model_picker_state.pop(chat_id, None)
3775
+
3103
3776
  elif data.startswith("mm:"):
3104
3777
  # --- Model selected: perform the switch ---
3105
3778
  try:
@@ -3121,11 +3794,43 @@ class TelegramAdapter(BasePlatformAdapter):
3121
3794
  await query.answer(text="Picker expired.")
3122
3795
  return
3123
3796
 
3797
+ try:
3798
+ from hermes_cli.model_cost_guard import expensive_model_warning
3799
+
3800
+ # Pricing lookup can hit models.dev / a /models endpoint on a
3801
+ # cache miss — keep it off the event loop.
3802
+ warning = await asyncio.to_thread(
3803
+ expensive_model_warning,
3804
+ model_id,
3805
+ provider=provider_slug,
3806
+ )
3807
+ except Exception:
3808
+ warning = None
3809
+ if warning is not None:
3810
+ keyboard = InlineKeyboardMarkup([
3811
+ [InlineKeyboardButton("Switch anyway", callback_data=f"mc:{idx}")],
3812
+ [
3813
+ InlineKeyboardButton("◀ Back", callback_data="mb"),
3814
+ InlineKeyboardButton("✗ Cancel", callback_data="mx"),
3815
+ ],
3816
+ ])
3817
+ await query.edit_message_text(
3818
+ text=self.format_message(
3819
+ f"⚠ *Expensive Model Warning*\n\n{warning.message}"
3820
+ ),
3821
+ parse_mode=ParseMode.MARKDOWN_V2,
3822
+ reply_markup=keyboard,
3823
+ )
3824
+ await query.answer(text="Confirm expensive model")
3825
+ return
3826
+
3827
+ switch_failed = False
3124
3828
  try:
3125
3829
  result_text = await callback(chat_id, model_id, provider_slug)
3126
3830
  except Exception as exc:
3127
3831
  logger.error("Model picker switch failed: %s", exc)
3128
3832
  result_text = f"Error switching model: {exc}"
3833
+ switch_failed = True
3129
3834
 
3130
3835
  # Edit message to show confirmation, remove buttons
3131
3836
  try:
@@ -3144,7 +3849,9 @@ class TelegramAdapter(BasePlatformAdapter):
3144
3849
  )
3145
3850
  except Exception:
3146
3851
  pass
3147
- await query.answer(text="Model switched!")
3852
+ await query.answer(
3853
+ text="Switch failed." if switch_failed else "Model switched!"
3854
+ )
3148
3855
 
3149
3856
  # Clean up state
3150
3857
  self._model_picker_state.pop(chat_id, None)
@@ -3245,7 +3952,7 @@ class TelegramAdapter(BasePlatformAdapter):
3245
3952
  query_user_name = getattr(query.from_user, "first_name", None)
3246
3953
 
3247
3954
  # --- Model picker callbacks ---
3248
- if data.startswith(("mp:", "mpg:", "mm:", "mb", "mx", "mg:")):
3955
+ if data.startswith(("mp:", "mpg:", "mm:", "mc:", "mb", "mx", "mg:")):
3249
3956
  chat_id = str(query.message.chat_id) if query.message else None
3250
3957
  if chat_id:
3251
3958
  await self._handle_model_picker_callback(query, data, chat_id)
@@ -3706,6 +4413,33 @@ class TelegramAdapter(BasePlatformAdapter):
3706
4413
  )
3707
4414
  return error
3708
4415
 
4416
+ def _telegram_media_too_large_note(self, label: str, file_size: Any, max_bytes: int) -> str:
4417
+ limit_mb = max(1, max_bytes // (1024 * 1024))
4418
+ try:
4419
+ size_mb = int(file_size or 0) / (1024 * 1024)
4420
+ size_text = f"{size_mb:.1f} MB"
4421
+ except (TypeError, ValueError):
4422
+ size_text = "unknown size"
4423
+ return (
4424
+ f"[Telegram {label} skipped: file size {size_text} exceeds the "
4425
+ f"{limit_mb} MB limit. Ask the user to send a shorter voice note "
4426
+ "or a smaller audio file.]"
4427
+ )
4428
+
4429
+ def _telegram_media_size_allowed(self, source: Any, label: str) -> tuple[bool, Optional[str]]:
4430
+ """Validate Telegram media size before downloading into memory."""
4431
+ max_bytes = int(getattr(self, "_max_doc_bytes", 20 * 1024 * 1024) or 20 * 1024 * 1024)
4432
+ file_size = getattr(source, "file_size", None)
4433
+ try:
4434
+ size = int(file_size or 0)
4435
+ except (TypeError, ValueError):
4436
+ size = 0
4437
+ if size <= 0:
4438
+ return True, None
4439
+ if size <= max_bytes:
4440
+ return True, None
4441
+ return False, self._telegram_media_too_large_note(label, size, max_bytes)
4442
+
3709
4443
  async def send_voice(
3710
4444
  self,
3711
4445
  chat_id: str,
@@ -4073,7 +4807,7 @@ class TelegramAdapter(BasePlatformAdapter):
4073
4807
  )
4074
4808
  return SendResult(success=True, message_id=str(msg.message_id))
4075
4809
  except Exception as e:
4076
- print(f"[{self.name}] Failed to send document: {e}")
4810
+ logger.warning("[%s] Failed to send document: %s", self.name, e, exc_info=True)
4077
4811
  return await super().send_document(chat_id, file_path, caption, file_name, reply_to, metadata=metadata)
4078
4812
 
4079
4813
  async def send_video(
@@ -4120,7 +4854,7 @@ class TelegramAdapter(BasePlatformAdapter):
4120
4854
  )
4121
4855
  return SendResult(success=True, message_id=str(msg.message_id))
4122
4856
  except Exception as e:
4123
- print(f"[{self.name}] Failed to send video: {e}")
4857
+ logger.warning("[%s] Failed to send video: %s", self.name, e, exc_info=True)
4124
4858
  return await super().send_video(chat_id, video_path, caption, reply_to, metadata=metadata)
4125
4859
 
4126
4860
  async def send_image(
@@ -5009,6 +5743,52 @@ class TelegramAdapter(BasePlatformAdapter):
5009
5743
  event.text = self._append_observed_note(event.text, cached.context_note())
5010
5744
  logger.info("[Telegram] Cached observed group %s at %s", cached.kind, cached.path)
5011
5745
 
5746
+ async def _cache_replied_media(self, msg: Any, event: MessageEvent) -> None:
5747
+ """Cache media from the message this turn replies to, if any."""
5748
+ from gateway.platforms.base import cache_media_bytes
5749
+
5750
+ reply_msg = getattr(msg, "reply_to_message", None)
5751
+ if reply_msg is None:
5752
+ return
5753
+ source, filename, mime, kind = self._observed_media_source(reply_msg)
5754
+ if source is None:
5755
+ return
5756
+
5757
+ max_bytes = getattr(self, "_max_doc_bytes", 20 * 1024 * 1024)
5758
+ file_size = getattr(source, "file_size", None)
5759
+ try:
5760
+ size = int(file_size or 0)
5761
+ except (TypeError, ValueError):
5762
+ size = 0
5763
+ if not (0 < size <= max_bytes):
5764
+ return
5765
+
5766
+ try:
5767
+ file_obj = await source.get_file()
5768
+ data = bytes(await file_obj.download_as_bytearray())
5769
+ if not filename:
5770
+ filename = os.path.basename(getattr(file_obj, "file_path", "") or "")
5771
+ cached = cache_media_bytes(data, filename=filename, mime_type=mime, default_kind=kind)
5772
+ except Exception as exc:
5773
+ logger.warning("[Telegram] Failed to cache replied-to media: %s", exc, exc_info=True)
5774
+ return
5775
+
5776
+ if cached is None:
5777
+ return
5778
+
5779
+ event.media_urls.append(cached.path)
5780
+ event.media_types.append(cached.media_type)
5781
+ if len(event.media_urls) == 1:
5782
+ if cached.kind == "image":
5783
+ event.message_type = MessageType.PHOTO
5784
+ elif cached.kind == "video":
5785
+ event.message_type = MessageType.VIDEO
5786
+ event.text = self._append_observed_note(
5787
+ event.text,
5788
+ f"[Replied-to {cached.kind} '{cached.display_name}' saved at: {cached.path}]",
5789
+ )
5790
+ logger.info("[Telegram] Cached replied-to %s at %s", cached.kind, cached.path)
5791
+
5012
5792
  def _observed_media_source(self, msg: Message):
5013
5793
  """Return (telegram_file_source, filename, mime, default_kind) or Nones."""
5014
5794
  if msg.photo:
@@ -5198,6 +5978,7 @@ class TelegramAdapter(BasePlatformAdapter):
5198
5978
 
5199
5979
  event = self._build_message_event(msg, MessageType.TEXT, update_id=update.update_id)
5200
5980
  event.text = self._clean_bot_trigger_text(event.text)
5981
+ await self._cache_replied_media(msg, event)
5201
5982
  event = self._apply_telegram_group_observe_attribution(event)
5202
5983
  self._enqueue_text_event(event)
5203
5984
 
@@ -5212,6 +5993,7 @@ class TelegramAdapter(BasePlatformAdapter):
5212
5993
 
5213
5994
  event = self._build_message_event(msg, MessageType.COMMAND, update_id=update.update_id)
5214
5995
  event.text = self._clean_bot_trigger_text(event.text)
5996
+ await self._cache_replied_media(msg, event)
5215
5997
  event = self._apply_telegram_group_observe_attribution(event)
5216
5998
  await self.handle_message(event)
5217
5999
 
@@ -5471,6 +6253,12 @@ class TelegramAdapter(BasePlatformAdapter):
5471
6253
  # Download voice/audio messages to cache for STT transcription
5472
6254
  if msg.voice:
5473
6255
  try:
6256
+ allowed, note = self._telegram_media_size_allowed(msg.voice, "voice message")
6257
+ if not allowed:
6258
+ event.text = self._append_observed_note(event.text, note or "")
6259
+ logger.info("[Telegram] Skipped oversized user voice (size=%s)", getattr(msg.voice, "file_size", None))
6260
+ await self.handle_message(event)
6261
+ return
5474
6262
  file_obj = await msg.voice.get_file()
5475
6263
  audio_bytes = await file_obj.download_as_bytearray()
5476
6264
  cached_path = cache_audio_from_bytes(bytes(audio_bytes), ext=".ogg")
@@ -5481,6 +6269,12 @@ class TelegramAdapter(BasePlatformAdapter):
5481
6269
  logger.warning("[Telegram] Failed to cache voice: %s", e, exc_info=True)
5482
6270
  elif msg.audio:
5483
6271
  try:
6272
+ allowed, note = self._telegram_media_size_allowed(msg.audio, "audio file")
6273
+ if not allowed:
6274
+ event.text = self._append_observed_note(event.text, note or "")
6275
+ logger.info("[Telegram] Skipped oversized user audio (size=%s)", getattr(msg.audio, "file_size", None))
6276
+ await self.handle_message(event)
6277
+ return
5484
6278
  file_obj = await msg.audio.get_file()
5485
6279
  audio_bytes = await file_obj.download_as_bytearray()
5486
6280
  cached_path = cache_audio_from_bytes(bytes(audio_bytes), ext=".mp3")
@@ -5977,6 +6771,19 @@ class TelegramAdapter(BasePlatformAdapter):
5977
6771
  or message.reply_to_message.caption
5978
6772
  or None
5979
6773
  )
6774
+ if not reply_to_text:
6775
+ # Rich messages (sendRichMessage — the launchd briefings and
6776
+ # the gateway's own rich finals) are NOT echoed with their
6777
+ # content in reply_to_message; Telegram sends no text,
6778
+ # caption, or api_kwargs for them. Recover the text we sent
6779
+ # from our local send-time index, keyed by message id.
6780
+ try:
6781
+ from gateway import rich_sent_store
6782
+ reply_to_text = rich_sent_store.lookup(
6783
+ str(chat.id), reply_to_id
6784
+ )
6785
+ except Exception:
6786
+ reply_to_text = None
5980
6787
 
5981
6788
  # Per-channel/topic ephemeral prompt
5982
6789
  from gateway.platforms.base import resolve_channel_prompt