@clawpump/claw-agent 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent/.dockerignore +67 -0
- package/agent/.envrc +1 -1
- package/agent/.gitattributes +8 -0
- package/agent/AGENTS.md +216 -4
- package/agent/CONTRIBUTING.md +46 -8
- package/agent/Dockerfile +78 -35
- package/agent/MANIFEST.in +2 -0
- package/agent/README.md +12 -5
- package/agent/README.ur-pk.md +261 -0
- package/agent/README.zh-CN.md +11 -8
- package/agent/SECURITY.md +5 -4
- package/agent/acp_adapter/provenance.py +127 -0
- package/agent/acp_adapter/server.py +112 -5
- package/agent/acp_adapter/session.py +1 -6
- package/agent/acp_registry/agent.json +2 -2
- package/agent/agent/account_usage.py +313 -1
- package/agent/agent/agent_init.py +140 -37
- package/agent/agent/agent_runtime_helpers.py +342 -83
- package/agent/agent/anthropic_adapter.py +320 -33
- package/agent/agent/auxiliary_client.py +525 -105
- package/agent/agent/background_review.py +157 -19
- package/agent/agent/bedrock_adapter.py +71 -6
- package/agent/agent/billing_view.py +295 -0
- package/agent/agent/chat_completion_helpers.py +229 -4
- package/agent/agent/codex_responses_adapter.py +86 -10
- package/agent/agent/codex_runtime.py +153 -1
- package/agent/agent/coding_context.py +738 -0
- package/agent/agent/context_compressor.py +392 -44
- package/agent/agent/context_references.py +34 -1
- package/agent/agent/conversation_compression.py +159 -22
- package/agent/agent/conversation_loop.py +643 -908
- package/agent/agent/copilot_acp_client.py +4 -11
- package/agent/agent/credential_pool.py +5 -3
- package/agent/agent/credits_tracker.py +794 -0
- package/agent/agent/curator.py +91 -18
- package/agent/agent/curator_backup.py +26 -10
- package/agent/agent/display.py +42 -1
- package/agent/agent/error_classifier.py +52 -3
- package/agent/agent/errors.py +3 -0
- package/agent/agent/file_safety.py +0 -17
- package/agent/agent/gemini_native_adapter.py +31 -1
- package/agent/agent/i18n.py +48 -4
- package/agent/agent/image_gen_provider.py +74 -5
- package/agent/agent/image_routing.py +29 -0
- package/agent/agent/insights.py +8 -17
- package/agent/agent/lsp/install.py +3 -0
- package/agent/agent/memory_manager.py +326 -31
- package/agent/agent/message_content.py +50 -0
- package/agent/agent/model_metadata.py +214 -3
- package/agent/agent/moonshot_schema.py +8 -1
- package/agent/agent/onboarding.py +60 -0
- package/agent/agent/prompt_builder.py +327 -37
- package/agent/agent/redact.py +1 -0
- package/agent/agent/runtime_cwd.py +34 -5
- package/agent/agent/secret_scope.py +205 -0
- package/agent/agent/secret_sources/bitwarden.py +34 -2
- package/agent/agent/skill_commands.py +90 -1
- package/agent/agent/skill_preprocessing.py +1 -0
- package/agent/agent/skill_utils.py +209 -36
- package/agent/agent/ssl_guard.py +94 -0
- package/agent/agent/system_prompt.py +133 -5
- package/agent/agent/tool_executor.py +496 -70
- package/agent/agent/transports/anthropic.py +83 -21
- package/agent/agent/transports/chat_completions.py +94 -5
- package/agent/agent/transports/codex.py +67 -2
- package/agent/agent/transports/codex_app_server.py +1 -0
- package/agent/agent/transports/codex_app_server_session.py +30 -0
- package/agent/agent/transports/types.py +12 -0
- package/agent/agent/turn_context.py +408 -0
- package/agent/agent/turn_finalizer.py +428 -0
- package/agent/agent/turn_retry_state.py +68 -0
- package/agent/agent/usage_pricing.py +3 -0
- package/agent/apps/bootstrap-installer/package.json +6 -5
- package/agent/apps/bootstrap-installer/src/routes/failure.tsx +12 -5
- package/agent/apps/bootstrap-installer/src/routes/progress.tsx +1 -3
- package/agent/apps/bootstrap-installer/src/store.ts +3 -2
- package/agent/apps/bootstrap-installer/src-tauri/src/bootstrap.rs +172 -7
- package/agent/apps/bootstrap-installer/src-tauri/src/events.rs +14 -1
- package/agent/apps/bootstrap-installer/src-tauri/src/paths.rs +29 -0
- package/agent/apps/bootstrap-installer/src-tauri/src/powershell.rs +93 -3
- package/agent/apps/bootstrap-installer/src-tauri/src/update.rs +695 -39
- package/agent/apps/bootstrap-installer/tsconfig.json +3 -4
- package/agent/apps/desktop/DESIGN.md +167 -0
- package/agent/apps/desktop/README.md +20 -16
- package/agent/apps/desktop/assets/icon.icns +0 -0
- package/agent/apps/desktop/assets/icon.ico +0 -0
- package/agent/apps/desktop/assets/icon.png +0 -0
- package/agent/apps/desktop/electron/backend-env.cjs +112 -0
- package/agent/apps/desktop/electron/backend-env.test.cjs +111 -0
- package/agent/apps/desktop/electron/backend-probes.test.cjs +3 -1
- package/agent/apps/desktop/electron/backend-ready.cjs +66 -0
- package/agent/apps/desktop/electron/bootstrap-platform.cjs +52 -0
- package/agent/apps/desktop/electron/bootstrap-platform.test.cjs +59 -1
- package/agent/apps/desktop/electron/bootstrap-runner.cjs +176 -38
- package/agent/apps/desktop/electron/bootstrap-runner.test.cjs +112 -1
- package/agent/apps/desktop/electron/connection-config.cjs +288 -0
- package/agent/apps/desktop/electron/connection-config.test.cjs +396 -0
- package/agent/apps/desktop/electron/dashboard-token.cjs +99 -0
- package/agent/apps/desktop/electron/dashboard-token.test.cjs +142 -0
- package/agent/apps/desktop/electron/desktop-uninstall.cjs +232 -0
- package/agent/apps/desktop/electron/desktop-uninstall.test.cjs +246 -0
- package/agent/apps/desktop/electron/entitlements.mac.inherit.plist +2 -0
- package/agent/apps/desktop/electron/fs-read-dir.cjs +109 -0
- package/agent/apps/desktop/electron/fs-read-dir.test.cjs +364 -0
- package/agent/apps/desktop/electron/gateway-ws-probe.cjs +188 -0
- package/agent/apps/desktop/electron/gateway-ws-probe.test.cjs +122 -0
- package/agent/apps/desktop/electron/git-root.cjs +54 -0
- package/agent/apps/desktop/electron/git-root.test.cjs +40 -0
- package/agent/apps/desktop/electron/git-worktrees.cjs +174 -0
- package/agent/apps/desktop/electron/hardening.cjs +123 -28
- package/agent/apps/desktop/electron/hardening.test.cjs +163 -0
- package/agent/apps/desktop/electron/main.cjs +3121 -331
- package/agent/apps/desktop/electron/oauth-net-request.cjs +20 -0
- package/agent/apps/desktop/electron/oauth-net-request.test.cjs +34 -0
- package/agent/apps/desktop/electron/preload.cjs +52 -2
- package/agent/apps/desktop/electron/session-windows.cjs +124 -0
- package/agent/apps/desktop/electron/session-windows.test.cjs +199 -0
- package/agent/apps/desktop/electron/update-rebuild.cjs +29 -0
- package/agent/apps/desktop/electron/update-rebuild.test.cjs +55 -0
- package/agent/apps/desktop/electron/update-remote.cjs +56 -0
- package/agent/apps/desktop/electron/update-remote.test.cjs +78 -0
- package/agent/apps/desktop/electron/vscode-marketplace.cjs +331 -0
- package/agent/apps/desktop/electron/vscode-marketplace.test.cjs +113 -0
- package/agent/apps/desktop/electron/windows-child-process.test.cjs +57 -0
- package/agent/apps/desktop/electron/windows-user-env.cjs +76 -0
- package/agent/apps/desktop/electron/windows-user-env.test.cjs +90 -0
- package/agent/apps/desktop/electron/workspace-cwd.cjs +38 -0
- package/agent/apps/desktop/electron/workspace-cwd.test.cjs +45 -0
- package/agent/apps/desktop/eslint.config.mjs +0 -3
- package/agent/apps/desktop/index.html +27 -2
- package/agent/apps/desktop/package.json +31 -11
- package/agent/apps/desktop/pr-assets/session-source-folders.png +0 -0
- package/agent/apps/desktop/public/apple-touch-icon.png +0 -0
- package/agent/apps/desktop/public/nous-girl.jpg +0 -0
- package/agent/apps/desktop/scripts/assert-dist-built.cjs +70 -0
- package/agent/apps/desktop/scripts/assert-dist-built.test.cjs +84 -0
- package/agent/apps/desktop/scripts/before-pack.cjs +78 -0
- package/agent/apps/desktop/scripts/before-pack.test.cjs +53 -0
- package/agent/apps/desktop/scripts/diag-scroll-reset.mjs +229 -0
- package/agent/apps/desktop/scripts/patch-electron-builder-mac-binary.cjs +64 -0
- package/agent/apps/desktop/scripts/run-electron-builder.cjs +57 -0
- package/agent/apps/desktop/src/app/agents/index.tsx +53 -45
- package/agent/apps/desktop/src/app/artifacts/index.tsx +102 -83
- package/agent/apps/desktop/src/app/chat/chat-drop-overlay.tsx +29 -8
- package/agent/apps/desktop/src/app/chat/chat-swap-overlay.tsx +47 -0
- package/agent/apps/desktop/src/app/chat/composer/attachments.tsx +81 -45
- package/agent/apps/desktop/src/app/chat/composer/completion-drawer.tsx +13 -24
- package/agent/apps/desktop/src/app/chat/composer/context-menu.tsx +138 -88
- package/agent/apps/desktop/src/app/chat/composer/controls.tsx +138 -90
- package/agent/apps/desktop/src/app/chat/composer/enter-submit-dom-race.test.tsx +218 -0
- package/agent/apps/desktop/src/app/chat/composer/focus.ts +32 -0
- package/agent/apps/desktop/src/app/chat/composer/help-hint.tsx +38 -25
- package/agent/apps/desktop/src/app/chat/composer/hooks/use-live-completion-adapter.ts +7 -0
- package/agent/apps/desktop/src/app/chat/composer/hooks/use-mic-recorder.ts +22 -12
- package/agent/apps/desktop/src/app/chat/composer/hooks/use-slash-completions.ts +142 -14
- package/agent/apps/desktop/src/app/chat/composer/hooks/use-voice-conversation.ts +14 -11
- package/agent/apps/desktop/src/app/chat/composer/hooks/use-voice-recorder.ts +9 -6
- package/agent/apps/desktop/src/app/chat/composer/ime-composition-dom-repro.test.tsx +108 -0
- package/agent/apps/desktop/src/app/chat/composer/index.tsx +930 -180
- package/agent/apps/desktop/src/app/chat/composer/inline-refs.ts +136 -32
- package/agent/apps/desktop/src/app/chat/composer/model-pill.tsx +86 -0
- package/agent/apps/desktop/src/app/chat/composer/queue-panel.tsx +54 -75
- package/agent/apps/desktop/src/app/chat/composer/rich-editor.test.ts +117 -1
- package/agent/apps/desktop/src/app/chat/composer/rich-editor.ts +117 -6
- package/agent/apps/desktop/src/app/chat/composer/slash-nav-dom-repro.test.tsx +186 -0
- package/agent/apps/desktop/src/app/chat/composer/status-stack/index.tsx +202 -0
- package/agent/apps/desktop/src/app/chat/composer/status-stack/status-row.tsx +155 -0
- package/agent/apps/desktop/src/app/chat/composer/text-utils.test.ts +104 -0
- package/agent/apps/desktop/src/app/chat/composer/text-utils.ts +37 -9
- package/agent/apps/desktop/src/app/chat/composer/trigger-popover.test.tsx +50 -0
- package/agent/apps/desktop/src/app/chat/composer/trigger-popover.tsx +105 -40
- package/agent/apps/desktop/src/app/chat/composer/types.ts +5 -0
- package/agent/apps/desktop/src/app/chat/composer/url-dialog.tsx +11 -15
- package/agent/apps/desktop/src/app/chat/composer/voice-activity.tsx +8 -4
- package/agent/apps/desktop/src/app/chat/hooks/use-composer-actions.test.ts +57 -0
- package/agent/apps/desktop/src/app/chat/hooks/use-composer-actions.ts +70 -16
- package/agent/apps/desktop/src/app/chat/hooks/use-file-drop-zone.ts +52 -16
- package/agent/apps/desktop/src/app/chat/index.tsx +234 -81
- package/agent/apps/desktop/src/app/chat/perf-probe.tsx +69 -21
- package/agent/apps/desktop/src/app/chat/right-rail/preview-console.tsx +44 -40
- package/agent/apps/desktop/src/app/chat/right-rail/preview-file.tsx +71 -25
- package/agent/apps/desktop/src/app/chat/right-rail/preview-pane.test.tsx +40 -1
- package/agent/apps/desktop/src/app/chat/right-rail/preview-pane.tsx +55 -53
- package/agent/apps/desktop/src/app/chat/right-rail/preview.tsx +35 -17
- package/agent/apps/desktop/src/app/chat/scroll-to-bottom-button.test.tsx +67 -0
- package/agent/apps/desktop/src/app/chat/scroll-to-bottom-button.tsx +74 -0
- package/agent/apps/desktop/src/app/chat/sidebar/cron-jobs-section.tsx +356 -0
- package/agent/apps/desktop/src/app/chat/sidebar/index.tsx +1189 -364
- package/agent/apps/desktop/src/app/chat/sidebar/load-more-row.tsx +30 -0
- package/agent/apps/desktop/src/app/chat/sidebar/order.test.ts +21 -0
- package/agent/apps/desktop/src/app/chat/sidebar/order.ts +17 -0
- package/agent/apps/desktop/src/app/chat/sidebar/profile-switcher.tsx +524 -0
- package/agent/apps/desktop/src/app/chat/sidebar/session-actions-menu.tsx +80 -45
- package/agent/apps/desktop/src/app/chat/sidebar/session-row.tsx +120 -25
- package/agent/apps/desktop/src/app/chat/sidebar/virtual-session-list.tsx +7 -13
- package/agent/apps/desktop/src/app/chat/sidebar/workspace-groups.test.ts +149 -0
- package/agent/apps/desktop/src/app/chat/sidebar/workspace-groups.ts +326 -0
- package/agent/apps/desktop/src/app/chat/thread-loading.ts +7 -2
- package/agent/apps/desktop/src/app/command-center/index.tsx +320 -581
- package/agent/apps/desktop/src/app/command-palette/index.tsx +681 -0
- package/agent/apps/desktop/src/app/command-palette/marketplace-theme-page.tsx +157 -0
- package/agent/apps/desktop/src/app/cron/index.tsx +392 -324
- package/agent/apps/desktop/src/app/cron/job-state.ts +29 -0
- package/agent/apps/desktop/src/app/desktop-controller.tsx +618 -123
- package/agent/apps/desktop/src/app/floating-hud.ts +22 -0
- package/agent/apps/desktop/src/app/gateway/hooks/use-gateway-boot.test.tsx +265 -0
- package/agent/apps/desktop/src/app/gateway/hooks/use-gateway-boot.ts +260 -14
- package/agent/apps/desktop/src/app/gateway/hooks/use-gateway-request.ts +48 -4
- package/agent/apps/desktop/src/app/hooks/use-keybinds.ts +270 -0
- package/agent/apps/desktop/src/app/hooks/use-refresh-hotkey.ts +45 -0
- package/agent/apps/desktop/src/app/layout-constants.ts +19 -0
- package/agent/apps/desktop/src/app/messaging/index.tsx +136 -241
- package/agent/apps/desktop/src/app/messaging/platform-icon.tsx +95 -0
- package/agent/apps/desktop/src/app/model-visibility-overlay.tsx +31 -0
- package/agent/apps/desktop/src/app/overlays/overlay-search-input.tsx +18 -62
- package/agent/apps/desktop/src/app/overlays/overlay-split-layout.tsx +59 -7
- package/agent/apps/desktop/src/app/overlays/overlay-view.tsx +9 -5
- package/agent/apps/desktop/src/app/page-search-shell.tsx +42 -20
- package/agent/apps/desktop/src/app/profiles/create-profile-dialog.tsx +165 -0
- package/agent/apps/desktop/src/app/profiles/delete-profile-dialog.tsx +65 -0
- package/agent/apps/desktop/src/app/profiles/index.tsx +174 -199
- package/agent/apps/desktop/src/app/profiles/rename-profile-dialog.tsx +125 -0
- package/agent/apps/desktop/src/app/right-sidebar/files/dnd-manager.ts +27 -0
- package/agent/apps/desktop/src/app/right-sidebar/files/ipc.test.ts +100 -0
- package/agent/apps/desktop/src/app/right-sidebar/files/ipc.ts +12 -18
- package/agent/apps/desktop/src/app/right-sidebar/files/remote-picker.tsx +177 -0
- package/agent/apps/desktop/src/app/right-sidebar/files/tree.tsx +35 -21
- package/agent/apps/desktop/src/app/right-sidebar/files/use-project-tree.test.ts +75 -3
- package/agent/apps/desktop/src/app/right-sidebar/files/use-project-tree.ts +152 -5
- package/agent/apps/desktop/src/app/right-sidebar/index.test.tsx +75 -0
- package/agent/apps/desktop/src/app/right-sidebar/index.tsx +166 -129
- package/agent/apps/desktop/src/app/right-sidebar/store.ts +19 -4
- package/agent/apps/desktop/src/app/right-sidebar/terminal/buffer.ts +65 -0
- package/agent/apps/desktop/src/app/right-sidebar/terminal/index.tsx +29 -34
- package/agent/apps/desktop/src/app/right-sidebar/terminal/persistent.tsx +18 -6
- package/agent/apps/desktop/src/app/right-sidebar/terminal/selection.ts +93 -32
- package/agent/apps/desktop/src/app/right-sidebar/terminal/use-terminal-session.ts +381 -119
- package/agent/apps/desktop/src/app/routes.ts +9 -0
- package/agent/apps/desktop/src/app/session/hooks/use-cwd-actions.ts +17 -7
- package/agent/apps/desktop/src/app/session/hooks/use-message-stream.ts +365 -47
- package/agent/apps/desktop/src/app/session/hooks/use-model-controls.test.tsx +198 -0
- package/agent/apps/desktop/src/app/session/hooks/use-model-controls.ts +70 -34
- package/agent/apps/desktop/src/app/session/hooks/use-prompt-actions.test.tsx +1061 -0
- package/agent/apps/desktop/src/app/session/hooks/use-prompt-actions.ts +1143 -165
- package/agent/apps/desktop/src/app/session/hooks/use-route-resume.test.tsx +341 -2
- package/agent/apps/desktop/src/app/session/hooks/use-route-resume.ts +176 -5
- package/agent/apps/desktop/src/app/session/hooks/use-session-actions.test.tsx +259 -0
- package/agent/apps/desktop/src/app/session/hooks/use-session-actions.ts +452 -149
- package/agent/apps/desktop/src/app/session/hooks/use-session-state-cache.test.tsx +327 -0
- package/agent/apps/desktop/src/app/session/hooks/use-session-state-cache.ts +133 -4
- package/agent/apps/desktop/src/app/session-picker-overlay.tsx +32 -0
- package/agent/apps/desktop/src/app/session-switcher.tsx +107 -0
- package/agent/apps/desktop/src/app/settings/about-settings.tsx +45 -36
- package/agent/apps/desktop/src/app/settings/appearance-settings.tsx +243 -162
- package/agent/apps/desktop/src/app/settings/config-settings.tsx +86 -66
- package/agent/apps/desktop/src/app/settings/constants.ts +459 -122
- package/agent/apps/desktop/src/app/settings/credential-key-ui.tsx +373 -0
- package/agent/apps/desktop/src/app/settings/env-credentials.tsx +198 -0
- package/agent/apps/desktop/src/app/settings/env-var-actions-menu.tsx +136 -0
- package/agent/apps/desktop/src/app/settings/field-copy.ts +56 -0
- package/agent/apps/desktop/src/app/settings/gateway-settings.tsx +385 -72
- package/agent/apps/desktop/src/app/settings/helpers.test.ts +156 -1
- package/agent/apps/desktop/src/app/settings/helpers.ts +30 -2
- package/agent/apps/desktop/src/app/settings/index.tsx +118 -84
- package/agent/apps/desktop/src/app/settings/keys-settings.tsx +62 -419
- package/agent/apps/desktop/src/app/settings/mcp-settings.tsx +65 -60
- package/agent/apps/desktop/src/app/settings/model-settings.test.tsx +129 -5
- package/agent/apps/desktop/src/app/settings/model-settings.tsx +370 -65
- package/agent/apps/desktop/src/app/settings/notifications-settings.tsx +150 -0
- package/agent/apps/desktop/src/app/settings/primitives.tsx +5 -11
- package/agent/apps/desktop/src/app/settings/provider-config-panel.test.tsx +142 -0
- package/agent/apps/desktop/src/app/settings/provider-config-panel.tsx +182 -0
- package/agent/apps/desktop/src/app/settings/providers-settings.test.tsx +171 -0
- package/agent/apps/desktop/src/app/settings/providers-settings.tsx +471 -0
- package/agent/apps/desktop/src/app/settings/sessions-settings.tsx +183 -71
- package/agent/apps/desktop/src/app/settings/toolset-config-panel.test.tsx +135 -1
- package/agent/apps/desktop/src/app/settings/toolset-config-panel.tsx +180 -57
- package/agent/apps/desktop/src/app/settings/types.ts +9 -6
- package/agent/apps/desktop/src/app/settings/uninstall-section.tsx +185 -0
- package/agent/apps/desktop/src/app/settings/use-deep-link-highlight.ts +60 -0
- package/agent/apps/desktop/src/app/shell/app-shell.tsx +59 -13
- package/agent/apps/desktop/src/app/shell/gateway-menu-panel.tsx +37 -32
- package/agent/apps/desktop/src/app/shell/hooks/use-overlay-routing.ts +6 -3
- package/agent/apps/desktop/src/app/shell/hooks/use-statusbar-items.tsx +212 -53
- package/agent/apps/desktop/src/app/shell/keybind-panel.tsx +215 -0
- package/agent/apps/desktop/src/app/shell/model-edit-submenu.test.tsx +84 -0
- package/agent/apps/desktop/src/app/shell/model-edit-submenu.tsx +244 -0
- package/agent/apps/desktop/src/app/shell/model-menu-panel.tsx +392 -0
- package/agent/apps/desktop/src/app/shell/statusbar-controls.tsx +23 -33
- package/agent/apps/desktop/src/app/shell/titlebar-controls.tsx +79 -95
- package/agent/apps/desktop/src/app/shell/titlebar.ts +8 -2
- package/agent/apps/desktop/src/app/skills/index.test.tsx +11 -0
- package/agent/apps/desktop/src/app/skills/index.tsx +79 -64
- package/agent/apps/desktop/src/app/types.ts +85 -0
- package/agent/apps/desktop/src/app/updates-overlay.tsx +110 -105
- package/agent/apps/desktop/src/components/assistant-ui/ansi-text.tsx +34 -0
- package/agent/apps/desktop/src/components/assistant-ui/block-direction.test.tsx +129 -0
- package/agent/apps/desktop/src/components/assistant-ui/clarify-tool.tsx +102 -81
- package/agent/apps/desktop/src/components/assistant-ui/directive-text.tsx +92 -15
- package/agent/apps/desktop/src/components/assistant-ui/markdown-text.test.ts +38 -0
- package/agent/apps/desktop/src/components/assistant-ui/markdown-text.tsx +304 -45
- package/agent/apps/desktop/src/components/assistant-ui/message-render-boundary.test.tsx +80 -0
- package/agent/apps/desktop/src/components/assistant-ui/message-render-boundary.tsx +48 -0
- package/agent/apps/desktop/src/components/assistant-ui/streaming.test.tsx +142 -90
- package/agent/apps/desktop/src/components/assistant-ui/thread-list.tsx +337 -0
- package/agent/apps/desktop/src/components/assistant-ui/thread.tsx +667 -190
- package/agent/apps/desktop/src/components/assistant-ui/tool-approval-group.test.tsx +299 -0
- package/agent/apps/desktop/src/components/assistant-ui/tool-approval.test.tsx +133 -0
- package/agent/apps/desktop/src/components/assistant-ui/tool-approval.tsx +239 -0
- package/agent/apps/desktop/src/components/assistant-ui/tool-fallback-model.test.ts +31 -0
- package/agent/apps/desktop/src/components/assistant-ui/tool-fallback-model.ts +152 -134
- package/agent/apps/desktop/src/components/assistant-ui/tool-fallback.tsx +142 -150
- package/agent/apps/desktop/src/components/assistant-ui/tooltip-icon-button.tsx +14 -12
- package/agent/apps/desktop/src/components/assistant-ui/user-message-edit.test.tsx +141 -0
- package/agent/apps/desktop/src/components/assistant-ui/user-message-text.tsx +152 -0
- package/agent/apps/desktop/src/components/boot-failure-overlay.tsx +150 -33
- package/agent/apps/desktop/src/components/boot-failure-reauth.test.ts +100 -0
- package/agent/apps/desktop/src/components/boot-failure-reauth.ts +81 -0
- package/agent/apps/desktop/src/components/brand-mark.tsx +19 -0
- package/agent/apps/desktop/src/components/chat/code-card.tsx +1 -1
- package/agent/apps/desktop/src/components/chat/composer-dock.ts +31 -0
- package/agent/apps/desktop/src/components/chat/diff-lines.tsx +1 -1
- package/agent/apps/desktop/src/components/chat/disclosure-row.tsx +13 -3
- package/agent/apps/desktop/src/components/chat/expandable-block.tsx +52 -0
- package/agent/apps/desktop/src/components/chat/generated-image-result.tsx +174 -0
- package/agent/apps/desktop/src/components/chat/image-generation-placeholder.tsx +70 -37
- package/agent/apps/desktop/src/components/chat/intro.tsx +8 -7
- package/agent/apps/desktop/src/components/chat/preview-attachment.tsx +4 -2
- package/agent/apps/desktop/src/components/chat/shiki-highlighter.test.ts +37 -0
- package/agent/apps/desktop/src/components/chat/shiki-highlighter.tsx +96 -22
- package/agent/apps/desktop/src/components/chat/status-row.tsx +70 -0
- package/agent/apps/desktop/src/components/chat/status-section.tsx +42 -0
- package/agent/apps/desktop/src/components/chat/terminal-output.tsx +54 -0
- package/agent/apps/desktop/src/components/chat/zoomable-image.tsx +70 -109
- package/agent/apps/desktop/src/components/desktop-install-overlay.tsx +154 -84
- package/agent/apps/desktop/src/components/desktop-onboarding-overlay.test.tsx +38 -8
- package/agent/apps/desktop/src/components/desktop-onboarding-overlay.tsx +789 -233
- package/agent/apps/desktop/src/components/error-boundary.tsx +77 -0
- package/agent/apps/desktop/src/components/gateway-connecting-overlay.test.tsx +144 -0
- package/agent/apps/desktop/src/components/gateway-connecting-overlay.tsx +7 -1
- package/agent/apps/desktop/src/components/haptics-provider.tsx +24 -0
- package/agent/apps/desktop/src/components/language-switcher.test.tsx +53 -0
- package/agent/apps/desktop/src/components/language-switcher.tsx +175 -0
- package/agent/apps/desktop/src/components/model-picker.tsx +42 -40
- package/agent/apps/desktop/src/components/model-visibility-dialog.tsx +166 -0
- package/agent/apps/desktop/src/components/notifications.tsx +48 -27
- package/agent/apps/desktop/src/components/pane-shell/index.ts +1 -1
- package/agent/apps/desktop/src/components/pane-shell/pane-shell.tsx +146 -9
- package/agent/apps/desktop/src/components/prompt-overlays.tsx +234 -0
- package/agent/apps/desktop/src/components/session-picker.tsx +108 -0
- package/agent/apps/desktop/src/components/ui/action-status.tsx +25 -0
- package/agent/apps/desktop/src/components/ui/badge.tsx +35 -0
- package/agent/apps/desktop/src/components/ui/button.tsx +37 -13
- package/agent/apps/desktop/src/components/ui/confirm-dialog.tsx +109 -0
- package/agent/apps/desktop/src/components/ui/control.ts +25 -0
- package/agent/apps/desktop/src/components/ui/copy-button.test.tsx +36 -0
- package/agent/apps/desktop/src/components/ui/copy-button.tsx +38 -27
- package/agent/apps/desktop/src/components/ui/dialog.tsx +39 -11
- package/agent/apps/desktop/src/components/ui/dropdown-menu.tsx +98 -24
- package/agent/apps/desktop/src/components/ui/error-state.tsx +50 -0
- package/agent/apps/desktop/src/components/ui/fade-text.tsx +9 -2
- package/agent/apps/desktop/src/components/ui/{braille-spinner.tsx → glyph-spinner.tsx} +15 -13
- package/agent/apps/desktop/src/components/ui/input.tsx +5 -2
- package/agent/apps/desktop/src/components/ui/kbd.tsx +83 -12
- package/agent/apps/desktop/src/components/ui/log-view.tsx +19 -0
- package/agent/apps/desktop/src/components/ui/pagination.tsx +12 -5
- package/agent/apps/desktop/src/components/ui/popover.tsx +44 -0
- package/agent/apps/desktop/src/components/ui/search-field.tsx +80 -0
- package/agent/apps/desktop/src/components/ui/segmented-control.tsx +51 -0
- package/agent/apps/desktop/src/components/ui/select.tsx +10 -3
- package/agent/apps/desktop/src/components/ui/sheet.tsx +8 -2
- package/agent/apps/desktop/src/components/ui/sidebar.tsx +18 -25
- package/agent/apps/desktop/src/components/ui/switch.tsx +38 -15
- package/agent/apps/desktop/src/components/ui/textarea.tsx +4 -11
- package/agent/apps/desktop/src/components/ui/tool-icon.tsx +65 -0
- package/agent/apps/desktop/src/components/ui/tooltip.tsx +31 -4
- package/agent/apps/desktop/src/fonts/JetBrainsMono-Bold.woff2 +0 -0
- package/agent/apps/desktop/src/fonts/JetBrainsMono-Italic.woff2 +0 -0
- package/agent/apps/desktop/src/fonts/JetBrainsMono-Regular.woff2 +0 -0
- package/agent/apps/desktop/src/global.d.ts +181 -4
- package/agent/apps/desktop/src/hermes.test.ts +60 -0
- package/agent/apps/desktop/src/hermes.ts +190 -13
- package/agent/apps/desktop/src/hooks/use-image-download.ts +85 -0
- package/agent/apps/desktop/src/hooks/use-resize-observer.ts +13 -4
- package/agent/apps/desktop/src/hooks/use-worktree-info.ts +68 -0
- package/agent/apps/desktop/src/i18n/catalog.ts +12 -0
- package/agent/apps/desktop/src/i18n/context.test.tsx +232 -0
- package/agent/apps/desktop/src/i18n/context.tsx +183 -0
- package/agent/apps/desktop/src/i18n/define-locale.ts +41 -0
- package/agent/apps/desktop/src/i18n/en.ts +1921 -0
- package/agent/apps/desktop/src/i18n/index.ts +20 -0
- package/agent/apps/desktop/src/i18n/ja.ts +2053 -0
- package/agent/apps/desktop/src/i18n/languages.test.ts +43 -0
- package/agent/apps/desktop/src/i18n/languages.ts +86 -0
- package/agent/apps/desktop/src/i18n/runtime.test.ts +75 -0
- package/agent/apps/desktop/src/i18n/runtime.ts +53 -0
- package/agent/apps/desktop/src/i18n/types.ts +1559 -0
- package/agent/apps/desktop/src/i18n/zh-hant.ts +1992 -0
- package/agent/apps/desktop/src/i18n/zh.ts +2099 -0
- package/agent/apps/desktop/src/lib/ansi.test.ts +123 -0
- package/agent/apps/desktop/src/lib/ansi.ts +186 -0
- package/agent/apps/desktop/src/lib/chat-messages.test.ts +79 -0
- package/agent/apps/desktop/src/lib/chat-messages.ts +68 -29
- package/agent/apps/desktop/src/lib/chat-runtime.test.ts +65 -1
- package/agent/apps/desktop/src/lib/chat-runtime.ts +39 -3
- package/agent/apps/desktop/src/lib/completion-sound.ts +519 -0
- package/agent/apps/desktop/src/lib/desktop-fs.test.ts +116 -0
- package/agent/apps/desktop/src/lib/desktop-fs.ts +113 -0
- package/agent/apps/desktop/src/lib/desktop-slash-commands.test.ts +89 -6
- package/agent/apps/desktop/src/lib/desktop-slash-commands.ts +270 -131
- package/agent/apps/desktop/src/lib/external-link.test.tsx +27 -0
- package/agent/apps/desktop/src/lib/external-link.tsx +9 -2
- package/agent/apps/desktop/src/lib/gateway-events.test.ts +27 -0
- package/agent/apps/desktop/src/lib/gateway-events.ts +16 -0
- package/agent/apps/desktop/src/lib/gateway-ws-url.test.ts +78 -0
- package/agent/apps/desktop/src/lib/gateway-ws-url.ts +91 -0
- package/agent/apps/desktop/src/lib/generated-images.test.ts +97 -0
- package/agent/apps/desktop/src/lib/generated-images.ts +116 -0
- package/agent/apps/desktop/src/lib/haptics.ts +17 -0
- package/agent/apps/desktop/src/lib/icons.ts +10 -2
- package/agent/apps/desktop/src/lib/keybinds/actions.ts +137 -0
- package/agent/apps/desktop/src/lib/keybinds/combo.test.ts +86 -0
- package/agent/apps/desktop/src/lib/keybinds/combo.ts +195 -0
- package/agent/apps/desktop/src/lib/local-preview.ts +23 -2
- package/agent/apps/desktop/src/lib/markdown-preprocess.ts +20 -7
- package/agent/apps/desktop/src/lib/media.remote.test.ts +90 -0
- package/agent/apps/desktop/src/lib/media.ts +40 -1
- package/agent/apps/desktop/src/lib/model-status-label.test.ts +59 -0
- package/agent/apps/desktop/src/lib/model-status-label.ts +122 -0
- package/agent/apps/desktop/src/lib/mutable-ref.ts +6 -0
- package/agent/apps/desktop/src/lib/profile-color.ts +58 -0
- package/agent/apps/desktop/src/lib/query-client.ts +13 -0
- package/agent/apps/desktop/src/lib/remend-tail.test.ts +105 -0
- package/agent/apps/desktop/src/lib/remend-tail.ts +108 -0
- package/agent/apps/desktop/src/lib/session-export.ts +6 -3
- package/agent/apps/desktop/src/lib/session-ids.test.ts +44 -0
- package/agent/apps/desktop/src/lib/session-ids.ts +26 -0
- package/agent/apps/desktop/src/lib/session-search.test.ts +66 -0
- package/agent/apps/desktop/src/lib/session-search.ts +21 -0
- package/agent/apps/desktop/src/lib/session-source.ts +126 -0
- package/agent/apps/desktop/src/lib/storage.test.ts +25 -0
- package/agent/apps/desktop/src/lib/storage.ts +35 -1
- package/agent/apps/desktop/src/lib/todos.test.ts +46 -1
- package/agent/apps/desktop/src/lib/todos.ts +37 -0
- package/agent/apps/desktop/src/lib/tool-result-summary.ts +5 -1
- package/agent/apps/desktop/src/lib/update-copy.test.ts +38 -0
- package/agent/apps/desktop/src/lib/update-copy.ts +44 -0
- package/agent/apps/desktop/src/lib/use-enter-animation.ts +2 -2
- package/agent/apps/desktop/src/lib/yolo-session.ts +50 -0
- package/agent/apps/desktop/src/main.tsx +19 -19
- package/agent/apps/desktop/src/store/boot.ts +4 -3
- package/agent/apps/desktop/src/store/clarify.test.ts +81 -0
- package/agent/apps/desktop/src/store/clarify.ts +50 -13
- package/agent/apps/desktop/src/store/command-palette.ts +20 -0
- package/agent/apps/desktop/src/store/compaction.test.ts +53 -0
- package/agent/apps/desktop/src/store/compaction.ts +38 -0
- package/agent/apps/desktop/src/store/completion-sound.ts +32 -0
- package/agent/apps/desktop/src/store/composer-input-history.test.ts +147 -0
- package/agent/apps/desktop/src/store/composer-input-history.ts +158 -0
- package/agent/apps/desktop/src/store/composer-queue.test.ts +68 -0
- package/agent/apps/desktop/src/store/composer-queue.ts +76 -0
- package/agent/apps/desktop/src/store/composer-status.test.ts +99 -0
- package/agent/apps/desktop/src/store/composer-status.ts +277 -0
- package/agent/apps/desktop/src/store/composer.test.ts +106 -0
- package/agent/apps/desktop/src/store/composer.ts +116 -0
- package/agent/apps/desktop/src/store/cron.ts +19 -0
- package/agent/apps/desktop/src/store/gateway.ts +280 -6
- package/agent/apps/desktop/src/store/keybinds.ts +143 -0
- package/agent/apps/desktop/src/store/layout.ts +107 -9
- package/agent/apps/desktop/src/store/model-presets.test.ts +51 -0
- package/agent/apps/desktop/src/store/model-presets.ts +86 -0
- package/agent/apps/desktop/src/store/model-visibility.test.ts +99 -0
- package/agent/apps/desktop/src/store/model-visibility.ts +161 -0
- package/agent/apps/desktop/src/store/native-notifications.test.ts +192 -0
- package/agent/apps/desktop/src/store/native-notifications.ts +203 -0
- package/agent/apps/desktop/src/store/notifications.ts +10 -7
- package/agent/apps/desktop/src/store/onboarding.test.ts +271 -1
- package/agent/apps/desktop/src/store/onboarding.ts +268 -38
- package/agent/apps/desktop/src/store/preview.ts +10 -1
- package/agent/apps/desktop/src/store/profile.test.ts +89 -0
- package/agent/apps/desktop/src/store/profile.ts +395 -0
- package/agent/apps/desktop/src/store/prompts.test.ts +127 -0
- package/agent/apps/desktop/src/store/prompts.ts +117 -0
- package/agent/apps/desktop/src/store/session-switcher.test.ts +115 -0
- package/agent/apps/desktop/src/store/session-switcher.ts +128 -0
- package/agent/apps/desktop/src/store/session-sync.ts +25 -0
- package/agent/apps/desktop/src/store/session.test.ts +268 -2
- package/agent/apps/desktop/src/store/session.ts +392 -18
- package/agent/apps/desktop/src/store/subagents.ts +3 -0
- package/agent/apps/desktop/src/store/system-actions.ts +48 -0
- package/agent/apps/desktop/src/store/thread-scroll.ts +58 -5
- package/agent/apps/desktop/src/store/todos.test.ts +47 -0
- package/agent/apps/desktop/src/store/todos.ts +64 -0
- package/agent/apps/desktop/src/store/tool-dismiss.ts +45 -0
- package/agent/apps/desktop/src/store/translucency.ts +38 -0
- package/agent/apps/desktop/src/store/updates.test.ts +187 -2
- package/agent/apps/desktop/src/store/updates.ts +268 -18
- package/agent/apps/desktop/src/store/windows.test.ts +143 -0
- package/agent/apps/desktop/src/store/windows.ts +115 -0
- package/agent/apps/desktop/src/styles.css +510 -119
- package/agent/apps/desktop/src/themes/color.ts +142 -0
- package/agent/apps/desktop/src/themes/context.tsx +128 -75
- package/agent/apps/desktop/src/themes/install.test.ts +119 -0
- package/agent/apps/desktop/src/themes/install.ts +95 -0
- package/agent/apps/desktop/src/themes/presets.test.ts +33 -0
- package/agent/apps/desktop/src/themes/presets.ts +13 -4
- package/agent/apps/desktop/src/themes/profile-theme.test.ts +41 -0
- package/agent/apps/desktop/src/themes/types.ts +35 -0
- package/agent/apps/desktop/src/themes/user-themes.test.ts +63 -0
- package/agent/apps/desktop/src/themes/user-themes.ts +122 -0
- package/agent/apps/desktop/src/themes/vscode.test.ts +171 -0
- package/agent/apps/desktop/src/themes/vscode.ts +343 -0
- package/agent/apps/desktop/src/types/hermes.ts +138 -1
- package/agent/apps/desktop/tsconfig.json +2 -2
- package/agent/apps/desktop/vite.config.ts +18 -0
- package/agent/apps/shared/package.json +1 -1
- package/agent/apps/shared/src/json-rpc-gateway.ts +63 -2
- package/agent/apps/shared/tsconfig.json +2 -2
- package/agent/cli-config.yaml.example +78 -1
- package/agent/cli.py +2177 -3162
- package/agent/cron/blueprint_catalog.py +713 -0
- package/agent/cron/jobs.py +226 -110
- package/agent/cron/scheduler.py +468 -193
- package/agent/cron/scheduler_provider.py +177 -0
- package/agent/cron/scripts/__init__.py +1 -0
- package/agent/cron/scripts/classify_items.py +226 -0
- package/agent/cron/suggestion_catalog.py +154 -0
- package/agent/cron/suggestions.py +257 -0
- package/agent/docs/chronos-managed-cron-contract.md +196 -0
- package/agent/docs/design/profile-builder.md +146 -0
- package/agent/docs/middleware/README.md +260 -0
- package/agent/docs/observability/README.md +316 -0
- package/agent/docs/plans/2026-06-09-003-fix-telegram-stream-overflow-continuations-plan.md +240 -0
- package/agent/docs/rca-ssl-cacert-post-git-pull.md +54 -0
- package/agent/docs/relay-connector-contract.md +285 -0
- package/agent/gateway/authz_mixin.py +536 -0
- package/agent/gateway/channel_directory.py +65 -3
- package/agent/gateway/config.py +222 -12
- package/agent/gateway/display_config.py +10 -0
- package/agent/gateway/hooks.py +17 -0
- package/agent/gateway/kanban_watchers.py +1146 -0
- package/agent/gateway/message_timestamps.py +166 -0
- package/agent/gateway/platforms/ADDING_A_PLATFORM.md +29 -0
- package/agent/gateway/platforms/api_server.py +216 -38
- package/agent/gateway/platforms/base.py +210 -58
- package/agent/gateway/platforms/email.py +122 -12
- package/agent/gateway/platforms/feishu.py +80 -11
- package/agent/gateway/platforms/feishu_meeting_invite.py +212 -0
- package/agent/gateway/platforms/matrix.py +1498 -297
- package/agent/gateway/platforms/qqbot/adapter.py +6 -0
- package/agent/gateway/platforms/signal.py +8 -0
- package/agent/gateway/platforms/slack.py +308 -12
- package/agent/gateway/platforms/telegram.py +831 -24
- package/agent/gateway/platforms/webhook.py +109 -21
- package/agent/gateway/platforms/weixin.py +113 -2
- package/agent/gateway/platforms/whatsapp.py +94 -288
- package/agent/gateway/platforms/whatsapp_cloud.py +1956 -0
- package/agent/gateway/platforms/whatsapp_common.py +367 -0
- package/agent/gateway/platforms/yuanbao.py +608 -191
- package/agent/gateway/platforms/yuanbao_proto.py +232 -23
- package/agent/gateway/relay/__init__.py +375 -0
- package/agent/gateway/relay/adapter.py +222 -0
- package/agent/gateway/relay/auth.py +168 -0
- package/agent/gateway/relay/descriptor.py +118 -0
- package/agent/gateway/relay/transport.py +101 -0
- package/agent/gateway/relay/ws_transport.py +327 -0
- package/agent/gateway/response_filters.py +53 -0
- package/agent/gateway/rich_sent_store.py +80 -0
- package/agent/gateway/run.py +2940 -5001
- package/agent/gateway/session.py +109 -8
- package/agent/gateway/session_context.py +22 -4
- package/agent/gateway/slash_commands.py +3854 -0
- package/agent/gateway/status.py +141 -21
- package/agent/gateway/stream_consumer.py +288 -31
- package/agent/hermes-already-has-routines.md +1 -1
- package/agent/hermes_cli/__init__.py +62 -17
- package/agent/hermes_cli/_parser.py +30 -0
- package/agent/hermes_cli/_subprocess_compat.py +61 -0
- package/agent/hermes_cli/active_sessions.py +320 -0
- package/agent/hermes_cli/auth.py +707 -59
- package/agent/hermes_cli/auth_commands.py +39 -22
- package/agent/hermes_cli/backup.py +109 -7
- package/agent/hermes_cli/banner.py +88 -0
- package/agent/hermes_cli/blueprint_cmd.py +318 -0
- package/agent/hermes_cli/cli_agent_setup_mixin.py +684 -0
- package/agent/hermes_cli/cli_commands_mixin.py +2293 -0
- package/agent/hermes_cli/commands.py +215 -91
- package/agent/hermes_cli/config.py +967 -130
- package/agent/hermes_cli/container_boot.py +76 -11
- package/agent/hermes_cli/cron.py +5 -11
- package/agent/hermes_cli/curator.py +21 -0
- package/agent/hermes_cli/dashboard_auth/__init__.py +2 -0
- package/agent/hermes_cli/dashboard_auth/base.py +62 -0
- package/agent/hermes_cli/dashboard_auth/cookies.py +32 -19
- package/agent/hermes_cli/dashboard_auth/login_page.py +156 -6
- package/agent/hermes_cli/dashboard_auth/middleware.py +28 -4
- package/agent/hermes_cli/dashboard_auth/prefix.py +46 -2
- package/agent/hermes_cli/dashboard_auth/public_paths.py +6 -0
- package/agent/hermes_cli/dashboard_auth/routes.py +158 -2
- package/agent/hermes_cli/dashboard_auth/ws_tickets.py +85 -11
- package/agent/hermes_cli/dashboard_register.py +427 -0
- package/agent/hermes_cli/debug.py +155 -50
- package/agent/hermes_cli/doctor.py +255 -14
- package/agent/hermes_cli/dump.py +60 -6
- package/agent/hermes_cli/env_loader.py +33 -0
- package/agent/hermes_cli/gateway.py +755 -103
- package/agent/hermes_cli/gateway_enroll.py +250 -0
- package/agent/hermes_cli/gateway_windows.py +254 -11
- package/agent/hermes_cli/gui_uninstall.py +285 -0
- package/agent/hermes_cli/inventory.py +105 -4
- package/agent/hermes_cli/kanban.py +58 -71
- package/agent/hermes_cli/kanban_db.py +391 -14
- package/agent/hermes_cli/kanban_decompose.py +2 -2
- package/agent/hermes_cli/kanban_specify.py +3 -1
- package/agent/hermes_cli/logs.py +2 -0
- package/agent/hermes_cli/main.py +2889 -5287
- package/agent/hermes_cli/managed_scope.py +214 -0
- package/agent/hermes_cli/managed_uv.py +254 -0
- package/agent/hermes_cli/mcp_catalog.py +6 -3
- package/agent/hermes_cli/mcp_config.py +145 -21
- package/agent/hermes_cli/mcp_security.py +96 -0
- package/agent/hermes_cli/mcp_startup.py +32 -3
- package/agent/hermes_cli/memory_providers.py +149 -0
- package/agent/hermes_cli/memory_setup.py +97 -42
- package/agent/hermes_cli/middleware.py +313 -0
- package/agent/hermes_cli/model_catalog.py +31 -0
- package/agent/hermes_cli/model_cost_guard.py +134 -0
- package/agent/hermes_cli/model_normalize.py +2 -1
- package/agent/hermes_cli/model_setup_flows.py +2759 -0
- package/agent/hermes_cli/model_switch.py +242 -27
- package/agent/hermes_cli/models.py +284 -44
- package/agent/hermes_cli/nous_account.py +33 -6
- package/agent/hermes_cli/nous_billing.py +406 -0
- package/agent/hermes_cli/nous_subscription.py +202 -5
- package/agent/hermes_cli/platforms.py +1 -0
- package/agent/hermes_cli/plugins.py +218 -18
- package/agent/hermes_cli/plugins_cmd.py +249 -105
- package/agent/hermes_cli/portal_cli.py +56 -16
- package/agent/hermes_cli/profile_distribution.py +6 -1
- package/agent/hermes_cli/profiles.py +283 -32
- package/agent/hermes_cli/provider_catalog.py +170 -0
- package/agent/hermes_cli/providers.py +4 -1
- package/agent/hermes_cli/pty_bridge.py +53 -4
- package/agent/hermes_cli/runtime_provider.py +216 -34
- package/agent/hermes_cli/secret_prompt.py +4 -4
- package/agent/hermes_cli/secrets_cli.py +24 -0
- package/agent/hermes_cli/send_cmd.py +28 -2
- package/agent/hermes_cli/service_manager.py +166 -19
- package/agent/hermes_cli/session_listing.py +97 -0
- package/agent/hermes_cli/setup.py +158 -94
- package/agent/hermes_cli/setup_whatsapp_cloud.py +541 -0
- package/agent/hermes_cli/skills_config.py +8 -2
- package/agent/hermes_cli/skills_hub.py +149 -7
- package/agent/hermes_cli/status.py +2 -2
- package/agent/hermes_cli/subcommands/__init__.py +18 -0
- package/agent/hermes_cli/subcommands/_shared.py +29 -0
- package/agent/hermes_cli/subcommands/acp.py +52 -0
- package/agent/hermes_cli/subcommands/auth.py +109 -0
- package/agent/hermes_cli/subcommands/backup.py +38 -0
- package/agent/hermes_cli/subcommands/claw.py +92 -0
- package/agent/hermes_cli/subcommands/config.py +49 -0
- package/agent/hermes_cli/subcommands/cron.py +163 -0
- package/agent/hermes_cli/subcommands/dashboard.py +143 -0
- package/agent/hermes_cli/subcommands/debug.py +77 -0
- package/agent/hermes_cli/subcommands/doctor.py +35 -0
- package/agent/hermes_cli/subcommands/dump.py +28 -0
- package/agent/hermes_cli/subcommands/gateway.py +332 -0
- package/agent/hermes_cli/subcommands/gui.py +63 -0
- package/agent/hermes_cli/subcommands/hooks.py +77 -0
- package/agent/hermes_cli/subcommands/import_cmd.py +31 -0
- package/agent/hermes_cli/subcommands/insights.py +25 -0
- package/agent/hermes_cli/subcommands/login.py +78 -0
- package/agent/hermes_cli/subcommands/logout.py +28 -0
- package/agent/hermes_cli/subcommands/logs.py +78 -0
- package/agent/hermes_cli/subcommands/mcp.py +108 -0
- package/agent/hermes_cli/subcommands/memory.py +53 -0
- package/agent/hermes_cli/subcommands/model.py +72 -0
- package/agent/hermes_cli/subcommands/pairing.py +36 -0
- package/agent/hermes_cli/subcommands/plugins.py +94 -0
- package/agent/hermes_cli/subcommands/postinstall.py +23 -0
- package/agent/hermes_cli/subcommands/profile.py +203 -0
- package/agent/hermes_cli/subcommands/prompt_size.py +36 -0
- package/agent/hermes_cli/subcommands/security.py +62 -0
- package/agent/hermes_cli/subcommands/setup.py +58 -0
- package/agent/hermes_cli/subcommands/skills.py +298 -0
- package/agent/hermes_cli/subcommands/slack.py +60 -0
- package/agent/hermes_cli/subcommands/status.py +28 -0
- package/agent/hermes_cli/subcommands/tools.py +95 -0
- package/agent/hermes_cli/subcommands/uninstall.py +41 -0
- package/agent/hermes_cli/subcommands/update.py +70 -0
- package/agent/hermes_cli/subcommands/version.py +18 -0
- package/agent/hermes_cli/subcommands/webhook.py +76 -0
- package/agent/hermes_cli/subcommands/whatsapp.py +22 -0
- package/agent/hermes_cli/suggestions_cmd.py +153 -0
- package/agent/hermes_cli/telegram_managed_bot.py +358 -0
- package/agent/hermes_cli/tips.py +3 -4
- package/agent/hermes_cli/tools_config.py +155 -28
- package/agent/hermes_cli/uninstall.py +231 -35
- package/agent/hermes_cli/web_server.py +6190 -973
- package/agent/hermes_cli/win_pty_bridge.py +179 -0
- package/agent/hermes_cli/write_approval_commands.py +209 -0
- package/agent/hermes_constants.py +164 -33
- package/agent/hermes_logging.py +74 -2
- package/agent/hermes_state.py +919 -106
- package/agent/hermes_time.py +20 -0
- package/agent/locales/af.yaml +23 -0
- package/agent/locales/de.yaml +23 -0
- package/agent/locales/en.yaml +20 -0
- package/agent/locales/es.yaml +23 -0
- package/agent/locales/fr.yaml +23 -0
- package/agent/locales/ga.yaml +23 -0
- package/agent/locales/hu.yaml +23 -0
- package/agent/locales/it.yaml +23 -0
- package/agent/locales/ja.yaml +23 -0
- package/agent/locales/ko.yaml +23 -0
- package/agent/locales/pt.yaml +23 -0
- package/agent/locales/ru.yaml +23 -0
- package/agent/locales/tr.yaml +23 -0
- package/agent/locales/uk.yaml +23 -0
- package/agent/locales/zh-hant.yaml +23 -0
- package/agent/locales/zh.yaml +23 -0
- package/agent/model_tools.py +204 -40
- package/agent/optional-mcps/clawpump/manifest.yaml +4 -2
- package/agent/optional-mcps/clawpump-stdio/manifest.yaml +2 -0
- package/agent/optional-mcps/unreal-engine/manifest.yaml +54 -0
- package/agent/optional-skills/blockchain/hyperliquid/SKILL.md +2 -2
- package/agent/optional-skills/blockchain/hyperliquid/scripts/hyperliquid_client.py +1 -1
- package/agent/optional-skills/creative/kanban-video-orchestrator/SKILL.md +1 -1
- package/agent/optional-skills/creative/kanban-video-orchestrator/assets/setup.sh.tmpl +4 -3
- package/agent/optional-skills/creative/kanban-video-orchestrator/references/kanban-setup.md +6 -4
- package/agent/optional-skills/creative/kanban-video-orchestrator/references/tool-matrix.md +2 -2
- package/agent/{skills/software-development → optional-skills/devops}/hermes-s6-container-supervision/SKILL.md +2 -0
- package/agent/optional-skills/devops/watchers/SKILL.md +1 -1
- package/agent/optional-skills/devops/watchers/scripts/watch_github.py +2 -1
- package/agent/optional-skills/payments/mpp-agent/SKILL.md +124 -0
- package/agent/optional-skills/payments/stripe-link-cli/SKILL.md +184 -0
- package/agent/optional-skills/payments/stripe-projects/SKILL.md +120 -0
- package/agent/optional-skills/productivity/canvas/SKILL.md +1 -1
- package/agent/optional-skills/productivity/canvas/scripts/canvas_api.py +4 -1
- package/agent/optional-skills/productivity/shop/SKILL.md +224 -0
- package/agent/optional-skills/productivity/shop/references/catalog-mcp.md +236 -0
- package/agent/optional-skills/productivity/shop/references/direct-api.md +278 -0
- package/agent/optional-skills/productivity/shop/references/legal.md +3 -0
- package/agent/optional-skills/productivity/shop/references/safety.md +36 -0
- package/agent/optional-skills/productivity/shopify/SKILL.md +1 -1
- package/agent/optional-skills/productivity/siyuan/SKILL.md +1 -1
- package/agent/optional-skills/productivity/telephony/SKILL.md +4 -4
- package/agent/optional-skills/productivity/telephony/scripts/telephony.py +15 -15
- package/agent/optional-skills/security/1password/SKILL.md +1 -1
- package/agent/{skills/red-teaming → optional-skills/security}/godmode/SKILL.md +3 -4
- package/agent/{skills/red-teaming → optional-skills/security}/godmode/scripts/auto_jailbreak.py +3 -1
- package/agent/optional-skills/software-development/rest-graphql-debug/SKILL.md +1 -1
- package/agent/{skills → optional-skills}/software-development/subagent-driven-development/SKILL.md +5 -5
- package/agent/package-lock.json +4082 -7907
- package/agent/package.json +18 -3
- package/agent/plugins/browser/firecrawl/provider.py +4 -1
- package/agent/plugins/cron/__init__.py +344 -0
- package/agent/plugins/cron/chronos/__init__.py +241 -0
- package/agent/plugins/cron/chronos/_nas_client.py +123 -0
- package/agent/plugins/cron/chronos/plugin.yaml +9 -0
- package/agent/plugins/cron/chronos/verify.py +103 -0
- package/agent/plugins/dashboard_auth/basic/__init__.py +491 -0
- package/agent/plugins/dashboard_auth/basic/plugin.yaml +7 -0
- package/agent/plugins/dashboard_auth/nous/__init__.py +12 -14
- package/agent/plugins/dashboard_auth/self_hosted/__init__.py +736 -0
- package/agent/plugins/dashboard_auth/self_hosted/plugin.yaml +8 -0
- package/agent/plugins/disk-cleanup/disk_cleanup.py +100 -20
- package/agent/plugins/google_meet/audio_bridge.py +4 -0
- package/agent/plugins/google_meet/meet_bot.py +7 -1
- package/agent/plugins/hermes-achievements/dashboard/dist/index.js +9 -15
- package/agent/plugins/image_gen/fal/__init__.py +35 -6
- package/agent/plugins/image_gen/krea/__init__.py +56 -13
- package/agent/plugins/image_gen/openai/__init__.py +122 -24
- package/agent/plugins/image_gen/openai-codex/__init__.py +28 -2
- package/agent/plugins/image_gen/xai/__init__.py +92 -12
- package/agent/plugins/kanban/dashboard/dist/index.js +63 -48
- package/agent/plugins/kanban/dashboard/plugin_api.py +39 -35
- package/agent/plugins/memory/__init__.py +48 -5
- package/agent/plugins/memory/byterover/__init__.py +1 -0
- package/agent/plugins/memory/hindsight/README.md +1 -1
- package/agent/plugins/memory/hindsight/__init__.py +138 -24
- package/agent/plugins/memory/hindsight/plugin.yaml +1 -1
- package/agent/plugins/memory/honcho/README.md +13 -10
- package/agent/plugins/memory/honcho/cli.py +247 -122
- package/agent/plugins/memory/honcho/client.py +112 -102
- package/agent/plugins/memory/openviking/README.md +12 -1
- package/agent/plugins/memory/openviking/__init__.py +2281 -107
- package/agent/plugins/memory/openviking/plugin.yaml +1 -2
- package/agent/plugins/memory/supermemory/README.md +22 -10
- package/agent/plugins/memory/supermemory/__init__.py +142 -37
- package/agent/plugins/memory/supermemory/plugin.yaml +1 -1
- package/agent/plugins/model-providers/anthropic/__init__.py +1 -0
- package/agent/plugins/model-providers/bedrock/__init__.py +1 -0
- package/agent/plugins/model-providers/copilot-acp/__init__.py +1 -0
- package/agent/plugins/model-providers/custom/__init__.py +8 -2
- package/agent/plugins/model-providers/kimi-coding/__init__.py +16 -7
- package/agent/plugins/model-providers/minimax/__init__.py +60 -8
- package/agent/plugins/model-providers/opencode-zen/__init__.py +12 -3
- package/agent/plugins/model-providers/openrouter/__init__.py +75 -4
- package/agent/plugins/model-providers/xiaomi/__init__.py +2 -0
- package/agent/plugins/model-providers/zai/__init__.py +1 -0
- package/agent/plugins/observability/langfuse/__init__.py +147 -14
- package/agent/plugins/observability/nemo_relay/README.md +559 -0
- package/agent/plugins/observability/nemo_relay/__init__.py +962 -0
- package/agent/plugins/observability/nemo_relay/plugin.yaml +20 -0
- package/agent/plugins/platforms/discord/adapter.py +932 -61
- package/agent/plugins/platforms/discord/voice_mixer.py +379 -0
- package/agent/plugins/platforms/google_chat/adapter.py +9 -3
- package/agent/plugins/platforms/google_chat/oauth.py +1 -1
- package/agent/plugins/platforms/homeassistant/__init__.py +3 -0
- package/agent/{gateway/platforms/homeassistant.py → plugins/platforms/homeassistant/adapter.py} +128 -0
- package/agent/plugins/platforms/homeassistant/plugin.yaml +22 -0
- package/agent/plugins/platforms/irc/adapter.py +4 -1
- package/agent/plugins/platforms/line/adapter.py +16 -1
- package/agent/plugins/platforms/mattermost/adapter.py +100 -24
- package/agent/plugins/platforms/photon/README.md +179 -0
- package/agent/plugins/platforms/photon/__init__.py +4 -0
- package/agent/plugins/platforms/photon/adapter.py +1586 -0
- package/agent/plugins/platforms/photon/auth.py +1046 -0
- package/agent/plugins/platforms/photon/cli.py +439 -0
- package/agent/plugins/platforms/photon/plugin.yaml +88 -0
- package/agent/plugins/platforms/photon/sidecar/README.md +52 -0
- package/agent/plugins/platforms/photon/sidecar/index.mjs +720 -0
- package/agent/plugins/platforms/photon/sidecar/package-lock.json +1730 -0
- package/agent/plugins/platforms/photon/sidecar/package.json +25 -0
- package/agent/plugins/platforms/photon/sidecar/patch-spectrum-mixed-attachments.mjs +155 -0
- package/agent/plugins/platforms/raft/__init__.py +3 -0
- package/agent/plugins/platforms/raft/adapter.py +774 -0
- package/agent/plugins/platforms/raft/plugin.yaml +19 -0
- package/agent/plugins/platforms/simplex/adapter.py +777 -220
- package/agent/plugins/platforms/simplex/plugin.yaml +21 -2
- package/agent/plugins/platforms/teams/adapter.py +175 -5
- package/agent/plugins/plugin_utils.py +135 -0
- package/agent/plugins/video_gen/fal/__init__.py +10 -3
- package/agent/plugins/web/searxng/provider.py +15 -2
- package/agent/plugins/web/xai/provider.py +2 -2
- package/agent/providers/base.py +22 -3
- package/agent/pyproject.toml +115 -21
- package/agent/run_agent.py +733 -39
- package/agent/scripts/build_skills_index.py +51 -19
- package/agent/scripts/check_subprocess_stdin.py +177 -0
- package/agent/scripts/contributor_audit.py +2 -0
- package/agent/scripts/docker_config_migrate.py +67 -0
- package/agent/scripts/install.cmd +3 -3
- package/agent/scripts/install.ps1 +580 -154
- package/agent/scripts/install.sh +402 -185
- package/agent/scripts/lib/node-bootstrap.sh +39 -4
- package/agent/scripts/release.py +183 -0
- package/agent/scripts/run_tests.sh +1 -0
- package/agent/scripts/run_tests_parallel.py +18 -23
- package/agent/scripts/whatsapp-bridge/bridge.js +25 -4
- package/agent/setup.py +59 -0
- package/agent/skills/autonomous-ai-agents/codex/SKILL.md +19 -0
- package/agent/skills/autonomous-ai-agents/hermes-agent/SKILL.md +10 -3
- package/agent/skills/{mcp/native-mcp/SKILL.md → autonomous-ai-agents/hermes-agent/references/native-mcp.md} +0 -13
- package/agent/skills/{devops/webhook-subscriptions/SKILL.md → autonomous-ai-agents/hermes-agent/references/webhooks.md} +1 -11
- package/agent/skills/clawpump/SKILL.md +4 -1
- package/agent/skills/devops/kanban-orchestrator/SKILL.md +1 -0
- package/agent/skills/devops/kanban-worker/SKILL.md +1 -0
- package/agent/skills/github/github-auth/SKILL.md +2 -2
- package/agent/skills/github/github-auth/scripts/gh-env.sh +2 -2
- package/agent/skills/github/github-code-review/SKILL.md +2 -2
- package/agent/skills/github/github-issues/SKILL.md +2 -2
- package/agent/skills/github/github-pr-workflow/SKILL.md +2 -2
- package/agent/skills/github/github-repo-management/SKILL.md +2 -2
- package/agent/skills/media/gif-search/SKILL.md +1 -1
- package/agent/skills/media/youtube-content/SKILL.md +10 -7
- package/agent/skills/media/youtube-content/scripts/fetch_transcript.py +3 -3
- package/agent/skills/note-taking/obsidian/SKILL.md +1 -1
- package/agent/skills/productivity/airtable/SKILL.md +2 -2
- package/agent/skills/productivity/google-workspace/scripts/setup.py +33 -7
- package/agent/skills/productivity/notion/SKILL.md +2 -2
- package/agent/skills/productivity/teams-meeting-pipeline/SKILL.md +1 -1
- package/agent/skills/research/llm-wiki/SKILL.md +1 -1
- package/agent/skills/social-media/xurl/SKILL.md +9 -0
- package/agent/skills/software-development/hermes-agent-skill-authoring/SKILL.md +1 -1
- package/agent/skills/software-development/plan/SKILL.md +285 -5
- package/agent/skills/software-development/requesting-code-review/SKILL.md +2 -2
- package/agent/skills/software-development/simplify-code/SKILL.md +212 -0
- package/agent/skills/software-development/spike/SKILL.md +2 -2
- package/agent/skills/software-development/systematic-debugging/SKILL.md +1 -1
- package/agent/skills/software-development/test-driven-development/SKILL.md +1 -1
- package/agent/tools/approval.py +302 -4
- package/agent/tools/async_delegation.py +386 -0
- package/agent/tools/blueprints.py +325 -0
- package/agent/tools/browser_cdp_tool.py +3 -3
- package/agent/tools/browser_tool.py +34 -6
- package/agent/tools/checkpoint_manager.py +31 -1
- package/agent/tools/clarify_tool.py +55 -5
- package/agent/tools/code_execution_tool.py +31 -14
- package/agent/tools/computer_use/cua_backend.py +81 -3
- package/agent/tools/computer_use/tool.py +79 -5
- package/agent/tools/computer_use/vision_routing.py +55 -3
- package/agent/tools/credential_files.py +31 -12
- package/agent/tools/cronjob_tools.py +30 -20
- package/agent/tools/delegate_tool.py +356 -31
- package/agent/tools/env_probe.py +1 -0
- package/agent/tools/environments/docker.py +163 -8
- package/agent/tools/environments/file_sync.py +2 -1
- package/agent/tools/environments/local.py +74 -23
- package/agent/tools/environments/singularity.py +4 -1
- package/agent/tools/environments/ssh.py +78 -11
- package/agent/tools/file_operations.py +277 -41
- package/agent/tools/file_tools.py +166 -28
- package/agent/tools/image_generation_tool.py +515 -29
- package/agent/tools/kanban_tools.py +99 -0
- package/agent/tools/lazy_deps.py +33 -2
- package/agent/tools/mcp_oauth.py +5 -5
- package/agent/tools/mcp_oauth_manager.py +7 -5
- package/agent/tools/mcp_tool.py +840 -33
- package/agent/tools/memory_tool.py +335 -38
- package/agent/tools/osv_check.py +15 -1
- package/agent/tools/process_registry.py +155 -11
- package/agent/tools/read_extract.py +248 -0
- package/agent/tools/read_terminal_tool.py +93 -0
- package/agent/tools/schema_sanitizer.py +38 -0
- package/agent/tools/send_message_tool.py +163 -49
- package/agent/tools/session_search_tool.py +189 -7
- package/agent/tools/skill_manager_tool.py +202 -3
- package/agent/tools/skill_usage.py +52 -4
- package/agent/tools/skills_hub.py +184 -44
- package/agent/tools/skills_sync.py +232 -5
- package/agent/tools/skills_tool.py +125 -11
- package/agent/tools/terminal_tool.py +148 -26
- package/agent/tools/tirith_security.py +2 -0
- package/agent/tools/todo_tool.py +32 -1
- package/agent/tools/transcription_tools.py +13 -5
- package/agent/tools/tts_tool.py +332 -38
- package/agent/tools/url_safety.py +52 -1
- package/agent/tools/vision_tools.py +124 -39
- package/agent/tools/voice_mode.py +4 -3
- package/agent/tools/web_tools.py +45 -15
- package/agent/tools/write_approval.py +493 -0
- package/agent/toolsets.py +34 -10
- package/agent/trajectory_compressor.py +81 -10
- package/agent/tui_gateway/entry.py +43 -6
- package/agent/tui_gateway/server.py +3335 -330
- package/agent/tui_gateway/slash_worker.py +61 -0
- package/agent/tui_gateway/ws.py +67 -9
- package/agent/ui-tui/eslint.config.mjs +0 -4
- package/agent/ui-tui/package.json +6 -6
- package/agent/ui-tui/packages/hermes-ink/package.json +1 -1
- package/agent/ui-tui/packages/hermes-ink/src/ink/app-mouse.test.ts +34 -1
- package/agent/ui-tui/packages/hermes-ink/src/ink/app-rawmode-mouse.test.ts +91 -0
- package/agent/ui-tui/packages/hermes-ink/src/ink/components/App.tsx +35 -2
- package/agent/ui-tui/packages/hermes-ink/src/ink/events/input-event.ts +4 -11
- package/agent/ui-tui/packages/hermes-ink/src/ink/parse-keypress.test.ts +23 -57
- package/agent/ui-tui/packages/hermes-ink/src/ink/parse-keypress.ts +11 -135
- package/agent/ui-tui/packages/hermes-ink/src/ink/termio/tokenize.test.ts +185 -0
- package/agent/ui-tui/packages/hermes-ink/src/ink/termio/tokenize.ts +37 -3
- package/agent/ui-tui/packages/hermes-ink/src/utils/execFileNoThrow.ts +5 -5
- package/agent/ui-tui/src/__tests__/appChromeStatusRule.test.tsx +217 -0
- package/agent/ui-tui/src/__tests__/appChromeStatusRuleDevCredits.test.tsx +73 -0
- package/agent/ui-tui/src/__tests__/approvalAction.test.ts +11 -0
- package/agent/ui-tui/src/__tests__/billingCommand.test.ts +301 -0
- package/agent/ui-tui/src/__tests__/blockLayout.test.ts +122 -0
- package/agent/ui-tui/src/__tests__/brandingMcpCount.test.ts +111 -0
- package/agent/ui-tui/src/__tests__/completionApply.test.ts +51 -0
- package/agent/ui-tui/src/__tests__/createGatewayEventHandler.test.ts +487 -2
- package/agent/ui-tui/src/__tests__/createSlashHandler.test.ts +54 -0
- package/agent/ui-tui/src/__tests__/creditsCommand.test.ts +144 -0
- package/agent/ui-tui/src/__tests__/gatewayClient.test.ts +120 -99
- package/agent/ui-tui/src/__tests__/gracefulExit.test.ts +11 -0
- package/agent/ui-tui/src/__tests__/memoryMonitor.test.ts +102 -0
- package/agent/ui-tui/src/__tests__/paths.test.ts +41 -1
- package/agent/ui-tui/src/__tests__/terminalModes.test.ts +22 -0
- package/agent/ui-tui/src/__tests__/text.test.ts +23 -0
- package/agent/ui-tui/src/__tests__/textInputFastEcho.test.ts +37 -0
- package/agent/ui-tui/src/__tests__/turnControllerNotice.test.ts +43 -0
- package/agent/ui-tui/src/__tests__/useInputHandlers.test.ts +38 -1
- package/agent/ui-tui/src/__tests__/virtualHeights.test.ts +8 -0
- package/agent/ui-tui/src/app/createGatewayEventHandler.ts +102 -7
- package/agent/ui-tui/src/app/interfaces.ts +64 -1
- package/agent/ui-tui/src/app/overlayStore.ts +18 -2
- package/agent/ui-tui/src/app/slash/commands/billing.ts +332 -0
- package/agent/ui-tui/src/app/slash/commands/core.ts +31 -2
- package/agent/ui-tui/src/app/slash/commands/credits.ts +57 -0
- package/agent/ui-tui/src/app/slash/commands/ops.ts +28 -0
- package/agent/ui-tui/src/app/slash/commands/session.ts +32 -4
- package/agent/ui-tui/src/app/slash/registry.ts +4 -0
- package/agent/ui-tui/src/app/turnController.ts +145 -2
- package/agent/ui-tui/src/app/uiStore.ts +2 -0
- package/agent/ui-tui/src/app/useInputHandlers.ts +42 -4
- package/agent/ui-tui/src/app/useMainApp.ts +54 -8
- package/agent/ui-tui/src/app/useSessionLifecycle.ts +40 -31
- package/agent/ui-tui/src/app/useSubmission.ts +23 -31
- package/agent/ui-tui/src/components/appChrome.tsx +112 -5
- package/agent/ui-tui/src/components/appLayout.tsx +9 -0
- package/agent/ui-tui/src/components/appOverlays.tsx +25 -1
- package/agent/ui-tui/src/components/billingOverlay.tsx +684 -0
- package/agent/ui-tui/src/components/branding.tsx +15 -3
- package/agent/ui-tui/src/components/messageLine.tsx +25 -3
- package/agent/ui-tui/src/components/pluginsHub.tsx +238 -0
- package/agent/ui-tui/src/components/prompts.tsx +31 -17
- package/agent/ui-tui/src/components/streamingAssistant.tsx +63 -55
- package/agent/ui-tui/src/components/textInput.tsx +16 -0
- package/agent/ui-tui/src/config/env.ts +12 -0
- package/agent/ui-tui/src/config/limits.ts +13 -0
- package/agent/ui-tui/src/domain/blockLayout.ts +146 -0
- package/agent/ui-tui/src/domain/paths.ts +24 -0
- package/agent/ui-tui/src/domain/slash.ts +40 -0
- package/agent/ui-tui/src/entry.tsx +35 -4
- package/agent/ui-tui/src/gatewayClient.ts +22 -10
- package/agent/ui-tui/src/gatewayTypes.ts +130 -1
- package/agent/ui-tui/src/lib/gracefulExit.ts +24 -4
- package/agent/ui-tui/src/lib/memory.test.ts +162 -0
- package/agent/ui-tui/src/lib/memory.ts +60 -1
- package/agent/ui-tui/src/lib/memoryMonitor.ts +79 -4
- package/agent/ui-tui/src/lib/osc52.ts +1 -1
- package/agent/ui-tui/src/lib/text.test.ts +32 -1
- package/agent/ui-tui/src/lib/text.ts +29 -2
- package/agent/ui-tui/src/lib/virtualHeights.ts +13 -0
- package/agent/ui-tui/src/types.ts +5 -0
- package/agent/ui-tui/tsconfig.build.json +0 -1
- package/agent/ui-tui/tsconfig.json +2 -1
- package/agent/utils.py +66 -2
- package/agent/uv.lock +300 -684
- package/agent/web/index.html +2 -2
- package/agent/web/package.json +11 -6
- package/agent/web/public/claw-bg.webp +0 -0
- package/agent/web/public/claw-logo.webp +0 -0
- package/agent/web/src/App.tsx +138 -48
- package/agent/web/src/components/AutomationBlueprints.tsx +225 -0
- package/agent/web/src/components/Backdrop.tsx +15 -0
- package/agent/web/src/components/ChatSessionList.tsx +260 -0
- package/agent/web/src/components/ChatSidebar.tsx +262 -78
- package/agent/web/src/components/ConfirmDialog.tsx +122 -0
- package/agent/web/src/components/ModelPickerDialog.tsx +111 -16
- package/agent/web/src/components/ModelReloadConfirm.tsx +40 -0
- package/agent/web/src/components/ProfileScopeBanner.tsx +30 -0
- package/agent/web/src/components/ProfileSwitcher.tsx +67 -0
- package/agent/web/src/components/ReasoningPicker.tsx +167 -0
- package/agent/web/src/components/SkillEditorDialog.tsx +215 -0
- package/agent/web/src/components/ThemeSwitcher.tsx +119 -4
- package/agent/web/src/components/ToolsetConfigDrawer.tsx +457 -0
- package/agent/web/src/contexts/PageHeaderProvider.tsx +7 -4
- package/agent/web/src/contexts/ProfileProvider.tsx +137 -0
- package/agent/web/src/contexts/SystemActions.tsx +6 -8
- package/agent/web/src/contexts/profile-context.ts +19 -0
- package/agent/web/src/contexts/useProfileScope.ts +6 -0
- package/agent/web/src/i18n/af.ts +5 -4
- package/agent/web/src/i18n/de.ts +5 -4
- package/agent/web/src/i18n/en.ts +58 -4
- package/agent/web/src/i18n/es.ts +5 -3
- package/agent/web/src/i18n/fr.ts +5 -3
- package/agent/web/src/i18n/ga.ts +5 -4
- package/agent/web/src/i18n/hu.ts +5 -4
- package/agent/web/src/i18n/it.ts +5 -4
- package/agent/web/src/i18n/ja.ts +5 -4
- package/agent/web/src/i18n/ko.ts +5 -4
- package/agent/web/src/i18n/pt.ts +5 -3
- package/agent/web/src/i18n/ru.ts +5 -4
- package/agent/web/src/i18n/tr.ts +5 -4
- package/agent/web/src/i18n/types.ts +59 -1
- package/agent/web/src/i18n/uk.ts +5 -3
- package/agent/web/src/i18n/zh-hant.ts +5 -4
- package/agent/web/src/i18n/zh.ts +5 -4
- package/agent/web/src/index.css +2 -2
- package/agent/web/src/lib/api.ts +819 -52
- package/agent/web/src/lib/dashboard-flags.ts +16 -7
- package/agent/web/src/lib/reasoning-effort.test.ts +48 -0
- package/agent/web/src/lib/reasoning-effort.ts +36 -0
- package/agent/web/src/lib/session-refresh.test.ts +21 -0
- package/agent/web/src/lib/session-refresh.ts +26 -0
- package/agent/web/src/pages/ChannelsPage.tsx +529 -68
- package/agent/web/src/pages/ChatPage.tsx +249 -56
- package/agent/web/src/pages/ConfigPage.tsx +11 -1
- package/agent/web/src/pages/CronPage.tsx +219 -31
- package/agent/web/src/pages/EnvPage.tsx +25 -6
- package/agent/web/src/pages/FilesPage.tsx +525 -0
- package/agent/web/src/pages/McpPage.tsx +80 -3
- package/agent/web/src/pages/ModelsPage.tsx +97 -12
- package/agent/web/src/pages/PluginsPage.tsx +1 -1
- package/agent/web/src/pages/ProfileBuilderPage.tsx +611 -0
- package/agent/web/src/pages/ProfilesPage.tsx +1038 -172
- package/agent/web/src/pages/SessionsPage.tsx +144 -13
- package/agent/web/src/pages/SkillsPage.tsx +851 -70
- package/agent/web/src/pages/SystemPage.tsx +340 -4
- package/agent/web/src/pages/WalletPage.tsx +401 -0
- package/agent/web/src/pages/WebhooksPage.tsx +145 -15
- package/agent/web/src/pages/X402Page.tsx +207 -0
- package/agent/web/src/plugins/registry.ts +28 -11
- package/agent/web/src/plugins/sdk.d.ts +160 -0
- package/agent/web/src/themes/context.tsx +112 -5
- package/agent/web/src/themes/fonts.ts +167 -0
- package/agent/web/src/themes/index.ts +7 -0
- package/agent/web/tsconfig.app.json +0 -1
- package/agent/web/vite.config.ts +1 -8
- package/agent/web/vitest.config.ts +16 -0
- package/package.json +1 -1
- package/agent/apps/desktop/package-lock.json +0 -18363
- package/agent/apps/desktop/src/app/chat/composer/skin-slash-popover.tsx +0 -56
- package/agent/apps/desktop/src/components/assistant-ui/thread-virtualizer.tsx +0 -382
- package/agent/apps/desktop/src/components/assistant-ui/todo-tool.tsx +0 -109
- package/agent/apps/desktop/src/components/chat/generated-image-context.tsx +0 -19
- package/agent/optional-skills/productivity/shop-app/SKILL.md +0 -340
- package/agent/skills/autonomous-ai-agents/kanban-codex-lane/SKILL.md +0 -277
- package/agent/skills/autonomous-ai-agents/kanban-codex-lane/templates/pmb-codex-lane-prompt.md +0 -57
- package/agent/skills/diagramming/DESCRIPTION.md +0 -3
- package/agent/skills/domain/DESCRIPTION.md +0 -24
- package/agent/skills/gifs/DESCRIPTION.md +0 -3
- package/agent/skills/inference-sh/DESCRIPTION.md +0 -19
- package/agent/skills/mcp/DESCRIPTION.md +0 -3
- package/agent/skills/media/spotify/SKILL.md +0 -135
- package/agent/skills/mlops/training/DESCRIPTION.md +0 -3
- package/agent/skills/mlops/vector-databases/DESCRIPTION.md +0 -3
- package/agent/skills/productivity/linear/SKILL.md +0 -380
- package/agent/skills/productivity/linear/scripts/linear_api.py +0 -445
- package/agent/skills/software-development/debugging-hermes-tui-commands/SKILL.md +0 -152
- package/agent/skills/software-development/writing-plans/SKILL.md +0 -297
- package/agent/ui-tui/package-lock.json +0 -7449
- package/agent/ui-tui/packages/hermes-ink/package-lock.json +0 -1289
- package/agent/web/package-lock.json +0 -8887
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/PORT_NOTES.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/SKILL.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/prompts/system.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/palettes/macaron.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/palettes/mono-ink.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/palettes/neon.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/palettes/warm.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/prompt-construction.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/style-presets.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/blueprint.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/chalkboard.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/editorial.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/elegant.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/fantasy-animation.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/flat-doodle.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/flat.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/ink-notes.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/intuition-machine.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/minimal.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/nature.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/notion.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/pixel-art.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/playful.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/retro.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/scientific.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/screen-print.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/sketch-notes.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/sketch.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/vector-illustration.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/vintage.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/warm.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/watercolor.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/usage.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/workflow.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/PORT_NOTES.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/SKILL.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/analysis-framework.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/chalk.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/ink-brush.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/ligne-claire.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/manga.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/minimalist.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/realistic.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/auto-selection.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/base-prompt.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/character-template.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/cinematic.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/dense.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/four-panel.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/mixed.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/splash.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/standard.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/webtoon.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/ohmsha-guide.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/partial-workflows.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/concept-story.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/four-panel.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/ohmsha.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/shoujo.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/wuxia.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/storyboard-template.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/action.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/dramatic.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/energetic.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/neutral.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/romantic.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/vintage.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/warm.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/workflow.md +0 -0
- /package/agent/{skills → optional-skills}/creative/creative-ideation/SKILL.md +0 -0
- /package/agent/{skills → optional-skills}/creative/creative-ideation/references/full-prompt-library.md +0 -0
- /package/agent/{skills → optional-skills}/creative/pixel-art/ATTRIBUTION.md +0 -0
- /package/agent/{skills → optional-skills}/creative/pixel-art/SKILL.md +0 -0
- /package/agent/{skills → optional-skills}/creative/pixel-art/references/palettes.md +0 -0
- /package/agent/{skills → optional-skills}/creative/pixel-art/scripts/__init__.py +0 -0
- /package/agent/{skills → optional-skills}/creative/pixel-art/scripts/palettes.py +0 -0
- /package/agent/{skills → optional-skills}/creative/pixel-art/scripts/pixel_art.py +0 -0
- /package/agent/{skills → optional-skills}/creative/pixel-art/scripts/pixel_art_video.py +0 -0
- /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/SKILL.md +0 -0
- /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/references/analysis-modules.md +0 -0
- /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/references/methods-guide.md +0 -0
- /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/templates/abliteration-config.yaml +0 -0
- /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/templates/analysis-study.yaml +0 -0
- /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/templates/batch-abliteration.yaml +0 -0
- /package/agent/{skills → optional-skills}/mlops/research/DESCRIPTION.md +0 -0
- /package/agent/{skills → optional-skills}/mlops/research/dspy/SKILL.md +0 -0
- /package/agent/{skills → optional-skills}/mlops/research/dspy/references/examples.md +0 -0
- /package/agent/{skills → optional-skills}/mlops/research/dspy/references/modules.md +0 -0
- /package/agent/{skills → optional-skills}/mlops/research/dspy/references/optimizers.md +0 -0
- /package/agent/{skills/red-teaming → optional-skills/security}/godmode/references/jailbreak-templates.md +0 -0
- /package/agent/{skills/red-teaming → optional-skills/security}/godmode/references/refusal-detection.md +0 -0
- /package/agent/{skills/red-teaming → optional-skills/security}/godmode/scripts/godmode_race.py +0 -0
- /package/agent/{skills/red-teaming → optional-skills/security}/godmode/scripts/load_godmode.py +0 -0
- /package/agent/{skills/red-teaming → optional-skills/security}/godmode/scripts/parseltongue.py +0 -0
- /package/agent/{skills/red-teaming → optional-skills/security}/godmode/templates/prefill-subtle.json +0 -0
- /package/agent/{skills/red-teaming → optional-skills/security}/godmode/templates/prefill.json +0 -0
- /package/agent/{skills → optional-skills}/software-development/subagent-driven-development/references/context-budget-discipline.md +0 -0
- /package/agent/{skills → optional-skills}/software-development/subagent-driven-development/references/gates-taxonomy.md +0 -0
|
@@ -13,30 +13,51 @@ import {
|
|
|
13
13
|
useState
|
|
14
14
|
} from 'react'
|
|
15
15
|
|
|
16
|
-
import { hermesDirectiveFormatter } from '@/components/assistant-ui/directive-text'
|
|
16
|
+
import { hermesDirectiveFormatter, type SlashChipKind } from '@/components/assistant-ui/directive-text'
|
|
17
|
+
import { composerFill, composerSurfaceGlass } from '@/components/chat/composer-dock'
|
|
17
18
|
import { Button } from '@/components/ui/button'
|
|
18
19
|
import { useMediaQuery } from '@/hooks/use-media-query'
|
|
19
20
|
import { useResizeObserver } from '@/hooks/use-resize-observer'
|
|
21
|
+
import { useI18n } from '@/i18n'
|
|
20
22
|
import { chatMessageText } from '@/lib/chat-messages'
|
|
23
|
+
import { SLASH_COMMAND_RE } from '@/lib/chat-runtime'
|
|
24
|
+
import { desktopSlashCommandTakesArgs } from '@/lib/desktop-slash-commands'
|
|
21
25
|
import { DATA_IMAGE_URL_RE } from '@/lib/embedded-images'
|
|
22
26
|
import { triggerHaptic } from '@/lib/haptics'
|
|
23
27
|
import { cn } from '@/lib/utils'
|
|
24
28
|
import {
|
|
25
29
|
$composerAttachments,
|
|
26
30
|
clearComposerAttachments,
|
|
27
|
-
|
|
31
|
+
clearSessionDraft,
|
|
32
|
+
type ComposerAttachment,
|
|
33
|
+
stashSessionDraft,
|
|
34
|
+
takeSessionDraft
|
|
28
35
|
} from '@/store/composer'
|
|
36
|
+
import {
|
|
37
|
+
browseBackward,
|
|
38
|
+
browseForward,
|
|
39
|
+
deriveUserHistory,
|
|
40
|
+
isBrowsingHistory,
|
|
41
|
+
resetBrowseState
|
|
42
|
+
} from '@/store/composer-input-history'
|
|
29
43
|
import {
|
|
30
44
|
$queuedPromptsBySession,
|
|
31
45
|
enqueueQueuedPrompt,
|
|
46
|
+
MAX_AUTO_DRAIN_ATTEMPTS,
|
|
47
|
+
migrateQueuedPrompts,
|
|
48
|
+
promoteQueuedPrompt,
|
|
32
49
|
type QueuedPromptEntry,
|
|
33
50
|
removeQueuedPrompt,
|
|
51
|
+
shouldAutoDrain,
|
|
34
52
|
updateQueuedPrompt
|
|
35
53
|
} from '@/store/composer-queue'
|
|
36
|
-
import { $
|
|
54
|
+
import { $statusItemsBySession } from '@/store/composer-status'
|
|
55
|
+
import { notify } from '@/store/notifications'
|
|
56
|
+
import { $gatewayState, $messages, setSessionPickerOpen } from '@/store/session'
|
|
37
57
|
import { $threadScrolledUp } from '@/store/thread-scroll'
|
|
58
|
+
import { useTheme } from '@/themes'
|
|
38
59
|
|
|
39
|
-
import { extractDroppedFiles, HERMES_PATHS_MIME } from '../hooks/use-composer-actions'
|
|
60
|
+
import { extractDroppedFiles, HERMES_PATHS_MIME, partitionDroppedFiles } from '../hooks/use-composer-actions'
|
|
40
61
|
|
|
41
62
|
import { AttachmentList } from './attachments'
|
|
42
63
|
import { ContextMenu } from './context-menu'
|
|
@@ -47,6 +68,7 @@ import {
|
|
|
47
68
|
focusComposerInput,
|
|
48
69
|
markActiveComposer,
|
|
49
70
|
onComposerFocusRequest,
|
|
71
|
+
onComposerInsertRefsRequest,
|
|
50
72
|
onComposerInsertRequest
|
|
51
73
|
} from './focus'
|
|
52
74
|
import { HelpHint } from './help-hint'
|
|
@@ -54,16 +76,25 @@ import { useAtCompletions } from './hooks/use-at-completions'
|
|
|
54
76
|
import { useSlashCompletions } from './hooks/use-slash-completions'
|
|
55
77
|
import { useVoiceConversation } from './hooks/use-voice-conversation'
|
|
56
78
|
import { useVoiceRecorder } from './hooks/use-voice-recorder'
|
|
57
|
-
import {
|
|
79
|
+
import {
|
|
80
|
+
dragHasAttachments,
|
|
81
|
+
droppedFileInlineRefs,
|
|
82
|
+
type InlineRefInput,
|
|
83
|
+
insertInlineRefsIntoEditor
|
|
84
|
+
} from './inline-refs'
|
|
58
85
|
import { QueuePanel } from './queue-panel'
|
|
59
86
|
import {
|
|
60
87
|
composerPlainText,
|
|
88
|
+
deleteSelectionInEditor,
|
|
89
|
+
insertPlainTextAtCaret,
|
|
90
|
+
normalizeComposerEditorDom,
|
|
61
91
|
placeCaretEnd,
|
|
62
92
|
refChipElement,
|
|
63
93
|
renderComposerContents,
|
|
64
|
-
RICH_INPUT_SLOT
|
|
94
|
+
RICH_INPUT_SLOT,
|
|
95
|
+
slashChipElement
|
|
65
96
|
} from './rich-editor'
|
|
66
|
-
import {
|
|
97
|
+
import { ComposerStatusStack } from './status-stack'
|
|
67
98
|
import { detectTrigger, extractClipboardImageBlobs, textBeforeCaret, type TriggerState } from './text-utils'
|
|
68
99
|
import { ComposerTriggerPopover } from './trigger-popover'
|
|
69
100
|
import type { ChatBarProps } from './types'
|
|
@@ -72,9 +103,46 @@ import { VoiceActivity, VoicePlaybackActivity } from './voice-activity'
|
|
|
72
103
|
|
|
73
104
|
const COMPOSER_STACK_BREAKPOINT_PX = 320
|
|
74
105
|
|
|
106
|
+
// A single editor line is ~28px (--composer-input-min-height 1.625rem + 0.5rem
|
|
107
|
+
// vertical padding). Anything taller means the text wrapped to a second line,
|
|
108
|
+
// which is when the composer should expand to the stacked layout.
|
|
109
|
+
const COMPOSER_SINGLE_LINE_MAX_PX = 36
|
|
110
|
+
|
|
75
111
|
const COMPOSER_FADE_BACKGROUND =
|
|
76
112
|
'linear-gradient(to bottom, transparent, color-mix(in srgb, var(--dt-background) 10%, transparent))'
|
|
77
113
|
|
|
114
|
+
const pickPlaceholder = (pool: readonly string[]) => pool[Math.floor(Math.random() * pool.length)]
|
|
115
|
+
|
|
116
|
+
/** Completion items can carry an `action` (set in use-slash-completions) that
|
|
117
|
+
* runs a side effect on pick instead of inserting a chip — e.g. the session
|
|
118
|
+
* picker's "Browse all…" entry opens the overlay. Table-driven so new action
|
|
119
|
+
* items are a registry row, not a composer branch. */
|
|
120
|
+
const COMPLETION_ACTIONS: Record<string, () => void> = {
|
|
121
|
+
'session-picker': () => setSessionPickerOpen(true)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Map a picked `/` completion to its pill accent. Driven by the completion
|
|
125
|
+
* group set in use-slash-completions (Skills / Themes / Commands|Options). */
|
|
126
|
+
function slashChipKindForItem(item: Unstable_TriggerItem): SlashChipKind {
|
|
127
|
+
const group = (item.metadata as { group?: unknown } | undefined)?.group
|
|
128
|
+
|
|
129
|
+
if (group === 'Skills') {
|
|
130
|
+
return 'skill'
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (group === 'Themes') {
|
|
134
|
+
return 'theme'
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return 'command'
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** A `/` query is at its arg stage once it's past the command name. */
|
|
141
|
+
const slashArgStage = (query: string) => query.includes(' ')
|
|
142
|
+
|
|
143
|
+
/** The `/command` token of a slash query (`personality x` → `/personality`). */
|
|
144
|
+
const slashCommandToken = (query: string) => `/${query.split(/\s+/, 1)[0]?.toLowerCase() ?? ''}`
|
|
145
|
+
|
|
78
146
|
interface QueueEditState {
|
|
79
147
|
attachments: ComposerAttachment[]
|
|
80
148
|
draft: string
|
|
@@ -84,6 +152,10 @@ interface QueueEditState {
|
|
|
84
152
|
|
|
85
153
|
const cloneAttachments = (attachments: ComposerAttachment[]) => attachments.map(a => ({ ...a }))
|
|
86
154
|
|
|
155
|
+
// Quiet period after the last keystroke before persisting the draft;
|
|
156
|
+
// unmount/pagehide flushes bypass it.
|
|
157
|
+
const DRAFT_PERSIST_DEBOUNCE_MS = 400
|
|
158
|
+
|
|
87
159
|
export function ChatBar({
|
|
88
160
|
busy,
|
|
89
161
|
cwd,
|
|
@@ -103,6 +175,7 @@ export function ChatBar({
|
|
|
103
175
|
onPickFolders,
|
|
104
176
|
onPickImages,
|
|
105
177
|
onRemoveAttachment,
|
|
178
|
+
onSteer,
|
|
106
179
|
onSubmit,
|
|
107
180
|
onTranscribeAudio
|
|
108
181
|
}: ChatBarProps) {
|
|
@@ -110,6 +183,7 @@ export function ChatBar({
|
|
|
110
183
|
const draft = useAuiState(s => s.composer.text)
|
|
111
184
|
const attachments = useStore($composerAttachments)
|
|
112
185
|
const queuedPromptsBySession = useStore($queuedPromptsBySession)
|
|
186
|
+
const statusItemsBySession = useStore($statusItemsBySession)
|
|
113
187
|
const scrolledUp = useStore($threadScrolledUp)
|
|
114
188
|
const activeQueueSessionKey = queueSessionKey || sessionId || null
|
|
115
189
|
|
|
@@ -118,12 +192,29 @@ export function ChatBar({
|
|
|
118
192
|
[activeQueueSessionKey, queuedPromptsBySession]
|
|
119
193
|
)
|
|
120
194
|
|
|
195
|
+
// Status items (subagents, background processes) are keyed by the RUNTIME
|
|
196
|
+
// session id — gateway events and process.list both speak that id. Only the
|
|
197
|
+
// queue uses the stored-session fallback key (prompts can queue pre-resume).
|
|
198
|
+
const statusSessionId = sessionId ?? null
|
|
199
|
+
|
|
200
|
+
const statusStackVisible = useMemo(
|
|
201
|
+
() =>
|
|
202
|
+
queuedPrompts.length > 0 || (statusSessionId ? (statusItemsBySession[statusSessionId]?.length ?? 0) > 0 : false),
|
|
203
|
+
[queuedPrompts.length, statusItemsBySession, statusSessionId]
|
|
204
|
+
)
|
|
205
|
+
|
|
121
206
|
const composerRef = useRef<HTMLFormElement | null>(null)
|
|
122
207
|
const composerSurfaceRef = useRef<HTMLDivElement | null>(null)
|
|
123
208
|
const editorRef = useRef<HTMLDivElement | null>(null)
|
|
124
209
|
const draftRef = useRef(draft)
|
|
125
|
-
const
|
|
210
|
+
const pendingDraftPersistRef = useRef<{ scope: string | null; text: string } | null>(null)
|
|
211
|
+
const activeQueueSessionKeyRef = useRef(activeQueueSessionKey)
|
|
212
|
+
activeQueueSessionKeyRef.current = activeQueueSessionKey
|
|
213
|
+
const prevQueueKeyRef = useRef(activeQueueSessionKey)
|
|
126
214
|
const drainingQueueRef = useRef(false)
|
|
215
|
+
// Per-entry auto-drain failure counts; bounds retries so a persistent 404
|
|
216
|
+
// can't spin-loop. Cleared on success; reset naturally on remount/reconnect.
|
|
217
|
+
const drainFailuresRef = useRef(new Map<string, number>())
|
|
127
218
|
const urlInputRef = useRef<HTMLInputElement | null>(null)
|
|
128
219
|
|
|
129
220
|
const [urlOpen, setUrlOpen] = useState(false)
|
|
@@ -134,22 +225,78 @@ export function ChatBar({
|
|
|
134
225
|
const [dragActive, setDragActive] = useState(false)
|
|
135
226
|
const [queueEdit, setQueueEdit] = useState<QueueEditState | null>(null)
|
|
136
227
|
const [focusRequestId, setFocusRequestId] = useState(0)
|
|
228
|
+
const queueEditRef = useRef(queueEdit)
|
|
229
|
+
queueEditRef.current = queueEdit
|
|
137
230
|
const dragDepthRef = useRef(0)
|
|
231
|
+
const composingRef = useRef(false) // true during IME composition (CJK input)
|
|
138
232
|
const lastSpokenIdRef = useRef<string | null>(null)
|
|
139
233
|
|
|
140
234
|
const narrow = useMediaQuery('(max-width: 30rem)')
|
|
141
235
|
|
|
236
|
+
const { availableThemes, themeName } = useTheme()
|
|
142
237
|
const at = useAtCompletions({ gateway: gateway ?? null, sessionId: sessionId ?? null, cwd: cwd ?? null })
|
|
143
|
-
const slash = useSlashCompletions({ gateway: gateway ?? null })
|
|
238
|
+
const slash = useSlashCompletions({ activeSkin: themeName, gateway: gateway ?? null, skinThemes: availableThemes })
|
|
144
239
|
|
|
145
240
|
const stacked = expanded || narrow || tight
|
|
146
|
-
const
|
|
241
|
+
const trimmedDraft = draft.trim()
|
|
242
|
+
const hasComposerPayload = trimmedDraft.length > 0 || attachments.length > 0
|
|
147
243
|
const canSubmit = busy || hasComposerPayload
|
|
148
244
|
const editingQueuedPrompt = queueEdit ? (queuedPrompts.find(entry => entry.id === queueEdit.entryId) ?? null) : null
|
|
149
245
|
const busyAction = busy && hasComposerPayload ? 'queue' : 'stop'
|
|
246
|
+
|
|
247
|
+
// Steer only makes sense mid-turn, text-only (the gateway can't carry images
|
|
248
|
+
// into a tool result) and never for a slash command (those execute inline).
|
|
249
|
+
const canSteer =
|
|
250
|
+
busy && !!onSteer && attachments.length === 0 && trimmedDraft.length > 0 && !SLASH_COMMAND_RE.test(trimmedDraft)
|
|
251
|
+
|
|
150
252
|
const showHelpHint = draft === '?'
|
|
151
253
|
|
|
152
|
-
const
|
|
254
|
+
const { t } = useI18n()
|
|
255
|
+
const gatewayState = useStore($gatewayState)
|
|
256
|
+
const newSessionPlaceholders = t.composer.newSessionPlaceholders
|
|
257
|
+
const followUpPlaceholders = t.composer.followUpPlaceholders
|
|
258
|
+
const reconnecting = gatewayState === 'closed' || gatewayState === 'error'
|
|
259
|
+
const inputDisabled = disabled && !reconnecting
|
|
260
|
+
|
|
261
|
+
// Resting placeholder: a starter for brand-new sessions, a continuation for
|
|
262
|
+
// existing ones. Picked once and only re-rolled when we genuinely move to a
|
|
263
|
+
// *different* conversation. Critically, the first id assignment of a freshly
|
|
264
|
+
// started session (null → id, on the first send) is treated as the same
|
|
265
|
+
// conversation so the placeholder doesn't visibly flip mid-stream.
|
|
266
|
+
const [restingPlaceholder, setRestingPlaceholder] = useState(() =>
|
|
267
|
+
pickPlaceholder(sessionId ? followUpPlaceholders : newSessionPlaceholders)
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
const prevSessionIdRef = useRef(sessionId)
|
|
271
|
+
|
|
272
|
+
useEffect(() => {
|
|
273
|
+
const prev = prevSessionIdRef.current
|
|
274
|
+
prevSessionIdRef.current = sessionId
|
|
275
|
+
|
|
276
|
+
if (prev === sessionId) {
|
|
277
|
+
return
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// null → id: the new session we're already in just got persisted. Keep the
|
|
281
|
+
// starter we showed instead of swapping to a follow-up under the user.
|
|
282
|
+
if (prev == null && sessionId) {
|
|
283
|
+
return
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
resetBrowseState(prev)
|
|
287
|
+
setRestingPlaceholder(pickPlaceholder(sessionId ? followUpPlaceholders : newSessionPlaceholders))
|
|
288
|
+
}, [followUpPlaceholders, newSessionPlaceholders, sessionId])
|
|
289
|
+
|
|
290
|
+
// When the transport is disabled it's because the gateway isn't open.
|
|
291
|
+
// Distinguish a cold start ("Starting Hermes...") from a dropped connection
|
|
292
|
+
// we're trying to restore. During reconnect, keep the textbox editable so a
|
|
293
|
+
// flaky network doesn't block drafting; only submit/backend actions stay
|
|
294
|
+
// disabled until the gateway is open again.
|
|
295
|
+
const placeholder = disabled
|
|
296
|
+
? reconnecting
|
|
297
|
+
? t.composer.placeholderReconnecting
|
|
298
|
+
: t.composer.placeholderStarting
|
|
299
|
+
: restingPlaceholder
|
|
153
300
|
|
|
154
301
|
const focusInput = useCallback(() => {
|
|
155
302
|
focusComposerInput(editorRef.current)
|
|
@@ -188,13 +335,13 @@ export function ChatBar({
|
|
|
188
335
|
)
|
|
189
336
|
|
|
190
337
|
useEffect(() => {
|
|
191
|
-
if (!
|
|
338
|
+
if (!inputDisabled) {
|
|
192
339
|
focusInput()
|
|
193
340
|
}
|
|
194
|
-
}, [
|
|
341
|
+
}, [focusInput, focusKey, focusRequestId, inputDisabled])
|
|
195
342
|
|
|
196
343
|
useEffect(() => {
|
|
197
|
-
if (
|
|
344
|
+
if (inputDisabled) {
|
|
198
345
|
return undefined
|
|
199
346
|
}
|
|
200
347
|
|
|
@@ -214,7 +361,7 @@ export function ChatBar({
|
|
|
214
361
|
offFocus()
|
|
215
362
|
offInsert()
|
|
216
363
|
}
|
|
217
|
-
}, [appendExternalText,
|
|
364
|
+
}, [appendExternalText, inputDisabled])
|
|
218
365
|
|
|
219
366
|
// Keep draftRef in sync with the assistant-ui composer state for callers
|
|
220
367
|
// that read the latest text outside the React render cycle. We don't push
|
|
@@ -240,14 +387,13 @@ export function ChatBar({
|
|
|
240
387
|
}
|
|
241
388
|
}, [urlOpen])
|
|
242
389
|
|
|
243
|
-
//
|
|
244
|
-
//
|
|
245
|
-
//
|
|
246
|
-
//
|
|
247
|
-
//
|
|
248
|
-
//
|
|
249
|
-
//
|
|
250
|
-
// a height delta and we still expand.
|
|
390
|
+
// Expansion (input on its own full-width row, controls below) is driven by
|
|
391
|
+
// the editor's *actual* rendered height via the ResizeObserver in
|
|
392
|
+
// syncComposerMetrics — it only fires when the text genuinely wraps to a
|
|
393
|
+
// second line, so the layout flips exactly at the wrap point rather than at
|
|
394
|
+
// a guessed character count. We only handle the two cases the observer
|
|
395
|
+
// can't: an explicit newline (expand before layout settles) and an emptied
|
|
396
|
+
// draft (collapse back). We never read scrollHeight per keystroke.
|
|
251
397
|
useEffect(() => {
|
|
252
398
|
if (!draft) {
|
|
253
399
|
setExpanded(false)
|
|
@@ -259,7 +405,7 @@ export function ChatBar({
|
|
|
259
405
|
return
|
|
260
406
|
}
|
|
261
407
|
|
|
262
|
-
if (draft.includes('\n')
|
|
408
|
+
if (draft.includes('\n')) {
|
|
263
409
|
setExpanded(true)
|
|
264
410
|
}
|
|
265
411
|
}, [draft, expanded])
|
|
@@ -295,6 +441,18 @@ export function ChatBar({
|
|
|
295
441
|
}
|
|
296
442
|
}
|
|
297
443
|
|
|
444
|
+
// Expand once the input has actually wrapped past a single line. The
|
|
445
|
+
// observer only fires on real size changes, so this reads scrollHeight at
|
|
446
|
+
// most once per wrap (not per keystroke). One line ≈ 28px (1.625rem
|
|
447
|
+
// min-height + padding); a second line clears ~36px. We only ever expand
|
|
448
|
+
// here — collapse is handled by the emptied-draft effect to avoid
|
|
449
|
+
// oscillating across the wrap boundary as the input switches widths.
|
|
450
|
+
const editor = editorRef.current
|
|
451
|
+
|
|
452
|
+
if (editor && editor.scrollHeight > COMPOSER_SINGLE_LINE_MAX_PX) {
|
|
453
|
+
setExpanded(true)
|
|
454
|
+
}
|
|
455
|
+
|
|
298
456
|
if (height > 0) {
|
|
299
457
|
const bucket = Math.round(height / 8) * 8
|
|
300
458
|
|
|
@@ -314,7 +472,7 @@ export function ChatBar({
|
|
|
314
472
|
}
|
|
315
473
|
}, [])
|
|
316
474
|
|
|
317
|
-
useResizeObserver(syncComposerMetrics, composerRef, composerSurfaceRef)
|
|
475
|
+
useResizeObserver(syncComposerMetrics, composerRef, composerSurfaceRef, editorRef)
|
|
318
476
|
|
|
319
477
|
useEffect(() => {
|
|
320
478
|
return () => {
|
|
@@ -349,7 +507,7 @@ export function ChatBar({
|
|
|
349
507
|
requestMainFocus()
|
|
350
508
|
}
|
|
351
509
|
|
|
352
|
-
const insertInlineRefs = (refs:
|
|
510
|
+
const insertInlineRefs = (refs: InlineRefInput[]) => {
|
|
353
511
|
const editor = editorRef.current
|
|
354
512
|
|
|
355
513
|
if (!editor) {
|
|
@@ -369,51 +527,30 @@ export function ChatBar({
|
|
|
369
527
|
return true
|
|
370
528
|
}
|
|
371
529
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
}
|
|
530
|
+
// Latest-closure ref so the (once-only) subscription always calls the current
|
|
531
|
+
// insertInlineRefs without re-subscribing every render.
|
|
532
|
+
const insertInlineRefsRef = useRef(insertInlineRefs)
|
|
533
|
+
insertInlineRefsRef.current = insertInlineRefs
|
|
377
534
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
event.preventDefault()
|
|
383
|
-
|
|
384
|
-
if (onAttachImageBlob) {
|
|
385
|
-
triggerHaptic('selection')
|
|
386
|
-
|
|
387
|
-
for (const blob of imageBlobs) {
|
|
388
|
-
void onAttachImageBlob(blob)
|
|
389
|
-
}
|
|
535
|
+
useEffect(() => {
|
|
536
|
+
return onComposerInsertRefsRequest(({ refs, target }) => {
|
|
537
|
+
if (target === 'main') {
|
|
538
|
+
insertInlineRefsRef.current(refs)
|
|
390
539
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
const pastedText = event.clipboardData.getData('text')
|
|
396
|
-
|
|
397
|
-
if (!pastedText) {
|
|
398
|
-
return
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
if (DATA_IMAGE_URL_RE.test(pastedText.trim())) {
|
|
402
|
-
event.preventDefault()
|
|
403
|
-
|
|
404
|
-
return
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
event.preventDefault()
|
|
408
|
-
document.execCommand('insertText', false, pastedText)
|
|
409
|
-
const nextDraft = composerPlainText(event.currentTarget)
|
|
410
|
-
draftRef.current = nextDraft
|
|
411
|
-
aui.composer().setText(nextDraft)
|
|
412
|
-
}
|
|
540
|
+
})
|
|
541
|
+
}, [])
|
|
413
542
|
|
|
414
543
|
const [trigger, setTrigger] = useState<TriggerState | null>(null)
|
|
415
544
|
const [triggerActive, setTriggerActive] = useState(0)
|
|
416
545
|
const [triggerItems, setTriggerItems] = useState<readonly Unstable_TriggerItem[]>([])
|
|
546
|
+
// Set synchronously in keydown when the open trigger popover consumes a
|
|
547
|
+
// navigation/control key (Arrow/Enter/Tab/Escape). The subsequent keyup must
|
|
548
|
+
// NOT run refreshTrigger for that keypress: it never edits text, and for
|
|
549
|
+
// Escape the keydown has already set trigger=null, so a keyup refresh would
|
|
550
|
+
// re-detect the still-present `/` and instantly reopen the menu. A ref is
|
|
551
|
+
// used instead of reading `trigger` in keyup because by keyup time React has
|
|
552
|
+
// re-rendered and the handler closure sees the post-keydown state.
|
|
553
|
+
const triggerKeyConsumedRef = useRef(false)
|
|
417
554
|
|
|
418
555
|
const refreshTrigger = useCallback(() => {
|
|
419
556
|
const editor = editorRef.current
|
|
@@ -439,18 +576,32 @@ export function ChatBar({
|
|
|
439
576
|
}
|
|
440
577
|
|
|
441
578
|
const before = textBeforeCaret(editor)
|
|
442
|
-
const
|
|
579
|
+
const found = detectTrigger(before ?? composerPlainText(editor))
|
|
443
580
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
581
|
+
// The arg-stage popover is only useful for commands with an options screen.
|
|
582
|
+
// For a no-arg command it would dead-end on "No matches", so drop it — the
|
|
583
|
+
// directive is already complete.
|
|
584
|
+
const detected =
|
|
585
|
+
found?.kind === '/' && slashArgStage(found.query) && !desktopSlashCommandTakesArgs(slashCommandToken(found.query))
|
|
586
|
+
? null
|
|
587
|
+
: found
|
|
447
588
|
|
|
448
|
-
|
|
449
|
-
const editor = event.currentTarget
|
|
589
|
+
setTrigger(detected)
|
|
450
590
|
|
|
451
|
-
|
|
452
|
-
|
|
591
|
+
// Only reset the highlight when the trigger actually changed (opened, or
|
|
592
|
+
// the query/kind differs). Re-detecting the *same* trigger — e.g. on a
|
|
593
|
+
// caret move (mouseup) or a stray refresh — must preserve the user's
|
|
594
|
+
// current selection instead of snapping back to the first item.
|
|
595
|
+
if (detected?.kind !== trigger?.kind || detected?.query !== trigger?.query) {
|
|
596
|
+
setTriggerActive(0)
|
|
453
597
|
}
|
|
598
|
+
}, [trigger])
|
|
599
|
+
|
|
600
|
+
// Pull the live contentEditable text into draftRef + the AUI composer state
|
|
601
|
+
// (which drives `hasComposerPayload` → the send button). Shared by the input
|
|
602
|
+
// and compositionend paths so committed IME text reaches state through either.
|
|
603
|
+
const flushEditorToDraft = (editor: HTMLDivElement) => {
|
|
604
|
+
normalizeComposerEditorDom(editor)
|
|
454
605
|
|
|
455
606
|
const nextDraft = composerPlainText(editor)
|
|
456
607
|
|
|
@@ -462,6 +613,57 @@ export function ChatBar({
|
|
|
462
613
|
window.setTimeout(refreshTrigger, 0)
|
|
463
614
|
}
|
|
464
615
|
|
|
616
|
+
const handleEditorInput = (event: FormEvent<HTMLDivElement>) => {
|
|
617
|
+
// During IME composition the DOM contains uncommitted preedit text
|
|
618
|
+
// mixed with real content. Skip state writes — compositionend flushes
|
|
619
|
+
// the finalized text (see onCompositionEnd).
|
|
620
|
+
if (composingRef.current) {
|
|
621
|
+
return
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
flushEditorToDraft(event.currentTarget)
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const handlePaste = (event: ClipboardEvent<HTMLDivElement>) => {
|
|
628
|
+
const imageBlobs = extractClipboardImageBlobs(event.clipboardData)
|
|
629
|
+
|
|
630
|
+
if (imageBlobs.length > 0) {
|
|
631
|
+
event.preventDefault()
|
|
632
|
+
|
|
633
|
+
if (onAttachImageBlob) {
|
|
634
|
+
triggerHaptic('selection')
|
|
635
|
+
|
|
636
|
+
for (const blob of imageBlobs) {
|
|
637
|
+
void onAttachImageBlob(blob)
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
return
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// Trim surrounding whitespace so a copy that dragged along leading/trailing
|
|
645
|
+
// blank lines (common when selecting from terminals, code blocks, web pages)
|
|
646
|
+
// doesn't dump multiline padding into the composer. Internal newlines are
|
|
647
|
+
// preserved — only the edges are cleaned up.
|
|
648
|
+
const pastedText = event.clipboardData.getData('text').trim()
|
|
649
|
+
|
|
650
|
+
if (!pastedText) {
|
|
651
|
+
event.preventDefault()
|
|
652
|
+
|
|
653
|
+
return
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
if (DATA_IMAGE_URL_RE.test(pastedText)) {
|
|
657
|
+
event.preventDefault()
|
|
658
|
+
|
|
659
|
+
return
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
event.preventDefault()
|
|
663
|
+
insertPlainTextAtCaret(event.currentTarget, pastedText)
|
|
664
|
+
flushEditorToDraft(event.currentTarget)
|
|
665
|
+
}
|
|
666
|
+
|
|
465
667
|
const triggerAdapter: Unstable_TriggerAdapter | null =
|
|
466
668
|
trigger?.kind === '@' ? at.adapter : trigger?.kind === '/' ? slash.adapter : null
|
|
467
669
|
|
|
@@ -477,6 +679,12 @@ export function ChatBar({
|
|
|
477
679
|
|
|
478
680
|
const triggerLoading = trigger?.kind === '@' ? at.loading : trigger?.kind === '/' ? slash.loading : false
|
|
479
681
|
|
|
682
|
+
// Suppress the "No matches" empty state once a slash command is past its name:
|
|
683
|
+
// a no-arg command has nothing to offer, and a fully-typed arg commits on
|
|
684
|
+
// Space/Tab — neither should dead-end on a popover.
|
|
685
|
+
const argStageEmpty =
|
|
686
|
+
trigger?.kind === '/' && slashArgStage(trigger.query) && !triggerLoading && !triggerItems.length
|
|
687
|
+
|
|
480
688
|
const closeTrigger = () => {
|
|
481
689
|
setTrigger(null)
|
|
482
690
|
setTriggerItems([])
|
|
@@ -487,6 +695,25 @@ export function ChatBar({
|
|
|
487
695
|
setTriggerActive(idx => Math.min(idx, Math.max(0, triggerItems.length - 1)))
|
|
488
696
|
}, [triggerItems.length])
|
|
489
697
|
|
|
698
|
+
// Commit the literally-typed `/command arg` as a directive chip — used when
|
|
699
|
+
// the completion list is empty because the arg is already fully typed (the
|
|
700
|
+
// backend completer drops exact matches). Reuses the chip path via a
|
|
701
|
+
// synthetic item whose serialized form is the verbatim text.
|
|
702
|
+
const commitTypedSlashDirective = () => {
|
|
703
|
+
if (trigger?.kind !== '/') {
|
|
704
|
+
return
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
const text = `/${trigger.query.trimEnd()}`
|
|
708
|
+
|
|
709
|
+
replaceTriggerWithChip({
|
|
710
|
+
id: text,
|
|
711
|
+
type: 'slash',
|
|
712
|
+
label: text.slice(1),
|
|
713
|
+
metadata: { command: slashCommandToken(trigger.query), display: text, meta: '', group: '', action: '', rawText: text }
|
|
714
|
+
})
|
|
715
|
+
}
|
|
716
|
+
|
|
490
717
|
const replaceTriggerWithChip = (item: Unstable_TriggerItem) => {
|
|
491
718
|
const editor = editorRef.current
|
|
492
719
|
|
|
@@ -494,16 +721,49 @@ export function ChatBar({
|
|
|
494
721
|
return
|
|
495
722
|
}
|
|
496
723
|
|
|
724
|
+
// Action items (e.g. "Browse all sessions…") run a side effect instead of
|
|
725
|
+
// inserting a chip: strip the typed trigger token, then fire the action.
|
|
726
|
+
const completionAction = (item.metadata as { action?: unknown } | undefined)?.action
|
|
727
|
+
const runAction = typeof completionAction === 'string' ? COMPLETION_ACTIONS[completionAction] : undefined
|
|
728
|
+
|
|
729
|
+
if (runAction) {
|
|
730
|
+
const current = composerPlainText(editor)
|
|
731
|
+
const prefix = current.slice(0, Math.max(0, current.length - trigger.tokenLength))
|
|
732
|
+
|
|
733
|
+
renderComposerContents(editor, prefix)
|
|
734
|
+
placeCaretEnd(editor)
|
|
735
|
+
draftRef.current = composerPlainText(editor)
|
|
736
|
+
aui.composer().setText(draftRef.current)
|
|
737
|
+
closeTrigger()
|
|
738
|
+
runAction()
|
|
739
|
+
requestMainFocus()
|
|
740
|
+
|
|
741
|
+
return
|
|
742
|
+
}
|
|
743
|
+
|
|
497
744
|
const serialized = hermesDirectiveFormatter.serialize(item)
|
|
498
745
|
const starter = serialized.endsWith(':')
|
|
746
|
+
|
|
747
|
+
// Picking a bare arg-taking command (e.g. `/personality`) shouldn't commit
|
|
748
|
+
// it — expand to its options step so the popover shows the inline list, just
|
|
749
|
+
// as typing `/personality ` by hand would. A serialized value with a space is
|
|
750
|
+
// already an arg pick (`/personality alice`), so it commits normally.
|
|
751
|
+
const command = (item.metadata as { command?: string } | undefined)?.command ?? ''
|
|
752
|
+
|
|
753
|
+
const expandsToArgs = trigger.kind === '/' && !serialized.includes(' ') && desktopSlashCommandTakesArgs(command)
|
|
754
|
+
|
|
499
755
|
const text = starter || serialized.endsWith(' ') ? serialized : `${serialized} `
|
|
500
756
|
const directive = !starter && serialized.match(/^@([^:]+):(.+)$/)
|
|
757
|
+
// No pill while expanding — the bare command stays plain text until an arg
|
|
758
|
+
// is picked, at which point a single pill is emitted for the full command.
|
|
759
|
+
const slashKind = !expandsToArgs && trigger.kind === '/' ? slashChipKindForItem(item) : null
|
|
760
|
+
const keepTriggerOpen = starter || expandsToArgs
|
|
501
761
|
|
|
502
762
|
const finish = () => {
|
|
503
763
|
draftRef.current = composerPlainText(editor)
|
|
504
764
|
aui.composer().setText(draftRef.current)
|
|
505
765
|
requestMainFocus()
|
|
506
|
-
|
|
766
|
+
keepTriggerOpen ? window.setTimeout(refreshTrigger, 0) : closeTrigger()
|
|
507
767
|
}
|
|
508
768
|
|
|
509
769
|
const sel = window.getSelection()
|
|
@@ -513,7 +773,20 @@ export function ChatBar({
|
|
|
513
773
|
|
|
514
774
|
if (!sel || !range || node?.nodeType !== Node.TEXT_NODE || offset < trigger.tokenLength) {
|
|
515
775
|
const current = composerPlainText(editor)
|
|
516
|
-
|
|
776
|
+
const prefix = current.slice(0, Math.max(0, current.length - trigger.tokenLength))
|
|
777
|
+
|
|
778
|
+
if (slashKind) {
|
|
779
|
+
// Two-step arg picks (e.g. `/handoff` pill already inserted, now picking
|
|
780
|
+
// the platform) land here because the caret sits past a contenteditable
|
|
781
|
+
// chip. Rebuild the prefix and re-emit a single pill for the full command.
|
|
782
|
+
renderComposerContents(editor, prefix)
|
|
783
|
+
editor.append(slashChipElement(serialized, slashKind), document.createTextNode(' '))
|
|
784
|
+
placeCaretEnd(editor)
|
|
785
|
+
|
|
786
|
+
return finish()
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
renderComposerContents(editor, `${prefix}${text}`)
|
|
517
790
|
placeCaretEnd(editor)
|
|
518
791
|
|
|
519
792
|
return finish()
|
|
@@ -524,8 +797,13 @@ export function ChatBar({
|
|
|
524
797
|
replaceRange.setEnd(node, offset)
|
|
525
798
|
replaceRange.deleteContents()
|
|
526
799
|
|
|
527
|
-
|
|
528
|
-
|
|
800
|
+
const chip = slashKind
|
|
801
|
+
? slashChipElement(serialized, slashKind)
|
|
802
|
+
: directive
|
|
803
|
+
? refChipElement(directive[1], directive[2])
|
|
804
|
+
: null
|
|
805
|
+
|
|
806
|
+
if (chip) {
|
|
529
807
|
const space = document.createTextNode(' ')
|
|
530
808
|
const fragment = document.createDocumentFragment()
|
|
531
809
|
fragment.append(chip, space)
|
|
@@ -545,7 +823,30 @@ export function ChatBar({
|
|
|
545
823
|
}
|
|
546
824
|
|
|
547
825
|
const handleEditorKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
|
|
548
|
-
|
|
826
|
+
// IME composition: Enter confirms composed text, not a message submission.
|
|
827
|
+
// We check both composingRef (set by compositionstart/compositionend, robust
|
|
828
|
+
// across browsers) and nativeEvent.isComposing (Chromium fallback). Without
|
|
829
|
+
// this guard, pressing Enter to finalise a Korean/Japanese/Chinese IME
|
|
830
|
+
// preedit fires submitDraft() and splits the message mid-word.
|
|
831
|
+
if (composingRef.current || event.nativeEvent.isComposing) {
|
|
832
|
+
return
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// Non-collapsed Backspace/Delete: native selection-delete is ~O(n²) on large
|
|
836
|
+
// drafts (Ctrl+A → Delete froze ~1.3s). Collapsed carets fall through.
|
|
837
|
+
if (
|
|
838
|
+
(event.key === 'Backspace' || event.key === 'Delete') &&
|
|
839
|
+
deleteSelectionInEditor(event.currentTarget)
|
|
840
|
+
) {
|
|
841
|
+
event.preventDefault()
|
|
842
|
+
flushEditorToDraft(event.currentTarget)
|
|
843
|
+
|
|
844
|
+
return
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// Cmd/Ctrl+Shift+K drains the next queued message. Plain Cmd/Ctrl+K is
|
|
848
|
+
// reserved for the global command palette.
|
|
849
|
+
if ((event.metaKey || event.ctrlKey) && !event.altKey && event.shiftKey && event.key.toLowerCase() === 'k') {
|
|
549
850
|
event.preventDefault()
|
|
550
851
|
|
|
551
852
|
if (!busy) {
|
|
@@ -558,6 +859,7 @@ export function ChatBar({
|
|
|
558
859
|
if (trigger && triggerItems.length > 0) {
|
|
559
860
|
if (event.key === 'ArrowDown') {
|
|
560
861
|
event.preventDefault()
|
|
862
|
+
triggerKeyConsumedRef.current = true
|
|
561
863
|
setTriggerActive(idx => (idx + 1) % triggerItems.length)
|
|
562
864
|
|
|
563
865
|
return
|
|
@@ -565,13 +867,23 @@ export function ChatBar({
|
|
|
565
867
|
|
|
566
868
|
if (event.key === 'ArrowUp') {
|
|
567
869
|
event.preventDefault()
|
|
870
|
+
triggerKeyConsumedRef.current = true
|
|
568
871
|
setTriggerActive(idx => (idx - 1 + triggerItems.length) % triggerItems.length)
|
|
569
872
|
|
|
570
873
|
return
|
|
571
874
|
}
|
|
572
875
|
|
|
573
|
-
|
|
876
|
+
// Enter / Tab / Space all accept the highlighted item: a no-arg command
|
|
877
|
+
// commits its directive chip, an arg-taking command expands to its
|
|
878
|
+
// options step, and an arg option commits the full `/cmd arg` chip. Space
|
|
879
|
+
// is slash-only (an `@` mention takes a literal space) and gated to a
|
|
880
|
+
// non-empty query so a bare `/ ` still types a space.
|
|
881
|
+
const acceptOnSpace = event.key === ' ' && trigger.kind === '/' && Boolean(trigger.query.trim())
|
|
882
|
+
const accept = event.key === 'Enter' || event.key === 'Tab' || acceptOnSpace
|
|
883
|
+
|
|
884
|
+
if (accept) {
|
|
574
885
|
event.preventDefault()
|
|
886
|
+
triggerKeyConsumedRef.current = true
|
|
575
887
|
const item = triggerItems[triggerActive]
|
|
576
888
|
|
|
577
889
|
if (item) {
|
|
@@ -583,26 +895,181 @@ export function ChatBar({
|
|
|
583
895
|
|
|
584
896
|
if (event.key === 'Escape') {
|
|
585
897
|
event.preventDefault()
|
|
898
|
+
triggerKeyConsumedRef.current = true
|
|
586
899
|
closeTrigger()
|
|
587
900
|
|
|
588
901
|
return
|
|
589
902
|
}
|
|
590
903
|
}
|
|
591
904
|
|
|
905
|
+
// Arg stage with nothing left to suggest — a fully-typed arg the backend
|
|
906
|
+
// completer no longer echoes (it drops the exact match), e.g.
|
|
907
|
+
// `/personality creative`. Space/Tab still commit what's typed as a single
|
|
908
|
+
// directive chip; Enter falls through to submit (send it as-is).
|
|
909
|
+
if (
|
|
910
|
+
trigger?.kind === '/' &&
|
|
911
|
+
!triggerItems.length &&
|
|
912
|
+
(event.key === ' ' || event.key === 'Tab') &&
|
|
913
|
+
slashArgStage(trigger.query) &&
|
|
914
|
+
trigger.query.trim()
|
|
915
|
+
) {
|
|
916
|
+
event.preventDefault()
|
|
917
|
+
triggerKeyConsumedRef.current = true
|
|
918
|
+
commitTypedSlashDirective()
|
|
919
|
+
|
|
920
|
+
return
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
// ArrowUp/ArrowDown navigate, in priority order: the queue (edit entries in
|
|
924
|
+
// place) then sent-message history. The history ring is derived from live
|
|
925
|
+
// session messages each press — single source of truth, no mirror.
|
|
926
|
+
if (event.key === 'ArrowUp') {
|
|
927
|
+
const currentDraft = draftRef.current
|
|
928
|
+
|
|
929
|
+
// Editing a queued turn → walk to the older entry.
|
|
930
|
+
if (queueEdit && stepQueuedEdit(-1)) {
|
|
931
|
+
event.preventDefault()
|
|
932
|
+
triggerKeyConsumedRef.current = true
|
|
933
|
+
|
|
934
|
+
return
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
// Empty composer + a queued turn → open the newest queued entry for edit
|
|
938
|
+
// (the row's pencil), not a text recall. Enter saves it back to the queue.
|
|
939
|
+
if (!currentDraft.trim() && !queueEdit && queuedPrompts.length > 0) {
|
|
940
|
+
event.preventDefault()
|
|
941
|
+
triggerKeyConsumedRef.current = true
|
|
942
|
+
beginQueuedEdit(queuedPrompts[queuedPrompts.length - 1]!)
|
|
943
|
+
|
|
944
|
+
return
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
// Don't hijack a typed draft unless already browsing — they'd lose it.
|
|
948
|
+
if (currentDraft.trim() && !isBrowsingHistory(sessionId)) {
|
|
949
|
+
return
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
event.preventDefault()
|
|
953
|
+
triggerKeyConsumedRef.current = true
|
|
954
|
+
|
|
955
|
+
// $messages is read imperatively (not subscribed) so the composer
|
|
956
|
+
// doesn't re-render on every streaming delta flush.
|
|
957
|
+
const history = deriveUserHistory($messages.get(), chatMessageText)
|
|
958
|
+
const entry = browseBackward(sessionId, currentDraft, history)
|
|
959
|
+
|
|
960
|
+
if (entry !== null) {
|
|
961
|
+
loadIntoComposer(entry, $composerAttachments.get())
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
return
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
if (event.key === 'ArrowDown') {
|
|
968
|
+
// Editing a queued turn → walk to the newer entry (past the newest exits).
|
|
969
|
+
if (queueEdit) {
|
|
970
|
+
event.preventDefault()
|
|
971
|
+
triggerKeyConsumedRef.current = true
|
|
972
|
+
stepQueuedEdit(1)
|
|
973
|
+
|
|
974
|
+
return
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
// Browsing sent history → step toward the present, restoring the draft.
|
|
978
|
+
if (isBrowsingHistory(sessionId)) {
|
|
979
|
+
event.preventDefault()
|
|
980
|
+
triggerKeyConsumedRef.current = true
|
|
981
|
+
|
|
982
|
+
const history = deriveUserHistory($messages.get(), chatMessageText)
|
|
983
|
+
const result = browseForward(sessionId, history)
|
|
984
|
+
|
|
985
|
+
if (result !== null) {
|
|
986
|
+
loadIntoComposer(result.text, $composerAttachments.get())
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
return
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
// Cmd/Ctrl+Enter is reserved for steering the live run — never a send.
|
|
994
|
+
// Steer when there's a steerable draft, otherwise swallow it so it can't
|
|
995
|
+
// surprise-send. (Plain Enter still queues while busy / sends when idle.)
|
|
996
|
+
if (event.key === 'Enter' && (event.metaKey || event.ctrlKey) && !event.shiftKey) {
|
|
997
|
+
event.preventDefault()
|
|
998
|
+
|
|
999
|
+
if (canSteer) {
|
|
1000
|
+
steerDraft()
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
return
|
|
1004
|
+
}
|
|
1005
|
+
|
|
592
1006
|
if (event.key === 'Enter' && !event.shiftKey) {
|
|
593
1007
|
event.preventDefault()
|
|
594
1008
|
|
|
595
|
-
|
|
1009
|
+
// Decide from the DOM, not React state. `hasComposerPayload` is derived
|
|
1010
|
+
// from the AUI composer state, which lags the latest keystroke by a
|
|
1011
|
+
// render, so on fast typing / IME the just-typed text isn't in state yet.
|
|
1012
|
+
// Without the live read, a real message typed while prompts are queued
|
|
1013
|
+
// would drain the queue instead of sending. submitDraft() re-syncs and
|
|
1014
|
+
// sends the live editor text.
|
|
1015
|
+
const editorText = editorRef.current ? composerPlainText(editorRef.current) : draftRef.current
|
|
1016
|
+
const hasLivePayload = editorText.trim().length > 0 || attachments.length > 0
|
|
1017
|
+
|
|
1018
|
+
if (disabled) {
|
|
1019
|
+
return
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
if (!busy && !hasLivePayload && queuedPrompts.length > 0) {
|
|
596
1023
|
void drainNextQueued()
|
|
597
1024
|
|
|
598
1025
|
return
|
|
599
1026
|
}
|
|
600
1027
|
|
|
1028
|
+
// Empty Enter while busy is a no-op — interrupting is explicit (Stop/Esc),
|
|
1029
|
+
// never a stray Enter after sending. With a payload, submitDraft queues it.
|
|
1030
|
+
// Gate on the live DOM payload (not the render-lagged composer state) so a
|
|
1031
|
+
// message typed fast / via IME while busy still reaches submitDraft() and
|
|
1032
|
+
// gets queued instead of being mistaken for an empty Enter.
|
|
1033
|
+
if (busy && !hasLivePayload) {
|
|
1034
|
+
return
|
|
1035
|
+
}
|
|
1036
|
+
|
|
601
1037
|
submitDraft()
|
|
1038
|
+
|
|
1039
|
+
return
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
if (event.key === 'Escape') {
|
|
1043
|
+
// Editing a queued turn → Esc cancels the edit, restoring the prior draft.
|
|
1044
|
+
if (queueEdit) {
|
|
1045
|
+
event.preventDefault()
|
|
1046
|
+
exitQueuedEdit('cancel')
|
|
1047
|
+
|
|
1048
|
+
return
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
// Otherwise Esc interrupts the running turn (Stop-button parity).
|
|
1052
|
+
if (busy) {
|
|
1053
|
+
event.preventDefault()
|
|
1054
|
+
triggerHaptic('cancel')
|
|
1055
|
+
void Promise.resolve(onCancel())
|
|
1056
|
+
}
|
|
602
1057
|
}
|
|
603
1058
|
}
|
|
604
1059
|
|
|
605
1060
|
const handleEditorKeyUp = () => {
|
|
1061
|
+
// If this keyup belongs to a key the open trigger popover already consumed
|
|
1062
|
+
// in keydown (Arrow/Enter/Tab/Escape), skip the refresh. Those keys never
|
|
1063
|
+
// edit text, and for Escape the keydown already closed the menu — a refresh
|
|
1064
|
+
// here would re-detect the still-present `/` and instantly reopen it. We
|
|
1065
|
+
// read a ref set during keydown rather than `trigger`, because by keyup
|
|
1066
|
+
// time React has re-rendered and `trigger` may already be null.
|
|
1067
|
+
if (triggerKeyConsumedRef.current) {
|
|
1068
|
+
triggerKeyConsumedRef.current = false
|
|
1069
|
+
|
|
1070
|
+
return
|
|
1071
|
+
}
|
|
1072
|
+
|
|
606
1073
|
window.setTimeout(refreshTrigger, 0)
|
|
607
1074
|
}
|
|
608
1075
|
|
|
@@ -660,24 +1127,25 @@ export function ChatBar({
|
|
|
660
1127
|
return
|
|
661
1128
|
}
|
|
662
1129
|
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
1130
|
+
// In-app drags (project tree / gutter) are workspace-relative paths the
|
|
1131
|
+
// gateway resolves directly, so they stay inline @file:/@line: refs. OS
|
|
1132
|
+
// drops are absolute local paths a remote gateway can't read (and images
|
|
1133
|
+
// need byte upload for vision), so route them through the upload pipeline.
|
|
1134
|
+
const { inAppRefs, osDrops } = partitionDroppedFiles(candidates)
|
|
1135
|
+
const refs = droppedFileInlineRefs(inAppRefs, cwd)
|
|
667
1136
|
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
return
|
|
1137
|
+
if (refs.length && insertInlineRefs(refs)) {
|
|
1138
|
+
triggerHaptic('selection')
|
|
673
1139
|
}
|
|
674
1140
|
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
1141
|
+
if (osDrops.length) {
|
|
1142
|
+
void Promise.resolve(onAttachDroppedItems(osDrops)).then(attached => {
|
|
1143
|
+
if (attached) {
|
|
1144
|
+
triggerHaptic('selection')
|
|
1145
|
+
requestMainFocus()
|
|
1146
|
+
}
|
|
1147
|
+
})
|
|
1148
|
+
}
|
|
681
1149
|
}
|
|
682
1150
|
|
|
683
1151
|
const handleInputDragOver = (event: ReactDragEvent<HTMLDivElement>) => {
|
|
@@ -697,11 +1165,7 @@ export function ChatBar({
|
|
|
697
1165
|
|
|
698
1166
|
const candidates = extractDroppedFiles(event.dataTransfer)
|
|
699
1167
|
|
|
700
|
-
|
|
701
|
-
.map(candidate => droppedFileInlineRef(candidate, cwd))
|
|
702
|
-
.filter((ref): ref is string => Boolean(ref))
|
|
703
|
-
|
|
704
|
-
if (!refs.length) {
|
|
1168
|
+
if (!candidates.length) {
|
|
705
1169
|
return
|
|
706
1170
|
}
|
|
707
1171
|
|
|
@@ -709,9 +1173,27 @@ export function ChatBar({
|
|
|
709
1173
|
event.stopPropagation()
|
|
710
1174
|
resetDragState()
|
|
711
1175
|
|
|
712
|
-
|
|
1176
|
+
// Dropping straight onto the text box used to inline-ref *every* file —
|
|
1177
|
+
// including OS/Finder drops, whose absolute local path a remote gateway
|
|
1178
|
+
// can't read and whose image bytes never reached vision. Split by origin:
|
|
1179
|
+
// in-app drags stay inline refs; OS drops go through the upload pipeline.
|
|
1180
|
+
// (When no upload handler is wired, fall back to inline refs for all.)
|
|
1181
|
+
const attach = onAttachDroppedItems
|
|
1182
|
+
const { inAppRefs, osDrops } = partitionDroppedFiles(candidates)
|
|
1183
|
+
const refs = droppedFileInlineRefs(attach ? inAppRefs : candidates, cwd)
|
|
1184
|
+
|
|
1185
|
+
if (refs.length && insertInlineRefs(refs)) {
|
|
713
1186
|
triggerHaptic('selection')
|
|
714
1187
|
}
|
|
1188
|
+
|
|
1189
|
+
if (attach && osDrops.length) {
|
|
1190
|
+
void Promise.resolve(attach(osDrops)).then(attached => {
|
|
1191
|
+
if (attached) {
|
|
1192
|
+
triggerHaptic('selection')
|
|
1193
|
+
requestMainFocus()
|
|
1194
|
+
}
|
|
1195
|
+
})
|
|
1196
|
+
}
|
|
715
1197
|
}
|
|
716
1198
|
|
|
717
1199
|
const clearDraft = useCallback(() => {
|
|
@@ -736,6 +1218,66 @@ export function ChatBar({
|
|
|
736
1218
|
}
|
|
737
1219
|
}
|
|
738
1220
|
|
|
1221
|
+
const stashAt = (scope: string | null, text = draftRef.current, attachments = $composerAttachments.get()) =>
|
|
1222
|
+
stashSessionDraft(scope, text, attachments)
|
|
1223
|
+
|
|
1224
|
+
// Per-thread draft swap — the composer's only session coupling. Lifecycle
|
|
1225
|
+
// never clears composer state; this effect alone stashes on leave, restores
|
|
1226
|
+
// on enter. Keyed writes are idempotent, so no skip-sentinel.
|
|
1227
|
+
useEffect(() => {
|
|
1228
|
+
const { attachments, text } = takeSessionDraft(activeQueueSessionKey)
|
|
1229
|
+
loadIntoComposer(text, attachments)
|
|
1230
|
+
|
|
1231
|
+
return () => {
|
|
1232
|
+
const editing = queueEditRef.current
|
|
1233
|
+
|
|
1234
|
+
if (editing?.sessionKey === activeQueueSessionKey) {
|
|
1235
|
+
stashAt(activeQueueSessionKey, editing.draft, editing.attachments)
|
|
1236
|
+
} else if (!isBrowsingHistory(sessionId)) {
|
|
1237
|
+
stashAt(activeQueueSessionKey)
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
}, [activeQueueSessionKey]) // eslint-disable-line react-hooks/exhaustive-deps
|
|
1241
|
+
|
|
1242
|
+
// Debounced stash into the active scope. Skipped while browsing history or
|
|
1243
|
+
// editing a queued prompt — recalled text must not clobber the real draft.
|
|
1244
|
+
useEffect(() => {
|
|
1245
|
+
if (isBrowsingHistory(sessionId) || queueEdit) {
|
|
1246
|
+
return
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
pendingDraftPersistRef.current = { scope: activeQueueSessionKey, text: draft }
|
|
1250
|
+
|
|
1251
|
+
const handle = window.setTimeout(() => {
|
|
1252
|
+
pendingDraftPersistRef.current = null
|
|
1253
|
+
stashAt(activeQueueSessionKey, draft)
|
|
1254
|
+
}, DRAFT_PERSIST_DEBOUNCE_MS)
|
|
1255
|
+
|
|
1256
|
+
return () => window.clearTimeout(handle)
|
|
1257
|
+
}, [activeQueueSessionKey, draft, queueEdit, sessionId])
|
|
1258
|
+
|
|
1259
|
+
// pagehide is load-bearing: React skips effect cleanups on reload, so Cmd+R
|
|
1260
|
+
// inside the debounce window would drop trailing keystrokes without this.
|
|
1261
|
+
useEffect(() => {
|
|
1262
|
+
const flushPendingDraftPersist = () => {
|
|
1263
|
+
const pending = pendingDraftPersistRef.current
|
|
1264
|
+
|
|
1265
|
+
if (!pending) {
|
|
1266
|
+
return
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
pendingDraftPersistRef.current = null
|
|
1270
|
+
stashAt(pending.scope, pending.text)
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
window.addEventListener('pagehide', flushPendingDraftPersist)
|
|
1274
|
+
|
|
1275
|
+
return () => {
|
|
1276
|
+
window.removeEventListener('pagehide', flushPendingDraftPersist)
|
|
1277
|
+
flushPendingDraftPersist()
|
|
1278
|
+
}
|
|
1279
|
+
}, [])
|
|
1280
|
+
|
|
739
1281
|
const beginQueuedEdit = (entry: QueuedPromptEntry) => {
|
|
740
1282
|
if (!activeQueueSessionKey || queueEdit) {
|
|
741
1283
|
return
|
|
@@ -752,6 +1294,42 @@ export function ChatBar({
|
|
|
752
1294
|
focusInput()
|
|
753
1295
|
}
|
|
754
1296
|
|
|
1297
|
+
// Walk queued entries while editing (ArrowUp = older, ArrowDown = newer),
|
|
1298
|
+
// saving the in-progress edit on each step. Stepping newer past the last
|
|
1299
|
+
// entry exits edit mode and restores the pre-edit draft.
|
|
1300
|
+
const stepQueuedEdit = (direction: -1 | 1) => {
|
|
1301
|
+
if (!queueEdit) {
|
|
1302
|
+
return false
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
const index = queuedPrompts.findIndex(e => e.id === queueEdit.entryId)
|
|
1306
|
+
const target = index + direction
|
|
1307
|
+
|
|
1308
|
+
if (index < 0 || target < 0) {
|
|
1309
|
+
return index >= 0 // at the oldest: swallow; missing entry: let it fall through
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
const saved = updateQueuedPrompt(queueEdit.sessionKey, queueEdit.entryId, {
|
|
1313
|
+
attachments: cloneAttachments($composerAttachments.get()),
|
|
1314
|
+
text: draftRef.current
|
|
1315
|
+
})
|
|
1316
|
+
|
|
1317
|
+
const next = queuedPrompts[target]
|
|
1318
|
+
|
|
1319
|
+
if (next) {
|
|
1320
|
+
setQueueEdit({ ...queueEdit, entryId: next.id })
|
|
1321
|
+
loadIntoComposer(next.text, next.attachments)
|
|
1322
|
+
} else {
|
|
1323
|
+
setQueueEdit(null)
|
|
1324
|
+
loadIntoComposer(queueEdit.draft, queueEdit.attachments)
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
triggerHaptic(saved ? 'success' : 'selection')
|
|
1328
|
+
focusInput()
|
|
1329
|
+
|
|
1330
|
+
return true
|
|
1331
|
+
}
|
|
1332
|
+
|
|
755
1333
|
const exitQueuedEdit = (action: 'cancel' | 'save'): boolean => {
|
|
756
1334
|
if (!queueEdit) {
|
|
757
1335
|
return false
|
|
@@ -794,6 +1372,26 @@ export function ChatBar({
|
|
|
794
1372
|
return true
|
|
795
1373
|
}, [activeQueueSessionKey, attachments, clearDraft, draft])
|
|
796
1374
|
|
|
1375
|
+
// Steer the live turn (nudge without interrupting). Clears the draft up front
|
|
1376
|
+
// for snappy feedback; if the gateway rejects (no live tool window) the words
|
|
1377
|
+
// are re-queued so nothing is lost — same safety net as a plain queue.
|
|
1378
|
+
const steerDraft = useCallback(() => {
|
|
1379
|
+
if (!onSteer || !canSteer) {
|
|
1380
|
+
return
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
const text = draftRef.current.trim()
|
|
1384
|
+
|
|
1385
|
+
triggerHaptic('submit')
|
|
1386
|
+
clearDraft()
|
|
1387
|
+
|
|
1388
|
+
void Promise.resolve(onSteer(text)).then(accepted => {
|
|
1389
|
+
if (!accepted && activeQueueSessionKey) {
|
|
1390
|
+
enqueueQueuedPrompt(activeQueueSessionKey, { text, attachments: [] })
|
|
1391
|
+
}
|
|
1392
|
+
})
|
|
1393
|
+
}, [activeQueueSessionKey, canSteer, clearDraft, onSteer])
|
|
1394
|
+
|
|
797
1395
|
// All queue drain paths share one lock + send-then-remove sequence.
|
|
798
1396
|
// `pickEntry` lets each caller choose head, by-id, or skip-edited.
|
|
799
1397
|
const runDrain = useCallback(
|
|
@@ -819,86 +1417,207 @@ export function ChatBar({
|
|
|
819
1417
|
return false
|
|
820
1418
|
}
|
|
821
1419
|
|
|
1420
|
+
drainFailuresRef.current.delete(entry.id)
|
|
822
1421
|
removeQueuedPrompt(activeQueueSessionKey, entry.id)
|
|
1422
|
+
resetBrowseState(sessionId)
|
|
823
1423
|
|
|
824
1424
|
return true
|
|
825
1425
|
} finally {
|
|
826
1426
|
drainingQueueRef.current = false
|
|
827
1427
|
}
|
|
828
1428
|
},
|
|
829
|
-
[activeQueueSessionKey, onSubmit, queuedPrompts]
|
|
1429
|
+
[activeQueueSessionKey, onSubmit, queuedPrompts, sessionId]
|
|
830
1430
|
)
|
|
831
1431
|
|
|
832
|
-
const
|
|
833
|
-
() =>
|
|
834
|
-
|
|
835
|
-
const skip = queueEdit?.entryId
|
|
1432
|
+
const pickDrainHead = useCallback(
|
|
1433
|
+
(entries: QueuedPromptEntry[]) => {
|
|
1434
|
+
const skip = queueEditRef.current?.entryId
|
|
836
1435
|
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
[
|
|
1436
|
+
return skip ? entries.find(e => e.id !== skip) : entries[0]
|
|
1437
|
+
},
|
|
1438
|
+
[] // reads the edit id off a ref so the lock-holder always sees the latest
|
|
840
1439
|
)
|
|
841
1440
|
|
|
1441
|
+
const drainNextQueued = useCallback(() => runDrain(pickDrainHead), [pickDrainHead, runDrain])
|
|
1442
|
+
|
|
842
1443
|
const sendQueuedNow = useCallback(
|
|
843
|
-
(id: string) =>
|
|
844
|
-
|
|
1444
|
+
(id: string) => {
|
|
1445
|
+
if (!activeQueueSessionKey || id === queueEdit?.entryId) {
|
|
1446
|
+
return false
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
if (busy) {
|
|
1450
|
+
// Promote to the head, then interrupt. The gateway always emits a
|
|
1451
|
+
// settle (message.complete + session.info running:false) when the
|
|
1452
|
+
// turn unwinds, and the busy→false auto-drain below sends this entry.
|
|
1453
|
+
promoteQueuedPrompt(activeQueueSessionKey, id)
|
|
1454
|
+
triggerHaptic('selection')
|
|
1455
|
+
void Promise.resolve(onCancel())
|
|
1456
|
+
|
|
1457
|
+
return true
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
// A manual send clears the auto-drain backoff so a stuck entry the user
|
|
1461
|
+
// taps gets a fresh attempt (and re-enables auto-retry on success).
|
|
1462
|
+
drainFailuresRef.current.delete(id)
|
|
1463
|
+
|
|
1464
|
+
return runDrain(entries => entries.find(e => e.id === id))
|
|
1465
|
+
},
|
|
1466
|
+
[activeQueueSessionKey, busy, onCancel, queueEdit, runDrain]
|
|
845
1467
|
)
|
|
846
1468
|
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
1469
|
+
// Edge-independent auto-drain: send the head whenever the session is idle and
|
|
1470
|
+
// the queue is non-empty, bounding retries so a thrown/rejected onSubmit (e.g.
|
|
1471
|
+
// a stale-session 404) can't strand the entry permanently nor spin-loop. The
|
|
1472
|
+
// drain lock serializes sends; a remount/reconnect resets the failure counts.
|
|
1473
|
+
const autoDrainNext = useCallback(() => {
|
|
1474
|
+
if (busy || drainingQueueRef.current || !activeQueueSessionKey) {
|
|
1475
|
+
return
|
|
850
1476
|
}
|
|
851
1477
|
|
|
852
|
-
|
|
1478
|
+
const entry = pickDrainHead(queuedPrompts)
|
|
853
1479
|
|
|
854
|
-
|
|
855
|
-
|
|
1480
|
+
if (!entry || (drainFailuresRef.current.get(entry.id) ?? 0) >= MAX_AUTO_DRAIN_ATTEMPTS) {
|
|
1481
|
+
return
|
|
1482
|
+
}
|
|
856
1483
|
|
|
857
|
-
|
|
1484
|
+
const onFail = () => {
|
|
1485
|
+
const fails = (drainFailuresRef.current.get(entry.id) ?? 0) + 1
|
|
1486
|
+
drainFailuresRef.current.set(entry.id, fails)
|
|
1487
|
+
|
|
1488
|
+
if (fails >= MAX_AUTO_DRAIN_ATTEMPTS) {
|
|
1489
|
+
notify({
|
|
1490
|
+
id: 'composer-queue-stuck',
|
|
1491
|
+
kind: 'error',
|
|
1492
|
+
title: t.composer.queueStuckTitle,
|
|
1493
|
+
message: t.composer.queueStuckBody
|
|
1494
|
+
})
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
void runDrain(() => entry)
|
|
1499
|
+
.then(sent => {
|
|
1500
|
+
if (!sent) {
|
|
1501
|
+
onFail()
|
|
1502
|
+
}
|
|
1503
|
+
})
|
|
1504
|
+
.catch(onFail)
|
|
1505
|
+
}, [activeQueueSessionKey, busy, pickDrainHead, queuedPrompts, runDrain, t])
|
|
1506
|
+
|
|
1507
|
+
// Re-key on a runtime session-id change. A stable stored id (queueSessionKey)
|
|
1508
|
+
// never churns, so a change there is a real session switch and must NOT
|
|
1509
|
+
// migrate; only the runtime-derived key (queueSessionKey falsy → key is
|
|
1510
|
+
// sessionId) churns on a backend bounce/resume of the same conversation.
|
|
858
1511
|
useEffect(() => {
|
|
859
|
-
const
|
|
860
|
-
|
|
1512
|
+
const prev = prevQueueKeyRef.current
|
|
1513
|
+
prevQueueKeyRef.current = activeQueueSessionKey
|
|
861
1514
|
|
|
862
|
-
if (
|
|
1515
|
+
if (queueSessionKey || !prev || !activeQueueSessionKey || prev === activeQueueSessionKey) {
|
|
863
1516
|
return
|
|
864
1517
|
}
|
|
865
1518
|
|
|
866
|
-
|
|
867
|
-
}, [
|
|
1519
|
+
migrateQueuedPrompts(prev, activeQueueSessionKey)
|
|
1520
|
+
}, [activeQueueSessionKey, queueSessionKey])
|
|
868
1521
|
|
|
869
|
-
//
|
|
1522
|
+
// Queued turns flow whenever the session is idle — on the busy→false settle
|
|
1523
|
+
// edge, on mount/reconnect, and after a re-key — so a swallowed edge can't
|
|
1524
|
+
// strand them. To cancel queued turns, the user deletes them from the panel.
|
|
1525
|
+
useEffect(() => {
|
|
1526
|
+
if (shouldAutoDrain({ isBusy: busy, queueLength: queuedPrompts.length })) {
|
|
1527
|
+
autoDrainNext()
|
|
1528
|
+
}
|
|
1529
|
+
}, [autoDrainNext, busy, queuedPrompts.length])
|
|
1530
|
+
|
|
1531
|
+
// Queue-edit cleanup: on session swap the scope effect already stashed the
|
|
1532
|
+
// edit snapshot; only restore into the composer when still on the same scope.
|
|
870
1533
|
useEffect(() => {
|
|
871
1534
|
if (!queueEdit) {
|
|
872
1535
|
return
|
|
873
1536
|
}
|
|
874
1537
|
|
|
875
|
-
if (queueEdit.sessionKey === activeQueueSessionKey
|
|
876
|
-
|
|
1538
|
+
if (queueEdit.sessionKey === activeQueueSessionKey) {
|
|
1539
|
+
if (editingQueuedPrompt) {
|
|
1540
|
+
return
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
loadIntoComposer(queueEdit.draft, queueEdit.attachments)
|
|
877
1544
|
}
|
|
878
1545
|
|
|
879
|
-
loadIntoComposer(queueEdit.draft, queueEdit.attachments)
|
|
880
1546
|
setQueueEdit(null)
|
|
881
1547
|
}, [activeQueueSessionKey, editingQueuedPrompt, queueEdit]) // eslint-disable-line react-hooks/exhaustive-deps
|
|
882
1548
|
|
|
1549
|
+
const dispatchSubmit = (text: string, attachments?: ComposerAttachment[]) => {
|
|
1550
|
+
const submittedScope = activeQueueSessionKeyRef.current
|
|
1551
|
+
const submittedAttachments = attachments ?? []
|
|
1552
|
+
|
|
1553
|
+
const restore = () => {
|
|
1554
|
+
loadIntoComposer(text, submittedAttachments)
|
|
1555
|
+
stashAt(activeQueueSessionKeyRef.current, text, submittedAttachments)
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
void Promise.resolve(attachments ? onSubmit(text, { attachments }) : onSubmit(text))
|
|
1559
|
+
.then(accepted => void (accepted === false ? restore() : clearSessionDraft(submittedScope)))
|
|
1560
|
+
.catch(restore)
|
|
1561
|
+
}
|
|
1562
|
+
|
|
883
1563
|
const submitDraft = () => {
|
|
1564
|
+
if (disabled) {
|
|
1565
|
+
return
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
// Source the text from the DOM editor, not React state. The AUI composer
|
|
1569
|
+
// state (`draft`) and the derived `hasComposerPayload` lag the DOM by a
|
|
1570
|
+
// render, so on fast typing or IME composition the final keystroke(s) may
|
|
1571
|
+
// not have synced yet — reading state here drops the message (Enter looks
|
|
1572
|
+
// like it does nothing; typing a trailing space only "fixes" it because the
|
|
1573
|
+
// extra input event forces a state sync). draftRef is updated on every
|
|
1574
|
+
// input event; refresh it from the editor once more to also cover an
|
|
1575
|
+
// in-flight keystroke that hasn't fired its input event yet.
|
|
1576
|
+
const editor = editorRef.current
|
|
1577
|
+
|
|
1578
|
+
if (editor) {
|
|
1579
|
+
const domText = composerPlainText(editor)
|
|
1580
|
+
|
|
1581
|
+
if (domText !== draftRef.current) {
|
|
1582
|
+
draftRef.current = domText
|
|
1583
|
+
aui.composer().setText(domText)
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
const text = draftRef.current
|
|
1588
|
+
const payloadPresent = text.trim().length > 0 || attachments.length > 0
|
|
1589
|
+
|
|
884
1590
|
if (queueEdit) {
|
|
885
1591
|
exitQueuedEdit('save')
|
|
886
1592
|
} else if (busy) {
|
|
887
|
-
|
|
1593
|
+
// Slash commands should execute immediately even while the agent is
|
|
1594
|
+
// busy — they're client-side operations (/yolo, /skin, /new, /help,
|
|
1595
|
+
// etc.) or self-contained gateway RPCs (/status, /compress). onSubmit
|
|
1596
|
+
// routes them to executeSlashCommand, which has its own per-command
|
|
1597
|
+
// busy guard for commands that genuinely need an idle session (skill
|
|
1598
|
+
// /send directives). Queuing them would make every slash command wait
|
|
1599
|
+
// for the current turn to finish, which is how the TUI never behaves.
|
|
1600
|
+
if (!attachments.length && SLASH_COMMAND_RE.test(text.trim())) {
|
|
1601
|
+
triggerHaptic('submit')
|
|
1602
|
+
clearDraft()
|
|
1603
|
+
dispatchSubmit(text)
|
|
1604
|
+
} else if (payloadPresent) {
|
|
888
1605
|
queueCurrentDraft()
|
|
889
|
-
} else if (queuedPrompts.length > 0) {
|
|
890
|
-
void interruptAndSendNextQueued()
|
|
891
1606
|
} else {
|
|
1607
|
+
// Stop button (the only way to reach here while busy with an empty
|
|
1608
|
+
// composer — empty Enter is short-circuited in the keydown handler).
|
|
892
1609
|
triggerHaptic('cancel')
|
|
893
1610
|
void Promise.resolve(onCancel())
|
|
894
1611
|
}
|
|
895
|
-
} else if (!
|
|
1612
|
+
} else if (!payloadPresent && queuedPrompts.length > 0) {
|
|
896
1613
|
void drainNextQueued()
|
|
897
|
-
} else if (
|
|
898
|
-
const
|
|
1614
|
+
} else if (payloadPresent) {
|
|
1615
|
+
const submittedAttachments = cloneAttachments(attachments)
|
|
899
1616
|
triggerHaptic('submit')
|
|
1617
|
+
resetBrowseState(sessionId)
|
|
900
1618
|
clearDraft()
|
|
901
|
-
|
|
1619
|
+
clearComposerAttachments()
|
|
1620
|
+
dispatchSubmit(text, submittedAttachments)
|
|
902
1621
|
}
|
|
903
1622
|
|
|
904
1623
|
focusInput()
|
|
@@ -965,6 +1684,7 @@ export function ChatBar({
|
|
|
965
1684
|
}
|
|
966
1685
|
|
|
967
1686
|
triggerHaptic('submit')
|
|
1687
|
+
resetBrowseState(sessionId)
|
|
968
1688
|
clearDraft()
|
|
969
1689
|
await onSubmit(text)
|
|
970
1690
|
}
|
|
@@ -998,6 +1718,7 @@ export function ChatBar({
|
|
|
998
1718
|
<ComposerControls
|
|
999
1719
|
busy={busy}
|
|
1000
1720
|
busyAction={busyAction}
|
|
1721
|
+
canSteer={canSteer}
|
|
1001
1722
|
canSubmit={canSubmit}
|
|
1002
1723
|
conversation={{
|
|
1003
1724
|
active: voiceConversationActive,
|
|
@@ -1015,6 +1736,7 @@ export function ChatBar({
|
|
|
1015
1736
|
disabled={disabled}
|
|
1016
1737
|
hasComposerPayload={hasComposerPayload}
|
|
1017
1738
|
onDictate={dictate}
|
|
1739
|
+
onSteer={steerDraft}
|
|
1018
1740
|
state={state}
|
|
1019
1741
|
voiceStatus={voiceStatus}
|
|
1020
1742
|
/>
|
|
@@ -1023,18 +1745,36 @@ export function ChatBar({
|
|
|
1023
1745
|
const input = (
|
|
1024
1746
|
<div className={cn('relative', stacked ? 'w-full' : 'min-w-(--composer-input-inline-min-width) flex-1')}>
|
|
1025
1747
|
<div
|
|
1026
|
-
aria-
|
|
1748
|
+
aria-disabled={inputDisabled ? true : undefined}
|
|
1749
|
+
aria-label={t.composer.message}
|
|
1750
|
+
autoCapitalize="off"
|
|
1751
|
+
autoCorrect="off"
|
|
1027
1752
|
className={cn(
|
|
1028
|
-
'min-h-(--composer-input-min-height) max-h-(--composer-input-max-height) overflow-y-auto bg-transparent pb-1 pr-1 pt-1 leading-normal text-foreground outline-none disabled:cursor-not-allowed',
|
|
1753
|
+
'min-h-(--composer-input-min-height) max-h-(--composer-input-max-height) overflow-y-auto whitespace-pre-wrap break-words [overflow-wrap:anywhere] bg-transparent pb-1 pr-1 pt-1 leading-normal text-foreground outline-none disabled:cursor-not-allowed',
|
|
1029
1754
|
'empty:before:content-[attr(data-placeholder)] empty:before:text-muted-foreground/60',
|
|
1030
1755
|
'**:data-ref-text:cursor-default',
|
|
1031
1756
|
stacked && 'pl-3',
|
|
1032
1757
|
stacked ? 'w-full' : 'min-w-(--composer-input-inline-min-width) flex-1'
|
|
1033
1758
|
)}
|
|
1034
|
-
contentEditable={!
|
|
1759
|
+
contentEditable={!inputDisabled}
|
|
1035
1760
|
data-placeholder={placeholder}
|
|
1036
1761
|
data-slot={RICH_INPUT_SLOT}
|
|
1037
1762
|
onBlur={() => window.setTimeout(closeTrigger, 80)}
|
|
1763
|
+
onCompositionEnd={event => {
|
|
1764
|
+
composingRef.current = false
|
|
1765
|
+
|
|
1766
|
+
// The input events fired *during* composition were skipped (they
|
|
1767
|
+
// carried uncommitted preedit text), and Chromium does NOT reliably
|
|
1768
|
+
// emit a trailing input event after compositionend on Windows IMEs.
|
|
1769
|
+
// Without flushing here, committed multi-character IME input (e.g.
|
|
1770
|
+
// Chinese "你好", Japanese, Korean) never reaches composer state, so
|
|
1771
|
+
// `hasComposerPayload` stays false and the send button stays hidden
|
|
1772
|
+
// until an unrelated edit forces a sync (#39614).
|
|
1773
|
+
flushEditorToDraft(event.currentTarget)
|
|
1774
|
+
}}
|
|
1775
|
+
onCompositionStart={() => {
|
|
1776
|
+
composingRef.current = true
|
|
1777
|
+
}}
|
|
1038
1778
|
onDragOver={handleInputDragOver}
|
|
1039
1779
|
onDrop={handleInputDrop}
|
|
1040
1780
|
onFocus={() => markActiveComposer('main')}
|
|
@@ -1045,6 +1785,7 @@ export function ChatBar({
|
|
|
1045
1785
|
onPaste={handlePaste}
|
|
1046
1786
|
ref={editorRef}
|
|
1047
1787
|
role="textbox"
|
|
1788
|
+
spellCheck={false}
|
|
1048
1789
|
suppressContentEditableWarning
|
|
1049
1790
|
/>
|
|
1050
1791
|
{/* assistant-ui requires ComposerPrimitive.Input somewhere in the tree
|
|
@@ -1062,8 +1803,16 @@ export function ChatBar({
|
|
|
1062
1803
|
|
|
1063
1804
|
`asChild` swaps TextareaAutosize for a Radix Slot wrapping our
|
|
1064
1805
|
plain <textarea>, which carries the binding but skips autosize. */}
|
|
1065
|
-
<ComposerPrimitive.Input asChild tabIndex={-1} unstable_focusOnScrollToBottom={false}>
|
|
1066
|
-
<textarea
|
|
1806
|
+
<ComposerPrimitive.Input asChild submitMode="ctrlEnter" tabIndex={-1} unstable_focusOnScrollToBottom={false}>
|
|
1807
|
+
<textarea
|
|
1808
|
+
aria-hidden
|
|
1809
|
+
autoCapitalize="off"
|
|
1810
|
+
autoComplete="off"
|
|
1811
|
+
autoCorrect="off"
|
|
1812
|
+
className="sr-only"
|
|
1813
|
+
spellCheck={false}
|
|
1814
|
+
tabIndex={-1}
|
|
1815
|
+
/>
|
|
1067
1816
|
</ComposerPrimitive.Input>
|
|
1068
1817
|
</div>
|
|
1069
1818
|
)
|
|
@@ -1075,6 +1824,7 @@ export function ChatBar({
|
|
|
1075
1824
|
className="group/composer absolute bottom-0 left-1/2 z-30 w-[min(var(--composer-width),calc(100%-2rem))] max-w-full -translate-x-1/2 rounded-2xl pt-2 pb-[var(--composer-shell-pad-block-end)]"
|
|
1076
1825
|
data-drag-active={dragActive ? '' : undefined}
|
|
1077
1826
|
data-slot="composer-root"
|
|
1827
|
+
data-status-stack={statusStackVisible ? '' : undefined}
|
|
1078
1828
|
data-thread-scrolled-up={scrolledUp ? '' : undefined}
|
|
1079
1829
|
onDragEnter={handleDragEnter}
|
|
1080
1830
|
onDragLeave={handleDragLeave}
|
|
@@ -1082,12 +1832,17 @@ export function ChatBar({
|
|
|
1082
1832
|
onDrop={handleDrop}
|
|
1083
1833
|
onSubmit={e => {
|
|
1084
1834
|
e.preventDefault()
|
|
1835
|
+
|
|
1836
|
+
if (composingRef.current) {
|
|
1837
|
+
return
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1085
1840
|
submitDraft()
|
|
1086
1841
|
}}
|
|
1087
1842
|
ref={composerRef}
|
|
1088
1843
|
>
|
|
1089
1844
|
{showHelpHint && <HelpHint />}
|
|
1090
|
-
{trigger && (
|
|
1845
|
+
{trigger && !argStageEmpty && (
|
|
1091
1846
|
<ComposerTriggerPopover
|
|
1092
1847
|
activeIndex={triggerActive}
|
|
1093
1848
|
items={triggerItems}
|
|
@@ -1097,23 +1852,30 @@ export function ChatBar({
|
|
|
1097
1852
|
onPick={replaceTriggerWithChip}
|
|
1098
1853
|
/>
|
|
1099
1854
|
)}
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
}
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1855
|
+
{/* Session-scoped status stack (todos, subagents, background tasks,
|
|
1856
|
+
queue). Out of flow so it never inflates the composer's measured
|
|
1857
|
+
height; it overlays the chat instead of pushing it, and publishes
|
|
1858
|
+
its own --status-stack-measured-height so the thread's clearance
|
|
1859
|
+
accounts for it. Collapses to nothing when every status is empty. */}
|
|
1860
|
+
<ComposerStatusStack
|
|
1861
|
+
queue={
|
|
1862
|
+
activeQueueSessionKey && queuedPrompts.length > 0 ? (
|
|
1863
|
+
<QueuePanel
|
|
1864
|
+
busy={busy}
|
|
1865
|
+
editingId={queueEdit?.entryId ?? null}
|
|
1866
|
+
entries={queuedPrompts}
|
|
1867
|
+
onDelete={id => {
|
|
1868
|
+
if (removeQueuedPrompt(activeQueueSessionKey, id) && queueEdit?.entryId === id) {
|
|
1869
|
+
exitQueuedEdit('cancel')
|
|
1870
|
+
}
|
|
1871
|
+
}}
|
|
1872
|
+
onEdit={beginQueuedEdit}
|
|
1873
|
+
onSendNow={id => void sendQueuedNow(id)}
|
|
1874
|
+
/>
|
|
1875
|
+
) : null
|
|
1876
|
+
}
|
|
1877
|
+
sessionId={statusSessionId}
|
|
1878
|
+
/>
|
|
1117
1879
|
<div
|
|
1118
1880
|
className="pointer-events-none absolute inset-0 rounded-[inherit]"
|
|
1119
1881
|
style={{ background: COMPOSER_FADE_BACKGROUND }}
|
|
@@ -1121,11 +1883,9 @@ export function ChatBar({
|
|
|
1121
1883
|
<div className="relative w-full rounded-[inherit]">
|
|
1122
1884
|
<div
|
|
1123
1885
|
className={cn(
|
|
1124
|
-
'relative z-4 isolate rounded-[inherit] border border-[color-mix(in_srgb,var(--dt-composer-ring)_calc(18%*var(--composer-ring-strength)),var(--dt-input))]
|
|
1886
|
+
'group/composer-surface relative z-4 isolate rounded-[inherit] border border-[color-mix(in_srgb,var(--dt-composer-ring)_calc(18%*var(--composer-ring-strength)),var(--dt-input))] transition-[border-color] duration-200 ease-out focus-within:border-[color-mix(in_srgb,var(--dt-composer-ring)_calc(45%*var(--composer-ring-strength)),transparent)]',
|
|
1125
1887
|
COMPOSER_DROP_FADE_CLASS,
|
|
1126
|
-
'group-focus-within/composer:border-[color-mix(in_srgb,var(--dt-composer-ring)_calc(45%*var(--composer-ring-strength)),transparent)] group-focus-within/composer:shadow-composer-focus',
|
|
1127
1888
|
'group-has-data-[state=open]/composer:border-t-transparent',
|
|
1128
|
-
'group-has-data-[state=open]/composer:shadow-[0_0.0625rem_0_0.0625rem_color-mix(in_srgb,var(--dt-composer-ring)_calc(35%*var(--composer-ring-strength)),transparent),0_0.5rem_1.5rem_color-mix(in_srgb,var(--shadow-ink)_6%,transparent)]',
|
|
1129
1889
|
dragActive && COMPOSER_DROP_ACTIVE_CLASS
|
|
1130
1890
|
)}
|
|
1131
1891
|
data-slot="composer-surface"
|
|
@@ -1135,20 +1895,14 @@ export function ChatBar({
|
|
|
1135
1895
|
aria-hidden
|
|
1136
1896
|
className={cn(
|
|
1137
1897
|
'pointer-events-none absolute inset-0 -z-10 rounded-[inherit]',
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
'[-webkit-backdrop-filter:blur(0.75rem)_saturate(1.12)]',
|
|
1141
|
-
'transition-[background-color] duration-150 ease-out',
|
|
1142
|
-
'group-data-[thread-scrolled-up]/composer:bg-[color-mix(in_srgb,var(--dt-card)_48%,transparent)]',
|
|
1143
|
-
'group-focus-within/composer:bg-[color-mix(in_srgb,var(--dt-card)_85%,transparent)]'
|
|
1898
|
+
composerFill,
|
|
1899
|
+
composerSurfaceGlass
|
|
1144
1900
|
)}
|
|
1145
1901
|
/>
|
|
1146
1902
|
<div
|
|
1147
1903
|
className={cn(
|
|
1148
1904
|
'relative z-1 flex min-h-0 w-full flex-col gap-(--composer-row-gap) overflow-hidden rounded-[inherit] px-(--composer-surface-pad-x) py-(--composer-surface-pad-y) transition-opacity duration-200 ease-out',
|
|
1149
|
-
scrolledUp
|
|
1150
|
-
? 'opacity-30 group-hover/composer:opacity-100 group-focus-within/composer:opacity-100'
|
|
1151
|
-
: 'opacity-100'
|
|
1905
|
+
scrolledUp ? 'opacity-30 group-hover/composer:opacity-100 group-focus-within/composer-surface:opacity-100' : 'opacity-100'
|
|
1152
1906
|
)}
|
|
1153
1907
|
data-slot="composer-fade"
|
|
1154
1908
|
>
|
|
@@ -1157,7 +1911,7 @@ export function ChatBar({
|
|
|
1157
1911
|
{queueEdit && editingQueuedPrompt && (
|
|
1158
1912
|
<div className="flex items-center justify-between gap-2 rounded-lg border border-[color-mix(in_srgb,var(--dt-composer-ring)_32%,transparent)] bg-accent/18 px-2 py-1">
|
|
1159
1913
|
<div className="min-w-0 text-[0.7rem] text-muted-foreground/88">
|
|
1160
|
-
|
|
1914
|
+
{t.composer.editingQueuedInComposer}
|
|
1161
1915
|
</div>
|
|
1162
1916
|
<div className="flex shrink-0 items-center gap-1">
|
|
1163
1917
|
<Button
|
|
@@ -1166,14 +1920,14 @@ export function ChatBar({
|
|
|
1166
1920
|
type="button"
|
|
1167
1921
|
variant="ghost"
|
|
1168
1922
|
>
|
|
1169
|
-
|
|
1923
|
+
{t.common.cancel}
|
|
1170
1924
|
</Button>
|
|
1171
1925
|
<Button
|
|
1172
1926
|
className="h-6 rounded-md px-2 text-[0.68rem]"
|
|
1173
1927
|
onClick={() => exitQueuedEdit('save')}
|
|
1174
1928
|
type="button"
|
|
1175
1929
|
>
|
|
1176
|
-
|
|
1930
|
+
{t.common.save}
|
|
1177
1931
|
</Button>
|
|
1178
1932
|
</div>
|
|
1179
1933
|
</div>
|
|
@@ -1184,7 +1938,7 @@ export function ChatBar({
|
|
|
1184
1938
|
'grid w-full',
|
|
1185
1939
|
stacked
|
|
1186
1940
|
? 'grid-cols-[auto_1fr] gap-(--composer-row-gap) [grid-template-areas:"input_input"_"menu_controls"]'
|
|
1187
|
-
: 'grid-cols-[auto_1fr_auto] items-
|
|
1941
|
+
: 'grid-cols-[auto_1fr_auto] items-center gap-(--composer-control-gap) [grid-template-areas:"menu_input_controls"]'
|
|
1188
1942
|
)}
|
|
1189
1943
|
>
|
|
1190
1944
|
<div className="flex items-center [grid-area:menu]">{contextMenu}</div>
|
|
@@ -1218,17 +1972,13 @@ export function ChatBarFallback() {
|
|
|
1218
1972
|
)}
|
|
1219
1973
|
data-slot="composer-root"
|
|
1220
1974
|
>
|
|
1221
|
-
<div className="composer-fallback-surface relative isolate h-(--composer-fallback-height) w-full rounded-[inherit] border border-[color-mix(in_srgb,var(--dt-composer-ring)_calc(18%*var(--composer-ring-strength)),var(--dt-input))]
|
|
1975
|
+
<div className="composer-fallback-surface relative isolate h-(--composer-fallback-height) w-full rounded-[inherit] border border-[color-mix(in_srgb,var(--dt-composer-ring)_calc(18%*var(--composer-ring-strength)),var(--dt-input))]">
|
|
1222
1976
|
<div
|
|
1223
1977
|
aria-hidden
|
|
1224
1978
|
className={cn(
|
|
1225
1979
|
'pointer-events-none absolute inset-0 -z-10 rounded-[inherit]',
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
'[-webkit-backdrop-filter:blur(0.75rem)_saturate(1.12)]',
|
|
1229
|
-
'transition-[background-color] duration-150 ease-out',
|
|
1230
|
-
'group-data-[thread-scrolled-up]/composer:bg-[color-mix(in_srgb,var(--dt-card)_48%,transparent)]',
|
|
1231
|
-
'group-focus-within/composer:bg-[color-mix(in_srgb,var(--dt-card)_85%,transparent)]'
|
|
1980
|
+
composerFill,
|
|
1981
|
+
composerSurfaceGlass
|
|
1232
1982
|
)}
|
|
1233
1983
|
/>
|
|
1234
1984
|
</div>
|