@clawpump/claw-agent 0.1.5 → 0.1.7
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 +2177 -3162
- 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/cli_agent_setup_mixin.py +684 -0
- package/agent/hermes_cli/cli_commands_mixin.py +2293 -0
- package/agent/hermes_cli/commands.py +215 -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/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 +6190 -973
- 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 +4 -2
- package/agent/optional-mcps/clawpump-stdio/manifest.yaml +2 -0
- 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 +4 -1
- 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 +300 -684
- 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
package/agent/cron/scheduler.py
CHANGED
|
@@ -9,15 +9,17 @@ runs at a time if multiple processes overlap.
|
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
import asyncio
|
|
12
|
+
import atexit
|
|
12
13
|
import concurrent.futures
|
|
13
14
|
import contextvars
|
|
14
15
|
import json
|
|
15
16
|
import logging
|
|
16
17
|
import os
|
|
18
|
+
import re
|
|
17
19
|
import shutil
|
|
18
20
|
import subprocess
|
|
19
21
|
import sys
|
|
20
|
-
|
|
22
|
+
import threading
|
|
21
23
|
|
|
22
24
|
# fcntl is Unix-only; on Windows use msvcrt for file locking
|
|
23
25
|
try:
|
|
@@ -44,6 +46,59 @@ from hermes_time import now as _hermes_now
|
|
|
44
46
|
logger = logging.getLogger(__name__)
|
|
45
47
|
|
|
46
48
|
|
|
49
|
+
def _summarize_cron_failure_for_delivery(job: dict, error: str | None) -> str:
|
|
50
|
+
"""Return a compact one-line failure message for chat delivery.
|
|
51
|
+
|
|
52
|
+
Full details stay in the cron output directory and the logs. Chat should
|
|
53
|
+
show the operator what broke without dumping provider JSON, retry noise, or
|
|
54
|
+
stack traces into the delivery channel.
|
|
55
|
+
"""
|
|
56
|
+
job_name = job.get("name") or job.get("id") or "cron job"
|
|
57
|
+
text = (error or "unknown error").strip()
|
|
58
|
+
lower = text.lower()
|
|
59
|
+
|
|
60
|
+
# Provider/API failures are the common noisy path. Keep these short.
|
|
61
|
+
if "429" in text or "rate limit" in lower or "usage limit" in lower:
|
|
62
|
+
reason = "rate limit"
|
|
63
|
+
if "weekly usage limit" in lower:
|
|
64
|
+
reason = "weekly usage limit"
|
|
65
|
+
elif "quota" in lower:
|
|
66
|
+
reason = "quota limit"
|
|
67
|
+
return (
|
|
68
|
+
f"⚠️ Cron '{job_name}' failed: provider {reason}. "
|
|
69
|
+
"Fallback chain was exhausted or unavailable. "
|
|
70
|
+
"Full details saved in cron output."
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
if "readtimeout" in lower or "timed out" in lower or "timeout" in lower:
|
|
74
|
+
return (
|
|
75
|
+
f"⚠️ Cron '{job_name}' failed: provider timeout. "
|
|
76
|
+
"Fallback chain was exhausted or unavailable. "
|
|
77
|
+
"Full details saved in cron output."
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# Match authentication/authorization wording at a word boundary and the
|
|
81
|
+
# 401/403 status codes as whole tokens, so "oauth", "4015" and similar do
|
|
82
|
+
# not trip a misleading auth message.
|
|
83
|
+
if re.search(r"authenticat|authoriz", lower) or re.search(r"\b(401|403)\b", text):
|
|
84
|
+
return (
|
|
85
|
+
f"⚠️ Cron '{job_name}' failed: provider authentication error. "
|
|
86
|
+
"Full details saved in cron output."
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# Strip common exception wrappers and collapse provider payloads. Bound
|
|
90
|
+
# the input first so a multi-KB provider blob cannot slow the
|
|
91
|
+
# substitutions.
|
|
92
|
+
cleaned = re.sub(
|
|
93
|
+
r"^(RuntimeError|Exception|ValueError|HTTPStatusError):\s*",
|
|
94
|
+
"", text[:2000],
|
|
95
|
+
)
|
|
96
|
+
cleaned = re.sub(r"\s+", " ", cleaned).strip()
|
|
97
|
+
if len(cleaned) > 180:
|
|
98
|
+
cleaned = cleaned[:177].rstrip() + "..."
|
|
99
|
+
return f"⚠️ Cron '{job_name}' failed: {cleaned}"
|
|
100
|
+
|
|
101
|
+
|
|
47
102
|
class CronPromptInjectionBlocked(Exception):
|
|
48
103
|
"""Raised by _build_job_prompt when the fully-assembled prompt trips the
|
|
49
104
|
injection scanner. Caught in run_job so the operator sees a clean
|
|
@@ -137,6 +192,7 @@ _HOME_TARGET_ENV_VARS = {
|
|
|
137
192
|
"bluebubbles": "BLUEBUBBLES_HOME_CHANNEL",
|
|
138
193
|
"qqbot": "QQBOT_HOME_CHANNEL",
|
|
139
194
|
"whatsapp": "WHATSAPP_HOME_CHANNEL",
|
|
195
|
+
"whatsapp_cloud": "WHATSAPP_CLOUD_HOME_CHANNEL",
|
|
140
196
|
}
|
|
141
197
|
|
|
142
198
|
# Legacy env var names kept for back-compat. Each entry is the current
|
|
@@ -154,6 +210,69 @@ from cron.jobs import get_due_jobs, mark_job_run, save_job_output, advance_next_
|
|
|
154
210
|
# locally for audit.
|
|
155
211
|
SILENT_MARKER = "[SILENT]"
|
|
156
212
|
|
|
213
|
+
# ---------------------------------------------------------------------------
|
|
214
|
+
# Persistent thread pool for parallel cron jobs.
|
|
215
|
+
# The tick function submits jobs here and returns immediately so the ticker
|
|
216
|
+
# thread is never blocked by long-running jobs (e.g. the fixer running 15+ min).
|
|
217
|
+
# ---------------------------------------------------------------------------
|
|
218
|
+
_parallel_pool: Optional[concurrent.futures.ThreadPoolExecutor] = None
|
|
219
|
+
_parallel_pool_max_workers: Optional[int] = None
|
|
220
|
+
_running_job_ids: set = set()
|
|
221
|
+
_running_lock = threading.Lock()
|
|
222
|
+
|
|
223
|
+
# Sequential (env-mutating) cron jobs — workdir jobs that touch
|
|
224
|
+
# process-global runtime state — must run one at a time, but must NOT block the
|
|
225
|
+
# ticker thread. A persistent single-thread executor preserves ordering across
|
|
226
|
+
# ticks while keeping dispatch fire-and-forget, the same as the parallel pool.
|
|
227
|
+
_sequential_pool: Optional[concurrent.futures.ThreadPoolExecutor] = None
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _get_parallel_pool(max_workers: Optional[int]) -> concurrent.futures.ThreadPoolExecutor:
|
|
231
|
+
"""Return (or create) the persistent parallel pool."""
|
|
232
|
+
global _parallel_pool, _parallel_pool_max_workers
|
|
233
|
+
if _parallel_pool is None or _parallel_pool_max_workers != max_workers:
|
|
234
|
+
if _parallel_pool is not None:
|
|
235
|
+
_parallel_pool.shutdown(wait=False, cancel_futures=False)
|
|
236
|
+
_parallel_pool = concurrent.futures.ThreadPoolExecutor(
|
|
237
|
+
max_workers=max_workers,
|
|
238
|
+
thread_name_prefix="cron-parallel",
|
|
239
|
+
)
|
|
240
|
+
_parallel_pool_max_workers = max_workers
|
|
241
|
+
return _parallel_pool
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _get_sequential_pool() -> concurrent.futures.ThreadPoolExecutor:
|
|
245
|
+
"""Return (or create) the persistent single-thread sequential pool.
|
|
246
|
+
|
|
247
|
+
A single worker guarantees env-mutating jobs never overlap, even
|
|
248
|
+
across ticks: a job queued by a newer tick waits for the previous tick's
|
|
249
|
+
sequential jobs to finish rather than corrupting their os.environ
|
|
250
|
+
state.
|
|
251
|
+
"""
|
|
252
|
+
global _sequential_pool
|
|
253
|
+
if _sequential_pool is None:
|
|
254
|
+
_sequential_pool = concurrent.futures.ThreadPoolExecutor(
|
|
255
|
+
max_workers=1,
|
|
256
|
+
thread_name_prefix="cron-seq",
|
|
257
|
+
)
|
|
258
|
+
return _sequential_pool
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _shutdown_parallel_pool() -> None:
|
|
262
|
+
"""Shut down the persistent pools on process exit."""
|
|
263
|
+
global _parallel_pool, _parallel_pool_max_workers, _sequential_pool
|
|
264
|
+
if _parallel_pool is not None:
|
|
265
|
+
_parallel_pool.shutdown(wait=True, cancel_futures=False)
|
|
266
|
+
_parallel_pool = None
|
|
267
|
+
_parallel_pool_max_workers = None
|
|
268
|
+
if _sequential_pool is not None:
|
|
269
|
+
_sequential_pool.shutdown(wait=True, cancel_futures=False)
|
|
270
|
+
_sequential_pool = None
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
atexit.register(_shutdown_parallel_pool)
|
|
274
|
+
|
|
275
|
+
|
|
157
276
|
# Backward-compatible module override used by tests and emergency monkeypatches.
|
|
158
277
|
_hermes_home: Path | None = None
|
|
159
278
|
|
|
@@ -170,71 +289,6 @@ def _get_lock_paths() -> tuple[Path, Path]:
|
|
|
170
289
|
return lock_dir, lock_dir / ".tick.lock"
|
|
171
290
|
|
|
172
291
|
|
|
173
|
-
@contextmanager
|
|
174
|
-
def _job_profile_context(job_id: str, profile: Optional[str]):
|
|
175
|
-
"""Temporarily run a job under a specific Hermes profile.
|
|
176
|
-
|
|
177
|
-
Cron jobs are stored and scheduled by the profile running the scheduler, but
|
|
178
|
-
an individual job can opt into a different runtime profile. While active,
|
|
179
|
-
the scheduler's test/override hook and a context-local Hermes home override
|
|
180
|
-
both point at the resolved profile directory so _get_hermes_home(),
|
|
181
|
-
.env/config loading, script resolution, AIAgent construction, and downstream
|
|
182
|
-
get_hermes_home() callers agree on the same home.
|
|
183
|
-
|
|
184
|
-
Some existing provider/config paths still load profile .env values through
|
|
185
|
-
os.environ, so profile jobs also snapshot and restore the process
|
|
186
|
-
environment on exit. tick() runs profile jobs sequentially to keep that
|
|
187
|
-
temporary mutation isolated from other scheduled jobs.
|
|
188
|
-
"""
|
|
189
|
-
raw_profile = str(profile or "").strip()
|
|
190
|
-
if not raw_profile:
|
|
191
|
-
yield None
|
|
192
|
-
return
|
|
193
|
-
|
|
194
|
-
global _hermes_home
|
|
195
|
-
prior_override = _hermes_home
|
|
196
|
-
env_snapshot = os.environ.copy()
|
|
197
|
-
|
|
198
|
-
from hermes_cli.profiles import normalize_profile_name, resolve_profile_env
|
|
199
|
-
from hermes_constants import reset_hermes_home_override, set_hermes_home_override
|
|
200
|
-
|
|
201
|
-
normalized_profile = normalize_profile_name(raw_profile)
|
|
202
|
-
try:
|
|
203
|
-
profile_home = Path(resolve_profile_env(normalized_profile)).resolve()
|
|
204
|
-
except (FileNotFoundError, ValueError) as exc:
|
|
205
|
-
logger.warning(
|
|
206
|
-
"Job '%s': configured profile %r no longer valid (%s) — "
|
|
207
|
-
"falling back to scheduler default",
|
|
208
|
-
job_id, raw_profile, exc,
|
|
209
|
-
)
|
|
210
|
-
yield None
|
|
211
|
-
return
|
|
212
|
-
|
|
213
|
-
override_token = None
|
|
214
|
-
try:
|
|
215
|
-
override_token = set_hermes_home_override(profile_home)
|
|
216
|
-
_hermes_home = profile_home
|
|
217
|
-
logger.info(
|
|
218
|
-
"Job '%s': using Hermes profile '%s' (%s)",
|
|
219
|
-
job_id,
|
|
220
|
-
normalized_profile,
|
|
221
|
-
profile_home,
|
|
222
|
-
)
|
|
223
|
-
yield normalized_profile
|
|
224
|
-
finally:
|
|
225
|
-
_hermes_home = prior_override
|
|
226
|
-
if override_token is not None:
|
|
227
|
-
reset_hermes_home_override(override_token)
|
|
228
|
-
# Delta-based restore: remove added keys, restore changed keys.
|
|
229
|
-
# Avoids a brief window where other threads see an empty env.
|
|
230
|
-
added = set(os.environ.keys()) - set(env_snapshot.keys())
|
|
231
|
-
for k in added:
|
|
232
|
-
os.environ.pop(k, None)
|
|
233
|
-
for k, v in env_snapshot.items():
|
|
234
|
-
if os.environ.get(k) != v:
|
|
235
|
-
os.environ[k] = v
|
|
236
|
-
|
|
237
|
-
|
|
238
292
|
def _resolve_origin(job: dict) -> Optional[dict]:
|
|
239
293
|
"""Extract origin info from a job, preserving any extra routing metadata.
|
|
240
294
|
|
|
@@ -383,6 +437,47 @@ def _iter_home_target_platforms():
|
|
|
383
437
|
pass
|
|
384
438
|
|
|
385
439
|
|
|
440
|
+
def cron_delivery_targets() -> list[dict]:
|
|
441
|
+
"""Return the platforms a cron job can auto-deliver to.
|
|
442
|
+
|
|
443
|
+
Single source of truth for any UI (dashboard dropdown, etc.) that lets a
|
|
444
|
+
user pick a cron delivery target. A platform is included when it is a valid
|
|
445
|
+
cron delivery platform AND its gateway is configured (enabled + credentials
|
|
446
|
+
present). Each entry reports whether the platform's home target (the
|
|
447
|
+
room/channel cron posts to) is set — a platform can be configured for
|
|
448
|
+
interactive use but still lack the home target an unattended cron job needs.
|
|
449
|
+
|
|
450
|
+
Returns a list of dicts: ``{"id", "name", "home_target_set", "home_env_var"}``
|
|
451
|
+
ordered by the gateway's canonical platform order. Callers should always
|
|
452
|
+
prepend the implicit ``local`` option themselves — it needs no config.
|
|
453
|
+
"""
|
|
454
|
+
targets: list[dict] = []
|
|
455
|
+
try:
|
|
456
|
+
from gateway.config import load_gateway_config
|
|
457
|
+
|
|
458
|
+
gateway_config = load_gateway_config()
|
|
459
|
+
connected = {p.value for p in gateway_config.get_connected_platforms()}
|
|
460
|
+
except Exception:
|
|
461
|
+
logger.debug("cron_delivery_targets: gateway config unavailable", exc_info=True)
|
|
462
|
+
connected = set()
|
|
463
|
+
|
|
464
|
+
for name in _iter_home_target_platforms():
|
|
465
|
+
if name not in connected:
|
|
466
|
+
continue
|
|
467
|
+
if not _is_known_delivery_platform(name):
|
|
468
|
+
continue
|
|
469
|
+
env_var = _resolve_home_env_var(name)
|
|
470
|
+
targets.append(
|
|
471
|
+
{
|
|
472
|
+
"id": name,
|
|
473
|
+
"name": name.replace("_", " ").title(),
|
|
474
|
+
"home_target_set": bool(_get_home_target_chat_id(name)),
|
|
475
|
+
"home_env_var": env_var or None,
|
|
476
|
+
}
|
|
477
|
+
)
|
|
478
|
+
return targets
|
|
479
|
+
|
|
480
|
+
|
|
386
481
|
def _resolve_single_delivery_target(job: dict, deliver_value: str) -> Optional[dict]:
|
|
387
482
|
"""Resolve one concrete auto-delivery target for a cron job."""
|
|
388
483
|
|
|
@@ -866,6 +961,10 @@ def _run_job_script(script_path: str) -> tuple[bool, str]:
|
|
|
866
961
|
Shell support lets ``no_agent=True`` jobs ship classic bash watchdogs
|
|
867
962
|
(the `memory-watchdog.sh` pattern) without wrapping them in Python.
|
|
868
963
|
|
|
964
|
+
Subprocess environment is passed through ``_sanitize_subprocess_env`` so
|
|
965
|
+
provider credentials and other Hermes-managed secrets are not inherited
|
|
966
|
+
(SECURITY.md §2.3), matching terminal and MCP child processes.
|
|
967
|
+
|
|
869
968
|
Args:
|
|
870
969
|
script_path: Path to the script. Relative paths are resolved
|
|
871
970
|
against HERMES_HOME/scripts/. Absolute and ~-prefixed paths
|
|
@@ -926,18 +1025,9 @@ def _run_job_script(script_path: str) -> tuple[bool, str]:
|
|
|
926
1025
|
else:
|
|
927
1026
|
argv = [sys.executable, str(path)]
|
|
928
1027
|
|
|
929
|
-
run_env = os.environ.copy()
|
|
930
|
-
run_env["HERMES_HOME"] = str(_get_hermes_home())
|
|
931
1028
|
try:
|
|
932
|
-
from
|
|
1029
|
+
from tools.environments.local import _sanitize_subprocess_env
|
|
933
1030
|
|
|
934
|
-
profile_home = get_subprocess_home()
|
|
935
|
-
if profile_home:
|
|
936
|
-
run_env["HOME"] = profile_home
|
|
937
|
-
except Exception:
|
|
938
|
-
pass
|
|
939
|
-
|
|
940
|
-
try:
|
|
941
1031
|
popen_kwargs = {"creationflags": windows_hide_flags()} if sys.platform == "win32" else {}
|
|
942
1032
|
result = subprocess.run(
|
|
943
1033
|
argv,
|
|
@@ -945,7 +1035,7 @@ def _run_job_script(script_path: str) -> tuple[bool, str]:
|
|
|
945
1035
|
text=True,
|
|
946
1036
|
timeout=script_timeout,
|
|
947
1037
|
cwd=str(path.parent),
|
|
948
|
-
env=
|
|
1038
|
+
env=_sanitize_subprocess_env(os.environ.copy()),
|
|
949
1039
|
**popen_kwargs,
|
|
950
1040
|
)
|
|
951
1041
|
stdout = (result.stdout or "").strip()
|
|
@@ -1012,8 +1102,15 @@ def _build_job_prompt(job: dict, prerun_script: Optional[tuple] = None) -> str:
|
|
|
1012
1102
|
result is used for prompt injection. When omitted, the script
|
|
1013
1103
|
(if any) runs inline as before.
|
|
1014
1104
|
"""
|
|
1015
|
-
|
|
1105
|
+
user_prompt = str(job.get("prompt") or "")
|
|
1106
|
+
prompt = user_prompt
|
|
1016
1107
|
skills = job.get("skills")
|
|
1108
|
+
# True when runtime-collected DATA (script stdout, upstream-job output)
|
|
1109
|
+
# has been injected into the prompt. Data content legitimately quotes
|
|
1110
|
+
# command-shape strings (a triage feed ingesting a bug report that
|
|
1111
|
+
# pastes `rm -rf /`), so it must not be scanned with the strict
|
|
1112
|
+
# user-prompt pattern set — see _scan_assembled_cron_prompt.
|
|
1113
|
+
has_injected_data = False
|
|
1017
1114
|
|
|
1018
1115
|
# Run data-collection script if configured, inject output as context.
|
|
1019
1116
|
script_path = job.get("script")
|
|
@@ -1031,6 +1128,7 @@ def _build_job_prompt(job: dict, prerun_script: Optional[tuple] = None) -> str:
|
|
|
1031
1128
|
f"```\n{script_output}\n```\n\n"
|
|
1032
1129
|
f"{prompt}"
|
|
1033
1130
|
)
|
|
1131
|
+
has_injected_data = True
|
|
1034
1132
|
else:
|
|
1035
1133
|
# Script produced no output — nothing to report, skip AI call.
|
|
1036
1134
|
return None
|
|
@@ -1041,6 +1139,7 @@ def _build_job_prompt(job: dict, prerun_script: Optional[tuple] = None) -> str:
|
|
|
1041
1139
|
f"```\n{script_output}\n```\n\n"
|
|
1042
1140
|
f"{prompt}"
|
|
1043
1141
|
)
|
|
1142
|
+
has_injected_data = True
|
|
1044
1143
|
|
|
1045
1144
|
# Inject output from referenced cron jobs as context.
|
|
1046
1145
|
context_from = job.get("context_from")
|
|
@@ -1083,6 +1182,7 @@ def _build_job_prompt(job: dict, prerun_script: Optional[tuple] = None) -> str:
|
|
|
1083
1182
|
f"```\n{latest_output}\n```\n\n"
|
|
1084
1183
|
f"{prompt}"
|
|
1085
1184
|
)
|
|
1185
|
+
has_injected_data = True
|
|
1086
1186
|
else:
|
|
1087
1187
|
continue # silent skip — empty output
|
|
1088
1188
|
except (OSError, PermissionError) as e:
|
|
@@ -1111,14 +1211,46 @@ def _build_job_prompt(job: dict, prerun_script: Optional[tuple] = None) -> str:
|
|
|
1111
1211
|
|
|
1112
1212
|
skill_names = [str(name).strip() for name in skills if str(name).strip()]
|
|
1113
1213
|
if not skill_names:
|
|
1114
|
-
return _scan_assembled_cron_prompt(
|
|
1214
|
+
return _scan_assembled_cron_prompt(
|
|
1215
|
+
prompt,
|
|
1216
|
+
job,
|
|
1217
|
+
has_skills=False,
|
|
1218
|
+
has_injected_data=has_injected_data,
|
|
1219
|
+
user_prompt=user_prompt,
|
|
1220
|
+
)
|
|
1115
1221
|
|
|
1116
1222
|
from tools.skills_tool import skill_view
|
|
1117
1223
|
from tools.skill_usage import bump_use
|
|
1224
|
+
from agent.skill_bundles import build_bundle_invocation_message, resolve_bundle_command_key
|
|
1118
1225
|
|
|
1119
1226
|
parts = []
|
|
1120
1227
|
skipped: list[str] = []
|
|
1121
1228
|
for skill_name in skill_names:
|
|
1229
|
+
# Cron jobs historically accepted only skill names here, but the CLI/gateway
|
|
1230
|
+
# slash-command path lets bundles shadow skills with the same slug. Mirror
|
|
1231
|
+
# that behavior so `skills: ["my-bundle"]` expands bundle members instead
|
|
1232
|
+
# of being treated as a missing skill.
|
|
1233
|
+
bundle_key = resolve_bundle_command_key(skill_name.lstrip("/"))
|
|
1234
|
+
if bundle_key:
|
|
1235
|
+
bundle_payload = build_bundle_invocation_message(
|
|
1236
|
+
bundle_key,
|
|
1237
|
+
user_instruction="",
|
|
1238
|
+
task_id=str(job.get("id") or "") or None,
|
|
1239
|
+
)
|
|
1240
|
+
if bundle_payload:
|
|
1241
|
+
bundle_message, _loaded_bundle_skills, _missing_bundle_skills = bundle_payload
|
|
1242
|
+
if parts:
|
|
1243
|
+
parts.append("")
|
|
1244
|
+
parts.append(bundle_message)
|
|
1245
|
+
continue
|
|
1246
|
+
logger.warning(
|
|
1247
|
+
"Cron job '%s': bundle '%s' could not load any skills, skipping",
|
|
1248
|
+
job.get("name", job.get("id")),
|
|
1249
|
+
skill_name,
|
|
1250
|
+
)
|
|
1251
|
+
skipped.append(skill_name)
|
|
1252
|
+
continue
|
|
1253
|
+
|
|
1122
1254
|
try:
|
|
1123
1255
|
loaded = json.loads(skill_view(skill_name))
|
|
1124
1256
|
except (json.JSONDecodeError, TypeError):
|
|
@@ -1162,7 +1294,14 @@ def _build_job_prompt(job: dict, prerun_script: Optional[tuple] = None) -> str:
|
|
|
1162
1294
|
return _scan_assembled_cron_prompt("\n".join(parts), job, has_skills=True)
|
|
1163
1295
|
|
|
1164
1296
|
|
|
1165
|
-
def _scan_assembled_cron_prompt(
|
|
1297
|
+
def _scan_assembled_cron_prompt(
|
|
1298
|
+
assembled: str,
|
|
1299
|
+
job: dict,
|
|
1300
|
+
*,
|
|
1301
|
+
has_skills: bool = False,
|
|
1302
|
+
has_injected_data: bool = False,
|
|
1303
|
+
user_prompt: Optional[str] = None,
|
|
1304
|
+
) -> str:
|
|
1166
1305
|
"""Scan the fully-assembled cron prompt for injection patterns. Raises
|
|
1167
1306
|
``CronPromptInjectionBlocked`` when a match fires so ``run_job`` can
|
|
1168
1307
|
surface a clear refusal to the operator.
|
|
@@ -1173,29 +1312,45 @@ def _scan_assembled_cron_prompt(assembled: str, job: dict, *, has_skills: bool =
|
|
|
1173
1312
|
(auto-approves tool calls), a malicious skill carrying an injection
|
|
1174
1313
|
payload bypassed every gate.
|
|
1175
1314
|
|
|
1176
|
-
Two pattern tiers
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
``_scan_cron_prompt``
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1315
|
+
Two pattern tiers, selected by what the assembled prompt CONTAINS,
|
|
1316
|
+
not just whether skills are attached:
|
|
1317
|
+
|
|
1318
|
+
- When the assembled prompt is essentially the user prompt + the cron
|
|
1319
|
+
hint (no skills, no injected data), the STRICT ``_scan_cron_prompt``
|
|
1320
|
+
patterns apply: a bare ``rm -rf /`` in a small directive prompt is a
|
|
1321
|
+
smoking gun, not prose.
|
|
1322
|
+
- When the assembled prompt includes runtime-loaded content — skill
|
|
1323
|
+
markdown (``has_skills=True``) or DATA injected from a job script's
|
|
1324
|
+
stdout / an upstream job's output (``has_injected_data=True``) — the
|
|
1325
|
+
LOOSER ``_scan_cron_skill_assembled`` pattern set is used: only
|
|
1326
|
+
unambiguous prompt-injection directives block; command-shape
|
|
1327
|
+
patterns are dropped and invisible unicode is sanitized (stripped +
|
|
1328
|
+
logged) rather than blocked, to avoid false-positives that
|
|
1329
|
+
permanently kill a job. Skill bodies are vetted at install time by
|
|
1330
|
+
``skills_guard.py``; script output is produced by operator-authored
|
|
1331
|
+
code, the same trust class — and data feeds (e.g. a triage bot
|
|
1332
|
+
ingesting bug reports) legitimately quote dangerous commands.
|
|
1333
|
+
|
|
1334
|
+
When the looser tier is selected because of injected data only,
|
|
1335
|
+
``user_prompt`` (the raw, pre-assembly prompt) is additionally scanned
|
|
1336
|
+
with the STRICT set so the user-authored surface keeps the full
|
|
1337
|
+
create/update-time guarantee at runtime (defense-in-depth for legacy
|
|
1338
|
+
jobs that predate the create-time scanner).
|
|
1189
1339
|
"""
|
|
1190
1340
|
from tools.cronjob_tools import _scan_cron_prompt, _scan_cron_skill_assembled
|
|
1191
1341
|
|
|
1192
|
-
if has_skills:
|
|
1193
|
-
#
|
|
1194
|
-
#
|
|
1195
|
-
#
|
|
1342
|
+
if has_skills or has_injected_data:
|
|
1343
|
+
# Runtime-loaded content (vetted skill markdown and/or data from
|
|
1344
|
+
# operator-authored scripts) legitimately contains command-shape
|
|
1345
|
+
# strings. Invisible unicode is sanitized (not blocked) so a stray
|
|
1346
|
+
# zero-width space can't permanently kill the job; the cleaned
|
|
1196
1347
|
# prompt is what actually runs.
|
|
1197
1348
|
cleaned, scan_error = _scan_cron_skill_assembled(assembled)
|
|
1198
1349
|
assembled = cleaned
|
|
1350
|
+
if not scan_error and not has_skills and user_prompt:
|
|
1351
|
+
# Data-injection path: keep the strict guarantee on the
|
|
1352
|
+
# user-authored prompt itself.
|
|
1353
|
+
scan_error = _scan_cron_prompt(user_prompt)
|
|
1199
1354
|
else:
|
|
1200
1355
|
scan_error = _scan_cron_prompt(assembled)
|
|
1201
1356
|
if scan_error:
|
|
@@ -1210,13 +1365,6 @@ def _scan_assembled_cron_prompt(assembled: str, job: dict, *, has_skills: bool =
|
|
|
1210
1365
|
|
|
1211
1366
|
|
|
1212
1367
|
def run_job(job: dict) -> tuple[bool, str, str, Optional[str]]:
|
|
1213
|
-
"""Execute a single cron job, applying any per-job profile override."""
|
|
1214
|
-
job_id = job["id"]
|
|
1215
|
-
with _job_profile_context(job_id, job.get("profile")):
|
|
1216
|
-
return _run_job_impl(job)
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
def _run_job_impl(job: dict) -> tuple[bool, str, str, Optional[str]]:
|
|
1220
1368
|
"""
|
|
1221
1369
|
Execute a single cron job.
|
|
1222
1370
|
|
|
@@ -1453,9 +1601,8 @@ def _run_job_impl(job: dict) -> tuple[bool, str, str, Optional[str]]:
|
|
|
1453
1601
|
# .cursorrules from the job's project dir, AND
|
|
1454
1602
|
# - the terminal, file, and code-exec tools run commands from there.
|
|
1455
1603
|
#
|
|
1456
|
-
# tick() serializes jobs
|
|
1457
|
-
#
|
|
1458
|
-
# os.environ["TERMINAL_CWD"] here is safe for those jobs. For workdir-less
|
|
1604
|
+
# tick() serializes workdir-jobs outside the parallel pool, so mutating
|
|
1605
|
+
# os.environ["TERMINAL_CWD"] here is safe for those jobs. For workdir-less
|
|
1459
1606
|
# jobs we leave TERMINAL_CWD untouched — preserves the original behaviour
|
|
1460
1607
|
# (skip_context_files=True, tools use whatever cwd the scheduler has).
|
|
1461
1608
|
_job_workdir = (job.get("workdir") or "").strip() or None
|
|
@@ -1501,6 +1648,15 @@ def _run_job_impl(job: dict) -> tuple[bool, str, str, Optional[str]]:
|
|
|
1501
1648
|
if os.path.exists(_cfg_path):
|
|
1502
1649
|
with open(_cfg_path, encoding="utf-8") as _f:
|
|
1503
1650
|
_cfg = yaml.safe_load(_f) or {}
|
|
1651
|
+
# Managed scope: a scheduled job must honor administrator-pinned
|
|
1652
|
+
# model / reasoning / toolsets / provider_routing too. This loader
|
|
1653
|
+
# builds its own dict, so overlay managed values via the shared
|
|
1654
|
+
# helper (fail-open, no-op when no managed scope).
|
|
1655
|
+
try:
|
|
1656
|
+
from hermes_cli import managed_scope
|
|
1657
|
+
_cfg = managed_scope.apply_managed_overlay(_cfg)
|
|
1658
|
+
except Exception:
|
|
1659
|
+
pass
|
|
1504
1660
|
_cfg = _expand_env_vars(_cfg)
|
|
1505
1661
|
_model_cfg = _cfg.get("model", {})
|
|
1506
1662
|
if not job.get("model"):
|
|
@@ -1525,9 +1681,16 @@ def _run_job_impl(job: dict) -> tuple[bool, str, str, Optional[str]]:
|
|
|
1525
1681
|
effort = str(_cfg.get("agent", {}).get("reasoning_effort", "")).strip()
|
|
1526
1682
|
reasoning_config = parse_reasoning_effort(effort)
|
|
1527
1683
|
|
|
1528
|
-
# Prefill messages from env or config.yaml
|
|
1684
|
+
# Prefill messages from env or config.yaml. The top-level
|
|
1685
|
+
# prefill_messages_file key is canonical; agent.prefill_messages_file is
|
|
1686
|
+
# retained as a legacy fallback for older CLI/godmode configs.
|
|
1529
1687
|
prefill_messages = None
|
|
1530
|
-
|
|
1688
|
+
agent_cfg = _cfg.get("agent", {}) if isinstance(_cfg.get("agent", {}), dict) else {}
|
|
1689
|
+
prefill_file = (
|
|
1690
|
+
os.getenv("HERMES_PREFILL_MESSAGES_FILE", "")
|
|
1691
|
+
or _cfg.get("prefill_messages_file", "")
|
|
1692
|
+
or agent_cfg.get("prefill_messages_file", "")
|
|
1693
|
+
)
|
|
1531
1694
|
if prefill_file:
|
|
1532
1695
|
pfpath = Path(prefill_file).expanduser()
|
|
1533
1696
|
if not pfpath.is_absolute():
|
|
@@ -1834,6 +1997,18 @@ def _run_job_impl(job: dict) -> tuple[bool, str, str, Optional[str]]:
|
|
|
1834
1997
|
for _var_name in _cron_delivery_vars:
|
|
1835
1998
|
_VAR_MAP[_var_name].set("")
|
|
1836
1999
|
if _session_db:
|
|
2000
|
+
# Title the cron session from the job (name → short prompt → id) so
|
|
2001
|
+
# sidebars/history show a meaningful label instead of the injected
|
|
2002
|
+
# "[IMPORTANT: …]" hint that is the session's first message. Set here
|
|
2003
|
+
# (not at create time) so the agent's own INSERT keeps model /
|
|
2004
|
+
# system_prompt; this only UPDATEs the title column. The run-time
|
|
2005
|
+
# suffix keeps it unique against the sessions.title index across runs.
|
|
2006
|
+
try:
|
|
2007
|
+
_title_base = " ".join(job_name.split())[:60].strip() or f"cron {job_id}"
|
|
2008
|
+
_cron_title = f"{_title_base} · {_hermes_now().strftime('%b %d %H:%M')}"
|
|
2009
|
+
_session_db.set_session_title(_cron_session_id, _cron_title)
|
|
2010
|
+
except (Exception, KeyboardInterrupt) as e:
|
|
2011
|
+
logger.debug("Job '%s': failed to set cron session title: %s", job_id, e)
|
|
1837
2012
|
try:
|
|
1838
2013
|
_session_db.end_session(_cron_session_id, "cron_complete")
|
|
1839
2014
|
except (Exception, KeyboardInterrupt) as e:
|
|
@@ -1862,7 +2037,83 @@ def _run_job_impl(job: dict) -> tuple[bool, str, str, Optional[str]]:
|
|
|
1862
2037
|
logger.debug("Job '%s': failed to reap stale auxiliary clients: %s", job_id, e)
|
|
1863
2038
|
|
|
1864
2039
|
|
|
1865
|
-
def
|
|
2040
|
+
def run_one_job(job: dict, *, adapters=None, loop=None, verbose: bool = False) -> bool:
|
|
2041
|
+
"""Run ONE due job end-to-end: execute → save output → deliver → mark.
|
|
2042
|
+
|
|
2043
|
+
This is the shared firing body extracted from ``tick``'s per-job closure so
|
|
2044
|
+
that BOTH the built-in ticker and an external provider's ``fire_due`` (e.g.
|
|
2045
|
+
Chronos) run the identical sequence — no duplicated correctness.
|
|
2046
|
+
|
|
2047
|
+
It does NOT decide whether the job is due, claim it, or compute the next
|
|
2048
|
+
run — those are the caller's concern (``tick`` advances ``next_run_at``
|
|
2049
|
+
under the file lock before dispatch; an external provider claims via the
|
|
2050
|
+
store CAS). This function only fires the given job once.
|
|
2051
|
+
|
|
2052
|
+
Returns True if the job was processed (even if the job itself failed —
|
|
2053
|
+
failure is recorded via ``mark_job_run``), False only if processing raised.
|
|
2054
|
+
"""
|
|
2055
|
+
try:
|
|
2056
|
+
success, output, final_response, error = run_job(job)
|
|
2057
|
+
|
|
2058
|
+
output_file = save_job_output(job["id"], output)
|
|
2059
|
+
if verbose:
|
|
2060
|
+
logger.info("Output saved to: %s", output_file)
|
|
2061
|
+
|
|
2062
|
+
# Deliver the final response to the origin/target chat.
|
|
2063
|
+
# If the agent responded with [SILENT], skip delivery (but
|
|
2064
|
+
# output is already saved above). Failed jobs always deliver.
|
|
2065
|
+
deliver_content = final_response if success else _summarize_cron_failure_for_delivery(job, error)
|
|
2066
|
+
# Treat whitespace-only final responses the same as empty
|
|
2067
|
+
# responses: do not deliver a blank message, and let the
|
|
2068
|
+
# empty-response guard below mark the run as a soft failure.
|
|
2069
|
+
should_deliver = bool(deliver_content.strip())
|
|
2070
|
+
if should_deliver and success and SILENT_MARKER in deliver_content.strip().upper():
|
|
2071
|
+
logger.info("Job '%s': agent returned %s — skipping delivery", job["id"], SILENT_MARKER)
|
|
2072
|
+
should_deliver = False
|
|
2073
|
+
|
|
2074
|
+
delivery_error = None
|
|
2075
|
+
if should_deliver:
|
|
2076
|
+
try:
|
|
2077
|
+
delivery_error = _deliver_result(job, deliver_content, adapters=adapters, loop=loop)
|
|
2078
|
+
except Exception as de:
|
|
2079
|
+
delivery_error = str(de)
|
|
2080
|
+
logger.error("Delivery failed for job %s: %s", job["id"], de)
|
|
2081
|
+
|
|
2082
|
+
# Treat empty final_response as a soft failure so last_status
|
|
2083
|
+
# is not "ok" — the agent ran but produced nothing useful.
|
|
2084
|
+
# (issue #8585)
|
|
2085
|
+
if success and not final_response.strip():
|
|
2086
|
+
success = False
|
|
2087
|
+
error = "Agent completed but produced empty response (model error, timeout, or misconfiguration)"
|
|
2088
|
+
|
|
2089
|
+
mark_job_run(job["id"], success, error, delivery_error=delivery_error)
|
|
2090
|
+
return True
|
|
2091
|
+
|
|
2092
|
+
except Exception as e:
|
|
2093
|
+
logger.error("Error processing job %s: %s", job['id'], e)
|
|
2094
|
+
mark_job_run(job["id"], False, str(e))
|
|
2095
|
+
return False
|
|
2096
|
+
|
|
2097
|
+
|
|
2098
|
+
def _notify_provider_jobs_changed() -> None:
|
|
2099
|
+
"""Best-effort: tell the active scheduler provider the job set changed.
|
|
2100
|
+
|
|
2101
|
+
Called by the consumer surfaces (model tool / CLI / REST) AFTER a
|
|
2102
|
+
successful store mutation (create/update/remove/pause/resume) so an external
|
|
2103
|
+
provider (Chronos) can re-provision/cancel the affected one-shot via NAS.
|
|
2104
|
+
No-op for the built-in (it re-reads jobs.json each tick), so the default
|
|
2105
|
+
path is unchanged. Lives here (not in cron/jobs.py) to keep the store free
|
|
2106
|
+
of provider imports — avoids an import cycle and keeps jobs.py low-coupling.
|
|
2107
|
+
Never raises into the caller.
|
|
2108
|
+
"""
|
|
2109
|
+
try:
|
|
2110
|
+
from cron.scheduler_provider import resolve_cron_scheduler
|
|
2111
|
+
resolve_cron_scheduler().on_jobs_changed()
|
|
2112
|
+
except Exception as e:
|
|
2113
|
+
logger.debug("on_jobs_changed notify failed: %s", e)
|
|
2114
|
+
|
|
2115
|
+
|
|
2116
|
+
def tick(verbose: bool = True, adapters=None, loop=None, sync: bool = True) -> int:
|
|
1866
2117
|
"""
|
|
1867
2118
|
Check and run all due jobs.
|
|
1868
2119
|
|
|
@@ -1906,6 +2157,9 @@ def tick(verbose: bool = True, adapters=None, loop=None) -> int:
|
|
|
1906
2157
|
|
|
1907
2158
|
# Advance next_run_at for all recurring jobs FIRST, under the file lock,
|
|
1908
2159
|
# before any execution begins. This preserves at-most-once semantics.
|
|
2160
|
+
# For parallel jobs that are already running, advance_next_run keeps
|
|
2161
|
+
# bumping next_run_at forward so the grace window never expires.
|
|
2162
|
+
# mark_job_run() overwrites next_run_at on completion.
|
|
1909
2163
|
for job in due_jobs:
|
|
1910
2164
|
advance_next_run(job["id"])
|
|
1911
2165
|
|
|
@@ -1937,96 +2191,117 @@ def tick(verbose: bool = True, adapters=None, loop=None) -> int:
|
|
|
1937
2191
|
)
|
|
1938
2192
|
|
|
1939
2193
|
def _process_job(job: dict) -> bool:
|
|
1940
|
-
"""Run one due job end-to-end
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
# Treat whitespace-only final responses the same as empty
|
|
1953
|
-
# responses: do not deliver a blank message, and let the
|
|
1954
|
-
# empty-response guard below mark the run as a soft failure.
|
|
1955
|
-
should_deliver = bool(deliver_content.strip())
|
|
1956
|
-
if should_deliver and success and SILENT_MARKER in deliver_content.strip().upper():
|
|
1957
|
-
logger.info("Job '%s': agent returned %s — skipping delivery", job["id"], SILENT_MARKER)
|
|
1958
|
-
should_deliver = False
|
|
1959
|
-
|
|
1960
|
-
delivery_error = None
|
|
1961
|
-
if should_deliver:
|
|
1962
|
-
try:
|
|
1963
|
-
delivery_error = _deliver_result(job, deliver_content, adapters=adapters, loop=loop)
|
|
1964
|
-
except Exception as de:
|
|
1965
|
-
delivery_error = str(de)
|
|
1966
|
-
logger.error("Delivery failed for job %s: %s", job["id"], de)
|
|
1967
|
-
|
|
1968
|
-
# Treat empty final_response as a soft failure so last_status
|
|
1969
|
-
# is not "ok" — the agent ran but produced nothing useful.
|
|
1970
|
-
# (issue #8585)
|
|
1971
|
-
if success and not final_response.strip():
|
|
1972
|
-
success = False
|
|
1973
|
-
error = "Agent completed but produced empty response (model error, timeout, or misconfiguration)"
|
|
1974
|
-
|
|
1975
|
-
mark_job_run(job["id"], success, error, delivery_error=delivery_error)
|
|
1976
|
-
return True
|
|
1977
|
-
|
|
1978
|
-
except Exception as e:
|
|
1979
|
-
logger.error("Error processing job %s: %s", job['id'], e)
|
|
1980
|
-
mark_job_run(job["id"], False, str(e))
|
|
1981
|
-
return False
|
|
1982
|
-
|
|
1983
|
-
# Partition due jobs: jobs with a per-job workdir and/or profile touch
|
|
1984
|
-
# process-global runtime state inside run_job. Workdir jobs temporarily
|
|
1985
|
-
# set os.environ["TERMINAL_CWD"]; profile jobs use a context-local
|
|
1986
|
-
# Hermes home override, scheduler _hermes_home hook, and temporary
|
|
1987
|
-
# profile .env load into os.environ with snapshot/restore. They MUST run
|
|
1988
|
-
# sequentially to avoid corrupting each other. Jobs without either field
|
|
1989
|
-
# stay parallel-safe.
|
|
1990
|
-
sequential_jobs = [
|
|
1991
|
-
j for j in due_jobs
|
|
1992
|
-
if (j.get("workdir") or "").strip() or (j.get("profile") or "").strip()
|
|
1993
|
-
]
|
|
1994
|
-
parallel_jobs = [
|
|
1995
|
-
j for j in due_jobs
|
|
1996
|
-
if not ((j.get("workdir") or "").strip() or (j.get("profile") or "").strip())
|
|
1997
|
-
]
|
|
2194
|
+
"""Run one due job end-to-end. Thin wrapper around the shared
|
|
2195
|
+
module-level ``run_one_job`` so ``tick`` and external providers
|
|
2196
|
+
(Chronos ``fire_due``) use the identical execute→save→deliver→mark
|
|
2197
|
+
body."""
|
|
2198
|
+
return run_one_job(job, adapters=adapters, loop=loop, verbose=verbose)
|
|
2199
|
+
|
|
2200
|
+
# Partition due jobs: those with a per-job workdir mutate
|
|
2201
|
+
# os.environ["TERMINAL_CWD"] inside run_job, which is process-global —
|
|
2202
|
+
# so they MUST run sequentially to avoid corrupting each other. Jobs
|
|
2203
|
+
# without a workdir leave env untouched and stay parallel-safe.
|
|
2204
|
+
sequential_jobs = [j for j in due_jobs if (j.get("workdir") or "").strip()]
|
|
2205
|
+
parallel_jobs = [j for j in due_jobs if not (j.get("workdir") or "").strip()]
|
|
1998
2206
|
|
|
1999
2207
|
_results: list = []
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2208
|
+
_all_futures: list = []
|
|
2209
|
+
|
|
2210
|
+
def _submit_with_guard(job: dict, pool: concurrent.futures.ThreadPoolExecutor):
|
|
2211
|
+
"""Submit a job fire-and-forget with the in-flight dedup guard.
|
|
2212
|
+
|
|
2213
|
+
Returns the future, or None if the job was skipped because a prior
|
|
2214
|
+
tick's run of the same job is still in flight. The running-set
|
|
2215
|
+
membership is released in the worker's finally block.
|
|
2216
|
+
"""
|
|
2217
|
+
job_id = job["id"]
|
|
2218
|
+
with _running_lock:
|
|
2219
|
+
if job_id in _running_job_ids:
|
|
2220
|
+
logger.info("Job '%s' already running — skipping", job.get("name", job_id))
|
|
2221
|
+
return None
|
|
2222
|
+
_running_job_ids.add(job_id)
|
|
2003
2223
|
_ctx = contextvars.copy_context()
|
|
2004
|
-
_results.append(_ctx.run(_process_job, job))
|
|
2005
2224
|
|
|
2006
|
-
|
|
2225
|
+
def _run_and_release(j=job, ctx=_ctx):
|
|
2226
|
+
try:
|
|
2227
|
+
return ctx.run(_process_job, j)
|
|
2228
|
+
finally:
|
|
2229
|
+
with _running_lock:
|
|
2230
|
+
_running_job_ids.discard(j["id"])
|
|
2231
|
+
|
|
2232
|
+
return pool.submit(_run_and_release)
|
|
2233
|
+
|
|
2234
|
+
# Sequential pass for env-mutating (workdir) jobs.
|
|
2235
|
+
# Queued to a persistent single-thread pool so they run one at a time
|
|
2236
|
+
# WITHOUT blocking the ticker thread — a long workdir job no
|
|
2237
|
+
# longer starves the rest of the schedule (same fix as the parallel
|
|
2238
|
+
# pass, just serialized). The in-flight guard prevents a still-running
|
|
2239
|
+
# job from being re-queued on the next tick.
|
|
2240
|
+
if sequential_jobs:
|
|
2241
|
+
seq_pool = _get_sequential_pool()
|
|
2242
|
+
for job in sequential_jobs:
|
|
2243
|
+
fut = _submit_with_guard(job, seq_pool)
|
|
2244
|
+
if fut is None:
|
|
2245
|
+
continue
|
|
2246
|
+
_all_futures.append(fut)
|
|
2247
|
+
if not sync:
|
|
2248
|
+
_results.append(True) # optimistically counted
|
|
2249
|
+
|
|
2250
|
+
# Parallel pass — persistent pool, non-blocking dispatch.
|
|
2251
|
+
# Jobs that are already running (from a previous tick) are skipped.
|
|
2252
|
+
# mark_job_run() updates next_run_at on completion, so the next tick
|
|
2253
|
+
# after completion finds the job due again naturally. No catch-up
|
|
2254
|
+
# queue needed.
|
|
2007
2255
|
if parallel_jobs:
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
except Exception as exc:
|
|
2017
|
-
logger.error("Parallel cron job future failed: %s", exc)
|
|
2018
|
-
_results.append(False)
|
|
2256
|
+
pool = _get_parallel_pool(_max_workers)
|
|
2257
|
+
for job in parallel_jobs:
|
|
2258
|
+
fut = _submit_with_guard(job, pool)
|
|
2259
|
+
if fut is None:
|
|
2260
|
+
continue
|
|
2261
|
+
_all_futures.append(fut)
|
|
2262
|
+
if not sync:
|
|
2263
|
+
_results.append(True) # optimistically counted
|
|
2019
2264
|
|
|
2020
2265
|
# Best-effort sweep of MCP stdio subprocesses that survived their
|
|
2021
|
-
# session teardown
|
|
2022
|
-
#
|
|
2023
|
-
#
|
|
2024
|
-
#
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2266
|
+
# session teardown. Must run AFTER jobs finish so active sessions
|
|
2267
|
+
# (including live user chats) are never touched — only PIDs explicitly
|
|
2268
|
+
# detected as orphans in tools.mcp_tool._run_stdio's finally block are
|
|
2269
|
+
# reaped.
|
|
2270
|
+
def _sweep_mcp_orphans() -> None:
|
|
2271
|
+
try:
|
|
2272
|
+
from tools.mcp_tool import _kill_orphaned_mcp_children
|
|
2273
|
+
_kill_orphaned_mcp_children()
|
|
2274
|
+
except Exception as _e:
|
|
2275
|
+
logger.debug("Post-tick MCP orphan cleanup failed: %s", _e)
|
|
2276
|
+
|
|
2277
|
+
if sync:
|
|
2278
|
+
# Sync mode (tests / manual ticks): wait for all dispatched jobs,
|
|
2279
|
+
# collect results, then sweep once.
|
|
2280
|
+
for f in concurrent.futures.as_completed(_all_futures):
|
|
2281
|
+
try:
|
|
2282
|
+
_results.append(f.result())
|
|
2283
|
+
except Exception as exc:
|
|
2284
|
+
logger.error("Cron job future failed: %s", exc)
|
|
2285
|
+
_results.append(False)
|
|
2286
|
+
_sweep_mcp_orphans()
|
|
2287
|
+
return sum(_results)
|
|
2288
|
+
|
|
2289
|
+
# Async (gateway ticker) mode: don't block. Sweep orphans via a
|
|
2290
|
+
# done-callback fired after the LAST dispatched job completes, so the
|
|
2291
|
+
# sweep still happens after jobs finish without stalling the tick.
|
|
2292
|
+
if _all_futures:
|
|
2293
|
+
_remaining = [len(_all_futures)]
|
|
2294
|
+
|
|
2295
|
+
def _on_done(_f: concurrent.futures.Future) -> None:
|
|
2296
|
+
_remaining[0] -= 1
|
|
2297
|
+
if _remaining[0] <= 0:
|
|
2298
|
+
_sweep_mcp_orphans()
|
|
2299
|
+
|
|
2300
|
+
for _f in _all_futures:
|
|
2301
|
+
_f.add_done_callback(_on_done)
|
|
2302
|
+
else:
|
|
2303
|
+
# Nothing dispatched (all skipped / no due jobs) — sweep inline.
|
|
2304
|
+
_sweep_mcp_orphans()
|
|
2030
2305
|
|
|
2031
2306
|
return sum(_results)
|
|
2032
2307
|
finally:
|