@clawpump/claw-agent 0.1.5 → 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 (1212) 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 +2177 -3162
  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/cli_agent_setup_mixin.py +684 -0
  586. package/agent/hermes_cli/cli_commands_mixin.py +2293 -0
  587. package/agent/hermes_cli/commands.py +215 -91
  588. package/agent/hermes_cli/config.py +967 -130
  589. package/agent/hermes_cli/container_boot.py +76 -11
  590. package/agent/hermes_cli/cron.py +5 -11
  591. package/agent/hermes_cli/curator.py +21 -0
  592. package/agent/hermes_cli/dashboard_auth/__init__.py +2 -0
  593. package/agent/hermes_cli/dashboard_auth/base.py +62 -0
  594. package/agent/hermes_cli/dashboard_auth/cookies.py +32 -19
  595. package/agent/hermes_cli/dashboard_auth/login_page.py +156 -6
  596. package/agent/hermes_cli/dashboard_auth/middleware.py +28 -4
  597. package/agent/hermes_cli/dashboard_auth/prefix.py +46 -2
  598. package/agent/hermes_cli/dashboard_auth/public_paths.py +6 -0
  599. package/agent/hermes_cli/dashboard_auth/routes.py +158 -2
  600. package/agent/hermes_cli/dashboard_auth/ws_tickets.py +85 -11
  601. package/agent/hermes_cli/dashboard_register.py +427 -0
  602. package/agent/hermes_cli/debug.py +155 -50
  603. package/agent/hermes_cli/doctor.py +255 -14
  604. package/agent/hermes_cli/dump.py +60 -6
  605. package/agent/hermes_cli/env_loader.py +33 -0
  606. package/agent/hermes_cli/gateway.py +755 -103
  607. package/agent/hermes_cli/gateway_enroll.py +250 -0
  608. package/agent/hermes_cli/gateway_windows.py +254 -11
  609. package/agent/hermes_cli/gui_uninstall.py +285 -0
  610. package/agent/hermes_cli/inventory.py +105 -4
  611. package/agent/hermes_cli/kanban.py +58 -71
  612. package/agent/hermes_cli/kanban_db.py +391 -14
  613. package/agent/hermes_cli/kanban_decompose.py +2 -2
  614. package/agent/hermes_cli/kanban_specify.py +3 -1
  615. package/agent/hermes_cli/logs.py +2 -0
  616. package/agent/hermes_cli/main.py +2889 -5287
  617. package/agent/hermes_cli/managed_scope.py +214 -0
  618. package/agent/hermes_cli/managed_uv.py +254 -0
  619. package/agent/hermes_cli/mcp_catalog.py +6 -3
  620. package/agent/hermes_cli/mcp_config.py +145 -21
  621. package/agent/hermes_cli/mcp_security.py +96 -0
  622. package/agent/hermes_cli/mcp_startup.py +32 -3
  623. package/agent/hermes_cli/memory_providers.py +149 -0
  624. package/agent/hermes_cli/memory_setup.py +97 -42
  625. package/agent/hermes_cli/middleware.py +313 -0
  626. package/agent/hermes_cli/model_catalog.py +31 -0
  627. package/agent/hermes_cli/model_cost_guard.py +134 -0
  628. package/agent/hermes_cli/model_normalize.py +2 -1
  629. package/agent/hermes_cli/model_setup_flows.py +2759 -0
  630. package/agent/hermes_cli/model_switch.py +242 -27
  631. package/agent/hermes_cli/models.py +284 -44
  632. package/agent/hermes_cli/nous_account.py +33 -6
  633. package/agent/hermes_cli/nous_billing.py +406 -0
  634. package/agent/hermes_cli/nous_subscription.py +202 -5
  635. package/agent/hermes_cli/platforms.py +1 -0
  636. package/agent/hermes_cli/plugins.py +218 -18
  637. package/agent/hermes_cli/plugins_cmd.py +249 -105
  638. package/agent/hermes_cli/portal_cli.py +56 -16
  639. package/agent/hermes_cli/profile_distribution.py +6 -1
  640. package/agent/hermes_cli/profiles.py +283 -32
  641. package/agent/hermes_cli/provider_catalog.py +170 -0
  642. package/agent/hermes_cli/providers.py +4 -1
  643. package/agent/hermes_cli/pty_bridge.py +53 -4
  644. package/agent/hermes_cli/runtime_provider.py +216 -34
  645. package/agent/hermes_cli/secret_prompt.py +4 -4
  646. package/agent/hermes_cli/secrets_cli.py +24 -0
  647. package/agent/hermes_cli/send_cmd.py +28 -2
  648. package/agent/hermes_cli/service_manager.py +166 -19
  649. package/agent/hermes_cli/session_listing.py +97 -0
  650. package/agent/hermes_cli/setup.py +158 -94
  651. package/agent/hermes_cli/setup_whatsapp_cloud.py +541 -0
  652. package/agent/hermes_cli/skills_config.py +8 -2
  653. package/agent/hermes_cli/skills_hub.py +149 -7
  654. package/agent/hermes_cli/status.py +2 -2
  655. package/agent/hermes_cli/subcommands/__init__.py +18 -0
  656. package/agent/hermes_cli/subcommands/_shared.py +29 -0
  657. package/agent/hermes_cli/subcommands/acp.py +52 -0
  658. package/agent/hermes_cli/subcommands/auth.py +109 -0
  659. package/agent/hermes_cli/subcommands/backup.py +38 -0
  660. package/agent/hermes_cli/subcommands/claw.py +92 -0
  661. package/agent/hermes_cli/subcommands/config.py +49 -0
  662. package/agent/hermes_cli/subcommands/cron.py +163 -0
  663. package/agent/hermes_cli/subcommands/dashboard.py +143 -0
  664. package/agent/hermes_cli/subcommands/debug.py +77 -0
  665. package/agent/hermes_cli/subcommands/doctor.py +35 -0
  666. package/agent/hermes_cli/subcommands/dump.py +28 -0
  667. package/agent/hermes_cli/subcommands/gateway.py +332 -0
  668. package/agent/hermes_cli/subcommands/gui.py +63 -0
  669. package/agent/hermes_cli/subcommands/hooks.py +77 -0
  670. package/agent/hermes_cli/subcommands/import_cmd.py +31 -0
  671. package/agent/hermes_cli/subcommands/insights.py +25 -0
  672. package/agent/hermes_cli/subcommands/login.py +78 -0
  673. package/agent/hermes_cli/subcommands/logout.py +28 -0
  674. package/agent/hermes_cli/subcommands/logs.py +78 -0
  675. package/agent/hermes_cli/subcommands/mcp.py +108 -0
  676. package/agent/hermes_cli/subcommands/memory.py +53 -0
  677. package/agent/hermes_cli/subcommands/model.py +72 -0
  678. package/agent/hermes_cli/subcommands/pairing.py +36 -0
  679. package/agent/hermes_cli/subcommands/plugins.py +94 -0
  680. package/agent/hermes_cli/subcommands/postinstall.py +23 -0
  681. package/agent/hermes_cli/subcommands/profile.py +203 -0
  682. package/agent/hermes_cli/subcommands/prompt_size.py +36 -0
  683. package/agent/hermes_cli/subcommands/security.py +62 -0
  684. package/agent/hermes_cli/subcommands/setup.py +58 -0
  685. package/agent/hermes_cli/subcommands/skills.py +298 -0
  686. package/agent/hermes_cli/subcommands/slack.py +60 -0
  687. package/agent/hermes_cli/subcommands/status.py +28 -0
  688. package/agent/hermes_cli/subcommands/tools.py +95 -0
  689. package/agent/hermes_cli/subcommands/uninstall.py +41 -0
  690. package/agent/hermes_cli/subcommands/update.py +70 -0
  691. package/agent/hermes_cli/subcommands/version.py +18 -0
  692. package/agent/hermes_cli/subcommands/webhook.py +76 -0
  693. package/agent/hermes_cli/subcommands/whatsapp.py +22 -0
  694. package/agent/hermes_cli/suggestions_cmd.py +153 -0
  695. package/agent/hermes_cli/telegram_managed_bot.py +358 -0
  696. package/agent/hermes_cli/tips.py +3 -4
  697. package/agent/hermes_cli/tools_config.py +155 -28
  698. package/agent/hermes_cli/uninstall.py +231 -35
  699. package/agent/hermes_cli/web_server.py +6188 -975
  700. package/agent/hermes_cli/win_pty_bridge.py +179 -0
  701. package/agent/hermes_cli/write_approval_commands.py +209 -0
  702. package/agent/hermes_constants.py +164 -33
  703. package/agent/hermes_logging.py +74 -2
  704. package/agent/hermes_state.py +919 -106
  705. package/agent/hermes_time.py +20 -0
  706. package/agent/locales/af.yaml +23 -0
  707. package/agent/locales/de.yaml +23 -0
  708. package/agent/locales/en.yaml +20 -0
  709. package/agent/locales/es.yaml +23 -0
  710. package/agent/locales/fr.yaml +23 -0
  711. package/agent/locales/ga.yaml +23 -0
  712. package/agent/locales/hu.yaml +23 -0
  713. package/agent/locales/it.yaml +23 -0
  714. package/agent/locales/ja.yaml +23 -0
  715. package/agent/locales/ko.yaml +23 -0
  716. package/agent/locales/pt.yaml +23 -0
  717. package/agent/locales/ru.yaml +23 -0
  718. package/agent/locales/tr.yaml +23 -0
  719. package/agent/locales/uk.yaml +23 -0
  720. package/agent/locales/zh-hant.yaml +23 -0
  721. package/agent/locales/zh.yaml +23 -0
  722. package/agent/model_tools.py +204 -40
  723. package/agent/optional-mcps/clawpump/manifest.yaml +4 -2
  724. package/agent/optional-mcps/clawpump-stdio/manifest.yaml +2 -0
  725. package/agent/optional-mcps/unreal-engine/manifest.yaml +54 -0
  726. package/agent/optional-skills/blockchain/hyperliquid/SKILL.md +2 -2
  727. package/agent/optional-skills/blockchain/hyperliquid/scripts/hyperliquid_client.py +1 -1
  728. package/agent/optional-skills/creative/kanban-video-orchestrator/SKILL.md +1 -1
  729. package/agent/optional-skills/creative/kanban-video-orchestrator/assets/setup.sh.tmpl +4 -3
  730. package/agent/optional-skills/creative/kanban-video-orchestrator/references/kanban-setup.md +6 -4
  731. package/agent/optional-skills/creative/kanban-video-orchestrator/references/tool-matrix.md +2 -2
  732. package/agent/{skills/software-development → optional-skills/devops}/hermes-s6-container-supervision/SKILL.md +2 -0
  733. package/agent/optional-skills/devops/watchers/SKILL.md +1 -1
  734. package/agent/optional-skills/devops/watchers/scripts/watch_github.py +2 -1
  735. package/agent/optional-skills/payments/mpp-agent/SKILL.md +124 -0
  736. package/agent/optional-skills/payments/stripe-link-cli/SKILL.md +184 -0
  737. package/agent/optional-skills/payments/stripe-projects/SKILL.md +120 -0
  738. package/agent/optional-skills/productivity/canvas/SKILL.md +1 -1
  739. package/agent/optional-skills/productivity/canvas/scripts/canvas_api.py +4 -1
  740. package/agent/optional-skills/productivity/shop/SKILL.md +224 -0
  741. package/agent/optional-skills/productivity/shop/references/catalog-mcp.md +236 -0
  742. package/agent/optional-skills/productivity/shop/references/direct-api.md +278 -0
  743. package/agent/optional-skills/productivity/shop/references/legal.md +3 -0
  744. package/agent/optional-skills/productivity/shop/references/safety.md +36 -0
  745. package/agent/optional-skills/productivity/shopify/SKILL.md +1 -1
  746. package/agent/optional-skills/productivity/siyuan/SKILL.md +1 -1
  747. package/agent/optional-skills/productivity/telephony/SKILL.md +4 -4
  748. package/agent/optional-skills/productivity/telephony/scripts/telephony.py +15 -15
  749. package/agent/optional-skills/security/1password/SKILL.md +1 -1
  750. package/agent/{skills/red-teaming → optional-skills/security}/godmode/SKILL.md +3 -4
  751. package/agent/{skills/red-teaming → optional-skills/security}/godmode/scripts/auto_jailbreak.py +3 -1
  752. package/agent/optional-skills/software-development/rest-graphql-debug/SKILL.md +1 -1
  753. package/agent/{skills → optional-skills}/software-development/subagent-driven-development/SKILL.md +5 -5
  754. package/agent/package-lock.json +4082 -7907
  755. package/agent/package.json +18 -3
  756. package/agent/plugins/browser/firecrawl/provider.py +4 -1
  757. package/agent/plugins/cron/__init__.py +344 -0
  758. package/agent/plugins/cron/chronos/__init__.py +241 -0
  759. package/agent/plugins/cron/chronos/_nas_client.py +123 -0
  760. package/agent/plugins/cron/chronos/plugin.yaml +9 -0
  761. package/agent/plugins/cron/chronos/verify.py +103 -0
  762. package/agent/plugins/dashboard_auth/basic/__init__.py +491 -0
  763. package/agent/plugins/dashboard_auth/basic/plugin.yaml +7 -0
  764. package/agent/plugins/dashboard_auth/nous/__init__.py +12 -14
  765. package/agent/plugins/dashboard_auth/self_hosted/__init__.py +736 -0
  766. package/agent/plugins/dashboard_auth/self_hosted/plugin.yaml +8 -0
  767. package/agent/plugins/disk-cleanup/disk_cleanup.py +100 -20
  768. package/agent/plugins/google_meet/audio_bridge.py +4 -0
  769. package/agent/plugins/google_meet/meet_bot.py +7 -1
  770. package/agent/plugins/hermes-achievements/dashboard/dist/index.js +9 -15
  771. package/agent/plugins/image_gen/fal/__init__.py +35 -6
  772. package/agent/plugins/image_gen/krea/__init__.py +56 -13
  773. package/agent/plugins/image_gen/openai/__init__.py +122 -24
  774. package/agent/plugins/image_gen/openai-codex/__init__.py +28 -2
  775. package/agent/plugins/image_gen/xai/__init__.py +92 -12
  776. package/agent/plugins/kanban/dashboard/dist/index.js +63 -48
  777. package/agent/plugins/kanban/dashboard/plugin_api.py +39 -35
  778. package/agent/plugins/memory/__init__.py +48 -5
  779. package/agent/plugins/memory/byterover/__init__.py +1 -0
  780. package/agent/plugins/memory/hindsight/README.md +1 -1
  781. package/agent/plugins/memory/hindsight/__init__.py +138 -24
  782. package/agent/plugins/memory/hindsight/plugin.yaml +1 -1
  783. package/agent/plugins/memory/honcho/README.md +13 -10
  784. package/agent/plugins/memory/honcho/cli.py +247 -122
  785. package/agent/plugins/memory/honcho/client.py +112 -102
  786. package/agent/plugins/memory/openviking/README.md +12 -1
  787. package/agent/plugins/memory/openviking/__init__.py +2281 -107
  788. package/agent/plugins/memory/openviking/plugin.yaml +1 -2
  789. package/agent/plugins/memory/supermemory/README.md +22 -10
  790. package/agent/plugins/memory/supermemory/__init__.py +142 -37
  791. package/agent/plugins/memory/supermemory/plugin.yaml +1 -1
  792. package/agent/plugins/model-providers/anthropic/__init__.py +1 -0
  793. package/agent/plugins/model-providers/bedrock/__init__.py +1 -0
  794. package/agent/plugins/model-providers/copilot-acp/__init__.py +1 -0
  795. package/agent/plugins/model-providers/custom/__init__.py +8 -2
  796. package/agent/plugins/model-providers/kimi-coding/__init__.py +16 -7
  797. package/agent/plugins/model-providers/minimax/__init__.py +60 -8
  798. package/agent/plugins/model-providers/opencode-zen/__init__.py +12 -3
  799. package/agent/plugins/model-providers/openrouter/__init__.py +75 -4
  800. package/agent/plugins/model-providers/xiaomi/__init__.py +2 -0
  801. package/agent/plugins/model-providers/zai/__init__.py +1 -0
  802. package/agent/plugins/observability/langfuse/__init__.py +147 -14
  803. package/agent/plugins/observability/nemo_relay/README.md +559 -0
  804. package/agent/plugins/observability/nemo_relay/__init__.py +962 -0
  805. package/agent/plugins/observability/nemo_relay/plugin.yaml +20 -0
  806. package/agent/plugins/platforms/discord/adapter.py +932 -61
  807. package/agent/plugins/platforms/discord/voice_mixer.py +379 -0
  808. package/agent/plugins/platforms/google_chat/adapter.py +9 -3
  809. package/agent/plugins/platforms/google_chat/oauth.py +1 -1
  810. package/agent/plugins/platforms/homeassistant/__init__.py +3 -0
  811. package/agent/{gateway/platforms/homeassistant.py → plugins/platforms/homeassistant/adapter.py} +128 -0
  812. package/agent/plugins/platforms/homeassistant/plugin.yaml +22 -0
  813. package/agent/plugins/platforms/irc/adapter.py +4 -1
  814. package/agent/plugins/platforms/line/adapter.py +16 -1
  815. package/agent/plugins/platforms/mattermost/adapter.py +100 -24
  816. package/agent/plugins/platforms/photon/README.md +179 -0
  817. package/agent/plugins/platforms/photon/__init__.py +4 -0
  818. package/agent/plugins/platforms/photon/adapter.py +1586 -0
  819. package/agent/plugins/platforms/photon/auth.py +1046 -0
  820. package/agent/plugins/platforms/photon/cli.py +439 -0
  821. package/agent/plugins/platforms/photon/plugin.yaml +88 -0
  822. package/agent/plugins/platforms/photon/sidecar/README.md +52 -0
  823. package/agent/plugins/platforms/photon/sidecar/index.mjs +720 -0
  824. package/agent/plugins/platforms/photon/sidecar/package-lock.json +1730 -0
  825. package/agent/plugins/platforms/photon/sidecar/package.json +25 -0
  826. package/agent/plugins/platforms/photon/sidecar/patch-spectrum-mixed-attachments.mjs +155 -0
  827. package/agent/plugins/platforms/raft/__init__.py +3 -0
  828. package/agent/plugins/platforms/raft/adapter.py +774 -0
  829. package/agent/plugins/platforms/raft/plugin.yaml +19 -0
  830. package/agent/plugins/platforms/simplex/adapter.py +777 -220
  831. package/agent/plugins/platforms/simplex/plugin.yaml +21 -2
  832. package/agent/plugins/platforms/teams/adapter.py +175 -5
  833. package/agent/plugins/plugin_utils.py +135 -0
  834. package/agent/plugins/video_gen/fal/__init__.py +10 -3
  835. package/agent/plugins/web/searxng/provider.py +15 -2
  836. package/agent/plugins/web/xai/provider.py +2 -2
  837. package/agent/providers/base.py +22 -3
  838. package/agent/pyproject.toml +115 -21
  839. package/agent/run_agent.py +733 -39
  840. package/agent/scripts/build_skills_index.py +51 -19
  841. package/agent/scripts/check_subprocess_stdin.py +177 -0
  842. package/agent/scripts/contributor_audit.py +2 -0
  843. package/agent/scripts/docker_config_migrate.py +67 -0
  844. package/agent/scripts/install.cmd +3 -3
  845. package/agent/scripts/install.ps1 +580 -154
  846. package/agent/scripts/install.sh +402 -185
  847. package/agent/scripts/lib/node-bootstrap.sh +39 -4
  848. package/agent/scripts/release.py +183 -0
  849. package/agent/scripts/run_tests.sh +1 -0
  850. package/agent/scripts/run_tests_parallel.py +18 -23
  851. package/agent/scripts/whatsapp-bridge/bridge.js +25 -4
  852. package/agent/setup.py +59 -0
  853. package/agent/skills/autonomous-ai-agents/codex/SKILL.md +19 -0
  854. package/agent/skills/autonomous-ai-agents/hermes-agent/SKILL.md +10 -3
  855. package/agent/skills/{mcp/native-mcp/SKILL.md → autonomous-ai-agents/hermes-agent/references/native-mcp.md} +0 -13
  856. package/agent/skills/{devops/webhook-subscriptions/SKILL.md → autonomous-ai-agents/hermes-agent/references/webhooks.md} +1 -11
  857. package/agent/skills/clawpump/SKILL.md +3 -1
  858. package/agent/skills/devops/kanban-orchestrator/SKILL.md +1 -0
  859. package/agent/skills/devops/kanban-worker/SKILL.md +1 -0
  860. package/agent/skills/github/github-auth/SKILL.md +2 -2
  861. package/agent/skills/github/github-auth/scripts/gh-env.sh +2 -2
  862. package/agent/skills/github/github-code-review/SKILL.md +2 -2
  863. package/agent/skills/github/github-issues/SKILL.md +2 -2
  864. package/agent/skills/github/github-pr-workflow/SKILL.md +2 -2
  865. package/agent/skills/github/github-repo-management/SKILL.md +2 -2
  866. package/agent/skills/media/gif-search/SKILL.md +1 -1
  867. package/agent/skills/media/youtube-content/SKILL.md +10 -7
  868. package/agent/skills/media/youtube-content/scripts/fetch_transcript.py +3 -3
  869. package/agent/skills/note-taking/obsidian/SKILL.md +1 -1
  870. package/agent/skills/productivity/airtable/SKILL.md +2 -2
  871. package/agent/skills/productivity/google-workspace/scripts/setup.py +33 -7
  872. package/agent/skills/productivity/notion/SKILL.md +2 -2
  873. package/agent/skills/productivity/teams-meeting-pipeline/SKILL.md +1 -1
  874. package/agent/skills/research/llm-wiki/SKILL.md +1 -1
  875. package/agent/skills/social-media/xurl/SKILL.md +9 -0
  876. package/agent/skills/software-development/hermes-agent-skill-authoring/SKILL.md +1 -1
  877. package/agent/skills/software-development/plan/SKILL.md +285 -5
  878. package/agent/skills/software-development/requesting-code-review/SKILL.md +2 -2
  879. package/agent/skills/software-development/simplify-code/SKILL.md +212 -0
  880. package/agent/skills/software-development/spike/SKILL.md +2 -2
  881. package/agent/skills/software-development/systematic-debugging/SKILL.md +1 -1
  882. package/agent/skills/software-development/test-driven-development/SKILL.md +1 -1
  883. package/agent/tools/approval.py +302 -4
  884. package/agent/tools/async_delegation.py +386 -0
  885. package/agent/tools/blueprints.py +325 -0
  886. package/agent/tools/browser_cdp_tool.py +3 -3
  887. package/agent/tools/browser_tool.py +34 -6
  888. package/agent/tools/checkpoint_manager.py +31 -1
  889. package/agent/tools/clarify_tool.py +55 -5
  890. package/agent/tools/code_execution_tool.py +31 -14
  891. package/agent/tools/computer_use/cua_backend.py +81 -3
  892. package/agent/tools/computer_use/tool.py +79 -5
  893. package/agent/tools/computer_use/vision_routing.py +55 -3
  894. package/agent/tools/credential_files.py +31 -12
  895. package/agent/tools/cronjob_tools.py +30 -20
  896. package/agent/tools/delegate_tool.py +356 -31
  897. package/agent/tools/env_probe.py +1 -0
  898. package/agent/tools/environments/docker.py +163 -8
  899. package/agent/tools/environments/file_sync.py +2 -1
  900. package/agent/tools/environments/local.py +74 -23
  901. package/agent/tools/environments/singularity.py +4 -1
  902. package/agent/tools/environments/ssh.py +78 -11
  903. package/agent/tools/file_operations.py +277 -41
  904. package/agent/tools/file_tools.py +166 -28
  905. package/agent/tools/image_generation_tool.py +515 -29
  906. package/agent/tools/kanban_tools.py +99 -0
  907. package/agent/tools/lazy_deps.py +33 -2
  908. package/agent/tools/mcp_oauth.py +5 -5
  909. package/agent/tools/mcp_oauth_manager.py +7 -5
  910. package/agent/tools/mcp_tool.py +840 -33
  911. package/agent/tools/memory_tool.py +335 -38
  912. package/agent/tools/osv_check.py +15 -1
  913. package/agent/tools/process_registry.py +155 -11
  914. package/agent/tools/read_extract.py +248 -0
  915. package/agent/tools/read_terminal_tool.py +93 -0
  916. package/agent/tools/schema_sanitizer.py +38 -0
  917. package/agent/tools/send_message_tool.py +163 -49
  918. package/agent/tools/session_search_tool.py +189 -7
  919. package/agent/tools/skill_manager_tool.py +202 -3
  920. package/agent/tools/skill_usage.py +52 -4
  921. package/agent/tools/skills_hub.py +184 -44
  922. package/agent/tools/skills_sync.py +232 -5
  923. package/agent/tools/skills_tool.py +125 -11
  924. package/agent/tools/terminal_tool.py +148 -26
  925. package/agent/tools/tirith_security.py +2 -0
  926. package/agent/tools/todo_tool.py +32 -1
  927. package/agent/tools/transcription_tools.py +13 -5
  928. package/agent/tools/tts_tool.py +332 -38
  929. package/agent/tools/url_safety.py +52 -1
  930. package/agent/tools/vision_tools.py +124 -39
  931. package/agent/tools/voice_mode.py +4 -3
  932. package/agent/tools/web_tools.py +45 -15
  933. package/agent/tools/write_approval.py +493 -0
  934. package/agent/toolsets.py +34 -10
  935. package/agent/trajectory_compressor.py +81 -10
  936. package/agent/tui_gateway/entry.py +43 -6
  937. package/agent/tui_gateway/server.py +3335 -330
  938. package/agent/tui_gateway/slash_worker.py +61 -0
  939. package/agent/tui_gateway/ws.py +67 -9
  940. package/agent/ui-tui/eslint.config.mjs +0 -4
  941. package/agent/ui-tui/package.json +6 -6
  942. package/agent/ui-tui/packages/hermes-ink/package.json +1 -1
  943. package/agent/ui-tui/packages/hermes-ink/src/ink/app-mouse.test.ts +34 -1
  944. package/agent/ui-tui/packages/hermes-ink/src/ink/app-rawmode-mouse.test.ts +91 -0
  945. package/agent/ui-tui/packages/hermes-ink/src/ink/components/App.tsx +35 -2
  946. package/agent/ui-tui/packages/hermes-ink/src/ink/events/input-event.ts +4 -11
  947. package/agent/ui-tui/packages/hermes-ink/src/ink/parse-keypress.test.ts +23 -57
  948. package/agent/ui-tui/packages/hermes-ink/src/ink/parse-keypress.ts +11 -135
  949. package/agent/ui-tui/packages/hermes-ink/src/ink/termio/tokenize.test.ts +185 -0
  950. package/agent/ui-tui/packages/hermes-ink/src/ink/termio/tokenize.ts +37 -3
  951. package/agent/ui-tui/packages/hermes-ink/src/utils/execFileNoThrow.ts +5 -5
  952. package/agent/ui-tui/src/__tests__/appChromeStatusRule.test.tsx +217 -0
  953. package/agent/ui-tui/src/__tests__/appChromeStatusRuleDevCredits.test.tsx +73 -0
  954. package/agent/ui-tui/src/__tests__/approvalAction.test.ts +11 -0
  955. package/agent/ui-tui/src/__tests__/billingCommand.test.ts +301 -0
  956. package/agent/ui-tui/src/__tests__/blockLayout.test.ts +122 -0
  957. package/agent/ui-tui/src/__tests__/brandingMcpCount.test.ts +111 -0
  958. package/agent/ui-tui/src/__tests__/completionApply.test.ts +51 -0
  959. package/agent/ui-tui/src/__tests__/createGatewayEventHandler.test.ts +487 -2
  960. package/agent/ui-tui/src/__tests__/createSlashHandler.test.ts +54 -0
  961. package/agent/ui-tui/src/__tests__/creditsCommand.test.ts +144 -0
  962. package/agent/ui-tui/src/__tests__/gatewayClient.test.ts +120 -99
  963. package/agent/ui-tui/src/__tests__/gracefulExit.test.ts +11 -0
  964. package/agent/ui-tui/src/__tests__/memoryMonitor.test.ts +102 -0
  965. package/agent/ui-tui/src/__tests__/paths.test.ts +41 -1
  966. package/agent/ui-tui/src/__tests__/terminalModes.test.ts +22 -0
  967. package/agent/ui-tui/src/__tests__/text.test.ts +23 -0
  968. package/agent/ui-tui/src/__tests__/textInputFastEcho.test.ts +37 -0
  969. package/agent/ui-tui/src/__tests__/turnControllerNotice.test.ts +43 -0
  970. package/agent/ui-tui/src/__tests__/useInputHandlers.test.ts +38 -1
  971. package/agent/ui-tui/src/__tests__/virtualHeights.test.ts +8 -0
  972. package/agent/ui-tui/src/app/createGatewayEventHandler.ts +102 -7
  973. package/agent/ui-tui/src/app/interfaces.ts +64 -1
  974. package/agent/ui-tui/src/app/overlayStore.ts +18 -2
  975. package/agent/ui-tui/src/app/slash/commands/billing.ts +332 -0
  976. package/agent/ui-tui/src/app/slash/commands/core.ts +31 -2
  977. package/agent/ui-tui/src/app/slash/commands/credits.ts +57 -0
  978. package/agent/ui-tui/src/app/slash/commands/ops.ts +28 -0
  979. package/agent/ui-tui/src/app/slash/commands/session.ts +32 -4
  980. package/agent/ui-tui/src/app/slash/registry.ts +4 -0
  981. package/agent/ui-tui/src/app/turnController.ts +145 -2
  982. package/agent/ui-tui/src/app/uiStore.ts +2 -0
  983. package/agent/ui-tui/src/app/useInputHandlers.ts +42 -4
  984. package/agent/ui-tui/src/app/useMainApp.ts +54 -8
  985. package/agent/ui-tui/src/app/useSessionLifecycle.ts +40 -31
  986. package/agent/ui-tui/src/app/useSubmission.ts +23 -31
  987. package/agent/ui-tui/src/components/appChrome.tsx +112 -5
  988. package/agent/ui-tui/src/components/appLayout.tsx +9 -0
  989. package/agent/ui-tui/src/components/appOverlays.tsx +25 -1
  990. package/agent/ui-tui/src/components/billingOverlay.tsx +684 -0
  991. package/agent/ui-tui/src/components/branding.tsx +15 -3
  992. package/agent/ui-tui/src/components/messageLine.tsx +25 -3
  993. package/agent/ui-tui/src/components/pluginsHub.tsx +238 -0
  994. package/agent/ui-tui/src/components/prompts.tsx +31 -17
  995. package/agent/ui-tui/src/components/streamingAssistant.tsx +63 -55
  996. package/agent/ui-tui/src/components/textInput.tsx +16 -0
  997. package/agent/ui-tui/src/config/env.ts +12 -0
  998. package/agent/ui-tui/src/config/limits.ts +13 -0
  999. package/agent/ui-tui/src/domain/blockLayout.ts +146 -0
  1000. package/agent/ui-tui/src/domain/paths.ts +24 -0
  1001. package/agent/ui-tui/src/domain/slash.ts +40 -0
  1002. package/agent/ui-tui/src/entry.tsx +35 -4
  1003. package/agent/ui-tui/src/gatewayClient.ts +22 -10
  1004. package/agent/ui-tui/src/gatewayTypes.ts +130 -1
  1005. package/agent/ui-tui/src/lib/gracefulExit.ts +24 -4
  1006. package/agent/ui-tui/src/lib/memory.test.ts +162 -0
  1007. package/agent/ui-tui/src/lib/memory.ts +60 -1
  1008. package/agent/ui-tui/src/lib/memoryMonitor.ts +79 -4
  1009. package/agent/ui-tui/src/lib/osc52.ts +1 -1
  1010. package/agent/ui-tui/src/lib/text.test.ts +32 -1
  1011. package/agent/ui-tui/src/lib/text.ts +29 -2
  1012. package/agent/ui-tui/src/lib/virtualHeights.ts +13 -0
  1013. package/agent/ui-tui/src/types.ts +5 -0
  1014. package/agent/ui-tui/tsconfig.build.json +0 -1
  1015. package/agent/ui-tui/tsconfig.json +2 -1
  1016. package/agent/utils.py +66 -2
  1017. package/agent/uv.lock +300 -684
  1018. package/agent/web/index.html +2 -2
  1019. package/agent/web/package.json +11 -6
  1020. package/agent/web/public/claw-bg.webp +0 -0
  1021. package/agent/web/public/claw-logo.webp +0 -0
  1022. package/agent/web/src/App.tsx +138 -48
  1023. package/agent/web/src/components/AutomationBlueprints.tsx +225 -0
  1024. package/agent/web/src/components/Backdrop.tsx +15 -0
  1025. package/agent/web/src/components/ChatSessionList.tsx +260 -0
  1026. package/agent/web/src/components/ChatSidebar.tsx +262 -78
  1027. package/agent/web/src/components/ConfirmDialog.tsx +122 -0
  1028. package/agent/web/src/components/ModelPickerDialog.tsx +111 -16
  1029. package/agent/web/src/components/ModelReloadConfirm.tsx +40 -0
  1030. package/agent/web/src/components/ProfileScopeBanner.tsx +30 -0
  1031. package/agent/web/src/components/ProfileSwitcher.tsx +67 -0
  1032. package/agent/web/src/components/ReasoningPicker.tsx +167 -0
  1033. package/agent/web/src/components/SkillEditorDialog.tsx +215 -0
  1034. package/agent/web/src/components/ThemeSwitcher.tsx +119 -4
  1035. package/agent/web/src/components/ToolsetConfigDrawer.tsx +457 -0
  1036. package/agent/web/src/contexts/PageHeaderProvider.tsx +7 -4
  1037. package/agent/web/src/contexts/ProfileProvider.tsx +137 -0
  1038. package/agent/web/src/contexts/SystemActions.tsx +6 -8
  1039. package/agent/web/src/contexts/profile-context.ts +19 -0
  1040. package/agent/web/src/contexts/useProfileScope.ts +6 -0
  1041. package/agent/web/src/i18n/af.ts +5 -4
  1042. package/agent/web/src/i18n/de.ts +5 -4
  1043. package/agent/web/src/i18n/en.ts +58 -4
  1044. package/agent/web/src/i18n/es.ts +5 -3
  1045. package/agent/web/src/i18n/fr.ts +5 -3
  1046. package/agent/web/src/i18n/ga.ts +5 -4
  1047. package/agent/web/src/i18n/hu.ts +5 -4
  1048. package/agent/web/src/i18n/it.ts +5 -4
  1049. package/agent/web/src/i18n/ja.ts +5 -4
  1050. package/agent/web/src/i18n/ko.ts +5 -4
  1051. package/agent/web/src/i18n/pt.ts +5 -3
  1052. package/agent/web/src/i18n/ru.ts +5 -4
  1053. package/agent/web/src/i18n/tr.ts +5 -4
  1054. package/agent/web/src/i18n/types.ts +59 -1
  1055. package/agent/web/src/i18n/uk.ts +5 -3
  1056. package/agent/web/src/i18n/zh-hant.ts +5 -4
  1057. package/agent/web/src/i18n/zh.ts +5 -4
  1058. package/agent/web/src/index.css +2 -2
  1059. package/agent/web/src/lib/api.ts +819 -52
  1060. package/agent/web/src/lib/dashboard-flags.ts +16 -7
  1061. package/agent/web/src/lib/reasoning-effort.test.ts +48 -0
  1062. package/agent/web/src/lib/reasoning-effort.ts +36 -0
  1063. package/agent/web/src/lib/session-refresh.test.ts +21 -0
  1064. package/agent/web/src/lib/session-refresh.ts +26 -0
  1065. package/agent/web/src/pages/ChannelsPage.tsx +529 -68
  1066. package/agent/web/src/pages/ChatPage.tsx +249 -56
  1067. package/agent/web/src/pages/ConfigPage.tsx +11 -1
  1068. package/agent/web/src/pages/CronPage.tsx +219 -31
  1069. package/agent/web/src/pages/EnvPage.tsx +25 -6
  1070. package/agent/web/src/pages/FilesPage.tsx +525 -0
  1071. package/agent/web/src/pages/McpPage.tsx +80 -3
  1072. package/agent/web/src/pages/ModelsPage.tsx +97 -12
  1073. package/agent/web/src/pages/PluginsPage.tsx +1 -1
  1074. package/agent/web/src/pages/ProfileBuilderPage.tsx +611 -0
  1075. package/agent/web/src/pages/ProfilesPage.tsx +1038 -172
  1076. package/agent/web/src/pages/SessionsPage.tsx +144 -13
  1077. package/agent/web/src/pages/SkillsPage.tsx +851 -70
  1078. package/agent/web/src/pages/SystemPage.tsx +340 -4
  1079. package/agent/web/src/pages/WalletPage.tsx +401 -0
  1080. package/agent/web/src/pages/WebhooksPage.tsx +145 -15
  1081. package/agent/web/src/pages/X402Page.tsx +207 -0
  1082. package/agent/web/src/plugins/registry.ts +28 -11
  1083. package/agent/web/src/plugins/sdk.d.ts +160 -0
  1084. package/agent/web/src/themes/context.tsx +112 -5
  1085. package/agent/web/src/themes/fonts.ts +167 -0
  1086. package/agent/web/src/themes/index.ts +7 -0
  1087. package/agent/web/tsconfig.app.json +0 -1
  1088. package/agent/web/vite.config.ts +1 -8
  1089. package/agent/web/vitest.config.ts +16 -0
  1090. package/package.json +1 -1
  1091. package/agent/apps/desktop/package-lock.json +0 -18363
  1092. package/agent/apps/desktop/src/app/chat/composer/skin-slash-popover.tsx +0 -56
  1093. package/agent/apps/desktop/src/components/assistant-ui/thread-virtualizer.tsx +0 -382
  1094. package/agent/apps/desktop/src/components/assistant-ui/todo-tool.tsx +0 -109
  1095. package/agent/apps/desktop/src/components/chat/generated-image-context.tsx +0 -19
  1096. package/agent/optional-skills/productivity/shop-app/SKILL.md +0 -340
  1097. package/agent/skills/autonomous-ai-agents/kanban-codex-lane/SKILL.md +0 -277
  1098. package/agent/skills/autonomous-ai-agents/kanban-codex-lane/templates/pmb-codex-lane-prompt.md +0 -57
  1099. package/agent/skills/diagramming/DESCRIPTION.md +0 -3
  1100. package/agent/skills/domain/DESCRIPTION.md +0 -24
  1101. package/agent/skills/gifs/DESCRIPTION.md +0 -3
  1102. package/agent/skills/inference-sh/DESCRIPTION.md +0 -19
  1103. package/agent/skills/mcp/DESCRIPTION.md +0 -3
  1104. package/agent/skills/media/spotify/SKILL.md +0 -135
  1105. package/agent/skills/mlops/training/DESCRIPTION.md +0 -3
  1106. package/agent/skills/mlops/vector-databases/DESCRIPTION.md +0 -3
  1107. package/agent/skills/productivity/linear/SKILL.md +0 -380
  1108. package/agent/skills/productivity/linear/scripts/linear_api.py +0 -445
  1109. package/agent/skills/software-development/debugging-hermes-tui-commands/SKILL.md +0 -152
  1110. package/agent/skills/software-development/writing-plans/SKILL.md +0 -297
  1111. package/agent/ui-tui/package-lock.json +0 -7449
  1112. package/agent/ui-tui/packages/hermes-ink/package-lock.json +0 -1289
  1113. package/agent/web/package-lock.json +0 -8887
  1114. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/PORT_NOTES.md +0 -0
  1115. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/SKILL.md +0 -0
  1116. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/prompts/system.md +0 -0
  1117. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/palettes/macaron.md +0 -0
  1118. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/palettes/mono-ink.md +0 -0
  1119. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/palettes/neon.md +0 -0
  1120. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/palettes/warm.md +0 -0
  1121. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/prompt-construction.md +0 -0
  1122. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/style-presets.md +0 -0
  1123. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/blueprint.md +0 -0
  1124. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/chalkboard.md +0 -0
  1125. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/editorial.md +0 -0
  1126. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/elegant.md +0 -0
  1127. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/fantasy-animation.md +0 -0
  1128. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/flat-doodle.md +0 -0
  1129. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/flat.md +0 -0
  1130. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/ink-notes.md +0 -0
  1131. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/intuition-machine.md +0 -0
  1132. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/minimal.md +0 -0
  1133. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/nature.md +0 -0
  1134. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/notion.md +0 -0
  1135. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/pixel-art.md +0 -0
  1136. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/playful.md +0 -0
  1137. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/retro.md +0 -0
  1138. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/scientific.md +0 -0
  1139. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/screen-print.md +0 -0
  1140. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/sketch-notes.md +0 -0
  1141. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/sketch.md +0 -0
  1142. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/vector-illustration.md +0 -0
  1143. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/vintage.md +0 -0
  1144. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/warm.md +0 -0
  1145. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/watercolor.md +0 -0
  1146. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles.md +0 -0
  1147. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/usage.md +0 -0
  1148. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/workflow.md +0 -0
  1149. /package/agent/{skills → optional-skills}/creative/baoyu-comic/PORT_NOTES.md +0 -0
  1150. /package/agent/{skills → optional-skills}/creative/baoyu-comic/SKILL.md +0 -0
  1151. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/analysis-framework.md +0 -0
  1152. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/chalk.md +0 -0
  1153. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/ink-brush.md +0 -0
  1154. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/ligne-claire.md +0 -0
  1155. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/manga.md +0 -0
  1156. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/minimalist.md +0 -0
  1157. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/realistic.md +0 -0
  1158. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/auto-selection.md +0 -0
  1159. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/base-prompt.md +0 -0
  1160. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/character-template.md +0 -0
  1161. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/cinematic.md +0 -0
  1162. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/dense.md +0 -0
  1163. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/four-panel.md +0 -0
  1164. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/mixed.md +0 -0
  1165. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/splash.md +0 -0
  1166. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/standard.md +0 -0
  1167. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/webtoon.md +0 -0
  1168. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/ohmsha-guide.md +0 -0
  1169. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/partial-workflows.md +0 -0
  1170. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/concept-story.md +0 -0
  1171. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/four-panel.md +0 -0
  1172. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/ohmsha.md +0 -0
  1173. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/shoujo.md +0 -0
  1174. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/wuxia.md +0 -0
  1175. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/storyboard-template.md +0 -0
  1176. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/action.md +0 -0
  1177. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/dramatic.md +0 -0
  1178. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/energetic.md +0 -0
  1179. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/neutral.md +0 -0
  1180. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/romantic.md +0 -0
  1181. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/vintage.md +0 -0
  1182. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/warm.md +0 -0
  1183. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/workflow.md +0 -0
  1184. /package/agent/{skills → optional-skills}/creative/creative-ideation/SKILL.md +0 -0
  1185. /package/agent/{skills → optional-skills}/creative/creative-ideation/references/full-prompt-library.md +0 -0
  1186. /package/agent/{skills → optional-skills}/creative/pixel-art/ATTRIBUTION.md +0 -0
  1187. /package/agent/{skills → optional-skills}/creative/pixel-art/SKILL.md +0 -0
  1188. /package/agent/{skills → optional-skills}/creative/pixel-art/references/palettes.md +0 -0
  1189. /package/agent/{skills → optional-skills}/creative/pixel-art/scripts/__init__.py +0 -0
  1190. /package/agent/{skills → optional-skills}/creative/pixel-art/scripts/palettes.py +0 -0
  1191. /package/agent/{skills → optional-skills}/creative/pixel-art/scripts/pixel_art.py +0 -0
  1192. /package/agent/{skills → optional-skills}/creative/pixel-art/scripts/pixel_art_video.py +0 -0
  1193. /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/SKILL.md +0 -0
  1194. /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/references/analysis-modules.md +0 -0
  1195. /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/references/methods-guide.md +0 -0
  1196. /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/templates/abliteration-config.yaml +0 -0
  1197. /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/templates/analysis-study.yaml +0 -0
  1198. /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/templates/batch-abliteration.yaml +0 -0
  1199. /package/agent/{skills → optional-skills}/mlops/research/DESCRIPTION.md +0 -0
  1200. /package/agent/{skills → optional-skills}/mlops/research/dspy/SKILL.md +0 -0
  1201. /package/agent/{skills → optional-skills}/mlops/research/dspy/references/examples.md +0 -0
  1202. /package/agent/{skills → optional-skills}/mlops/research/dspy/references/modules.md +0 -0
  1203. /package/agent/{skills → optional-skills}/mlops/research/dspy/references/optimizers.md +0 -0
  1204. /package/agent/{skills/red-teaming → optional-skills/security}/godmode/references/jailbreak-templates.md +0 -0
  1205. /package/agent/{skills/red-teaming → optional-skills/security}/godmode/references/refusal-detection.md +0 -0
  1206. /package/agent/{skills/red-teaming → optional-skills/security}/godmode/scripts/godmode_race.py +0 -0
  1207. /package/agent/{skills/red-teaming → optional-skills/security}/godmode/scripts/load_godmode.py +0 -0
  1208. /package/agent/{skills/red-teaming → optional-skills/security}/godmode/scripts/parseltongue.py +0 -0
  1209. /package/agent/{skills/red-teaming → optional-skills/security}/godmode/templates/prefill-subtle.json +0 -0
  1210. /package/agent/{skills/red-teaming → optional-skills/security}/godmode/templates/prefill.json +0 -0
  1211. /package/agent/{skills → optional-skills}/software-development/subagent-driven-development/references/context-budget-discipline.md +0 -0
  1212. /package/agent/{skills → optional-skills}/software-development/subagent-driven-development/references/gates-taxonomy.md +0 -0
@@ -71,6 +71,7 @@ DEFAULT_NOUS_PORTAL_URL = "https://portal.nousresearch.com"
71
71
  DEFAULT_NOUS_INFERENCE_URL = "https://inference-api.nousresearch.com/v1"
72
72
  DEFAULT_NOUS_CLIENT_ID = "hermes-cli"
73
73
  NOUS_INFERENCE_INVOKE_SCOPE = "inference:invoke"
74
+ NOUS_BILLING_MANAGE_SCOPE = "billing:manage"
74
75
  DEFAULT_NOUS_SCOPE = NOUS_INFERENCE_INVOKE_SCOPE
75
76
  NOUS_DEVICE_CODE_SOURCE = "device_code"
76
77
  NOUS_AUTH_PATH_INVOKE_JWT = "invoke_jwt"
@@ -103,7 +104,12 @@ XAI_OAUTH_SCOPE = "openid profile email offline_access grok-cli:access api:acces
103
104
  XAI_OAUTH_REDIRECT_HOST = "127.0.0.1"
104
105
  XAI_OAUTH_REDIRECT_PORT = 56121
105
106
  XAI_OAUTH_REDIRECT_PATH = "/callback"
106
- XAI_ACCESS_TOKEN_REFRESH_SKEW_SECONDS = 120
107
+ # xAI/Grok OAuth access tokens are intentionally short-lived (about 6h in
108
+ # current SuperGrok flows). A two-minute refresh window is too narrow for
109
+ # gateway/cron workloads that may only touch the provider every 30 minutes,
110
+ # leaving brief but noisy credential-expiry gaps. Refresh up to one hour
111
+ # early so ordinary runtime calls keep the token warm without user reauth.
112
+ XAI_ACCESS_TOKEN_REFRESH_SKEW_SECONDS = 3600
107
113
  QWEN_OAUTH_CLIENT_ID = "f0304373b74a44d2b584a3fb70ca9e56"
108
114
  QWEN_OAUTH_TOKEN_URL = "https://chat.qwen.ai/api/v1/oauth2/token"
109
115
  QWEN_ACCESS_TOKEN_REFRESH_SKEW_SECONDS = 120
@@ -643,8 +649,8 @@ ZAI_ENDPOINTS = [
643
649
  # (id, base_url, probe_models, label)
644
650
  ("global", "https://api.z.ai/api/paas/v4", ["glm-5"], "Global"),
645
651
  ("cn", "https://open.bigmodel.cn/api/paas/v4", ["glm-5"], "China"),
646
- ("coding-global", "https://api.z.ai/api/coding/paas/v4", ["glm-5.1", "glm-5v-turbo", "glm-4.7"], "Global (Coding Plan)"),
647
- ("coding-cn", "https://open.bigmodel.cn/api/coding/paas/v4", ["glm-5.1", "glm-5v-turbo", "glm-4.7"], "China (Coding Plan)"),
652
+ ("coding-global", "https://api.z.ai/api/coding/paas/v4", ["glm-5.2", "glm-5.1", "glm-5v-turbo", "glm-4.7"], "Global (Coding Plan)"),
653
+ ("coding-cn", "https://open.bigmodel.cn/api/coding/paas/v4", ["glm-5.2", "glm-5.1", "glm-5v-turbo", "glm-4.7"], "China (Coding Plan)"),
648
654
  ]
649
655
 
650
656
 
@@ -1106,8 +1112,13 @@ def _load_auth_store(auth_file: Optional[Path] = None) -> Dict[str, Any]:
1106
1112
  return {"version": AUTH_STORE_VERSION, "providers": {}}
1107
1113
 
1108
1114
 
1109
- def _save_auth_store(auth_store: Dict[str, Any]) -> Path:
1110
- auth_file = _auth_file_path()
1115
+ def _save_auth_store(auth_store: Dict[str, Any], target_path: Optional[Path] = None) -> Path:
1116
+ # target_path=None preserves the existing contract (write the active
1117
+ # store at _auth_file_path()). An explicit path lets callers persist a
1118
+ # specific store — e.g. the global-root write-through for rotating xAI
1119
+ # OAuth grants (#43589) — reusing this function's atomic O_EXCL + 0o600
1120
+ # write so the root auth.json gets the same TOCTOU-safe treatment.
1121
+ auth_file = target_path if target_path is not None else _auth_file_path()
1111
1122
  auth_file.parent.mkdir(parents=True, exist_ok=True)
1112
1123
  # Tighten parent dir to 0o700 so siblings can't traverse to creds.
1113
1124
  # No-op on Windows (POSIX mode bits not enforced); ignore failures.
@@ -1209,6 +1220,24 @@ def _store_provider_state(
1209
1220
  auth_store["active_provider"] = provider_id
1210
1221
 
1211
1222
 
1223
+ def mark_provider_active_if_unset(provider_id: str) -> None:
1224
+ """Set ``active_provider`` to *provider_id* only when none is set yet.
1225
+
1226
+ Used by ``hermes auth add`` OAuth paths that create credential-pool
1227
+ entries directly (no singleton ``providers.<id>`` block). Adding the
1228
+ very first credential for a provider should make it the active provider
1229
+ so the setup wizard's ``_model_section_has_credentials()`` check (which
1230
+ consults ``get_active_provider()``) does not report "No inference
1231
+ provider configured". Subsequent adds for an already-active setup leave
1232
+ the user's chosen active provider untouched.
1233
+ """
1234
+ with _auth_store_lock():
1235
+ auth_store = _load_auth_store()
1236
+ if not (auth_store.get("active_provider") or "").strip():
1237
+ auth_store["active_provider"] = provider_id
1238
+ _save_auth_store(auth_store)
1239
+
1240
+
1212
1241
  def is_known_auth_provider(provider_id: str) -> bool:
1213
1242
  normalized = (provider_id or "").strip().lower()
1214
1243
  return normalized in PROVIDER_REGISTRY or normalized in SERVICE_PROVIDER_NAMES
@@ -1588,6 +1617,21 @@ def resolve_provider(
1588
1617
  if has_usable_secret(os.getenv("OPENAI_API_KEY")) or has_usable_secret(os.getenv("OPENROUTER_API_KEY")):
1589
1618
  return "openrouter"
1590
1619
 
1620
+ # Auto-detect an OpenRouter credential added via `hermes auth add openrouter`
1621
+ # (manual pool entry, no env var). Without this, a key that only lives in
1622
+ # the credential pool is invisible to auto-detection — the user sees
1623
+ # `hermes auth list` showing the credential while requests go out with no
1624
+ # Authorization header ("HTTP 401: Missing Authentication header"). The
1625
+ # env-var check above only covers keys exported as OPENROUTER_API_KEY /
1626
+ # OPENAI_API_KEY. See issue #42130.
1627
+ try:
1628
+ from agent.credential_pool import load_pool as _load_pool
1629
+
1630
+ if _load_pool("openrouter").has_credentials():
1631
+ return "openrouter"
1632
+ except Exception as e:
1633
+ logger.debug("Could not check OpenRouter credential pool: %s", e)
1634
+
1591
1635
  # Auto-detect API-key providers by checking their env vars
1592
1636
  for pid, pconfig in PROVIDER_REGISTRY.items():
1593
1637
  if pconfig.auth_type != "api_key":
@@ -2064,6 +2108,25 @@ def _refresh_qwen_cli_tokens(tokens: Dict[str, Any], timeout_seconds: float = 20
2064
2108
  return refreshed
2065
2109
 
2066
2110
 
2111
+ def _mark_qwen_oauth_active(creds: Dict[str, Any]) -> None:
2112
+ """Set active_provider to qwen-oauth in auth.json.
2113
+
2114
+ Qwen OAuth tokens live in the Qwen CLI credential file managed by
2115
+ _save_qwen_cli_tokens / resolve_qwen_runtime_credentials. This function
2116
+ only writes a minimal provider-state entry (base_url for display) and
2117
+ sets active_provider so that get_active_provider() and
2118
+ _model_section_has_credentials() detect the provider for the setup wizard
2119
+ and status commands.
2120
+ """
2121
+ with _auth_store_lock():
2122
+ auth_store = _load_auth_store()
2123
+ state: Dict[str, Any] = {}
2124
+ if creds.get("base_url"):
2125
+ state["base_url"] = str(creds["base_url"])
2126
+ _save_provider_state(auth_store, "qwen-oauth", state)
2127
+ _save_auth_store(auth_store)
2128
+
2129
+
2067
2130
  def resolve_qwen_runtime_credentials(
2068
2131
  *,
2069
2132
  force_refresh: bool = False,
@@ -2127,6 +2190,24 @@ def get_qwen_auth_status() -> Dict[str, Any]:
2127
2190
  # Actual HTTP traffic goes to https://cloudcode-pa.googleapis.com/v1internal:*.
2128
2191
  # =============================================================================
2129
2192
 
2193
+ def _mark_google_gemini_cli_active(creds: Dict[str, Any]) -> None:
2194
+ """Set active_provider to google-gemini-cli in auth.json.
2195
+
2196
+ The actual OAuth tokens live in the Google credential file managed by
2197
+ agent.google_oauth. This function only writes a minimal provider-state
2198
+ entry (email for display) and sets active_provider so that
2199
+ get_active_provider() and _model_section_has_credentials() detect the
2200
+ provider for the setup wizard and status commands.
2201
+ """
2202
+ with _auth_store_lock():
2203
+ auth_store = _load_auth_store()
2204
+ state: Dict[str, Any] = {}
2205
+ if creds.get("email"):
2206
+ state["email"] = str(creds["email"])
2207
+ _save_provider_state(auth_store, "google-gemini-cli", state)
2208
+ _save_auth_store(auth_store)
2209
+
2210
+
2130
2211
  def resolve_gemini_oauth_runtime_credentials(
2131
2212
  *,
2132
2213
  force_refresh: bool = False,
@@ -2622,12 +2703,23 @@ def _xai_wait_for_callback(
2622
2703
  result: dict[str, Any],
2623
2704
  *,
2624
2705
  timeout_seconds: float = 180.0,
2706
+ manual_paste_redirect_uri: Optional[str] = None,
2625
2707
  ) -> dict[str, Any]:
2626
2708
  deadline = time.monotonic() + max(5.0, timeout_seconds)
2709
+ if manual_paste_redirect_uri and sys.stdin.isatty():
2710
+ print()
2711
+ print("If xAI shows a Grok Build code instead of redirecting,")
2712
+ print("paste that code here and press Enter.")
2627
2713
  try:
2628
2714
  while time.monotonic() < deadline:
2629
2715
  if result["code"] or result["error"]:
2630
2716
  return result
2717
+ if manual_paste_redirect_uri:
2718
+ raw_paste = _read_ready_stdin_line()
2719
+ if raw_paste and raw_paste.strip():
2720
+ pasted = _parse_pasted_callback(raw_paste)
2721
+ pasted["_manual_paste"] = True
2722
+ return pasted
2631
2723
  time.sleep(0.1)
2632
2724
  finally:
2633
2725
  server.shutdown()
@@ -2651,6 +2743,21 @@ def _xai_wait_for_callback(
2651
2743
  )
2652
2744
 
2653
2745
 
2746
+ def _read_ready_stdin_line() -> Optional[str]:
2747
+ """Return one pending stdin line without blocking, if the terminal has one."""
2748
+ try:
2749
+ if not sys.stdin.isatty():
2750
+ return None
2751
+ import select
2752
+
2753
+ ready, _, _ = select.select([sys.stdin], [], [], 0)
2754
+ if not ready:
2755
+ return None
2756
+ return sys.stdin.readline()
2757
+ except Exception:
2758
+ return None
2759
+
2760
+
2654
2761
  def _spotify_token_payload_to_state(
2655
2762
  token_payload: Dict[str, Any],
2656
2763
  *,
@@ -3330,6 +3437,7 @@ def _sync_codex_pool_entries(
3330
3437
  auth_store: Dict[str, Any],
3331
3438
  tokens: Dict[str, str],
3332
3439
  last_refresh: Optional[str],
3440
+ previous_singleton_tokens: Optional[Dict[str, str]] = None,
3333
3441
  ) -> None:
3334
3442
  """Mirror a fresh Codex re-auth into the credential_pool OAuth entries.
3335
3443
 
@@ -3345,24 +3453,34 @@ def _sync_codex_pool_entries(
3345
3453
  OAuth flow when the user logged in via ``hermes setup`` / the model
3346
3454
  picker. Always synced with the fresh tokens.
3347
3455
  * ``manual:device_code`` — entries created by ``hermes auth add openai-codex``
3348
- that use the same device-code OAuth mechanism. An interactive re-auth
3349
- proves the user owns the ChatGPT account, so it is safe (and expected)
3350
- to refresh these entries too. Without this, a user who once ran the
3351
- ``hermes auth add`` workaround for #33000 would silently leave that
3352
- manual entry stale on every subsequent re-auth, recreating the issue
3353
- reported in #33538.
3456
+ that use the same device-code OAuth mechanism. ONLY synced if the
3457
+ entry's existing access_token matches the *previous* singleton
3458
+ access_token (i.e. the entry is a legacy singleton-alias from the
3459
+ #33000 workaround era). Manual entries whose tokens never matched the
3460
+ singleton represent INDEPENDENT accounts added via
3461
+ ``hermes auth add openai-codex`` and must not be overwritten by a
3462
+ re-auth that targeted a different account (regression for #39236).
3463
+
3464
+ The original #33538 fix refreshed every ``manual:device_code`` entry
3465
+ unconditionally. That worked when ``manual:device_code`` only meant
3466
+ "legacy alias of the singleton", but the same source string is now
3467
+ also produced by independent-account additions, and the broad sync
3468
+ silently clobbered distinct accounts with the latest-authenticated
3469
+ token pair. The access_token-match check distinguishes the two cases
3470
+ without changing the source-string contract.
3354
3471
 
3355
3472
  What does NOT get refreshed:
3356
3473
 
3357
3474
  * ``manual:api_key`` and any other non-device-code manual sources — those
3358
3475
  are independent credentials (an explicit API key, a different ChatGPT
3359
3476
  account, etc.) and must not be overwritten by a single re-auth.
3477
+ * ``manual:device_code`` entries whose access_token does NOT match the
3478
+ previous singleton — see above; these are independent accounts.
3360
3479
 
3361
- Error markers (``last_status``, ``last_error_*``) are also cleared on
3362
- every device-code-backed entry even those whose tokens we did not
3363
- rewrite so that an interactive re-auth gives every relevant pool entry
3364
- a fresh selection chance instead of leaving them marked unhealthy from a
3365
- pre-re-auth 401.
3480
+ Error markers (``last_status``, ``last_error_*``) are cleared ONLY on
3481
+ entries that actually had their tokens rewritten by this re-auth.
3482
+ Independent entries keep their own error state (their 401/429 markers
3483
+ belong to that account's own auth flow, not this re-auth).
3366
3484
  """
3367
3485
  access_token = tokens.get("access_token")
3368
3486
  if not access_token:
@@ -3374,15 +3492,34 @@ def _sync_codex_pool_entries(
3374
3492
  entries = pool.get("openai-codex")
3375
3493
  if not isinstance(entries, list):
3376
3494
  return
3377
- # Sources whose tokens should be rewritten by a fresh Codex device-code
3378
- # OAuth re-auth. ``manual:api_key`` and unknown sources are intentionally
3379
- # excluded they represent independent credentials.
3380
- REFRESHABLE_SOURCES = {"device_code", "manual:device_code"}
3495
+ # Previous singleton access_token (before this re-auth overwrote it)
3496
+ # used to distinguish legacy singleton-aliases from independent accounts.
3497
+ # When None or empty, no manual entry can be treated as an alias (which
3498
+ # is the right default for first-ever-save or a freshly initialized
3499
+ # auth.json).
3500
+ prev_at = None
3501
+ if isinstance(previous_singleton_tokens, dict):
3502
+ prev_at = previous_singleton_tokens.get("access_token") or None
3381
3503
  for entry in entries:
3382
3504
  if not isinstance(entry, dict):
3383
3505
  continue
3384
3506
  source = entry.get("source")
3385
- if source not in REFRESHABLE_SOURCES:
3507
+ if source == "device_code":
3508
+ # Singleton-seeded mirror — always refresh.
3509
+ refresh_this_entry = True
3510
+ elif source == "manual:device_code":
3511
+ # Refresh only if this entry's existing access_token matches the
3512
+ # previous singleton access_token (i.e. it is a true alias of the
3513
+ # singleton from the #33000 workaround era). An entry with its
3514
+ # own distinct token material is an independent account and must
3515
+ # be left alone (#39236).
3516
+ refresh_this_entry = bool(
3517
+ prev_at and entry.get("access_token") == prev_at
3518
+ )
3519
+ else:
3520
+ # ``manual:api_key`` and any future non-device-code sources.
3521
+ refresh_this_entry = False
3522
+ if not refresh_this_entry:
3386
3523
  continue
3387
3524
  entry["access_token"] = access_token
3388
3525
  if refresh_token:
@@ -3404,16 +3541,43 @@ def _save_codex_tokens(tokens: Dict[str, str], last_refresh: str = None, label:
3404
3541
  with _auth_store_lock():
3405
3542
  auth_store = _load_auth_store()
3406
3543
  state = _load_provider_state(auth_store, "openai-codex") or {}
3544
+ # Capture the previous singleton tokens BEFORE overwriting them. The
3545
+ # pool-sync step uses this to distinguish legacy singleton-aliases
3546
+ # (which should be refreshed) from independent accounts that
3547
+ # ``hermes auth add openai-codex`` created (which must not be
3548
+ # overwritten — see #39236).
3549
+ previous_singleton_tokens = state.get("tokens") if isinstance(state.get("tokens"), dict) else None
3407
3550
  state["tokens"] = tokens
3408
3551
  state["last_refresh"] = last_refresh
3409
3552
  state["auth_mode"] = "chatgpt"
3410
3553
  if label and str(label).strip():
3411
3554
  state["label"] = str(label).strip()
3412
3555
  _save_provider_state(auth_store, "openai-codex", state)
3413
- _sync_codex_pool_entries(auth_store, tokens, last_refresh)
3556
+ _sync_codex_pool_entries(
3557
+ auth_store,
3558
+ tokens,
3559
+ last_refresh,
3560
+ previous_singleton_tokens=previous_singleton_tokens,
3561
+ )
3414
3562
  _save_auth_store(auth_store)
3415
3563
 
3416
3564
 
3565
+ def _recover_codex_tokens_from_cli(reason: str) -> Optional[Dict[str, str]]:
3566
+ """Adopt a valid Codex CLI token pair into Hermes auth, if available."""
3567
+ imported = _import_codex_cli_tokens()
3568
+ # Require BOTH tokens before adopting: persisting a payload without a
3569
+ # usable refresh_token would only break the next refresh cycle.
3570
+ if not (
3571
+ imported
3572
+ and str(imported.get("access_token", "") or "").strip()
3573
+ and str(imported.get("refresh_token", "") or "").strip()
3574
+ ):
3575
+ return None
3576
+ logger.info("Codex auth recovered from Codex CLI auth.json (%s).", reason)
3577
+ _save_codex_tokens(imported)
3578
+ return dict(imported)
3579
+
3580
+
3417
3581
  def refresh_codex_oauth_pure(
3418
3582
  access_token: str,
3419
3583
  refresh_token: str,
@@ -3550,11 +3714,34 @@ def _refresh_codex_auth_tokens(
3550
3714
 
3551
3715
  Saves the new tokens to Hermes auth store automatically.
3552
3716
  """
3553
- refreshed = refresh_codex_oauth_pure(
3554
- str(tokens.get("access_token", "") or ""),
3555
- str(tokens.get("refresh_token", "") or ""),
3556
- timeout_seconds=timeout_seconds,
3557
- )
3717
+ try:
3718
+ refreshed = refresh_codex_oauth_pure(
3719
+ str(tokens.get("access_token", "") or ""),
3720
+ str(tokens.get("refresh_token", "") or ""),
3721
+ timeout_seconds=timeout_seconds,
3722
+ )
3723
+ except AuthError as exc:
3724
+ # Self-heal cross-store refresh_token rotation. Hermes keeps its OWN
3725
+ # Codex OAuth token (per profile + top-level), separate from the Codex
3726
+ # CLI's ~/.codex/auth.json. OAuth refresh_tokens are single-use, so when
3727
+ # the Codex CLI (or another Hermes process) rotates the shared token,
3728
+ # this frozen copy's refresh_token goes stale and the refresh fails with
3729
+ # a relogin-required error (invalid_grant / refresh_token_reused / 401).
3730
+ # Before surfacing that as a hard 401 to the turn, adopt the canonical
3731
+ # fresh token from ~/.codex/auth.json (the Codex CLI keeps it current) so
3732
+ # idle profiles / desktop sessions recover automatically instead of
3733
+ # 401'ing until a manual re-auth. Transient failures (e.g. 429 quota)
3734
+ # keep relogin_required=False — the stored token is still valid there, so
3735
+ # we never self-heal those and re-raise unchanged.
3736
+ if not getattr(exc, "relogin_required", False):
3737
+ raise
3738
+ imported = _recover_codex_tokens_from_cli(
3739
+ f"refresh_token rejected: {getattr(exc, 'code', None) or 'auth_error'}"
3740
+ )
3741
+ if not imported:
3742
+ raise
3743
+ return imported
3744
+
3558
3745
  updated_tokens = dict(tokens)
3559
3746
  updated_tokens["access_token"] = refreshed["access_token"]
3560
3747
  updated_tokens["refresh_token"] = refreshed["refresh_token"]
@@ -3614,9 +3801,25 @@ def resolve_codex_runtime_credentials(
3614
3801
  HTTP 401 ``Missing Authentication header`` from the wire instead of a usable
3615
3802
  credential. See issue #32992.
3616
3803
  """
3804
+ read_error: Optional[AuthError] = None
3617
3805
  try:
3618
3806
  data = _read_codex_tokens()
3619
- except AuthError:
3807
+ except AuthError as exc:
3808
+ read_error = exc
3809
+ if getattr(exc, "relogin_required", False) and getattr(exc, "code", None) in {
3810
+ "codex_auth_missing_access_token",
3811
+ "codex_auth_missing_refresh_token",
3812
+ "codex_auth_invalid_shape",
3813
+ }:
3814
+ imported = _recover_codex_tokens_from_cli(str(getattr(exc, "code", None) or "auth_error"))
3815
+ if imported:
3816
+ data = {"tokens": imported, "last_refresh": imported.get("last_refresh")}
3817
+ else:
3818
+ data = None
3819
+ else:
3820
+ data = None
3821
+
3822
+ if data is None:
3620
3823
  pool_token = _pool_codex_access_token()
3621
3824
  if pool_token:
3622
3825
  base_url = (
@@ -3631,7 +3834,34 @@ def resolve_codex_runtime_credentials(
3631
3834
  "last_refresh": None,
3632
3835
  "auth_mode": "chatgpt",
3633
3836
  }
3634
- raise
3837
+ pool_rate_limit = _codex_pool_rate_limit_status()
3838
+ if pool_rate_limit:
3839
+ reset_at = pool_rate_limit.get("reset_at")
3840
+ if isinstance(reset_at, (int, float)) and reset_at > time.time():
3841
+ remaining = int(reset_at - time.time())
3842
+ message = (
3843
+ f"Codex provider quota exhausted (429); retry after {remaining}s. "
3844
+ "Credentials are still valid."
3845
+ )
3846
+ else:
3847
+ message = (
3848
+ "Codex provider quota exhausted (429). Credentials are still valid; "
3849
+ "retry after the usage limit resets."
3850
+ )
3851
+ raise AuthError(
3852
+ message,
3853
+ provider="openai-codex",
3854
+ code=CODEX_RATE_LIMITED_CODE,
3855
+ relogin_required=False,
3856
+ )
3857
+ if read_error is not None:
3858
+ raise read_error
3859
+ raise AuthError(
3860
+ "No Codex credentials stored. Run `hermes auth` to authenticate.",
3861
+ provider="openai-codex",
3862
+ code="codex_auth_missing",
3863
+ relogin_required=True,
3864
+ )
3635
3865
 
3636
3866
  tokens = dict(data["tokens"])
3637
3867
  access_token = str(tokens.get("access_token", "") or "").strip()
@@ -3670,6 +3900,79 @@ def resolve_codex_runtime_credentials(
3670
3900
  }
3671
3901
 
3672
3902
 
3903
+ def _codex_pool_rate_limit_status() -> Optional[Dict[str, Any]]:
3904
+ """Return metadata for a pool-only Codex credential in quota cooldown."""
3905
+ def _parse_reset_at(value: Any) -> Optional[float]:
3906
+ if value is None or value == "":
3907
+ return None
3908
+ if isinstance(value, (int, float)):
3909
+ numeric = float(value)
3910
+ if numeric <= 0:
3911
+ return None
3912
+ return numeric / 1000.0 if numeric > 1_000_000_000_000 else numeric
3913
+ if isinstance(value, str):
3914
+ raw = value.strip()
3915
+ if not raw:
3916
+ return None
3917
+ try:
3918
+ numeric = float(raw)
3919
+ except ValueError:
3920
+ numeric = None
3921
+ if numeric is not None:
3922
+ return numeric / 1000.0 if numeric > 1_000_000_000_000 else numeric
3923
+ try:
3924
+ return datetime.fromisoformat(raw.replace("Z", "+00:00")).timestamp()
3925
+ except ValueError:
3926
+ return None
3927
+ return None
3928
+
3929
+ try:
3930
+ with _auth_store_lock():
3931
+ auth_store = _load_auth_store()
3932
+ pool = auth_store.get("credential_pool")
3933
+ if not isinstance(pool, dict):
3934
+ return None
3935
+ entries = pool.get("openai-codex")
3936
+ if not isinstance(entries, list):
3937
+ return None
3938
+ now = time.time()
3939
+ for entry in entries:
3940
+ if not isinstance(entry, dict):
3941
+ continue
3942
+ token = entry.get("access_token")
3943
+ if not isinstance(token, str) or not token.strip():
3944
+ continue
3945
+ if entry.get("last_status") != "exhausted":
3946
+ continue
3947
+ code = entry.get("last_error_code")
3948
+ reason = str(entry.get("last_error_reason") or "").lower()
3949
+ message = str(entry.get("last_error_message") or "").lower()
3950
+ is_rate_limited = (
3951
+ code == 429
3952
+ or "rate_limit" in reason
3953
+ or "usage_limit" in reason
3954
+ or "quota" in reason
3955
+ or "rate limit" in message
3956
+ or "usage limit" in message
3957
+ or "quota" in message
3958
+ )
3959
+ if not is_rate_limited:
3960
+ continue
3961
+ reset_at = _parse_reset_at(entry.get("last_error_reset_at"))
3962
+ if reset_at is not None and reset_at <= now:
3963
+ continue
3964
+ return {
3965
+ "label": entry.get("label"),
3966
+ "last_refresh": entry.get("last_refresh"),
3967
+ "reset_at": reset_at,
3968
+ "reason": entry.get("last_error_reason"),
3969
+ "message": entry.get("last_error_message"),
3970
+ }
3971
+ except Exception:
3972
+ logger.debug("Codex pool rate-limit lookup failed", exc_info=True)
3973
+ return None
3974
+
3975
+
3673
3976
  def _pool_codex_access_token() -> str:
3674
3977
  """Return the most-recent usable access_token from the openai-codex pool.
3675
3978
 
@@ -3714,13 +4017,64 @@ def _pool_codex_access_token() -> str:
3714
4017
  # xAI Grok OAuth — tokens stored in ~/.hermes/auth.json
3715
4018
  # =============================================================================
3716
4019
 
4020
+ def _xai_oauth_state_from_store(auth_store: Dict[str, Any]) -> Optional[Dict[str, Any]]:
4021
+ """Return usable xAI OAuth state from provider state or credential pool."""
4022
+ state = _load_provider_state(auth_store, "xai-oauth")
4023
+ tokens = state.get("tokens") if isinstance(state, dict) else None
4024
+ if isinstance(tokens, dict):
4025
+ access_token = str(tokens.get("access_token", "") or "").strip()
4026
+ refresh_token = str(tokens.get("refresh_token", "") or "").strip()
4027
+ if access_token and refresh_token:
4028
+ return state
4029
+
4030
+ credential_pool = auth_store.get("credential_pool")
4031
+ entries = (
4032
+ credential_pool.get("xai-oauth")
4033
+ if isinstance(credential_pool, dict)
4034
+ else None
4035
+ )
4036
+ if isinstance(entries, list):
4037
+ for entry in entries:
4038
+ if not isinstance(entry, dict):
4039
+ continue
4040
+ access_token = str(entry.get("access_token", "") or "").strip()
4041
+ refresh_token = str(entry.get("refresh_token", "") or "").strip()
4042
+ if not access_token or not refresh_token:
4043
+ continue
4044
+ merged = dict(state or {})
4045
+ merged["tokens"] = {
4046
+ "access_token": access_token,
4047
+ "refresh_token": refresh_token,
4048
+ "token_type": str(entry.get("token_type") or "Bearer"),
4049
+ }
4050
+ if entry.get("last_refresh"):
4051
+ merged["last_refresh"] = entry.get("last_refresh")
4052
+ merged.setdefault("auth_mode", "oauth_pkce")
4053
+ return merged
4054
+
4055
+ return state if isinstance(state, dict) else None
4056
+
4057
+
4058
+ def _xai_oauth_state_has_usable_tokens(state: Optional[Dict[str, Any]]) -> bool:
4059
+ tokens = state.get("tokens") if isinstance(state, dict) else None
4060
+ return (
4061
+ isinstance(tokens, dict)
4062
+ and bool(str(tokens.get("access_token", "") or "").strip())
4063
+ and bool(str(tokens.get("refresh_token", "") or "").strip())
4064
+ )
4065
+
4066
+
3717
4067
  def _read_xai_oauth_tokens(*, _lock: bool = True) -> Dict[str, Any]:
3718
4068
  if _lock:
3719
4069
  with _auth_store_lock():
3720
4070
  auth_store = _load_auth_store()
3721
4071
  else:
3722
4072
  auth_store = _load_auth_store()
3723
- state = _load_provider_state(auth_store, "xai-oauth")
4073
+ state = _xai_oauth_state_from_store(auth_store)
4074
+ if not _xai_oauth_state_has_usable_tokens(state):
4075
+ global_state = _xai_oauth_state_from_store(_load_global_auth_store())
4076
+ if _xai_oauth_state_has_usable_tokens(global_state):
4077
+ state = global_state
3724
4078
  if not state:
3725
4079
  raise AuthError(
3726
4080
  "No xAI OAuth credentials stored. Select xAI Grok OAuth (SuperGrok / Premium+) in `hermes model`.",
@@ -3760,6 +4114,62 @@ def _read_xai_oauth_tokens(*, _lock: bool = True) -> Dict[str, Any]:
3760
4114
  }
3761
4115
 
3762
4116
 
4117
+ def _profile_has_own_xai_oauth_state(auth_store: Dict[str, Any]) -> bool:
4118
+ """True when this store has its OWN ``providers.xai-oauth`` block.
4119
+
4120
+ Distinguishes a profile that genuinely shadows the root xAI grant from
4121
+ one that only *reads* root via ``_load_provider_state``'s fallback. Only
4122
+ the latter needs the refresh write-through below.
4123
+ """
4124
+ providers = auth_store.get("providers")
4125
+ return isinstance(providers, dict) and isinstance(providers.get("xai-oauth"), dict)
4126
+
4127
+
4128
+ def _write_through_xai_oauth_to_global_root(state: Dict[str, Any]) -> None:
4129
+ """Persist a rotated xAI OAuth ``state`` into the global-root auth.json.
4130
+
4131
+ Best-effort write-through for the multi-profile rotation hazard (#43589):
4132
+ xAI rotates the refresh_token on every refresh, so when a profile session
4133
+ refreshes a grant it resolved from the root fallback, the rotated chain
4134
+ must land back in root. Otherwise root keeps a now-revoked refresh token
4135
+ and every other profile reading the stale root grant dies with
4136
+ ``invalid_grant`` once its access token expires.
4137
+
4138
+ Only updates ``providers.xai-oauth`` in the root store; never touches the
4139
+ profile store (the caller already saved that). Swallows all errors — a
4140
+ failed write-through degrades to the pre-existing behavior (root stale),
4141
+ it must never break the profile's own successful save.
4142
+ """
4143
+ global_path = _global_auth_file_path()
4144
+ if global_path is None:
4145
+ # Classic mode (profile == root); the profile save already hit root.
4146
+ return
4147
+ # Seat belt: under pytest, refuse to write the real user's
4148
+ # ~/.hermes/auth.json even when HERMES_HOME points at a profile path
4149
+ # (mirrors the read-side guard in _load_global_auth_store). Uses the
4150
+ # unmodified HOME env, not Path.home() which fixtures may monkeypatch.
4151
+ if os.environ.get("PYTEST_CURRENT_TEST"):
4152
+ real_home_env = os.environ.get("HOME", "")
4153
+ if real_home_env:
4154
+ real_root = Path(real_home_env) / ".hermes" / "auth.json"
4155
+ try:
4156
+ if global_path.resolve(strict=False) == real_root.resolve(strict=False):
4157
+ return
4158
+ except Exception:
4159
+ return
4160
+ try:
4161
+ if global_path.exists():
4162
+ global_store = _load_auth_store(global_path)
4163
+ else:
4164
+ global_store = {}
4165
+ if not isinstance(global_store, dict):
4166
+ return
4167
+ _store_provider_state(global_store, "xai-oauth", dict(state), set_active=False)
4168
+ _save_auth_store(global_store, global_path)
4169
+ except Exception as exc: # pragma: no cover - best effort
4170
+ logger.debug("xAI OAuth: write-through to global root failed: %s", exc)
4171
+
4172
+
3763
4173
  def _save_xai_oauth_tokens(
3764
4174
  tokens: Dict[str, Any],
3765
4175
  *,
@@ -3771,6 +4181,11 @@ def _save_xai_oauth_tokens(
3771
4181
  last_refresh = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
3772
4182
  with _auth_store_lock():
3773
4183
  auth_store = _load_auth_store()
4184
+ # A profile that lacks its own xai-oauth block is reading the root
4185
+ # grant through _load_provider_state's fallback. When such a profile
4186
+ # refreshes the (rotating) grant, we must write the rotated chain back
4187
+ # to root too, or root is left holding a revoked refresh token (#43589).
4188
+ write_through_to_root = not _profile_has_own_xai_oauth_state(auth_store)
3774
4189
  state = _load_provider_state(auth_store, "xai-oauth") or {}
3775
4190
  state["tokens"] = tokens
3776
4191
  state["last_refresh"] = last_refresh
@@ -3781,6 +4196,8 @@ def _save_xai_oauth_tokens(
3781
4196
  state["redirect_uri"] = redirect_uri
3782
4197
  _save_provider_state(auth_store, "xai-oauth", state)
3783
4198
  _save_auth_store(auth_store)
4199
+ if write_through_to_root:
4200
+ _write_through_xai_oauth_to_global_root(state)
3784
4201
 
3785
4202
 
3786
4203
  def _xai_access_token_is_expiring(access_token: str, skew_seconds: int = 0) -> bool:
@@ -5467,18 +5884,24 @@ def _snapshot_nous_pool_status() -> Dict[str, Any]:
5467
5884
  # subscription-feature checks) call it many times per render — `hermes tools` → "All Platforms"
5468
5885
  # was firing the refresh ~31× during one menu paint, racking up >13s of HTTP and burning
5469
5886
  # single-use refresh tokens. Cache the snapshot for a few seconds, keyed on the auth.json
5470
- # mtime so that `hermes auth login/logout/add/remove` invalidate naturally on the next call.
5887
+ # path + mtime so that profile switches do not share a process memo and
5888
+ # `hermes auth login/logout/add/remove` invalidate naturally on the next call.
5471
5889
  _NOUS_AUTH_STATUS_CACHE_TTL = 15.0 # seconds
5472
- _nous_auth_status_cache: Optional[Tuple[float, Optional[float], Dict[str, Any]]] = None
5890
+ _nous_auth_status_cache: Optional[Tuple[float, str, Optional[float], Dict[str, Any]]] = None
5473
5891
 
5474
5892
 
5475
- def _auth_file_mtime() -> Optional[float]:
5893
+ def _auth_file_cache_key() -> Tuple[str, Optional[float]]:
5894
+ auth_file = _auth_file_path()
5476
5895
  try:
5477
- return _auth_file_path().stat().st_mtime
5896
+ auth_file_key = str(auth_file.resolve(strict=False))
5897
+ except Exception:
5898
+ auth_file_key = str(auth_file)
5899
+ try:
5900
+ return auth_file_key, auth_file.stat().st_mtime
5478
5901
  except FileNotFoundError:
5479
- return None
5902
+ return auth_file_key, None
5480
5903
  except Exception:
5481
- return None
5904
+ return auth_file_key, None
5482
5905
 
5483
5906
 
5484
5907
  def invalidate_nous_auth_status_cache() -> None:
@@ -5510,18 +5933,19 @@ def get_nous_auth_status() -> Dict[str, Any]:
5510
5933
  """
5511
5934
  global _nous_auth_status_cache
5512
5935
  now = time.monotonic()
5513
- mtime = _auth_file_mtime()
5936
+ auth_file_key, mtime = _auth_file_cache_key()
5514
5937
  cached = _nous_auth_status_cache
5515
5938
  if cached is not None:
5516
- cached_at, cached_mtime, cached_status = cached
5939
+ cached_at, cached_auth_file_key, cached_mtime, cached_status = cached
5517
5940
  if (
5518
- cached_mtime == mtime
5941
+ cached_auth_file_key == auth_file_key
5942
+ and cached_mtime == mtime
5519
5943
  and (now - cached_at) < _NOUS_AUTH_STATUS_CACHE_TTL
5520
5944
  ):
5521
5945
  return dict(cached_status)
5522
5946
 
5523
5947
  status = _compute_nous_auth_status()
5524
- _nous_auth_status_cache = (now, mtime, dict(status))
5948
+ _nous_auth_status_cache = (now, auth_file_key, mtime, dict(status))
5525
5949
  return status
5526
5950
 
5527
5951
 
@@ -5604,6 +6028,22 @@ def get_codex_auth_status() -> Dict[str, Any]:
5604
6028
  "source": f"pool:{getattr(entry, 'label', 'unknown')}",
5605
6029
  "api_key": api_key,
5606
6030
  }
6031
+ rate_limit = _codex_pool_rate_limit_status()
6032
+ if rate_limit:
6033
+ return {
6034
+ "logged_in": True,
6035
+ "auth_store": str(_auth_file_path()),
6036
+ "last_refresh": rate_limit.get("last_refresh"),
6037
+ "auth_mode": "chatgpt",
6038
+ "source": f"pool:{rate_limit.get('label') or 'unknown'}",
6039
+ "rate_limited": True,
6040
+ "error_code": CODEX_RATE_LIMITED_CODE,
6041
+ "error": (
6042
+ rate_limit.get("message")
6043
+ or "Codex provider quota exhausted; retry after the usage limit resets."
6044
+ ),
6045
+ "reset_at": rate_limit.get("reset_at"),
6046
+ }
5607
6047
  except Exception:
5608
6048
  pass
5609
6049
 
@@ -6069,6 +6509,40 @@ def _reset_config_provider() -> Path:
6069
6509
  return config_path
6070
6510
 
6071
6511
 
6512
+ def _confirm_expensive_model_selection(
6513
+ model_id: str,
6514
+ *,
6515
+ provider: str = "",
6516
+ base_url: str = "",
6517
+ api_key: str = "",
6518
+ ) -> bool:
6519
+ """Prompt before saving a model whose known pricing exceeds guardrails."""
6520
+ try:
6521
+ from hermes_cli.model_cost_guard import expensive_model_warning
6522
+
6523
+ warning = expensive_model_warning(
6524
+ model_id,
6525
+ provider=provider,
6526
+ base_url=base_url,
6527
+ api_key=api_key,
6528
+ )
6529
+ except Exception:
6530
+ warning = None
6531
+ if warning is None:
6532
+ return True
6533
+
6534
+ print()
6535
+ print("=" * 72)
6536
+ print(warning.message)
6537
+ print("=" * 72)
6538
+ try:
6539
+ response = input("Switch anyway? [y/N]: ").strip().lower()
6540
+ except (KeyboardInterrupt, EOFError):
6541
+ print()
6542
+ return False
6543
+ return response in {"y", "yes"}
6544
+
6545
+
6072
6546
  def _prompt_model_selection(
6073
6547
  model_ids: List[str],
6074
6548
  current_model: str = "",
@@ -6076,6 +6550,9 @@ def _prompt_model_selection(
6076
6550
  unavailable_models: Optional[List[str]] = None,
6077
6551
  portal_url: str = "",
6078
6552
  unavailable_message: str = "",
6553
+ confirm_provider: str = "",
6554
+ confirm_base_url: str = "",
6555
+ confirm_api_key: str = "",
6079
6556
  ) -> Optional[str]:
6080
6557
  """Interactive model selection. Puts current_model first with a marker. Returns chosen model ID or None.
6081
6558
 
@@ -6089,6 +6566,18 @@ def _prompt_model_selection(
6089
6566
 
6090
6567
  _unavailable = unavailable_models or []
6091
6568
 
6569
+ def _confirmed_selection(mid: str) -> Optional[str]:
6570
+ if not mid:
6571
+ return None
6572
+ if confirm_provider and not _confirm_expensive_model_selection(
6573
+ mid,
6574
+ provider=confirm_provider,
6575
+ base_url=confirm_base_url,
6576
+ api_key=confirm_api_key,
6577
+ ):
6578
+ return None
6579
+ return mid
6580
+
6092
6581
  # Reorder: current model first, then the rest (deduplicated)
6093
6582
  ordered = []
6094
6583
  if current_model and current_model in model_ids:
@@ -6204,13 +6693,13 @@ def _prompt_model_selection(
6204
6693
  return None
6205
6694
  print()
6206
6695
  if idx < len(ordered):
6207
- return ordered[idx]
6696
+ return _confirmed_selection(ordered[idx])
6208
6697
  elif idx == len(ordered):
6209
6698
  try:
6210
6699
  custom = input("Enter model name: ").strip()
6211
6700
  except (EOFError, KeyboardInterrupt):
6212
6701
  return None
6213
- return custom if custom else None
6702
+ return _confirmed_selection(custom) if custom else None
6214
6703
  return None
6215
6704
  except (ImportError, NotImplementedError, OSError, subprocess.SubprocessError):
6216
6705
  pass
@@ -6242,10 +6731,10 @@ def _prompt_model_selection(
6242
6731
  return None
6243
6732
  idx = int(choice)
6244
6733
  if 1 <= idx <= n:
6245
- return ordered[idx - 1]
6734
+ return _confirmed_selection(ordered[idx - 1])
6246
6735
  elif idx == n + 1:
6247
6736
  custom = input("Enter model name: ").strip()
6248
- return custom if custom else None
6737
+ return _confirmed_selection(custom) if custom else None
6249
6738
  elif idx == n + 2:
6250
6739
  return None
6251
6740
  print(f"Please enter 1-{n + 2}")
@@ -6589,6 +7078,7 @@ def _xai_oauth_loopback_login(
6589
7078
  authorization_endpoint = discovery["authorization_endpoint"]
6590
7079
  token_endpoint = discovery["token_endpoint"]
6591
7080
 
7081
+ allow_missing_state = False
6592
7082
  if manual_paste:
6593
7083
  # No HTTP listener — synthesize a redirect_uri matching what
6594
7084
  # the server would have bound to so the authorize URL the user
@@ -6615,6 +7105,7 @@ def _xai_oauth_loopback_login(
6615
7105
  print("Open this URL to authorize Hermes with xAI:")
6616
7106
  print(authorize_url)
6617
7107
  callback = _prompt_manual_callback_paste(redirect_uri)
7108
+ allow_missing_state = True
6618
7109
  else:
6619
7110
  server, thread, callback_result, redirect_uri = _xai_start_callback_server()
6620
7111
  try:
@@ -6654,6 +7145,7 @@ def _xai_oauth_loopback_login(
6654
7145
  thread,
6655
7146
  callback_result,
6656
7147
  timeout_seconds=max(30.0, timeout_seconds * 9),
7148
+ manual_paste_redirect_uri=redirect_uri,
6657
7149
  )
6658
7150
  except AuthError as exc:
6659
7151
  if (
@@ -6670,6 +7162,7 @@ def _xai_oauth_loopback_login(
6670
7162
  callback = _prompt_manual_callback_paste(redirect_uri)
6671
7163
  if callback.get("code") is None and callback.get("error") is None:
6672
7164
  raise exc
7165
+ allow_missing_state = True
6673
7166
  except Exception:
6674
7167
  try:
6675
7168
  server.shutdown()
@@ -6690,7 +7183,7 @@ def _xai_oauth_loopback_login(
6690
7183
  code="xai_authorization_failed",
6691
7184
  )
6692
7185
  callback_state = callback.get("state")
6693
- # Manual-paste bare-code path: when a user pastes only the opaque
7186
+ # Manual bare-code paths: when a user pastes only the opaque
6694
7187
  # authorization code (no ``code=``/``state=`` query parameters),
6695
7188
  # ``_parse_pasted_callback`` returns ``state=None``. xAI's consent
6696
7189
  # page renders the code in-page rather than redirecting through the
@@ -6698,10 +7191,12 @@ def _xai_oauth_loopback_login(
6698
7191
  # VPS, container consoles) the bare code is the only thing the user
6699
7192
  # can obtain. PKCE (code_verifier) still binds the exchange to this
6700
7193
  # client, so the local state-equality check is redundant on the
6701
- # bare-code path — we substitute the locally generated state to keep
7194
+ # bare-code paths — we substitute the locally generated state to keep
6702
7195
  # the rest of the validation chain (and the token exchange) unchanged.
6703
7196
  # See #26923 (AccursedGalaxy comment, 2026-05-20).
6704
- if callback_state is None and manual_paste:
7197
+ if callback.get("_manual_paste"):
7198
+ allow_missing_state = True
7199
+ if callback_state is None and (manual_paste or allow_missing_state):
6705
7200
  callback_state = state
6706
7201
  if callback_state != state:
6707
7202
  raise AuthError(
@@ -6768,23 +7263,61 @@ def _codex_device_code_login() -> Dict[str, Any]:
6768
7263
  issuer = "https://auth.openai.com"
6769
7264
  client_id = CODEX_OAUTH_CLIENT_ID
6770
7265
 
6771
- # Step 1: Request device code
6772
- try:
6773
- with httpx.Client(timeout=httpx.Timeout(15.0)) as client:
6774
- resp = client.post(
6775
- f"{issuer}/api/accounts/deviceauth/usercode",
6776
- json={"client_id": client_id},
6777
- headers={"Content-Type": "application/json"},
7266
+ # Step 1: Request device code. OpenAI's auth endpoint rate-limits this
7267
+ # request (HTTP 429) when login is attempted too often from the same
7268
+ # IP/account — retry with capped backoff (honoring ``Retry-After``)
7269
+ # before surfacing a clear, actionable message instead of a bare status.
7270
+ resp = None
7271
+ max_attempts = 4
7272
+ for attempt in range(1, max_attempts + 1):
7273
+ try:
7274
+ with httpx.Client(timeout=httpx.Timeout(15.0)) as client:
7275
+ resp = client.post(
7276
+ f"{issuer}/api/accounts/deviceauth/usercode",
7277
+ json={"client_id": client_id},
7278
+ headers={"Content-Type": "application/json"},
7279
+ )
7280
+ except Exception as exc:
7281
+ raise AuthError(
7282
+ f"Failed to request device code: {exc}",
7283
+ provider="openai-codex", code="device_code_request_failed",
6778
7284
  )
6779
- except Exception as exc:
7285
+
7286
+ if resp.status_code != 429:
7287
+ break
7288
+
7289
+ if attempt < max_attempts:
7290
+ retry_after = _parse_retry_after_seconds(
7291
+ getattr(resp, "headers", None)
7292
+ )
7293
+ # Exponential backoff (2s, 4s, 8s) capped, preferring the
7294
+ # server-provided Retry-After when present.
7295
+ delay = retry_after if retry_after is not None else 2 ** attempt
7296
+ delay = max(1, min(int(delay), 60))
7297
+ print(
7298
+ "OpenAI is rate-limiting login requests "
7299
+ f"(429); retrying in {delay}s..."
7300
+ )
7301
+ _time.sleep(delay)
7302
+
7303
+ if resp is not None and resp.status_code == 429:
7304
+ retry_after = _parse_retry_after_seconds(getattr(resp, "headers", None))
7305
+ wait_hint = (
7306
+ f" Try again in about {retry_after}s."
7307
+ if retry_after is not None
7308
+ else " Wait a minute and run the login again."
7309
+ )
6780
7310
  raise AuthError(
6781
- f"Failed to request device code: {exc}",
6782
- provider="openai-codex", code="device_code_request_failed",
7311
+ "OpenAI is rate-limiting Codex login requests (HTTP 429). "
7312
+ "This is a temporary throttle on OpenAI's side, not a credential "
7313
+ f"problem.{wait_hint}",
7314
+ provider="openai-codex", code=CODEX_RATE_LIMITED_CODE,
6783
7315
  )
6784
7316
 
6785
- if resp.status_code != 200:
7317
+ if resp is None or resp.status_code != 200:
7318
+ status = resp.status_code if resp is not None else "unknown"
6786
7319
  raise AuthError(
6787
- f"Device code request returned status {resp.status_code}.",
7320
+ f"Device code request returned status {status}.",
6788
7321
  provider="openai-codex", code="device_code_request_error",
6789
7322
  )
6790
7323
 
@@ -6872,6 +7405,22 @@ def _codex_device_code_login() -> Dict[str, Any]:
6872
7405
  provider="openai-codex", code="token_exchange_failed",
6873
7406
  )
6874
7407
 
7408
+ if token_resp.status_code == 429:
7409
+ retry_after = _parse_retry_after_seconds(
7410
+ getattr(token_resp, "headers", None)
7411
+ )
7412
+ wait_hint = (
7413
+ f" Try again in about {retry_after}s."
7414
+ if retry_after is not None
7415
+ else " Wait a minute and run the login again."
7416
+ )
7417
+ raise AuthError(
7418
+ "OpenAI is rate-limiting Codex login requests (HTTP 429) during "
7419
+ "token exchange. This is a temporary throttle on OpenAI's side, "
7420
+ f"not a credential problem.{wait_hint}",
7421
+ provider="openai-codex", code=CODEX_RATE_LIMITED_CODE,
7422
+ )
7423
+
6875
7424
  if token_resp.status_code != 200:
6876
7425
  raise AuthError(
6877
7426
  f"Token exchange returned status {token_resp.status_code}.",
@@ -7348,6 +7897,7 @@ def _nous_device_code_login(
7348
7897
  timeout_seconds: float = 15.0,
7349
7898
  insecure: bool = False,
7350
7899
  ca_bundle: Optional[str] = None,
7900
+ on_verification: Optional[Callable[[str, str], None]] = None,
7351
7901
  ) -> Dict[str, Any]:
7352
7902
  """Run the Nous device-code flow and return full OAuth state without persisting."""
7353
7903
  pconfig = PROVIDER_REGISTRY["nous"]
@@ -7402,6 +7952,16 @@ def _nous_device_code_login(
7402
7952
  else:
7403
7953
  print(" Could not open browser automatically — use the URL above.")
7404
7954
 
7955
+ # Surface the verification URL/code to an out-of-band consumer (e.g. the
7956
+ # TUI gateway, whose stdout is a JSON-RPC pipe — a plain print() there is
7957
+ # dropped). Fired AFTER the print/browser block and BEFORE polling blocks,
7958
+ # so the consumer can render the link while we wait. Best-effort.
7959
+ if on_verification is not None:
7960
+ try:
7961
+ on_verification(verification_url, user_code)
7962
+ except Exception:
7963
+ pass
7964
+
7405
7965
  effective_interval = max(1, min(interval, DEVICE_AUTH_POLL_INTERVAL_CAP_SECONDS))
7406
7966
  print(f"Waiting for approval (polling every {effective_interval}s)...")
7407
7967
 
@@ -7467,6 +8027,91 @@ def _nous_device_code_login(
7467
8027
  raise
7468
8028
 
7469
8029
 
8030
+ def nous_token_has_billing_scope() -> bool:
8031
+ """Return True if the currently-held Nous token carries ``billing:manage``.
8032
+
8033
+ Reads the persisted ``scope`` string saved at login (``_save_provider_state``
8034
+ stores ``token_data.get("scope") or scope``). A space-delimited match. Used by
8035
+ the lazy step-up: if False, the first billing call will 403 ``insufficient_scope``
8036
+ anyway, but checking up front lets a surface skip a doomed round-trip.
8037
+ """
8038
+ try:
8039
+ state = get_provider_auth_state("nous") or {}
8040
+ except Exception:
8041
+ return False
8042
+ scope = state.get("scope")
8043
+ if not isinstance(scope, str):
8044
+ return False
8045
+ return NOUS_BILLING_MANAGE_SCOPE in scope.split()
8046
+
8047
+
8048
+ def step_up_nous_billing_scope(
8049
+ *,
8050
+ open_browser: bool = True,
8051
+ timeout_seconds: float = 15.0,
8052
+ on_verification: Optional[Callable[[str, str], None]] = None,
8053
+ ) -> bool:
8054
+ """Re-run the device flow requesting ``billing:manage`` and persist the result.
8055
+
8056
+ The lazy step-up (plan D-A): triggered when a billing endpoint returns
8057
+ ``403 insufficient_scope``. Runs a fresh device-connect with
8058
+ ``inference:invoke tool:invoke billing:manage`` on the scope. The user must be
8059
+ an ADMIN/OWNER and tick "Allow terminal billing" in the portal for the minted
8060
+ token to actually carry the scope; otherwise the server silently downscopes and this
8061
+ returns False.
8062
+
8063
+ Reuses the held credential's portal/inference URLs + client_id so the step-up
8064
+ targets the same deployment (incl. a preview via ``HERMES_PORTAL_BASE_URL`` set
8065
+ at the original login). Persists to the auth store + shared store + pool, exactly
8066
+ like ``_login_nous`` — but WITHOUT the model picker (this is a scope upgrade, not
8067
+ a fresh login).
8068
+
8069
+ Returns True iff the new token carries ``billing:manage``.
8070
+ """
8071
+ prior = get_provider_auth_state("nous") or {}
8072
+ pconfig = PROVIDER_REGISTRY["nous"]
8073
+
8074
+ # Build the step-up scope: existing scopes (if any) + billing:manage, deduped,
8075
+ # order-stable. Fall back to the standard inference+tool+billing set.
8076
+ _raw_scope = prior.get("scope")
8077
+ prior_scope = _raw_scope if isinstance(_raw_scope, str) else ""
8078
+ requested: list[str] = []
8079
+ for tok in (prior_scope.split() or [NOUS_INFERENCE_INVOKE_SCOPE, "tool:invoke"]):
8080
+ if tok and tok not in requested:
8081
+ requested.append(tok)
8082
+ if NOUS_BILLING_MANAGE_SCOPE not in requested:
8083
+ requested.append(NOUS_BILLING_MANAGE_SCOPE)
8084
+ scope = " ".join(requested)
8085
+
8086
+ auth_state = _nous_device_code_login(
8087
+ portal_base_url=prior.get("portal_base_url") or None,
8088
+ inference_base_url=prior.get("inference_base_url") or None,
8089
+ client_id=prior.get("client_id") or pconfig.client_id,
8090
+ scope=scope,
8091
+ open_browser=open_browser,
8092
+ timeout_seconds=timeout_seconds,
8093
+ on_verification=on_verification,
8094
+ )
8095
+
8096
+ with _auth_store_lock():
8097
+ auth_store = _load_auth_store()
8098
+ _save_provider_state(auth_store, "nous", auth_state)
8099
+ _save_auth_store(auth_store)
8100
+
8101
+ # Mirror to shared store + reseed the pool (best-effort), same as _login_nous.
8102
+ try:
8103
+ _write_shared_nous_state(auth_state)
8104
+ except Exception:
8105
+ pass
8106
+ try:
8107
+ _sync_nous_pool_from_auth_store()
8108
+ except Exception:
8109
+ pass
8110
+
8111
+ granted = auth_state.get("scope")
8112
+ return isinstance(granted, str) and NOUS_BILLING_MANAGE_SCOPE in granted.split()
8113
+
8114
+
7470
8115
  def _login_nous(args, pconfig: ProviderConfig) -> None:
7471
8116
  """Nous Portal device authorization flow."""
7472
8117
  timeout_seconds = getattr(args, "timeout", None) or 15.0
@@ -7618,6 +8263,9 @@ def _login_nous(args, pconfig: ProviderConfig) -> None:
7618
8263
  unavailable_models=unavailable_models,
7619
8264
  portal_url=_portal,
7620
8265
  unavailable_message=unavailable_message,
8266
+ confirm_provider="nous",
8267
+ confirm_base_url=inference_base_url,
8268
+ confirm_api_key=runtime_key,
7621
8269
  )
7622
8270
  elif unavailable_models:
7623
8271
  _url = (_portal or DEFAULT_NOUS_PORTAL_URL).rstrip("/")