@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
@@ -7,6 +7,7 @@ Handles: hermes gateway [run|start|stop|restart|status|install|uninstall|setup]
7
7
  import asyncio
8
8
  import logging
9
9
  import os
10
+ import shlex
10
11
  import shutil
11
12
  import signal
12
13
  import subprocess
@@ -227,9 +228,8 @@ def _graceful_restart_via_sigusr1(pid: int, drain_timeout: float) -> bool:
227
228
 
228
229
  SIGUSR1 is wired in gateway/run.py to ``request_restart(via_service=True)``
229
230
  which drains in-flight agent runs (up to ``agent.restart_drain_timeout``
230
- seconds), then exits. systemd relaunches clean exits via
231
- ``Restart=always``; launchd still uses a non-zero planned-restart exit
232
- because its plist has ``KeepAlive.SuccessfulExit = false``.
231
+ seconds), then exits. Both systemd (``Restart=always``) and launchd
232
+ (unconditional ``<key>KeepAlive</key><true/>``) restart on any exit.
233
233
 
234
234
  This is the drain-aware alternative to ``systemctl restart`` / ``SIGTERM``,
235
235
  which SIGKILL in-flight agents after a short timeout.
@@ -319,23 +319,12 @@ def _scan_gateway_pids(exclude_pids: set[int], all_profiles: bool = False) -> li
319
319
  # gateway. See #13242.
320
320
  exclude_pids = exclude_pids | _get_ancestor_pids()
321
321
  pids: list[int] = []
322
- patterns = [
323
- "hermes_cli.main gateway",
324
- "hermes_cli.main --profile",
325
- "hermes_cli.main -p",
326
- "hermes_cli/main.py gateway",
327
- "hermes_cli/main.py --profile",
328
- "hermes_cli/main.py -p",
329
- "hermes gateway",
330
- # Windows: only match invocations that actually carry the ``gateway``
331
- # subcommand or the gateway-dedicated console-script shim. Bare
332
- # ``hermes.exe --profile`` / ``hermes.exe -p`` would also match
333
- # ``hermes.exe --profile foo dashboard`` and other CLI subcommands,
334
- # producing false-positive gateway PIDs (Copilot review).
335
- "hermes.exe gateway",
336
- "hermes-gateway.exe",
337
- "gateway/run.py",
338
- ]
322
+ # Strict command-line matcher shared with gateway.status: requires the
323
+ # actual ``gateway run`` subcommand (or the dedicated entrypoints), so this
324
+ # scan no longer false-matches ``gateway status``/``dashboard`` siblings or
325
+ # unrelated processes like ``python -m tui_gateway``. Lazy import mirrors the
326
+ # circular-import avoidance used elsewhere in this module.
327
+ from gateway.status import looks_like_gateway_command_line
339
328
  current_home = str(get_hermes_home().resolve())
340
329
  current_home_lc = current_home.lower()
341
330
  current_profile_arg = _profile_arg(current_home)
@@ -430,8 +419,7 @@ def _scan_gateway_pids(exclude_pids: set[int], all_profiles: bool = False) -> li
430
419
  current_cmd = line[len("CommandLine=") :]
431
420
  elif line.startswith("ProcessId="):
432
421
  pid_str = line[len("ProcessId=") :]
433
- current_cmd_lc = current_cmd.lower()
434
- if any(p in current_cmd_lc for p in patterns) and (
422
+ if looks_like_gateway_command_line(current_cmd) and (
435
423
  all_profiles or _matches_current_profile(current_cmd)
436
424
  ):
437
425
  try:
@@ -456,8 +444,7 @@ def _scan_gateway_pids(exclude_pids: set[int], all_profiles: bool = False) -> li
456
444
  with open(f"/proc/{pid}/cmdline", "rb") as _f:
457
445
  cmdline = _f.read().decode("utf-8", errors="replace")
458
446
  cmdline = cmdline.replace("\x00", " ")
459
- cmdline_lc = cmdline.lower()
460
- if any(p in cmdline_lc for p in patterns) and (
447
+ if looks_like_gateway_command_line(cmdline) and (
461
448
  all_profiles or _matches_current_profile(cmdline)
462
449
  ):
463
450
  _append_unique_pid(pids, pid, exclude_pids)
@@ -500,8 +487,7 @@ def _scan_gateway_pids(exclude_pids: set[int], all_profiles: bool = False) -> li
500
487
 
501
488
  if pid is None:
502
489
  continue
503
- command_lc = command.lower()
504
- if any(pattern in command_lc for pattern in patterns) and (
490
+ if looks_like_gateway_command_line(command) and (
505
491
  all_profiles or _matches_current_profile(command)
506
492
  ):
507
493
  _append_unique_pid(pids, pid, exclude_pids)
@@ -642,7 +628,10 @@ def launch_detached_profile_gateway_restart(profile: str, old_pid: int) -> bool:
642
628
  #
643
629
  # ``windows_detach_popen_kwargs()`` returns the right kwargs for the
644
630
  # host platform and is a no-op on POSIX (just ``start_new_session=True``).
645
- from hermes_cli._subprocess_compat import windows_detach_popen_kwargs
631
+ from hermes_cli._subprocess_compat import (
632
+ windows_detach_flags_without_breakaway,
633
+ windows_detach_popen_kwargs,
634
+ )
646
635
 
647
636
  watcher = textwrap.dedent(
648
637
  """
@@ -665,6 +654,10 @@ def launch_detached_profile_gateway_restart(profile: str, old_pid: int) -> bool:
665
654
  # Platform-appropriate detach for the respawned gateway. On POSIX
666
655
  # start_new_session=True maps to os.setsid; on Windows we need
667
656
  # explicit creationflags because start_new_session is a no-op there.
657
+ # CREATE_BREAKAWAY_FROM_JOB is critical: the watcher itself may have
658
+ # been spawned inside a job object (Electron/Tauri parent), and
659
+ # without breakaway the respawned gateway would die when that job
660
+ # tears down. See _subprocess_compat.windows_detach_flags().
668
661
  _popen_kwargs = {
669
662
  "stdout": subprocess.DEVNULL,
670
663
  "stderr": subprocess.DEVNULL,
@@ -673,32 +666,67 @@ def launch_detached_profile_gateway_restart(profile: str, old_pid: int) -> bool:
673
666
  _CREATE_NEW_PROCESS_GROUP = 0x00000200
674
667
  _DETACHED_PROCESS = 0x00000008
675
668
  _CREATE_NO_WINDOW = 0x08000000
676
- _popen_kwargs["creationflags"] = (
677
- _CREATE_NEW_PROCESS_GROUP | _DETACHED_PROCESS | _CREATE_NO_WINDOW
669
+ _CREATE_BREAKAWAY_FROM_JOB = 0x01000000
670
+ _flags = (
671
+ _CREATE_NEW_PROCESS_GROUP
672
+ | _DETACHED_PROCESS
673
+ | _CREATE_NO_WINDOW
674
+ | _CREATE_BREAKAWAY_FROM_JOB
678
675
  )
676
+ try:
677
+ _popen_kwargs["creationflags"] = _flags
678
+ subprocess.Popen(cmd, **_popen_kwargs)
679
+ except OSError:
680
+ # CREATE_BREAKAWAY_FROM_JOB can be rejected with
681
+ # ERROR_ACCESS_DENIED when the parent's job object refuses
682
+ # breakaway. Retry without it — DETACHED_PROCESS et al.
683
+ # alone are enough in most setups. Mirrors the canonical
684
+ # fallback in gateway_windows._spawn_detached.
685
+ _popen_kwargs["creationflags"] = _flags & ~_CREATE_BREAKAWAY_FROM_JOB
686
+ subprocess.Popen(cmd, **_popen_kwargs)
679
687
  else:
680
688
  _popen_kwargs["start_new_session"] = True
681
- subprocess.Popen(cmd, **_popen_kwargs)
689
+ subprocess.Popen(cmd, **_popen_kwargs)
682
690
  """
683
691
  ).strip()
684
692
 
693
+ watcher_argv = [
694
+ sys.executable,
695
+ "-c",
696
+ watcher,
697
+ str(old_pid),
698
+ *_gateway_run_args_for_profile(profile),
699
+ ]
700
+
701
+ # Same platform-aware detach for the watcher process itself — so
702
+ # closing the user's terminal doesn't kill the watcher.
685
703
  try:
686
- # Same platform-aware detach for the watcher process itself — so
687
- # closing the user's terminal doesn't kill the watcher.
688
704
  subprocess.Popen(
689
- [
690
- sys.executable,
691
- "-c",
692
- watcher,
693
- str(old_pid),
694
- *_gateway_run_args_for_profile(profile),
695
- ],
705
+ watcher_argv,
696
706
  stdout=subprocess.DEVNULL,
697
707
  stderr=subprocess.DEVNULL,
698
708
  **windows_detach_popen_kwargs(),
699
709
  )
700
710
  except OSError:
701
- return False
711
+ # CREATE_BREAKAWAY_FROM_JOB rejected by the parent job object
712
+ # (Electron, Windows Terminal with restrictive job settings, …).
713
+ # Retry without it. POSIX never reaches this branch — there
714
+ # ``start_new_session=True`` cannot raise OSError — so the
715
+ # fallback is only meaningful on Windows.
716
+ try:
717
+ fallback_kwargs: dict = (
718
+ {"creationflags": windows_detach_flags_without_breakaway()}
719
+ if sys.platform == "win32"
720
+ else {"start_new_session": True}
721
+ )
722
+ subprocess.Popen(
723
+ watcher_argv,
724
+ stdout=subprocess.DEVNULL,
725
+ stderr=subprocess.DEVNULL,
726
+ **fallback_kwargs,
727
+ )
728
+ except OSError:
729
+ return False
702
730
  return True
703
731
 
704
732
 
@@ -1054,11 +1082,30 @@ def get_gateway_runtime_snapshot(system: bool = False) -> GatewayRuntimeSnapshot
1054
1082
  # Other container runtimes (or containers built before Phase 2)
1055
1083
  # still get the original "docker (foreground)" label.
1056
1084
  try:
1057
- from hermes_cli.service_manager import detect_service_manager
1085
+ from hermes_cli.service_manager import detect_service_manager, get_service_manager
1058
1086
  if detect_service_manager() == "s6":
1087
+ profile = _profile_suffix() or "default"
1088
+ service_name = f"gateway-{profile}"
1089
+ mgr = get_service_manager()
1090
+ service_installed = False
1091
+ service_running = False
1092
+ try:
1093
+ service_dir = getattr(mgr, "scandir", None)
1094
+ if service_dir is not None:
1095
+ service_installed = (service_dir / service_name).is_dir()
1096
+ except Exception:
1097
+ service_installed = False
1098
+ if service_installed:
1099
+ try:
1100
+ service_running = bool(mgr.is_running(service_name))
1101
+ except Exception:
1102
+ service_running = False
1059
1103
  return GatewayRuntimeSnapshot(
1060
1104
  manager="s6 (container supervisor)",
1105
+ service_installed=service_installed,
1106
+ service_running=service_running,
1061
1107
  gateway_pids=gateway_pids,
1108
+ service_scope="s6",
1062
1109
  )
1063
1110
  except Exception:
1064
1111
  pass # Fall through to the legacy label on any detection error.
@@ -1389,7 +1436,7 @@ def _profile_suffix() -> str:
1389
1436
  return hashlib.sha256(str(home).encode()).hexdigest()[:8]
1390
1437
 
1391
1438
 
1392
- def _profile_arg(hermes_home: str | None = None) -> str:
1439
+ def _profile_arg(hermes_home: str | None = None, default_root: str | Path | None = None) -> str:
1393
1440
  """Return ``--profile <name>`` only when HERMES_HOME is a named profile.
1394
1441
 
1395
1442
  For ``~/.hermes/profiles/<name>``, returns ``"--profile <name>"``.
@@ -1399,12 +1446,16 @@ def _profile_arg(hermes_home: str | None = None) -> str:
1399
1446
  hermes_home: Optional explicit HERMES_HOME path. Defaults to the current
1400
1447
  ``get_hermes_home()`` value. Should be passed when generating a
1401
1448
  service definition for a different user (e.g. system service).
1449
+ default_root: Optional Hermes root to compare against. Used when
1450
+ generating a system service for another user from a sudo/root
1451
+ process, where ``Path.home()`` and ``get_default_hermes_root()``
1452
+ refer to root but the target profile lives under the service user.
1402
1453
  """
1403
1454
  import re
1404
1455
  from hermes_constants import get_default_hermes_root
1405
1456
 
1406
1457
  home = Path(hermes_home or str(get_hermes_home())).resolve()
1407
- default = get_default_hermes_root().resolve()
1458
+ default = Path(default_root).resolve() if default_root else get_default_hermes_root().resolve()
1408
1459
  if home == default:
1409
1460
  return ""
1410
1461
  profiles_root = (default / "profiles").resolve()
@@ -1418,6 +1469,16 @@ def _profile_arg(hermes_home: str | None = None) -> str:
1418
1469
  return ""
1419
1470
 
1420
1471
 
1472
+ def _profile_arg_for_target_user(hermes_home: str, target_home_dir: str) -> str:
1473
+ """Return the profile arg for a system service running as another user."""
1474
+ target_root = Path(target_home_dir) / ".hermes"
1475
+ try:
1476
+ Path(hermes_home).resolve().relative_to(target_root.resolve())
1477
+ return _profile_arg(hermes_home, default_root=target_root)
1478
+ except ValueError:
1479
+ return _profile_arg(hermes_home)
1480
+
1481
+
1421
1482
  def get_service_name() -> str:
1422
1483
  """Derive a systemd service name scoped to this HERMES_HOME.
1423
1484
 
@@ -2343,7 +2404,7 @@ def generate_systemd_unit(system: bool = False, run_as_user: str | None = None)
2343
2404
  if system:
2344
2405
  username, group_name, home_dir = _system_service_identity(run_as_user)
2345
2406
  hermes_home = _hermes_home_for_target_user(home_dir)
2346
- profile_arg = _profile_arg(hermes_home)
2407
+ profile_arg = _profile_arg_for_target_user(hermes_home, home_dir)
2347
2408
  # Remap all paths that may resolve under the calling user's home
2348
2409
  # (e.g. /root/) to the target user's home so the service can
2349
2410
  # actually access them.
@@ -2368,7 +2429,7 @@ StartLimitIntervalSec=0
2368
2429
  Type=simple
2369
2430
  User={username}
2370
2431
  Group={group_name}
2371
- ExecStart={python_path} -m hermes_cli.main{f" {profile_arg}" if profile_arg else ""} gateway run --replace
2432
+ ExecStart={python_path} -m hermes_cli.main{f" {profile_arg}" if profile_arg else ""} gateway run
2372
2433
  WorkingDirectory={working_dir}
2373
2434
  Environment="HOME={home_dir}"
2374
2435
  Environment="USER={username}"
@@ -2378,8 +2439,6 @@ Environment="VIRTUAL_ENV={venv_dir}"
2378
2439
  Environment="HERMES_HOME={hermes_home}"
2379
2440
  Restart=always
2380
2441
  RestartSec=5
2381
- RestartMaxDelaySec=300
2382
- RestartSteps=5
2383
2442
  RestartForceExitStatus={GATEWAY_SERVICE_RESTART_EXIT_CODE}
2384
2443
  KillMode=mixed
2385
2444
  KillSignal=SIGTERM
@@ -2406,15 +2465,13 @@ StartLimitIntervalSec=0
2406
2465
 
2407
2466
  [Service]
2408
2467
  Type=simple
2409
- ExecStart={python_path} -m hermes_cli.main{f" {profile_arg}" if profile_arg else ""} gateway run --replace
2468
+ ExecStart={python_path} -m hermes_cli.main{f" {profile_arg}" if profile_arg else ""} gateway run
2410
2469
  WorkingDirectory={working_dir}
2411
2470
  Environment="PATH={sane_path}"
2412
2471
  Environment="VIRTUAL_ENV={venv_dir}"
2413
2472
  Environment="HERMES_HOME={hermes_home}"
2414
2473
  Restart=always
2415
2474
  RestartSec=5
2416
- RestartMaxDelaySec=300
2417
- RestartSteps=5
2418
2475
  RestartForceExitStatus={GATEWAY_SERVICE_RESTART_EXIT_CODE}
2419
2476
  KillMode=mixed
2420
2477
  KillSignal=SIGTERM
@@ -2432,6 +2489,29 @@ def _normalize_service_definition(text: str) -> str:
2432
2489
  return "\n".join(line.rstrip() for line in text.strip().splitlines())
2433
2490
 
2434
2491
 
2492
+ # Directives that older systemd versions silently ignore/strip. Normalize
2493
+ # them out of stale-check comparisons so a unit that differs only by these
2494
+ # directives is not perpetually flagged as outdated.
2495
+ _SYSTEMD_OPTIONAL_DIRECTIVES = (
2496
+ "RestartMaxDelaySec",
2497
+ "RestartSteps",
2498
+ )
2499
+
2500
+
2501
+ def _strip_optional_systemd_directives(text: str) -> str:
2502
+ """Remove systemd directives that older hosts silently drop."""
2503
+ lines = text.splitlines()
2504
+ filtered = []
2505
+ for line in lines:
2506
+ stripped = line.strip()
2507
+ if stripped and not stripped.startswith("#"):
2508
+ key = stripped.split("=", 1)[0].strip()
2509
+ if key in _SYSTEMD_OPTIONAL_DIRECTIVES:
2510
+ continue
2511
+ filtered.append(line)
2512
+ return "\n".join(filtered)
2513
+
2514
+
2435
2515
  def _normalize_launchd_plist_for_comparison(text: str) -> str:
2436
2516
  """Normalize launchd plist text for staleness checks.
2437
2517
 
@@ -2459,9 +2539,75 @@ def systemd_unit_is_current(system: bool = False) -> bool:
2459
2539
  installed = unit_path.read_text(encoding="utf-8")
2460
2540
  expected_user = _read_systemd_user_from_unit(unit_path) if system else None
2461
2541
  expected = generate_systemd_unit(system=system, run_as_user=expected_user)
2462
- return _normalize_service_definition(installed) == _normalize_service_definition(
2463
- expected
2542
+ # Normalize out directives that older systemd versions silently drop
2543
+ # (RestartMaxDelaySec, RestartSteps) so a unit that differs only by
2544
+ # those directives is not perpetually flagged as outdated.
2545
+ norm_installed = _normalize_service_definition(
2546
+ _strip_optional_systemd_directives(installed)
2464
2547
  )
2548
+ norm_expected = _normalize_service_definition(
2549
+ _strip_optional_systemd_directives(expected)
2550
+ )
2551
+ return norm_installed == norm_expected
2552
+
2553
+
2554
+ def _temp_home_in_service_definition(definition: str) -> str | None:
2555
+ """Return the temp-dir HERMES_HOME baked into a service definition, or None.
2556
+
2557
+ A generated systemd unit / launchd plist carries the resolved HERMES_HOME
2558
+ in its environment block. If that path lives under the system temp dir,
2559
+ the definition was almost certainly generated by a test/E2E harness that
2560
+ exported a throwaway ``HERMES_HOME=/tmp/...`` — writing it to the real
2561
+ service file silently breaks the user's gateway on the next (re)start:
2562
+ the gateway comes back "active (running)" but pointed at an empty temp
2563
+ home ("No messaging platforms enabled"), deaf to every platform.
2564
+ Seen live 2026-06-11: an E2E guard probe ran ``hermes gateway restart``
2565
+ with ``HERMES_HOME=/tmp/hermes-e2e-<pr>`` exported; the restart path's
2566
+ unit refresh baked the temp path into the production unit and the
2567
+ post-update restart produced a zombie gateway for 7+ hours.
2568
+
2569
+ Matches both systemd ``Environment="HERMES_HOME=..."`` lines and launchd
2570
+ ``<key>HERMES_HOME</key><string>...</string>`` pairs.
2571
+ """
2572
+ import re
2573
+ import tempfile
2574
+
2575
+ candidates = re.findall(r'HERMES_HOME=([^"\n]+)', definition)
2576
+ candidates += re.findall(
2577
+ r"<key>HERMES_HOME</key>\s*<string>(.*?)</string>", definition, flags=re.S
2578
+ )
2579
+ temp_roots = {
2580
+ Path(tempfile.gettempdir()).resolve(),
2581
+ Path("/tmp"),
2582
+ Path("/var/tmp"),
2583
+ Path("/private/tmp"),
2584
+ Path("/private/var/tmp"),
2585
+ }
2586
+ for raw in candidates:
2587
+ try:
2588
+ resolved = Path(raw.strip().strip('"')).resolve()
2589
+ except (OSError, ValueError):
2590
+ continue
2591
+ for root in temp_roots:
2592
+ if resolved == root or root in resolved.parents:
2593
+ return raw.strip()
2594
+ return None
2595
+
2596
+
2597
+ def _refuse_temp_home_service_write(definition: str, kind: str) -> bool:
2598
+ """Refuse (with guidance) when a service definition carries a temp HERMES_HOME."""
2599
+ temp_home = _temp_home_in_service_definition(definition)
2600
+ if temp_home is None:
2601
+ return False
2602
+ print(
2603
+ f"✗ Refusing to write the gateway {kind}: HERMES_HOME resolves to a "
2604
+ f"temporary directory ({temp_home})."
2605
+ )
2606
+ print(
2607
+ " This usually means a test/E2E environment exported HERMES_HOME. "
2608
+ "Unset it (or run from a clean shell) and retry."
2609
+ )
2610
+ return True
2465
2611
 
2466
2612
 
2467
2613
  def refresh_systemd_unit_if_needed(system: bool = False) -> bool:
@@ -2494,6 +2640,12 @@ def refresh_systemd_unit_if_needed(system: bool = False) -> bool:
2494
2640
  ):
2495
2641
  return False
2496
2642
 
2643
+ # Structural variant of the same belt: refuse to bake ANY temp-dir
2644
+ # HERMES_HOME into the unit (manual E2E homes like /tmp/hermes-e2e-NNN
2645
+ # don't carry the pytest markers above but poison the unit identically).
2646
+ if _refuse_temp_home_service_write(new_unit, "systemd unit"):
2647
+ return False
2648
+
2497
2649
  unit_path.write_text(new_unit, encoding="utf-8")
2498
2650
  _run_systemctl(["daemon-reload"], system=system, check=True, timeout=30)
2499
2651
  print(
@@ -2662,10 +2814,11 @@ def systemd_install(
2662
2814
  return
2663
2815
 
2664
2816
  unit_path.parent.mkdir(parents=True, exist_ok=True)
2817
+ new_unit = generate_systemd_unit(system=system, run_as_user=run_as_user)
2818
+ if _refuse_temp_home_service_write(new_unit, "systemd unit"):
2819
+ return
2665
2820
  print(f"Installing {_service_scope_label(system)} systemd service to: {unit_path}")
2666
- unit_path.write_text(
2667
- generate_systemd_unit(system=system, run_as_user=run_as_user), encoding="utf-8"
2668
- )
2821
+ unit_path.write_text(new_unit, encoding="utf-8")
2669
2822
 
2670
2823
  _run_systemctl(["daemon-reload"], system=system, check=True, timeout=30)
2671
2824
  if enable_on_startup:
@@ -3000,8 +3153,178 @@ def get_launchd_label() -> str:
3000
3153
  return f"ai.hermes.gateway-{suffix}" if suffix else "ai.hermes.gateway"
3001
3154
 
3002
3155
 
3156
+ # Cached launchd domain result — probing is cheap but should only run once per
3157
+ # process invocation (each ``hermes gateway start/stop/status`` call).
3158
+ _resolved_launchd_domain: str | None = None
3159
+
3160
+
3003
3161
  def _launchd_domain() -> str:
3004
- return f"gui/{os.getuid()}" # windows-footgun: ok POSIX launchd (macOS) helper, never invoked on Windows
3162
+ """Return the launchd domain that actually manages the gateway service.
3163
+
3164
+ Probes ``gui/<uid>`` first (Aqua sessions), then ``user/<uid>``
3165
+ (Background/SSH sessions). When neither domain contains a loaded
3166
+ service, falls back to ``launchctl managername`` as a heuristic.
3167
+
3168
+ The result is cached for the lifetime of the process so that repeated
3169
+ calls (``start``, ``stop``, ``restart``) use a consistent domain.
3170
+
3171
+ See #40831, #23387.
3172
+ """
3173
+ global _resolved_launchd_domain
3174
+ if _resolved_launchd_domain is not None:
3175
+ return _resolved_launchd_domain
3176
+
3177
+ uid = os.getuid() # windows-footgun: ok — POSIX launchd (macOS) helper, never invoked on Windows
3178
+ label = get_launchd_label()
3179
+ gui_domain = f"gui/{uid}"
3180
+ user_domain = f"user/{uid}"
3181
+
3182
+ # 1. Probe gui/<uid> first — in Aqua sessions the service is loaded here.
3183
+ try:
3184
+ subprocess.run(
3185
+ ["launchctl", "print", f"{gui_domain}/{label}"],
3186
+ check=True,
3187
+ timeout=5,
3188
+ capture_output=True,
3189
+ )
3190
+ _resolved_launchd_domain = gui_domain
3191
+ return gui_domain
3192
+ except (subprocess.CalledProcessError, subprocess.TimeoutExpired, FileNotFoundError):
3193
+ pass
3194
+
3195
+ # 2. Probe user/<uid> — in Background/SSH sessions this is the working domain.
3196
+ try:
3197
+ subprocess.run(
3198
+ ["launchctl", "print", f"{user_domain}/{label}"],
3199
+ check=True,
3200
+ timeout=5,
3201
+ capture_output=True,
3202
+ )
3203
+ _resolved_launchd_domain = user_domain
3204
+ return user_domain
3205
+ except (subprocess.CalledProcessError, subprocess.TimeoutExpired, FileNotFoundError):
3206
+ pass
3207
+
3208
+ # 3. Neither domain has the service loaded — use managername as heuristic.
3209
+ # Aqua → gui/<uid>, anything else (Background, loginwindow) → user/<uid>.
3210
+ try:
3211
+ result = subprocess.run(
3212
+ ["launchctl", "managername"],
3213
+ capture_output=True,
3214
+ text=True,
3215
+ timeout=5,
3216
+ )
3217
+ if "Aqua" in (result.stdout or ""):
3218
+ _resolved_launchd_domain = gui_domain
3219
+ return gui_domain
3220
+ except (subprocess.CalledProcessError, subprocess.TimeoutExpired, FileNotFoundError):
3221
+ pass
3222
+
3223
+ # 4. Default to user/<uid> (matches the pre-probing behavior for
3224
+ # Background/SSH sessions and is the recommended domain on macOS 26+).
3225
+ _resolved_launchd_domain = user_domain
3226
+ return user_domain
3227
+
3228
+
3229
+ # On macOS, exit code 125 ("Domain does not support specified action") and
3230
+ # 3/113 ("Could not find service") all mean the job isn't currently loaded in
3231
+ # the target domain, so start/restart should re-bootstrap the plist and retry.
3232
+ _LAUNCHD_JOB_UNLOADED_EXIT_CODES = frozenset({3, 113, 125})
3233
+
3234
+ # When even a fresh bootstrap can't manage the domain, launchctl returns 5
3235
+ # ("Input/output error") or a persistent 125. On those hosts launchd cannot
3236
+ # supervise the gateway at all, so we degrade to a detached background process
3237
+ # (the documented `nohup hermes gateway run` workaround). See #23387.
3238
+ _LAUNCHCTL_DOMAIN_UNSUPPORTED_CODES = frozenset({5, 125})
3239
+
3240
+
3241
+ def _launchd_error_indicates_unloaded(exc: subprocess.CalledProcessError) -> bool:
3242
+ """True when launchctl failed because the job isn't loaded (retry bootstrap)."""
3243
+ return exc.returncode in _LAUNCHD_JOB_UNLOADED_EXIT_CODES
3244
+
3245
+
3246
+ def _launchctl_domain_unsupported(returncode: int) -> bool:
3247
+ """True when launchctl can't manage the domain even after a fresh bootstrap.
3248
+
3249
+ Codes 5 and 125 persist on macOS hosts where neither `gui/<uid>` nor
3250
+ `user/<uid>` supports service management; treat these as "launchd
3251
+ unavailable" and degrade gracefully to a detached process.
3252
+ """
3253
+ return returncode in _LAUNCHCTL_DOMAIN_UNSUPPORTED_CODES
3254
+
3255
+
3256
+ def _gateway_run_command() -> list[str]:
3257
+ """Build the `python -m hermes_cli.main [--profile X] gateway run --replace` argv.
3258
+
3259
+ Profile-aware: honors the active HERMES_HOME via `_profile_arg()` so the
3260
+ detached fallback launches into the same profile as the CLI invocation.
3261
+ """
3262
+ cmd = [get_python_path(), "-m", "hermes_cli.main"]
3263
+ profile_arg = _profile_arg()
3264
+ if profile_arg:
3265
+ cmd.extend(profile_arg.split())
3266
+ cmd.extend(["gateway", "run", "--replace"])
3267
+ return cmd
3268
+
3269
+
3270
+ def _spawn_detached_gateway() -> bool:
3271
+ """Launch the gateway as a detached background process (launchd fallback).
3272
+
3273
+ Used when launchctl can no longer bootstrap/kickstart the gateway on
3274
+ macOS 26+ (issue #23387). Mirrors the `nohup hermes gateway run --replace`
3275
+ workaround but keeps it CLI-managed: stdout/stderr go to the profile's
3276
+ gateway logs and the PID is tracked via the gateway.pid file that
3277
+ `run_gateway` writes, so stop/status/restart keep working.
3278
+ """
3279
+ from hermes_cli._subprocess_compat import windows_detach_popen_kwargs
3280
+
3281
+ log_dir = get_hermes_home() / "logs"
3282
+ log_dir.mkdir(parents=True, exist_ok=True)
3283
+ out_path = log_dir / "gateway.log"
3284
+ err_path = log_dir / "gateway.error.log"
3285
+ try:
3286
+ out = open(out_path, "ab")
3287
+ err = open(err_path, "ab")
3288
+ except OSError:
3289
+ return False
3290
+ try:
3291
+ with out, err:
3292
+ subprocess.Popen(
3293
+ _gateway_run_command(),
3294
+ stdin=subprocess.DEVNULL,
3295
+ stdout=out,
3296
+ stderr=err,
3297
+ **windows_detach_popen_kwargs(),
3298
+ )
3299
+ except OSError:
3300
+ return False
3301
+ return True
3302
+
3303
+
3304
+ def _launchd_fallback_to_detached(reason: str, *, exit_on_failure: bool = True) -> bool:
3305
+ """Start the gateway detached when launchd can't manage it, with guidance.
3306
+
3307
+ Returns True if the detached gateway was launched. When it can't be
3308
+ launched, prints the manual workaround and (by default) exits non-zero so
3309
+ the failure surfaces instead of silently doing nothing.
3310
+ """
3311
+ from hermes_constants import display_hermes_home as _dhh
3312
+
3313
+ print(f"⚠ launchd cannot manage the gateway on this macOS version ({reason}).")
3314
+ if _spawn_detached_gateway():
3315
+ print("✓ Started gateway as a background process instead")
3316
+ print(" It will NOT auto-start at login or auto-restart on crash.")
3317
+ print(f" Logs: {_dhh()}/logs/gateway.log")
3318
+ print(" Stop it with: hermes gateway stop")
3319
+ return True
3320
+ print_error("Failed to start the gateway as a background process.")
3321
+ print(
3322
+ f" Try manually: nohup hermes gateway run --replace "
3323
+ f"> {_dhh()}/logs/gateway.log 2>&1 &"
3324
+ )
3325
+ if exit_on_failure:
3326
+ sys.exit(1)
3327
+ return False
3005
3328
 
3006
3329
 
3007
3330
  def generate_launchd_plist() -> str:
@@ -3078,15 +3401,18 @@ def generate_launchd_plist() -> str:
3078
3401
  <key>HERMES_HOME</key>
3079
3402
  <string>{hermes_home}</string>
3080
3403
  </dict>
3404
+
3405
+ <key>LimitLoadToSessionType</key>
3406
+ <array>
3407
+ <string>Aqua</string>
3408
+ <string>Background</string>
3409
+ </array>
3081
3410
 
3082
3411
  <key>RunAtLoad</key>
3083
3412
  <true/>
3084
3413
 
3085
3414
  <key>KeepAlive</key>
3086
- <dict>
3087
- <key>SuccessfulExit</key>
3088
- <false/>
3089
- </dict>
3415
+ <true/>
3090
3416
 
3091
3417
  <key>StandardOutPath</key>
3092
3418
  <string>{log_dir}/gateway.log</string>
@@ -3122,16 +3448,66 @@ def refresh_launchd_plist_if_needed() -> bool:
3122
3448
  if not plist_path.exists() or launchd_plist_is_current():
3123
3449
  return False
3124
3450
 
3125
- plist_path.write_text(generate_launchd_plist(), encoding="utf-8")
3451
+ new_plist = generate_launchd_plist()
3452
+ if _refuse_temp_home_service_write(new_plist, "launchd plist"):
3453
+ return False
3454
+
3455
+ plist_path.write_text(new_plist, encoding="utf-8")
3126
3456
  label = get_launchd_label()
3457
+ domain = _launchd_domain()
3458
+ target = f"{domain}/{label}"
3459
+
3460
+ # If this refresh is running INSIDE the gateway's own launchd process tree
3461
+ # (e.g. the agent triggered a self-update via its terminal tool), a direct
3462
+ # `launchctl bootout` tears down the service's process group — which
3463
+ # includes THIS CLI — before the follow-up `bootstrap` can run. The gateway
3464
+ # then stays unloaded and KeepAlive can't revive it (#43842). Detect that
3465
+ # case and hand the reload to a detached session that survives the bootout.
3466
+ gateway_pid = None
3467
+ try:
3468
+ from gateway.status import get_running_pid
3469
+ gateway_pid = get_running_pid()
3470
+ except Exception:
3471
+ gateway_pid = None
3472
+
3473
+ if (
3474
+ gateway_pid is not None
3475
+ and _is_pid_ancestor_of_current_process(gateway_pid)
3476
+ and hasattr(os, "setsid") # POSIX-only; launchd is macOS so always true here
3477
+ ):
3478
+ # Delegate to a new session: `start_new_session=True` detaches the
3479
+ # helper from the gateway's process group, so the bootout that kills
3480
+ # the gateway (and us) does not kill the helper before it bootstraps.
3481
+ reload_script = (
3482
+ f"sleep 2; "
3483
+ f"launchctl bootout {shlex.quote(target)} 2>/dev/null; "
3484
+ f"sleep 1; "
3485
+ f"launchctl bootstrap {shlex.quote(domain)} {shlex.quote(str(plist_path))} 2>/dev/null"
3486
+ )
3487
+ try:
3488
+ subprocess.Popen(
3489
+ ["/bin/bash", "-c", reload_script],
3490
+ start_new_session=True,
3491
+ stdout=subprocess.DEVNULL,
3492
+ stderr=subprocess.DEVNULL,
3493
+ )
3494
+ except Exception as e:
3495
+ logger.warning("Deferred launchd reload could not be spawned: %s", e)
3496
+ return False
3497
+ print(
3498
+ "↻ Updated gateway launchd service definition; reload deferred to a "
3499
+ "detached helper (refresh ran inside the gateway process tree)"
3500
+ )
3501
+ return True
3502
+
3127
3503
  # Bootout/bootstrap so launchd picks up the new definition
3128
3504
  subprocess.run(
3129
- ["launchctl", "bootout", f"{_launchd_domain()}/{label}"],
3505
+ ["launchctl", "bootout", target],
3130
3506
  check=False,
3131
3507
  timeout=90,
3132
3508
  )
3133
3509
  subprocess.run(
3134
- ["launchctl", "bootstrap", _launchd_domain(), str(plist_path)],
3510
+ ["launchctl", "bootstrap", domain, str(plist_path)],
3135
3511
  check=False,
3136
3512
  timeout=30,
3137
3513
  )
@@ -3155,14 +3531,23 @@ def launchd_install(force: bool = False):
3155
3531
  return
3156
3532
 
3157
3533
  plist_path.parent.mkdir(parents=True, exist_ok=True)
3534
+ new_plist = generate_launchd_plist()
3535
+ if _refuse_temp_home_service_write(new_plist, "launchd plist"):
3536
+ return
3158
3537
  print(f"Installing launchd service to: {plist_path}")
3159
- plist_path.write_text(generate_launchd_plist())
3538
+ plist_path.write_text(new_plist)
3160
3539
 
3161
- subprocess.run(
3162
- ["launchctl", "bootstrap", _launchd_domain(), str(plist_path)],
3163
- check=True,
3164
- timeout=30,
3165
- )
3540
+ try:
3541
+ subprocess.run(
3542
+ ["launchctl", "bootstrap", _launchd_domain(), str(plist_path)],
3543
+ check=True,
3544
+ timeout=30,
3545
+ )
3546
+ except subprocess.CalledProcessError as e:
3547
+ if not _launchctl_domain_unsupported(e.returncode):
3548
+ raise
3549
+ _launchd_fallback_to_detached(f"launchctl bootstrap exit {e.returncode}")
3550
+ return
3166
3551
 
3167
3552
  print()
3168
3553
  print("✓ Service installed and loaded!")
@@ -3196,19 +3581,28 @@ def launchd_start():
3196
3581
 
3197
3582
  # Self-heal if the plist is missing entirely (e.g., manual cleanup, failed upgrade)
3198
3583
  if not plist_path.exists():
3584
+ new_plist = generate_launchd_plist()
3585
+ if _refuse_temp_home_service_write(new_plist, "launchd plist"):
3586
+ sys.exit(1)
3199
3587
  print("↻ launchd plist missing; regenerating service definition")
3200
3588
  plist_path.parent.mkdir(parents=True, exist_ok=True)
3201
- plist_path.write_text(generate_launchd_plist(), encoding="utf-8")
3202
- subprocess.run(
3203
- ["launchctl", "bootstrap", _launchd_domain(), str(plist_path)],
3204
- check=True,
3205
- timeout=30,
3206
- )
3207
- subprocess.run(
3208
- ["launchctl", "kickstart", f"{_launchd_domain()}/{label}"],
3209
- check=True,
3210
- timeout=30,
3211
- )
3589
+ plist_path.write_text(new_plist, encoding="utf-8")
3590
+ try:
3591
+ subprocess.run(
3592
+ ["launchctl", "bootstrap", _launchd_domain(), str(plist_path)],
3593
+ check=True,
3594
+ timeout=30,
3595
+ )
3596
+ subprocess.run(
3597
+ ["launchctl", "kickstart", f"{_launchd_domain()}/{label}"],
3598
+ check=True,
3599
+ timeout=30,
3600
+ )
3601
+ except subprocess.CalledProcessError as e:
3602
+ if not _launchctl_domain_unsupported(e.returncode):
3603
+ raise
3604
+ _launchd_fallback_to_detached(f"launchctl exit {e.returncode}")
3605
+ return
3212
3606
  print("✓ Service started")
3213
3607
  return
3214
3608
 
@@ -3220,19 +3614,28 @@ def launchd_start():
3220
3614
  timeout=30,
3221
3615
  )
3222
3616
  except subprocess.CalledProcessError as e:
3223
- if e.returncode not in {3, 113}:
3617
+ if not _launchd_error_indicates_unloaded(e):
3224
3618
  raise
3619
+ # Job not loaded in this domain — re-bootstrap the plist and retry.
3225
3620
  print("↻ launchd job was unloaded; reloading service definition")
3226
- subprocess.run(
3227
- ["launchctl", "bootstrap", _launchd_domain(), str(plist_path)],
3228
- check=True,
3229
- timeout=30,
3230
- )
3231
- subprocess.run(
3232
- ["launchctl", "kickstart", f"{_launchd_domain()}/{label}"],
3233
- check=True,
3234
- timeout=30,
3235
- )
3621
+ try:
3622
+ subprocess.run(
3623
+ ["launchctl", "bootstrap", _launchd_domain(), str(plist_path)],
3624
+ check=True,
3625
+ timeout=30,
3626
+ )
3627
+ subprocess.run(
3628
+ ["launchctl", "kickstart", f"{_launchd_domain()}/{label}"],
3629
+ check=True,
3630
+ timeout=30,
3631
+ )
3632
+ except subprocess.CalledProcessError as e2:
3633
+ # Even a fresh bootstrap can't manage the domain on this host —
3634
+ # degrade to a detached background process (issue #23387).
3635
+ if not _launchctl_domain_unsupported(e2.returncode):
3636
+ raise
3637
+ _launchd_fallback_to_detached(f"launchctl exit {e2.returncode}")
3638
+ return
3236
3639
  print("✓ Service started")
3237
3640
 
3238
3641
 
@@ -3249,13 +3652,18 @@ def launchd_stop():
3249
3652
  pass
3250
3653
  # bootout unloads the service definition so KeepAlive doesn't respawn
3251
3654
  # the process. A plain `kill SIGTERM` only signals the process — launchd
3252
- # immediately restarts it because KeepAlive.SuccessfulExit = false.
3655
+ # immediately restarts it because KeepAlive is unconditionally true.
3253
3656
  # `hermes gateway start` re-bootstraps when it detects the job is unloaded.
3254
3657
  try:
3255
3658
  subprocess.run(["launchctl", "bootout", target], check=True, timeout=90)
3256
3659
  except subprocess.CalledProcessError as e:
3257
- if e.returncode in {3, 113}:
3258
- pass # Already unloaded nothing to stop.
3660
+ # Job already unloaded (3/113/125), or the domain can't be managed at
3661
+ # all (5/125, macOS 26+ detached-fallback process, issue #23387) — in
3662
+ # both cases just fall through to the PID-based kill below.
3663
+ if _launchd_error_indicates_unloaded(e) or _launchctl_domain_unsupported(
3664
+ e.returncode
3665
+ ):
3666
+ pass
3259
3667
  else:
3260
3668
  raise
3261
3669
  _wait_for_gateway_exit(timeout=10.0, force_after=5.0)
@@ -3339,17 +3747,29 @@ def launchd_restart():
3339
3747
  subprocess.run(["launchctl", "kickstart", "-k", target], check=True, timeout=90)
3340
3748
  print("✓ Service restarted")
3341
3749
  except subprocess.CalledProcessError as e:
3342
- if e.returncode not in {3, 113}:
3750
+ if not _launchd_error_indicates_unloaded(e):
3751
+ # Not a "job unloaded" code. If the domain is fundamentally
3752
+ # unmanageable (error 5), degrade to detached; the old process was
3753
+ # already drained/terminated above. Otherwise re-raise.
3754
+ if _launchctl_domain_unsupported(e.returncode):
3755
+ _launchd_fallback_to_detached(f"launchctl kickstart exit {e.returncode}")
3756
+ return
3343
3757
  raise
3344
3758
  # Job not loaded — bootstrap and start fresh
3345
3759
  print("↻ launchd job was unloaded; reloading")
3346
3760
  plist_path = get_launchd_plist_path()
3347
- subprocess.run(
3348
- ["launchctl", "bootstrap", _launchd_domain(), str(plist_path)],
3349
- check=True,
3350
- timeout=30,
3351
- )
3352
- subprocess.run(["launchctl", "kickstart", target], check=True, timeout=30)
3761
+ try:
3762
+ subprocess.run(
3763
+ ["launchctl", "bootstrap", _launchd_domain(), str(plist_path)],
3764
+ check=True,
3765
+ timeout=30,
3766
+ )
3767
+ subprocess.run(["launchctl", "kickstart", target], check=True, timeout=30)
3768
+ except subprocess.CalledProcessError as e2:
3769
+ if not _launchctl_domain_unsupported(e2.returncode):
3770
+ raise
3771
+ _launchd_fallback_to_detached(f"launchctl exit {e2.returncode}")
3772
+ return
3353
3773
  print("✓ Service restarted")
3354
3774
 
3355
3775
 
@@ -3408,6 +3828,181 @@ def _is_official_docker_checkout() -> bool:
3408
3828
  )
3409
3829
 
3410
3830
 
3831
+ def _running_under_gateway_supervisor() -> bool:
3832
+ """Return True when this process IS the gateway a service manager launched.
3833
+
3834
+ The conflict guard below must never fire on the service's own startup, or
3835
+ it would wedge the unit into a respawn/refuse loop. Each supervisor exports
3836
+ a reliable marker into the child's environment:
3837
+
3838
+ - systemd sets ``INVOCATION_ID`` for every unit it launches (the same
3839
+ marker ``gateway/run.py`` already uses to pick the restart path).
3840
+ - launchd sets ``XPC_SERVICE_NAME`` to the job label for jobs it spawns;
3841
+ interactive shells inherit the sentinel ``"0"`` instead.
3842
+ - the s6-overlay container longrun exports ``HERMES_S6_SUPERVISED_CHILD``.
3843
+ """
3844
+ if os.environ.get("INVOCATION_ID"):
3845
+ return True
3846
+ if os.environ.get("HERMES_S6_SUPERVISED_CHILD"):
3847
+ return True
3848
+ xpc_service = os.environ.get("XPC_SERVICE_NAME", "")
3849
+ if xpc_service and xpc_service != "0":
3850
+ return True
3851
+ return False
3852
+
3853
+
3854
+ def _guard_named_profile_under_multiplexer(force: bool = False) -> None:
3855
+ """Refuse a named-profile gateway when a multiplexer is already serving it.
3856
+
3857
+ When the default profile's gateway runs with gateway.multiplex_profiles=on,
3858
+ it is the sole inbound process for EVERY profile on the host. Starting a
3859
+ separate gateway for a named profile would double-bind that profile's
3860
+ platforms (two pollers on one bot token, port fights). In that mode a
3861
+ named-profile ``hermes gateway run`` is always a misconfiguration, so we
3862
+ hard-error with a pointer to the multiplexer. ``--force`` overrides.
3863
+
3864
+ Inert unless ALL of: (a) this invocation is a named profile, (b) a default-
3865
+ profile gateway is running, (c) that gateway's config has multiplexing on.
3866
+ """
3867
+ if force:
3868
+ return
3869
+ # (a) Are we a named profile? Default/custom-hash homes return "".
3870
+ try:
3871
+ suffix = _profile_suffix()
3872
+ except Exception:
3873
+ return
3874
+ if not suffix:
3875
+ return # default profile (or unrecognized) — this guard doesn't apply
3876
+
3877
+ try:
3878
+ from hermes_constants import get_default_hermes_root
3879
+ default_root = get_default_hermes_root()
3880
+ # (b) Is the default-profile gateway running?
3881
+ from gateway.status import get_running_pid as _default_running_pid # noqa
3882
+ except Exception:
3883
+ return
3884
+
3885
+ try:
3886
+ import yaml as _yaml
3887
+ from gateway.status import _read_pid_record # type: ignore
3888
+
3889
+ # (b) default gateway PID file present + alive
3890
+ default_pid_path = default_root / "gateway.pid"
3891
+ rec = _read_pid_record(default_pid_path)
3892
+ if not rec:
3893
+ return
3894
+ from gateway.status import _pid_exists, _pid_from_record
3895
+ pid = _pid_from_record(rec)
3896
+ if not pid or not _pid_exists(pid):
3897
+ return
3898
+
3899
+ # (c) default config has multiplexing on
3900
+ cfg_path = default_root / "config.yaml"
3901
+ if not cfg_path.exists():
3902
+ return
3903
+ with open(cfg_path, encoding="utf-8") as f:
3904
+ cfg = _yaml.safe_load(f) or {}
3905
+ multiplex = bool(
3906
+ cfg.get("multiplex_profiles")
3907
+ or (cfg.get("gateway", {}) or {}).get("multiplex_profiles")
3908
+ )
3909
+ if not multiplex:
3910
+ return
3911
+ except Exception:
3912
+ logger.debug("Multiplexer-conflict probe failed", exc_info=True)
3913
+ return
3914
+
3915
+ print_error(
3916
+ f"The default gateway is running as a profile multiplexer and already "
3917
+ f"serves profile '{suffix}'."
3918
+ )
3919
+ print(
3920
+ " When gateway.multiplex_profiles is on, the default gateway is the\n"
3921
+ " single inbound process for every profile. Starting a separate\n"
3922
+ " gateway for this profile would double-bind its platforms (two\n"
3923
+ " pollers on one bot token, port conflicts).\n"
3924
+ )
3925
+ print(" Manage the multiplexer instead (from the default profile):")
3926
+ print()
3927
+ print(" hermes gateway restart")
3928
+ print()
3929
+ print(" Pass --force to start a separate profile gateway anyway (not")
3930
+ print(" recommended while the multiplexer is running).")
3931
+ sys.exit(1)
3932
+
3933
+
3934
+ def _guard_supervised_gateway_conflict(force: bool = False) -> None:
3935
+ """Refuse a foreground gateway when a service manager already supervises one.
3936
+
3937
+ Running ``hermes gateway run [--replace]`` (or the manual-restart fallback)
3938
+ from a shell on a systemd/launchd host spawns a second, long-lived
3939
+ dispatcher that escapes the service cgroup, survives
3940
+ ``systemctl restart``, and becomes a silent concurrent writer on the shared
3941
+ kanban DB — the documented root cause of multi-writer SQLite WAL corruption
3942
+ (issue #35240). Pass ``--force`` to start anyway.
3943
+ """
3944
+ if force or _running_under_gateway_supervisor():
3945
+ return
3946
+ try:
3947
+ snapshot = get_gateway_runtime_snapshot()
3948
+ except Exception:
3949
+ # Best-effort guard: a probe failure must never block a real startup.
3950
+ logger.debug("Supervised-gateway conflict probe failed", exc_info=True)
3951
+ return
3952
+ if not (snapshot.service_installed and snapshot.service_running):
3953
+ return
3954
+
3955
+ print_error(
3956
+ f"A gateway is already running under {snapshot.manager} for this profile."
3957
+ )
3958
+ print(
3959
+ " Starting another one from a shell leaves an orphan dispatcher that\n"
3960
+ " escapes the service, survives restarts, and writes to the same kanban\n"
3961
+ " DB concurrently — which can corrupt it. Restart the supervised gateway\n"
3962
+ " instead:"
3963
+ )
3964
+ print()
3965
+ print(" hermes gateway restart")
3966
+ print()
3967
+ print(
3968
+ " Pass --force to start a foreground gateway anyway (not recommended\n"
3969
+ " while the service is running)."
3970
+ )
3971
+ sys.exit(1)
3972
+
3973
+
3974
+ def _guard_existing_gateway_process_conflict(replace: bool = False) -> None:
3975
+ """Refuse duplicate foreground startup before importing gateway.run.
3976
+
3977
+ ``gateway.run`` performs the authoritative PID/lock check, but importing it
3978
+ is expensive: it pulls in model_tools/plugin discovery first. On small
3979
+ instances, a supervisor or dashboard loop repeatedly running bare
3980
+ ``hermes gateway run`` can burn memory/CPU just to fail with "already
3981
+ running" after plugin discovery. This cheap PID-file preflight preserves the
3982
+ same user-facing contract while avoiding that startup work without scanning
3983
+ unrelated gateway processes from other HERMES_HOME roots.
3984
+ """
3985
+ if replace or _running_under_gateway_supervisor():
3986
+ return
3987
+ try:
3988
+ from gateway.status import get_running_pid
3989
+
3990
+ pid = get_running_pid()
3991
+ except Exception:
3992
+ logger.debug("Existing-gateway process probe failed", exc_info=True)
3993
+ return
3994
+ if pid is None:
3995
+ return
3996
+
3997
+ print_error(
3998
+ f"Another gateway instance is already running (PID {pid})."
3999
+ )
4000
+ print(" Use 'hermes gateway restart' to replace it,")
4001
+ print(" or 'hermes gateway stop' first.")
4002
+ print(" Or use 'hermes gateway run --replace' to auto-replace.")
4003
+ sys.exit(1)
4004
+
4005
+
3411
4006
  def _guard_official_docker_root_gateway() -> None:
3412
4007
  """Refuse gateway startup when the official Docker privilege drop was bypassed."""
3413
4008
  if not hasattr(os, "geteuid") or os.geteuid() != 0:
@@ -3435,7 +4030,7 @@ def _guard_official_docker_root_gateway() -> None:
3435
4030
  sys.exit(1)
3436
4031
 
3437
4032
 
3438
- def run_gateway(verbose: int = 0, quiet: bool = False, replace: bool = False):
4033
+ def run_gateway(verbose: int = 0, quiet: bool = False, replace: bool = False, force: bool = False):
3439
4034
  """Run the gateway in foreground.
3440
4035
 
3441
4036
  Args:
@@ -3444,8 +4039,13 @@ def run_gateway(verbose: int = 0, quiet: bool = False, replace: bool = False):
3444
4039
  replace: If True, kill any existing gateway instance before starting.
3445
4040
  This prevents systemd restart loops when the old process
3446
4041
  hasn't fully exited yet.
4042
+ force: Skip the supervised-gateway conflict guard and start even when a
4043
+ systemd/launchd service is already supervising this profile.
3447
4044
  """
3448
4045
  _guard_official_docker_root_gateway()
4046
+ _guard_named_profile_under_multiplexer(force=force)
4047
+ _guard_supervised_gateway_conflict(force=force)
4048
+ _guard_existing_gateway_process_conflict(replace=replace)
3449
4049
  sys.path.insert(0, str(PROJECT_ROOT))
3450
4050
 
3451
4051
  # Detached Windows gateway runs must ignore console-control broadcasts
@@ -4386,6 +4986,35 @@ def _setup_standard_platform(platform: dict):
4386
4986
  if not prompt_yes_no(f" Reconfigure {label}?", False):
4387
4987
  return
4388
4988
 
4989
+ auto_token_saved = False
4990
+ auto_owner_user_id = None
4991
+ if platform.get("key") == "telegram":
4992
+ print()
4993
+ print_info(" Telegram can be configured automatically with a managed bot:")
4994
+ print_info(" [1] Automatic (scan QR → confirm in Telegram → done)")
4995
+ print_info(" [2] Manual BotFather token")
4996
+ choice = prompt(" Choice [1/2]", default="1")
4997
+ if choice.strip() == "1":
4998
+ try:
4999
+ from hermes_cli.telegram_managed_bot import (
5000
+ auto_setup_telegram_bot_result,
5001
+ is_valid_telegram_bot_token,
5002
+ )
5003
+ except ImportError:
5004
+ print_warning(" Automatic setup is unavailable in this install.")
5005
+ else:
5006
+ result = auto_setup_telegram_bot_result()
5007
+ if result and is_valid_telegram_bot_token(result.token):
5008
+ save_env_value(token_var, result.token)
5009
+ print_success(" Saved TELEGRAM_BOT_TOKEN")
5010
+ auto_token_saved = True
5011
+ auto_owner_user_id = result.owner_user_id
5012
+ else:
5013
+ if result:
5014
+ print_warning(" Automatic setup returned an invalid Telegram token.")
5015
+ print()
5016
+ print_info(" Falling back to manual setup...")
5017
+
4389
5018
  allowed_val_set = None # Track if user set an allowlist (for home channel offer)
4390
5019
 
4391
5020
  for var in platform["vars"]:
@@ -4395,8 +5024,30 @@ def _setup_standard_platform(platform: dict):
4395
5024
  if existing and var["name"] != token_var:
4396
5025
  print_info(f" Current: {existing}")
4397
5026
 
5027
+ if auto_token_saved and var["name"] == token_var:
5028
+ print_info(" Token saved by automatic setup.")
5029
+ continue
5030
+
4398
5031
  # Allowlist fields get special handling for the deny-by-default security model
4399
5032
  if var.get("is_allowlist"):
5033
+ if "TELEGRAM" in var["name"] and auto_owner_user_id:
5034
+ detected_id = str(auto_owner_user_id)
5035
+ print_success(f" Detected your Telegram user ID: {detected_id}")
5036
+ if prompt_yes_no(" Allow this Telegram account to use the bot?", True):
5037
+ extra = prompt(
5038
+ " Additional allowed user IDs (comma-separated, optional)",
5039
+ password=False,
5040
+ )
5041
+ ids = [detected_id]
5042
+ for uid in extra.replace(" ", "").split(","):
5043
+ if uid and uid not in ids:
5044
+ ids.append(uid)
5045
+ cleaned = ",".join(ids)
5046
+ save_env_value(var["name"], cleaned)
5047
+ print_success(" Saved — only these users can interact with the bot.")
5048
+ allowed_val_set = cleaned
5049
+ continue
5050
+
4400
5051
  print_info(" The gateway DENIES all users by default for security.")
4401
5052
  print_info(" Enter user IDs to create an allowlist, or leave empty")
4402
5053
  print_info(" and you'll be asked about open access next.")
@@ -5940,7 +6591,8 @@ def _gateway_command_inner(args):
5940
6591
  verbose = getattr(args, "verbose", 0)
5941
6592
  quiet = getattr(args, "quiet", False)
5942
6593
  replace = getattr(args, "replace", False)
5943
- run_gateway(verbose, quiet=quiet, replace=replace)
6594
+ force = getattr(args, "force", False)
6595
+ run_gateway(verbose, quiet=quiet, replace=replace, force=force)
5944
6596
  return
5945
6597
 
5946
6598
  if subcmd == "setup":