@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
@@ -20,24 +20,40 @@ Required environment variables:
20
20
  (default: ws://127.0.0.1:5225)
21
21
 
22
22
  Optional environment variables:
23
- SIMPLEX_ALLOWED_USERS Comma-separated contact IDs (allowlist)
23
+ SIMPLEX_ALLOWED_USERS Comma-separated allowlist. Each entry may be
24
+ either a numeric contactId (stable across
25
+ renames; visible via `/contacts` in the CLI)
26
+ or a contact display name (what the SimpleX
27
+ UI shows). Both forms are accepted.
24
28
  SIMPLEX_ALLOW_ALL_USERS Set 'true' to allow all contacts
29
+ SIMPLEX_AUTO_ACCEPT Set 'false' to disable contact-request auto-accept
30
+ (default: 'true')
31
+ SIMPLEX_GROUP_ALLOWED Comma-separated group IDs to monitor, or '*'
32
+ for any group. Omit to disable groups entirely.
25
33
  SIMPLEX_HOME_CHANNEL Default contact/group ID for cron delivery
26
34
  SIMPLEX_HOME_CHANNEL_NAME Human label for the home channel
35
+ HERMES_SIMPLEX_TEXT_BATCH_DELAY
36
+ Quiet-period seconds (default: 0.8) used to
37
+ concatenate rapid-fire inbound text messages
38
+ into a single MessageEvent — same pattern as
39
+ Telegram's text batching.
27
40
 
28
41
  The ``websockets`` Python package is imported lazily — the plugin is
29
- discoverable and `hermes setup` can describe it even when websockets is
42
+ discoverable and ``hermes setup`` can describe it even when websockets is
30
43
  not installed. ``check_requirements()`` returns False until the package
31
44
  is present, so the gateway will not attempt to instantiate the adapter.
32
45
  """
33
46
 
34
47
  import asyncio
48
+ import base64
35
49
  import json
36
50
  import logging
37
51
  import os
38
52
  import random
53
+ import re
39
54
  import time
40
55
  from datetime import datetime, timezone
56
+ from pathlib import Path
41
57
  from typing import Any, Dict, List, Optional
42
58
 
43
59
  # Lazy import: BasePlatformAdapter and friends live in the main repo.
@@ -49,9 +65,6 @@ from gateway.platforms.base import (
49
65
  MessageEvent,
50
66
  MessageType,
51
67
  SendResult,
52
- cache_image_from_bytes,
53
- cache_audio_from_bytes,
54
- cache_document_from_bytes,
55
68
  )
56
69
 
57
70
  logger = logging.getLogger(__name__)
@@ -59,12 +72,11 @@ logger = logging.getLogger(__name__)
59
72
  # ---------------------------------------------------------------------------
60
73
  # Constants
61
74
  # ---------------------------------------------------------------------------
62
- MAX_MESSAGE_LENGTH = 16_000 # SimpleX has no hard limit; keep chunking sane
63
- TYPING_INTERVAL = 10.0
75
+ MAX_MESSAGE_LENGTH = 8000 # SimpleX has no hard limit; chunk for sanity
64
76
  WS_RETRY_DELAY_INITIAL = 2.0
65
77
  WS_RETRY_DELAY_MAX = 60.0
66
78
  HEALTH_CHECK_INTERVAL = 30.0
67
- HEALTH_CHECK_STALE_THRESHOLD = 120.0
79
+ HEALTH_CHECK_STALE_THRESHOLD = 300.0
68
80
 
69
81
  # Correlation ID prefix for requests we send so we can ignore our own echoes.
70
82
  _CORR_PREFIX = "hermes-"
@@ -79,6 +91,16 @@ def _parse_comma_list(value: str) -> List[str]:
79
91
  return [v.strip() for v in value.split(",") if v.strip()]
80
92
 
81
93
 
94
+ def _redact_id(contact_id: str) -> str:
95
+ """Redact a contact/group ID for logging."""
96
+ if not contact_id:
97
+ return "<none>"
98
+ s = str(contact_id)
99
+ if len(s) <= 4:
100
+ return s
101
+ return s[:2] + "**" + s[-2:]
102
+
103
+
82
104
  def _guess_extension(data: bytes) -> str:
83
105
  """Guess file extension from magic bytes."""
84
106
  if data[:4] == b"\x89PNG":
@@ -105,7 +127,7 @@ def _is_image_ext(ext: str) -> bool:
105
127
 
106
128
 
107
129
  def _is_audio_ext(ext: str) -> bool:
108
- return ext.lower() in {".mp3", ".wav", ".ogg", ".m4a", ".aac"}
130
+ return ext.lower() in {".mp3", ".wav", ".ogg", ".m4a", ".aac", ".opus"}
109
131
 
110
132
 
111
133
  # ---------------------------------------------------------------------------
@@ -119,6 +141,8 @@ class SimplexAdapter(BasePlatformAdapter):
119
141
  ``ctx.register_platform()`` in :func:`register`.
120
142
  """
121
143
 
144
+ MAX_MESSAGE_LENGTH = MAX_MESSAGE_LENGTH
145
+
122
146
  def __init__(self, config: PlatformConfig, **kwargs):
123
147
  platform = Platform("simplex")
124
148
  super().__init__(config=config, platform=platform)
@@ -126,11 +150,27 @@ class SimplexAdapter(BasePlatformAdapter):
126
150
  extra = getattr(config, "extra", {}) or {}
127
151
  self.ws_url = extra.get("ws_url", "ws://127.0.0.1:5225").rstrip("/")
128
152
 
153
+ # Contact-request auto-accept (on by default — matches the way most
154
+ # bot deployments expect to behave). Read from env first, then fall
155
+ # back to the value seeded by ``_env_enablement``.
156
+ env_auto = os.getenv("SIMPLEX_AUTO_ACCEPT")
157
+ if env_auto is not None:
158
+ self.auto_accept = env_auto.strip().lower() not in {"0", "false", "no", ""}
159
+ else:
160
+ self.auto_accept = bool(extra.get("auto_accept", True))
161
+
162
+ # Group allowlist. Without ``SIMPLEX_GROUP_ALLOWED``, group messages
163
+ # are ignored entirely (safer default — a bot in a group otherwise
164
+ # processes every member's traffic). Use ``*`` to accept any group.
165
+ group_allowed_str = os.getenv("SIMPLEX_GROUP_ALLOWED", "") or extra.get(
166
+ "group_allowed", ""
167
+ )
168
+ self.group_allow_from = set(_parse_comma_list(group_allowed_str))
169
+
129
170
  # Running state
130
171
  self._ws = None # websockets connection
131
172
  self._ws_task: Optional[asyncio.Task] = None
132
173
  self._health_task: Optional[asyncio.Task] = None
133
- self._typing_tasks: Dict[str, asyncio.Task] = {}
134
174
  self._running = False
135
175
  self._last_ws_activity = 0.0
136
176
 
@@ -138,7 +178,31 @@ class SimplexAdapter(BasePlatformAdapter):
138
178
  self._pending_corr_ids: set = set()
139
179
  self._max_pending_corr = 200
140
180
 
141
- logger.info("SimpleX adapter initialized: url=%s", self.ws_url)
181
+ # File transfers awaiting rcvFileComplete (keyed by fileId). Populated
182
+ # when a newChatItems event carries an unfinished rcvFileTransfer,
183
+ # consumed when the file finishes downloading.
184
+ self._pending_file_transfers: Dict[int, dict] = {}
185
+
186
+ # Correlation tracking for ``_send_command``. Separate from
187
+ # ``_pending_corr_ids`` (which is the upstream cosmetic echo filter)
188
+ # because we actually await responses to commands we send.
189
+ self._pending_responses: Dict[str, asyncio.Future] = {}
190
+ self._corr_counter = 0
191
+
192
+ # Text message batching — concatenate rapid-fire messages into one
193
+ # event before dispatching, mirroring Telegram's batching.
194
+ self._text_batch_delay = float(
195
+ os.getenv("HERMES_SIMPLEX_TEXT_BATCH_DELAY", "0.8")
196
+ )
197
+ self._pending_text_batches: Dict[str, MessageEvent] = {}
198
+ self._pending_text_batch_tasks: Dict[str, asyncio.Task] = {}
199
+
200
+ logger.info(
201
+ "SimpleX adapter initialized: url=%s auto_accept=%s groups=%s",
202
+ self.ws_url,
203
+ self.auto_accept,
204
+ "enabled" if self.group_allow_from else "disabled",
205
+ )
142
206
 
143
207
  # ------------------------------------------------------------------
144
208
  # Lifecycle
@@ -173,6 +237,8 @@ class SimplexAdapter(BasePlatformAdapter):
173
237
  self._ws_task = asyncio.create_task(self._ws_listener())
174
238
  self._health_task = asyncio.create_task(self._health_monitor())
175
239
 
240
+ if hasattr(self, "_mark_connected"):
241
+ self._mark_connected()
176
242
  logger.info("SimpleX: connected to %s", self.ws_url)
177
243
  return True
178
244
 
@@ -194,10 +260,6 @@ class SimplexAdapter(BasePlatformAdapter):
194
260
  except asyncio.CancelledError:
195
261
  pass
196
262
 
197
- for task in self._typing_tasks.values():
198
- task.cancel()
199
- self._typing_tasks.clear()
200
-
201
263
  if self._ws:
202
264
  try:
203
265
  await self._ws.close()
@@ -205,6 +267,21 @@ class SimplexAdapter(BasePlatformAdapter):
205
267
  pass
206
268
  self._ws = None
207
269
 
270
+ # Cancel pending text-batch flush timers
271
+ for task in list(self._pending_text_batch_tasks.values()):
272
+ if not task.done():
273
+ task.cancel()
274
+ self._pending_text_batch_tasks.clear()
275
+ self._pending_text_batches.clear()
276
+
277
+ # Cancel pending command futures
278
+ for fut in self._pending_responses.values():
279
+ if not fut.done():
280
+ fut.cancel()
281
+ self._pending_responses.clear()
282
+
283
+ if hasattr(self, "_mark_disconnected"):
284
+ self._mark_disconnected()
208
285
  logger.info("SimpleX: disconnected")
209
286
 
210
287
  # ------------------------------------------------------------------
@@ -214,7 +291,7 @@ class SimplexAdapter(BasePlatformAdapter):
214
291
  async def _ws_listener(self) -> None:
215
292
  """Maintain a persistent WebSocket connection to the daemon."""
216
293
  import websockets as _wsclient
217
- import websockets as _wsexc
294
+ from websockets.exceptions import ConnectionClosed
218
295
 
219
296
  backoff = WS_RETRY_DELAY_INITIAL
220
297
 
@@ -225,6 +302,7 @@ class SimplexAdapter(BasePlatformAdapter):
225
302
  self.ws_url,
226
303
  ping_interval=20,
227
304
  ping_timeout=20,
305
+ close_timeout=10,
228
306
  ) as ws:
229
307
  self._ws = ws
230
308
  backoff = WS_RETRY_DELAY_INITIAL
@@ -245,10 +323,11 @@ class SimplexAdapter(BasePlatformAdapter):
245
323
 
246
324
  except asyncio.CancelledError:
247
325
  break
248
- except _wsexc.WebSocketException as e:
326
+ except ConnectionClosed as e:
249
327
  if self._running:
250
328
  logger.warning(
251
- "SimpleX WS: error: %s (reconnecting in %.0fs)", e, backoff
329
+ "SimpleX WS: connection closed: %s (reconnecting in %.0fs)",
330
+ e, backoff,
252
331
  )
253
332
  except Exception as e:
254
333
  if self._running:
@@ -280,7 +359,6 @@ class SimplexAdapter(BasePlatformAdapter):
280
359
  await asyncio.sleep(HEALTH_CHECK_INTERVAL)
281
360
  if not self._running:
282
361
  break
283
-
284
362
  elapsed = time.time() - self._last_ws_activity
285
363
  if elapsed > HEALTH_CHECK_STALE_THRESHOLD:
286
364
  logger.debug("SimpleX: WS application-idle for %.0fs", elapsed)
@@ -296,121 +374,248 @@ class SimplexAdapter(BasePlatformAdapter):
296
374
  # Older/examples may put the response fields at top-level. Normalize
297
375
  # both forms before dispatching, otherwise inbound chatItems are lost.
298
376
  resp = event.get("resp") if isinstance(event.get("resp"), dict) else event
299
- resp_type = event.get("type") or resp.get("type", "")
377
+ corr_id = event.get("corrId")
300
378
 
301
- # Filter responses to our own commands (echoes)
302
- corr_id = event.get("corrId", "")
303
- if corr_id and corr_id.startswith(_CORR_PREFIX):
379
+ # Handle correlated responses (replies to our own commands)
380
+ if corr_id and corr_id in self._pending_responses:
381
+ fut = self._pending_responses.pop(corr_id)
382
+ if not fut.done():
383
+ fut.set_result(resp)
384
+ return
385
+
386
+ # Cosmetic echo filter: prefixed corrIds are ours but didn't make it
387
+ # into _pending_responses (e.g. fire-and-forget).
388
+ if corr_id and isinstance(corr_id, str) and corr_id.startswith(_CORR_PREFIX):
304
389
  self._pending_corr_ids.discard(corr_id)
305
390
  return
306
391
 
392
+ resp_type = resp.get("type") or event.get("type", "")
393
+
394
+ # Auto-accept contact requests
395
+ if resp_type == "contactRequest" and self.auto_accept:
396
+ contact_req = resp.get("contactRequest", {}) or {}
397
+ contact_req_id = contact_req.get("contactRequestId")
398
+ if contact_req_id is not None:
399
+ logger.info(
400
+ "SimpleX: auto-accepting contact request %s",
401
+ _redact_id(str(contact_req_id)),
402
+ )
403
+ await self._send_command(f"/accept {contact_req_id}")
404
+ return
405
+
406
+ # Early file-descriptor ready: simplex fires this before newChatItems
407
+ # for some file types (especially large files and voice messages
408
+ # transferred via XFTP). Send /freceive immediately so the download
409
+ # starts; the chat item arrives in a subsequent newChatItems event.
410
+ if resp_type == "rcvFileDescrReady":
411
+ rcv_file = resp.get("rcvFileTransfer", {}) or {}
412
+ file_id = rcv_file.get("fileId") if isinstance(rcv_file, dict) else None
413
+ if file_id is not None:
414
+ logger.debug(
415
+ "SimpleX: rcvFileDescrReady for fileId=%s — sending /freceive",
416
+ file_id,
417
+ )
418
+ await self._send_fire_and_forget(f"/freceive {file_id}")
419
+ return
420
+
421
+ # New messages — simplex-chat sends "newChatItems" with an array
422
+ if resp_type == "newChatItems":
423
+ chat_items = resp.get("chatItems", []) or []
424
+ if not isinstance(chat_items, list):
425
+ chat_items = [chat_items]
426
+ for item in chat_items:
427
+ try:
428
+ await self._handle_chat_item(item)
429
+ except Exception:
430
+ logger.exception("SimpleX: error processing chat item")
431
+ return
432
+
433
+ # Singular variant — some daemon versions emit this
307
434
  if resp_type == "newChatItem":
308
- await self._handle_new_chat_item(resp)
309
- elif resp_type == "newChatItems":
310
- # Batch variant — process each item
311
- items = resp.get("chatItems") or []
312
- for item_wrapper in items:
313
- await self._handle_new_chat_item(item_wrapper)
314
- # Ignore all other event types (delivery receipts, contact updates, etc.)
315
-
316
- async def _handle_new_chat_item(self, wrapper: dict) -> None:
317
- """Process a single newChatItem event into a MessageEvent."""
318
- # The daemon wraps the chat item differently depending on version;
319
- # normalise both layouts.
320
- chat_info = wrapper.get("chatInfo") or wrapper.get("chat") or {}
321
- chat_item = wrapper.get("chatItem") or wrapper.get("item") or {}
322
-
323
- # Only process messages (not calls, deleted items, etc.)
324
- item_content = chat_item.get("content") or {}
325
- msg_content = item_content.get("msgContent") or {}
326
- if not msg_content:
435
+ try:
436
+ await self._handle_chat_item(resp)
437
+ except Exception:
438
+ logger.exception("SimpleX: error processing chat item")
327
439
  return
328
440
 
329
- # Filter out messages sent by us (direction == "snd")
330
- meta = chat_item.get("meta") or {}
331
- direction = (meta.get("itemStatus") or {}).get("type", "")
332
- if direction in {"sndSent", "sndSentDirect", "sndSentViaProxy", "sndNew"}:
441
+ # File transfer completion deliver any deferred chat item
442
+ if resp_type == "rcvFileComplete":
443
+ chat_item = resp.get("chatItem", {}) or {}
444
+ chat_item_data = chat_item.get("chatItem", {}) or {}
445
+ file_info = chat_item_data.get("file", {}) or {}
446
+ file_id = file_info.get("fileId") if isinstance(file_info, dict) else None
447
+ if file_id is not None and file_id in self._pending_file_transfers:
448
+ pending = self._pending_file_transfers.pop(file_id)
449
+ file_source = file_info.get("fileSource", {}) or {}
450
+ file_path = (
451
+ file_source.get("filePath")
452
+ if isinstance(file_source, dict)
453
+ else None
454
+ )
455
+ if file_path:
456
+ pending_item_data = pending.get("chatItem", {}) or {}
457
+ pending_item_data.setdefault("file", {})["fileSource"] = {
458
+ "filePath": file_path
459
+ }
460
+ pending["chatItem"] = pending_item_data
461
+ try:
462
+ await self._handle_chat_item(pending)
463
+ except Exception:
464
+ logger.exception(
465
+ "SimpleX: error processing deferred file message"
466
+ )
333
467
  return
334
468
 
335
- # Determine chat type and IDs
336
- chat_type_raw = chat_info.get("type", "")
337
- is_group = chat_type_raw in {"group", "groupInfo"}
469
+ if resp_type:
470
+ logger.debug("SimpleX: unhandled event type: %s", resp_type)
338
471
 
339
- if is_group:
340
- group_info = chat_info.get("groupInfo") or chat_info.get("group") or {}
341
- group_id = str(group_info.get("groupId") or group_info.get("id") or "")
342
- group_name = group_info.get("displayName") or group_info.get("groupProfile", {}).get("displayName", "")
343
- chat_id = f"group:{group_id}" if group_id else ""
344
- chat_name = group_name
345
- else:
346
- contact_info = chat_info.get("contact") or {}
347
- contact_id = str(contact_info.get("contactId") or contact_info.get("id") or "")
348
- contact_name = (
349
- contact_info.get("displayName")
350
- or contact_info.get("localDisplayName")
351
- or contact_id
352
- )
353
- # Replies must be routed by SimpleX CLI display name, while
354
- # authorization should still use the stable numeric contactId.
355
- chat_id = contact_name or contact_id
356
- chat_name = contact_name
472
+ async def _handle_chat_item(self, chat_item: dict) -> None:
473
+ """Process a single chat item from a newChatItems event."""
474
+ chat_info = chat_item.get("chatInfo", {}) or {}
475
+ chat_item_data = chat_item.get("chatItem", {}) or {}
476
+
477
+ chat_type = chat_info.get("type", "")
478
+
479
+ meta = chat_item_data.get("meta", {}) or {}
480
+ content = chat_item_data.get("content", {}) or {}
481
+ msg_content = content.get("msgContent", {}) or {}
357
482
 
358
- if not chat_id:
359
- logger.debug("SimpleX: ignoring event with no chat_id")
483
+ # Filter out our own messages
484
+ item_direction = chat_item_data.get("chatDir", {}) or {}
485
+ direction_type = (
486
+ item_direction.get("type", "") if isinstance(item_direction, dict) else ""
487
+ )
488
+ if direction_type in ("directSnd", "groupSnd"):
360
489
  return
361
490
 
362
- # Sender for groups the message includes a chatItemMember sub-object
363
- member = chat_item.get("chatItemMember") or {}
364
- if is_group and member:
365
- sender_id = str(member.get("memberId") or member.get("id") or chat_id)
366
- sender_name = (
367
- member.get("displayName")
368
- or member.get("localDisplayName")
369
- or sender_id
370
- )
491
+ # Only process received messages
492
+ content_type = content.get("type", "") if isinstance(content, dict) else ""
493
+ if content_type != "rcvMsgContent":
494
+ return
495
+
496
+ # Text content
497
+ text = ""
498
+ msg_type_str = (
499
+ msg_content.get("type", "") if isinstance(msg_content, dict) else ""
500
+ )
501
+ if msg_type_str in ("text", "file", "image", "voice", "link", "video"):
502
+ text = msg_content.get("text", "")
503
+
504
+ if not text and msg_type_str not in ("image", "file", "voice"):
505
+ return
506
+
507
+ # Sender + chat IDs
508
+ sender_id = ""
509
+ sender_name = ""
510
+ chat_id = ""
511
+ is_group = False
512
+
513
+ if chat_type == "direct":
514
+ contact = chat_info.get("contact", {}) or {}
515
+ sender_id = str(contact.get("contactId", ""))
516
+ sender_name = contact.get("localDisplayName", "") or contact.get(
517
+ "profile", {}
518
+ ).get("displayName", "")
519
+ chat_id = sender_id
520
+ elif chat_type == "group":
521
+ group_info = chat_info.get("groupInfo", {}) or {}
522
+ group_id = str(group_info.get("groupId", ""))
523
+ chat_id = f"group:{group_id}"
524
+ is_group = True
525
+
526
+ member = item_direction.get("groupMember", {}) or {}
527
+ sender_id = str(member.get("memberId", ""))
528
+ sender_name = member.get("localDisplayName", "") or member.get(
529
+ "memberProfile", {}
530
+ ).get("displayName", "")
531
+
532
+ # Group allowlist
533
+ if self.group_allow_from:
534
+ if (
535
+ "*" not in self.group_allow_from
536
+ and group_id not in self.group_allow_from
537
+ ):
538
+ logger.debug(
539
+ "SimpleX: group %s not in allowlist",
540
+ _redact_id(group_id),
541
+ )
542
+ return
543
+ else:
544
+ logger.debug(
545
+ "SimpleX: ignoring group message (no SIMPLEX_GROUP_ALLOWED)"
546
+ )
547
+ return
371
548
  else:
372
- sender_id = contact_id if not is_group else chat_id
373
- sender_name = chat_name
549
+ logger.debug("SimpleX: unhandled chat type: %s", chat_type)
550
+ return
374
551
 
375
- # Extract text
376
- text = msg_content.get("text") or ""
552
+ if not sender_id:
553
+ logger.debug("SimpleX: ignoring message with no sender")
554
+ return
377
555
 
378
- # Media attachments
556
+ # File / image / voice attachment handling. File info is at
557
+ # chatItem.chatItem.file (sibling of meta, content, chatDir).
379
558
  media_urls: List[str] = []
380
559
  media_types: List[str] = []
381
- file_info = chat_item.get("file") or {}
382
- if file_info and file_info.get("fileStatus") not in {"cancelled", "error"}:
560
+ file_info = chat_item_data.get("file")
561
+
562
+ if file_info and isinstance(file_info, dict):
563
+ file_source = file_info.get("fileSource", {}) or {}
564
+ file_path = (
565
+ file_source.get("filePath")
566
+ if isinstance(file_source, dict)
567
+ else None
568
+ )
569
+ file_name = file_info.get("fileName", "")
383
570
  file_id = file_info.get("fileId")
384
- file_name = file_info.get("fileName", "file")
385
- if file_id:
386
- try:
387
- cached = await self._fetch_file(file_id, file_name)
388
- if cached:
389
- ext = cached.rsplit(".", 1)[-1]
390
- if _is_image_ext("." + ext):
391
- media_types.append("image/" + ext.replace("jpg", "jpeg"))
392
- elif _is_audio_ext("." + ext):
393
- media_types.append("audio/" + ext)
394
- else:
395
- media_types.append("application/octet-stream")
396
- media_urls.append(cached)
397
- except Exception:
398
- logger.exception("SimpleX: failed to fetch file %s", file_id)
399
571
 
400
- # Timestamp
401
- ts_str = meta.get("itemTs") or meta.get("createdAt") or ""
402
- try:
403
- timestamp = datetime.fromisoformat(ts_str.replace("Z", "+00:00"))
404
- except (ValueError, AttributeError):
405
- timestamp = datetime.now(tz=timezone.utc)
572
+ ext = ""
573
+ if file_path:
574
+ ext = Path(file_path).suffix.lower()
575
+ if not ext and file_name:
576
+ ext = Path(file_name).suffix.lower()
577
+
578
+ # Voice notes typically arrive before the file finishes
579
+ # downloading. Defer the message until rcvFileComplete fires.
580
+ if not file_path and _is_audio_ext(ext) and file_id is not None:
581
+ logger.info(
582
+ "SimpleX: voice file %d not yet received, accepting transfer",
583
+ file_id,
584
+ )
585
+ self._pending_file_transfers[file_id] = chat_item
586
+ # Fire-and-forget: simplex-chat does not return a corrId reply
587
+ # for /freceive, so awaiting one would block the event loop.
588
+ await self._send_fire_and_forget(f"/freceive {file_id}")
589
+ return
590
+
591
+ if file_path:
592
+ ext = Path(file_path).suffix.lower() or (
593
+ Path(file_name).suffix.lower() if file_name else ""
594
+ )
595
+ if _is_image_ext(ext):
596
+ media_urls.append(file_path)
597
+ media_types.append(f"image/{ext.lstrip('.')}")
598
+ elif _is_audio_ext(ext):
599
+ media_urls.append(file_path)
600
+ media_types.append(f"audio/{ext.lstrip('.')}")
601
+ else:
602
+ media_urls.append(file_path)
603
+ media_types.append("application/octet-stream")
604
+
605
+ # Source
606
+ chat_name = sender_name
607
+ if is_group:
608
+ group_info = chat_info.get("groupInfo", {}) or {}
609
+ chat_name = group_info.get("localDisplayName", "") or group_info.get(
610
+ "groupProfile", {}
611
+ ).get("displayName", chat_id)
406
612
 
407
- # Build source
408
613
  source = self.build_source(
409
614
  chat_id=chat_id,
410
615
  chat_name=chat_name,
411
616
  chat_type="group" if is_group else "dm",
412
617
  user_id=sender_id,
413
- user_name=sender_name,
618
+ user_name=sender_name or sender_id,
414
619
  )
415
620
 
416
621
  # Message type
@@ -420,85 +625,179 @@ class SimplexAdapter(BasePlatformAdapter):
420
625
  msg_type = MessageType.VOICE
421
626
  elif any(mt.startswith("image/") for mt in media_types):
422
627
  msg_type = MessageType.PHOTO
628
+ else:
629
+ # Catch-all: non-image/non-audio files (tagged
630
+ # application/octet-stream above) are documents so run.py's
631
+ # document-context injection surfaces the file to the agent.
632
+ msg_type = MessageType.DOCUMENT
423
633
 
424
- event_obj = MessageEvent(
634
+ # Timestamp
635
+ ts_str = meta.get("itemTs") or meta.get("createdAt", "")
636
+ try:
637
+ if ts_str:
638
+ timestamp = datetime.fromisoformat(ts_str.replace("Z", "+00:00"))
639
+ else:
640
+ timestamp = datetime.now(tz=timezone.utc)
641
+ except (ValueError, AttributeError):
642
+ timestamp = datetime.now(tz=timezone.utc)
643
+
644
+ msg_event = MessageEvent(
425
645
  source=source,
426
- text=text,
646
+ text=text or "",
427
647
  message_type=msg_type,
428
648
  media_urls=media_urls,
429
649
  media_types=media_types,
430
650
  timestamp=timestamp,
431
- raw_message=wrapper,
651
+ raw_message=chat_item,
432
652
  )
433
653
 
434
- await self.handle_message(event_obj)
654
+ logger.debug(
655
+ "SimpleX: message from %s in %s: %s",
656
+ _redact_id(sender_id),
657
+ chat_id[:20],
658
+ (text or "")[:50],
659
+ )
435
660
 
436
- async def _fetch_file(self, file_id: Any, file_name: str) -> Optional[str]:
437
- """Ask the daemon to receive and return a file attachment."""
438
- # simplex-chat exposes `/api/v1/files/{fileId}` on an HTTP port
439
- # when started with --http-port. However, the canonical WebSocket API
440
- # does not have a direct binary download command; files are stored on
441
- # the local filesystem after the daemon accepts them.
442
- #
443
- # We request acceptance first, then read from the daemon's local path.
444
- corr_id = self._make_corr_id()
445
- cmd = {
446
- "corrId": corr_id,
447
- "cmd": f"/freceive {file_id}",
448
- }
449
- await self._send_ws(cmd)
450
- # The daemon will emit a chatItemUpdated event when the file lands;
451
- # for simplicity we just wait briefly and rely on the daemon's default path.
452
- await asyncio.sleep(2)
453
-
454
- # simplex-chat stores received files in ~/Downloads or a configured path.
455
- # We try common locations.
456
- for search_dir in (
457
- os.path.expanduser("~/Downloads"),
458
- os.path.expanduser("~/.simplex/files"),
459
- "/tmp/simplex_files",
460
- ):
461
- candidate = os.path.join(search_dir, file_name)
462
- if os.path.exists(candidate):
463
- with open(candidate, "rb") as f:
464
- data = f.read()
465
- ext = _guess_extension(data)
466
- if _is_image_ext(ext):
467
- return cache_image_from_bytes(data, ext)
468
- elif _is_audio_ext(ext):
469
- return cache_audio_from_bytes(data, ext)
470
- else:
471
- return cache_document_from_bytes(data, file_name)
472
- return None
661
+ # Batch consecutive text messages so the agent sees one combined
662
+ # message instead of dropping earlier ones when the user pastes
663
+ # several lines in quick succession.
664
+ if msg_type == MessageType.TEXT and text:
665
+ self._enqueue_text_event(msg_event)
666
+ else:
667
+ await self.handle_message(msg_event)
668
+
669
+ # ------------------------------------------------------------------
670
+ # Text message batching
671
+ # ------------------------------------------------------------------
672
+
673
+ def _text_batch_key(self, event: MessageEvent) -> str:
674
+ """Session-scoped key for text message batching."""
675
+ return f"{event.source.platform.value}:{event.source.chat_id}"
676
+
677
+ def _enqueue_text_event(self, event: MessageEvent) -> None:
678
+ """Buffer a text event and reset the flush timer."""
679
+ key = self._text_batch_key(event)
680
+ existing = self._pending_text_batches.get(key)
681
+ if existing is None:
682
+ self._pending_text_batches[key] = event
683
+ else:
684
+ if event.text:
685
+ existing.text = (
686
+ f"{existing.text}\n{event.text}" if existing.text else event.text
687
+ )
688
+ if event.media_urls:
689
+ existing.media_urls.extend(event.media_urls)
690
+ existing.media_types.extend(event.media_types)
691
+
692
+ prior_task = self._pending_text_batch_tasks.get(key)
693
+ if prior_task and not prior_task.done():
694
+ prior_task.cancel()
695
+ self._pending_text_batch_tasks[key] = asyncio.create_task(
696
+ self._flush_text_batch(key)
697
+ )
698
+
699
+ async def _flush_text_batch(self, key: str) -> None:
700
+ """Wait for the quiet period then dispatch the aggregated text."""
701
+ current_task = asyncio.current_task()
702
+ try:
703
+ await asyncio.sleep(self._text_batch_delay)
704
+ event = self._pending_text_batches.pop(key, None)
705
+ if not event:
706
+ return
707
+ logger.info(
708
+ "[SimpleX] Flushing text batch %s (%d chars)",
709
+ key,
710
+ len(event.text or ""),
711
+ )
712
+ await self.handle_message(event)
713
+ finally:
714
+ if self._pending_text_batch_tasks.get(key) is current_task:
715
+ self._pending_text_batch_tasks.pop(key, None)
473
716
 
474
717
  # ------------------------------------------------------------------
475
- # Outbound messages
718
+ # Command interface
476
719
  # ------------------------------------------------------------------
477
720
 
478
721
  def _make_corr_id(self) -> str:
479
- """Generate a unique correlation ID for a request."""
480
- corr_id = f"{_CORR_PREFIX}{int(time.time() * 1000)}-{random.randint(0, 9999)}"
722
+ """Mint a new correlation ID and remember it for echo-filtering.
723
+
724
+ We add every minted id to ``_pending_corr_ids`` so the inbound
725
+ event loop can drop the daemon's echo of our own commands without
726
+ ever invoking ``_handle_chat_item``. The set is bounded — when
727
+ it grows past ``_max_pending_corr``, the oldest entries are
728
+ evicted in a single sweep.
729
+ """
730
+ self._corr_counter += 1
731
+ corr_id = f"{_CORR_PREFIX}{self._corr_counter}-{int(time.time() * 1000)}"
481
732
  self._pending_corr_ids.add(corr_id)
482
733
  if len(self._pending_corr_ids) > self._max_pending_corr:
483
- # Trim oldest sets are unordered so just clear the oldest half
484
- to_remove = list(self._pending_corr_ids)[:self._max_pending_corr // 2]
485
- self._pending_corr_ids -= set(to_remove)
734
+ overflow = len(self._pending_corr_ids) - self._max_pending_corr
735
+ for _ in range(overflow):
736
+ try:
737
+ self._pending_corr_ids.pop()
738
+ except KeyError:
739
+ break
486
740
  return corr_id
487
741
 
488
742
  async def _send_ws(self, payload: dict) -> None:
489
- """Send a JSON payload over the WebSocket, queuing if not yet connected."""
490
- import websockets as _wsexc
743
+ """Fire-and-forget JSON payload write.
744
+
745
+ Drops cleanly when the WebSocket is missing or already closed; the
746
+ caller never has to handle reconnection — the ``_ws_listener``
747
+ loop does that out of band.
748
+ """
491
749
  ws = self._ws
492
750
  if not ws:
493
- logger.debug("SimpleX: WS not connected, dropping outbound command")
751
+ logger.debug("SimpleX: WS send dropped (not connected)")
494
752
  return
495
753
  try:
496
754
  await ws.send(json.dumps(payload))
497
- except _wsexc.ConnectionClosed:
498
- logger.warning("SimpleX: WS closed while sending")
499
755
  except Exception as e:
500
756
  logger.warning("SimpleX: WS send error: %s", e)
501
757
 
758
+ async def _send_command(
759
+ self, command: str, timeout: float = 30.0
760
+ ) -> Optional[dict]:
761
+ """Send a command and await the correlated response."""
762
+ ws = self._ws
763
+ if not ws:
764
+ logger.warning("SimpleX: command sent but WebSocket not connected")
765
+ return None
766
+
767
+ corr_id = self._make_corr_id()
768
+ payload = json.dumps({"corrId": corr_id, "cmd": command})
769
+
770
+ loop = asyncio.get_event_loop()
771
+ fut: asyncio.Future = loop.create_future()
772
+ self._pending_responses[corr_id] = fut
773
+
774
+ try:
775
+ await ws.send(payload)
776
+ result = await asyncio.wait_for(fut, timeout=timeout)
777
+ return result
778
+ except asyncio.TimeoutError:
779
+ logger.warning("SimpleX: command timed out: %s", command[:50])
780
+ self._pending_responses.pop(corr_id, None)
781
+ return None
782
+ except Exception as e:
783
+ logger.warning("SimpleX: command failed: %s — %s", command[:50], e)
784
+ self._pending_responses.pop(corr_id, None)
785
+ return None
786
+
787
+ async def _send_fire_and_forget(self, command: str) -> None:
788
+ """Send a command without waiting for a correlated response.
789
+
790
+ Use this for commands the daemon never sends a corrId reply for,
791
+ such as ``/freceive``. Awaiting a corr-id reply on those would
792
+ stall the event loop for the full command timeout.
793
+ """
794
+ corr_id = self._make_corr_id()
795
+ await self._send_ws({"corrId": corr_id, "cmd": command})
796
+
797
+ # ------------------------------------------------------------------
798
+ # Outbound — text
799
+ # ------------------------------------------------------------------
800
+
502
801
  async def send(
503
802
  self,
504
803
  chat_id: str,
@@ -506,50 +805,283 @@ class SimplexAdapter(BasePlatformAdapter):
506
805
  reply_to: Optional[str] = None,
507
806
  metadata: Optional[Dict[str, Any]] = None,
508
807
  ) -> SendResult:
509
- """Send a text message to a contact or group."""
510
- corr_id = self._make_corr_id()
808
+ """Send a text message.
809
+
810
+ If *content* contains ``MEDIA:<path>`` tags (embedded by TTS / audio
811
+ tools to signal file attachments), they are stripped from the text
812
+ body and sent as native voice notes or documents.
813
+
814
+ Groups use the structured ``/_send #<id> json [...]`` form
815
+ because the bracket chat-command syntax (``#[<id>] text``) is
816
+ parsed by the daemon as a display-name lookup, which silently
817
+ drops when the group's display name isn't the literal ID. DMs
818
+ use the simple ``@<id> text`` form which has always worked in
819
+ production.
820
+
821
+ The call is fire-and-forget at the WebSocket level: the daemon
822
+ doesn't always return a corrId reply for chat commands, and
823
+ waiting for one would serialise all outbound traffic behind a
824
+ 30-second timeout.
825
+ """
826
+ _voice_exts = {".ogg", ".mp3", ".wav", ".m4a", ".opus"}
827
+ media_paths = re.findall(r"MEDIA:(\S+)", content)
828
+ if media_paths:
829
+ content = re.sub(r"MEDIA:\S+", "", content).strip()
830
+
831
+ if content:
832
+ corr_id = self._make_corr_id()
833
+ if chat_id.startswith("group:"):
834
+ # Structured form: addresses by numeric ID, and json.dumps
835
+ # escapes newlines + special chars correctly.
836
+ composed = json.dumps(
837
+ [{"msgContent": {"type": "text", "text": content}}]
838
+ )
839
+ cmd_str = f"/_send #{chat_id[6:]} json {composed}"
840
+ else:
841
+ cmd_str = f"@{chat_id} {content}"
511
842
 
512
- if chat_id.startswith("group:"):
513
- group_id = chat_id[6:]
514
- cmd_str = f"#[{group_id}] {content}"
515
- else:
516
- # SimpleX CLI addresses direct contacts by display name, e.g.
517
- # `@Alice hello`. `@[Alice]` is interpreted literally as a contact
518
- # named "[Alice]" and `@[4]` as "[4]", so do not wrap direct
519
- # chat IDs / display names in brackets.
520
- cmd_str = f"@{chat_id} {content}"
843
+ await self._send_ws({"corrId": corr_id, "cmd": cmd_str})
521
844
 
522
- payload = {
523
- "corrId": corr_id,
524
- "cmd": cmd_str,
525
- }
845
+ for path in media_paths:
846
+ is_voice = os.path.splitext(path)[1].lower() in _voice_exts
847
+ if is_voice:
848
+ media_result = await self.send_voice(chat_id, path)
849
+ else:
850
+ media_result = await self.send_document(chat_id, path)
851
+ if not media_result.success:
852
+ return media_result
526
853
 
527
- await self._send_ws(payload)
528
854
  return SendResult(success=True)
529
855
 
530
- async def send_typing(self, chat_id: str, metadata=None) -> None:
531
- """SimpleX does not expose a typing indicator API no-op."""
532
- pass
856
+ # ------------------------------------------------------------------
857
+ # Outboundmedia
858
+ # ------------------------------------------------------------------
859
+
860
+ @staticmethod
861
+ def _prepare_image(file_path: str) -> tuple[str, str]:
862
+ """Ensure *file_path* is a PNG and return ``(png_path, thumb_data_uri)``.
863
+
864
+ SimpleX clients can't display WebP and a few other formats inline.
865
+ This converts to PNG when needed and generates a small JPEG thumbnail
866
+ for the ``image`` field in the ``/_send`` payload so the chat shows
867
+ an inline preview. Uses Pillow when available, falls back to
868
+ ImageMagick ``convert``.
869
+ """
870
+ import subprocess
871
+ import tempfile
872
+
873
+ p = Path(file_path)
874
+ png_path = file_path
875
+ thumb_uri = ""
876
+
877
+ try:
878
+ from PIL import Image
879
+
880
+ img = Image.open(file_path)
881
+ if p.suffix.lower() not in (".png", ".jpg", ".jpeg"):
882
+ png_path = str(p.with_suffix(".png"))
883
+ img.save(png_path, "PNG")
884
+ thumb = img.copy()
885
+ thumb.thumbnail((128, 128))
886
+ import io
887
+
888
+ buf = io.BytesIO()
889
+ thumb.save(buf, "JPEG", quality=70)
890
+ thumb_uri = (
891
+ "data:image/jpg;base64,"
892
+ + base64.b64encode(buf.getvalue()).decode()
893
+ )
894
+ except ImportError:
895
+ try:
896
+ if p.suffix.lower() not in (".png", ".jpg", ".jpeg"):
897
+ png_path = str(p.with_suffix(".png"))
898
+ subprocess.run(
899
+ ["convert", file_path, png_path],
900
+ check=True,
901
+ capture_output=True,
902
+ timeout=30,
903
+ )
904
+ with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tmp:
905
+ tmp_path = tmp.name
906
+ subprocess.run(
907
+ [
908
+ "convert",
909
+ file_path,
910
+ "-resize",
911
+ "128x128",
912
+ "-quality",
913
+ "70",
914
+ tmp_path,
915
+ ],
916
+ check=True,
917
+ capture_output=True,
918
+ timeout=30,
919
+ )
920
+ with open(tmp_path, "rb") as f:
921
+ thumb_uri = (
922
+ "data:image/jpg;base64," + base64.b64encode(f.read()).decode()
923
+ )
924
+ os.remove(tmp_path)
925
+ except (FileNotFoundError, subprocess.SubprocessError) as exc:
926
+ logger.warning("SimpleX: image conversion unavailable: %s", exc)
927
+
928
+ return png_path, thumb_uri
533
929
 
534
930
  async def send_image(
535
931
  self,
536
932
  chat_id: str,
537
933
  image_url: str,
538
934
  caption: Optional[str] = None,
935
+ **kwargs,
936
+ ) -> SendResult:
937
+ """Send an image. Supports ``file://`` URLs and ``http(s)://`` URLs."""
938
+ from urllib.parse import unquote
939
+
940
+ if image_url.startswith("file://"):
941
+ file_path = unquote(image_url[7:])
942
+ else:
943
+ try:
944
+ from gateway.platforms.base import cache_image_from_url
945
+
946
+ file_path = await cache_image_from_url(image_url)
947
+ except Exception as e:
948
+ logger.warning("SimpleX: failed to download image: %s", e)
949
+ return SendResult(success=False, error=str(e))
950
+
951
+ if not file_path or not Path(file_path).exists():
952
+ return SendResult(success=False, error="Image file not found")
953
+
954
+ png_path, thumb_uri = self._prepare_image(file_path)
955
+
956
+ # /_send addresses by numeric ID; /f only accepts display names which
957
+ # breaks for group IDs.
958
+ composed = json.dumps(
959
+ [
960
+ {
961
+ "filePath": png_path,
962
+ "msgContent": {
963
+ "type": "image",
964
+ "image": thumb_uri,
965
+ "text": caption or "",
966
+ },
967
+ }
968
+ ]
969
+ )
970
+
971
+ if chat_id.startswith("group:"):
972
+ group_id = chat_id[6:]
973
+ command = f"/_send #{group_id} json {composed}"
974
+ else:
975
+ command = f"/_send @{chat_id} json {composed}"
976
+
977
+ result = await self._send_command(command)
978
+ if result is not None:
979
+ return SendResult(success=True)
980
+ return SendResult(success=False, error="Failed to send image")
981
+
982
+ async def send_image_file(
983
+ self,
984
+ chat_id: str,
985
+ image_path: str,
986
+ caption: Optional[str] = None,
539
987
  reply_to: Optional[str] = None,
540
- metadata: Optional[Dict[str, Any]] = None,
988
+ **kwargs,
989
+ ) -> SendResult:
990
+ """Send a local image file via SimpleX."""
991
+ return await self.send_image(
992
+ chat_id, f"file://{image_path}", caption=caption, **kwargs
993
+ )
994
+
995
+ async def send_video(
996
+ self,
997
+ chat_id: str,
998
+ video_path: str,
999
+ caption: Optional[str] = None,
1000
+ reply_to: Optional[str] = None,
1001
+ **kwargs,
541
1002
  ) -> SendResult:
542
- """Send an image (URL) as a message with optional caption.
1003
+ """Send a video file via SimpleX (as a file attachment)."""
1004
+ return await self.send_document(chat_id, video_path, caption=caption)
543
1005
 
544
- SimpleX has no native ``send_image`` over the WebSocket API — file
545
- attachments require the daemon's filesystem-backed flow which is
546
- not driven from this adapter. Fall back to a plain text message
547
- containing the URL and caption.
1006
+ async def send_document(
1007
+ self,
1008
+ chat_id: str,
1009
+ file_path: str,
1010
+ caption: Optional[str] = None,
1011
+ filename: Optional[str] = None,
1012
+ **kwargs,
1013
+ ) -> SendResult:
1014
+ """Send a document/file attachment."""
1015
+ if not Path(file_path).exists():
1016
+ return SendResult(success=False, error="File not found")
1017
+
1018
+ composed = json.dumps(
1019
+ [
1020
+ {
1021
+ "filePath": file_path,
1022
+ "msgContent": {"type": "file", "text": caption or ""},
1023
+ }
1024
+ ]
1025
+ )
1026
+
1027
+ if chat_id.startswith("group:"):
1028
+ group_id = chat_id[6:]
1029
+ command = f"/_send #{group_id} json {composed}"
1030
+ else:
1031
+ command = f"/_send @{chat_id} json {composed}"
1032
+
1033
+ result = await self._send_command(command)
1034
+ if result is not None:
1035
+ return SendResult(success=True)
1036
+ return SendResult(success=False, error="Failed to send document")
1037
+
1038
+ async def send_voice(
1039
+ self,
1040
+ chat_id: str,
1041
+ audio_path: str,
1042
+ caption: Optional[str] = None,
1043
+ reply_to: Optional[str] = None,
1044
+ duration: int = 0,
1045
+ **kwargs,
1046
+ ) -> SendResult:
1047
+ """Send an audio file as a SimpleX voice note (plays inline).
1048
+
1049
+ SimpleX distinguishes a generic file attachment (``type: "file"``)
1050
+ from an inline voice note (``type: "voice"``). ``/f`` would deliver
1051
+ a downloadable file; the structured ``/_send`` form with
1052
+ ``msgContent.type == "voice"`` produces the voice-note player.
548
1053
  """
549
- text = f"{caption}\n{image_url}".strip() if caption else image_url
550
- return await self.send(chat_id, text, reply_to=reply_to, metadata=metadata)
1054
+ if not Path(audio_path).exists():
1055
+ return SendResult(success=False, error="Voice file not found")
1056
+
1057
+ composed = json.dumps(
1058
+ [
1059
+ {
1060
+ "msgContent": {
1061
+ "type": "voice",
1062
+ "text": caption or "",
1063
+ "duration": duration,
1064
+ },
1065
+ "fileSource": {"filePath": audio_path},
1066
+ }
1067
+ ]
1068
+ )
1069
+
1070
+ if chat_id.startswith("group:"):
1071
+ group_id = chat_id[6:]
1072
+ command = f"/_send #{group_id} json {composed}"
1073
+ else:
1074
+ command = f"/_send @{chat_id} json {composed}"
1075
+
1076
+ result = await self._send_command(command)
1077
+ if result is not None:
1078
+ return SendResult(success=True)
1079
+ return SendResult(success=False, error="Failed to send voice message")
1080
+
1081
+ async def send_typing(self, chat_id: str, metadata=None) -> None:
1082
+ """SimpleX has no typing-indicator API — no-op."""
551
1083
 
552
- async def get_chat_info(self, chat_id: str) -> dict:
1084
+ async def get_chat_info(self, chat_id: str) -> Dict[str, Any]:
553
1085
  """Return basic chat info."""
554
1086
  if chat_id.startswith("group:"):
555
1087
  return {"chat_id": chat_id, "type": "group", "name": chat_id[6:]}
@@ -590,7 +1122,7 @@ def is_connected(config) -> bool:
590
1122
  return bool(ws_url)
591
1123
 
592
1124
 
593
- def _env_enablement() -> dict | None:
1125
+ def _env_enablement() -> Optional[dict]:
594
1126
  """Seed ``PlatformConfig.extra`` from env vars during gateway config load.
595
1127
 
596
1128
  Called by the platform registry's env-enablement hook BEFORE adapter
@@ -598,14 +1130,23 @@ def _env_enablement() -> dict | None:
598
1130
  reflect env-only configuration without instantiating the WebSocket
599
1131
  client. Returns ``None`` when SimpleX isn't minimally configured.
600
1132
 
601
- The special ``home_channel`` key in the returned dict is handled by
602
- the core hook — it becomes a proper ``HomeChannel`` dataclass on the
603
- ``PlatformConfig`` rather than being merged into ``extra``.
1133
+ The special ``home_channel`` key is handled by the core hook it
1134
+ becomes a proper ``HomeChannel`` dataclass on the ``PlatformConfig``
1135
+ rather than being merged into ``extra``.
604
1136
  """
605
1137
  ws_url = os.getenv("SIMPLEX_WS_URL", "").strip()
606
1138
  if not ws_url:
607
1139
  return None
608
1140
  seed: dict = {"ws_url": ws_url}
1141
+
1142
+ auto_accept = os.getenv("SIMPLEX_AUTO_ACCEPT", "").strip().lower()
1143
+ if auto_accept:
1144
+ seed["auto_accept"] = auto_accept not in {"0", "false", "no"}
1145
+
1146
+ group_allowed = os.getenv("SIMPLEX_GROUP_ALLOWED", "").strip()
1147
+ if group_allowed:
1148
+ seed["group_allowed"] = group_allowed
1149
+
609
1150
  home = os.getenv("SIMPLEX_HOME_CHANNEL", "").strip()
610
1151
  if home:
611
1152
  seed["home_channel"] = {
@@ -633,9 +1174,9 @@ async def _standalone_send(
633
1174
 
634
1175
  ``thread_id`` and ``force_document`` are accepted for signature parity
635
1176
  with other plugins but are not meaningful here. ``media_files`` is
636
- accepted but only the text body is delivered — SimpleX requires the
637
- daemon's filesystem-backed file flow which an ephemeral connection
638
- cannot drive safely.
1177
+ accepted but only the text body is delivered — SimpleX file transfers
1178
+ require the daemon's filesystem-backed flow, which an ephemeral
1179
+ connection cannot drive safely.
639
1180
  """
640
1181
  try:
641
1182
  import websockets as _wsclient
@@ -643,24 +1184,31 @@ async def _standalone_send(
643
1184
  return {"error": "websockets not installed. Run: pip install websockets"}
644
1185
 
645
1186
  extra = getattr(pconfig, "extra", {}) or {}
646
- ws_url = os.getenv("SIMPLEX_WS_URL") or extra.get("ws_url", "ws://127.0.0.1:5225")
1187
+ ws_url = os.getenv("SIMPLEX_WS_URL") or extra.get(
1188
+ "ws_url", "ws://127.0.0.1:5225"
1189
+ )
647
1190
  if not ws_url:
648
1191
  return {"error": "SimpleX standalone send: SIMPLEX_WS_URL is required"}
649
1192
 
650
1193
  try:
651
1194
  if chat_id.startswith("group:"):
652
1195
  group_id = chat_id[6:]
653
- cmd_str = f"#[{group_id}] {message}"
1196
+ composed = json.dumps(
1197
+ [{"msgContent": {"type": "text", "text": message}}]
1198
+ )
1199
+ cmd_str = f"/_send #{group_id} json {composed}"
654
1200
  else:
655
1201
  # Direct contacts are addressed by display name without brackets.
656
1202
  cmd_str = f"@{chat_id} {message}"
657
1203
 
658
1204
  payload = {
659
- "corrId": f"hermes-snd-{int(time.time() * 1000)}",
1205
+ "corrId": f"{_CORR_PREFIX}snd-{int(time.time() * 1000)}",
660
1206
  "cmd": cmd_str,
661
1207
  }
662
1208
 
663
- async with _wsclient.connect(ws_url, open_timeout=10, close_timeout=5) as ws:
1209
+ async with _wsclient.connect(
1210
+ ws_url, open_timeout=10, close_timeout=5
1211
+ ) as ws:
664
1212
  await ws.send(json.dumps(payload))
665
1213
  # Give the daemon a moment to process the command before closing.
666
1214
  await asyncio.sleep(0.5)
@@ -673,8 +1221,9 @@ async def _standalone_send(
673
1221
  def interactive_setup() -> None:
674
1222
  """Minimal stdin wizard for ``hermes setup gateway`` → SimpleX.
675
1223
 
676
- Prompts for the WebSocket URL and the optional allowlist / home channel.
677
- Writes to ``~/.hermes/.env`` via ``hermes_cli.config``.
1224
+ Prompts for the WebSocket URL and the optional allowlist / groups /
1225
+ auto-accept / home channel. Writes to ``~/.hermes/.env`` via
1226
+ ``hermes_cli.config``.
678
1227
  """
679
1228
  print()
680
1229
  print("SimpleX Chat setup")
@@ -687,7 +1236,10 @@ def interactive_setup() -> None:
687
1236
  try:
688
1237
  from hermes_cli.config import get_env_value, save_env_value
689
1238
  except ImportError:
690
- print("hermes_cli.config not available; set SIMPLEX_* vars manually in ~/.hermes/.env")
1239
+ print(
1240
+ "hermes_cli.config not available; set SIMPLEX_* vars manually in "
1241
+ "~/.hermes/.env"
1242
+ )
691
1243
  return
692
1244
 
693
1245
  def _prompt(var: str, prompt: str, *, secret: bool = False) -> None:
@@ -706,9 +1258,20 @@ def interactive_setup() -> None:
706
1258
  save_env_value(var, value)
707
1259
 
708
1260
  _prompt("SIMPLEX_WS_URL", "Daemon WebSocket URL (default ws://127.0.0.1:5225)")
709
- _prompt("SIMPLEX_ALLOWED_USERS", "Allowed contact IDs (comma-separated; blank=skip)")
1261
+ _prompt("SIMPLEX_ALLOWED_USERS", "Allowed contactIds or display names (comma-separated; blank=skip)")
1262
+ _prompt(
1263
+ "SIMPLEX_GROUP_ALLOWED",
1264
+ "Allowed group IDs (comma-separated, or '*' for any; blank=disable groups)",
1265
+ )
1266
+ _prompt(
1267
+ "SIMPLEX_AUTO_ACCEPT",
1268
+ "Auto-accept incoming contact requests? (true/false, default true)",
1269
+ )
710
1270
  _prompt("SIMPLEX_HOME_CHANNEL", "Home channel contact/group ID (or empty)")
711
- print("Done. Make sure the simplex-chat daemon is running before starting the gateway.")
1271
+ print(
1272
+ "Done. Make sure the simplex-chat daemon is running before starting "
1273
+ "the gateway."
1274
+ )
712
1275
 
713
1276
 
714
1277
  def register(ctx) -> None:
@@ -721,36 +1284,30 @@ def register(ctx) -> None:
721
1284
  validate_config=validate_config,
722
1285
  is_connected=is_connected,
723
1286
  required_env=["SIMPLEX_WS_URL"],
724
- install_hint="pip install websockets # SimpleX adapter requires the websockets package",
1287
+ install_hint=(
1288
+ "pip install websockets # SimpleX adapter requires the "
1289
+ "websockets package"
1290
+ ),
725
1291
  setup_fn=interactive_setup,
726
- # Env-driven auto-configuration: seeds PlatformConfig.extra so
727
- # env-only setups show up in `hermes gateway status` without
728
- # instantiating the adapter.
729
1292
  env_enablement_fn=_env_enablement,
730
- # Cron home-channel delivery support — `deliver=simplex` cron jobs
731
- # route to SIMPLEX_HOME_CHANNEL when set.
732
1293
  cron_deliver_env_var="SIMPLEX_HOME_CHANNEL",
733
- # Out-of-process cron delivery. Without this hook, deliver=simplex
734
- # cron jobs fail with "No live adapter" when cron runs separately
735
- # from the gateway.
736
1294
  standalone_sender_fn=_standalone_send,
737
- # Auth env vars for _is_user_authorized() integration
738
1295
  allowed_users_env="SIMPLEX_ALLOWED_USERS",
739
1296
  allow_all_env="SIMPLEX_ALLOW_ALL_USERS",
740
- # SimpleX has no hard line length; we still chunk for sanity.
741
1297
  max_message_length=MAX_MESSAGE_LENGTH,
742
- # Display
743
1298
  emoji="🔒",
744
- # SimpleX uses opaque contact IDs only — no phone numbers or
745
- # email addresses to redact.
1299
+ # SimpleX uses opaque contact IDs only — no phone numbers or email
1300
+ # addresses to redact.
746
1301
  pii_safe=True,
747
1302
  allow_update_command=True,
748
- # LLM guidance
749
1303
  platform_hint=(
750
1304
  "You are chatting via SimpleX Chat, a private decentralised "
751
1305
  "messenger. Contacts are identified by opaque internal IDs, "
752
1306
  "not phone numbers or usernames. SimpleX supports standard "
753
1307
  "markdown formatting. There is no typing indicator and no "
754
- "hard message length limit, but keep responses conversational."
1308
+ "hard message length limit, but keep responses conversational. "
1309
+ "You can attach native images, voice notes, and arbitrary "
1310
+ "files; the adapter handles MEDIA:<path> tags by sending them "
1311
+ "as inline voice notes (audio extensions) or documents."
755
1312
  ),
756
1313
  )