@clawpump/claw-agent 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent/.dockerignore +67 -0
- package/agent/.envrc +1 -1
- package/agent/.gitattributes +8 -0
- package/agent/AGENTS.md +216 -4
- package/agent/CONTRIBUTING.md +46 -8
- package/agent/Dockerfile +78 -35
- package/agent/MANIFEST.in +2 -0
- package/agent/README.md +12 -5
- package/agent/README.ur-pk.md +261 -0
- package/agent/README.zh-CN.md +11 -8
- package/agent/SECURITY.md +5 -4
- package/agent/acp_adapter/provenance.py +127 -0
- package/agent/acp_adapter/server.py +112 -5
- package/agent/acp_adapter/session.py +1 -6
- package/agent/acp_registry/agent.json +2 -2
- package/agent/agent/account_usage.py +313 -1
- package/agent/agent/agent_init.py +140 -37
- package/agent/agent/agent_runtime_helpers.py +342 -83
- package/agent/agent/anthropic_adapter.py +320 -33
- package/agent/agent/auxiliary_client.py +525 -105
- package/agent/agent/background_review.py +157 -19
- package/agent/agent/bedrock_adapter.py +71 -6
- package/agent/agent/billing_view.py +295 -0
- package/agent/agent/chat_completion_helpers.py +229 -4
- package/agent/agent/codex_responses_adapter.py +86 -10
- package/agent/agent/codex_runtime.py +153 -1
- package/agent/agent/coding_context.py +738 -0
- package/agent/agent/context_compressor.py +392 -44
- package/agent/agent/context_references.py +34 -1
- package/agent/agent/conversation_compression.py +159 -22
- package/agent/agent/conversation_loop.py +643 -908
- package/agent/agent/copilot_acp_client.py +4 -11
- package/agent/agent/credential_pool.py +5 -3
- package/agent/agent/credits_tracker.py +794 -0
- package/agent/agent/curator.py +91 -18
- package/agent/agent/curator_backup.py +26 -10
- package/agent/agent/display.py +42 -1
- package/agent/agent/error_classifier.py +52 -3
- package/agent/agent/errors.py +3 -0
- package/agent/agent/file_safety.py +0 -17
- package/agent/agent/gemini_native_adapter.py +31 -1
- package/agent/agent/i18n.py +48 -4
- package/agent/agent/image_gen_provider.py +74 -5
- package/agent/agent/image_routing.py +29 -0
- package/agent/agent/insights.py +8 -17
- package/agent/agent/lsp/install.py +3 -0
- package/agent/agent/memory_manager.py +326 -31
- package/agent/agent/message_content.py +50 -0
- package/agent/agent/model_metadata.py +214 -3
- package/agent/agent/moonshot_schema.py +8 -1
- package/agent/agent/onboarding.py +60 -0
- package/agent/agent/prompt_builder.py +327 -37
- package/agent/agent/redact.py +1 -0
- package/agent/agent/runtime_cwd.py +34 -5
- package/agent/agent/secret_scope.py +205 -0
- package/agent/agent/secret_sources/bitwarden.py +34 -2
- package/agent/agent/skill_commands.py +90 -1
- package/agent/agent/skill_preprocessing.py +1 -0
- package/agent/agent/skill_utils.py +209 -36
- package/agent/agent/ssl_guard.py +94 -0
- package/agent/agent/system_prompt.py +133 -5
- package/agent/agent/tool_executor.py +496 -70
- package/agent/agent/transports/anthropic.py +83 -21
- package/agent/agent/transports/chat_completions.py +94 -5
- package/agent/agent/transports/codex.py +67 -2
- package/agent/agent/transports/codex_app_server.py +1 -0
- package/agent/agent/transports/codex_app_server_session.py +30 -0
- package/agent/agent/transports/types.py +12 -0
- package/agent/agent/turn_context.py +408 -0
- package/agent/agent/turn_finalizer.py +428 -0
- package/agent/agent/turn_retry_state.py +68 -0
- package/agent/agent/usage_pricing.py +3 -0
- package/agent/apps/bootstrap-installer/package.json +6 -5
- package/agent/apps/bootstrap-installer/src/routes/failure.tsx +12 -5
- package/agent/apps/bootstrap-installer/src/routes/progress.tsx +1 -3
- package/agent/apps/bootstrap-installer/src/store.ts +3 -2
- package/agent/apps/bootstrap-installer/src-tauri/src/bootstrap.rs +172 -7
- package/agent/apps/bootstrap-installer/src-tauri/src/events.rs +14 -1
- package/agent/apps/bootstrap-installer/src-tauri/src/paths.rs +29 -0
- package/agent/apps/bootstrap-installer/src-tauri/src/powershell.rs +93 -3
- package/agent/apps/bootstrap-installer/src-tauri/src/update.rs +695 -39
- package/agent/apps/bootstrap-installer/tsconfig.json +3 -4
- package/agent/apps/desktop/DESIGN.md +167 -0
- package/agent/apps/desktop/README.md +20 -16
- package/agent/apps/desktop/assets/icon.icns +0 -0
- package/agent/apps/desktop/assets/icon.ico +0 -0
- package/agent/apps/desktop/assets/icon.png +0 -0
- package/agent/apps/desktop/electron/backend-env.cjs +112 -0
- package/agent/apps/desktop/electron/backend-env.test.cjs +111 -0
- package/agent/apps/desktop/electron/backend-probes.test.cjs +3 -1
- package/agent/apps/desktop/electron/backend-ready.cjs +66 -0
- package/agent/apps/desktop/electron/bootstrap-platform.cjs +52 -0
- package/agent/apps/desktop/electron/bootstrap-platform.test.cjs +59 -1
- package/agent/apps/desktop/electron/bootstrap-runner.cjs +176 -38
- package/agent/apps/desktop/electron/bootstrap-runner.test.cjs +112 -1
- package/agent/apps/desktop/electron/connection-config.cjs +288 -0
- package/agent/apps/desktop/electron/connection-config.test.cjs +396 -0
- package/agent/apps/desktop/electron/dashboard-token.cjs +99 -0
- package/agent/apps/desktop/electron/dashboard-token.test.cjs +142 -0
- package/agent/apps/desktop/electron/desktop-uninstall.cjs +232 -0
- package/agent/apps/desktop/electron/desktop-uninstall.test.cjs +246 -0
- package/agent/apps/desktop/electron/entitlements.mac.inherit.plist +2 -0
- package/agent/apps/desktop/electron/fs-read-dir.cjs +109 -0
- package/agent/apps/desktop/electron/fs-read-dir.test.cjs +364 -0
- package/agent/apps/desktop/electron/gateway-ws-probe.cjs +188 -0
- package/agent/apps/desktop/electron/gateway-ws-probe.test.cjs +122 -0
- package/agent/apps/desktop/electron/git-root.cjs +54 -0
- package/agent/apps/desktop/electron/git-root.test.cjs +40 -0
- package/agent/apps/desktop/electron/git-worktrees.cjs +174 -0
- package/agent/apps/desktop/electron/hardening.cjs +123 -28
- package/agent/apps/desktop/electron/hardening.test.cjs +163 -0
- package/agent/apps/desktop/electron/main.cjs +3121 -331
- package/agent/apps/desktop/electron/oauth-net-request.cjs +20 -0
- package/agent/apps/desktop/electron/oauth-net-request.test.cjs +34 -0
- package/agent/apps/desktop/electron/preload.cjs +52 -2
- package/agent/apps/desktop/electron/session-windows.cjs +124 -0
- package/agent/apps/desktop/electron/session-windows.test.cjs +199 -0
- package/agent/apps/desktop/electron/update-rebuild.cjs +29 -0
- package/agent/apps/desktop/electron/update-rebuild.test.cjs +55 -0
- package/agent/apps/desktop/electron/update-remote.cjs +56 -0
- package/agent/apps/desktop/electron/update-remote.test.cjs +78 -0
- package/agent/apps/desktop/electron/vscode-marketplace.cjs +331 -0
- package/agent/apps/desktop/electron/vscode-marketplace.test.cjs +113 -0
- package/agent/apps/desktop/electron/windows-child-process.test.cjs +57 -0
- package/agent/apps/desktop/electron/windows-user-env.cjs +76 -0
- package/agent/apps/desktop/electron/windows-user-env.test.cjs +90 -0
- package/agent/apps/desktop/electron/workspace-cwd.cjs +38 -0
- package/agent/apps/desktop/electron/workspace-cwd.test.cjs +45 -0
- package/agent/apps/desktop/eslint.config.mjs +0 -3
- package/agent/apps/desktop/index.html +27 -2
- package/agent/apps/desktop/package.json +31 -11
- package/agent/apps/desktop/pr-assets/session-source-folders.png +0 -0
- package/agent/apps/desktop/public/apple-touch-icon.png +0 -0
- package/agent/apps/desktop/public/nous-girl.jpg +0 -0
- package/agent/apps/desktop/scripts/assert-dist-built.cjs +70 -0
- package/agent/apps/desktop/scripts/assert-dist-built.test.cjs +84 -0
- package/agent/apps/desktop/scripts/before-pack.cjs +78 -0
- package/agent/apps/desktop/scripts/before-pack.test.cjs +53 -0
- package/agent/apps/desktop/scripts/diag-scroll-reset.mjs +229 -0
- package/agent/apps/desktop/scripts/patch-electron-builder-mac-binary.cjs +64 -0
- package/agent/apps/desktop/scripts/run-electron-builder.cjs +57 -0
- package/agent/apps/desktop/src/app/agents/index.tsx +53 -45
- package/agent/apps/desktop/src/app/artifacts/index.tsx +102 -83
- package/agent/apps/desktop/src/app/chat/chat-drop-overlay.tsx +29 -8
- package/agent/apps/desktop/src/app/chat/chat-swap-overlay.tsx +47 -0
- package/agent/apps/desktop/src/app/chat/composer/attachments.tsx +81 -45
- package/agent/apps/desktop/src/app/chat/composer/completion-drawer.tsx +13 -24
- package/agent/apps/desktop/src/app/chat/composer/context-menu.tsx +138 -88
- package/agent/apps/desktop/src/app/chat/composer/controls.tsx +138 -90
- package/agent/apps/desktop/src/app/chat/composer/enter-submit-dom-race.test.tsx +218 -0
- package/agent/apps/desktop/src/app/chat/composer/focus.ts +32 -0
- package/agent/apps/desktop/src/app/chat/composer/help-hint.tsx +38 -25
- package/agent/apps/desktop/src/app/chat/composer/hooks/use-live-completion-adapter.ts +7 -0
- package/agent/apps/desktop/src/app/chat/composer/hooks/use-mic-recorder.ts +22 -12
- package/agent/apps/desktop/src/app/chat/composer/hooks/use-slash-completions.ts +142 -14
- package/agent/apps/desktop/src/app/chat/composer/hooks/use-voice-conversation.ts +14 -11
- package/agent/apps/desktop/src/app/chat/composer/hooks/use-voice-recorder.ts +9 -6
- package/agent/apps/desktop/src/app/chat/composer/ime-composition-dom-repro.test.tsx +108 -0
- package/agent/apps/desktop/src/app/chat/composer/index.tsx +930 -180
- package/agent/apps/desktop/src/app/chat/composer/inline-refs.ts +136 -32
- package/agent/apps/desktop/src/app/chat/composer/model-pill.tsx +86 -0
- package/agent/apps/desktop/src/app/chat/composer/queue-panel.tsx +54 -75
- package/agent/apps/desktop/src/app/chat/composer/rich-editor.test.ts +117 -1
- package/agent/apps/desktop/src/app/chat/composer/rich-editor.ts +117 -6
- package/agent/apps/desktop/src/app/chat/composer/slash-nav-dom-repro.test.tsx +186 -0
- package/agent/apps/desktop/src/app/chat/composer/status-stack/index.tsx +202 -0
- package/agent/apps/desktop/src/app/chat/composer/status-stack/status-row.tsx +155 -0
- package/agent/apps/desktop/src/app/chat/composer/text-utils.test.ts +104 -0
- package/agent/apps/desktop/src/app/chat/composer/text-utils.ts +37 -9
- package/agent/apps/desktop/src/app/chat/composer/trigger-popover.test.tsx +50 -0
- package/agent/apps/desktop/src/app/chat/composer/trigger-popover.tsx +105 -40
- package/agent/apps/desktop/src/app/chat/composer/types.ts +5 -0
- package/agent/apps/desktop/src/app/chat/composer/url-dialog.tsx +11 -15
- package/agent/apps/desktop/src/app/chat/composer/voice-activity.tsx +8 -4
- package/agent/apps/desktop/src/app/chat/hooks/use-composer-actions.test.ts +57 -0
- package/agent/apps/desktop/src/app/chat/hooks/use-composer-actions.ts +70 -16
- package/agent/apps/desktop/src/app/chat/hooks/use-file-drop-zone.ts +52 -16
- package/agent/apps/desktop/src/app/chat/index.tsx +234 -81
- package/agent/apps/desktop/src/app/chat/perf-probe.tsx +69 -21
- package/agent/apps/desktop/src/app/chat/right-rail/preview-console.tsx +44 -40
- package/agent/apps/desktop/src/app/chat/right-rail/preview-file.tsx +71 -25
- package/agent/apps/desktop/src/app/chat/right-rail/preview-pane.test.tsx +40 -1
- package/agent/apps/desktop/src/app/chat/right-rail/preview-pane.tsx +55 -53
- package/agent/apps/desktop/src/app/chat/right-rail/preview.tsx +35 -17
- package/agent/apps/desktop/src/app/chat/scroll-to-bottom-button.test.tsx +67 -0
- package/agent/apps/desktop/src/app/chat/scroll-to-bottom-button.tsx +74 -0
- package/agent/apps/desktop/src/app/chat/sidebar/cron-jobs-section.tsx +356 -0
- package/agent/apps/desktop/src/app/chat/sidebar/index.tsx +1189 -364
- package/agent/apps/desktop/src/app/chat/sidebar/load-more-row.tsx +30 -0
- package/agent/apps/desktop/src/app/chat/sidebar/order.test.ts +21 -0
- package/agent/apps/desktop/src/app/chat/sidebar/order.ts +17 -0
- package/agent/apps/desktop/src/app/chat/sidebar/profile-switcher.tsx +524 -0
- package/agent/apps/desktop/src/app/chat/sidebar/session-actions-menu.tsx +80 -45
- package/agent/apps/desktop/src/app/chat/sidebar/session-row.tsx +120 -25
- package/agent/apps/desktop/src/app/chat/sidebar/virtual-session-list.tsx +7 -13
- package/agent/apps/desktop/src/app/chat/sidebar/workspace-groups.test.ts +149 -0
- package/agent/apps/desktop/src/app/chat/sidebar/workspace-groups.ts +326 -0
- package/agent/apps/desktop/src/app/chat/thread-loading.ts +7 -2
- package/agent/apps/desktop/src/app/command-center/index.tsx +320 -581
- package/agent/apps/desktop/src/app/command-palette/index.tsx +681 -0
- package/agent/apps/desktop/src/app/command-palette/marketplace-theme-page.tsx +157 -0
- package/agent/apps/desktop/src/app/cron/index.tsx +392 -324
- package/agent/apps/desktop/src/app/cron/job-state.ts +29 -0
- package/agent/apps/desktop/src/app/desktop-controller.tsx +618 -123
- package/agent/apps/desktop/src/app/floating-hud.ts +22 -0
- package/agent/apps/desktop/src/app/gateway/hooks/use-gateway-boot.test.tsx +265 -0
- package/agent/apps/desktop/src/app/gateway/hooks/use-gateway-boot.ts +260 -14
- package/agent/apps/desktop/src/app/gateway/hooks/use-gateway-request.ts +48 -4
- package/agent/apps/desktop/src/app/hooks/use-keybinds.ts +270 -0
- package/agent/apps/desktop/src/app/hooks/use-refresh-hotkey.ts +45 -0
- package/agent/apps/desktop/src/app/layout-constants.ts +19 -0
- package/agent/apps/desktop/src/app/messaging/index.tsx +136 -241
- package/agent/apps/desktop/src/app/messaging/platform-icon.tsx +95 -0
- package/agent/apps/desktop/src/app/model-visibility-overlay.tsx +31 -0
- package/agent/apps/desktop/src/app/overlays/overlay-search-input.tsx +18 -62
- package/agent/apps/desktop/src/app/overlays/overlay-split-layout.tsx +59 -7
- package/agent/apps/desktop/src/app/overlays/overlay-view.tsx +9 -5
- package/agent/apps/desktop/src/app/page-search-shell.tsx +42 -20
- package/agent/apps/desktop/src/app/profiles/create-profile-dialog.tsx +165 -0
- package/agent/apps/desktop/src/app/profiles/delete-profile-dialog.tsx +65 -0
- package/agent/apps/desktop/src/app/profiles/index.tsx +174 -199
- package/agent/apps/desktop/src/app/profiles/rename-profile-dialog.tsx +125 -0
- package/agent/apps/desktop/src/app/right-sidebar/files/dnd-manager.ts +27 -0
- package/agent/apps/desktop/src/app/right-sidebar/files/ipc.test.ts +100 -0
- package/agent/apps/desktop/src/app/right-sidebar/files/ipc.ts +12 -18
- package/agent/apps/desktop/src/app/right-sidebar/files/remote-picker.tsx +177 -0
- package/agent/apps/desktop/src/app/right-sidebar/files/tree.tsx +35 -21
- package/agent/apps/desktop/src/app/right-sidebar/files/use-project-tree.test.ts +75 -3
- package/agent/apps/desktop/src/app/right-sidebar/files/use-project-tree.ts +152 -5
- package/agent/apps/desktop/src/app/right-sidebar/index.test.tsx +75 -0
- package/agent/apps/desktop/src/app/right-sidebar/index.tsx +166 -129
- package/agent/apps/desktop/src/app/right-sidebar/store.ts +19 -4
- package/agent/apps/desktop/src/app/right-sidebar/terminal/buffer.ts +65 -0
- package/agent/apps/desktop/src/app/right-sidebar/terminal/index.tsx +29 -34
- package/agent/apps/desktop/src/app/right-sidebar/terminal/persistent.tsx +18 -6
- package/agent/apps/desktop/src/app/right-sidebar/terminal/selection.ts +93 -32
- package/agent/apps/desktop/src/app/right-sidebar/terminal/use-terminal-session.ts +381 -119
- package/agent/apps/desktop/src/app/routes.ts +9 -0
- package/agent/apps/desktop/src/app/session/hooks/use-cwd-actions.ts +17 -7
- package/agent/apps/desktop/src/app/session/hooks/use-message-stream.ts +365 -47
- package/agent/apps/desktop/src/app/session/hooks/use-model-controls.test.tsx +198 -0
- package/agent/apps/desktop/src/app/session/hooks/use-model-controls.ts +70 -34
- package/agent/apps/desktop/src/app/session/hooks/use-prompt-actions.test.tsx +1061 -0
- package/agent/apps/desktop/src/app/session/hooks/use-prompt-actions.ts +1143 -165
- package/agent/apps/desktop/src/app/session/hooks/use-route-resume.test.tsx +341 -2
- package/agent/apps/desktop/src/app/session/hooks/use-route-resume.ts +176 -5
- package/agent/apps/desktop/src/app/session/hooks/use-session-actions.test.tsx +259 -0
- package/agent/apps/desktop/src/app/session/hooks/use-session-actions.ts +452 -149
- package/agent/apps/desktop/src/app/session/hooks/use-session-state-cache.test.tsx +327 -0
- package/agent/apps/desktop/src/app/session/hooks/use-session-state-cache.ts +133 -4
- package/agent/apps/desktop/src/app/session-picker-overlay.tsx +32 -0
- package/agent/apps/desktop/src/app/session-switcher.tsx +107 -0
- package/agent/apps/desktop/src/app/settings/about-settings.tsx +45 -36
- package/agent/apps/desktop/src/app/settings/appearance-settings.tsx +243 -162
- package/agent/apps/desktop/src/app/settings/config-settings.tsx +86 -66
- package/agent/apps/desktop/src/app/settings/constants.ts +459 -122
- package/agent/apps/desktop/src/app/settings/credential-key-ui.tsx +373 -0
- package/agent/apps/desktop/src/app/settings/env-credentials.tsx +198 -0
- package/agent/apps/desktop/src/app/settings/env-var-actions-menu.tsx +136 -0
- package/agent/apps/desktop/src/app/settings/field-copy.ts +56 -0
- package/agent/apps/desktop/src/app/settings/gateway-settings.tsx +385 -72
- package/agent/apps/desktop/src/app/settings/helpers.test.ts +156 -1
- package/agent/apps/desktop/src/app/settings/helpers.ts +30 -2
- package/agent/apps/desktop/src/app/settings/index.tsx +118 -84
- package/agent/apps/desktop/src/app/settings/keys-settings.tsx +62 -419
- package/agent/apps/desktop/src/app/settings/mcp-settings.tsx +65 -60
- package/agent/apps/desktop/src/app/settings/model-settings.test.tsx +129 -5
- package/agent/apps/desktop/src/app/settings/model-settings.tsx +370 -65
- package/agent/apps/desktop/src/app/settings/notifications-settings.tsx +150 -0
- package/agent/apps/desktop/src/app/settings/primitives.tsx +5 -11
- package/agent/apps/desktop/src/app/settings/provider-config-panel.test.tsx +142 -0
- package/agent/apps/desktop/src/app/settings/provider-config-panel.tsx +182 -0
- package/agent/apps/desktop/src/app/settings/providers-settings.test.tsx +171 -0
- package/agent/apps/desktop/src/app/settings/providers-settings.tsx +471 -0
- package/agent/apps/desktop/src/app/settings/sessions-settings.tsx +183 -71
- package/agent/apps/desktop/src/app/settings/toolset-config-panel.test.tsx +135 -1
- package/agent/apps/desktop/src/app/settings/toolset-config-panel.tsx +180 -57
- package/agent/apps/desktop/src/app/settings/types.ts +9 -6
- package/agent/apps/desktop/src/app/settings/uninstall-section.tsx +185 -0
- package/agent/apps/desktop/src/app/settings/use-deep-link-highlight.ts +60 -0
- package/agent/apps/desktop/src/app/shell/app-shell.tsx +59 -13
- package/agent/apps/desktop/src/app/shell/gateway-menu-panel.tsx +37 -32
- package/agent/apps/desktop/src/app/shell/hooks/use-overlay-routing.ts +6 -3
- package/agent/apps/desktop/src/app/shell/hooks/use-statusbar-items.tsx +212 -53
- package/agent/apps/desktop/src/app/shell/keybind-panel.tsx +215 -0
- package/agent/apps/desktop/src/app/shell/model-edit-submenu.test.tsx +84 -0
- package/agent/apps/desktop/src/app/shell/model-edit-submenu.tsx +244 -0
- package/agent/apps/desktop/src/app/shell/model-menu-panel.tsx +392 -0
- package/agent/apps/desktop/src/app/shell/statusbar-controls.tsx +23 -33
- package/agent/apps/desktop/src/app/shell/titlebar-controls.tsx +79 -95
- package/agent/apps/desktop/src/app/shell/titlebar.ts +8 -2
- package/agent/apps/desktop/src/app/skills/index.test.tsx +11 -0
- package/agent/apps/desktop/src/app/skills/index.tsx +79 -64
- package/agent/apps/desktop/src/app/types.ts +85 -0
- package/agent/apps/desktop/src/app/updates-overlay.tsx +110 -105
- package/agent/apps/desktop/src/components/assistant-ui/ansi-text.tsx +34 -0
- package/agent/apps/desktop/src/components/assistant-ui/block-direction.test.tsx +129 -0
- package/agent/apps/desktop/src/components/assistant-ui/clarify-tool.tsx +102 -81
- package/agent/apps/desktop/src/components/assistant-ui/directive-text.tsx +92 -15
- package/agent/apps/desktop/src/components/assistant-ui/markdown-text.test.ts +38 -0
- package/agent/apps/desktop/src/components/assistant-ui/markdown-text.tsx +304 -45
- package/agent/apps/desktop/src/components/assistant-ui/message-render-boundary.test.tsx +80 -0
- package/agent/apps/desktop/src/components/assistant-ui/message-render-boundary.tsx +48 -0
- package/agent/apps/desktop/src/components/assistant-ui/streaming.test.tsx +142 -90
- package/agent/apps/desktop/src/components/assistant-ui/thread-list.tsx +337 -0
- package/agent/apps/desktop/src/components/assistant-ui/thread.tsx +667 -190
- package/agent/apps/desktop/src/components/assistant-ui/tool-approval-group.test.tsx +299 -0
- package/agent/apps/desktop/src/components/assistant-ui/tool-approval.test.tsx +133 -0
- package/agent/apps/desktop/src/components/assistant-ui/tool-approval.tsx +239 -0
- package/agent/apps/desktop/src/components/assistant-ui/tool-fallback-model.test.ts +31 -0
- package/agent/apps/desktop/src/components/assistant-ui/tool-fallback-model.ts +152 -134
- package/agent/apps/desktop/src/components/assistant-ui/tool-fallback.tsx +142 -150
- package/agent/apps/desktop/src/components/assistant-ui/tooltip-icon-button.tsx +14 -12
- package/agent/apps/desktop/src/components/assistant-ui/user-message-edit.test.tsx +141 -0
- package/agent/apps/desktop/src/components/assistant-ui/user-message-text.tsx +152 -0
- package/agent/apps/desktop/src/components/boot-failure-overlay.tsx +150 -33
- package/agent/apps/desktop/src/components/boot-failure-reauth.test.ts +100 -0
- package/agent/apps/desktop/src/components/boot-failure-reauth.ts +81 -0
- package/agent/apps/desktop/src/components/brand-mark.tsx +19 -0
- package/agent/apps/desktop/src/components/chat/code-card.tsx +1 -1
- package/agent/apps/desktop/src/components/chat/composer-dock.ts +31 -0
- package/agent/apps/desktop/src/components/chat/diff-lines.tsx +1 -1
- package/agent/apps/desktop/src/components/chat/disclosure-row.tsx +13 -3
- package/agent/apps/desktop/src/components/chat/expandable-block.tsx +52 -0
- package/agent/apps/desktop/src/components/chat/generated-image-result.tsx +174 -0
- package/agent/apps/desktop/src/components/chat/image-generation-placeholder.tsx +70 -37
- package/agent/apps/desktop/src/components/chat/intro.tsx +8 -7
- package/agent/apps/desktop/src/components/chat/preview-attachment.tsx +4 -2
- package/agent/apps/desktop/src/components/chat/shiki-highlighter.test.ts +37 -0
- package/agent/apps/desktop/src/components/chat/shiki-highlighter.tsx +96 -22
- package/agent/apps/desktop/src/components/chat/status-row.tsx +70 -0
- package/agent/apps/desktop/src/components/chat/status-section.tsx +42 -0
- package/agent/apps/desktop/src/components/chat/terminal-output.tsx +54 -0
- package/agent/apps/desktop/src/components/chat/zoomable-image.tsx +70 -109
- package/agent/apps/desktop/src/components/desktop-install-overlay.tsx +154 -84
- package/agent/apps/desktop/src/components/desktop-onboarding-overlay.test.tsx +38 -8
- package/agent/apps/desktop/src/components/desktop-onboarding-overlay.tsx +789 -233
- package/agent/apps/desktop/src/components/error-boundary.tsx +77 -0
- package/agent/apps/desktop/src/components/gateway-connecting-overlay.test.tsx +144 -0
- package/agent/apps/desktop/src/components/gateway-connecting-overlay.tsx +7 -1
- package/agent/apps/desktop/src/components/haptics-provider.tsx +24 -0
- package/agent/apps/desktop/src/components/language-switcher.test.tsx +53 -0
- package/agent/apps/desktop/src/components/language-switcher.tsx +175 -0
- package/agent/apps/desktop/src/components/model-picker.tsx +42 -40
- package/agent/apps/desktop/src/components/model-visibility-dialog.tsx +166 -0
- package/agent/apps/desktop/src/components/notifications.tsx +48 -27
- package/agent/apps/desktop/src/components/pane-shell/index.ts +1 -1
- package/agent/apps/desktop/src/components/pane-shell/pane-shell.tsx +146 -9
- package/agent/apps/desktop/src/components/prompt-overlays.tsx +234 -0
- package/agent/apps/desktop/src/components/session-picker.tsx +108 -0
- package/agent/apps/desktop/src/components/ui/action-status.tsx +25 -0
- package/agent/apps/desktop/src/components/ui/badge.tsx +35 -0
- package/agent/apps/desktop/src/components/ui/button.tsx +37 -13
- package/agent/apps/desktop/src/components/ui/confirm-dialog.tsx +109 -0
- package/agent/apps/desktop/src/components/ui/control.ts +25 -0
- package/agent/apps/desktop/src/components/ui/copy-button.test.tsx +36 -0
- package/agent/apps/desktop/src/components/ui/copy-button.tsx +38 -27
- package/agent/apps/desktop/src/components/ui/dialog.tsx +39 -11
- package/agent/apps/desktop/src/components/ui/dropdown-menu.tsx +98 -24
- package/agent/apps/desktop/src/components/ui/error-state.tsx +50 -0
- package/agent/apps/desktop/src/components/ui/fade-text.tsx +9 -2
- package/agent/apps/desktop/src/components/ui/{braille-spinner.tsx → glyph-spinner.tsx} +15 -13
- package/agent/apps/desktop/src/components/ui/input.tsx +5 -2
- package/agent/apps/desktop/src/components/ui/kbd.tsx +83 -12
- package/agent/apps/desktop/src/components/ui/log-view.tsx +19 -0
- package/agent/apps/desktop/src/components/ui/pagination.tsx +12 -5
- package/agent/apps/desktop/src/components/ui/popover.tsx +44 -0
- package/agent/apps/desktop/src/components/ui/search-field.tsx +80 -0
- package/agent/apps/desktop/src/components/ui/segmented-control.tsx +51 -0
- package/agent/apps/desktop/src/components/ui/select.tsx +10 -3
- package/agent/apps/desktop/src/components/ui/sheet.tsx +8 -2
- package/agent/apps/desktop/src/components/ui/sidebar.tsx +18 -25
- package/agent/apps/desktop/src/components/ui/switch.tsx +38 -15
- package/agent/apps/desktop/src/components/ui/textarea.tsx +4 -11
- package/agent/apps/desktop/src/components/ui/tool-icon.tsx +65 -0
- package/agent/apps/desktop/src/components/ui/tooltip.tsx +31 -4
- package/agent/apps/desktop/src/fonts/JetBrainsMono-Bold.woff2 +0 -0
- package/agent/apps/desktop/src/fonts/JetBrainsMono-Italic.woff2 +0 -0
- package/agent/apps/desktop/src/fonts/JetBrainsMono-Regular.woff2 +0 -0
- package/agent/apps/desktop/src/global.d.ts +181 -4
- package/agent/apps/desktop/src/hermes.test.ts +60 -0
- package/agent/apps/desktop/src/hermes.ts +190 -13
- package/agent/apps/desktop/src/hooks/use-image-download.ts +85 -0
- package/agent/apps/desktop/src/hooks/use-resize-observer.ts +13 -4
- package/agent/apps/desktop/src/hooks/use-worktree-info.ts +68 -0
- package/agent/apps/desktop/src/i18n/catalog.ts +12 -0
- package/agent/apps/desktop/src/i18n/context.test.tsx +232 -0
- package/agent/apps/desktop/src/i18n/context.tsx +183 -0
- package/agent/apps/desktop/src/i18n/define-locale.ts +41 -0
- package/agent/apps/desktop/src/i18n/en.ts +1921 -0
- package/agent/apps/desktop/src/i18n/index.ts +20 -0
- package/agent/apps/desktop/src/i18n/ja.ts +2053 -0
- package/agent/apps/desktop/src/i18n/languages.test.ts +43 -0
- package/agent/apps/desktop/src/i18n/languages.ts +86 -0
- package/agent/apps/desktop/src/i18n/runtime.test.ts +75 -0
- package/agent/apps/desktop/src/i18n/runtime.ts +53 -0
- package/agent/apps/desktop/src/i18n/types.ts +1559 -0
- package/agent/apps/desktop/src/i18n/zh-hant.ts +1992 -0
- package/agent/apps/desktop/src/i18n/zh.ts +2099 -0
- package/agent/apps/desktop/src/lib/ansi.test.ts +123 -0
- package/agent/apps/desktop/src/lib/ansi.ts +186 -0
- package/agent/apps/desktop/src/lib/chat-messages.test.ts +79 -0
- package/agent/apps/desktop/src/lib/chat-messages.ts +68 -29
- package/agent/apps/desktop/src/lib/chat-runtime.test.ts +65 -1
- package/agent/apps/desktop/src/lib/chat-runtime.ts +39 -3
- package/agent/apps/desktop/src/lib/completion-sound.ts +519 -0
- package/agent/apps/desktop/src/lib/desktop-fs.test.ts +116 -0
- package/agent/apps/desktop/src/lib/desktop-fs.ts +113 -0
- package/agent/apps/desktop/src/lib/desktop-slash-commands.test.ts +89 -6
- package/agent/apps/desktop/src/lib/desktop-slash-commands.ts +270 -131
- package/agent/apps/desktop/src/lib/external-link.test.tsx +27 -0
- package/agent/apps/desktop/src/lib/external-link.tsx +9 -2
- package/agent/apps/desktop/src/lib/gateway-events.test.ts +27 -0
- package/agent/apps/desktop/src/lib/gateway-events.ts +16 -0
- package/agent/apps/desktop/src/lib/gateway-ws-url.test.ts +78 -0
- package/agent/apps/desktop/src/lib/gateway-ws-url.ts +91 -0
- package/agent/apps/desktop/src/lib/generated-images.test.ts +97 -0
- package/agent/apps/desktop/src/lib/generated-images.ts +116 -0
- package/agent/apps/desktop/src/lib/haptics.ts +17 -0
- package/agent/apps/desktop/src/lib/icons.ts +10 -2
- package/agent/apps/desktop/src/lib/keybinds/actions.ts +137 -0
- package/agent/apps/desktop/src/lib/keybinds/combo.test.ts +86 -0
- package/agent/apps/desktop/src/lib/keybinds/combo.ts +195 -0
- package/agent/apps/desktop/src/lib/local-preview.ts +23 -2
- package/agent/apps/desktop/src/lib/markdown-preprocess.ts +20 -7
- package/agent/apps/desktop/src/lib/media.remote.test.ts +90 -0
- package/agent/apps/desktop/src/lib/media.ts +40 -1
- package/agent/apps/desktop/src/lib/model-status-label.test.ts +59 -0
- package/agent/apps/desktop/src/lib/model-status-label.ts +122 -0
- package/agent/apps/desktop/src/lib/mutable-ref.ts +6 -0
- package/agent/apps/desktop/src/lib/profile-color.ts +58 -0
- package/agent/apps/desktop/src/lib/query-client.ts +13 -0
- package/agent/apps/desktop/src/lib/remend-tail.test.ts +105 -0
- package/agent/apps/desktop/src/lib/remend-tail.ts +108 -0
- package/agent/apps/desktop/src/lib/session-export.ts +6 -3
- package/agent/apps/desktop/src/lib/session-ids.test.ts +44 -0
- package/agent/apps/desktop/src/lib/session-ids.ts +26 -0
- package/agent/apps/desktop/src/lib/session-search.test.ts +66 -0
- package/agent/apps/desktop/src/lib/session-search.ts +21 -0
- package/agent/apps/desktop/src/lib/session-source.ts +126 -0
- package/agent/apps/desktop/src/lib/storage.test.ts +25 -0
- package/agent/apps/desktop/src/lib/storage.ts +35 -1
- package/agent/apps/desktop/src/lib/todos.test.ts +46 -1
- package/agent/apps/desktop/src/lib/todos.ts +37 -0
- package/agent/apps/desktop/src/lib/tool-result-summary.ts +5 -1
- package/agent/apps/desktop/src/lib/update-copy.test.ts +38 -0
- package/agent/apps/desktop/src/lib/update-copy.ts +44 -0
- package/agent/apps/desktop/src/lib/use-enter-animation.ts +2 -2
- package/agent/apps/desktop/src/lib/yolo-session.ts +50 -0
- package/agent/apps/desktop/src/main.tsx +19 -19
- package/agent/apps/desktop/src/store/boot.ts +4 -3
- package/agent/apps/desktop/src/store/clarify.test.ts +81 -0
- package/agent/apps/desktop/src/store/clarify.ts +50 -13
- package/agent/apps/desktop/src/store/command-palette.ts +20 -0
- package/agent/apps/desktop/src/store/compaction.test.ts +53 -0
- package/agent/apps/desktop/src/store/compaction.ts +38 -0
- package/agent/apps/desktop/src/store/completion-sound.ts +32 -0
- package/agent/apps/desktop/src/store/composer-input-history.test.ts +147 -0
- package/agent/apps/desktop/src/store/composer-input-history.ts +158 -0
- package/agent/apps/desktop/src/store/composer-queue.test.ts +68 -0
- package/agent/apps/desktop/src/store/composer-queue.ts +76 -0
- package/agent/apps/desktop/src/store/composer-status.test.ts +99 -0
- package/agent/apps/desktop/src/store/composer-status.ts +277 -0
- package/agent/apps/desktop/src/store/composer.test.ts +106 -0
- package/agent/apps/desktop/src/store/composer.ts +116 -0
- package/agent/apps/desktop/src/store/cron.ts +19 -0
- package/agent/apps/desktop/src/store/gateway.ts +280 -6
- package/agent/apps/desktop/src/store/keybinds.ts +143 -0
- package/agent/apps/desktop/src/store/layout.ts +107 -9
- package/agent/apps/desktop/src/store/model-presets.test.ts +51 -0
- package/agent/apps/desktop/src/store/model-presets.ts +86 -0
- package/agent/apps/desktop/src/store/model-visibility.test.ts +99 -0
- package/agent/apps/desktop/src/store/model-visibility.ts +161 -0
- package/agent/apps/desktop/src/store/native-notifications.test.ts +192 -0
- package/agent/apps/desktop/src/store/native-notifications.ts +203 -0
- package/agent/apps/desktop/src/store/notifications.ts +10 -7
- package/agent/apps/desktop/src/store/onboarding.test.ts +271 -1
- package/agent/apps/desktop/src/store/onboarding.ts +268 -38
- package/agent/apps/desktop/src/store/preview.ts +10 -1
- package/agent/apps/desktop/src/store/profile.test.ts +89 -0
- package/agent/apps/desktop/src/store/profile.ts +395 -0
- package/agent/apps/desktop/src/store/prompts.test.ts +127 -0
- package/agent/apps/desktop/src/store/prompts.ts +117 -0
- package/agent/apps/desktop/src/store/session-switcher.test.ts +115 -0
- package/agent/apps/desktop/src/store/session-switcher.ts +128 -0
- package/agent/apps/desktop/src/store/session-sync.ts +25 -0
- package/agent/apps/desktop/src/store/session.test.ts +268 -2
- package/agent/apps/desktop/src/store/session.ts +392 -18
- package/agent/apps/desktop/src/store/subagents.ts +3 -0
- package/agent/apps/desktop/src/store/system-actions.ts +48 -0
- package/agent/apps/desktop/src/store/thread-scroll.ts +58 -5
- package/agent/apps/desktop/src/store/todos.test.ts +47 -0
- package/agent/apps/desktop/src/store/todos.ts +64 -0
- package/agent/apps/desktop/src/store/tool-dismiss.ts +45 -0
- package/agent/apps/desktop/src/store/translucency.ts +38 -0
- package/agent/apps/desktop/src/store/updates.test.ts +187 -2
- package/agent/apps/desktop/src/store/updates.ts +268 -18
- package/agent/apps/desktop/src/store/windows.test.ts +143 -0
- package/agent/apps/desktop/src/store/windows.ts +115 -0
- package/agent/apps/desktop/src/styles.css +510 -119
- package/agent/apps/desktop/src/themes/color.ts +142 -0
- package/agent/apps/desktop/src/themes/context.tsx +128 -75
- package/agent/apps/desktop/src/themes/install.test.ts +119 -0
- package/agent/apps/desktop/src/themes/install.ts +95 -0
- package/agent/apps/desktop/src/themes/presets.test.ts +33 -0
- package/agent/apps/desktop/src/themes/presets.ts +13 -4
- package/agent/apps/desktop/src/themes/profile-theme.test.ts +41 -0
- package/agent/apps/desktop/src/themes/types.ts +35 -0
- package/agent/apps/desktop/src/themes/user-themes.test.ts +63 -0
- package/agent/apps/desktop/src/themes/user-themes.ts +122 -0
- package/agent/apps/desktop/src/themes/vscode.test.ts +171 -0
- package/agent/apps/desktop/src/themes/vscode.ts +343 -0
- package/agent/apps/desktop/src/types/hermes.ts +138 -1
- package/agent/apps/desktop/tsconfig.json +2 -2
- package/agent/apps/desktop/vite.config.ts +18 -0
- package/agent/apps/shared/package.json +1 -1
- package/agent/apps/shared/src/json-rpc-gateway.ts +63 -2
- package/agent/apps/shared/tsconfig.json +2 -2
- package/agent/cli-config.yaml.example +78 -1
- package/agent/cli.py +2294 -3146
- package/agent/cron/blueprint_catalog.py +713 -0
- package/agent/cron/jobs.py +226 -110
- package/agent/cron/scheduler.py +468 -193
- package/agent/cron/scheduler_provider.py +177 -0
- package/agent/cron/scripts/__init__.py +1 -0
- package/agent/cron/scripts/classify_items.py +226 -0
- package/agent/cron/suggestion_catalog.py +154 -0
- package/agent/cron/suggestions.py +257 -0
- package/agent/docs/chronos-managed-cron-contract.md +196 -0
- package/agent/docs/design/profile-builder.md +146 -0
- package/agent/docs/middleware/README.md +260 -0
- package/agent/docs/observability/README.md +316 -0
- package/agent/docs/plans/2026-06-09-003-fix-telegram-stream-overflow-continuations-plan.md +240 -0
- package/agent/docs/rca-ssl-cacert-post-git-pull.md +54 -0
- package/agent/docs/relay-connector-contract.md +285 -0
- package/agent/gateway/authz_mixin.py +536 -0
- package/agent/gateway/channel_directory.py +65 -3
- package/agent/gateway/config.py +222 -12
- package/agent/gateway/display_config.py +10 -0
- package/agent/gateway/hooks.py +17 -0
- package/agent/gateway/kanban_watchers.py +1146 -0
- package/agent/gateway/message_timestamps.py +166 -0
- package/agent/gateway/platforms/ADDING_A_PLATFORM.md +29 -0
- package/agent/gateway/platforms/api_server.py +216 -38
- package/agent/gateway/platforms/base.py +210 -58
- package/agent/gateway/platforms/email.py +122 -12
- package/agent/gateway/platforms/feishu.py +80 -11
- package/agent/gateway/platforms/feishu_meeting_invite.py +212 -0
- package/agent/gateway/platforms/matrix.py +1498 -297
- package/agent/gateway/platforms/qqbot/adapter.py +6 -0
- package/agent/gateway/platforms/signal.py +8 -0
- package/agent/gateway/platforms/slack.py +308 -12
- package/agent/gateway/platforms/telegram.py +831 -24
- package/agent/gateway/platforms/webhook.py +109 -21
- package/agent/gateway/platforms/weixin.py +113 -2
- package/agent/gateway/platforms/whatsapp.py +94 -288
- package/agent/gateway/platforms/whatsapp_cloud.py +1956 -0
- package/agent/gateway/platforms/whatsapp_common.py +367 -0
- package/agent/gateway/platforms/yuanbao.py +608 -191
- package/agent/gateway/platforms/yuanbao_proto.py +232 -23
- package/agent/gateway/relay/__init__.py +375 -0
- package/agent/gateway/relay/adapter.py +222 -0
- package/agent/gateway/relay/auth.py +168 -0
- package/agent/gateway/relay/descriptor.py +118 -0
- package/agent/gateway/relay/transport.py +101 -0
- package/agent/gateway/relay/ws_transport.py +327 -0
- package/agent/gateway/response_filters.py +53 -0
- package/agent/gateway/rich_sent_store.py +80 -0
- package/agent/gateway/run.py +2940 -5001
- package/agent/gateway/session.py +109 -8
- package/agent/gateway/session_context.py +22 -4
- package/agent/gateway/slash_commands.py +3854 -0
- package/agent/gateway/status.py +141 -21
- package/agent/gateway/stream_consumer.py +288 -31
- package/agent/hermes-already-has-routines.md +1 -1
- package/agent/hermes_cli/__init__.py +62 -17
- package/agent/hermes_cli/_parser.py +30 -0
- package/agent/hermes_cli/_subprocess_compat.py +61 -0
- package/agent/hermes_cli/active_sessions.py +320 -0
- package/agent/hermes_cli/auth.py +707 -59
- package/agent/hermes_cli/auth_commands.py +39 -22
- package/agent/hermes_cli/backup.py +109 -7
- package/agent/hermes_cli/banner.py +88 -0
- package/agent/hermes_cli/blueprint_cmd.py +318 -0
- package/agent/hermes_cli/clawpump_cli.py +3 -3
- package/agent/hermes_cli/cli_agent_setup_mixin.py +684 -0
- package/agent/hermes_cli/cli_commands_mixin.py +2293 -0
- package/agent/hermes_cli/commands.py +216 -91
- package/agent/hermes_cli/config.py +967 -130
- package/agent/hermes_cli/container_boot.py +76 -11
- package/agent/hermes_cli/cron.py +5 -11
- package/agent/hermes_cli/curator.py +21 -0
- package/agent/hermes_cli/dashboard_auth/__init__.py +2 -0
- package/agent/hermes_cli/dashboard_auth/base.py +62 -0
- package/agent/hermes_cli/dashboard_auth/cookies.py +32 -19
- package/agent/hermes_cli/dashboard_auth/login_page.py +156 -6
- package/agent/hermes_cli/dashboard_auth/middleware.py +28 -4
- package/agent/hermes_cli/dashboard_auth/prefix.py +46 -2
- package/agent/hermes_cli/dashboard_auth/public_paths.py +6 -0
- package/agent/hermes_cli/dashboard_auth/routes.py +158 -2
- package/agent/hermes_cli/dashboard_auth/ws_tickets.py +85 -11
- package/agent/hermes_cli/dashboard_register.py +427 -0
- package/agent/hermes_cli/debug.py +155 -50
- package/agent/hermes_cli/distribution.py +227 -0
- package/agent/hermes_cli/doctor.py +255 -14
- package/agent/hermes_cli/dump.py +60 -6
- package/agent/hermes_cli/env_loader.py +33 -0
- package/agent/hermes_cli/gateway.py +755 -103
- package/agent/hermes_cli/gateway_enroll.py +250 -0
- package/agent/hermes_cli/gateway_windows.py +254 -11
- package/agent/hermes_cli/gui_uninstall.py +285 -0
- package/agent/hermes_cli/inventory.py +105 -4
- package/agent/hermes_cli/kanban.py +58 -71
- package/agent/hermes_cli/kanban_db.py +391 -14
- package/agent/hermes_cli/kanban_decompose.py +2 -2
- package/agent/hermes_cli/kanban_specify.py +3 -1
- package/agent/hermes_cli/logs.py +2 -0
- package/agent/hermes_cli/main.py +2889 -5287
- package/agent/hermes_cli/managed_scope.py +214 -0
- package/agent/hermes_cli/managed_uv.py +254 -0
- package/agent/hermes_cli/mcp_catalog.py +6 -3
- package/agent/hermes_cli/mcp_config.py +145 -21
- package/agent/hermes_cli/mcp_security.py +96 -0
- package/agent/hermes_cli/mcp_startup.py +32 -3
- package/agent/hermes_cli/memory_providers.py +149 -0
- package/agent/hermes_cli/memory_setup.py +97 -42
- package/agent/hermes_cli/middleware.py +313 -0
- package/agent/hermes_cli/model_catalog.py +31 -0
- package/agent/hermes_cli/model_cost_guard.py +134 -0
- package/agent/hermes_cli/model_normalize.py +2 -1
- package/agent/hermes_cli/model_setup_flows.py +2759 -0
- package/agent/hermes_cli/model_switch.py +242 -27
- package/agent/hermes_cli/models.py +284 -44
- package/agent/hermes_cli/nous_account.py +33 -6
- package/agent/hermes_cli/nous_billing.py +406 -0
- package/agent/hermes_cli/nous_subscription.py +202 -5
- package/agent/hermes_cli/platforms.py +1 -0
- package/agent/hermes_cli/plugins.py +218 -18
- package/agent/hermes_cli/plugins_cmd.py +249 -105
- package/agent/hermes_cli/portal_cli.py +56 -16
- package/agent/hermes_cli/profile_distribution.py +6 -1
- package/agent/hermes_cli/profiles.py +283 -32
- package/agent/hermes_cli/provider_catalog.py +170 -0
- package/agent/hermes_cli/providers.py +4 -1
- package/agent/hermes_cli/pty_bridge.py +53 -4
- package/agent/hermes_cli/runtime_provider.py +216 -34
- package/agent/hermes_cli/secret_prompt.py +4 -4
- package/agent/hermes_cli/secrets_cli.py +24 -0
- package/agent/hermes_cli/send_cmd.py +28 -2
- package/agent/hermes_cli/service_manager.py +166 -19
- package/agent/hermes_cli/session_listing.py +97 -0
- package/agent/hermes_cli/setup.py +158 -94
- package/agent/hermes_cli/setup_whatsapp_cloud.py +541 -0
- package/agent/hermes_cli/skills_config.py +8 -2
- package/agent/hermes_cli/skills_hub.py +149 -7
- package/agent/hermes_cli/status.py +2 -2
- package/agent/hermes_cli/subcommands/__init__.py +18 -0
- package/agent/hermes_cli/subcommands/_shared.py +29 -0
- package/agent/hermes_cli/subcommands/acp.py +52 -0
- package/agent/hermes_cli/subcommands/auth.py +109 -0
- package/agent/hermes_cli/subcommands/backup.py +38 -0
- package/agent/hermes_cli/subcommands/claw.py +92 -0
- package/agent/hermes_cli/subcommands/config.py +49 -0
- package/agent/hermes_cli/subcommands/cron.py +163 -0
- package/agent/hermes_cli/subcommands/dashboard.py +143 -0
- package/agent/hermes_cli/subcommands/debug.py +77 -0
- package/agent/hermes_cli/subcommands/doctor.py +35 -0
- package/agent/hermes_cli/subcommands/dump.py +28 -0
- package/agent/hermes_cli/subcommands/gateway.py +332 -0
- package/agent/hermes_cli/subcommands/gui.py +63 -0
- package/agent/hermes_cli/subcommands/hooks.py +77 -0
- package/agent/hermes_cli/subcommands/import_cmd.py +31 -0
- package/agent/hermes_cli/subcommands/insights.py +25 -0
- package/agent/hermes_cli/subcommands/login.py +78 -0
- package/agent/hermes_cli/subcommands/logout.py +28 -0
- package/agent/hermes_cli/subcommands/logs.py +78 -0
- package/agent/hermes_cli/subcommands/mcp.py +108 -0
- package/agent/hermes_cli/subcommands/memory.py +53 -0
- package/agent/hermes_cli/subcommands/model.py +72 -0
- package/agent/hermes_cli/subcommands/pairing.py +36 -0
- package/agent/hermes_cli/subcommands/plugins.py +94 -0
- package/agent/hermes_cli/subcommands/postinstall.py +23 -0
- package/agent/hermes_cli/subcommands/profile.py +203 -0
- package/agent/hermes_cli/subcommands/prompt_size.py +36 -0
- package/agent/hermes_cli/subcommands/security.py +62 -0
- package/agent/hermes_cli/subcommands/setup.py +58 -0
- package/agent/hermes_cli/subcommands/skills.py +298 -0
- package/agent/hermes_cli/subcommands/slack.py +60 -0
- package/agent/hermes_cli/subcommands/status.py +28 -0
- package/agent/hermes_cli/subcommands/tools.py +95 -0
- package/agent/hermes_cli/subcommands/uninstall.py +41 -0
- package/agent/hermes_cli/subcommands/update.py +70 -0
- package/agent/hermes_cli/subcommands/version.py +18 -0
- package/agent/hermes_cli/subcommands/webhook.py +76 -0
- package/agent/hermes_cli/subcommands/whatsapp.py +22 -0
- package/agent/hermes_cli/suggestions_cmd.py +153 -0
- package/agent/hermes_cli/telegram_managed_bot.py +358 -0
- package/agent/hermes_cli/tips.py +3 -4
- package/agent/hermes_cli/tools_config.py +155 -28
- package/agent/hermes_cli/uninstall.py +231 -35
- package/agent/hermes_cli/web_server.py +6188 -975
- package/agent/hermes_cli/win_pty_bridge.py +179 -0
- package/agent/hermes_cli/write_approval_commands.py +209 -0
- package/agent/hermes_constants.py +164 -33
- package/agent/hermes_logging.py +74 -2
- package/agent/hermes_state.py +919 -106
- package/agent/hermes_time.py +20 -0
- package/agent/locales/af.yaml +23 -0
- package/agent/locales/de.yaml +23 -0
- package/agent/locales/en.yaml +20 -0
- package/agent/locales/es.yaml +23 -0
- package/agent/locales/fr.yaml +23 -0
- package/agent/locales/ga.yaml +23 -0
- package/agent/locales/hu.yaml +23 -0
- package/agent/locales/it.yaml +23 -0
- package/agent/locales/ja.yaml +23 -0
- package/agent/locales/ko.yaml +23 -0
- package/agent/locales/pt.yaml +23 -0
- package/agent/locales/ru.yaml +23 -0
- package/agent/locales/tr.yaml +23 -0
- package/agent/locales/uk.yaml +23 -0
- package/agent/locales/zh-hant.yaml +23 -0
- package/agent/locales/zh.yaml +23 -0
- package/agent/model_tools.py +204 -40
- package/agent/optional-mcps/clawpump/manifest.yaml +15 -5
- package/agent/optional-mcps/clawpump-stdio/manifest.yaml +14 -4
- package/agent/optional-mcps/unreal-engine/manifest.yaml +54 -0
- package/agent/optional-skills/blockchain/hyperliquid/SKILL.md +2 -2
- package/agent/optional-skills/blockchain/hyperliquid/scripts/hyperliquid_client.py +1 -1
- package/agent/optional-skills/creative/kanban-video-orchestrator/SKILL.md +1 -1
- package/agent/optional-skills/creative/kanban-video-orchestrator/assets/setup.sh.tmpl +4 -3
- package/agent/optional-skills/creative/kanban-video-orchestrator/references/kanban-setup.md +6 -4
- package/agent/optional-skills/creative/kanban-video-orchestrator/references/tool-matrix.md +2 -2
- package/agent/{skills/software-development → optional-skills/devops}/hermes-s6-container-supervision/SKILL.md +2 -0
- package/agent/optional-skills/devops/watchers/SKILL.md +1 -1
- package/agent/optional-skills/devops/watchers/scripts/watch_github.py +2 -1
- package/agent/optional-skills/payments/mpp-agent/SKILL.md +124 -0
- package/agent/optional-skills/payments/stripe-link-cli/SKILL.md +184 -0
- package/agent/optional-skills/payments/stripe-projects/SKILL.md +120 -0
- package/agent/optional-skills/productivity/canvas/SKILL.md +1 -1
- package/agent/optional-skills/productivity/canvas/scripts/canvas_api.py +4 -1
- package/agent/optional-skills/productivity/shop/SKILL.md +224 -0
- package/agent/optional-skills/productivity/shop/references/catalog-mcp.md +236 -0
- package/agent/optional-skills/productivity/shop/references/direct-api.md +278 -0
- package/agent/optional-skills/productivity/shop/references/legal.md +3 -0
- package/agent/optional-skills/productivity/shop/references/safety.md +36 -0
- package/agent/optional-skills/productivity/shopify/SKILL.md +1 -1
- package/agent/optional-skills/productivity/siyuan/SKILL.md +1 -1
- package/agent/optional-skills/productivity/telephony/SKILL.md +4 -4
- package/agent/optional-skills/productivity/telephony/scripts/telephony.py +15 -15
- package/agent/optional-skills/security/1password/SKILL.md +1 -1
- package/agent/{skills/red-teaming → optional-skills/security}/godmode/SKILL.md +3 -4
- package/agent/{skills/red-teaming → optional-skills/security}/godmode/scripts/auto_jailbreak.py +3 -1
- package/agent/optional-skills/software-development/rest-graphql-debug/SKILL.md +1 -1
- package/agent/{skills → optional-skills}/software-development/subagent-driven-development/SKILL.md +5 -5
- package/agent/package-lock.json +4082 -7907
- package/agent/package.json +18 -3
- package/agent/plugins/browser/firecrawl/provider.py +4 -1
- package/agent/plugins/cron/__init__.py +344 -0
- package/agent/plugins/cron/chronos/__init__.py +241 -0
- package/agent/plugins/cron/chronos/_nas_client.py +123 -0
- package/agent/plugins/cron/chronos/plugin.yaml +9 -0
- package/agent/plugins/cron/chronos/verify.py +103 -0
- package/agent/plugins/dashboard_auth/basic/__init__.py +491 -0
- package/agent/plugins/dashboard_auth/basic/plugin.yaml +7 -0
- package/agent/plugins/dashboard_auth/nous/__init__.py +12 -14
- package/agent/plugins/dashboard_auth/self_hosted/__init__.py +736 -0
- package/agent/plugins/dashboard_auth/self_hosted/plugin.yaml +8 -0
- package/agent/plugins/disk-cleanup/disk_cleanup.py +100 -20
- package/agent/plugins/google_meet/audio_bridge.py +4 -0
- package/agent/plugins/google_meet/meet_bot.py +7 -1
- package/agent/plugins/hermes-achievements/dashboard/dist/index.js +9 -15
- package/agent/plugins/image_gen/fal/__init__.py +35 -6
- package/agent/plugins/image_gen/krea/__init__.py +56 -13
- package/agent/plugins/image_gen/openai/__init__.py +122 -24
- package/agent/plugins/image_gen/openai-codex/__init__.py +28 -2
- package/agent/plugins/image_gen/xai/__init__.py +92 -12
- package/agent/plugins/kanban/dashboard/dist/index.js +63 -48
- package/agent/plugins/kanban/dashboard/plugin_api.py +39 -35
- package/agent/plugins/memory/__init__.py +48 -5
- package/agent/plugins/memory/byterover/__init__.py +1 -0
- package/agent/plugins/memory/hindsight/README.md +1 -1
- package/agent/plugins/memory/hindsight/__init__.py +138 -24
- package/agent/plugins/memory/hindsight/plugin.yaml +1 -1
- package/agent/plugins/memory/honcho/README.md +13 -10
- package/agent/plugins/memory/honcho/cli.py +247 -122
- package/agent/plugins/memory/honcho/client.py +112 -102
- package/agent/plugins/memory/openviking/README.md +12 -1
- package/agent/plugins/memory/openviking/__init__.py +2281 -107
- package/agent/plugins/memory/openviking/plugin.yaml +1 -2
- package/agent/plugins/memory/supermemory/README.md +22 -10
- package/agent/plugins/memory/supermemory/__init__.py +142 -37
- package/agent/plugins/memory/supermemory/plugin.yaml +1 -1
- package/agent/plugins/model-providers/anthropic/__init__.py +1 -0
- package/agent/plugins/model-providers/bedrock/__init__.py +1 -0
- package/agent/plugins/model-providers/copilot-acp/__init__.py +1 -0
- package/agent/plugins/model-providers/custom/__init__.py +8 -2
- package/agent/plugins/model-providers/kimi-coding/__init__.py +16 -7
- package/agent/plugins/model-providers/minimax/__init__.py +60 -8
- package/agent/plugins/model-providers/opencode-zen/__init__.py +12 -3
- package/agent/plugins/model-providers/openrouter/__init__.py +75 -4
- package/agent/plugins/model-providers/xiaomi/__init__.py +2 -0
- package/agent/plugins/model-providers/zai/__init__.py +1 -0
- package/agent/plugins/observability/langfuse/__init__.py +147 -14
- package/agent/plugins/observability/nemo_relay/README.md +559 -0
- package/agent/plugins/observability/nemo_relay/__init__.py +962 -0
- package/agent/plugins/observability/nemo_relay/plugin.yaml +20 -0
- package/agent/plugins/platforms/discord/adapter.py +932 -61
- package/agent/plugins/platforms/discord/voice_mixer.py +379 -0
- package/agent/plugins/platforms/google_chat/adapter.py +9 -3
- package/agent/plugins/platforms/google_chat/oauth.py +1 -1
- package/agent/plugins/platforms/homeassistant/__init__.py +3 -0
- package/agent/{gateway/platforms/homeassistant.py → plugins/platforms/homeassistant/adapter.py} +128 -0
- package/agent/plugins/platforms/homeassistant/plugin.yaml +22 -0
- package/agent/plugins/platforms/irc/adapter.py +4 -1
- package/agent/plugins/platforms/line/adapter.py +16 -1
- package/agent/plugins/platforms/mattermost/adapter.py +100 -24
- package/agent/plugins/platforms/photon/README.md +179 -0
- package/agent/plugins/platforms/photon/__init__.py +4 -0
- package/agent/plugins/platforms/photon/adapter.py +1586 -0
- package/agent/plugins/platforms/photon/auth.py +1046 -0
- package/agent/plugins/platforms/photon/cli.py +439 -0
- package/agent/plugins/platforms/photon/plugin.yaml +88 -0
- package/agent/plugins/platforms/photon/sidecar/README.md +52 -0
- package/agent/plugins/platforms/photon/sidecar/index.mjs +720 -0
- package/agent/plugins/platforms/photon/sidecar/package-lock.json +1730 -0
- package/agent/plugins/platforms/photon/sidecar/package.json +25 -0
- package/agent/plugins/platforms/photon/sidecar/patch-spectrum-mixed-attachments.mjs +155 -0
- package/agent/plugins/platforms/raft/__init__.py +3 -0
- package/agent/plugins/platforms/raft/adapter.py +774 -0
- package/agent/plugins/platforms/raft/plugin.yaml +19 -0
- package/agent/plugins/platforms/simplex/adapter.py +777 -220
- package/agent/plugins/platforms/simplex/plugin.yaml +21 -2
- package/agent/plugins/platforms/teams/adapter.py +175 -5
- package/agent/plugins/plugin_utils.py +135 -0
- package/agent/plugins/video_gen/fal/__init__.py +10 -3
- package/agent/plugins/web/searxng/provider.py +15 -2
- package/agent/plugins/web/xai/provider.py +2 -2
- package/agent/providers/base.py +22 -3
- package/agent/pyproject.toml +115 -21
- package/agent/run_agent.py +733 -39
- package/agent/scripts/build_skills_index.py +51 -19
- package/agent/scripts/check_subprocess_stdin.py +177 -0
- package/agent/scripts/contributor_audit.py +2 -0
- package/agent/scripts/docker_config_migrate.py +67 -0
- package/agent/scripts/install.cmd +3 -3
- package/agent/scripts/install.ps1 +580 -154
- package/agent/scripts/install.sh +402 -185
- package/agent/scripts/lib/node-bootstrap.sh +39 -4
- package/agent/scripts/release.py +183 -0
- package/agent/scripts/run_tests.sh +1 -0
- package/agent/scripts/run_tests_parallel.py +18 -23
- package/agent/scripts/whatsapp-bridge/bridge.js +25 -4
- package/agent/setup.py +59 -0
- package/agent/skills/autonomous-ai-agents/codex/SKILL.md +19 -0
- package/agent/skills/autonomous-ai-agents/hermes-agent/SKILL.md +10 -3
- package/agent/skills/{mcp/native-mcp/SKILL.md → autonomous-ai-agents/hermes-agent/references/native-mcp.md} +0 -13
- package/agent/skills/{devops/webhook-subscriptions/SKILL.md → autonomous-ai-agents/hermes-agent/references/webhooks.md} +1 -11
- package/agent/skills/clawpump/SKILL.md +53 -5
- package/agent/skills/devops/kanban-orchestrator/SKILL.md +1 -0
- package/agent/skills/devops/kanban-worker/SKILL.md +1 -0
- package/agent/skills/github/github-auth/SKILL.md +2 -2
- package/agent/skills/github/github-auth/scripts/gh-env.sh +2 -2
- package/agent/skills/github/github-code-review/SKILL.md +2 -2
- package/agent/skills/github/github-issues/SKILL.md +2 -2
- package/agent/skills/github/github-pr-workflow/SKILL.md +2 -2
- package/agent/skills/github/github-repo-management/SKILL.md +2 -2
- package/agent/skills/media/gif-search/SKILL.md +1 -1
- package/agent/skills/media/youtube-content/SKILL.md +10 -7
- package/agent/skills/media/youtube-content/scripts/fetch_transcript.py +3 -3
- package/agent/skills/note-taking/obsidian/SKILL.md +1 -1
- package/agent/skills/productivity/airtable/SKILL.md +2 -2
- package/agent/skills/productivity/google-workspace/scripts/setup.py +33 -7
- package/agent/skills/productivity/notion/SKILL.md +2 -2
- package/agent/skills/productivity/teams-meeting-pipeline/SKILL.md +1 -1
- package/agent/skills/research/llm-wiki/SKILL.md +1 -1
- package/agent/skills/social-media/xurl/SKILL.md +9 -0
- package/agent/skills/software-development/hermes-agent-skill-authoring/SKILL.md +1 -1
- package/agent/skills/software-development/plan/SKILL.md +285 -5
- package/agent/skills/software-development/requesting-code-review/SKILL.md +2 -2
- package/agent/skills/software-development/simplify-code/SKILL.md +212 -0
- package/agent/skills/software-development/spike/SKILL.md +2 -2
- package/agent/skills/software-development/systematic-debugging/SKILL.md +1 -1
- package/agent/skills/software-development/test-driven-development/SKILL.md +1 -1
- package/agent/tools/approval.py +302 -4
- package/agent/tools/async_delegation.py +386 -0
- package/agent/tools/blueprints.py +325 -0
- package/agent/tools/browser_cdp_tool.py +3 -3
- package/agent/tools/browser_tool.py +34 -6
- package/agent/tools/checkpoint_manager.py +31 -1
- package/agent/tools/clarify_tool.py +55 -5
- package/agent/tools/code_execution_tool.py +31 -14
- package/agent/tools/computer_use/cua_backend.py +81 -3
- package/agent/tools/computer_use/tool.py +79 -5
- package/agent/tools/computer_use/vision_routing.py +55 -3
- package/agent/tools/credential_files.py +31 -12
- package/agent/tools/cronjob_tools.py +30 -20
- package/agent/tools/delegate_tool.py +356 -31
- package/agent/tools/env_probe.py +1 -0
- package/agent/tools/environments/docker.py +163 -8
- package/agent/tools/environments/file_sync.py +2 -1
- package/agent/tools/environments/local.py +74 -23
- package/agent/tools/environments/singularity.py +4 -1
- package/agent/tools/environments/ssh.py +78 -11
- package/agent/tools/file_operations.py +277 -41
- package/agent/tools/file_tools.py +166 -28
- package/agent/tools/image_generation_tool.py +515 -29
- package/agent/tools/kanban_tools.py +99 -0
- package/agent/tools/lazy_deps.py +33 -2
- package/agent/tools/mcp_oauth.py +5 -5
- package/agent/tools/mcp_oauth_manager.py +7 -5
- package/agent/tools/mcp_tool.py +840 -33
- package/agent/tools/memory_tool.py +335 -38
- package/agent/tools/osv_check.py +15 -1
- package/agent/tools/process_registry.py +155 -11
- package/agent/tools/read_extract.py +248 -0
- package/agent/tools/read_terminal_tool.py +93 -0
- package/agent/tools/schema_sanitizer.py +38 -0
- package/agent/tools/send_message_tool.py +163 -49
- package/agent/tools/session_search_tool.py +189 -7
- package/agent/tools/skill_manager_tool.py +202 -3
- package/agent/tools/skill_usage.py +52 -4
- package/agent/tools/skills_hub.py +184 -44
- package/agent/tools/skills_sync.py +232 -5
- package/agent/tools/skills_tool.py +125 -11
- package/agent/tools/terminal_tool.py +148 -26
- package/agent/tools/tirith_security.py +2 -0
- package/agent/tools/todo_tool.py +32 -1
- package/agent/tools/transcription_tools.py +13 -5
- package/agent/tools/tts_tool.py +332 -38
- package/agent/tools/url_safety.py +52 -1
- package/agent/tools/vision_tools.py +124 -39
- package/agent/tools/voice_mode.py +4 -3
- package/agent/tools/web_tools.py +45 -15
- package/agent/tools/write_approval.py +493 -0
- package/agent/toolsets.py +34 -10
- package/agent/trajectory_compressor.py +81 -10
- package/agent/tui_gateway/entry.py +43 -6
- package/agent/tui_gateway/server.py +3335 -330
- package/agent/tui_gateway/slash_worker.py +61 -0
- package/agent/tui_gateway/ws.py +67 -9
- package/agent/ui-tui/eslint.config.mjs +0 -4
- package/agent/ui-tui/package.json +6 -6
- package/agent/ui-tui/packages/hermes-ink/package.json +1 -1
- package/agent/ui-tui/packages/hermes-ink/src/ink/app-mouse.test.ts +34 -1
- package/agent/ui-tui/packages/hermes-ink/src/ink/app-rawmode-mouse.test.ts +91 -0
- package/agent/ui-tui/packages/hermes-ink/src/ink/components/App.tsx +35 -2
- package/agent/ui-tui/packages/hermes-ink/src/ink/events/input-event.ts +4 -11
- package/agent/ui-tui/packages/hermes-ink/src/ink/parse-keypress.test.ts +23 -57
- package/agent/ui-tui/packages/hermes-ink/src/ink/parse-keypress.ts +11 -135
- package/agent/ui-tui/packages/hermes-ink/src/ink/termio/tokenize.test.ts +185 -0
- package/agent/ui-tui/packages/hermes-ink/src/ink/termio/tokenize.ts +37 -3
- package/agent/ui-tui/packages/hermes-ink/src/utils/execFileNoThrow.ts +5 -5
- package/agent/ui-tui/src/__tests__/appChromeStatusRule.test.tsx +217 -0
- package/agent/ui-tui/src/__tests__/appChromeStatusRuleDevCredits.test.tsx +73 -0
- package/agent/ui-tui/src/__tests__/approvalAction.test.ts +11 -0
- package/agent/ui-tui/src/__tests__/billingCommand.test.ts +301 -0
- package/agent/ui-tui/src/__tests__/blockLayout.test.ts +122 -0
- package/agent/ui-tui/src/__tests__/brandingMcpCount.test.ts +111 -0
- package/agent/ui-tui/src/__tests__/completionApply.test.ts +51 -0
- package/agent/ui-tui/src/__tests__/createGatewayEventHandler.test.ts +487 -2
- package/agent/ui-tui/src/__tests__/createSlashHandler.test.ts +54 -0
- package/agent/ui-tui/src/__tests__/creditsCommand.test.ts +144 -0
- package/agent/ui-tui/src/__tests__/gatewayClient.test.ts +120 -99
- package/agent/ui-tui/src/__tests__/gracefulExit.test.ts +11 -0
- package/agent/ui-tui/src/__tests__/memoryMonitor.test.ts +102 -0
- package/agent/ui-tui/src/__tests__/paths.test.ts +41 -1
- package/agent/ui-tui/src/__tests__/terminalModes.test.ts +22 -0
- package/agent/ui-tui/src/__tests__/text.test.ts +23 -0
- package/agent/ui-tui/src/__tests__/textInputFastEcho.test.ts +37 -0
- package/agent/ui-tui/src/__tests__/turnControllerNotice.test.ts +43 -0
- package/agent/ui-tui/src/__tests__/useInputHandlers.test.ts +38 -1
- package/agent/ui-tui/src/__tests__/virtualHeights.test.ts +8 -0
- package/agent/ui-tui/src/app/createGatewayEventHandler.ts +102 -7
- package/agent/ui-tui/src/app/interfaces.ts +64 -1
- package/agent/ui-tui/src/app/overlayStore.ts +18 -2
- package/agent/ui-tui/src/app/slash/commands/billing.ts +332 -0
- package/agent/ui-tui/src/app/slash/commands/core.ts +31 -2
- package/agent/ui-tui/src/app/slash/commands/credits.ts +57 -0
- package/agent/ui-tui/src/app/slash/commands/ops.ts +28 -0
- package/agent/ui-tui/src/app/slash/commands/session.ts +32 -4
- package/agent/ui-tui/src/app/slash/registry.ts +4 -0
- package/agent/ui-tui/src/app/turnController.ts +145 -2
- package/agent/ui-tui/src/app/uiStore.ts +2 -0
- package/agent/ui-tui/src/app/useInputHandlers.ts +42 -4
- package/agent/ui-tui/src/app/useMainApp.ts +54 -8
- package/agent/ui-tui/src/app/useSessionLifecycle.ts +40 -31
- package/agent/ui-tui/src/app/useSubmission.ts +23 -31
- package/agent/ui-tui/src/components/appChrome.tsx +112 -5
- package/agent/ui-tui/src/components/appLayout.tsx +9 -0
- package/agent/ui-tui/src/components/appOverlays.tsx +25 -1
- package/agent/ui-tui/src/components/billingOverlay.tsx +684 -0
- package/agent/ui-tui/src/components/branding.tsx +15 -3
- package/agent/ui-tui/src/components/messageLine.tsx +25 -3
- package/agent/ui-tui/src/components/pluginsHub.tsx +238 -0
- package/agent/ui-tui/src/components/prompts.tsx +31 -17
- package/agent/ui-tui/src/components/streamingAssistant.tsx +63 -55
- package/agent/ui-tui/src/components/textInput.tsx +16 -0
- package/agent/ui-tui/src/config/env.ts +12 -0
- package/agent/ui-tui/src/config/limits.ts +13 -0
- package/agent/ui-tui/src/domain/blockLayout.ts +146 -0
- package/agent/ui-tui/src/domain/paths.ts +24 -0
- package/agent/ui-tui/src/domain/slash.ts +40 -0
- package/agent/ui-tui/src/entry.tsx +35 -4
- package/agent/ui-tui/src/gatewayClient.ts +22 -10
- package/agent/ui-tui/src/gatewayTypes.ts +130 -1
- package/agent/ui-tui/src/lib/gracefulExit.ts +24 -4
- package/agent/ui-tui/src/lib/memory.test.ts +162 -0
- package/agent/ui-tui/src/lib/memory.ts +60 -1
- package/agent/ui-tui/src/lib/memoryMonitor.ts +79 -4
- package/agent/ui-tui/src/lib/osc52.ts +1 -1
- package/agent/ui-tui/src/lib/text.test.ts +32 -1
- package/agent/ui-tui/src/lib/text.ts +29 -2
- package/agent/ui-tui/src/lib/virtualHeights.ts +13 -0
- package/agent/ui-tui/src/types.ts +5 -0
- package/agent/ui-tui/tsconfig.build.json +0 -1
- package/agent/ui-tui/tsconfig.json +2 -1
- package/agent/utils.py +66 -2
- package/agent/uv.lock +308 -696
- package/agent/web/index.html +2 -2
- package/agent/web/package.json +11 -6
- package/agent/web/public/claw-bg.webp +0 -0
- package/agent/web/public/claw-logo.webp +0 -0
- package/agent/web/src/App.tsx +138 -48
- package/agent/web/src/components/AutomationBlueprints.tsx +225 -0
- package/agent/web/src/components/Backdrop.tsx +15 -0
- package/agent/web/src/components/ChatSessionList.tsx +260 -0
- package/agent/web/src/components/ChatSidebar.tsx +262 -78
- package/agent/web/src/components/ConfirmDialog.tsx +122 -0
- package/agent/web/src/components/ModelPickerDialog.tsx +111 -16
- package/agent/web/src/components/ModelReloadConfirm.tsx +40 -0
- package/agent/web/src/components/ProfileScopeBanner.tsx +30 -0
- package/agent/web/src/components/ProfileSwitcher.tsx +67 -0
- package/agent/web/src/components/ReasoningPicker.tsx +167 -0
- package/agent/web/src/components/SkillEditorDialog.tsx +215 -0
- package/agent/web/src/components/ThemeSwitcher.tsx +119 -4
- package/agent/web/src/components/ToolsetConfigDrawer.tsx +457 -0
- package/agent/web/src/contexts/PageHeaderProvider.tsx +7 -4
- package/agent/web/src/contexts/ProfileProvider.tsx +137 -0
- package/agent/web/src/contexts/SystemActions.tsx +6 -8
- package/agent/web/src/contexts/profile-context.ts +19 -0
- package/agent/web/src/contexts/useProfileScope.ts +6 -0
- package/agent/web/src/i18n/af.ts +5 -4
- package/agent/web/src/i18n/de.ts +5 -4
- package/agent/web/src/i18n/en.ts +58 -4
- package/agent/web/src/i18n/es.ts +5 -3
- package/agent/web/src/i18n/fr.ts +5 -3
- package/agent/web/src/i18n/ga.ts +5 -4
- package/agent/web/src/i18n/hu.ts +5 -4
- package/agent/web/src/i18n/it.ts +5 -4
- package/agent/web/src/i18n/ja.ts +5 -4
- package/agent/web/src/i18n/ko.ts +5 -4
- package/agent/web/src/i18n/pt.ts +5 -3
- package/agent/web/src/i18n/ru.ts +5 -4
- package/agent/web/src/i18n/tr.ts +5 -4
- package/agent/web/src/i18n/types.ts +59 -1
- package/agent/web/src/i18n/uk.ts +5 -3
- package/agent/web/src/i18n/zh-hant.ts +5 -4
- package/agent/web/src/i18n/zh.ts +5 -4
- package/agent/web/src/index.css +2 -2
- package/agent/web/src/lib/api.ts +819 -52
- package/agent/web/src/lib/dashboard-flags.ts +16 -7
- package/agent/web/src/lib/reasoning-effort.test.ts +48 -0
- package/agent/web/src/lib/reasoning-effort.ts +36 -0
- package/agent/web/src/lib/session-refresh.test.ts +21 -0
- package/agent/web/src/lib/session-refresh.ts +26 -0
- package/agent/web/src/pages/ChannelsPage.tsx +529 -68
- package/agent/web/src/pages/ChatPage.tsx +249 -56
- package/agent/web/src/pages/ConfigPage.tsx +11 -1
- package/agent/web/src/pages/CronPage.tsx +219 -31
- package/agent/web/src/pages/EnvPage.tsx +25 -6
- package/agent/web/src/pages/FilesPage.tsx +525 -0
- package/agent/web/src/pages/McpPage.tsx +80 -3
- package/agent/web/src/pages/ModelsPage.tsx +97 -12
- package/agent/web/src/pages/PluginsPage.tsx +1 -1
- package/agent/web/src/pages/ProfileBuilderPage.tsx +611 -0
- package/agent/web/src/pages/ProfilesPage.tsx +1038 -172
- package/agent/web/src/pages/SessionsPage.tsx +144 -13
- package/agent/web/src/pages/SkillsPage.tsx +851 -70
- package/agent/web/src/pages/SystemPage.tsx +340 -4
- package/agent/web/src/pages/WalletPage.tsx +401 -0
- package/agent/web/src/pages/WebhooksPage.tsx +145 -15
- package/agent/web/src/pages/X402Page.tsx +207 -0
- package/agent/web/src/plugins/registry.ts +28 -11
- package/agent/web/src/plugins/sdk.d.ts +160 -0
- package/agent/web/src/themes/context.tsx +112 -5
- package/agent/web/src/themes/fonts.ts +167 -0
- package/agent/web/src/themes/index.ts +7 -0
- package/agent/web/tsconfig.app.json +0 -1
- package/agent/web/vite.config.ts +1 -8
- package/agent/web/vitest.config.ts +16 -0
- package/package.json +1 -1
- package/agent/apps/desktop/package-lock.json +0 -18363
- package/agent/apps/desktop/src/app/chat/composer/skin-slash-popover.tsx +0 -56
- package/agent/apps/desktop/src/components/assistant-ui/thread-virtualizer.tsx +0 -382
- package/agent/apps/desktop/src/components/assistant-ui/todo-tool.tsx +0 -109
- package/agent/apps/desktop/src/components/chat/generated-image-context.tsx +0 -19
- package/agent/optional-skills/productivity/shop-app/SKILL.md +0 -340
- package/agent/skills/autonomous-ai-agents/kanban-codex-lane/SKILL.md +0 -277
- package/agent/skills/autonomous-ai-agents/kanban-codex-lane/templates/pmb-codex-lane-prompt.md +0 -57
- package/agent/skills/diagramming/DESCRIPTION.md +0 -3
- package/agent/skills/domain/DESCRIPTION.md +0 -24
- package/agent/skills/gifs/DESCRIPTION.md +0 -3
- package/agent/skills/inference-sh/DESCRIPTION.md +0 -19
- package/agent/skills/mcp/DESCRIPTION.md +0 -3
- package/agent/skills/media/spotify/SKILL.md +0 -135
- package/agent/skills/mlops/training/DESCRIPTION.md +0 -3
- package/agent/skills/mlops/vector-databases/DESCRIPTION.md +0 -3
- package/agent/skills/productivity/linear/SKILL.md +0 -380
- package/agent/skills/productivity/linear/scripts/linear_api.py +0 -445
- package/agent/skills/software-development/debugging-hermes-tui-commands/SKILL.md +0 -152
- package/agent/skills/software-development/writing-plans/SKILL.md +0 -297
- package/agent/ui-tui/package-lock.json +0 -7449
- package/agent/ui-tui/packages/hermes-ink/package-lock.json +0 -1289
- package/agent/web/package-lock.json +0 -8887
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/PORT_NOTES.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/SKILL.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/prompts/system.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/palettes/macaron.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/palettes/mono-ink.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/palettes/neon.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/palettes/warm.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/prompt-construction.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/style-presets.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/blueprint.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/chalkboard.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/editorial.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/elegant.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/fantasy-animation.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/flat-doodle.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/flat.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/ink-notes.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/intuition-machine.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/minimal.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/nature.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/notion.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/pixel-art.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/playful.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/retro.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/scientific.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/screen-print.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/sketch-notes.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/sketch.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/vector-illustration.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/vintage.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/warm.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/watercolor.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/usage.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/workflow.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/PORT_NOTES.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/SKILL.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/analysis-framework.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/chalk.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/ink-brush.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/ligne-claire.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/manga.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/minimalist.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/realistic.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/auto-selection.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/base-prompt.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/character-template.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/cinematic.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/dense.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/four-panel.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/mixed.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/splash.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/standard.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/webtoon.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/ohmsha-guide.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/partial-workflows.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/concept-story.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/four-panel.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/ohmsha.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/shoujo.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/wuxia.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/storyboard-template.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/action.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/dramatic.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/energetic.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/neutral.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/romantic.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/vintage.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/warm.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/workflow.md +0 -0
- /package/agent/{skills → optional-skills}/creative/creative-ideation/SKILL.md +0 -0
- /package/agent/{skills → optional-skills}/creative/creative-ideation/references/full-prompt-library.md +0 -0
- /package/agent/{skills → optional-skills}/creative/pixel-art/ATTRIBUTION.md +0 -0
- /package/agent/{skills → optional-skills}/creative/pixel-art/SKILL.md +0 -0
- /package/agent/{skills → optional-skills}/creative/pixel-art/references/palettes.md +0 -0
- /package/agent/{skills → optional-skills}/creative/pixel-art/scripts/__init__.py +0 -0
- /package/agent/{skills → optional-skills}/creative/pixel-art/scripts/palettes.py +0 -0
- /package/agent/{skills → optional-skills}/creative/pixel-art/scripts/pixel_art.py +0 -0
- /package/agent/{skills → optional-skills}/creative/pixel-art/scripts/pixel_art_video.py +0 -0
- /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/SKILL.md +0 -0
- /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/references/analysis-modules.md +0 -0
- /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/references/methods-guide.md +0 -0
- /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/templates/abliteration-config.yaml +0 -0
- /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/templates/analysis-study.yaml +0 -0
- /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/templates/batch-abliteration.yaml +0 -0
- /package/agent/{skills → optional-skills}/mlops/research/DESCRIPTION.md +0 -0
- /package/agent/{skills → optional-skills}/mlops/research/dspy/SKILL.md +0 -0
- /package/agent/{skills → optional-skills}/mlops/research/dspy/references/examples.md +0 -0
- /package/agent/{skills → optional-skills}/mlops/research/dspy/references/modules.md +0 -0
- /package/agent/{skills → optional-skills}/mlops/research/dspy/references/optimizers.md +0 -0
- /package/agent/{skills/red-teaming → optional-skills/security}/godmode/references/jailbreak-templates.md +0 -0
- /package/agent/{skills/red-teaming → optional-skills/security}/godmode/references/refusal-detection.md +0 -0
- /package/agent/{skills/red-teaming → optional-skills/security}/godmode/scripts/godmode_race.py +0 -0
- /package/agent/{skills/red-teaming → optional-skills/security}/godmode/scripts/load_godmode.py +0 -0
- /package/agent/{skills/red-teaming → optional-skills/security}/godmode/scripts/parseltongue.py +0 -0
- /package/agent/{skills/red-teaming → optional-skills/security}/godmode/templates/prefill-subtle.json +0 -0
- /package/agent/{skills/red-teaming → optional-skills/security}/godmode/templates/prefill.json +0 -0
- /package/agent/{skills → optional-skills}/software-development/subagent-driven-development/references/context-budget-discipline.md +0 -0
- /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
|
-
@
|
|
2160
|
+
@classmethod
|
|
2069
2161
|
def _observe_group_message(
|
|
2162
|
+
cls,
|
|
2070
2163
|
adapter, source, sender_display: str, text: str,
|
|
2071
|
-
*,
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
2181
|
-
|
|
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
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
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
|
-
|
|
2392
|
+
name = "forwarded-records-parse"
|
|
2221
2393
|
|
|
2222
2394
|
async def handle(self, ctx: InboundContext, next_fn) -> None:
|
|
2223
|
-
|
|
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=
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
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
|
-
|
|
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
|
|
2548
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
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=
|
|
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
|
-
|
|
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
|
|