@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
@@ -18,6 +18,8 @@ Configuration in config.yaml (or via env vars):
18
18
  from __future__ import annotations
19
19
 
20
20
  import asyncio
21
+ import base64
22
+ import binascii
21
23
  import collections
22
24
  import dataclasses
23
25
  import hashlib
@@ -31,9 +33,10 @@ import time
31
33
  import urllib.parse
32
34
  import uuid
33
35
  from datetime import datetime, timezone, timedelta
36
+ from enum import Enum
34
37
  from pathlib import Path
35
38
  from abc import ABC, abstractmethod
36
- from typing import Any, Callable, ClassVar, Dict, List, Optional, Tuple
39
+ from typing import Any, Callable, ClassVar, Dict, Iterator, List, Optional, Tuple
37
40
 
38
41
  import sys
39
42
 
@@ -55,6 +58,7 @@ from gateway.platforms.base import (
55
58
  SendResult,
56
59
  cache_document_from_bytes,
57
60
  cache_image_from_bytes,
61
+ cache_video_from_bytes,
58
62
  )
59
63
  from gateway.platforms.helpers import MessageDeduplicator
60
64
  from gateway.platforms.yuanbao_media import (
@@ -77,6 +81,7 @@ from gateway.platforms.yuanbao_proto import (
77
81
  HERMES_INSTANCE_ID,
78
82
  decode_conn_msg,
79
83
  decode_inbound_push,
84
+ decode_forward_msg_data,
80
85
  decode_query_group_info_rsp,
81
86
  decode_get_group_member_list_rsp,
82
87
  encode_auth_bind,
@@ -120,6 +125,16 @@ AUTH_TIMEOUT_SECONDS = 10.0
120
125
  MAX_RECONNECT_ATTEMPTS = 100
121
126
  DEFAULT_SEND_TIMEOUT = 30.0 # WS biz request timeout
122
127
 
128
+ # Upper bound on the WS close handshake during teardown (#40383). The
129
+ # websockets connection's own close_timeout (5s) blocks until the server
130
+ # echoes the close frame; an idle/unresponsive server never replies, stalling
131
+ # gateway shutdown by the full timeout. Bounding the close await here keeps
132
+ # teardown fast — a responsive server completes the handshake in well under a
133
+ # second, so this only caps the pathological hang. Also bounds the reconnect /
134
+ # connect-failure cleanup paths that reuse _cleanup_ws(), where a graceful
135
+ # close is unnecessary anyway (the socket is being discarded to redial).
136
+ WS_CLOSE_TIMEOUT_S = 1.0
137
+
123
138
  # Close codes that indicate permanent errors — do NOT reconnect.
124
139
  NO_RECONNECT_CLOSE_CODES = {4012, 4013, 4014, 4018, 4019, 4021}
125
140
 
@@ -147,8 +162,14 @@ _YB_RES_REF_RE = re.compile(
147
162
  r"\[(image|voice|video|file(?::[^|\]]*)?)\|ybres:([A-Za-z0-9_\-]+)\]"
148
163
  )
149
164
 
165
+ # Patched local-media anchors once an inbound resource has been downloaded to the local cache.
166
+ # [image: /opt/data/image_cache/img_xxx.bmp]
167
+ # [file: report.pdf → /opt/data/.../report.pdf]
168
+ # (and any future kind, e.g. [video: /opt/.../clip.mp4])
169
+ _YB_LOCAL_MEDIA_RE = re.compile(r"\[(\w+):[^\]]*?(/[^\]]+?)\s*\]")
170
+
150
171
  # Media kinds that can be resolved and injected into the model context
151
- _RESOLVABLE_MEDIA_KINDS = frozenset({"image", "file"})
172
+ _RESOLVABLE_MEDIA_KINDS = frozenset({"image", "file", "video"})
152
173
 
153
174
  # Strip page indicators like (1/3) appended by BasePlatformAdapter
154
175
  _INDICATOR_RE = re.compile(r'\s*\(\d+/\d+\)$')
@@ -916,6 +937,10 @@ class InboundContext:
916
937
  raw_text: str = ""
917
938
  media_refs: list = dc_field(default_factory=list)
918
939
 
940
+ # Populated by ExtractContentMiddleware for elem_type 1009 (WeChat forward).
941
+ # Contains the parsed ForwardMsgData dict (sub_type / nick_name / msg list).
942
+ forwarded_records: Optional[dict] = None
943
+
919
944
  # Owner command detection
920
945
  owner_command: Optional[str] = None
921
946
 
@@ -923,14 +948,18 @@ class InboundContext:
923
948
  source: Optional[Any] = None # SessionSource
924
949
 
925
950
  # Populated by ClassifyMessageTypeMiddleware
926
- msg_type: Optional[Any] = None # MessageType
951
+ msg_type: Optional[Any] = None # MessageType | YuanbaoMessageType
927
952
 
928
953
  # Populated by QuoteContextMiddleware
929
954
  reply_to_message_id: Optional[str] = None
930
955
  reply_to_text: Optional[str] = None
931
956
  quote_media_refs: list = dc_field(default_factory=list) # List of (rid, kind, filename)
932
957
 
933
- # Populated by MediaResolveMiddleware
958
+ # Populated by MediaResolveMiddleware. Combined list of resolved local
959
+ # paths from up to three sources (deduped, in this order):
960
+ # 1) media carried by the current message (always),
961
+ # 2) media from the quoted message (when reply_to_message_id is set),
962
+ # 3) recent group-observed media (only when chat_type == "group" and no quote is present).
934
963
  media_urls: list = dc_field(default_factory=list)
935
964
  media_types: list = dc_field(default_factory=list)
936
965
 
@@ -1675,10 +1704,10 @@ class ExtractContentMiddleware(InboundMiddleware):
1675
1704
  """Extract plain text content from MsgBody.
1676
1705
 
1677
1706
  - TIMTextElem -> text field
1678
- - TIMImageElem -> "[image]"
1679
- - TIMFileElem -> "[file: {filename}]"
1680
- - TIMSoundElem -> "[voice]"
1681
- - TIMVideoFileElem -> "[video]"
1707
+ - TIMImageElem -> "[image]" / "[image|ybres:RID]"
1708
+ - TIMFileElem -> "[file: {filename}]" / "[file:{name}|ybres:RID]"
1709
+ - TIMSoundElem -> "[voice]" / "[voice|ybres:RID]"
1710
+ - TIMVideoFileElem -> "[video]" / "[video|ybres:RID]"
1682
1711
  - TIMFaceElem -> "[emoji: {name}]" or "[emoji]"
1683
1712
  - TIMCustomElem -> try to extract data field, otherwise "[custom message]"
1684
1713
  - Multiple elems joined with spaces
@@ -1741,6 +1770,9 @@ class ExtractContentMiddleware(InboundMiddleware):
1741
1770
  parts.append(text)
1742
1771
  else:
1743
1772
  parts.append("[unsupported message type]")
1773
+ elif ctype == 1009:
1774
+ # WeChat forwarded chat record: use the truncated summary text.
1775
+ parts.append(custom.get("text", "[chat record]"))
1744
1776
  else:
1745
1777
  parts.append("[unsupported message type]")
1746
1778
  except (json.JSONDecodeError, TypeError):
@@ -1852,10 +1884,70 @@ class ExtractContentMiddleware(InboundMiddleware):
1852
1884
  pass
1853
1885
  return urls
1854
1886
 
1887
+ @staticmethod
1888
+ def _extract_forwarded_records(msg_body: list, user_id: str = "") -> Optional[dict]:
1889
+ """Extract ForwardMsgData from ext_map for elem_type 1009 (WeChat forward).
1890
+
1891
+ The detailed chat-record payload lives in ``msg_content.ext_map``
1892
+ (protobuf field 999, ``map<string, string>``):
1893
+ - key format: ``wexin_forward_msg_[forward_msg_id]_[userid]``
1894
+ - value: a **base64-encoded protobuf** ``ForwardMsgData`` (NOT JSON).
1895
+ Decode with base64 then ``decode_forward_msg_data`` to recover the
1896
+ ``sub_type`` / ``nick_name`` / ``msg`` structure.
1897
+
1898
+ Matching strategy: take the first ``wexin_forward_msg_`` entry whose
1899
+ decoded payload is a valid ``ForwardMsgData`` (``sub_type == 1``).
1900
+
1901
+ Returns the parsed ``ForwardMsgData`` dict or ``None``.
1902
+ """
1903
+ for elem in msg_body or []:
1904
+ if not isinstance(elem, dict) or elem.get("msg_type") != "TIMCustomElem":
1905
+ continue
1906
+ content = elem.get("msg_content", {}) or {}
1907
+ if not isinstance(content, dict):
1908
+ continue
1909
+ data_str = content.get("data", "")
1910
+ if not data_str:
1911
+ continue
1912
+ try:
1913
+ custom = json.loads(data_str)
1914
+ except (json.JSONDecodeError, TypeError):
1915
+ continue
1916
+ if not (isinstance(custom, dict) and custom.get("elem_type") == 1009):
1917
+ continue
1918
+
1919
+ ext_map = content.get("ext_map") or {}
1920
+ if not isinstance(ext_map, dict) or not ext_map:
1921
+ return None
1922
+
1923
+ def _parse_value(value):
1924
+ # ext_map values are base64-encoded ForwardMsgData protobuf.
1925
+ if not isinstance(value, str) or not value:
1926
+ return None
1927
+ try:
1928
+ pb = base64.b64decode(value)
1929
+ except (binascii.Error, ValueError):
1930
+ return None
1931
+ data = decode_forward_msg_data(pb)
1932
+ if isinstance(data, dict) and data.get("sub_type") == 1:
1933
+ return data
1934
+ return None
1935
+
1936
+ # Take the first valid wexin_forward_msg_ entry.
1937
+ for key, value in ext_map.items():
1938
+ if not key.startswith("wexin_forward_msg_"):
1939
+ continue
1940
+ parsed = _parse_value(value)
1941
+ if parsed is not None:
1942
+ return parsed
1943
+
1944
+ return None
1945
+
1855
1946
  async def handle(self, ctx: InboundContext, next_fn) -> None:
1856
1947
  ctx.raw_text = self._rewrite_slash_command(self._extract_text(ctx.msg_body))
1857
1948
  ctx.media_refs = self._extract_inbound_media_refs(ctx.msg_body)
1858
1949
  ctx.link_urls = self._extract_link_urls(ctx.msg_body)
1950
+ ctx.forwarded_records = self._extract_forwarded_records(ctx.msg_body, ctx.from_account)
1859
1951
  await next_fn()
1860
1952
 
1861
1953
  class PlaceholderFilterMiddleware(InboundMiddleware):
@@ -2065,10 +2157,14 @@ class GroupAtGuardMiddleware(InboundMiddleware):
2065
2157
  "and answer it directly."
2066
2158
  )
2067
2159
 
2068
- @staticmethod
2160
+ @classmethod
2069
2161
  def _observe_group_message(
2162
+ cls,
2070
2163
  adapter, source, sender_display: str, text: str,
2071
- *, msg_id: Optional[str] = None,
2164
+ *,
2165
+ ctx: InboundContext,
2166
+ msg_id: Optional[str] = None,
2167
+ forwarded_records: Optional[dict] = None,
2072
2168
  ) -> None:
2073
2169
  """Write a group message into the session transcript without triggering the agent.
2074
2170
 
@@ -2083,7 +2179,14 @@ class GroupAtGuardMiddleware(InboundMiddleware):
2083
2179
  try:
2084
2180
  session_entry = store.get_or_create_session(source)
2085
2181
  user_id = source.user_id or "unknown"
2086
- attributed = f"[{sender_display}|{user_id}]\n{text}"
2182
+ body_text = text
2183
+ if forwarded_records:
2184
+ summary = ForwardedRecordsParseMiddleware.build_forward_text(
2185
+ forwarded_records, ctx=ctx, is_dispatch=False,
2186
+ )
2187
+ if summary:
2188
+ body_text = f"{text}\n{summary}" if text else summary
2189
+ attributed = f"[{sender_display}|{user_id}]\n{body_text}"
2087
2190
  entry: dict = {
2088
2191
  "role": "user",
2089
2192
  "content": attributed,
@@ -2105,6 +2208,8 @@ class GroupAtGuardMiddleware(InboundMiddleware):
2105
2208
  self._observe_group_message(
2106
2209
  adapter, ctx.source, ctx.sender_nickname or ctx.from_account, ctx.raw_text,
2107
2210
  msg_id=ctx.msg_id or None,
2211
+ forwarded_records=ctx.forwarded_records,
2212
+ ctx=ctx,
2108
2213
  )
2109
2214
  logger.info(
2110
2215
  "[%s] Group message observed (no @bot): chat=%s from=%s",
@@ -2145,14 +2250,26 @@ class GroupAttributionMiddleware(InboundMiddleware):
2145
2250
  await next_fn()
2146
2251
 
2147
2252
 
2253
+ class YuanbaoMessageType(Enum):
2254
+ """Yuanbao-local message subtypes; coerced back to :class:`MessageType`
2255
+ before leaving the adapter (see :class:`DispatchMiddleware`)."""
2256
+
2257
+ # WeChat forwarded chat records (TIMCustomElem, elem_type 1009).
2258
+ CHAT_RECORD = "chat_record"
2259
+
2260
+
2148
2261
  class ClassifyMessageTypeMiddleware(InboundMiddleware):
2149
2262
  """Determine MessageType from text content and msg_body elements."""
2150
2263
 
2151
2264
  name = "classify-msg-type"
2152
2265
 
2153
2266
  @staticmethod
2154
- def _classify(text: str, msg_body: list) -> MessageType:
2155
- """Classify message type based on text and msg_body."""
2267
+ def _classify(text: str, msg_body: list):
2268
+ """Classify message type based on text and msg_body.
2269
+
2270
+ Returns a base :class:`MessageType`, or a yuanbao-local
2271
+ :class:`YuanbaoMessageType` for platform-specific subtypes.
2272
+ """
2156
2273
  if text.startswith("/"):
2157
2274
  return MessageType.COMMAND
2158
2275
  for elem in msg_body:
@@ -2165,6 +2282,14 @@ class ClassifyMessageTypeMiddleware(InboundMiddleware):
2165
2282
  return MessageType.VIDEO
2166
2283
  if etype == "TIMFileElem":
2167
2284
  return MessageType.DOCUMENT
2285
+ if etype == "TIMCustomElem":
2286
+ data_str = (elem.get("msg_content") or {}).get("data", "")
2287
+ try:
2288
+ custom = json.loads(data_str)
2289
+ except (json.JSONDecodeError, TypeError):
2290
+ custom = None
2291
+ if isinstance(custom, dict) and custom.get("elem_type") == 1009:
2292
+ return YuanbaoMessageType.CHAT_RECORD
2168
2293
  return MessageType.TEXT
2169
2294
 
2170
2295
  async def handle(self, ctx: InboundContext, next_fn) -> None:
@@ -2177,53 +2302,248 @@ class QuoteContextMiddleware(InboundMiddleware):
2177
2302
 
2178
2303
  name = "quote-context"
2179
2304
 
2180
- @staticmethod
2181
- def _extract_quote_context(cloud_custom_data: str) -> Tuple[Optional[str], Optional[str], list]:
2182
- """Extract quote context, mapping to MessageEvent.reply_to_*.
2183
-
2184
- Returns:
2185
- (reply_to_message_id, reply_to_text, quote_media_refs)
2186
- where quote_media_refs is a list of (rid, kind, filename) tuples
2305
+ def _extract_quote_context(self, cloud_custom_data: str) -> Tuple[Optional[str], Optional[str]]:
2306
+ """Extract quote text context, mapping to MessageEvent.reply_to_*.
2187
2307
  """
2188
2308
  if not cloud_custom_data:
2189
- return None, None, []
2309
+ return None, None
2190
2310
  try:
2191
2311
  parsed = json.loads(cloud_custom_data)
2192
2312
  except (json.JSONDecodeError, TypeError):
2193
- return None, None, []
2313
+ return None, None
2194
2314
 
2195
2315
  quote = parsed.get("quote") if isinstance(parsed, dict) else None
2196
2316
  if not isinstance(quote, dict):
2197
- return None, None, []
2198
-
2199
- # type=2 corresponds to image reference; desc may be empty, provide a placeholder.
2200
- quote_type = int(quote.get("type") or 0)
2201
- desc = str(quote.get("desc") or "").strip()
2202
- if quote_type == 2 and not desc:
2203
- desc = "[image]"
2204
- if not desc:
2205
- return None, None, []
2317
+ return None, None
2206
2318
 
2207
2319
  quote_id = str(quote.get("id") or "").strip() or None
2320
+ desc = str(quote.get("desc") or "").strip()
2208
2321
  sender = str(quote.get("sender_nickname") or quote.get("sender_id") or "").strip()
2209
- quote_text = f"{sender}: {desc}" if sender else desc
2322
+ quote_text = (f"{sender}: {desc}" if sender else desc) if desc else None
2210
2323
 
2211
- # Extract media references from desc using _YB_RES_REF_RE regex
2212
- media_refs: list = []
2213
- for m in _YB_RES_REF_RE.finditer(desc):
2214
- head = m.group(1) # "image" | "file:<name>" | "voice" | "video"
2215
- rid = m.group(2)
2216
- kind, _, filename = head.partition(":")
2217
- kind = kind.strip()
2218
- media_refs.append((rid, kind, filename.strip()))
2324
+ return quote_id, quote_text
2325
+
2326
+ async def _extract_media_refs_from_transcript(
2327
+ self, ctx: InboundContext
2328
+ ) -> List[Tuple[str, str, str]]:
2329
+ """Look up the quoted message in the transcript history and return any
2330
+ ``[kind|ybres:RID]`` anchors found in its content as
2331
+ ``(rid, kind, filename)`` tuples.
2332
+
2333
+ Returns ``[]`` when ``ctx.reply_to_message_id`` is unset, when the
2334
+ transcript store / source is unavailable, or when the quoted message
2335
+ carries no resolvable media anchors.
2336
+ """
2337
+ if ctx.reply_to_message_id is None:
2338
+ return []
2339
+ adapter = ctx.adapter
2340
+ media_refs: List[Tuple[str, str, str]] = []
2341
+ try:
2342
+ store = getattr(adapter, "_session_store", None)
2343
+ if not store or ctx.source is None:
2344
+ return []
2345
+ session_entry = store.get_or_create_session(ctx.source)
2346
+ history = store.load_transcript(session_entry.session_id)
2347
+ for msg in reversed(history or []):
2348
+ mid = msg.get("message_id", "")
2349
+ if not mid or mid != ctx.reply_to_message_id:
2350
+ continue
2351
+ _content = msg.get("content", "")
2352
+ if isinstance(_content, str) and "|ybres:" in _content:
2353
+ for m in _YB_RES_REF_RE.finditer(_content):
2354
+ head = m.group(1)
2355
+ rid = m.group(2)
2356
+ kind, _, filename = head.partition(":")
2357
+ kind = kind.strip()
2358
+ if kind in _RESOLVABLE_MEDIA_KINDS:
2359
+ media_refs.append((rid, kind, filename.strip()))
2360
+ break
2361
+ except Exception as exc:
2362
+ logger.warning(
2363
+ "[%s] quote transcript lookup failed: %s",
2364
+ getattr(adapter, "name", "yuanbao"), exc,
2365
+ )
2366
+ return media_refs
2367
+
2368
+ async def handle(self, ctx: InboundContext, next_fn) -> None:
2369
+ ctx.reply_to_message_id, ctx.reply_to_text = self._extract_quote_context(ctx.cloud_custom_data)
2370
+ ctx.quote_media_refs = await self._extract_media_refs_from_transcript(ctx)
2371
+ await next_fn()
2372
+
2373
+
2374
+ class ForwardedRecordsParseMiddleware(InboundMiddleware):
2375
+ """Deep-parse WeChat forwarded chat records (elem_type 1009) for dispatch.
2376
+
2377
+ Activates when a full ``ForwardMsgData`` dict is available on the current
2378
+ turn, carried by the current message (``ctx.forwarded_records``).
2379
+ Resolves media to ``[kind|ybres:RID]``
2380
+ placeholders, appends downloadable refs to ``ctx.media_refs`` (for
2381
+ :class:`MediaResolveMiddleware`), and rewrites ``ctx.raw_text``.
2382
+
2383
+ Group @bot turns *without* a forward on the current message rely on the
2384
+ eagerly-rendered summaries that :class:`GroupAtGuardMiddleware` writes to
2385
+ the transcript at observe time — there is no run-time summary fallback
2386
+ here.
2387
+
2388
+ On any failure the middleware leaves ``ctx.raw_text`` untouched
2389
+ (graceful degradation, design §2.8).
2390
+ """
2219
2391
 
2220
- return quote_id, quote_text, media_refs
2392
+ name = "forwarded-records-parse"
2221
2393
 
2222
2394
  async def handle(self, ctx: InboundContext, next_fn) -> None:
2223
- ctx.reply_to_message_id, ctx.reply_to_text, ctx.quote_media_refs = self._extract_quote_context(ctx.cloud_custom_data)
2395
+ try:
2396
+ if ctx.forwarded_records:
2397
+ self._send_loading_heartbeat(ctx)
2398
+ ctx.raw_text = self.build_forward_text(ctx.forwarded_records, ctx=ctx, is_dispatch=True)
2399
+ except Exception as exc:
2400
+ # Degrade gracefully: leave ctx.raw_text as-is.
2401
+ logger.warning(
2402
+ "[%s] forwarded-records deep parse failed: %s",
2403
+ getattr(ctx.adapter, "name", "yuanbao"), exc,
2404
+ )
2224
2405
 
2225
2406
  await next_fn()
2226
2407
 
2408
+ # -- Heartbeat ---------------------------------------------------------
2409
+
2410
+ @staticmethod
2411
+ async def _send_loading_heartbeat(ctx: InboundContext) -> None:
2412
+ """Best-effort RUNNING heartbeat so the user sees a loading bubble."""
2413
+ try:
2414
+ await ctx.adapter._outbound.heartbeat.send_heartbeat_once(
2415
+ ctx.chat_id, WS_HEARTBEAT_RUNNING,
2416
+ )
2417
+ except Exception:
2418
+ pass
2419
+
2420
+ # -- Record rendering helpers -----------------------------------------
2421
+
2422
+ @classmethod
2423
+ def _media_marker(
2424
+ cls, media: dict, plain_text: str = "",
2425
+ ) -> Tuple[str, Optional[Dict[str, str]]]:
2426
+ """Render one ``msgContent.multimedia`` entry as a textual marker.
2427
+
2428
+ Returns ``(marker, ref)``. Downloadable media emits a
2429
+ ``[kind|ybres:RID]`` marker and a ``ctx.media_refs`` ref dict when a
2430
+ usable RID/URL is present; otherwise a plain ``[kind] name`` marker
2431
+ and ``ref=None``.
2432
+ """
2433
+ media_type = (media.get("type", "") or media.get("doc_type", "")).strip().lower()
2434
+ url = str(media.get("url") or "").strip()
2435
+ media_id = str(media.get("media_id") or "").strip()
2436
+ file_name = str(media.get("file_name") or "").strip()
2437
+ # media_id is directly usable as a ybres RID (design §2.10.9);
2438
+ # fall back to parsing the resourceId out of the URL.
2439
+ rid = media_id or ExtractContentMiddleware._parse_resource_id(url)
2440
+
2441
+ if media_type == "image":
2442
+ if url and rid:
2443
+ return f"[image|ybres:{rid}] {file_name}".rstrip(), {"kind": "image", "url": url}
2444
+ return f"[image] {file_name or plain_text}".rstrip(), None
2445
+
2446
+ if media_type in ("file", "document", "code"):
2447
+ if url and rid:
2448
+ ref: Dict[str, str] = {"kind": "file", "url": url}
2449
+ if file_name:
2450
+ ref["name"] = file_name
2451
+ return f"[file|ybres:{rid}] {file_name}".rstrip(), ref
2452
+ return f"[file] {file_name}".rstrip(), None
2453
+
2454
+ if media_type == "url":
2455
+ # Link share (e.g. WeChat article) — keep URL for the agent.
2456
+ link_title = file_name or str(media.get("title") or "")
2457
+ return f"[link] {link_title} {url}".rstrip(), None
2458
+
2459
+ if media_type == "video":
2460
+ if url and rid:
2461
+ return f"[video|ybres:{rid}] {file_name}".rstrip(), {"kind": "video", "url": url}
2462
+ return f"[video] {file_name or url}".rstrip(), None
2463
+
2464
+ return f"[{media_type or 'media'}] {url or file_name}".rstrip(), None
2465
+
2466
+ # Per-record combined-text cap; record count is NOT capped (design §2.10.3).
2467
+ FORWARD_MSG_TEXT_MAX_CHARS = 1000
2468
+
2469
+ @classmethod
2470
+ def _walk_forward_msgs(
2471
+ cls,
2472
+ forward_data: dict,
2473
+ ) -> Iterator[Tuple[str, str, List[Dict[str, str]]]]:
2474
+ """Walk ``ForwardMsgData['msg']`` and yield ``(sender, body, refs)``.
2475
+
2476
+ Per-record dispatch over ``msgContent`` (text / multimedia / nested
2477
+ forward / fallback); ``body`` is capped at
2478
+ :attr:`FORWARD_MSG_TEXT_MAX_CHARS`. Media goes through
2479
+ :meth:`_media_marker`, always building full ``[kind|ybres:RID]``
2480
+ markers; ``refs`` holds that record's downloadable ``ctx.media_refs``
2481
+ entries in textual order — the order PatchAnchorsMiddleware relies on
2482
+ (design §2.10.6). Headers / footers are the caller's job.
2483
+ """
2484
+ for msg in (forward_data.get("msg") if isinstance(forward_data, dict) else None) or []:
2485
+ if not isinstance(msg, dict):
2486
+ continue
2487
+ sender = msg.get("sender", "")
2488
+ plain_text = msg.get("plainText", "")
2489
+ msg_contents = msg.get("msgContent", []) or []
2490
+
2491
+ refs: List[Dict[str, str]] = []
2492
+ if not msg_contents:
2493
+ rendered = plain_text
2494
+ else:
2495
+ parts: List[str] = []
2496
+ for mc in msg_contents:
2497
+ if not isinstance(mc, dict):
2498
+ continue
2499
+ mc_type = mc.get("type", 0) # EnumMsgContentType
2500
+ if mc_type == 1: # TEXT
2501
+ parts.append(mc.get("text", ""))
2502
+ elif mc_type == 2: # MULTIMEDIA
2503
+ for media in mc.get("multimedia", []) or []:
2504
+ if isinstance(media, dict):
2505
+ marker, ref = cls._media_marker(
2506
+ media, plain_text,
2507
+ )
2508
+ parts.append(marker)
2509
+ if ref is not None:
2510
+ refs.append(ref)
2511
+ elif mc_type == 3: # nested FORWARD_MSG (design §2.10.10)
2512
+ parts.append("[嵌套聊天记录]")
2513
+ else:
2514
+ if plain_text:
2515
+ parts.append(plain_text)
2516
+ rendered = " ".join(p for p in parts if p) or plain_text
2517
+
2518
+ if len(rendered) > cls.FORWARD_MSG_TEXT_MAX_CHARS:
2519
+ rendered = rendered[: cls.FORWARD_MSG_TEXT_MAX_CHARS] + "…(已截断)"
2520
+ yield sender, rendered, refs
2521
+
2522
+ # -- Prompt builders ---------------------------------------------------
2523
+
2524
+ @classmethod
2525
+ def build_forward_text(
2526
+ cls, forward_data: dict, *, ctx: InboundContext, is_dispatch: bool,
2527
+ ) -> str:
2528
+ """Render ``ForwardMsgData`` into forward text.
2529
+
2530
+ Body lines are ``发送人:正文`` with full ``[kind|ybres:RID]`` media
2531
+ markers preserved. When ``is_dispatch`` is true, refs are appended to
2532
+ ``ctx.media_refs`` for downstream resolution and a ``用户附言:
2533
+ {ctx.raw_text}`` footer is added; observed callers skip both since
2534
+ no later middleware runs.
2535
+ """
2536
+ nickname = ctx.sender_nickname or "用户"
2537
+ lines = [f"当前用户的昵称为{nickname}", "以下为用户的聊天记录"]
2538
+ for sender, body, refs in cls._walk_forward_msgs(forward_data):
2539
+ lines.append(f"{sender}:{body}")
2540
+ if is_dispatch:
2541
+ ctx.media_refs.extend(refs)
2542
+ text = "\n".join(lines)
2543
+ if is_dispatch and ctx.raw_text.strip():
2544
+ text += f"\n\n用户附言:{ctx.raw_text.strip()}"
2545
+ return text
2546
+
2227
2547
 
2228
2548
  class MediaResolveMiddleware(InboundMiddleware):
2229
2549
  """Resolve inbound media references to downloadable URLs."""
@@ -2232,9 +2552,6 @@ class MediaResolveMiddleware(InboundMiddleware):
2232
2552
 
2233
2553
  # --- Resource download cache (keyed by resourceId) ---
2234
2554
  # Avoids redundant downloads of the same resource within the TTL window.
2235
- # The same resourceId can be referenced multiple times in a session (own
2236
- # attachment, then quoted again, then observed in a group backfill); each
2237
- # reference otherwise triggers a fresh token exchange + download.
2238
2555
  _resource_cache: ClassVar[Dict[str, Tuple[str, str, float]]] = {} # rid -> (local_path, mime, ts)
2239
2556
  _RESOURCE_CACHE_TTL_S: ClassVar[int] = 24 * 60 * 60 # 24 hours
2240
2557
  _RESOURCE_CACHE_MAX_SIZE: ClassVar[int] = 256
@@ -2410,6 +2727,15 @@ class MediaResolveMiddleware(InboundMiddleware):
2410
2727
  cls._put_cached_resource(resource_id, local_path, mime)
2411
2728
  return local_path, mime
2412
2729
 
2730
+ if kind == "video":
2731
+ # Yuanbao video resources carry no reliable extension; default to mp4.
2732
+ local_path = cache_video_from_bytes(file_bytes)
2733
+ mime = guess_mime_type(local_path) or (
2734
+ content_type if content_type.startswith("video/") else "video/mp4"
2735
+ )
2736
+ cls._put_cached_resource(resource_id, local_path, mime)
2737
+ return local_path, mime
2738
+
2413
2739
  # kind == "file"
2414
2740
  if not file_name:
2415
2741
  parsed = urllib.parse.urlparse(fetch_url)
@@ -2426,11 +2752,6 @@ class MediaResolveMiddleware(InboundMiddleware):
2426
2752
  cls._put_cached_resource(resource_id, local_path, mime)
2427
2753
  return local_path, mime
2428
2754
 
2429
- @classmethod
2430
- async def _resolve_by_resource_id(cls, adapter, resource_id: str) -> str:
2431
- """Exchange a Yuanbao ``resourceId`` for a short-lived direct download URL. Raises on failure."""
2432
- return await cls._fetch_resource_url(adapter, resource_id)
2433
-
2434
2755
  @classmethod
2435
2756
  async def _resolve_media_urls(
2436
2757
  cls, adapter, media_refs: List[Dict[str, str]]
@@ -2446,6 +2767,7 @@ class MediaResolveMiddleware(InboundMiddleware):
2446
2767
  for ref in media_refs:
2447
2768
  kind = str(ref.get("kind") or "").strip().lower()
2448
2769
  url = str(ref.get("url") or "").strip()
2770
+ filename = str(ref.get("name") or "").strip()
2449
2771
  if kind not in _RESOLVABLE_MEDIA_KINDS or not url:
2450
2772
  continue
2451
2773
 
@@ -2465,7 +2787,7 @@ class MediaResolveMiddleware(InboundMiddleware):
2465
2787
  adapter,
2466
2788
  fetch_url=fetch_url,
2467
2789
  kind=kind,
2468
- file_name=str(ref.get("name") or "").strip() or None,
2790
+ file_name=filename or None,
2469
2791
  log_tag=f"placeholder_url={url[:80]}",
2470
2792
  resource_id=rid,
2471
2793
  )
@@ -2477,6 +2799,44 @@ class MediaResolveMiddleware(InboundMiddleware):
2477
2799
 
2478
2800
  return media_urls, media_types
2479
2801
 
2802
+ @classmethod
2803
+ async def _resolve_ybres_refs(
2804
+ cls,
2805
+ adapter,
2806
+ refs: List[Tuple[str, str, str]],
2807
+ *,
2808
+ log_prefix: str,
2809
+ ) -> Tuple[List[str], List[str]]:
2810
+ """Resolve a list of ``(rid, kind, filename)`` ybres tuples to local paths.
2811
+ """
2812
+ media_paths: List[str] = []
2813
+ mimes: List[str] = []
2814
+ for rid, kind, filename in refs:
2815
+ if kind not in _RESOLVABLE_MEDIA_KINDS:
2816
+ continue
2817
+ try:
2818
+ fresh_url = await cls._fetch_resource_url(adapter, rid)
2819
+ except Exception as exc:
2820
+ logger.warning(
2821
+ "[%s] %s resolve failed: rid=%s kind=%s err=%s",
2822
+ adapter.name, log_prefix, rid, kind, exc,
2823
+ )
2824
+ continue
2825
+ cached = await cls._download_and_cache(
2826
+ adapter,
2827
+ fetch_url=fresh_url,
2828
+ kind=kind,
2829
+ file_name=filename or None,
2830
+ log_tag=f"{log_prefix} rid={rid}",
2831
+ resource_id=rid,
2832
+ )
2833
+ if cached is None:
2834
+ continue
2835
+ path, mime = cached
2836
+ media_paths.append(path)
2837
+ mimes.append(mime)
2838
+ return media_paths, mimes
2839
+
2480
2840
  @classmethod
2481
2841
  async def _collect_observed_media(
2482
2842
  cls, adapter, source,
@@ -2497,14 +2857,22 @@ class MediaResolveMiddleware(InboundMiddleware):
2497
2857
  if not history:
2498
2858
  return [], []
2499
2859
 
2500
- start = max(0, len(history) - OBSERVED_MEDIA_BACKFILL_LOOKBACK)
2860
+ # Walk the most recent LOOKBACK messages newest→oldest so that when we
2861
+ # hit the per-turn resolve cap we keep the *latest* media references,
2862
+ # not the oldest ones in the window. Within a single message, also
2863
+ # iterate matches in reverse so the last-added image wins on ties.
2864
+ # Final ``order`` is reversed back to chronological (old→new) before
2865
+ # handing off to ``_resolve_ybres_refs`` so downstream prompt insertion
2866
+ # preserves natural reading order.
2867
+ window = history[-OBSERVED_MEDIA_BACKFILL_LOOKBACK:]
2501
2868
  order: List[Tuple[str, str, str]] = [] # (rid, kind, filename)
2502
2869
  seen: set = set()
2503
- for msg in history[start:]:
2870
+ for msg in reversed(window):
2504
2871
  content = msg.get("content")
2505
2872
  if not isinstance(content, str) or "|ybres:" not in content:
2506
2873
  continue
2507
- for m in _YB_RES_REF_RE.finditer(content):
2874
+ matches = list(_YB_RES_REF_RE.finditer(content))
2875
+ for m in reversed(matches):
2508
2876
  head = m.group(1) # "image" | "file:<name>" | "voice" | "video"
2509
2877
  rid = m.group(2)
2510
2878
  kind, _, filename = head.partition(":")
@@ -2520,45 +2888,184 @@ class MediaResolveMiddleware(InboundMiddleware):
2520
2888
  if len(order) >= OBSERVED_MEDIA_BACKFILL_MAX_RESOLVE_PER_TURN:
2521
2889
  break
2522
2890
 
2891
+ # Restore chronological order (oldest→newest) for downstream resolution.
2892
+ order.reverse()
2893
+
2523
2894
  if not order:
2524
2895
  return [], []
2525
2896
 
2526
- media_paths: List[str] = []
2897
+ return await cls._resolve_ybres_refs(
2898
+ adapter, order, log_prefix="observed-media",
2899
+ )
2900
+
2901
+ @classmethod
2902
+ async def _resolve_quote_media(
2903
+ cls, adapter, quote_media_refs: List[Tuple[str, str, str]],
2904
+ ) -> Tuple[List[str], List[str]]:
2905
+ """Resolve media anchors carried by the quoted message.
2906
+
2907
+ ``quote_media_refs`` is a list of ``(rid, kind, filename)`` tuples
2908
+ produced by :class:`QuoteContextMiddleware` from the transcript.
2909
+ """
2910
+ return await cls._resolve_ybres_refs(
2911
+ adapter, quote_media_refs, log_prefix="quote",
2912
+ )
2913
+
2914
+ @staticmethod
2915
+ def _collect_quote_local_media(ctx: InboundContext) -> Tuple[List[str], List[str]]:
2916
+ """Private-chat fallback for recovering already-local quoted media.
2917
+
2918
+ Only already-local media is handled here: by the time a turn is cached,
2919
+ ``PatchAnchorsMiddleware`` has rewritten resolved ``|ybres:`` anchors to
2920
+ ``[image: /path]`` / ``[file: name → /path]``. Unresolved anchors are an
2921
+ original-turn resolution failure and belong to that turn's handling, not
2922
+ this quote fallback — so no re-download happens here.
2923
+
2924
+ Returns ``(local_paths, mimes)`` for media already downloaded to the
2925
+ local cache on its original turn, ready to inject as-is.
2926
+ """
2927
+ paths: List[str] = []
2527
2928
  mimes: List[str] = []
2528
- for rid, kind, filename in order:
2529
- try:
2530
- fresh_url = await cls._resolve_by_resource_id(adapter, rid)
2531
- except Exception as exc:
2532
- logger.warning(
2533
- "[%s] observed-media resolve failed: rid=%s kind=%s err=%s",
2534
- adapter.name, rid, kind, exc,
2535
- )
2929
+ rid_key = ctx.reply_to_message_id
2930
+ if not rid_key:
2931
+ return paths, mimes
2932
+ cache = getattr(ctx.adapter, "_msg_content_cache", None)
2933
+ if not cache:
2934
+ return paths, mimes
2935
+ text = cache.get(rid_key)
2936
+ if not isinstance(text, str) or not text:
2937
+ return paths, mimes
2938
+
2939
+ # Already-local media paths written by PatchAnchorsMiddleware.
2940
+ seen: set = set()
2941
+ for m in _YB_LOCAL_MEDIA_RE.finditer(text):
2942
+ kind = (m.group(1) or "").strip().lower()
2943
+ path = (m.group(2) or "").strip()
2944
+ if not path or path in seen:
2536
2945
  continue
2537
- cached = await cls._download_and_cache(
2538
- adapter,
2539
- fetch_url=fresh_url,
2540
- kind=kind,
2541
- file_name=filename or None,
2542
- log_tag=f"rid={rid}",
2543
- resource_id=rid,
2544
- )
2545
- if cached is None:
2946
+ if not os.path.exists(path):
2546
2947
  continue
2547
- path, mime = cached
2548
- media_paths.append(path)
2948
+ seen.add(path)
2949
+ mime = guess_mime_type(os.path.basename(path)) or (
2950
+ "image/jpeg" if kind == "image" else "application/octet-stream"
2951
+ )
2952
+ paths.append(path)
2549
2953
  mimes.append(mime)
2550
- return media_paths, mimes
2954
+
2955
+ return paths, mimes
2551
2956
 
2552
2957
  async def handle(self, ctx: InboundContext, next_fn) -> None:
2958
+ # NOTE: Reaching this middleware in a group chat implies the message has
2959
+ # @-mentioned the bot (or is an owner command). GroupAtGuardMiddleware
2960
+ # short-circuits non-@bot group messages earlier in the pipeline, so we
2961
+ # don't need to re-check @bot status here before downloading media.
2553
2962
  adapter = ctx.adapter
2554
- ctx.media_urls, ctx.media_types = await self._resolve_media_urls(adapter, ctx.media_refs)
2555
- # Re-check placeholder after media resolution
2556
- if PlaceholderFilterMiddleware.is_skippable_placeholder(ctx.raw_text, len(ctx.media_urls)):
2963
+
2964
+ urls: List[str] = []
2965
+ types: List[str] = []
2966
+ seen: set = set()
2967
+
2968
+ def _add_unique_pairs(pair_lists: Tuple[List[str], List[str]]) -> None:
2969
+ u_list, m_list = pair_lists
2970
+ for u, m in zip(u_list, m_list):
2971
+ if not u or u in seen:
2972
+ continue
2973
+ seen.add(u)
2974
+ urls.append(u)
2975
+ types.append(m)
2976
+
2977
+ # 1) Media carried by the current message itself.
2978
+ own_pairs = await self._resolve_media_urls(adapter, ctx.media_refs)
2979
+ own_count = sum(1 for u in own_pairs[0] if u)
2980
+ _add_unique_pairs(own_pairs)
2981
+
2982
+ # 2) Second source — quoted media takes priority; otherwise fall back
2983
+ # to observed-media backfill in groups only (DMs already had their
2984
+ # media resolved on the turn it was sent).
2985
+ if ctx.reply_to_message_id is not None:
2986
+ if ctx.quote_media_refs:
2987
+ _add_unique_pairs(await self._resolve_quote_media(adapter, ctx.quote_media_refs))
2988
+ else:
2989
+ # DM quote fallback: no transcript message_id match (DM user rows
2990
+ # carry no platform message_id), so recover already-local media
2991
+ # from the adapter msg cache. Patched on its original turn — no
2992
+ # re-download needed, inject as-is.
2993
+ _add_unique_pairs(self._collect_quote_local_media(ctx))
2994
+ elif ctx.chat_type == "group":
2995
+ # Group chats: only @-bot turns reach this middleware
2996
+ # (see GroupAtGuardMiddleware note at top of handle()),
2997
+ # so unconditional observed-media hydration is safe here.
2998
+ try:
2999
+ _add_unique_pairs(await self._collect_observed_media(adapter, ctx.source))
3000
+ except Exception as exc:
3001
+ logger.warning(
3002
+ "[%s] observed-image hydration raised, continuing anyway: %s",
3003
+ adapter.name, exc,
3004
+ )
3005
+
3006
+ ctx.media_urls = urls
3007
+ ctx.media_types = types
3008
+
3009
+ # Re-check placeholder after media resolution.
3010
+ # Use ``own_count`` (not ``len(urls)``) to preserve the original
3011
+ # semantics: a placeholder text accompanied only by quote/observed
3012
+ # media (i.e. no fresh attachment of its own) is still skippable.
3013
+ if PlaceholderFilterMiddleware.is_skippable_placeholder(ctx.raw_text, own_count):
2557
3014
  logger.debug("[%s] Skip placeholder after media download: %r", adapter.name, ctx.raw_text)
2558
3015
  return # Stop pipeline
2559
3016
  await next_fn()
2560
3017
 
2561
3018
 
3019
+ class PatchAnchorsMiddleware(InboundMiddleware):
3020
+ """Replace ``[kind|ybres:RID]`` anchors in ``ctx.raw_text`` with local paths.
3021
+
3022
+ Runs after :class:`MediaResolveMiddleware` so that ``ctx.media_urls`` /
3023
+ ``ctx.media_types`` are already populated with downloaded resources
3024
+ (own media + quote media or group-observed media). The transcript
3025
+ written downstream then records usable local paths for the model
3026
+ instead of opaque ``ybres:`` references.
3027
+
3028
+ Only resolved media (paths starting with ``/``) are substituted; any
3029
+ anchor without a corresponding local resource is left untouched.
3030
+ """
3031
+
3032
+ name = "patch-anchors"
3033
+
3034
+ @staticmethod
3035
+ def _patch(text: str, urls: List[str], types: List[str]) -> str:
3036
+ if not text or not urls:
3037
+ return text
3038
+ patched = text
3039
+ for u, m in zip(urls, types):
3040
+ if not u.startswith("/"):
3041
+ continue
3042
+ anchor_match = _YB_RES_REF_RE.search(patched)
3043
+ if not anchor_match:
3044
+ break
3045
+ head = anchor_match.group(1)
3046
+ kind, _, filename = head.partition(":")
3047
+ kind = kind.strip()
3048
+ if kind == "image" and m.startswith("image/"):
3049
+ replacement = f"[image: {u}]"
3050
+ elif kind == "file":
3051
+ label = filename.strip() or os.path.basename(u)
3052
+ replacement = f"[file: {label} → {u}]"
3053
+ elif kind == "video":
3054
+ replacement = f"[video: {u}]"
3055
+ else:
3056
+ continue
3057
+ patched = (
3058
+ patched[: anchor_match.start()]
3059
+ + replacement
3060
+ + patched[anchor_match.end():]
3061
+ )
3062
+ return patched
3063
+
3064
+ async def handle(self, ctx: InboundContext, next_fn) -> None:
3065
+ ctx.raw_text = self._patch(ctx.raw_text, ctx.media_urls, ctx.media_types)
3066
+ await next_fn()
3067
+
3068
+
2562
3069
  class DispatchMiddleware(InboundMiddleware):
2563
3070
  """Build MessageEvent and dispatch to AI handler."""
2564
3071
 
@@ -2574,124 +3081,22 @@ class DispatchMiddleware(InboundMiddleware):
2574
3081
  )
2575
3082
 
2576
3083
  async def _dispatch_inbound_event() -> None:
2577
- media_urls = list(ctx.media_urls)
2578
- media_types = list(ctx.media_types)
2579
-
2580
- # If user quoted a message (reply_to_message_id is set), resolve only
2581
- # quote_media_refs to avoid injecting unrelated history media.
2582
- # Otherwise, backfill observed media from recent transcript history.
2583
- if ctx.reply_to_message_id is not None:
2584
- # Fallback: if desc didn't contain ybres refs, look up transcript
2585
- if not ctx.quote_media_refs:
2586
- try:
2587
- store = getattr(adapter, "_session_store", None)
2588
- if store:
2589
- session_entry = store.get_or_create_session(ctx.source)
2590
- history = store.load_transcript(session_entry.session_id)
2591
- for msg in reversed(history or []):
2592
- mid = msg.get("message_id", "")
2593
- if mid and mid == ctx.reply_to_message_id:
2594
- _content = msg.get("content", "")
2595
- if isinstance(_content, str) and "|ybres:" in _content:
2596
- for m in _YB_RES_REF_RE.finditer(_content):
2597
- head = m.group(1)
2598
- rid = m.group(2)
2599
- kind, _, filename = head.partition(":")
2600
- kind = kind.strip()
2601
- if kind in _RESOLVABLE_MEDIA_KINDS:
2602
- ctx.quote_media_refs.append((rid, kind, filename.strip()))
2603
- break
2604
- except Exception as exc:
2605
- logger.warning(
2606
- "[%s] quote transcript lookup failed: %s",
2607
- adapter.name, exc,
2608
- )
2609
- # User quoted a message — resolve only media from the quote
2610
- for rid, kind, filename in ctx.quote_media_refs:
2611
- if kind not in _RESOLVABLE_MEDIA_KINDS:
2612
- continue
2613
- try:
2614
- fresh_url = await MediaResolveMiddleware._resolve_by_resource_id(adapter, rid)
2615
- except Exception as exc:
2616
- logger.warning(
2617
- "[%s] quote media resolve failed: rid=%s kind=%s err=%s",
2618
- adapter.name, rid, kind, exc,
2619
- )
2620
- continue
2621
- cached = await MediaResolveMiddleware._download_and_cache(
2622
- adapter,
2623
- fetch_url=fresh_url,
2624
- kind=kind,
2625
- file_name=filename or None,
2626
- log_tag=f"quote rid={rid}",
2627
- resource_id=rid,
2628
- )
2629
- if cached is None:
2630
- continue
2631
- path, mime = cached
2632
- # Avoid duplicates
2633
- if path not in media_urls:
2634
- media_urls.append(path)
2635
- media_types.append(mime)
2636
- else:
2637
- # No quote — backfill observed media from recent transcript history
2638
- extra_img_urls: List[str] = []
2639
- extra_img_mimes: List[str] = []
2640
- try:
2641
- extra_img_urls, extra_img_mimes = await MediaResolveMiddleware._collect_observed_media(
2642
- adapter, ctx.source,
2643
- )
2644
- except Exception as exc:
2645
- logger.warning(
2646
- "[%s] observed-image hydration raised, continuing anyway: %s",
2647
- adapter.name, exc,
2648
- )
2649
- if extra_img_urls:
2650
- current = set(media_urls)
2651
- for u, m in zip(extra_img_urls, extra_img_mimes):
2652
- if u in current:
2653
- continue
2654
- media_urls.append(u)
2655
- media_types.append(m)
2656
- current.add(u)
2657
-
2658
- # Replace [kind|ybres:xxx] anchors with local cache paths so
2659
- # the transcript records usable paths for the model.
2660
- _patched_event_text = ctx.raw_text
2661
- for u, m in zip(media_urls, media_types):
2662
- if not u.startswith("/"):
2663
- continue
2664
- anchor_match = _YB_RES_REF_RE.search(_patched_event_text)
2665
- if not anchor_match:
2666
- continue
2667
- head = anchor_match.group(1)
2668
- kind, _, filename = head.partition(":")
2669
- kind = kind.strip()
2670
- if kind == "image" and m.startswith("image/"):
2671
- replacement = f"[image: {u}]"
2672
- elif kind == "file":
2673
- label = filename.strip() or os.path.basename(u)
2674
- replacement = f"[file: {label} → {u}]"
2675
- else:
2676
- continue
2677
- _patched_event_text = (
2678
- _patched_event_text[:anchor_match.start()]
2679
- + replacement
2680
- + _patched_event_text[anchor_match.end():]
2681
- )
2682
-
2683
3084
  event = MessageEvent(
2684
- text=_patched_event_text,
3085
+ text=ctx.raw_text,
2685
3086
  message_type=(
2686
3087
  MessageType.DOCUMENT
2687
- if any(mt.startswith(("application/", "text/")) for mt in media_types)
2688
- else ctx.msg_type
3088
+ if any(mt.startswith(("application/", "text/")) for mt in ctx.media_types)
3089
+ # Coerce yuanbao-local subtypes (e.g. CHAT_RECORD) back to a
3090
+ # base MessageType: chat records are deep-parsed into a text
3091
+ # prompt, so TEXT is the right kind for downstream routing.
3092
+ else ctx.msg_type if isinstance(ctx.msg_type, MessageType)
3093
+ else MessageType.TEXT
2689
3094
  ),
2690
3095
  source=ctx.source,
2691
3096
  message_id=ctx.msg_id or None,
2692
3097
  raw_message=ctx.push,
2693
- media_urls=media_urls,
2694
- media_types=media_types,
3098
+ media_urls=list(ctx.media_urls),
3099
+ media_types=list(ctx.media_types),
2695
3100
  reply_to_message_id=ctx.reply_to_message_id,
2696
3101
  reply_to_text=ctx.reply_to_text,
2697
3102
  channel_prompt=ctx.channel_prompt,
@@ -2784,7 +3189,9 @@ class InboundPipelineBuilder:
2784
3189
  GroupAttributionMiddleware,
2785
3190
  ClassifyMessageTypeMiddleware,
2786
3191
  QuoteContextMiddleware,
3192
+ ForwardedRecordsParseMiddleware,
2787
3193
  MediaResolveMiddleware,
3194
+ PatchAnchorsMiddleware,
2788
3195
  DispatchMiddleware,
2789
3196
  ]
2790
3197
 
@@ -3445,12 +3852,22 @@ class ConnectionManager:
3445
3852
  return False
3446
3853
 
3447
3854
  async def _cleanup_ws(self) -> None:
3448
- """Close and clear the WebSocket connection."""
3855
+ """Close and clear the WebSocket connection, bounded by
3856
+ ``WS_CLOSE_TIMEOUT_S`` so an unresponsive server can't stall teardown
3857
+ (see the constant's definition for the full rationale)."""
3449
3858
  ws = self._ws
3450
3859
  self._ws = None
3451
3860
  if ws is not None:
3452
3861
  try:
3453
- await ws.close()
3862
+ await asyncio.wait_for(ws.close(), timeout=WS_CLOSE_TIMEOUT_S)
3863
+ except asyncio.TimeoutError:
3864
+ # Server never echoed the close frame within the bound; drop the
3865
+ # connection. websockets force-closes the transport on cancel,
3866
+ # and at shutdown the loop is tearing down anyway.
3867
+ logger.debug(
3868
+ "[%s] WS close handshake exceeded %.1fs — dropping connection",
3869
+ self._adapter.name, WS_CLOSE_TIMEOUT_S,
3870
+ )
3454
3871
  except Exception:
3455
3872
  pass
3456
3873