@clawpump/claw-agent 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent/.dockerignore +67 -0
- package/agent/.envrc +1 -1
- package/agent/.gitattributes +8 -0
- package/agent/AGENTS.md +216 -4
- package/agent/CONTRIBUTING.md +46 -8
- package/agent/Dockerfile +78 -35
- package/agent/MANIFEST.in +2 -0
- package/agent/README.md +12 -5
- package/agent/README.ur-pk.md +261 -0
- package/agent/README.zh-CN.md +11 -8
- package/agent/SECURITY.md +5 -4
- package/agent/acp_adapter/provenance.py +127 -0
- package/agent/acp_adapter/server.py +112 -5
- package/agent/acp_adapter/session.py +1 -6
- package/agent/acp_registry/agent.json +2 -2
- package/agent/agent/account_usage.py +313 -1
- package/agent/agent/agent_init.py +140 -37
- package/agent/agent/agent_runtime_helpers.py +342 -83
- package/agent/agent/anthropic_adapter.py +320 -33
- package/agent/agent/auxiliary_client.py +525 -105
- package/agent/agent/background_review.py +157 -19
- package/agent/agent/bedrock_adapter.py +71 -6
- package/agent/agent/billing_view.py +295 -0
- package/agent/agent/chat_completion_helpers.py +229 -4
- package/agent/agent/codex_responses_adapter.py +86 -10
- package/agent/agent/codex_runtime.py +153 -1
- package/agent/agent/coding_context.py +738 -0
- package/agent/agent/context_compressor.py +392 -44
- package/agent/agent/context_references.py +34 -1
- package/agent/agent/conversation_compression.py +159 -22
- package/agent/agent/conversation_loop.py +643 -908
- package/agent/agent/copilot_acp_client.py +4 -11
- package/agent/agent/credential_pool.py +5 -3
- package/agent/agent/credits_tracker.py +794 -0
- package/agent/agent/curator.py +91 -18
- package/agent/agent/curator_backup.py +26 -10
- package/agent/agent/display.py +42 -1
- package/agent/agent/error_classifier.py +52 -3
- package/agent/agent/errors.py +3 -0
- package/agent/agent/file_safety.py +0 -17
- package/agent/agent/gemini_native_adapter.py +31 -1
- package/agent/agent/i18n.py +48 -4
- package/agent/agent/image_gen_provider.py +74 -5
- package/agent/agent/image_routing.py +29 -0
- package/agent/agent/insights.py +8 -17
- package/agent/agent/lsp/install.py +3 -0
- package/agent/agent/memory_manager.py +326 -31
- package/agent/agent/message_content.py +50 -0
- package/agent/agent/model_metadata.py +214 -3
- package/agent/agent/moonshot_schema.py +8 -1
- package/agent/agent/onboarding.py +60 -0
- package/agent/agent/prompt_builder.py +327 -37
- package/agent/agent/redact.py +1 -0
- package/agent/agent/runtime_cwd.py +34 -5
- package/agent/agent/secret_scope.py +205 -0
- package/agent/agent/secret_sources/bitwarden.py +34 -2
- package/agent/agent/skill_commands.py +90 -1
- package/agent/agent/skill_preprocessing.py +1 -0
- package/agent/agent/skill_utils.py +209 -36
- package/agent/agent/ssl_guard.py +94 -0
- package/agent/agent/system_prompt.py +133 -5
- package/agent/agent/tool_executor.py +496 -70
- package/agent/agent/transports/anthropic.py +83 -21
- package/agent/agent/transports/chat_completions.py +94 -5
- package/agent/agent/transports/codex.py +67 -2
- package/agent/agent/transports/codex_app_server.py +1 -0
- package/agent/agent/transports/codex_app_server_session.py +30 -0
- package/agent/agent/transports/types.py +12 -0
- package/agent/agent/turn_context.py +408 -0
- package/agent/agent/turn_finalizer.py +428 -0
- package/agent/agent/turn_retry_state.py +68 -0
- package/agent/agent/usage_pricing.py +3 -0
- package/agent/apps/bootstrap-installer/package.json +6 -5
- package/agent/apps/bootstrap-installer/src/routes/failure.tsx +12 -5
- package/agent/apps/bootstrap-installer/src/routes/progress.tsx +1 -3
- package/agent/apps/bootstrap-installer/src/store.ts +3 -2
- package/agent/apps/bootstrap-installer/src-tauri/src/bootstrap.rs +172 -7
- package/agent/apps/bootstrap-installer/src-tauri/src/events.rs +14 -1
- package/agent/apps/bootstrap-installer/src-tauri/src/paths.rs +29 -0
- package/agent/apps/bootstrap-installer/src-tauri/src/powershell.rs +93 -3
- package/agent/apps/bootstrap-installer/src-tauri/src/update.rs +695 -39
- package/agent/apps/bootstrap-installer/tsconfig.json +3 -4
- package/agent/apps/desktop/DESIGN.md +167 -0
- package/agent/apps/desktop/README.md +20 -16
- package/agent/apps/desktop/assets/icon.icns +0 -0
- package/agent/apps/desktop/assets/icon.ico +0 -0
- package/agent/apps/desktop/assets/icon.png +0 -0
- package/agent/apps/desktop/electron/backend-env.cjs +112 -0
- package/agent/apps/desktop/electron/backend-env.test.cjs +111 -0
- package/agent/apps/desktop/electron/backend-probes.test.cjs +3 -1
- package/agent/apps/desktop/electron/backend-ready.cjs +66 -0
- package/agent/apps/desktop/electron/bootstrap-platform.cjs +52 -0
- package/agent/apps/desktop/electron/bootstrap-platform.test.cjs +59 -1
- package/agent/apps/desktop/electron/bootstrap-runner.cjs +176 -38
- package/agent/apps/desktop/electron/bootstrap-runner.test.cjs +112 -1
- package/agent/apps/desktop/electron/connection-config.cjs +288 -0
- package/agent/apps/desktop/electron/connection-config.test.cjs +396 -0
- package/agent/apps/desktop/electron/dashboard-token.cjs +99 -0
- package/agent/apps/desktop/electron/dashboard-token.test.cjs +142 -0
- package/agent/apps/desktop/electron/desktop-uninstall.cjs +232 -0
- package/agent/apps/desktop/electron/desktop-uninstall.test.cjs +246 -0
- package/agent/apps/desktop/electron/entitlements.mac.inherit.plist +2 -0
- package/agent/apps/desktop/electron/fs-read-dir.cjs +109 -0
- package/agent/apps/desktop/electron/fs-read-dir.test.cjs +364 -0
- package/agent/apps/desktop/electron/gateway-ws-probe.cjs +188 -0
- package/agent/apps/desktop/electron/gateway-ws-probe.test.cjs +122 -0
- package/agent/apps/desktop/electron/git-root.cjs +54 -0
- package/agent/apps/desktop/electron/git-root.test.cjs +40 -0
- package/agent/apps/desktop/electron/git-worktrees.cjs +174 -0
- package/agent/apps/desktop/electron/hardening.cjs +123 -28
- package/agent/apps/desktop/electron/hardening.test.cjs +163 -0
- package/agent/apps/desktop/electron/main.cjs +3121 -331
- package/agent/apps/desktop/electron/oauth-net-request.cjs +20 -0
- package/agent/apps/desktop/electron/oauth-net-request.test.cjs +34 -0
- package/agent/apps/desktop/electron/preload.cjs +52 -2
- package/agent/apps/desktop/electron/session-windows.cjs +124 -0
- package/agent/apps/desktop/electron/session-windows.test.cjs +199 -0
- package/agent/apps/desktop/electron/update-rebuild.cjs +29 -0
- package/agent/apps/desktop/electron/update-rebuild.test.cjs +55 -0
- package/agent/apps/desktop/electron/update-remote.cjs +56 -0
- package/agent/apps/desktop/electron/update-remote.test.cjs +78 -0
- package/agent/apps/desktop/electron/vscode-marketplace.cjs +331 -0
- package/agent/apps/desktop/electron/vscode-marketplace.test.cjs +113 -0
- package/agent/apps/desktop/electron/windows-child-process.test.cjs +57 -0
- package/agent/apps/desktop/electron/windows-user-env.cjs +76 -0
- package/agent/apps/desktop/electron/windows-user-env.test.cjs +90 -0
- package/agent/apps/desktop/electron/workspace-cwd.cjs +38 -0
- package/agent/apps/desktop/electron/workspace-cwd.test.cjs +45 -0
- package/agent/apps/desktop/eslint.config.mjs +0 -3
- package/agent/apps/desktop/index.html +27 -2
- package/agent/apps/desktop/package.json +31 -11
- package/agent/apps/desktop/pr-assets/session-source-folders.png +0 -0
- package/agent/apps/desktop/public/apple-touch-icon.png +0 -0
- package/agent/apps/desktop/public/nous-girl.jpg +0 -0
- package/agent/apps/desktop/scripts/assert-dist-built.cjs +70 -0
- package/agent/apps/desktop/scripts/assert-dist-built.test.cjs +84 -0
- package/agent/apps/desktop/scripts/before-pack.cjs +78 -0
- package/agent/apps/desktop/scripts/before-pack.test.cjs +53 -0
- package/agent/apps/desktop/scripts/diag-scroll-reset.mjs +229 -0
- package/agent/apps/desktop/scripts/patch-electron-builder-mac-binary.cjs +64 -0
- package/agent/apps/desktop/scripts/run-electron-builder.cjs +57 -0
- package/agent/apps/desktop/src/app/agents/index.tsx +53 -45
- package/agent/apps/desktop/src/app/artifacts/index.tsx +102 -83
- package/agent/apps/desktop/src/app/chat/chat-drop-overlay.tsx +29 -8
- package/agent/apps/desktop/src/app/chat/chat-swap-overlay.tsx +47 -0
- package/agent/apps/desktop/src/app/chat/composer/attachments.tsx +81 -45
- package/agent/apps/desktop/src/app/chat/composer/completion-drawer.tsx +13 -24
- package/agent/apps/desktop/src/app/chat/composer/context-menu.tsx +138 -88
- package/agent/apps/desktop/src/app/chat/composer/controls.tsx +138 -90
- package/agent/apps/desktop/src/app/chat/composer/enter-submit-dom-race.test.tsx +218 -0
- package/agent/apps/desktop/src/app/chat/composer/focus.ts +32 -0
- package/agent/apps/desktop/src/app/chat/composer/help-hint.tsx +38 -25
- package/agent/apps/desktop/src/app/chat/composer/hooks/use-live-completion-adapter.ts +7 -0
- package/agent/apps/desktop/src/app/chat/composer/hooks/use-mic-recorder.ts +22 -12
- package/agent/apps/desktop/src/app/chat/composer/hooks/use-slash-completions.ts +142 -14
- package/agent/apps/desktop/src/app/chat/composer/hooks/use-voice-conversation.ts +14 -11
- package/agent/apps/desktop/src/app/chat/composer/hooks/use-voice-recorder.ts +9 -6
- package/agent/apps/desktop/src/app/chat/composer/ime-composition-dom-repro.test.tsx +108 -0
- package/agent/apps/desktop/src/app/chat/composer/index.tsx +930 -180
- package/agent/apps/desktop/src/app/chat/composer/inline-refs.ts +136 -32
- package/agent/apps/desktop/src/app/chat/composer/model-pill.tsx +86 -0
- package/agent/apps/desktop/src/app/chat/composer/queue-panel.tsx +54 -75
- package/agent/apps/desktop/src/app/chat/composer/rich-editor.test.ts +117 -1
- package/agent/apps/desktop/src/app/chat/composer/rich-editor.ts +117 -6
- package/agent/apps/desktop/src/app/chat/composer/slash-nav-dom-repro.test.tsx +186 -0
- package/agent/apps/desktop/src/app/chat/composer/status-stack/index.tsx +202 -0
- package/agent/apps/desktop/src/app/chat/composer/status-stack/status-row.tsx +155 -0
- package/agent/apps/desktop/src/app/chat/composer/text-utils.test.ts +104 -0
- package/agent/apps/desktop/src/app/chat/composer/text-utils.ts +37 -9
- package/agent/apps/desktop/src/app/chat/composer/trigger-popover.test.tsx +50 -0
- package/agent/apps/desktop/src/app/chat/composer/trigger-popover.tsx +105 -40
- package/agent/apps/desktop/src/app/chat/composer/types.ts +5 -0
- package/agent/apps/desktop/src/app/chat/composer/url-dialog.tsx +11 -15
- package/agent/apps/desktop/src/app/chat/composer/voice-activity.tsx +8 -4
- package/agent/apps/desktop/src/app/chat/hooks/use-composer-actions.test.ts +57 -0
- package/agent/apps/desktop/src/app/chat/hooks/use-composer-actions.ts +70 -16
- package/agent/apps/desktop/src/app/chat/hooks/use-file-drop-zone.ts +52 -16
- package/agent/apps/desktop/src/app/chat/index.tsx +234 -81
- package/agent/apps/desktop/src/app/chat/perf-probe.tsx +69 -21
- package/agent/apps/desktop/src/app/chat/right-rail/preview-console.tsx +44 -40
- package/agent/apps/desktop/src/app/chat/right-rail/preview-file.tsx +71 -25
- package/agent/apps/desktop/src/app/chat/right-rail/preview-pane.test.tsx +40 -1
- package/agent/apps/desktop/src/app/chat/right-rail/preview-pane.tsx +55 -53
- package/agent/apps/desktop/src/app/chat/right-rail/preview.tsx +35 -17
- package/agent/apps/desktop/src/app/chat/scroll-to-bottom-button.test.tsx +67 -0
- package/agent/apps/desktop/src/app/chat/scroll-to-bottom-button.tsx +74 -0
- package/agent/apps/desktop/src/app/chat/sidebar/cron-jobs-section.tsx +356 -0
- package/agent/apps/desktop/src/app/chat/sidebar/index.tsx +1189 -364
- package/agent/apps/desktop/src/app/chat/sidebar/load-more-row.tsx +30 -0
- package/agent/apps/desktop/src/app/chat/sidebar/order.test.ts +21 -0
- package/agent/apps/desktop/src/app/chat/sidebar/order.ts +17 -0
- package/agent/apps/desktop/src/app/chat/sidebar/profile-switcher.tsx +524 -0
- package/agent/apps/desktop/src/app/chat/sidebar/session-actions-menu.tsx +80 -45
- package/agent/apps/desktop/src/app/chat/sidebar/session-row.tsx +120 -25
- package/agent/apps/desktop/src/app/chat/sidebar/virtual-session-list.tsx +7 -13
- package/agent/apps/desktop/src/app/chat/sidebar/workspace-groups.test.ts +149 -0
- package/agent/apps/desktop/src/app/chat/sidebar/workspace-groups.ts +326 -0
- package/agent/apps/desktop/src/app/chat/thread-loading.ts +7 -2
- package/agent/apps/desktop/src/app/command-center/index.tsx +320 -581
- package/agent/apps/desktop/src/app/command-palette/index.tsx +681 -0
- package/agent/apps/desktop/src/app/command-palette/marketplace-theme-page.tsx +157 -0
- package/agent/apps/desktop/src/app/cron/index.tsx +392 -324
- package/agent/apps/desktop/src/app/cron/job-state.ts +29 -0
- package/agent/apps/desktop/src/app/desktop-controller.tsx +618 -123
- package/agent/apps/desktop/src/app/floating-hud.ts +22 -0
- package/agent/apps/desktop/src/app/gateway/hooks/use-gateway-boot.test.tsx +265 -0
- package/agent/apps/desktop/src/app/gateway/hooks/use-gateway-boot.ts +260 -14
- package/agent/apps/desktop/src/app/gateway/hooks/use-gateway-request.ts +48 -4
- package/agent/apps/desktop/src/app/hooks/use-keybinds.ts +270 -0
- package/agent/apps/desktop/src/app/hooks/use-refresh-hotkey.ts +45 -0
- package/agent/apps/desktop/src/app/layout-constants.ts +19 -0
- package/agent/apps/desktop/src/app/messaging/index.tsx +136 -241
- package/agent/apps/desktop/src/app/messaging/platform-icon.tsx +95 -0
- package/agent/apps/desktop/src/app/model-visibility-overlay.tsx +31 -0
- package/agent/apps/desktop/src/app/overlays/overlay-search-input.tsx +18 -62
- package/agent/apps/desktop/src/app/overlays/overlay-split-layout.tsx +59 -7
- package/agent/apps/desktop/src/app/overlays/overlay-view.tsx +9 -5
- package/agent/apps/desktop/src/app/page-search-shell.tsx +42 -20
- package/agent/apps/desktop/src/app/profiles/create-profile-dialog.tsx +165 -0
- package/agent/apps/desktop/src/app/profiles/delete-profile-dialog.tsx +65 -0
- package/agent/apps/desktop/src/app/profiles/index.tsx +174 -199
- package/agent/apps/desktop/src/app/profiles/rename-profile-dialog.tsx +125 -0
- package/agent/apps/desktop/src/app/right-sidebar/files/dnd-manager.ts +27 -0
- package/agent/apps/desktop/src/app/right-sidebar/files/ipc.test.ts +100 -0
- package/agent/apps/desktop/src/app/right-sidebar/files/ipc.ts +12 -18
- package/agent/apps/desktop/src/app/right-sidebar/files/remote-picker.tsx +177 -0
- package/agent/apps/desktop/src/app/right-sidebar/files/tree.tsx +35 -21
- package/agent/apps/desktop/src/app/right-sidebar/files/use-project-tree.test.ts +75 -3
- package/agent/apps/desktop/src/app/right-sidebar/files/use-project-tree.ts +152 -5
- package/agent/apps/desktop/src/app/right-sidebar/index.test.tsx +75 -0
- package/agent/apps/desktop/src/app/right-sidebar/index.tsx +166 -129
- package/agent/apps/desktop/src/app/right-sidebar/store.ts +19 -4
- package/agent/apps/desktop/src/app/right-sidebar/terminal/buffer.ts +65 -0
- package/agent/apps/desktop/src/app/right-sidebar/terminal/index.tsx +29 -34
- package/agent/apps/desktop/src/app/right-sidebar/terminal/persistent.tsx +18 -6
- package/agent/apps/desktop/src/app/right-sidebar/terminal/selection.ts +93 -32
- package/agent/apps/desktop/src/app/right-sidebar/terminal/use-terminal-session.ts +381 -119
- package/agent/apps/desktop/src/app/routes.ts +9 -0
- package/agent/apps/desktop/src/app/session/hooks/use-cwd-actions.ts +17 -7
- package/agent/apps/desktop/src/app/session/hooks/use-message-stream.ts +365 -47
- package/agent/apps/desktop/src/app/session/hooks/use-model-controls.test.tsx +198 -0
- package/agent/apps/desktop/src/app/session/hooks/use-model-controls.ts +70 -34
- package/agent/apps/desktop/src/app/session/hooks/use-prompt-actions.test.tsx +1061 -0
- package/agent/apps/desktop/src/app/session/hooks/use-prompt-actions.ts +1143 -165
- package/agent/apps/desktop/src/app/session/hooks/use-route-resume.test.tsx +341 -2
- package/agent/apps/desktop/src/app/session/hooks/use-route-resume.ts +176 -5
- package/agent/apps/desktop/src/app/session/hooks/use-session-actions.test.tsx +259 -0
- package/agent/apps/desktop/src/app/session/hooks/use-session-actions.ts +452 -149
- package/agent/apps/desktop/src/app/session/hooks/use-session-state-cache.test.tsx +327 -0
- package/agent/apps/desktop/src/app/session/hooks/use-session-state-cache.ts +133 -4
- package/agent/apps/desktop/src/app/session-picker-overlay.tsx +32 -0
- package/agent/apps/desktop/src/app/session-switcher.tsx +107 -0
- package/agent/apps/desktop/src/app/settings/about-settings.tsx +45 -36
- package/agent/apps/desktop/src/app/settings/appearance-settings.tsx +243 -162
- package/agent/apps/desktop/src/app/settings/config-settings.tsx +86 -66
- package/agent/apps/desktop/src/app/settings/constants.ts +459 -122
- package/agent/apps/desktop/src/app/settings/credential-key-ui.tsx +373 -0
- package/agent/apps/desktop/src/app/settings/env-credentials.tsx +198 -0
- package/agent/apps/desktop/src/app/settings/env-var-actions-menu.tsx +136 -0
- package/agent/apps/desktop/src/app/settings/field-copy.ts +56 -0
- package/agent/apps/desktop/src/app/settings/gateway-settings.tsx +385 -72
- package/agent/apps/desktop/src/app/settings/helpers.test.ts +156 -1
- package/agent/apps/desktop/src/app/settings/helpers.ts +30 -2
- package/agent/apps/desktop/src/app/settings/index.tsx +118 -84
- package/agent/apps/desktop/src/app/settings/keys-settings.tsx +62 -419
- package/agent/apps/desktop/src/app/settings/mcp-settings.tsx +65 -60
- package/agent/apps/desktop/src/app/settings/model-settings.test.tsx +129 -5
- package/agent/apps/desktop/src/app/settings/model-settings.tsx +370 -65
- package/agent/apps/desktop/src/app/settings/notifications-settings.tsx +150 -0
- package/agent/apps/desktop/src/app/settings/primitives.tsx +5 -11
- package/agent/apps/desktop/src/app/settings/provider-config-panel.test.tsx +142 -0
- package/agent/apps/desktop/src/app/settings/provider-config-panel.tsx +182 -0
- package/agent/apps/desktop/src/app/settings/providers-settings.test.tsx +171 -0
- package/agent/apps/desktop/src/app/settings/providers-settings.tsx +471 -0
- package/agent/apps/desktop/src/app/settings/sessions-settings.tsx +183 -71
- package/agent/apps/desktop/src/app/settings/toolset-config-panel.test.tsx +135 -1
- package/agent/apps/desktop/src/app/settings/toolset-config-panel.tsx +180 -57
- package/agent/apps/desktop/src/app/settings/types.ts +9 -6
- package/agent/apps/desktop/src/app/settings/uninstall-section.tsx +185 -0
- package/agent/apps/desktop/src/app/settings/use-deep-link-highlight.ts +60 -0
- package/agent/apps/desktop/src/app/shell/app-shell.tsx +59 -13
- package/agent/apps/desktop/src/app/shell/gateway-menu-panel.tsx +37 -32
- package/agent/apps/desktop/src/app/shell/hooks/use-overlay-routing.ts +6 -3
- package/agent/apps/desktop/src/app/shell/hooks/use-statusbar-items.tsx +212 -53
- package/agent/apps/desktop/src/app/shell/keybind-panel.tsx +215 -0
- package/agent/apps/desktop/src/app/shell/model-edit-submenu.test.tsx +84 -0
- package/agent/apps/desktop/src/app/shell/model-edit-submenu.tsx +244 -0
- package/agent/apps/desktop/src/app/shell/model-menu-panel.tsx +392 -0
- package/agent/apps/desktop/src/app/shell/statusbar-controls.tsx +23 -33
- package/agent/apps/desktop/src/app/shell/titlebar-controls.tsx +79 -95
- package/agent/apps/desktop/src/app/shell/titlebar.ts +8 -2
- package/agent/apps/desktop/src/app/skills/index.test.tsx +11 -0
- package/agent/apps/desktop/src/app/skills/index.tsx +79 -64
- package/agent/apps/desktop/src/app/types.ts +85 -0
- package/agent/apps/desktop/src/app/updates-overlay.tsx +110 -105
- package/agent/apps/desktop/src/components/assistant-ui/ansi-text.tsx +34 -0
- package/agent/apps/desktop/src/components/assistant-ui/block-direction.test.tsx +129 -0
- package/agent/apps/desktop/src/components/assistant-ui/clarify-tool.tsx +102 -81
- package/agent/apps/desktop/src/components/assistant-ui/directive-text.tsx +92 -15
- package/agent/apps/desktop/src/components/assistant-ui/markdown-text.test.ts +38 -0
- package/agent/apps/desktop/src/components/assistant-ui/markdown-text.tsx +304 -45
- package/agent/apps/desktop/src/components/assistant-ui/message-render-boundary.test.tsx +80 -0
- package/agent/apps/desktop/src/components/assistant-ui/message-render-boundary.tsx +48 -0
- package/agent/apps/desktop/src/components/assistant-ui/streaming.test.tsx +142 -90
- package/agent/apps/desktop/src/components/assistant-ui/thread-list.tsx +337 -0
- package/agent/apps/desktop/src/components/assistant-ui/thread.tsx +667 -190
- package/agent/apps/desktop/src/components/assistant-ui/tool-approval-group.test.tsx +299 -0
- package/agent/apps/desktop/src/components/assistant-ui/tool-approval.test.tsx +133 -0
- package/agent/apps/desktop/src/components/assistant-ui/tool-approval.tsx +239 -0
- package/agent/apps/desktop/src/components/assistant-ui/tool-fallback-model.test.ts +31 -0
- package/agent/apps/desktop/src/components/assistant-ui/tool-fallback-model.ts +152 -134
- package/agent/apps/desktop/src/components/assistant-ui/tool-fallback.tsx +142 -150
- package/agent/apps/desktop/src/components/assistant-ui/tooltip-icon-button.tsx +14 -12
- package/agent/apps/desktop/src/components/assistant-ui/user-message-edit.test.tsx +141 -0
- package/agent/apps/desktop/src/components/assistant-ui/user-message-text.tsx +152 -0
- package/agent/apps/desktop/src/components/boot-failure-overlay.tsx +150 -33
- package/agent/apps/desktop/src/components/boot-failure-reauth.test.ts +100 -0
- package/agent/apps/desktop/src/components/boot-failure-reauth.ts +81 -0
- package/agent/apps/desktop/src/components/brand-mark.tsx +19 -0
- package/agent/apps/desktop/src/components/chat/code-card.tsx +1 -1
- package/agent/apps/desktop/src/components/chat/composer-dock.ts +31 -0
- package/agent/apps/desktop/src/components/chat/diff-lines.tsx +1 -1
- package/agent/apps/desktop/src/components/chat/disclosure-row.tsx +13 -3
- package/agent/apps/desktop/src/components/chat/expandable-block.tsx +52 -0
- package/agent/apps/desktop/src/components/chat/generated-image-result.tsx +174 -0
- package/agent/apps/desktop/src/components/chat/image-generation-placeholder.tsx +70 -37
- package/agent/apps/desktop/src/components/chat/intro.tsx +8 -7
- package/agent/apps/desktop/src/components/chat/preview-attachment.tsx +4 -2
- package/agent/apps/desktop/src/components/chat/shiki-highlighter.test.ts +37 -0
- package/agent/apps/desktop/src/components/chat/shiki-highlighter.tsx +96 -22
- package/agent/apps/desktop/src/components/chat/status-row.tsx +70 -0
- package/agent/apps/desktop/src/components/chat/status-section.tsx +42 -0
- package/agent/apps/desktop/src/components/chat/terminal-output.tsx +54 -0
- package/agent/apps/desktop/src/components/chat/zoomable-image.tsx +70 -109
- package/agent/apps/desktop/src/components/desktop-install-overlay.tsx +154 -84
- package/agent/apps/desktop/src/components/desktop-onboarding-overlay.test.tsx +38 -8
- package/agent/apps/desktop/src/components/desktop-onboarding-overlay.tsx +789 -233
- package/agent/apps/desktop/src/components/error-boundary.tsx +77 -0
- package/agent/apps/desktop/src/components/gateway-connecting-overlay.test.tsx +144 -0
- package/agent/apps/desktop/src/components/gateway-connecting-overlay.tsx +7 -1
- package/agent/apps/desktop/src/components/haptics-provider.tsx +24 -0
- package/agent/apps/desktop/src/components/language-switcher.test.tsx +53 -0
- package/agent/apps/desktop/src/components/language-switcher.tsx +175 -0
- package/agent/apps/desktop/src/components/model-picker.tsx +42 -40
- package/agent/apps/desktop/src/components/model-visibility-dialog.tsx +166 -0
- package/agent/apps/desktop/src/components/notifications.tsx +48 -27
- package/agent/apps/desktop/src/components/pane-shell/index.ts +1 -1
- package/agent/apps/desktop/src/components/pane-shell/pane-shell.tsx +146 -9
- package/agent/apps/desktop/src/components/prompt-overlays.tsx +234 -0
- package/agent/apps/desktop/src/components/session-picker.tsx +108 -0
- package/agent/apps/desktop/src/components/ui/action-status.tsx +25 -0
- package/agent/apps/desktop/src/components/ui/badge.tsx +35 -0
- package/agent/apps/desktop/src/components/ui/button.tsx +37 -13
- package/agent/apps/desktop/src/components/ui/confirm-dialog.tsx +109 -0
- package/agent/apps/desktop/src/components/ui/control.ts +25 -0
- package/agent/apps/desktop/src/components/ui/copy-button.test.tsx +36 -0
- package/agent/apps/desktop/src/components/ui/copy-button.tsx +38 -27
- package/agent/apps/desktop/src/components/ui/dialog.tsx +39 -11
- package/agent/apps/desktop/src/components/ui/dropdown-menu.tsx +98 -24
- package/agent/apps/desktop/src/components/ui/error-state.tsx +50 -0
- package/agent/apps/desktop/src/components/ui/fade-text.tsx +9 -2
- package/agent/apps/desktop/src/components/ui/{braille-spinner.tsx → glyph-spinner.tsx} +15 -13
- package/agent/apps/desktop/src/components/ui/input.tsx +5 -2
- package/agent/apps/desktop/src/components/ui/kbd.tsx +83 -12
- package/agent/apps/desktop/src/components/ui/log-view.tsx +19 -0
- package/agent/apps/desktop/src/components/ui/pagination.tsx +12 -5
- package/agent/apps/desktop/src/components/ui/popover.tsx +44 -0
- package/agent/apps/desktop/src/components/ui/search-field.tsx +80 -0
- package/agent/apps/desktop/src/components/ui/segmented-control.tsx +51 -0
- package/agent/apps/desktop/src/components/ui/select.tsx +10 -3
- package/agent/apps/desktop/src/components/ui/sheet.tsx +8 -2
- package/agent/apps/desktop/src/components/ui/sidebar.tsx +18 -25
- package/agent/apps/desktop/src/components/ui/switch.tsx +38 -15
- package/agent/apps/desktop/src/components/ui/textarea.tsx +4 -11
- package/agent/apps/desktop/src/components/ui/tool-icon.tsx +65 -0
- package/agent/apps/desktop/src/components/ui/tooltip.tsx +31 -4
- package/agent/apps/desktop/src/fonts/JetBrainsMono-Bold.woff2 +0 -0
- package/agent/apps/desktop/src/fonts/JetBrainsMono-Italic.woff2 +0 -0
- package/agent/apps/desktop/src/fonts/JetBrainsMono-Regular.woff2 +0 -0
- package/agent/apps/desktop/src/global.d.ts +181 -4
- package/agent/apps/desktop/src/hermes.test.ts +60 -0
- package/agent/apps/desktop/src/hermes.ts +190 -13
- package/agent/apps/desktop/src/hooks/use-image-download.ts +85 -0
- package/agent/apps/desktop/src/hooks/use-resize-observer.ts +13 -4
- package/agent/apps/desktop/src/hooks/use-worktree-info.ts +68 -0
- package/agent/apps/desktop/src/i18n/catalog.ts +12 -0
- package/agent/apps/desktop/src/i18n/context.test.tsx +232 -0
- package/agent/apps/desktop/src/i18n/context.tsx +183 -0
- package/agent/apps/desktop/src/i18n/define-locale.ts +41 -0
- package/agent/apps/desktop/src/i18n/en.ts +1921 -0
- package/agent/apps/desktop/src/i18n/index.ts +20 -0
- package/agent/apps/desktop/src/i18n/ja.ts +2053 -0
- package/agent/apps/desktop/src/i18n/languages.test.ts +43 -0
- package/agent/apps/desktop/src/i18n/languages.ts +86 -0
- package/agent/apps/desktop/src/i18n/runtime.test.ts +75 -0
- package/agent/apps/desktop/src/i18n/runtime.ts +53 -0
- package/agent/apps/desktop/src/i18n/types.ts +1559 -0
- package/agent/apps/desktop/src/i18n/zh-hant.ts +1992 -0
- package/agent/apps/desktop/src/i18n/zh.ts +2099 -0
- package/agent/apps/desktop/src/lib/ansi.test.ts +123 -0
- package/agent/apps/desktop/src/lib/ansi.ts +186 -0
- package/agent/apps/desktop/src/lib/chat-messages.test.ts +79 -0
- package/agent/apps/desktop/src/lib/chat-messages.ts +68 -29
- package/agent/apps/desktop/src/lib/chat-runtime.test.ts +65 -1
- package/agent/apps/desktop/src/lib/chat-runtime.ts +39 -3
- package/agent/apps/desktop/src/lib/completion-sound.ts +519 -0
- package/agent/apps/desktop/src/lib/desktop-fs.test.ts +116 -0
- package/agent/apps/desktop/src/lib/desktop-fs.ts +113 -0
- package/agent/apps/desktop/src/lib/desktop-slash-commands.test.ts +89 -6
- package/agent/apps/desktop/src/lib/desktop-slash-commands.ts +270 -131
- package/agent/apps/desktop/src/lib/external-link.test.tsx +27 -0
- package/agent/apps/desktop/src/lib/external-link.tsx +9 -2
- package/agent/apps/desktop/src/lib/gateway-events.test.ts +27 -0
- package/agent/apps/desktop/src/lib/gateway-events.ts +16 -0
- package/agent/apps/desktop/src/lib/gateway-ws-url.test.ts +78 -0
- package/agent/apps/desktop/src/lib/gateway-ws-url.ts +91 -0
- package/agent/apps/desktop/src/lib/generated-images.test.ts +97 -0
- package/agent/apps/desktop/src/lib/generated-images.ts +116 -0
- package/agent/apps/desktop/src/lib/haptics.ts +17 -0
- package/agent/apps/desktop/src/lib/icons.ts +10 -2
- package/agent/apps/desktop/src/lib/keybinds/actions.ts +137 -0
- package/agent/apps/desktop/src/lib/keybinds/combo.test.ts +86 -0
- package/agent/apps/desktop/src/lib/keybinds/combo.ts +195 -0
- package/agent/apps/desktop/src/lib/local-preview.ts +23 -2
- package/agent/apps/desktop/src/lib/markdown-preprocess.ts +20 -7
- package/agent/apps/desktop/src/lib/media.remote.test.ts +90 -0
- package/agent/apps/desktop/src/lib/media.ts +40 -1
- package/agent/apps/desktop/src/lib/model-status-label.test.ts +59 -0
- package/agent/apps/desktop/src/lib/model-status-label.ts +122 -0
- package/agent/apps/desktop/src/lib/mutable-ref.ts +6 -0
- package/agent/apps/desktop/src/lib/profile-color.ts +58 -0
- package/agent/apps/desktop/src/lib/query-client.ts +13 -0
- package/agent/apps/desktop/src/lib/remend-tail.test.ts +105 -0
- package/agent/apps/desktop/src/lib/remend-tail.ts +108 -0
- package/agent/apps/desktop/src/lib/session-export.ts +6 -3
- package/agent/apps/desktop/src/lib/session-ids.test.ts +44 -0
- package/agent/apps/desktop/src/lib/session-ids.ts +26 -0
- package/agent/apps/desktop/src/lib/session-search.test.ts +66 -0
- package/agent/apps/desktop/src/lib/session-search.ts +21 -0
- package/agent/apps/desktop/src/lib/session-source.ts +126 -0
- package/agent/apps/desktop/src/lib/storage.test.ts +25 -0
- package/agent/apps/desktop/src/lib/storage.ts +35 -1
- package/agent/apps/desktop/src/lib/todos.test.ts +46 -1
- package/agent/apps/desktop/src/lib/todos.ts +37 -0
- package/agent/apps/desktop/src/lib/tool-result-summary.ts +5 -1
- package/agent/apps/desktop/src/lib/update-copy.test.ts +38 -0
- package/agent/apps/desktop/src/lib/update-copy.ts +44 -0
- package/agent/apps/desktop/src/lib/use-enter-animation.ts +2 -2
- package/agent/apps/desktop/src/lib/yolo-session.ts +50 -0
- package/agent/apps/desktop/src/main.tsx +19 -19
- package/agent/apps/desktop/src/store/boot.ts +4 -3
- package/agent/apps/desktop/src/store/clarify.test.ts +81 -0
- package/agent/apps/desktop/src/store/clarify.ts +50 -13
- package/agent/apps/desktop/src/store/command-palette.ts +20 -0
- package/agent/apps/desktop/src/store/compaction.test.ts +53 -0
- package/agent/apps/desktop/src/store/compaction.ts +38 -0
- package/agent/apps/desktop/src/store/completion-sound.ts +32 -0
- package/agent/apps/desktop/src/store/composer-input-history.test.ts +147 -0
- package/agent/apps/desktop/src/store/composer-input-history.ts +158 -0
- package/agent/apps/desktop/src/store/composer-queue.test.ts +68 -0
- package/agent/apps/desktop/src/store/composer-queue.ts +76 -0
- package/agent/apps/desktop/src/store/composer-status.test.ts +99 -0
- package/agent/apps/desktop/src/store/composer-status.ts +277 -0
- package/agent/apps/desktop/src/store/composer.test.ts +106 -0
- package/agent/apps/desktop/src/store/composer.ts +116 -0
- package/agent/apps/desktop/src/store/cron.ts +19 -0
- package/agent/apps/desktop/src/store/gateway.ts +280 -6
- package/agent/apps/desktop/src/store/keybinds.ts +143 -0
- package/agent/apps/desktop/src/store/layout.ts +107 -9
- package/agent/apps/desktop/src/store/model-presets.test.ts +51 -0
- package/agent/apps/desktop/src/store/model-presets.ts +86 -0
- package/agent/apps/desktop/src/store/model-visibility.test.ts +99 -0
- package/agent/apps/desktop/src/store/model-visibility.ts +161 -0
- package/agent/apps/desktop/src/store/native-notifications.test.ts +192 -0
- package/agent/apps/desktop/src/store/native-notifications.ts +203 -0
- package/agent/apps/desktop/src/store/notifications.ts +10 -7
- package/agent/apps/desktop/src/store/onboarding.test.ts +271 -1
- package/agent/apps/desktop/src/store/onboarding.ts +268 -38
- package/agent/apps/desktop/src/store/preview.ts +10 -1
- package/agent/apps/desktop/src/store/profile.test.ts +89 -0
- package/agent/apps/desktop/src/store/profile.ts +395 -0
- package/agent/apps/desktop/src/store/prompts.test.ts +127 -0
- package/agent/apps/desktop/src/store/prompts.ts +117 -0
- package/agent/apps/desktop/src/store/session-switcher.test.ts +115 -0
- package/agent/apps/desktop/src/store/session-switcher.ts +128 -0
- package/agent/apps/desktop/src/store/session-sync.ts +25 -0
- package/agent/apps/desktop/src/store/session.test.ts +268 -2
- package/agent/apps/desktop/src/store/session.ts +392 -18
- package/agent/apps/desktop/src/store/subagents.ts +3 -0
- package/agent/apps/desktop/src/store/system-actions.ts +48 -0
- package/agent/apps/desktop/src/store/thread-scroll.ts +58 -5
- package/agent/apps/desktop/src/store/todos.test.ts +47 -0
- package/agent/apps/desktop/src/store/todos.ts +64 -0
- package/agent/apps/desktop/src/store/tool-dismiss.ts +45 -0
- package/agent/apps/desktop/src/store/translucency.ts +38 -0
- package/agent/apps/desktop/src/store/updates.test.ts +187 -2
- package/agent/apps/desktop/src/store/updates.ts +268 -18
- package/agent/apps/desktop/src/store/windows.test.ts +143 -0
- package/agent/apps/desktop/src/store/windows.ts +115 -0
- package/agent/apps/desktop/src/styles.css +510 -119
- package/agent/apps/desktop/src/themes/color.ts +142 -0
- package/agent/apps/desktop/src/themes/context.tsx +128 -75
- package/agent/apps/desktop/src/themes/install.test.ts +119 -0
- package/agent/apps/desktop/src/themes/install.ts +95 -0
- package/agent/apps/desktop/src/themes/presets.test.ts +33 -0
- package/agent/apps/desktop/src/themes/presets.ts +13 -4
- package/agent/apps/desktop/src/themes/profile-theme.test.ts +41 -0
- package/agent/apps/desktop/src/themes/types.ts +35 -0
- package/agent/apps/desktop/src/themes/user-themes.test.ts +63 -0
- package/agent/apps/desktop/src/themes/user-themes.ts +122 -0
- package/agent/apps/desktop/src/themes/vscode.test.ts +171 -0
- package/agent/apps/desktop/src/themes/vscode.ts +343 -0
- package/agent/apps/desktop/src/types/hermes.ts +138 -1
- package/agent/apps/desktop/tsconfig.json +2 -2
- package/agent/apps/desktop/vite.config.ts +18 -0
- package/agent/apps/shared/package.json +1 -1
- package/agent/apps/shared/src/json-rpc-gateway.ts +63 -2
- package/agent/apps/shared/tsconfig.json +2 -2
- package/agent/cli-config.yaml.example +78 -1
- package/agent/cli.py +2294 -3146
- package/agent/cron/blueprint_catalog.py +713 -0
- package/agent/cron/jobs.py +226 -110
- package/agent/cron/scheduler.py +468 -193
- package/agent/cron/scheduler_provider.py +177 -0
- package/agent/cron/scripts/__init__.py +1 -0
- package/agent/cron/scripts/classify_items.py +226 -0
- package/agent/cron/suggestion_catalog.py +154 -0
- package/agent/cron/suggestions.py +257 -0
- package/agent/docs/chronos-managed-cron-contract.md +196 -0
- package/agent/docs/design/profile-builder.md +146 -0
- package/agent/docs/middleware/README.md +260 -0
- package/agent/docs/observability/README.md +316 -0
- package/agent/docs/plans/2026-06-09-003-fix-telegram-stream-overflow-continuations-plan.md +240 -0
- package/agent/docs/rca-ssl-cacert-post-git-pull.md +54 -0
- package/agent/docs/relay-connector-contract.md +285 -0
- package/agent/gateway/authz_mixin.py +536 -0
- package/agent/gateway/channel_directory.py +65 -3
- package/agent/gateway/config.py +222 -12
- package/agent/gateway/display_config.py +10 -0
- package/agent/gateway/hooks.py +17 -0
- package/agent/gateway/kanban_watchers.py +1146 -0
- package/agent/gateway/message_timestamps.py +166 -0
- package/agent/gateway/platforms/ADDING_A_PLATFORM.md +29 -0
- package/agent/gateway/platforms/api_server.py +216 -38
- package/agent/gateway/platforms/base.py +210 -58
- package/agent/gateway/platforms/email.py +122 -12
- package/agent/gateway/platforms/feishu.py +80 -11
- package/agent/gateway/platforms/feishu_meeting_invite.py +212 -0
- package/agent/gateway/platforms/matrix.py +1498 -297
- package/agent/gateway/platforms/qqbot/adapter.py +6 -0
- package/agent/gateway/platforms/signal.py +8 -0
- package/agent/gateway/platforms/slack.py +308 -12
- package/agent/gateway/platforms/telegram.py +831 -24
- package/agent/gateway/platforms/webhook.py +109 -21
- package/agent/gateway/platforms/weixin.py +113 -2
- package/agent/gateway/platforms/whatsapp.py +94 -288
- package/agent/gateway/platforms/whatsapp_cloud.py +1956 -0
- package/agent/gateway/platforms/whatsapp_common.py +367 -0
- package/agent/gateway/platforms/yuanbao.py +608 -191
- package/agent/gateway/platforms/yuanbao_proto.py +232 -23
- package/agent/gateway/relay/__init__.py +375 -0
- package/agent/gateway/relay/adapter.py +222 -0
- package/agent/gateway/relay/auth.py +168 -0
- package/agent/gateway/relay/descriptor.py +118 -0
- package/agent/gateway/relay/transport.py +101 -0
- package/agent/gateway/relay/ws_transport.py +327 -0
- package/agent/gateway/response_filters.py +53 -0
- package/agent/gateway/rich_sent_store.py +80 -0
- package/agent/gateway/run.py +2940 -5001
- package/agent/gateway/session.py +109 -8
- package/agent/gateway/session_context.py +22 -4
- package/agent/gateway/slash_commands.py +3854 -0
- package/agent/gateway/status.py +141 -21
- package/agent/gateway/stream_consumer.py +288 -31
- package/agent/hermes-already-has-routines.md +1 -1
- package/agent/hermes_cli/__init__.py +62 -17
- package/agent/hermes_cli/_parser.py +30 -0
- package/agent/hermes_cli/_subprocess_compat.py +61 -0
- package/agent/hermes_cli/active_sessions.py +320 -0
- package/agent/hermes_cli/auth.py +707 -59
- package/agent/hermes_cli/auth_commands.py +39 -22
- package/agent/hermes_cli/backup.py +109 -7
- package/agent/hermes_cli/banner.py +88 -0
- package/agent/hermes_cli/blueprint_cmd.py +318 -0
- package/agent/hermes_cli/clawpump_cli.py +3 -3
- package/agent/hermes_cli/cli_agent_setup_mixin.py +684 -0
- package/agent/hermes_cli/cli_commands_mixin.py +2293 -0
- package/agent/hermes_cli/commands.py +216 -91
- package/agent/hermes_cli/config.py +967 -130
- package/agent/hermes_cli/container_boot.py +76 -11
- package/agent/hermes_cli/cron.py +5 -11
- package/agent/hermes_cli/curator.py +21 -0
- package/agent/hermes_cli/dashboard_auth/__init__.py +2 -0
- package/agent/hermes_cli/dashboard_auth/base.py +62 -0
- package/agent/hermes_cli/dashboard_auth/cookies.py +32 -19
- package/agent/hermes_cli/dashboard_auth/login_page.py +156 -6
- package/agent/hermes_cli/dashboard_auth/middleware.py +28 -4
- package/agent/hermes_cli/dashboard_auth/prefix.py +46 -2
- package/agent/hermes_cli/dashboard_auth/public_paths.py +6 -0
- package/agent/hermes_cli/dashboard_auth/routes.py +158 -2
- package/agent/hermes_cli/dashboard_auth/ws_tickets.py +85 -11
- package/agent/hermes_cli/dashboard_register.py +427 -0
- package/agent/hermes_cli/debug.py +155 -50
- package/agent/hermes_cli/distribution.py +227 -0
- package/agent/hermes_cli/doctor.py +255 -14
- package/agent/hermes_cli/dump.py +60 -6
- package/agent/hermes_cli/env_loader.py +33 -0
- package/agent/hermes_cli/gateway.py +755 -103
- package/agent/hermes_cli/gateway_enroll.py +250 -0
- package/agent/hermes_cli/gateway_windows.py +254 -11
- package/agent/hermes_cli/gui_uninstall.py +285 -0
- package/agent/hermes_cli/inventory.py +105 -4
- package/agent/hermes_cli/kanban.py +58 -71
- package/agent/hermes_cli/kanban_db.py +391 -14
- package/agent/hermes_cli/kanban_decompose.py +2 -2
- package/agent/hermes_cli/kanban_specify.py +3 -1
- package/agent/hermes_cli/logs.py +2 -0
- package/agent/hermes_cli/main.py +2889 -5287
- package/agent/hermes_cli/managed_scope.py +214 -0
- package/agent/hermes_cli/managed_uv.py +254 -0
- package/agent/hermes_cli/mcp_catalog.py +6 -3
- package/agent/hermes_cli/mcp_config.py +145 -21
- package/agent/hermes_cli/mcp_security.py +96 -0
- package/agent/hermes_cli/mcp_startup.py +32 -3
- package/agent/hermes_cli/memory_providers.py +149 -0
- package/agent/hermes_cli/memory_setup.py +97 -42
- package/agent/hermes_cli/middleware.py +313 -0
- package/agent/hermes_cli/model_catalog.py +31 -0
- package/agent/hermes_cli/model_cost_guard.py +134 -0
- package/agent/hermes_cli/model_normalize.py +2 -1
- package/agent/hermes_cli/model_setup_flows.py +2759 -0
- package/agent/hermes_cli/model_switch.py +242 -27
- package/agent/hermes_cli/models.py +284 -44
- package/agent/hermes_cli/nous_account.py +33 -6
- package/agent/hermes_cli/nous_billing.py +406 -0
- package/agent/hermes_cli/nous_subscription.py +202 -5
- package/agent/hermes_cli/platforms.py +1 -0
- package/agent/hermes_cli/plugins.py +218 -18
- package/agent/hermes_cli/plugins_cmd.py +249 -105
- package/agent/hermes_cli/portal_cli.py +56 -16
- package/agent/hermes_cli/profile_distribution.py +6 -1
- package/agent/hermes_cli/profiles.py +283 -32
- package/agent/hermes_cli/provider_catalog.py +170 -0
- package/agent/hermes_cli/providers.py +4 -1
- package/agent/hermes_cli/pty_bridge.py +53 -4
- package/agent/hermes_cli/runtime_provider.py +216 -34
- package/agent/hermes_cli/secret_prompt.py +4 -4
- package/agent/hermes_cli/secrets_cli.py +24 -0
- package/agent/hermes_cli/send_cmd.py +28 -2
- package/agent/hermes_cli/service_manager.py +166 -19
- package/agent/hermes_cli/session_listing.py +97 -0
- package/agent/hermes_cli/setup.py +158 -94
- package/agent/hermes_cli/setup_whatsapp_cloud.py +541 -0
- package/agent/hermes_cli/skills_config.py +8 -2
- package/agent/hermes_cli/skills_hub.py +149 -7
- package/agent/hermes_cli/status.py +2 -2
- package/agent/hermes_cli/subcommands/__init__.py +18 -0
- package/agent/hermes_cli/subcommands/_shared.py +29 -0
- package/agent/hermes_cli/subcommands/acp.py +52 -0
- package/agent/hermes_cli/subcommands/auth.py +109 -0
- package/agent/hermes_cli/subcommands/backup.py +38 -0
- package/agent/hermes_cli/subcommands/claw.py +92 -0
- package/agent/hermes_cli/subcommands/config.py +49 -0
- package/agent/hermes_cli/subcommands/cron.py +163 -0
- package/agent/hermes_cli/subcommands/dashboard.py +143 -0
- package/agent/hermes_cli/subcommands/debug.py +77 -0
- package/agent/hermes_cli/subcommands/doctor.py +35 -0
- package/agent/hermes_cli/subcommands/dump.py +28 -0
- package/agent/hermes_cli/subcommands/gateway.py +332 -0
- package/agent/hermes_cli/subcommands/gui.py +63 -0
- package/agent/hermes_cli/subcommands/hooks.py +77 -0
- package/agent/hermes_cli/subcommands/import_cmd.py +31 -0
- package/agent/hermes_cli/subcommands/insights.py +25 -0
- package/agent/hermes_cli/subcommands/login.py +78 -0
- package/agent/hermes_cli/subcommands/logout.py +28 -0
- package/agent/hermes_cli/subcommands/logs.py +78 -0
- package/agent/hermes_cli/subcommands/mcp.py +108 -0
- package/agent/hermes_cli/subcommands/memory.py +53 -0
- package/agent/hermes_cli/subcommands/model.py +72 -0
- package/agent/hermes_cli/subcommands/pairing.py +36 -0
- package/agent/hermes_cli/subcommands/plugins.py +94 -0
- package/agent/hermes_cli/subcommands/postinstall.py +23 -0
- package/agent/hermes_cli/subcommands/profile.py +203 -0
- package/agent/hermes_cli/subcommands/prompt_size.py +36 -0
- package/agent/hermes_cli/subcommands/security.py +62 -0
- package/agent/hermes_cli/subcommands/setup.py +58 -0
- package/agent/hermes_cli/subcommands/skills.py +298 -0
- package/agent/hermes_cli/subcommands/slack.py +60 -0
- package/agent/hermes_cli/subcommands/status.py +28 -0
- package/agent/hermes_cli/subcommands/tools.py +95 -0
- package/agent/hermes_cli/subcommands/uninstall.py +41 -0
- package/agent/hermes_cli/subcommands/update.py +70 -0
- package/agent/hermes_cli/subcommands/version.py +18 -0
- package/agent/hermes_cli/subcommands/webhook.py +76 -0
- package/agent/hermes_cli/subcommands/whatsapp.py +22 -0
- package/agent/hermes_cli/suggestions_cmd.py +153 -0
- package/agent/hermes_cli/telegram_managed_bot.py +358 -0
- package/agent/hermes_cli/tips.py +3 -4
- package/agent/hermes_cli/tools_config.py +155 -28
- package/agent/hermes_cli/uninstall.py +231 -35
- package/agent/hermes_cli/web_server.py +6188 -975
- package/agent/hermes_cli/win_pty_bridge.py +179 -0
- package/agent/hermes_cli/write_approval_commands.py +209 -0
- package/agent/hermes_constants.py +164 -33
- package/agent/hermes_logging.py +74 -2
- package/agent/hermes_state.py +919 -106
- package/agent/hermes_time.py +20 -0
- package/agent/locales/af.yaml +23 -0
- package/agent/locales/de.yaml +23 -0
- package/agent/locales/en.yaml +20 -0
- package/agent/locales/es.yaml +23 -0
- package/agent/locales/fr.yaml +23 -0
- package/agent/locales/ga.yaml +23 -0
- package/agent/locales/hu.yaml +23 -0
- package/agent/locales/it.yaml +23 -0
- package/agent/locales/ja.yaml +23 -0
- package/agent/locales/ko.yaml +23 -0
- package/agent/locales/pt.yaml +23 -0
- package/agent/locales/ru.yaml +23 -0
- package/agent/locales/tr.yaml +23 -0
- package/agent/locales/uk.yaml +23 -0
- package/agent/locales/zh-hant.yaml +23 -0
- package/agent/locales/zh.yaml +23 -0
- package/agent/model_tools.py +204 -40
- package/agent/optional-mcps/clawpump/manifest.yaml +15 -5
- package/agent/optional-mcps/clawpump-stdio/manifest.yaml +14 -4
- package/agent/optional-mcps/unreal-engine/manifest.yaml +54 -0
- package/agent/optional-skills/blockchain/hyperliquid/SKILL.md +2 -2
- package/agent/optional-skills/blockchain/hyperliquid/scripts/hyperliquid_client.py +1 -1
- package/agent/optional-skills/creative/kanban-video-orchestrator/SKILL.md +1 -1
- package/agent/optional-skills/creative/kanban-video-orchestrator/assets/setup.sh.tmpl +4 -3
- package/agent/optional-skills/creative/kanban-video-orchestrator/references/kanban-setup.md +6 -4
- package/agent/optional-skills/creative/kanban-video-orchestrator/references/tool-matrix.md +2 -2
- package/agent/{skills/software-development → optional-skills/devops}/hermes-s6-container-supervision/SKILL.md +2 -0
- package/agent/optional-skills/devops/watchers/SKILL.md +1 -1
- package/agent/optional-skills/devops/watchers/scripts/watch_github.py +2 -1
- package/agent/optional-skills/payments/mpp-agent/SKILL.md +124 -0
- package/agent/optional-skills/payments/stripe-link-cli/SKILL.md +184 -0
- package/agent/optional-skills/payments/stripe-projects/SKILL.md +120 -0
- package/agent/optional-skills/productivity/canvas/SKILL.md +1 -1
- package/agent/optional-skills/productivity/canvas/scripts/canvas_api.py +4 -1
- package/agent/optional-skills/productivity/shop/SKILL.md +224 -0
- package/agent/optional-skills/productivity/shop/references/catalog-mcp.md +236 -0
- package/agent/optional-skills/productivity/shop/references/direct-api.md +278 -0
- package/agent/optional-skills/productivity/shop/references/legal.md +3 -0
- package/agent/optional-skills/productivity/shop/references/safety.md +36 -0
- package/agent/optional-skills/productivity/shopify/SKILL.md +1 -1
- package/agent/optional-skills/productivity/siyuan/SKILL.md +1 -1
- package/agent/optional-skills/productivity/telephony/SKILL.md +4 -4
- package/agent/optional-skills/productivity/telephony/scripts/telephony.py +15 -15
- package/agent/optional-skills/security/1password/SKILL.md +1 -1
- package/agent/{skills/red-teaming → optional-skills/security}/godmode/SKILL.md +3 -4
- package/agent/{skills/red-teaming → optional-skills/security}/godmode/scripts/auto_jailbreak.py +3 -1
- package/agent/optional-skills/software-development/rest-graphql-debug/SKILL.md +1 -1
- package/agent/{skills → optional-skills}/software-development/subagent-driven-development/SKILL.md +5 -5
- package/agent/package-lock.json +4082 -7907
- package/agent/package.json +18 -3
- package/agent/plugins/browser/firecrawl/provider.py +4 -1
- package/agent/plugins/cron/__init__.py +344 -0
- package/agent/plugins/cron/chronos/__init__.py +241 -0
- package/agent/plugins/cron/chronos/_nas_client.py +123 -0
- package/agent/plugins/cron/chronos/plugin.yaml +9 -0
- package/agent/plugins/cron/chronos/verify.py +103 -0
- package/agent/plugins/dashboard_auth/basic/__init__.py +491 -0
- package/agent/plugins/dashboard_auth/basic/plugin.yaml +7 -0
- package/agent/plugins/dashboard_auth/nous/__init__.py +12 -14
- package/agent/plugins/dashboard_auth/self_hosted/__init__.py +736 -0
- package/agent/plugins/dashboard_auth/self_hosted/plugin.yaml +8 -0
- package/agent/plugins/disk-cleanup/disk_cleanup.py +100 -20
- package/agent/plugins/google_meet/audio_bridge.py +4 -0
- package/agent/plugins/google_meet/meet_bot.py +7 -1
- package/agent/plugins/hermes-achievements/dashboard/dist/index.js +9 -15
- package/agent/plugins/image_gen/fal/__init__.py +35 -6
- package/agent/plugins/image_gen/krea/__init__.py +56 -13
- package/agent/plugins/image_gen/openai/__init__.py +122 -24
- package/agent/plugins/image_gen/openai-codex/__init__.py +28 -2
- package/agent/plugins/image_gen/xai/__init__.py +92 -12
- package/agent/plugins/kanban/dashboard/dist/index.js +63 -48
- package/agent/plugins/kanban/dashboard/plugin_api.py +39 -35
- package/agent/plugins/memory/__init__.py +48 -5
- package/agent/plugins/memory/byterover/__init__.py +1 -0
- package/agent/plugins/memory/hindsight/README.md +1 -1
- package/agent/plugins/memory/hindsight/__init__.py +138 -24
- package/agent/plugins/memory/hindsight/plugin.yaml +1 -1
- package/agent/plugins/memory/honcho/README.md +13 -10
- package/agent/plugins/memory/honcho/cli.py +247 -122
- package/agent/plugins/memory/honcho/client.py +112 -102
- package/agent/plugins/memory/openviking/README.md +12 -1
- package/agent/plugins/memory/openviking/__init__.py +2281 -107
- package/agent/plugins/memory/openviking/plugin.yaml +1 -2
- package/agent/plugins/memory/supermemory/README.md +22 -10
- package/agent/plugins/memory/supermemory/__init__.py +142 -37
- package/agent/plugins/memory/supermemory/plugin.yaml +1 -1
- package/agent/plugins/model-providers/anthropic/__init__.py +1 -0
- package/agent/plugins/model-providers/bedrock/__init__.py +1 -0
- package/agent/plugins/model-providers/copilot-acp/__init__.py +1 -0
- package/agent/plugins/model-providers/custom/__init__.py +8 -2
- package/agent/plugins/model-providers/kimi-coding/__init__.py +16 -7
- package/agent/plugins/model-providers/minimax/__init__.py +60 -8
- package/agent/plugins/model-providers/opencode-zen/__init__.py +12 -3
- package/agent/plugins/model-providers/openrouter/__init__.py +75 -4
- package/agent/plugins/model-providers/xiaomi/__init__.py +2 -0
- package/agent/plugins/model-providers/zai/__init__.py +1 -0
- package/agent/plugins/observability/langfuse/__init__.py +147 -14
- package/agent/plugins/observability/nemo_relay/README.md +559 -0
- package/agent/plugins/observability/nemo_relay/__init__.py +962 -0
- package/agent/plugins/observability/nemo_relay/plugin.yaml +20 -0
- package/agent/plugins/platforms/discord/adapter.py +932 -61
- package/agent/plugins/platforms/discord/voice_mixer.py +379 -0
- package/agent/plugins/platforms/google_chat/adapter.py +9 -3
- package/agent/plugins/platforms/google_chat/oauth.py +1 -1
- package/agent/plugins/platforms/homeassistant/__init__.py +3 -0
- package/agent/{gateway/platforms/homeassistant.py → plugins/platforms/homeassistant/adapter.py} +128 -0
- package/agent/plugins/platforms/homeassistant/plugin.yaml +22 -0
- package/agent/plugins/platforms/irc/adapter.py +4 -1
- package/agent/plugins/platforms/line/adapter.py +16 -1
- package/agent/plugins/platforms/mattermost/adapter.py +100 -24
- package/agent/plugins/platforms/photon/README.md +179 -0
- package/agent/plugins/platforms/photon/__init__.py +4 -0
- package/agent/plugins/platforms/photon/adapter.py +1586 -0
- package/agent/plugins/platforms/photon/auth.py +1046 -0
- package/agent/plugins/platforms/photon/cli.py +439 -0
- package/agent/plugins/platforms/photon/plugin.yaml +88 -0
- package/agent/plugins/platforms/photon/sidecar/README.md +52 -0
- package/agent/plugins/platforms/photon/sidecar/index.mjs +720 -0
- package/agent/plugins/platforms/photon/sidecar/package-lock.json +1730 -0
- package/agent/plugins/platforms/photon/sidecar/package.json +25 -0
- package/agent/plugins/platforms/photon/sidecar/patch-spectrum-mixed-attachments.mjs +155 -0
- package/agent/plugins/platforms/raft/__init__.py +3 -0
- package/agent/plugins/platforms/raft/adapter.py +774 -0
- package/agent/plugins/platforms/raft/plugin.yaml +19 -0
- package/agent/plugins/platforms/simplex/adapter.py +777 -220
- package/agent/plugins/platforms/simplex/plugin.yaml +21 -2
- package/agent/plugins/platforms/teams/adapter.py +175 -5
- package/agent/plugins/plugin_utils.py +135 -0
- package/agent/plugins/video_gen/fal/__init__.py +10 -3
- package/agent/plugins/web/searxng/provider.py +15 -2
- package/agent/plugins/web/xai/provider.py +2 -2
- package/agent/providers/base.py +22 -3
- package/agent/pyproject.toml +115 -21
- package/agent/run_agent.py +733 -39
- package/agent/scripts/build_skills_index.py +51 -19
- package/agent/scripts/check_subprocess_stdin.py +177 -0
- package/agent/scripts/contributor_audit.py +2 -0
- package/agent/scripts/docker_config_migrate.py +67 -0
- package/agent/scripts/install.cmd +3 -3
- package/agent/scripts/install.ps1 +580 -154
- package/agent/scripts/install.sh +402 -185
- package/agent/scripts/lib/node-bootstrap.sh +39 -4
- package/agent/scripts/release.py +183 -0
- package/agent/scripts/run_tests.sh +1 -0
- package/agent/scripts/run_tests_parallel.py +18 -23
- package/agent/scripts/whatsapp-bridge/bridge.js +25 -4
- package/agent/setup.py +59 -0
- package/agent/skills/autonomous-ai-agents/codex/SKILL.md +19 -0
- package/agent/skills/autonomous-ai-agents/hermes-agent/SKILL.md +10 -3
- package/agent/skills/{mcp/native-mcp/SKILL.md → autonomous-ai-agents/hermes-agent/references/native-mcp.md} +0 -13
- package/agent/skills/{devops/webhook-subscriptions/SKILL.md → autonomous-ai-agents/hermes-agent/references/webhooks.md} +1 -11
- package/agent/skills/clawpump/SKILL.md +53 -5
- package/agent/skills/devops/kanban-orchestrator/SKILL.md +1 -0
- package/agent/skills/devops/kanban-worker/SKILL.md +1 -0
- package/agent/skills/github/github-auth/SKILL.md +2 -2
- package/agent/skills/github/github-auth/scripts/gh-env.sh +2 -2
- package/agent/skills/github/github-code-review/SKILL.md +2 -2
- package/agent/skills/github/github-issues/SKILL.md +2 -2
- package/agent/skills/github/github-pr-workflow/SKILL.md +2 -2
- package/agent/skills/github/github-repo-management/SKILL.md +2 -2
- package/agent/skills/media/gif-search/SKILL.md +1 -1
- package/agent/skills/media/youtube-content/SKILL.md +10 -7
- package/agent/skills/media/youtube-content/scripts/fetch_transcript.py +3 -3
- package/agent/skills/note-taking/obsidian/SKILL.md +1 -1
- package/agent/skills/productivity/airtable/SKILL.md +2 -2
- package/agent/skills/productivity/google-workspace/scripts/setup.py +33 -7
- package/agent/skills/productivity/notion/SKILL.md +2 -2
- package/agent/skills/productivity/teams-meeting-pipeline/SKILL.md +1 -1
- package/agent/skills/research/llm-wiki/SKILL.md +1 -1
- package/agent/skills/social-media/xurl/SKILL.md +9 -0
- package/agent/skills/software-development/hermes-agent-skill-authoring/SKILL.md +1 -1
- package/agent/skills/software-development/plan/SKILL.md +285 -5
- package/agent/skills/software-development/requesting-code-review/SKILL.md +2 -2
- package/agent/skills/software-development/simplify-code/SKILL.md +212 -0
- package/agent/skills/software-development/spike/SKILL.md +2 -2
- package/agent/skills/software-development/systematic-debugging/SKILL.md +1 -1
- package/agent/skills/software-development/test-driven-development/SKILL.md +1 -1
- package/agent/tools/approval.py +302 -4
- package/agent/tools/async_delegation.py +386 -0
- package/agent/tools/blueprints.py +325 -0
- package/agent/tools/browser_cdp_tool.py +3 -3
- package/agent/tools/browser_tool.py +34 -6
- package/agent/tools/checkpoint_manager.py +31 -1
- package/agent/tools/clarify_tool.py +55 -5
- package/agent/tools/code_execution_tool.py +31 -14
- package/agent/tools/computer_use/cua_backend.py +81 -3
- package/agent/tools/computer_use/tool.py +79 -5
- package/agent/tools/computer_use/vision_routing.py +55 -3
- package/agent/tools/credential_files.py +31 -12
- package/agent/tools/cronjob_tools.py +30 -20
- package/agent/tools/delegate_tool.py +356 -31
- package/agent/tools/env_probe.py +1 -0
- package/agent/tools/environments/docker.py +163 -8
- package/agent/tools/environments/file_sync.py +2 -1
- package/agent/tools/environments/local.py +74 -23
- package/agent/tools/environments/singularity.py +4 -1
- package/agent/tools/environments/ssh.py +78 -11
- package/agent/tools/file_operations.py +277 -41
- package/agent/tools/file_tools.py +166 -28
- package/agent/tools/image_generation_tool.py +515 -29
- package/agent/tools/kanban_tools.py +99 -0
- package/agent/tools/lazy_deps.py +33 -2
- package/agent/tools/mcp_oauth.py +5 -5
- package/agent/tools/mcp_oauth_manager.py +7 -5
- package/agent/tools/mcp_tool.py +840 -33
- package/agent/tools/memory_tool.py +335 -38
- package/agent/tools/osv_check.py +15 -1
- package/agent/tools/process_registry.py +155 -11
- package/agent/tools/read_extract.py +248 -0
- package/agent/tools/read_terminal_tool.py +93 -0
- package/agent/tools/schema_sanitizer.py +38 -0
- package/agent/tools/send_message_tool.py +163 -49
- package/agent/tools/session_search_tool.py +189 -7
- package/agent/tools/skill_manager_tool.py +202 -3
- package/agent/tools/skill_usage.py +52 -4
- package/agent/tools/skills_hub.py +184 -44
- package/agent/tools/skills_sync.py +232 -5
- package/agent/tools/skills_tool.py +125 -11
- package/agent/tools/terminal_tool.py +148 -26
- package/agent/tools/tirith_security.py +2 -0
- package/agent/tools/todo_tool.py +32 -1
- package/agent/tools/transcription_tools.py +13 -5
- package/agent/tools/tts_tool.py +332 -38
- package/agent/tools/url_safety.py +52 -1
- package/agent/tools/vision_tools.py +124 -39
- package/agent/tools/voice_mode.py +4 -3
- package/agent/tools/web_tools.py +45 -15
- package/agent/tools/write_approval.py +493 -0
- package/agent/toolsets.py +34 -10
- package/agent/trajectory_compressor.py +81 -10
- package/agent/tui_gateway/entry.py +43 -6
- package/agent/tui_gateway/server.py +3335 -330
- package/agent/tui_gateway/slash_worker.py +61 -0
- package/agent/tui_gateway/ws.py +67 -9
- package/agent/ui-tui/eslint.config.mjs +0 -4
- package/agent/ui-tui/package.json +6 -6
- package/agent/ui-tui/packages/hermes-ink/package.json +1 -1
- package/agent/ui-tui/packages/hermes-ink/src/ink/app-mouse.test.ts +34 -1
- package/agent/ui-tui/packages/hermes-ink/src/ink/app-rawmode-mouse.test.ts +91 -0
- package/agent/ui-tui/packages/hermes-ink/src/ink/components/App.tsx +35 -2
- package/agent/ui-tui/packages/hermes-ink/src/ink/events/input-event.ts +4 -11
- package/agent/ui-tui/packages/hermes-ink/src/ink/parse-keypress.test.ts +23 -57
- package/agent/ui-tui/packages/hermes-ink/src/ink/parse-keypress.ts +11 -135
- package/agent/ui-tui/packages/hermes-ink/src/ink/termio/tokenize.test.ts +185 -0
- package/agent/ui-tui/packages/hermes-ink/src/ink/termio/tokenize.ts +37 -3
- package/agent/ui-tui/packages/hermes-ink/src/utils/execFileNoThrow.ts +5 -5
- package/agent/ui-tui/src/__tests__/appChromeStatusRule.test.tsx +217 -0
- package/agent/ui-tui/src/__tests__/appChromeStatusRuleDevCredits.test.tsx +73 -0
- package/agent/ui-tui/src/__tests__/approvalAction.test.ts +11 -0
- package/agent/ui-tui/src/__tests__/billingCommand.test.ts +301 -0
- package/agent/ui-tui/src/__tests__/blockLayout.test.ts +122 -0
- package/agent/ui-tui/src/__tests__/brandingMcpCount.test.ts +111 -0
- package/agent/ui-tui/src/__tests__/completionApply.test.ts +51 -0
- package/agent/ui-tui/src/__tests__/createGatewayEventHandler.test.ts +487 -2
- package/agent/ui-tui/src/__tests__/createSlashHandler.test.ts +54 -0
- package/agent/ui-tui/src/__tests__/creditsCommand.test.ts +144 -0
- package/agent/ui-tui/src/__tests__/gatewayClient.test.ts +120 -99
- package/agent/ui-tui/src/__tests__/gracefulExit.test.ts +11 -0
- package/agent/ui-tui/src/__tests__/memoryMonitor.test.ts +102 -0
- package/agent/ui-tui/src/__tests__/paths.test.ts +41 -1
- package/agent/ui-tui/src/__tests__/terminalModes.test.ts +22 -0
- package/agent/ui-tui/src/__tests__/text.test.ts +23 -0
- package/agent/ui-tui/src/__tests__/textInputFastEcho.test.ts +37 -0
- package/agent/ui-tui/src/__tests__/turnControllerNotice.test.ts +43 -0
- package/agent/ui-tui/src/__tests__/useInputHandlers.test.ts +38 -1
- package/agent/ui-tui/src/__tests__/virtualHeights.test.ts +8 -0
- package/agent/ui-tui/src/app/createGatewayEventHandler.ts +102 -7
- package/agent/ui-tui/src/app/interfaces.ts +64 -1
- package/agent/ui-tui/src/app/overlayStore.ts +18 -2
- package/agent/ui-tui/src/app/slash/commands/billing.ts +332 -0
- package/agent/ui-tui/src/app/slash/commands/core.ts +31 -2
- package/agent/ui-tui/src/app/slash/commands/credits.ts +57 -0
- package/agent/ui-tui/src/app/slash/commands/ops.ts +28 -0
- package/agent/ui-tui/src/app/slash/commands/session.ts +32 -4
- package/agent/ui-tui/src/app/slash/registry.ts +4 -0
- package/agent/ui-tui/src/app/turnController.ts +145 -2
- package/agent/ui-tui/src/app/uiStore.ts +2 -0
- package/agent/ui-tui/src/app/useInputHandlers.ts +42 -4
- package/agent/ui-tui/src/app/useMainApp.ts +54 -8
- package/agent/ui-tui/src/app/useSessionLifecycle.ts +40 -31
- package/agent/ui-tui/src/app/useSubmission.ts +23 -31
- package/agent/ui-tui/src/components/appChrome.tsx +112 -5
- package/agent/ui-tui/src/components/appLayout.tsx +9 -0
- package/agent/ui-tui/src/components/appOverlays.tsx +25 -1
- package/agent/ui-tui/src/components/billingOverlay.tsx +684 -0
- package/agent/ui-tui/src/components/branding.tsx +15 -3
- package/agent/ui-tui/src/components/messageLine.tsx +25 -3
- package/agent/ui-tui/src/components/pluginsHub.tsx +238 -0
- package/agent/ui-tui/src/components/prompts.tsx +31 -17
- package/agent/ui-tui/src/components/streamingAssistant.tsx +63 -55
- package/agent/ui-tui/src/components/textInput.tsx +16 -0
- package/agent/ui-tui/src/config/env.ts +12 -0
- package/agent/ui-tui/src/config/limits.ts +13 -0
- package/agent/ui-tui/src/domain/blockLayout.ts +146 -0
- package/agent/ui-tui/src/domain/paths.ts +24 -0
- package/agent/ui-tui/src/domain/slash.ts +40 -0
- package/agent/ui-tui/src/entry.tsx +35 -4
- package/agent/ui-tui/src/gatewayClient.ts +22 -10
- package/agent/ui-tui/src/gatewayTypes.ts +130 -1
- package/agent/ui-tui/src/lib/gracefulExit.ts +24 -4
- package/agent/ui-tui/src/lib/memory.test.ts +162 -0
- package/agent/ui-tui/src/lib/memory.ts +60 -1
- package/agent/ui-tui/src/lib/memoryMonitor.ts +79 -4
- package/agent/ui-tui/src/lib/osc52.ts +1 -1
- package/agent/ui-tui/src/lib/text.test.ts +32 -1
- package/agent/ui-tui/src/lib/text.ts +29 -2
- package/agent/ui-tui/src/lib/virtualHeights.ts +13 -0
- package/agent/ui-tui/src/types.ts +5 -0
- package/agent/ui-tui/tsconfig.build.json +0 -1
- package/agent/ui-tui/tsconfig.json +2 -1
- package/agent/utils.py +66 -2
- package/agent/uv.lock +308 -696
- package/agent/web/index.html +2 -2
- package/agent/web/package.json +11 -6
- package/agent/web/public/claw-bg.webp +0 -0
- package/agent/web/public/claw-logo.webp +0 -0
- package/agent/web/src/App.tsx +138 -48
- package/agent/web/src/components/AutomationBlueprints.tsx +225 -0
- package/agent/web/src/components/Backdrop.tsx +15 -0
- package/agent/web/src/components/ChatSessionList.tsx +260 -0
- package/agent/web/src/components/ChatSidebar.tsx +262 -78
- package/agent/web/src/components/ConfirmDialog.tsx +122 -0
- package/agent/web/src/components/ModelPickerDialog.tsx +111 -16
- package/agent/web/src/components/ModelReloadConfirm.tsx +40 -0
- package/agent/web/src/components/ProfileScopeBanner.tsx +30 -0
- package/agent/web/src/components/ProfileSwitcher.tsx +67 -0
- package/agent/web/src/components/ReasoningPicker.tsx +167 -0
- package/agent/web/src/components/SkillEditorDialog.tsx +215 -0
- package/agent/web/src/components/ThemeSwitcher.tsx +119 -4
- package/agent/web/src/components/ToolsetConfigDrawer.tsx +457 -0
- package/agent/web/src/contexts/PageHeaderProvider.tsx +7 -4
- package/agent/web/src/contexts/ProfileProvider.tsx +137 -0
- package/agent/web/src/contexts/SystemActions.tsx +6 -8
- package/agent/web/src/contexts/profile-context.ts +19 -0
- package/agent/web/src/contexts/useProfileScope.ts +6 -0
- package/agent/web/src/i18n/af.ts +5 -4
- package/agent/web/src/i18n/de.ts +5 -4
- package/agent/web/src/i18n/en.ts +58 -4
- package/agent/web/src/i18n/es.ts +5 -3
- package/agent/web/src/i18n/fr.ts +5 -3
- package/agent/web/src/i18n/ga.ts +5 -4
- package/agent/web/src/i18n/hu.ts +5 -4
- package/agent/web/src/i18n/it.ts +5 -4
- package/agent/web/src/i18n/ja.ts +5 -4
- package/agent/web/src/i18n/ko.ts +5 -4
- package/agent/web/src/i18n/pt.ts +5 -3
- package/agent/web/src/i18n/ru.ts +5 -4
- package/agent/web/src/i18n/tr.ts +5 -4
- package/agent/web/src/i18n/types.ts +59 -1
- package/agent/web/src/i18n/uk.ts +5 -3
- package/agent/web/src/i18n/zh-hant.ts +5 -4
- package/agent/web/src/i18n/zh.ts +5 -4
- package/agent/web/src/index.css +2 -2
- package/agent/web/src/lib/api.ts +819 -52
- package/agent/web/src/lib/dashboard-flags.ts +16 -7
- package/agent/web/src/lib/reasoning-effort.test.ts +48 -0
- package/agent/web/src/lib/reasoning-effort.ts +36 -0
- package/agent/web/src/lib/session-refresh.test.ts +21 -0
- package/agent/web/src/lib/session-refresh.ts +26 -0
- package/agent/web/src/pages/ChannelsPage.tsx +529 -68
- package/agent/web/src/pages/ChatPage.tsx +249 -56
- package/agent/web/src/pages/ConfigPage.tsx +11 -1
- package/agent/web/src/pages/CronPage.tsx +219 -31
- package/agent/web/src/pages/EnvPage.tsx +25 -6
- package/agent/web/src/pages/FilesPage.tsx +525 -0
- package/agent/web/src/pages/McpPage.tsx +80 -3
- package/agent/web/src/pages/ModelsPage.tsx +97 -12
- package/agent/web/src/pages/PluginsPage.tsx +1 -1
- package/agent/web/src/pages/ProfileBuilderPage.tsx +611 -0
- package/agent/web/src/pages/ProfilesPage.tsx +1038 -172
- package/agent/web/src/pages/SessionsPage.tsx +144 -13
- package/agent/web/src/pages/SkillsPage.tsx +851 -70
- package/agent/web/src/pages/SystemPage.tsx +340 -4
- package/agent/web/src/pages/WalletPage.tsx +401 -0
- package/agent/web/src/pages/WebhooksPage.tsx +145 -15
- package/agent/web/src/pages/X402Page.tsx +207 -0
- package/agent/web/src/plugins/registry.ts +28 -11
- package/agent/web/src/plugins/sdk.d.ts +160 -0
- package/agent/web/src/themes/context.tsx +112 -5
- package/agent/web/src/themes/fonts.ts +167 -0
- package/agent/web/src/themes/index.ts +7 -0
- package/agent/web/tsconfig.app.json +0 -1
- package/agent/web/vite.config.ts +1 -8
- package/agent/web/vitest.config.ts +16 -0
- package/package.json +1 -1
- package/agent/apps/desktop/package-lock.json +0 -18363
- package/agent/apps/desktop/src/app/chat/composer/skin-slash-popover.tsx +0 -56
- package/agent/apps/desktop/src/components/assistant-ui/thread-virtualizer.tsx +0 -382
- package/agent/apps/desktop/src/components/assistant-ui/todo-tool.tsx +0 -109
- package/agent/apps/desktop/src/components/chat/generated-image-context.tsx +0 -19
- package/agent/optional-skills/productivity/shop-app/SKILL.md +0 -340
- package/agent/skills/autonomous-ai-agents/kanban-codex-lane/SKILL.md +0 -277
- package/agent/skills/autonomous-ai-agents/kanban-codex-lane/templates/pmb-codex-lane-prompt.md +0 -57
- package/agent/skills/diagramming/DESCRIPTION.md +0 -3
- package/agent/skills/domain/DESCRIPTION.md +0 -24
- package/agent/skills/gifs/DESCRIPTION.md +0 -3
- package/agent/skills/inference-sh/DESCRIPTION.md +0 -19
- package/agent/skills/mcp/DESCRIPTION.md +0 -3
- package/agent/skills/media/spotify/SKILL.md +0 -135
- package/agent/skills/mlops/training/DESCRIPTION.md +0 -3
- package/agent/skills/mlops/vector-databases/DESCRIPTION.md +0 -3
- package/agent/skills/productivity/linear/SKILL.md +0 -380
- package/agent/skills/productivity/linear/scripts/linear_api.py +0 -445
- package/agent/skills/software-development/debugging-hermes-tui-commands/SKILL.md +0 -152
- package/agent/skills/software-development/writing-plans/SKILL.md +0 -297
- package/agent/ui-tui/package-lock.json +0 -7449
- package/agent/ui-tui/packages/hermes-ink/package-lock.json +0 -1289
- package/agent/web/package-lock.json +0 -8887
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/PORT_NOTES.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/SKILL.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/prompts/system.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/palettes/macaron.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/palettes/mono-ink.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/palettes/neon.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/palettes/warm.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/prompt-construction.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/style-presets.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/blueprint.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/chalkboard.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/editorial.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/elegant.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/fantasy-animation.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/flat-doodle.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/flat.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/ink-notes.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/intuition-machine.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/minimal.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/nature.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/notion.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/pixel-art.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/playful.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/retro.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/scientific.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/screen-print.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/sketch-notes.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/sketch.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/vector-illustration.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/vintage.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/warm.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/watercolor.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/usage.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/workflow.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/PORT_NOTES.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/SKILL.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/analysis-framework.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/chalk.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/ink-brush.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/ligne-claire.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/manga.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/minimalist.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/realistic.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/auto-selection.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/base-prompt.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/character-template.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/cinematic.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/dense.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/four-panel.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/mixed.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/splash.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/standard.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/webtoon.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/ohmsha-guide.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/partial-workflows.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/concept-story.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/four-panel.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/ohmsha.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/shoujo.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/wuxia.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/storyboard-template.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/action.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/dramatic.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/energetic.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/neutral.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/romantic.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/vintage.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/warm.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/workflow.md +0 -0
- /package/agent/{skills → optional-skills}/creative/creative-ideation/SKILL.md +0 -0
- /package/agent/{skills → optional-skills}/creative/creative-ideation/references/full-prompt-library.md +0 -0
- /package/agent/{skills → optional-skills}/creative/pixel-art/ATTRIBUTION.md +0 -0
- /package/agent/{skills → optional-skills}/creative/pixel-art/SKILL.md +0 -0
- /package/agent/{skills → optional-skills}/creative/pixel-art/references/palettes.md +0 -0
- /package/agent/{skills → optional-skills}/creative/pixel-art/scripts/__init__.py +0 -0
- /package/agent/{skills → optional-skills}/creative/pixel-art/scripts/palettes.py +0 -0
- /package/agent/{skills → optional-skills}/creative/pixel-art/scripts/pixel_art.py +0 -0
- /package/agent/{skills → optional-skills}/creative/pixel-art/scripts/pixel_art_video.py +0 -0
- /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/SKILL.md +0 -0
- /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/references/analysis-modules.md +0 -0
- /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/references/methods-guide.md +0 -0
- /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/templates/abliteration-config.yaml +0 -0
- /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/templates/analysis-study.yaml +0 -0
- /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/templates/batch-abliteration.yaml +0 -0
- /package/agent/{skills → optional-skills}/mlops/research/DESCRIPTION.md +0 -0
- /package/agent/{skills → optional-skills}/mlops/research/dspy/SKILL.md +0 -0
- /package/agent/{skills → optional-skills}/mlops/research/dspy/references/examples.md +0 -0
- /package/agent/{skills → optional-skills}/mlops/research/dspy/references/modules.md +0 -0
- /package/agent/{skills → optional-skills}/mlops/research/dspy/references/optimizers.md +0 -0
- /package/agent/{skills/red-teaming → optional-skills/security}/godmode/references/jailbreak-templates.md +0 -0
- /package/agent/{skills/red-teaming → optional-skills/security}/godmode/references/refusal-detection.md +0 -0
- /package/agent/{skills/red-teaming → optional-skills/security}/godmode/scripts/godmode_race.py +0 -0
- /package/agent/{skills/red-teaming → optional-skills/security}/godmode/scripts/load_godmode.py +0 -0
- /package/agent/{skills/red-teaming → optional-skills/security}/godmode/scripts/parseltongue.py +0 -0
- /package/agent/{skills/red-teaming → optional-skills/security}/godmode/templates/prefill-subtle.json +0 -0
- /package/agent/{skills/red-teaming → optional-skills/security}/godmode/templates/prefill.json +0 -0
- /package/agent/{skills → optional-skills}/software-development/subagent-driven-development/references/context-budget-discipline.md +0 -0
- /package/agent/{skills → optional-skills}/software-development/subagent-driven-development/references/gates-taxonomy.md +0 -0
|
@@ -0,0 +1,794 @@
|
|
|
1
|
+
"""Credits tracking for Nous inference API responses.
|
|
2
|
+
|
|
3
|
+
Parses x-nous-credits-* (and optional x-nous-tool-pool-*) headers from
|
|
4
|
+
inference responses into a validated CreditsState dataclass. Provides
|
|
5
|
+
depletion detection (paid_access), subscription-cap used_fraction, and
|
|
6
|
+
warn-once schema-version gating. This is the hardened parser used by all
|
|
7
|
+
live consumers (run_agent, tui_gateway) — not a dev-only shim.
|
|
8
|
+
|
|
9
|
+
Header schema (x-nous-credits-* family):
|
|
10
|
+
x-nous-credits-version contract/schema version
|
|
11
|
+
x-nous-credits-remaining-micros total remaining balance (micros)
|
|
12
|
+
x-nous-credits-remaining-usd same, formatted USD string
|
|
13
|
+
x-nous-credits-subscription-micros subscription balance (SIGNED; may be negative/debt)
|
|
14
|
+
x-nous-credits-subscription-usd same, formatted USD string
|
|
15
|
+
x-nous-credits-subscription-limit-micros subscription cap (PAIRED/optional)
|
|
16
|
+
x-nous-credits-subscription-limit-usd same, formatted USD string (PAIRED/optional)
|
|
17
|
+
x-nous-credits-rollover-micros rolled-over balance (micros)
|
|
18
|
+
x-nous-credits-purchased-micros purchased balance (micros)
|
|
19
|
+
x-nous-credits-purchased-usd same, formatted USD string
|
|
20
|
+
x-nous-credits-denominator-kind "subscription_cap" | "none"
|
|
21
|
+
x-nous-credits-paid-access "true" | "false" (STRING!)
|
|
22
|
+
x-nous-credits-disabled-reason reason string (header omitted when null)
|
|
23
|
+
x-nous-credits-as-of-ms server-side timestamp (ms epoch)
|
|
24
|
+
|
|
25
|
+
Tool-pool headers use a SEPARATE prefix:
|
|
26
|
+
x-nous-tool-pool-micros tool-pool balance (micros)
|
|
27
|
+
x-nous-tool-pool-gated-off "true" | "false" (STRING!)
|
|
28
|
+
|
|
29
|
+
Money is handled as micros ints only; *_usd values are preserved verbatim as
|
|
30
|
+
the raw strings the server sent (never re-parsed to float).
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
import logging
|
|
36
|
+
import os
|
|
37
|
+
import re
|
|
38
|
+
import time
|
|
39
|
+
from dataclasses import dataclass
|
|
40
|
+
from typing import Any, Mapping, Optional
|
|
41
|
+
|
|
42
|
+
from utils import is_truthy_value
|
|
43
|
+
|
|
44
|
+
logger = logging.getLogger(__name__)
|
|
45
|
+
|
|
46
|
+
# Warn-once latch: emit the version-unsupported warning at most once per process.
|
|
47
|
+
_version_warning_emitted: bool = False
|
|
48
|
+
|
|
49
|
+
# Valid denominator kinds (exhaustive set from the API contract).
|
|
50
|
+
_VALID_DENOMINATOR_KINDS = frozenset({"subscription_cap", "none"})
|
|
51
|
+
|
|
52
|
+
# USD format: optional leading minus, one-or-more digits, dot, exactly 2 digits.
|
|
53
|
+
_USD_RE = re.compile(r"^-?\d+\.\d{2}$")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# ── Internal helpers ─────────────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
_SENTINEL = object() # singleton sentinel for "parse failed"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _safe_int(value: Any) -> Any:
|
|
63
|
+
"""Parse a header value to an exact int (money-safe).
|
|
64
|
+
|
|
65
|
+
The contract guarantees every ``*_micros`` field is an integer string —
|
|
66
|
+
we parse with ``int()`` directly, NOT ``int(float(...))``, to avoid float-
|
|
67
|
+
precision loss above 2**53 that would silently corrupt large money values.
|
|
68
|
+
|
|
69
|
+
Returns the parsed int, or ``_SENTINEL`` if the value is not a valid integer
|
|
70
|
+
string (including float-shaped strings like "1.5"). The sentinel lets callers
|
|
71
|
+
detect the failure and return None from the overall parse (fail-hard-on-bad-
|
|
72
|
+
input, not silently coerce).
|
|
73
|
+
"""
|
|
74
|
+
if value is None:
|
|
75
|
+
return _SENTINEL
|
|
76
|
+
try:
|
|
77
|
+
return int(str(value))
|
|
78
|
+
except (TypeError, ValueError):
|
|
79
|
+
return _SENTINEL
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _validate_usd(value: Optional[str]) -> bool:
|
|
84
|
+
"""Return True iff value is a non-None string matching ^-?\\d+\\.\\d{2}$."""
|
|
85
|
+
if value is None:
|
|
86
|
+
return False
|
|
87
|
+
return bool(_USD_RE.match(value))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# ── CreditsState dataclass ───────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@dataclass
|
|
94
|
+
class CreditsState:
|
|
95
|
+
"""Full credits state parsed from x-nous-credits-* response headers."""
|
|
96
|
+
|
|
97
|
+
version: int = 0
|
|
98
|
+
remaining_micros: int = 0
|
|
99
|
+
remaining_usd: str = ""
|
|
100
|
+
subscription_micros: int = 0 # SIGNED — may be negative (debt). ONLY field allowed negative.
|
|
101
|
+
subscription_usd: str = ""
|
|
102
|
+
subscription_limit_micros: Optional[int] = None # PAIRED + OPTIONAL (only when subscription_cap)
|
|
103
|
+
subscription_limit_usd: Optional[str] = None
|
|
104
|
+
rollover_micros: int = 0
|
|
105
|
+
purchased_micros: int = 0
|
|
106
|
+
purchased_usd: str = ""
|
|
107
|
+
tool_pool_micros: int = 0
|
|
108
|
+
tool_pool_gated_off: bool = False
|
|
109
|
+
denominator_kind: str = "none" # "subscription_cap" | "none"
|
|
110
|
+
paid_access: bool = True # depletion keys off THIS == False, NEVER remaining==0
|
|
111
|
+
disabled_reason: Optional[str] = None # header omitted entirely when null
|
|
112
|
+
as_of_ms: int = 0
|
|
113
|
+
captured_at: float = 0.0 # time.time() when this was captured
|
|
114
|
+
from_header: bool = False # True only when populated by parse_credits_headers()
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def has_data(self) -> bool:
|
|
118
|
+
return self.captured_at > 0
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def age_seconds(self) -> float:
|
|
122
|
+
if not self.has_data:
|
|
123
|
+
return float("inf")
|
|
124
|
+
return time.time() - self.captured_at
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def depleted(self) -> bool:
|
|
128
|
+
"""True when the account has lost paid access.
|
|
129
|
+
|
|
130
|
+
Keyed off ``paid_access == False`` ONLY — never ``remaining_micros == 0``,
|
|
131
|
+
which would give a false positive whenever the balance is zero but access
|
|
132
|
+
is still live (e.g. subscription renewal pending).
|
|
133
|
+
"""
|
|
134
|
+
return not self.paid_access
|
|
135
|
+
|
|
136
|
+
@property
|
|
137
|
+
def used_fraction(self) -> Optional[float]:
|
|
138
|
+
"""Fraction of the subscription cap consumed, in [0.0, 1.0].
|
|
139
|
+
|
|
140
|
+
Computable only when ``subscription_limit_micros`` is a truthy (non-zero,
|
|
141
|
+
non-None) int. Guarded on the LIMIT FIELD, not ``denominator_kind`` —
|
|
142
|
+
the limit field is the real denominator; ``denominator_kind`` is metadata.
|
|
143
|
+
Returns None when there is no computable denominator (no limit, or limit==0).
|
|
144
|
+
"""
|
|
145
|
+
if not isinstance(self.subscription_limit_micros, int):
|
|
146
|
+
return None
|
|
147
|
+
if self.subscription_limit_micros <= 0:
|
|
148
|
+
return None
|
|
149
|
+
used = self.subscription_limit_micros - self.subscription_micros
|
|
150
|
+
return max(0.0, min(1.0, used / self.subscription_limit_micros))
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# ── Credits policy constants ─────────────────────────────────────────────────
|
|
154
|
+
# Switching credits notices from sticky→TTL later would also require wiring a
|
|
155
|
+
# paired *_TTL_MS companion for each notice kind — the field exists on AgentNotice
|
|
156
|
+
# but is not yet plumbed through the policy loop.
|
|
157
|
+
|
|
158
|
+
CREDITS_NOTICE_KIND = "sticky" # v1: credits notices are sticky
|
|
159
|
+
CREDITS_RESTORED_TTL_MS = 8000 # the only TTL notice in v1 (depletion-recovery confirmation)
|
|
160
|
+
|
|
161
|
+
# Usage-gauge bands (ascending). Each is (threshold_fraction, level, label_pct).
|
|
162
|
+
# The notice shows the HIGHEST band the current used_fraction has reached — a single
|
|
163
|
+
# escalating status-bar line (50 → 75 → 90), not three stacked notices. Crossing the
|
|
164
|
+
# next band up replaces the line; recovering below a band steps it back down. Edit
|
|
165
|
+
# this list to retune the bands; the policy derives everything from it.
|
|
166
|
+
CREDITS_USAGE_BANDS: tuple[tuple[float, str, int], ...] = (
|
|
167
|
+
(0.50, "info", 50),
|
|
168
|
+
(0.75, "warn", 75),
|
|
169
|
+
(0.90, "warn", 90),
|
|
170
|
+
)
|
|
171
|
+
CREDITS_USAGE_KEY = "credits.usage" # single key for the escalating usage notice
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
# ── AgentNotice (out-of-band notice payload; driver-agnostic) ────────────────
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@dataclass
|
|
178
|
+
class AgentNotice:
|
|
179
|
+
"""A structured, driver-agnostic out-of-band notice.
|
|
180
|
+
|
|
181
|
+
The agent fires these via ``AIAgent.notice_callback`` (and clears them via
|
|
182
|
+
``notice_clear_callback``); each driver renders it its own way — the TUI as a
|
|
183
|
+
status-bar override, the CLI as a console line, etc. v1 credits notices are all
|
|
184
|
+
``kind="sticky"``; ``kind``/``ttl_ms`` are kept fully expressive so a future
|
|
185
|
+
config/slash-command can switch them to TTL without touching the policy (a
|
|
186
|
+
single default seam — see L4).
|
|
187
|
+
"""
|
|
188
|
+
|
|
189
|
+
text: str
|
|
190
|
+
level: str = "info" # info | warn | error | success
|
|
191
|
+
kind: str = "sticky" # sticky | ttl
|
|
192
|
+
ttl_ms: Optional[int] = None # honored only when kind == "ttl"
|
|
193
|
+
key: Optional[str] = None # dedupe / fired-once-latch / clear key
|
|
194
|
+
id: Optional[str] = None
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
# ── is_free_tier_model (local-data-only free-model check) ────────────────────
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def is_free_tier_model(model: str, base_url: str = "") -> bool:
|
|
201
|
+
"""Return True when *model* is a Nous free-tier model, using ONLY local data.
|
|
202
|
+
|
|
203
|
+
Two signals, both zero-network:
|
|
204
|
+
|
|
205
|
+
1. The ``:free`` suffix — the canonical Nous free SKU marker (e.g.
|
|
206
|
+
``nvidia/nemotron-3-ultra:free``). Free by construction on the API side
|
|
207
|
+
(spend is forced to 0 for ``:free`` ids).
|
|
208
|
+
2. A peek into the in-process pricing cache in ``hermes_cli.models``
|
|
209
|
+
(populated when the model picker fetched ``/v1/models`` pricing for
|
|
210
|
+
*base_url*). PEEK ONLY — a cache miss never triggers a fetch. This is
|
|
211
|
+
CLI/TUI-session best-effort: gateway sessions never run the picker's
|
|
212
|
+
pricing fetch, so suppression there rests entirely on the ``:free``
|
|
213
|
+
suffix (which all Nous free SKUs carry).
|
|
214
|
+
|
|
215
|
+
Fail-open to False (the depleted notice still shows) on any error: wrongly
|
|
216
|
+
showing the warning is recoverable noise; wrongly hiding it on a paid model
|
|
217
|
+
would mask a real billing block.
|
|
218
|
+
"""
|
|
219
|
+
if not model:
|
|
220
|
+
return False
|
|
221
|
+
if model.endswith(":free"):
|
|
222
|
+
return True
|
|
223
|
+
if not base_url:
|
|
224
|
+
return False
|
|
225
|
+
try:
|
|
226
|
+
from hermes_cli.models import _is_model_free, _pricing_cache
|
|
227
|
+
|
|
228
|
+
# Mirror get_pricing_for_provider's key normalization: the agent's
|
|
229
|
+
# Nous base_url is /v1-suffixed (https://inference-api.nousresearch.com/v1)
|
|
230
|
+
# but the picker keys _pricing_cache on the pre-/v1 root.
|
|
231
|
+
key = base_url.rstrip("/")
|
|
232
|
+
if key.endswith("/v1"):
|
|
233
|
+
key = key[:-3].rstrip("/")
|
|
234
|
+
pricing = _pricing_cache.get(key)
|
|
235
|
+
if not pricing:
|
|
236
|
+
return False
|
|
237
|
+
return _is_model_free(model, pricing)
|
|
238
|
+
except Exception:
|
|
239
|
+
return False
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
# ── evaluate_credits_notices (pure reconciliation function) ──────────────────
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def evaluate_credits_notices(
|
|
246
|
+
state: CreditsState,
|
|
247
|
+
latch: dict,
|
|
248
|
+
*,
|
|
249
|
+
model_is_free: bool = False,
|
|
250
|
+
) -> tuple[list[AgentNotice], list[str]]:
|
|
251
|
+
"""Reconcile credits notices against the latch. Mutates ``latch`` IN PLACE.
|
|
252
|
+
|
|
253
|
+
latch = {"active": set[str], "seen_below_90": bool, "usage_band": Optional[int]}.
|
|
254
|
+
|
|
255
|
+
``model_is_free``: True when the session's active model is a Nous free-tier
|
|
256
|
+
model (see :func:`is_free_tier_model`). Suppresses the ``credits.depleted``
|
|
257
|
+
notice — a depleted account on a free model can keep inferencing, so the
|
|
258
|
+
error banner is noise (and confuses free-tier users who never had credits).
|
|
259
|
+
Suppression does NOT emit the "restored" success notice; that fires only on
|
|
260
|
+
a genuine ``paid_access`` flip back to True.
|
|
261
|
+
|
|
262
|
+
Returns ``(to_show: list[AgentNotice], to_clear: list[str])``.
|
|
263
|
+
Caller emits to_clear FIRST, then to_show.
|
|
264
|
+
|
|
265
|
+
Pure function — no I/O, no agent/run_agent imports.
|
|
266
|
+
"""
|
|
267
|
+
to_show: list[AgentNotice] = []
|
|
268
|
+
to_clear: list[str] = []
|
|
269
|
+
|
|
270
|
+
uf = state.used_fraction
|
|
271
|
+
|
|
272
|
+
# Crossing latch: once we've observed uf below the LOWEST band, escalating
|
|
273
|
+
# usage notices may fire. This prevents a brand-new session that opens
|
|
274
|
+
# mid-range from firing spuriously on the first observation (the cold-start
|
|
275
|
+
# seed primes this explicitly when it WANTS an open-high warning).
|
|
276
|
+
_lowest_band = CREDITS_USAGE_BANDS[0][0]
|
|
277
|
+
if uf is not None and uf < _lowest_band:
|
|
278
|
+
latch["seen_below_90"] = True # gate opened: usage-band notices may now fire
|
|
279
|
+
|
|
280
|
+
active = latch["active"]
|
|
281
|
+
|
|
282
|
+
# ── Conditions ───────────────────────────────────────────────────────────
|
|
283
|
+
# Highest band whose threshold the current usage has reached (None below all).
|
|
284
|
+
current_band: Optional[tuple[float, str, int]] = None
|
|
285
|
+
if uf is not None:
|
|
286
|
+
for band in CREDITS_USAGE_BANDS: # ascending → last match wins = highest
|
|
287
|
+
if uf >= band[0]:
|
|
288
|
+
current_band = band
|
|
289
|
+
# Top-up suppression: when the account holds purchased (top-up) credits,
|
|
290
|
+
# the subscription-cap gauge is the wrong denominator — warning "90% used"
|
|
291
|
+
# at a user sitting on $50 of top-up is noise (and it previously stuck
|
|
292
|
+
# PERMANENTLY alongside grant_spent at >=100%). Suppress the usage band
|
|
293
|
+
# entirely; the cap-reached case is covered by the grant_spent info notice
|
|
294
|
+
# below, which already names the remaining top-up balance. A top-up landing
|
|
295
|
+
# mid-session flips current_band → None and the clear path below removes
|
|
296
|
+
# any showing band line.
|
|
297
|
+
if state.purchased_micros > 0:
|
|
298
|
+
current_band = None
|
|
299
|
+
grant_cond = (
|
|
300
|
+
state.denominator_kind == "subscription_cap"
|
|
301
|
+
and uf is not None
|
|
302
|
+
and uf >= 1.0
|
|
303
|
+
and state.purchased_micros > 0
|
|
304
|
+
)
|
|
305
|
+
depleted_cond = not state.paid_access
|
|
306
|
+
|
|
307
|
+
# ── usage gauge (escalating single notice: 50 → 75 → 90) ──────────────────
|
|
308
|
+
# Show only the highest crossed band; replace the line when the band changes
|
|
309
|
+
# (climb or step-down on recovery); clear entirely when usage drops below the
|
|
310
|
+
# lowest band or the denominator disappears (uf is None).
|
|
311
|
+
shown_band = latch.get("usage_band") # the pct label currently displayed, or None
|
|
312
|
+
target_band = current_band[2] if (current_band and latch["seen_below_90"]) else None
|
|
313
|
+
if target_band != shown_band:
|
|
314
|
+
if CREDITS_USAGE_KEY in active:
|
|
315
|
+
to_clear.append(CREDITS_USAGE_KEY)
|
|
316
|
+
active.discard(CREDITS_USAGE_KEY)
|
|
317
|
+
if target_band is not None:
|
|
318
|
+
# Belt-and-suspenders: a producer could set subscription_limit_micros
|
|
319
|
+
# without subscription_limit_usd. Render "$? cap" rather than "$None cap".
|
|
320
|
+
_cap_usd = state.subscription_limit_usd or "?"
|
|
321
|
+
_level = current_band[1] # type: ignore[index] (current_band set when target_band set)
|
|
322
|
+
to_show.append(
|
|
323
|
+
AgentNotice(
|
|
324
|
+
text=f"{'⚠' if _level == 'warn' else '•'} Credits {target_band}% used · ${_cap_usd} cap",
|
|
325
|
+
level=_level,
|
|
326
|
+
kind=CREDITS_NOTICE_KIND,
|
|
327
|
+
key=CREDITS_USAGE_KEY,
|
|
328
|
+
id=CREDITS_USAGE_KEY,
|
|
329
|
+
)
|
|
330
|
+
)
|
|
331
|
+
active.add(CREDITS_USAGE_KEY)
|
|
332
|
+
latch["usage_band"] = target_band
|
|
333
|
+
|
|
334
|
+
# ── grant_spent ──────────────────────────────────────────────────────────
|
|
335
|
+
if grant_cond and "credits.grant_spent" not in active:
|
|
336
|
+
to_show.append(
|
|
337
|
+
AgentNotice(
|
|
338
|
+
text=f"• Grant spent · ${state.purchased_usd} top-up left",
|
|
339
|
+
level="info",
|
|
340
|
+
kind=CREDITS_NOTICE_KIND,
|
|
341
|
+
key="credits.grant_spent",
|
|
342
|
+
id="credits.grant_spent",
|
|
343
|
+
)
|
|
344
|
+
)
|
|
345
|
+
active.add("credits.grant_spent")
|
|
346
|
+
elif "credits.grant_spent" in active and not grant_cond:
|
|
347
|
+
to_clear.append("credits.grant_spent")
|
|
348
|
+
active.discard("credits.grant_spent")
|
|
349
|
+
|
|
350
|
+
# ── depleted ─────────────────────────────────────────────────────────────
|
|
351
|
+
# Suppressed while the active model is free: inference still works there,
|
|
352
|
+
# so the error banner would just alarm users (free-tier users especially,
|
|
353
|
+
# who never had paid credits to "lose").
|
|
354
|
+
show_depleted = depleted_cond and not model_is_free
|
|
355
|
+
if show_depleted and "credits.depleted" not in active:
|
|
356
|
+
to_show.append(
|
|
357
|
+
AgentNotice(
|
|
358
|
+
text="✕ Credit access paused · run /credits to top up",
|
|
359
|
+
level="error",
|
|
360
|
+
kind=CREDITS_NOTICE_KIND,
|
|
361
|
+
key="credits.depleted",
|
|
362
|
+
id="credits.depleted",
|
|
363
|
+
)
|
|
364
|
+
)
|
|
365
|
+
active.add("credits.depleted")
|
|
366
|
+
elif "credits.depleted" in active and not show_depleted:
|
|
367
|
+
to_clear.append("credits.depleted")
|
|
368
|
+
active.discard("credits.depleted")
|
|
369
|
+
if not depleted_cond:
|
|
370
|
+
# Genuine recovery (paid_access flipped back True): also emit the
|
|
371
|
+
# success notice. A clear caused by switching to a free model while
|
|
372
|
+
# still depleted must NOT claim access was restored.
|
|
373
|
+
to_show.append(
|
|
374
|
+
AgentNotice(
|
|
375
|
+
text="✓ Credit access restored",
|
|
376
|
+
level="success",
|
|
377
|
+
kind="ttl",
|
|
378
|
+
ttl_ms=CREDITS_RESTORED_TTL_MS,
|
|
379
|
+
key="credits.restored",
|
|
380
|
+
id="credits.restored",
|
|
381
|
+
)
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
return (to_show, to_clear)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
# ── parse_credits_headers ────────────────────────────────────────────────────
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def parse_credits_headers(
|
|
391
|
+
headers: Mapping[str, str],
|
|
392
|
+
provider: str = "",
|
|
393
|
+
) -> Optional[CreditsState]:
|
|
394
|
+
"""Parse x-nous-credits-* (and x-nous-tool-pool-*) headers into a CreditsState.
|
|
395
|
+
|
|
396
|
+
Returns None (miss) on ANY of:
|
|
397
|
+
- No ``x-nous-credits-version`` header present.
|
|
398
|
+
- Version != 1 (> 1 also emits a one-time logger.warning).
|
|
399
|
+
- Any ``*_micros`` field is non-integer, or negative for a non-subscription field.
|
|
400
|
+
- Any ``*_usd`` field doesn't match ``^-?\\d+\\.\\d{2}$``.
|
|
401
|
+
- ``denominator_kind`` is not in {"subscription_cap", "none"}.
|
|
402
|
+
- ``paid_access`` / ``tool_pool_gated_off`` is not exactly "true"/"false".
|
|
403
|
+
- ``as_of_ms`` is not a valid integer.
|
|
404
|
+
- Any unexpected exception.
|
|
405
|
+
|
|
406
|
+
Fail-open on the subscription_limit pair: a half-pair (only -micros or only
|
|
407
|
+
-usd present) is treated as both-absent; the overall parse STILL SUCCEEDS
|
|
408
|
+
but with subscription_limit_micros/usd both None.
|
|
409
|
+
"""
|
|
410
|
+
global _version_warning_emitted
|
|
411
|
+
|
|
412
|
+
try:
|
|
413
|
+
# Cheap probe before the full lowercase copy: bail when the version
|
|
414
|
+
# sentinel header is absent (the common case for non-Nous providers, on
|
|
415
|
+
# every API call) — skips allocating a dict over the whole response's
|
|
416
|
+
# headers on the hot path, while preserving case-insensitivity. Behaviour
|
|
417
|
+
# is identical: a missing version header was already a None return below.
|
|
418
|
+
if not any(k.lower() == "x-nous-credits-version" for k in headers):
|
|
419
|
+
return None
|
|
420
|
+
# Normalize to lowercase so lookups work regardless of how the server
|
|
421
|
+
# capitalises headers (HTTP header names are case-insensitive per RFC 7230).
|
|
422
|
+
lowered = {k.lower(): v for k, v in headers.items()}
|
|
423
|
+
|
|
424
|
+
# ── Version check ────────────────────────────────────────────────────
|
|
425
|
+
# Must be present and exactly 1; > 1 warns once then returns None.
|
|
426
|
+
version_raw = lowered.get("x-nous-credits-version")
|
|
427
|
+
if version_raw is None:
|
|
428
|
+
return None
|
|
429
|
+
version_val = _safe_int(version_raw)
|
|
430
|
+
if version_val is _SENTINEL:
|
|
431
|
+
return None
|
|
432
|
+
if version_val != 1:
|
|
433
|
+
if version_val > 1 and not _version_warning_emitted:
|
|
434
|
+
_version_warning_emitted = True
|
|
435
|
+
logger.warning(
|
|
436
|
+
"credits header version %d unsupported, ignoring — update Hermes",
|
|
437
|
+
version_val,
|
|
438
|
+
)
|
|
439
|
+
return None
|
|
440
|
+
|
|
441
|
+
# ── Helper: parse a required non-negative int field (fail → None) ───
|
|
442
|
+
def _req_nonneg(key: str) -> Any:
|
|
443
|
+
raw = lowered.get(key)
|
|
444
|
+
val = _safe_int(raw)
|
|
445
|
+
if val is _SENTINEL:
|
|
446
|
+
return _SENTINEL
|
|
447
|
+
if val < 0:
|
|
448
|
+
return _SENTINEL
|
|
449
|
+
return val
|
|
450
|
+
|
|
451
|
+
# ── Helper: parse a required int field that may be negative (subscription only) ─
|
|
452
|
+
def _req_int(key: str) -> Any:
|
|
453
|
+
raw = lowered.get(key)
|
|
454
|
+
val = _safe_int(raw)
|
|
455
|
+
if val is _SENTINEL:
|
|
456
|
+
return _SENTINEL
|
|
457
|
+
return val
|
|
458
|
+
|
|
459
|
+
# ── Parse micros fields ──────────────────────────────────────────────
|
|
460
|
+
remaining_micros = _req_nonneg("x-nous-credits-remaining-micros")
|
|
461
|
+
if remaining_micros is _SENTINEL:
|
|
462
|
+
return None
|
|
463
|
+
|
|
464
|
+
subscription_micros = _req_int("x-nous-credits-subscription-micros")
|
|
465
|
+
if subscription_micros is _SENTINEL:
|
|
466
|
+
return None
|
|
467
|
+
|
|
468
|
+
rollover_micros = _req_nonneg("x-nous-credits-rollover-micros")
|
|
469
|
+
if rollover_micros is _SENTINEL:
|
|
470
|
+
return None
|
|
471
|
+
|
|
472
|
+
purchased_micros = _req_nonneg("x-nous-credits-purchased-micros")
|
|
473
|
+
if purchased_micros is _SENTINEL:
|
|
474
|
+
return None
|
|
475
|
+
|
|
476
|
+
# tool_pool_micros is OPTIONAL: absent → 0 (default); present-but-invalid → None (miss).
|
|
477
|
+
_tp_raw = lowered.get("x-nous-tool-pool-micros")
|
|
478
|
+
if _tp_raw is None:
|
|
479
|
+
tool_pool_micros = 0
|
|
480
|
+
else:
|
|
481
|
+
_tp_val = _safe_int(_tp_raw)
|
|
482
|
+
if _tp_val is _SENTINEL or _tp_val < 0:
|
|
483
|
+
return None
|
|
484
|
+
tool_pool_micros = _tp_val
|
|
485
|
+
|
|
486
|
+
as_of_ms = _req_nonneg("x-nous-credits-as-of-ms")
|
|
487
|
+
if as_of_ms is _SENTINEL:
|
|
488
|
+
return None
|
|
489
|
+
|
|
490
|
+
# ── Validate USD strings ─────────────────────────────────────────────
|
|
491
|
+
remaining_usd = lowered.get("x-nous-credits-remaining-usd", "")
|
|
492
|
+
if not _validate_usd(remaining_usd):
|
|
493
|
+
return None
|
|
494
|
+
|
|
495
|
+
subscription_usd = lowered.get("x-nous-credits-subscription-usd", "")
|
|
496
|
+
if not _validate_usd(subscription_usd):
|
|
497
|
+
return None
|
|
498
|
+
|
|
499
|
+
purchased_usd = lowered.get("x-nous-credits-purchased-usd", "")
|
|
500
|
+
if not _validate_usd(purchased_usd):
|
|
501
|
+
return None
|
|
502
|
+
|
|
503
|
+
# ── subscription_limit_* PAIRED + OPTIONAL ───────────────────────────
|
|
504
|
+
# Both present → validate both; half-pair → treat BOTH as absent (parse
|
|
505
|
+
# still succeeds, just with no limit pair).
|
|
506
|
+
sub_limit_micros_raw = lowered.get("x-nous-credits-subscription-limit-micros")
|
|
507
|
+
sub_limit_usd_raw = lowered.get("x-nous-credits-subscription-limit-usd")
|
|
508
|
+
|
|
509
|
+
subscription_limit_micros: Optional[int] = None
|
|
510
|
+
subscription_limit_usd: Optional[str] = None
|
|
511
|
+
|
|
512
|
+
if sub_limit_micros_raw is not None and sub_limit_usd_raw is not None:
|
|
513
|
+
# Both present — validate both; any invalid → return None (bad data)
|
|
514
|
+
lm = _safe_int(sub_limit_micros_raw)
|
|
515
|
+
if lm is _SENTINEL:
|
|
516
|
+
return None
|
|
517
|
+
if lm < 0:
|
|
518
|
+
return None
|
|
519
|
+
if not _validate_usd(sub_limit_usd_raw):
|
|
520
|
+
return None
|
|
521
|
+
subscription_limit_micros = lm
|
|
522
|
+
subscription_limit_usd = sub_limit_usd_raw
|
|
523
|
+
# else: half-pair or both absent → leave both None, parse continues
|
|
524
|
+
|
|
525
|
+
# ── denominator_kind ─────────────────────────────────────────────────
|
|
526
|
+
denominator_kind = lowered.get("x-nous-credits-denominator-kind", "none")
|
|
527
|
+
if denominator_kind not in _VALID_DENOMINATOR_KINDS:
|
|
528
|
+
return None
|
|
529
|
+
|
|
530
|
+
# ── paid_access / tool_pool_gated_off ────────────────────────────────
|
|
531
|
+
# Both must be exactly "true" or "false" (case-insensitive). An absent
|
|
532
|
+
# paid_access header → fail-open (assume access); absent tool_pool_gated_off
|
|
533
|
+
# → default False. Present but invalid → return None.
|
|
534
|
+
if "x-nous-credits-paid-access" in lowered:
|
|
535
|
+
pa_raw = lowered["x-nous-credits-paid-access"].strip().lower()
|
|
536
|
+
if pa_raw not in ("true", "false"):
|
|
537
|
+
return None
|
|
538
|
+
paid_access = pa_raw == "true"
|
|
539
|
+
else:
|
|
540
|
+
paid_access = True # fail-open
|
|
541
|
+
|
|
542
|
+
if "x-nous-tool-pool-gated-off" in lowered:
|
|
543
|
+
tpgo_raw = lowered["x-nous-tool-pool-gated-off"].strip().lower()
|
|
544
|
+
if tpgo_raw not in ("true", "false"):
|
|
545
|
+
return None
|
|
546
|
+
tool_pool_gated_off = tpgo_raw == "true"
|
|
547
|
+
else:
|
|
548
|
+
tool_pool_gated_off = False
|
|
549
|
+
|
|
550
|
+
# ── disabled_reason: header omitted when null ────────────────────────
|
|
551
|
+
disabled_reason = lowered.get("x-nous-credits-disabled-reason") # None if absent
|
|
552
|
+
|
|
553
|
+
return CreditsState(
|
|
554
|
+
version=version_val,
|
|
555
|
+
remaining_micros=remaining_micros,
|
|
556
|
+
remaining_usd=remaining_usd,
|
|
557
|
+
subscription_micros=subscription_micros,
|
|
558
|
+
subscription_usd=subscription_usd,
|
|
559
|
+
subscription_limit_micros=subscription_limit_micros,
|
|
560
|
+
subscription_limit_usd=subscription_limit_usd,
|
|
561
|
+
rollover_micros=rollover_micros,
|
|
562
|
+
purchased_micros=purchased_micros,
|
|
563
|
+
purchased_usd=purchased_usd,
|
|
564
|
+
tool_pool_micros=tool_pool_micros,
|
|
565
|
+
tool_pool_gated_off=tool_pool_gated_off,
|
|
566
|
+
denominator_kind=denominator_kind,
|
|
567
|
+
paid_access=paid_access,
|
|
568
|
+
disabled_reason=disabled_reason,
|
|
569
|
+
as_of_ms=as_of_ms,
|
|
570
|
+
captured_at=time.time(),
|
|
571
|
+
from_header=True,
|
|
572
|
+
)
|
|
573
|
+
|
|
574
|
+
except Exception:
|
|
575
|
+
# Fail-open → miss, but leave a breadcrumb so a parser/import regression
|
|
576
|
+
# (feature silently dead) is distinguishable from a legitimate no-headers
|
|
577
|
+
# response in agent.log, without needing a dev flag.
|
|
578
|
+
logger.debug("credits ▸ parse_credits_headers raised (fail-open miss)", exc_info=True)
|
|
579
|
+
return None
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
# ── Dev test fixtures (HERMES_DEV_CREDITS_FIXTURE) ───────────────────────────
|
|
583
|
+
# Throwaway dev scaffolding: trigger any notice state on demand for testing,
|
|
584
|
+
# without real spend or Redis seeding. Set HERMES_DEV_CREDITS_FIXTURE to either a
|
|
585
|
+
# state NAME (fixed for the session) or a FILE PATH whose contents are a state
|
|
586
|
+
# name (re-read every turn → flip states live: `echo depleted > /tmp/cf`, take a
|
|
587
|
+
# turn; `echo healthy > /tmp/cf`, take a turn → recovery).
|
|
588
|
+
#
|
|
589
|
+
# A fixture drives THREE surfaces uniformly, so the whole credits UX is testable
|
|
590
|
+
# offline: (1) the per-turn capture/notice path (_capture_credits), (2) the
|
|
591
|
+
# cold-start seed at session open (conversation_loop → depletion/warn90 hydrate
|
|
592
|
+
# immediately), and (3) the /usage view (nous_credits_lines renders the fixture).
|
|
593
|
+
# `clear` / `none` / unset → real behaviour. Delete with the rest of the
|
|
594
|
+
# HERMES_DEV_CREDITS scaffolding.
|
|
595
|
+
_DEV_FIXTURES: dict[str, dict] = {
|
|
596
|
+
"healthy": dict( # used_fraction ~0.1, paid → no notice (recovery target)
|
|
597
|
+
remaining_micros=30_340_000, remaining_usd="30.34",
|
|
598
|
+
subscription_micros=18_000_000, subscription_usd="18.00",
|
|
599
|
+
subscription_limit_micros=20_000_000, subscription_limit_usd="20.00",
|
|
600
|
+
purchased_micros=12_340_000, purchased_usd="12.34",
|
|
601
|
+
denominator_kind="subscription_cap", paid_access=True,
|
|
602
|
+
),
|
|
603
|
+
"sub_50pct": dict( # used_fraction == 0.5 → credits.usage band 50 (info)
|
|
604
|
+
remaining_micros=10_000_000, remaining_usd="10.00",
|
|
605
|
+
subscription_micros=10_000_000, subscription_usd="10.00",
|
|
606
|
+
subscription_limit_micros=20_000_000, subscription_limit_usd="20.00",
|
|
607
|
+
denominator_kind="subscription_cap", paid_access=True,
|
|
608
|
+
),
|
|
609
|
+
"sub_75pct": dict( # used_fraction == 0.75 → credits.usage band 75 (warn)
|
|
610
|
+
remaining_micros=5_000_000, remaining_usd="5.00",
|
|
611
|
+
subscription_micros=5_000_000, subscription_usd="5.00",
|
|
612
|
+
subscription_limit_micros=20_000_000, subscription_limit_usd="20.00",
|
|
613
|
+
denominator_kind="subscription_cap", paid_access=True,
|
|
614
|
+
),
|
|
615
|
+
"sub_90pct": dict( # used_fraction == 0.9 → credits.usage band 90 (warn)
|
|
616
|
+
remaining_micros=2_000_000, remaining_usd="2.00",
|
|
617
|
+
subscription_micros=2_000_000, subscription_usd="2.00",
|
|
618
|
+
subscription_limit_micros=20_000_000, subscription_limit_usd="20.00",
|
|
619
|
+
denominator_kind="subscription_cap", paid_access=True,
|
|
620
|
+
),
|
|
621
|
+
"grant_exhausted": dict( # used_fraction == 1.0 + purchased>0 → credits.grant_spent
|
|
622
|
+
remaining_micros=12_340_000, remaining_usd="12.34",
|
|
623
|
+
subscription_micros=0, subscription_usd="0.00",
|
|
624
|
+
subscription_limit_micros=20_000_000, subscription_limit_usd="20.00",
|
|
625
|
+
purchased_micros=12_340_000, purchased_usd="12.34",
|
|
626
|
+
denominator_kind="subscription_cap", paid_access=True,
|
|
627
|
+
),
|
|
628
|
+
"depleted": dict( # paid_access False → credits.depleted (sticky)
|
|
629
|
+
remaining_micros=0, remaining_usd="0.00",
|
|
630
|
+
subscription_micros=0, subscription_usd="0.00",
|
|
631
|
+
purchased_micros=0, purchased_usd="0.00",
|
|
632
|
+
paid_access=False, disabled_reason="out_of_credits",
|
|
633
|
+
),
|
|
634
|
+
"debt": dict( # subscription in debt (negative, the only signed field) → depleted
|
|
635
|
+
remaining_micros=0, remaining_usd="0.00",
|
|
636
|
+
subscription_micros=-5_000_000, subscription_usd="-5.00",
|
|
637
|
+
subscription_limit_micros=20_000_000, subscription_limit_usd="20.00",
|
|
638
|
+
purchased_micros=0, purchased_usd="0.00",
|
|
639
|
+
denominator_kind="subscription_cap", paid_access=False,
|
|
640
|
+
disabled_reason="out_of_credits",
|
|
641
|
+
),
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
def dev_fixture_credits_state() -> Optional[CreditsState]:
|
|
646
|
+
"""Return a fixture CreditsState for HERMES_DEV_CREDITS_FIXTURE, or None.
|
|
647
|
+
|
|
648
|
+
The env value is a state name, OR a path to a file whose contents are a state
|
|
649
|
+
name (re-read each call → flip states live without a restart). Unknown name /
|
|
650
|
+
"clear" / "none" / unset → None (normal behaviour). Throwaway test scaffolding.
|
|
651
|
+
|
|
652
|
+
Hard prod-leak guard: a fixture applies ONLY when the dev flag HERMES_DEV_CREDITS
|
|
653
|
+
is also on, so a stray HERMES_DEV_CREDITS_FIXTURE (leaked into a shell profile, a
|
|
654
|
+
container env, a launch plist, …) can never surface fabricated balances/notices
|
|
655
|
+
on a real account.
|
|
656
|
+
"""
|
|
657
|
+
if not is_truthy_value(os.environ.get("HERMES_DEV_CREDITS")):
|
|
658
|
+
return None
|
|
659
|
+
raw = os.environ.get("HERMES_DEV_CREDITS_FIXTURE", "").strip()
|
|
660
|
+
if not raw:
|
|
661
|
+
return None
|
|
662
|
+
name = raw
|
|
663
|
+
if os.path.sep in raw or "/" in raw: # looks like a path → read the name from the file
|
|
664
|
+
try:
|
|
665
|
+
with open(raw, "r", encoding="utf-8") as fh:
|
|
666
|
+
name = fh.read().strip()
|
|
667
|
+
except OSError:
|
|
668
|
+
return None
|
|
669
|
+
spec = _DEV_FIXTURES.get(name.lower())
|
|
670
|
+
if not spec:
|
|
671
|
+
return None
|
|
672
|
+
# Stamp the fields the REAL parser always guarantees, so a fixture state is
|
|
673
|
+
# field-identical to a parse_credits_headers() result from equivalent headers
|
|
674
|
+
# (verified by the differential test): version is always 1, and purchased_usd
|
|
675
|
+
# is always a valid usd string (the parser rejects a missing/empty one, so a
|
|
676
|
+
# real zero-top-up account still carries "0.00"). Specs may override these.
|
|
677
|
+
merged = {"version": 1, "purchased_usd": "0.00", **spec}
|
|
678
|
+
return CreditsState(**merged, from_header=True, captured_at=time.time())
|
|
679
|
+
|
|
680
|
+
|
|
681
|
+
def _credits_state_from_account(info) -> Optional[CreditsState]:
|
|
682
|
+
"""Map a NousPortalAccountInfo into a header-shaped CreditsState for the seed.
|
|
683
|
+
|
|
684
|
+
Float account dollars → micros (plus a DISPLAY *_usd string — allowed, since
|
|
685
|
+
we're formatting account floats, NOT parsing a server-provided *_usd). Returns
|
|
686
|
+
None if the account can't yield a usable state (fail-open)."""
|
|
687
|
+
try:
|
|
688
|
+
_acc = getattr(info, "paid_service_access_info", None)
|
|
689
|
+
_sub = getattr(info, "subscription", None)
|
|
690
|
+
|
|
691
|
+
def _to_micros(dollars):
|
|
692
|
+
return int(round(dollars * 1_000_000)) if isinstance(dollars, (int, float)) else 0
|
|
693
|
+
|
|
694
|
+
def _to_usd(dollars):
|
|
695
|
+
# DISPLAY formatting of an account float (not a server *_usd string);
|
|
696
|
+
# "" when absent so render/notice copy falls back gracefully.
|
|
697
|
+
return f"{dollars:.2f}" if isinstance(dollars, (int, float)) else ""
|
|
698
|
+
|
|
699
|
+
_monthly = getattr(_sub, "monthly_credits", None)
|
|
700
|
+
_has_cap = isinstance(_monthly, (int, float)) and _monthly > 0
|
|
701
|
+
_paid = getattr(info, "paid_service_access", None)
|
|
702
|
+
return CreditsState(
|
|
703
|
+
remaining_micros=_to_micros(getattr(_acc, "total_usable_credits", None)),
|
|
704
|
+
remaining_usd=_to_usd(getattr(_acc, "total_usable_credits", None)),
|
|
705
|
+
subscription_micros=_to_micros(getattr(_acc, "subscription_credits_remaining", None)),
|
|
706
|
+
subscription_usd=_to_usd(getattr(_acc, "subscription_credits_remaining", None)),
|
|
707
|
+
subscription_limit_micros=_to_micros(_monthly) if _has_cap else None,
|
|
708
|
+
subscription_limit_usd=_to_usd(_monthly) if _has_cap else None,
|
|
709
|
+
purchased_micros=_to_micros(getattr(_acc, "purchased_credits_remaining", None)),
|
|
710
|
+
purchased_usd=_to_usd(getattr(_acc, "purchased_credits_remaining", None)),
|
|
711
|
+
rollover_micros=_to_micros(getattr(_sub, "rollover_credits", None)),
|
|
712
|
+
denominator_kind="subscription_cap" if _has_cap else "none",
|
|
713
|
+
paid_access=_paid if isinstance(_paid, bool) else True,
|
|
714
|
+
from_header=False,
|
|
715
|
+
captured_at=time.time(),
|
|
716
|
+
)
|
|
717
|
+
except Exception:
|
|
718
|
+
logger.debug("credits ▸ seed account→state mapping failed", exc_info=True)
|
|
719
|
+
return None
|
|
720
|
+
|
|
721
|
+
|
|
722
|
+
def _hydrate_seed_state(agent, state) -> None:
|
|
723
|
+
"""Install a seed CreditsState on the agent and fire the notice policy once.
|
|
724
|
+
|
|
725
|
+
Sets _credits_state, latches session-start remaining, and primes the crossing
|
|
726
|
+
gate (the cold-start snapshot IS the first observation, so a session that opens
|
|
727
|
+
already in a band warns immediately — the live header path keeps true crossing
|
|
728
|
+
semantics), then emits. Safe to call from a worker thread: emit already runs
|
|
729
|
+
off-thread in the TUI build path."""
|
|
730
|
+
agent._credits_state = state
|
|
731
|
+
if getattr(agent, "_credits_session_start_micros", None) is None:
|
|
732
|
+
agent._credits_session_start_micros = state.remaining_micros
|
|
733
|
+
_latch = getattr(agent, "_credits_latch", None)
|
|
734
|
+
if isinstance(_latch, dict) and state.used_fraction is not None:
|
|
735
|
+
_latch["seen_below_90"] = True
|
|
736
|
+
emit = getattr(agent, "_emit_credits_notices", None)
|
|
737
|
+
if callable(emit):
|
|
738
|
+
emit()
|
|
739
|
+
|
|
740
|
+
|
|
741
|
+
def seed_credits_at_session_start(agent) -> bool:
|
|
742
|
+
"""Hydrate agent._credits_state from /api/oauth/account (or a dev fixture) and
|
|
743
|
+
fire the notice policy, so depletion / usage-band warnings show at session OPEN.
|
|
744
|
+
|
|
745
|
+
Shared by (a) the TUI/desktop agent build (fires at "ready", before any message)
|
|
746
|
+
and (b) the first-turn conversation setup (fallback for plain CLI / when the
|
|
747
|
+
build path didn't seed). Idempotent: a second call is a no-op once a seed or a
|
|
748
|
+
real header has already populated _credits_state.
|
|
749
|
+
|
|
750
|
+
Returns True if it seeded this call, False otherwise (not nous / already seeded /
|
|
751
|
+
fail-open error). Never raises — credits must never block session startup.
|
|
752
|
+
"""
|
|
753
|
+
try:
|
|
754
|
+
if getattr(agent, "provider", "") != "nous":
|
|
755
|
+
return False
|
|
756
|
+
# Idempotent: don't re-seed if state already exists (seed or live header).
|
|
757
|
+
if getattr(agent, "_credits_state", None) is not None:
|
|
758
|
+
return False
|
|
759
|
+
fixture = None
|
|
760
|
+
try:
|
|
761
|
+
fixture = dev_fixture_credits_state()
|
|
762
|
+
except Exception:
|
|
763
|
+
fixture = None
|
|
764
|
+
if fixture is not None:
|
|
765
|
+
# Synchronous: a fixture is instant (no network), and tests rely on the
|
|
766
|
+
# state + notice landing before this returns.
|
|
767
|
+
_hydrate_seed_state(agent, fixture)
|
|
768
|
+
return True
|
|
769
|
+
|
|
770
|
+
# Real portal fetch is FIRE-AND-FORGET: a slow/unreachable portal must never
|
|
771
|
+
# delay session "ready". A daemon thread hydrates + emits when it resolves,
|
|
772
|
+
# re-checking idempotency first (a live inference header may land before it).
|
|
773
|
+
import threading
|
|
774
|
+
|
|
775
|
+
def _bg_seed() -> None:
|
|
776
|
+
try:
|
|
777
|
+
from hermes_cli.nous_account import get_nous_portal_account_info
|
|
778
|
+
info = get_nous_portal_account_info(force_fresh=True)
|
|
779
|
+
if getattr(agent, "_credits_state", None) is not None:
|
|
780
|
+
return # a live inference header beat us — don't clobber it
|
|
781
|
+
state = _credits_state_from_account(info)
|
|
782
|
+
if state is not None:
|
|
783
|
+
_hydrate_seed_state(agent, state)
|
|
784
|
+
except Exception:
|
|
785
|
+
logger.debug("credits ▸ session-start seed (background) failed", exc_info=True)
|
|
786
|
+
|
|
787
|
+
threading.Thread(target=_bg_seed, name="credits-seed", daemon=True).start()
|
|
788
|
+
return True
|
|
789
|
+
except Exception:
|
|
790
|
+
# Fail-open: any auth/portal hiccup leaves _credits_state as-is, never blocks.
|
|
791
|
+
# Innermost log across all four call sites (TUI build / CLI build / first
|
|
792
|
+
# turn / desktop), so a dead session-open seed is diagnosable in agent.log.
|
|
793
|
+
logger.debug("credits ▸ session-start seed failed (fail-open)", exc_info=True)
|
|
794
|
+
return False
|