@clawpump/claw-agent 0.1.5 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +6188 -975
- package/agent/hermes_cli/win_pty_bridge.py +179 -0
- package/agent/hermes_cli/write_approval_commands.py +209 -0
- package/agent/hermes_constants.py +164 -33
- package/agent/hermes_logging.py +74 -2
- package/agent/hermes_state.py +919 -106
- package/agent/hermes_time.py +20 -0
- package/agent/locales/af.yaml +23 -0
- package/agent/locales/de.yaml +23 -0
- package/agent/locales/en.yaml +20 -0
- package/agent/locales/es.yaml +23 -0
- package/agent/locales/fr.yaml +23 -0
- package/agent/locales/ga.yaml +23 -0
- package/agent/locales/hu.yaml +23 -0
- package/agent/locales/it.yaml +23 -0
- package/agent/locales/ja.yaml +23 -0
- package/agent/locales/ko.yaml +23 -0
- package/agent/locales/pt.yaml +23 -0
- package/agent/locales/ru.yaml +23 -0
- package/agent/locales/tr.yaml +23 -0
- package/agent/locales/uk.yaml +23 -0
- package/agent/locales/zh-hant.yaml +23 -0
- package/agent/locales/zh.yaml +23 -0
- package/agent/model_tools.py +204 -40
- package/agent/optional-mcps/clawpump/manifest.yaml +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 +3 -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
|
@@ -14,15 +14,16 @@ import {
|
|
|
14
14
|
useSortable,
|
|
15
15
|
verticalListSortingStrategy
|
|
16
16
|
} from '@dnd-kit/sortable'
|
|
17
|
-
import { CSS } from '@dnd-kit/utilities'
|
|
18
17
|
import { useStore } from '@nanostores/react'
|
|
19
18
|
import type * as React from 'react'
|
|
20
|
-
import { useEffect, useMemo, useState } from 'react'
|
|
19
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
21
20
|
|
|
21
|
+
import { PlatformAvatar } from '@/app/messaging/platform-icon'
|
|
22
22
|
import { Button } from '@/components/ui/button'
|
|
23
23
|
import { Codicon } from '@/components/ui/codicon'
|
|
24
24
|
import { DisclosureCaret } from '@/components/ui/disclosure-caret'
|
|
25
25
|
import { KbdGroup } from '@/components/ui/kbd'
|
|
26
|
+
import { SearchField } from '@/components/ui/search-field'
|
|
26
27
|
import {
|
|
27
28
|
Sidebar,
|
|
28
29
|
SidebarContent,
|
|
@@ -33,24 +34,60 @@ import {
|
|
|
33
34
|
SidebarMenuItem
|
|
34
35
|
} from '@/components/ui/sidebar'
|
|
35
36
|
import { Skeleton } from '@/components/ui/skeleton'
|
|
37
|
+
import { Tip } from '@/components/ui/tooltip'
|
|
36
38
|
import { searchSessions, type SessionInfo, type SessionSearchResult } from '@/hermes'
|
|
39
|
+
import { useWorktreeInfo } from '@/hooks/use-worktree-info'
|
|
40
|
+
import { useI18n } from '@/i18n'
|
|
41
|
+
import { comboTokens } from '@/lib/keybinds/combo'
|
|
42
|
+
import { profileColor } from '@/lib/profile-color'
|
|
43
|
+
import { sessionMatchesSearch } from '@/lib/session-search'
|
|
44
|
+
import { normalizeSessionSource, sessionSourceLabel } from '@/lib/session-source'
|
|
37
45
|
import { cn } from '@/lib/utils'
|
|
46
|
+
import { $cronJobs } from '@/store/cron'
|
|
38
47
|
import {
|
|
48
|
+
$panesFlipped,
|
|
39
49
|
$pinnedSessionIds,
|
|
40
50
|
$sidebarAgentsGrouped,
|
|
51
|
+
$sidebarCronOpen,
|
|
52
|
+
$sidebarMessagingOpenIds,
|
|
41
53
|
$sidebarOpen,
|
|
54
|
+
$sidebarOverlayMounted,
|
|
42
55
|
$sidebarPinsOpen,
|
|
43
56
|
$sidebarRecentsOpen,
|
|
57
|
+
$sidebarSessionOrderIds,
|
|
58
|
+
$sidebarSessionOrderManual,
|
|
59
|
+
$sidebarWorkspaceOrderIds,
|
|
60
|
+
$sidebarWorkspaceParentOrderIds,
|
|
44
61
|
pinSession,
|
|
45
|
-
|
|
62
|
+
SESSION_SEARCH_FOCUS_EVENT,
|
|
63
|
+
setPinnedSessionOrder,
|
|
46
64
|
setSidebarAgentsGrouped,
|
|
65
|
+
setSidebarCronOpen,
|
|
47
66
|
setSidebarPinsOpen,
|
|
48
67
|
setSidebarRecentsOpen,
|
|
68
|
+
setSidebarSessionOrderIds,
|
|
69
|
+
setSidebarSessionOrderManual,
|
|
70
|
+
setSidebarWorkspaceOrderIds,
|
|
71
|
+
setSidebarWorkspaceParentOrderIds,
|
|
49
72
|
SIDEBAR_SESSIONS_PAGE_SIZE,
|
|
73
|
+
toggleSidebarMessagingOpen,
|
|
50
74
|
unpinSession
|
|
51
75
|
} from '@/store/layout'
|
|
52
76
|
import {
|
|
77
|
+
$newChatProfile,
|
|
78
|
+
$profiles,
|
|
79
|
+
$profileScope,
|
|
80
|
+
ALL_PROFILES,
|
|
81
|
+
newSessionInProfile,
|
|
82
|
+
normalizeProfileKey
|
|
83
|
+
} from '@/store/profile'
|
|
84
|
+
import {
|
|
85
|
+
$cronSessions,
|
|
86
|
+
$messagingPlatformTotals,
|
|
87
|
+
$messagingSessions,
|
|
88
|
+
$messagingTruncated,
|
|
53
89
|
$selectedStoredSessionId,
|
|
90
|
+
$sessionProfileTotals,
|
|
54
91
|
$sessions,
|
|
55
92
|
$sessionsLoading,
|
|
56
93
|
$sessionsTotal,
|
|
@@ -62,33 +99,103 @@ import { type AppView, ARTIFACTS_ROUTE, MESSAGING_ROUTE, SKILLS_ROUTE } from '..
|
|
|
62
99
|
import { SidebarPanelLabel } from '../../shell/sidebar-label'
|
|
63
100
|
import type { SidebarNavItem } from '../../types'
|
|
64
101
|
|
|
102
|
+
import { SidebarCronJobsSection } from './cron-jobs-section'
|
|
103
|
+
import { SidebarLoadMoreRow } from './load-more-row'
|
|
104
|
+
import { resolveManualSessionOrderIds } from './order'
|
|
105
|
+
import { ProfileRail } from './profile-switcher'
|
|
65
106
|
import { SidebarSessionRow } from './session-row'
|
|
66
107
|
import { VirtualSessionList } from './virtual-session-list'
|
|
108
|
+
import { type SidebarSessionGroup, type SidebarWorkspaceTree, workspaceTreeFor } from './workspace-groups'
|
|
67
109
|
|
|
68
110
|
const VIRTUALIZE_THRESHOLD = 25
|
|
69
111
|
|
|
112
|
+
// Non-session groups (messaging platforms) stay compact: show a few rows up
|
|
113
|
+
// front, reveal more in larger steps on demand. Keeps a busy platform from
|
|
114
|
+
// dominating the sidebar before the user asks to see it.
|
|
115
|
+
const NON_SESSION_INITIAL_ROWS = 3
|
|
116
|
+
const NON_SESSION_LOAD_STEP = 10
|
|
117
|
+
|
|
118
|
+
const NEW_SESSION_KBD = comboTokens('mod+n')
|
|
119
|
+
|
|
70
120
|
const SIDEBAR_NAV: SidebarNavItem[] = [
|
|
71
121
|
{
|
|
72
122
|
id: 'new-session',
|
|
73
|
-
label: '
|
|
123
|
+
label: '',
|
|
74
124
|
icon: props => <Codicon name="robot" {...props} />,
|
|
75
125
|
action: 'new-session'
|
|
76
126
|
},
|
|
77
127
|
{
|
|
78
128
|
id: 'skills',
|
|
79
|
-
label: '
|
|
129
|
+
label: '',
|
|
80
130
|
icon: props => <Codicon name="symbol-misc" {...props} />,
|
|
81
131
|
route: SKILLS_ROUTE
|
|
82
132
|
},
|
|
83
|
-
{ id: 'messaging', label: '
|
|
84
|
-
{ id: 'artifacts', label: '
|
|
133
|
+
{ id: 'messaging', label: '', icon: props => <Codicon name="comment" {...props} />, route: MESSAGING_ROUTE },
|
|
134
|
+
{ id: 'artifacts', label: '', icon: props => <Codicon name="files" {...props} />, route: ARTIFACTS_ROUTE }
|
|
85
135
|
]
|
|
86
136
|
|
|
87
137
|
const WORKSPACE_PAGE = 5
|
|
88
|
-
|
|
138
|
+
// ALL-profiles view: show only the latest N per profile up front to keep the
|
|
139
|
+
// unified list scannable, then reveal/fetch more in N-sized steps on demand.
|
|
140
|
+
const PROFILE_INITIAL_PAGE = 5
|
|
141
|
+
// Two modes via the `compact` height variant (styles.css):
|
|
142
|
+
// tall → each section is shrink-0, capped, its own scroller; Sessions is flex-1.
|
|
143
|
+
// compact → COMPACT_FLAT drops the caps so the whole stack scrolls as one.
|
|
144
|
+
// Sections stay shrink-0 so none can be squeezed below its content and bleed onto
|
|
145
|
+
// the next — the flexbox `min-height: auto` overlap trap that caused the bug.
|
|
146
|
+
const COMPACT_FLAT = 'compact:max-h-none compact:overflow-visible'
|
|
147
|
+
|
|
148
|
+
// Vertical scroll only — never a horizontal bar from glow bleed, long titles, etc.
|
|
149
|
+
const SCROLL_Y = 'overflow-y-auto overflow-x-hidden overscroll-contain'
|
|
150
|
+
|
|
151
|
+
// A non-session group's scroll body: own scroller when tall, flattened when compact.
|
|
152
|
+
const GROUP_BODY = cn(SCROLL_Y, COMPACT_FLAT)
|
|
153
|
+
|
|
154
|
+
// Sidebar reordering is a strictly vertical list. The dragged item's transform
|
|
155
|
+
// is rendered Y-only in useSortableBindings (no x, no scale); this just stops
|
|
156
|
+
// dnd-kit's auto-scroll from dragging the rail — or the window — sideways when
|
|
157
|
+
// the pointer nears an edge, killing the horizontal "drag to valhalla".
|
|
158
|
+
const reorderAutoScroll = { threshold: { x: 0, y: 0.2 } }
|
|
159
|
+
|
|
160
|
+
// One self-contained, nesting-safe reorderable list. It owns its DndContext, so a
|
|
161
|
+
// drag only ever collides with THIS list's own items — drop it at any depth (repos,
|
|
162
|
+
// worktrees, sessions) and reordering "just works" without leaking into the lists
|
|
163
|
+
// around or inside it. Pair each item with useSortableBindings(id); the list reports
|
|
164
|
+
// the new id order and the caller persists it. This is the single generic primitive
|
|
165
|
+
// behind every reorderable surface in the sidebar.
|
|
166
|
+
function ReorderableList({
|
|
167
|
+
children,
|
|
168
|
+
ids,
|
|
169
|
+
onReorder,
|
|
170
|
+
sensors
|
|
171
|
+
}: {
|
|
172
|
+
children: React.ReactNode
|
|
173
|
+
ids: string[]
|
|
174
|
+
onReorder: (ids: string[]) => void
|
|
175
|
+
sensors?: ReturnType<typeof useSensors>
|
|
176
|
+
}) {
|
|
177
|
+
const handleDragEnd = ({ active, over }: DragEndEvent) => {
|
|
178
|
+
if (!over || active.id === over.id) {
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const from = ids.indexOf(String(active.id))
|
|
183
|
+
const to = ids.indexOf(String(over.id))
|
|
184
|
+
|
|
185
|
+
if (from >= 0 && to >= 0) {
|
|
186
|
+
onReorder(arrayMove(ids, from, to))
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return (
|
|
191
|
+
<DndContext autoScroll={reorderAutoScroll} collisionDetection={closestCenter} onDragEnd={handleDragEnd} sensors={sensors}>
|
|
192
|
+
<SortableContext items={ids} strategy={verticalListSortingStrategy}>
|
|
193
|
+
{children}
|
|
194
|
+
</SortableContext>
|
|
195
|
+
</DndContext>
|
|
196
|
+
)
|
|
197
|
+
}
|
|
89
198
|
|
|
90
|
-
const wsId = (id: string) => `${WS_ID_PREFIX}${id}`
|
|
91
|
-
const parseWsId = (id: string) => (id.startsWith(WS_ID_PREFIX) ? id.slice(WS_ID_PREFIX.length) : null)
|
|
92
199
|
const countLabel = (loaded: number, total: number) => (total > loaded ? `${loaded}/${total}` : String(loaded))
|
|
93
200
|
const sessionTime = (s: SessionInfo) => s.last_active || s.started_at || 0
|
|
94
201
|
|
|
@@ -99,32 +206,51 @@ function orderByIds<T>(items: T[], getId: (item: T) => string, orderIds: string[
|
|
|
99
206
|
|
|
100
207
|
const byId = new Map(items.map(item => [getId(item), item]))
|
|
101
208
|
const seen = new Set<string>()
|
|
102
|
-
const
|
|
209
|
+
const ordered: T[] = []
|
|
103
210
|
|
|
104
211
|
for (const id of orderIds) {
|
|
105
212
|
const item = byId.get(id)
|
|
106
213
|
|
|
107
214
|
if (item) {
|
|
108
|
-
|
|
215
|
+
ordered.push(item)
|
|
109
216
|
seen.add(id)
|
|
110
217
|
}
|
|
111
218
|
}
|
|
112
219
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
220
|
+
// Items missing from the persisted order are new since it was last
|
|
221
|
+
// reconciled. Callers pass recency-sorted lists (newest first), so surface
|
|
222
|
+
// these at the TOP instead of burying them beneath the saved order —
|
|
223
|
+
// otherwise a brand-new session sinks to the bottom of the sidebar and reads
|
|
224
|
+
// as "my latest session never showed up".
|
|
225
|
+
const fresh = items.filter(item => !seen.has(getId(item)))
|
|
226
|
+
|
|
227
|
+
return fresh.length ? [...fresh, ...ordered] : ordered
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function reconcileOrderIds(currentIds: string[], orderIds: string[]): string[] {
|
|
231
|
+
if (!currentIds.length) {
|
|
232
|
+
return []
|
|
117
233
|
}
|
|
118
234
|
|
|
119
|
-
|
|
235
|
+
if (!orderIds.length) {
|
|
236
|
+
return currentIds
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const current = new Set(currentIds)
|
|
240
|
+
const retained = orderIds.filter(id => current.has(id))
|
|
241
|
+
const retainedSet = new Set(retained)
|
|
242
|
+
|
|
243
|
+
// New ids (absent from the saved order) are the newest sessions/groups; keep
|
|
244
|
+
// them ahead of the persisted order so fresh activity surfaces at the top of
|
|
245
|
+
// the sidebar rather than being appended to the bottom.
|
|
246
|
+
const fresh = currentIds.filter(id => !retainedSet.has(id))
|
|
247
|
+
|
|
248
|
+
return [...fresh, ...retained]
|
|
120
249
|
}
|
|
121
250
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
.split(/[/\\]/)
|
|
126
|
-
.filter(Boolean)
|
|
127
|
-
.pop()
|
|
251
|
+
function sameIds(left: string[], right: string[]) {
|
|
252
|
+
return left.length === right.length && left.every((item, index) => item === right[index])
|
|
253
|
+
}
|
|
128
254
|
|
|
129
255
|
// FTS results cover sessions that aren't in the loaded page; synthesize a
|
|
130
256
|
// minimal SessionInfo so they render in the same row component (resume works
|
|
@@ -137,6 +263,7 @@ function searchResultToSession(result: SessionSearchResult): SessionInfo {
|
|
|
137
263
|
cwd: null,
|
|
138
264
|
ended_at: null,
|
|
139
265
|
id: result.session_id,
|
|
266
|
+
_lineage_root_id: result.lineage_root ?? null,
|
|
140
267
|
input_tokens: 0,
|
|
141
268
|
is_active: false,
|
|
142
269
|
last_active: ts,
|
|
@@ -151,30 +278,6 @@ function searchResultToSession(result: SessionSearchResult): SessionInfo {
|
|
|
151
278
|
}
|
|
152
279
|
}
|
|
153
280
|
|
|
154
|
-
function workspaceGroupsFor(sessions: SessionInfo[]): SidebarSessionGroup[] {
|
|
155
|
-
const groups = new Map<string, SidebarSessionGroup>()
|
|
156
|
-
|
|
157
|
-
for (const session of sessions) {
|
|
158
|
-
const path = session.cwd?.trim() || ''
|
|
159
|
-
const id = path || '__no_workspace__'
|
|
160
|
-
const label = baseName(path) || path || 'No workspace'
|
|
161
|
-
|
|
162
|
-
const group = groups.get(id) ?? { id, label, path: path || null, sessions: [] }
|
|
163
|
-
group.sessions.push(session)
|
|
164
|
-
groups.set(id, group)
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Groups keep recency order (Map insertion = first-seen in the recency-sorted
|
|
168
|
-
// input, so an active project floats up), but rows *within* a group sort by
|
|
169
|
-
// creation time so they don't reshuffle every time a message lands — keeps
|
|
170
|
-
// muscle memory intact.
|
|
171
|
-
for (const group of groups.values()) {
|
|
172
|
-
group.sessions.sort((a, b) => b.started_at - a.started_at)
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return [...groups.values()]
|
|
176
|
-
}
|
|
177
|
-
|
|
178
281
|
function useSortableBindings(id: string) {
|
|
179
282
|
const { attributes, isDragging, listeners, setNodeRef, transform, transition } = useSortable({ id })
|
|
180
283
|
|
|
@@ -183,7 +286,14 @@ function useSortableBindings(id: string) {
|
|
|
183
286
|
dragHandleProps: { ...attributes, ...listeners },
|
|
184
287
|
ref: setNodeRef,
|
|
185
288
|
reorderable: true as const,
|
|
186
|
-
style: {
|
|
289
|
+
style: {
|
|
290
|
+
// Uniform vertical list: only ever translate on Y. Ignoring x and the
|
|
291
|
+
// scaleX/scaleY that CSS.Transform.toString would emit keeps a dragged
|
|
292
|
+
// group/row from drifting sideways or morphing its size mid-drag.
|
|
293
|
+
transform: transform ? `translate3d(0px, ${transform.y}px, 0)` : undefined,
|
|
294
|
+
transition: isDragging ? undefined : transition,
|
|
295
|
+
willChange: isDragging ? 'transform' : undefined
|
|
296
|
+
}
|
|
187
297
|
}
|
|
188
298
|
}
|
|
189
299
|
|
|
@@ -191,37 +301,104 @@ interface ChatSidebarProps extends React.ComponentProps<typeof Sidebar> {
|
|
|
191
301
|
currentView: AppView
|
|
192
302
|
onNavigate: (item: SidebarNavItem) => void
|
|
193
303
|
onLoadMoreSessions: () => void
|
|
304
|
+
onLoadMoreProfileSessions?: (profile: string) => Promise<void> | void
|
|
305
|
+
onLoadMoreMessaging?: (platform: string) => Promise<void> | void
|
|
194
306
|
onResumeSession: (sessionId: string) => void
|
|
195
307
|
onDeleteSession: (sessionId: string) => void
|
|
196
308
|
onArchiveSession: (sessionId: string) => void
|
|
197
309
|
onNewSessionInWorkspace: (path: null | string) => void
|
|
310
|
+
onManageCronJob: (jobId: string) => void
|
|
311
|
+
onTriggerCronJob: (jobId: string) => void
|
|
198
312
|
}
|
|
199
313
|
|
|
200
314
|
export function ChatSidebar({
|
|
201
315
|
currentView,
|
|
202
316
|
onNavigate,
|
|
203
317
|
onLoadMoreSessions,
|
|
318
|
+
onLoadMoreProfileSessions,
|
|
319
|
+
onLoadMoreMessaging,
|
|
204
320
|
onResumeSession,
|
|
205
321
|
onDeleteSession,
|
|
206
322
|
onArchiveSession,
|
|
207
|
-
onNewSessionInWorkspace
|
|
323
|
+
onNewSessionInWorkspace,
|
|
324
|
+
onManageCronJob,
|
|
325
|
+
onTriggerCronJob
|
|
208
326
|
}: ChatSidebarProps) {
|
|
327
|
+
const { t } = useI18n()
|
|
328
|
+
const s = t.sidebar
|
|
209
329
|
const sidebarOpen = useStore($sidebarOpen)
|
|
330
|
+
// Collapsed-but-overlay-mounted → render the full sidebar, not just the nav rail.
|
|
331
|
+
const overlayMounted = useStore($sidebarOverlayMounted)
|
|
332
|
+
const contentVisible = sidebarOpen || overlayMounted
|
|
333
|
+
const panesFlipped = useStore($panesFlipped)
|
|
210
334
|
const agentsGrouped = useStore($sidebarAgentsGrouped)
|
|
211
335
|
const pinnedSessionIds = useStore($pinnedSessionIds)
|
|
212
336
|
const pinsOpen = useStore($sidebarPinsOpen)
|
|
213
337
|
const agentsOpen = useStore($sidebarRecentsOpen)
|
|
338
|
+
const cronOpen = useStore($sidebarCronOpen)
|
|
214
339
|
const selectedSessionId = useStore($selectedStoredSessionId)
|
|
215
340
|
const sessions = useStore($sessions)
|
|
341
|
+
const cronSessions = useStore($cronSessions)
|
|
342
|
+
const cronJobs = useStore($cronJobs)
|
|
343
|
+
const messagingSessions = useStore($messagingSessions)
|
|
344
|
+
const messagingPlatformTotals = useStore($messagingPlatformTotals)
|
|
345
|
+
const messagingTruncated = useStore($messagingTruncated)
|
|
216
346
|
const sessionsLoading = useStore($sessionsLoading)
|
|
217
347
|
const sessionsTotal = useStore($sessionsTotal)
|
|
348
|
+
const sessionProfileTotals = useStore($sessionProfileTotals)
|
|
218
349
|
const workingSessionIds = useStore($workingSessionIds)
|
|
219
|
-
const
|
|
220
|
-
const
|
|
350
|
+
const profiles = useStore($profiles)
|
|
351
|
+
const profileScope = useStore($profileScope)
|
|
352
|
+
// Only surface the profile switcher when more than one profile exists, so
|
|
353
|
+
// single-profile users see the unchanged sidebar.
|
|
354
|
+
const multiProfile = profiles.length > 1
|
|
355
|
+
// Gate ALL-profiles grouping on multiProfile too: if a user drops back to one
|
|
356
|
+
// profile while scope is still ALL (persisted), the rail is hidden and they'd
|
|
357
|
+
// otherwise be stuck in the grouped view with no way out.
|
|
358
|
+
const showAllProfiles = multiProfile && profileScope === ALL_PROFILES
|
|
359
|
+
const agentOrderIds = useStore($sidebarSessionOrderIds)
|
|
360
|
+
const agentOrderManual = useStore($sidebarSessionOrderManual)
|
|
361
|
+
const workspaceOrderIds = useStore($sidebarWorkspaceOrderIds)
|
|
362
|
+
const workspaceParentOrderIds = useStore($sidebarWorkspaceParentOrderIds)
|
|
221
363
|
const [searchQuery, setSearchQuery] = useState('')
|
|
222
364
|
const [serverMatches, setServerMatches] = useState<SessionSearchResult[]>([])
|
|
365
|
+
const [newSessionKbdFlash, setNewSessionKbdFlash] = useState(false)
|
|
366
|
+
const [profileLoadMorePending, setProfileLoadMorePending] = useState<Record<string, boolean>>({})
|
|
367
|
+
const [messagingLoadMorePending, setMessagingLoadMorePending] = useState<Record<string, boolean>>({})
|
|
368
|
+
const messagingOpenIds = useStore($sidebarMessagingOpenIds)
|
|
369
|
+
// Per-platform count of rows currently revealed (starts at NON_SESSION_INITIAL_ROWS).
|
|
370
|
+
const [messagingVisible, setMessagingVisible] = useState<Record<string, number>>({})
|
|
371
|
+
const searchInputRef = useRef<HTMLInputElement>(null)
|
|
223
372
|
const trimmedQuery = searchQuery.trim()
|
|
224
373
|
|
|
374
|
+
// Hotkey (session.focusSearch) → focus the field once it's mounted.
|
|
375
|
+
useEffect(() => {
|
|
376
|
+
const onFocus = () => searchInputRef.current?.focus({ preventScroll: true })
|
|
377
|
+
|
|
378
|
+
window.addEventListener(SESSION_SEARCH_FOCUS_EVENT, onFocus)
|
|
379
|
+
|
|
380
|
+
return () => window.removeEventListener(SESSION_SEARCH_FOCUS_EVENT, onFocus)
|
|
381
|
+
}, [])
|
|
382
|
+
|
|
383
|
+
// Flash the ⌘N hint full-opacity (no transition) for the press, so hitting
|
|
384
|
+
// the shortcut visibly pings its affordance in the sidebar.
|
|
385
|
+
useEffect(() => {
|
|
386
|
+
let timeout: ReturnType<typeof setTimeout> | undefined
|
|
387
|
+
|
|
388
|
+
const onShortcut = () => {
|
|
389
|
+
setNewSessionKbdFlash(true)
|
|
390
|
+
clearTimeout(timeout)
|
|
391
|
+
timeout = setTimeout(() => setNewSessionKbdFlash(false), 140)
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
window.addEventListener('hermes:new-session-shortcut', onShortcut)
|
|
395
|
+
|
|
396
|
+
return () => {
|
|
397
|
+
window.removeEventListener('hermes:new-session-shortcut', onShortcut)
|
|
398
|
+
clearTimeout(timeout)
|
|
399
|
+
}
|
|
400
|
+
}, [])
|
|
401
|
+
|
|
225
402
|
const activeSidebarSessionId = currentView === 'chat' ? selectedSessionId : null
|
|
226
403
|
|
|
227
404
|
const dndSensors = useSensors(
|
|
@@ -229,7 +406,22 @@ export function ChatSidebar({
|
|
|
229
406
|
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
|
|
230
407
|
)
|
|
231
408
|
|
|
232
|
-
|
|
409
|
+
// Profile scope = the "workspace switcher" context. Concrete scope shows only
|
|
410
|
+
// that profile's sessions (clean rows, no per-row tags); ALL fans every
|
|
411
|
+
// profile in, grouped by profile below. Single-profile users land here with
|
|
412
|
+
// scope === their only profile, so nothing is filtered out.
|
|
413
|
+
const visibleSessions = useMemo(
|
|
414
|
+
() => (showAllProfiles ? sessions : sessions.filter(s => normalizeProfileKey(s.profile) === profileScope)),
|
|
415
|
+
[sessions, showAllProfiles, profileScope]
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
// Agent session order is pinned to creation time (started_at), NOT activity —
|
|
419
|
+
// a new message must never float a session to the top. Position only changes
|
|
420
|
+
// for a brand-new session or an explicit manual drag (agentOrderIds).
|
|
421
|
+
const sortedSessions = useMemo(
|
|
422
|
+
() => [...visibleSessions].sort((a, b) => (b.started_at || 0) - (a.started_at || 0)),
|
|
423
|
+
[visibleSessions]
|
|
424
|
+
)
|
|
233
425
|
|
|
234
426
|
const workingSessionIdSet = useMemo(() => new Set(workingSessionIds), [workingSessionIds])
|
|
235
427
|
|
|
@@ -238,7 +430,10 @@ export function ChatSidebar({
|
|
|
238
430
|
const sessionByAnyId = useMemo(() => {
|
|
239
431
|
const map = new Map<string, SessionInfo>()
|
|
240
432
|
|
|
241
|
-
|
|
433
|
+
// Cron sessions are listed separately but can still be pinned, so index
|
|
434
|
+
// them too — otherwise a pinned cron job can't resolve into the Pinned
|
|
435
|
+
// section. Recents take precedence on id collisions (set last).
|
|
436
|
+
for (const s of [...cronSessions, ...visibleSessions]) {
|
|
242
437
|
map.set(s.id, s)
|
|
243
438
|
|
|
244
439
|
if (s._lineage_root_id && !map.has(s._lineage_root_id)) {
|
|
@@ -247,7 +442,7 @@ export function ChatSidebar({
|
|
|
247
442
|
}
|
|
248
443
|
|
|
249
444
|
return map
|
|
250
|
-
}, [
|
|
445
|
+
}, [visibleSessions, cronSessions])
|
|
251
446
|
|
|
252
447
|
const pinnedSessions = useMemo(() => {
|
|
253
448
|
const seen = new Set<string>()
|
|
@@ -300,11 +495,10 @@ export function ChatSidebar({
|
|
|
300
495
|
return []
|
|
301
496
|
}
|
|
302
497
|
|
|
303
|
-
const needle = trimmedQuery.toLowerCase()
|
|
304
498
|
const out = new Map<string, SessionInfo>()
|
|
305
499
|
|
|
306
500
|
for (const s of sortedSessions) {
|
|
307
|
-
if (
|
|
501
|
+
if (sessionMatchesSearch(s, trimmedQuery)) {
|
|
308
502
|
out.set(s.id, s)
|
|
309
503
|
}
|
|
310
504
|
}
|
|
@@ -326,83 +520,289 @@ export function ChatSidebar({
|
|
|
326
520
|
[sortedSessions, pinnedRealIdSet]
|
|
327
521
|
)
|
|
328
522
|
|
|
523
|
+
useEffect(() => {
|
|
524
|
+
const next = resolveManualSessionOrderIds(
|
|
525
|
+
unpinnedAgentSessions.map(s => s.id),
|
|
526
|
+
agentOrderIds,
|
|
527
|
+
agentOrderManual
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
if (!next.length && agentOrderManual) {
|
|
531
|
+
setSidebarSessionOrderManual(false)
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
if (!next.length && agentOrderIds.length) {
|
|
535
|
+
setSidebarSessionOrderIds([])
|
|
536
|
+
return
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (next.length && !sameIds(next, agentOrderIds)) {
|
|
540
|
+
setSidebarSessionOrderIds(next)
|
|
541
|
+
}
|
|
542
|
+
}, [agentOrderIds, agentOrderManual, unpinnedAgentSessions])
|
|
543
|
+
|
|
329
544
|
const agentSessions = useMemo(
|
|
330
|
-
() => orderByIds(unpinnedAgentSessions, s => s.id, agentOrderIds),
|
|
331
|
-
[unpinnedAgentSessions, agentOrderIds]
|
|
545
|
+
() => (agentOrderManual ? orderByIds(unpinnedAgentSessions, s => s.id, agentOrderIds) : unpinnedAgentSessions),
|
|
546
|
+
[unpinnedAgentSessions, agentOrderIds, agentOrderManual]
|
|
332
547
|
)
|
|
333
548
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
549
|
+
// Recents are local-only: messaging-platform sessions are fetched as their
|
|
550
|
+
// own slice ($messagingSessions) and rendered in self-managed per-platform
|
|
551
|
+
// sections below, so there is no source-grouping magic to untangle here.
|
|
552
|
+
//
|
|
553
|
+
// Workspace grouping is a `parent (repo) → worktree → sessions` tree. Git
|
|
554
|
+
// metadata (probed locally) is authoritative; unresolved cwds fall back to a
|
|
555
|
+
// path-name heuristic inside workspaceTreeFor. Parents reorder via
|
|
556
|
+
// workspaceParentOrderIds; worktrees within a parent via workspaceOrderIds.
|
|
557
|
+
const worktreeGroupingActive = agentsGrouped && !showAllProfiles
|
|
558
|
+
const worktreeResolver = useWorktreeInfo(agentSessions, worktreeGroupingActive)
|
|
559
|
+
|
|
560
|
+
const agentTree = useMemo<SidebarWorkspaceTree[] | undefined>(() => {
|
|
561
|
+
if (!worktreeGroupingActive) {
|
|
562
|
+
return undefined
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
const tree = workspaceTreeFor(agentSessions, s.noWorkspace, worktreeResolver)
|
|
566
|
+
const orderedParents = orderByIds(tree, parent => parent.id, workspaceParentOrderIds)
|
|
567
|
+
|
|
568
|
+
return orderedParents.map(parent => ({
|
|
569
|
+
...parent,
|
|
570
|
+
groups: orderByIds(parent.groups, group => group.id, workspaceOrderIds)
|
|
571
|
+
}))
|
|
572
|
+
}, [worktreeGroupingActive, agentSessions, s.noWorkspace, worktreeResolver, workspaceParentOrderIds, workspaceOrderIds])
|
|
573
|
+
|
|
574
|
+
const loadMoreForProfileGroup = useCallback(
|
|
575
|
+
(profile: string) => {
|
|
576
|
+
if (!onLoadMoreProfileSessions) {
|
|
577
|
+
return
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
setProfileLoadMorePending(prev => ({ ...prev, [profile]: true }))
|
|
581
|
+
|
|
582
|
+
void Promise.resolve(onLoadMoreProfileSessions(profile))
|
|
583
|
+
.catch(() => undefined)
|
|
584
|
+
.finally(() => setProfileLoadMorePending(({ [profile]: _done, ...rest }) => rest))
|
|
585
|
+
},
|
|
586
|
+
[onLoadMoreProfileSessions]
|
|
337
587
|
)
|
|
338
588
|
|
|
339
|
-
const
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
589
|
+
const loadMoreForMessaging = useCallback(
|
|
590
|
+
(platform: string) => {
|
|
591
|
+
if (!onLoadMoreMessaging) {
|
|
592
|
+
return
|
|
593
|
+
}
|
|
344
594
|
|
|
345
|
-
|
|
346
|
-
if (!over || active.id === over.id) {
|
|
347
|
-
return
|
|
348
|
-
}
|
|
595
|
+
setMessagingLoadMorePending(prev => ({ ...prev, [platform]: true }))
|
|
349
596
|
|
|
350
|
-
|
|
597
|
+
void Promise.resolve(onLoadMoreMessaging(platform))
|
|
598
|
+
.catch(() => undefined)
|
|
599
|
+
.finally(() => setMessagingLoadMorePending(({ [platform]: _done, ...rest }) => rest))
|
|
600
|
+
},
|
|
601
|
+
[onLoadMoreMessaging]
|
|
602
|
+
)
|
|
351
603
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
604
|
+
// Reveal another batch of a platform's rows; fetch from the backend too if we
|
|
605
|
+
// run past what's loaded and more remain on disk.
|
|
606
|
+
const revealMoreMessaging = (platform: string, loaded: number, hasMore: boolean) => {
|
|
607
|
+
const next = (messagingVisible[platform] ?? NON_SESSION_INITIAL_ROWS) + NON_SESSION_LOAD_STEP
|
|
355
608
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
609
|
+
setMessagingVisible(prev => ({ ...prev, [platform]: next }))
|
|
610
|
+
|
|
611
|
+
if (next > loaded && hasMore) {
|
|
612
|
+
loadMoreForMessaging(platform)
|
|
613
|
+
}
|
|
360
614
|
}
|
|
361
615
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
616
|
+
// Each messaging platform is its own self-managed section: split the
|
|
617
|
+
// separately-fetched messaging slice by source, newest platform first, rows
|
|
618
|
+
// within a platform by recency. Per-platform totals (when a "load more" has
|
|
619
|
+
// resolved them) drive the count + whether more remain on disk.
|
|
620
|
+
const messagingGroups = useMemo<MessagingSection[]>(() => {
|
|
621
|
+
if (!messagingSessions.length) {
|
|
622
|
+
return []
|
|
365
623
|
}
|
|
366
624
|
|
|
367
|
-
const
|
|
368
|
-
const overId = String(over.id)
|
|
369
|
-
const activeWs = parseWsId(activeId)
|
|
370
|
-
const overWs = parseWsId(overId)
|
|
625
|
+
const bySource = new Map<string, SessionInfo[]>()
|
|
371
626
|
|
|
372
|
-
|
|
373
|
-
const
|
|
374
|
-
const newIdx = agentGroups.findIndex(g => g.id === overWs)
|
|
627
|
+
for (const session of messagingSessions) {
|
|
628
|
+
const sourceId = normalizeSessionSource(session.source)
|
|
375
629
|
|
|
376
|
-
if (
|
|
377
|
-
|
|
630
|
+
if (!sourceId) {
|
|
631
|
+
continue
|
|
378
632
|
}
|
|
379
633
|
|
|
380
|
-
|
|
634
|
+
const list = bySource.get(sourceId) ?? []
|
|
635
|
+
list.push(session)
|
|
636
|
+
bySource.set(sourceId, list)
|
|
637
|
+
}
|
|
381
638
|
|
|
382
|
-
|
|
639
|
+
return [...bySource.entries()]
|
|
640
|
+
.map(([sourceId, list]) => {
|
|
641
|
+
const ordered = [...list].sort((a, b) => sessionTime(b) - sessionTime(a))
|
|
642
|
+
const known = messagingPlatformTotals[sourceId]
|
|
643
|
+
const total = Math.max(ordered.length, known ?? 0)
|
|
644
|
+
|
|
645
|
+
return {
|
|
646
|
+
// Known exact total → more exist iff total exceeds loaded; otherwise
|
|
647
|
+
// the seed fetch was capped, so assume more until a per-platform load
|
|
648
|
+
// resolves the count.
|
|
649
|
+
hasMore: known != null ? known > ordered.length : messagingTruncated,
|
|
650
|
+
label: sessionSourceLabel(sourceId) ?? sourceId,
|
|
651
|
+
sessions: ordered,
|
|
652
|
+
sourceId,
|
|
653
|
+
total
|
|
654
|
+
}
|
|
655
|
+
})
|
|
656
|
+
.sort((a, b) => sessionTime(b.sessions[0]) - sessionTime(a.sessions[0]))
|
|
657
|
+
}, [messagingSessions, messagingPlatformTotals, messagingTruncated])
|
|
658
|
+
|
|
659
|
+
// ALL-profiles view: one collapsible group per profile, color on the header
|
|
660
|
+
// (not on every row). Default profile floats to the top, the rest alpha.
|
|
661
|
+
const profileGroups = useMemo<SidebarSessionGroup[] | undefined>(() => {
|
|
662
|
+
if (!showAllProfiles) {
|
|
663
|
+
return undefined
|
|
383
664
|
}
|
|
384
665
|
|
|
385
|
-
|
|
386
|
-
|
|
666
|
+
const groups = new Map<string, SidebarSessionGroup>()
|
|
667
|
+
|
|
668
|
+
for (const session of agentSessions) {
|
|
669
|
+
const key = normalizeProfileKey(session.profile)
|
|
670
|
+
|
|
671
|
+
const group = groups.get(key) ?? {
|
|
672
|
+
color: profileColor(key),
|
|
673
|
+
id: key,
|
|
674
|
+
label: key,
|
|
675
|
+
mode: 'profile',
|
|
676
|
+
path: null,
|
|
677
|
+
sessions: []
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
group.sessions.push(session)
|
|
681
|
+
|
|
682
|
+
groups.set(key, group)
|
|
387
683
|
}
|
|
388
684
|
|
|
389
|
-
|
|
390
|
-
|
|
685
|
+
return (
|
|
686
|
+
[...groups.values()]
|
|
687
|
+
.map(group => ({
|
|
688
|
+
...group,
|
|
689
|
+
loadingMore: Boolean(profileLoadMorePending[group.id]),
|
|
690
|
+
onLoadMore: onLoadMoreProfileSessions ? () => loadMoreForProfileGroup(group.id) : undefined,
|
|
691
|
+
totalCount: Math.max(group.sessions.length, sessionProfileTotals[group.id] ?? 0)
|
|
692
|
+
}))
|
|
693
|
+
// default (root) first, then the rest alphabetically.
|
|
694
|
+
.sort((a, b) => (a.id === 'default' ? -1 : b.id === 'default' ? 1 : a.label.localeCompare(b.label)))
|
|
695
|
+
)
|
|
696
|
+
}, [
|
|
697
|
+
showAllProfiles,
|
|
698
|
+
agentSessions,
|
|
699
|
+
loadMoreForProfileGroup,
|
|
700
|
+
onLoadMoreProfileSessions,
|
|
701
|
+
profileLoadMorePending,
|
|
702
|
+
sessionProfileTotals
|
|
703
|
+
])
|
|
704
|
+
|
|
705
|
+
const displayAgentSessions = agentSessions
|
|
706
|
+
|
|
707
|
+
// Pagination is scope-aware. In "All profiles" mode it tracks the global
|
|
708
|
+
// unified set. When scoped to one profile it must compare that profile's own
|
|
709
|
+
// loaded rows against that profile's total — otherwise a huge default profile
|
|
710
|
+
// keeps "Load more" stuck on while you browse a small one (the aggregator's
|
|
711
|
+
// total sums every profile). Per-profile totals come from the aggregator
|
|
712
|
+
// (children excluded); fall back to the global total / loaded count.
|
|
713
|
+
const loadedSessionCount = showAllProfiles ? sessions.length : visibleSessions.length
|
|
714
|
+
const scopedProfileTotal = showAllProfiles ? undefined : sessionProfileTotals[profileScope]
|
|
715
|
+
|
|
716
|
+
const knownSessionTotal = Math.max(
|
|
717
|
+
showAllProfiles ? sessionsTotal : (scopedProfileTotal ?? loadedSessionCount),
|
|
718
|
+
loadedSessionCount
|
|
719
|
+
)
|
|
720
|
+
|
|
721
|
+
const hasMoreSessions = knownSessionTotal > loadedSessionCount
|
|
722
|
+
const remainingSessionCount = Math.max(0, knownSessionTotal - loadedSessionCount)
|
|
723
|
+
|
|
724
|
+
const recentsMeta = countLabel(agentSessions.length, knownSessionTotal)
|
|
391
725
|
|
|
392
|
-
|
|
726
|
+
const displayAgentGroups = showAllProfiles ? profileGroups : undefined
|
|
727
|
+
|
|
728
|
+
// The recents list owns its own (virtualized) scroll container only when it's a
|
|
729
|
+
// long flat list. In that case it must keep its scroller even in short mode, so
|
|
730
|
+
// we don't flatten it (flattening would defeat virtualization). Short flat lists
|
|
731
|
+
// and grouped views (profile groups or the worktree tree) flatten into the
|
|
732
|
+
// single outer scroll instead.
|
|
733
|
+
const recentsVirtualizes =
|
|
734
|
+
!displayAgentGroups?.length && !agentTree?.length && displayAgentSessions.length >= VIRTUALIZE_THRESHOLD
|
|
735
|
+
|
|
736
|
+
// Keep the persisted parent + worktree orders reconciled with what's on screen:
|
|
737
|
+
// freshly-seen repos/worktrees surface at the top, vanished ones drop out of
|
|
738
|
+
// the saved order.
|
|
739
|
+
useEffect(() => {
|
|
740
|
+
if (!agentTree?.length) {
|
|
393
741
|
return
|
|
394
742
|
}
|
|
395
743
|
|
|
396
|
-
|
|
744
|
+
const nextParents = reconcileOrderIds(
|
|
745
|
+
agentTree.map(parent => parent.id),
|
|
746
|
+
workspaceParentOrderIds
|
|
747
|
+
)
|
|
748
|
+
|
|
749
|
+
if (!sameIds(nextParents, workspaceParentOrderIds)) {
|
|
750
|
+
setSidebarWorkspaceParentOrderIds(nextParents)
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
const nextWorktrees = reconcileOrderIds(
|
|
754
|
+
agentTree.flatMap(parent => parent.groups.map(group => group.id)),
|
|
755
|
+
workspaceOrderIds
|
|
756
|
+
)
|
|
757
|
+
|
|
758
|
+
if (!sameIds(nextWorktrees, workspaceOrderIds)) {
|
|
759
|
+
setSidebarWorkspaceOrderIds(nextWorktrees)
|
|
760
|
+
}
|
|
761
|
+
}, [agentTree, workspaceParentOrderIds, workspaceOrderIds])
|
|
762
|
+
|
|
763
|
+
const showSessionSkeletons = sessionsLoading && sortedSessions.length === 0
|
|
764
|
+
|
|
765
|
+
const showSessionSections = showSessionSkeletons || sortedSessions.length > 0
|
|
766
|
+
|
|
767
|
+
// Each reorderable list reports its OWN new id order; persisting is a direct,
|
|
768
|
+
// typed write — no id-prefix sniffing to figure out which level moved.
|
|
769
|
+
const reorderSessions = (ids: string[]) => {
|
|
770
|
+
setSidebarSessionOrderManual(true)
|
|
771
|
+
setSidebarSessionOrderIds(ids)
|
|
397
772
|
}
|
|
398
773
|
|
|
774
|
+
const reorderParents = (ids: string[]) => setSidebarWorkspaceParentOrderIds(ids)
|
|
775
|
+
|
|
776
|
+
// Worktrees persist as one flat list (orderByIds applies it per parent), so a
|
|
777
|
+
// single parent's new worktree order is spliced back over its slice.
|
|
778
|
+
const reorderWorktree = (parentId: string, ids: string[]) =>
|
|
779
|
+
setSidebarWorkspaceOrderIds(
|
|
780
|
+
(agentTree ?? []).flatMap(parent => (parent.id === parentId ? ids : parent.groups.map(group => group.id)))
|
|
781
|
+
)
|
|
782
|
+
|
|
783
|
+
// Sortable rows carry live session ids; the pinned store is keyed by durable
|
|
784
|
+
// (lineage-root) ids, so translate before persisting the new order.
|
|
785
|
+
const reorderPinned = (ids: string[]) =>
|
|
786
|
+
setPinnedSessionOrder(
|
|
787
|
+
ids.map(id => {
|
|
788
|
+
const session = sessionByAnyId.get(id)
|
|
789
|
+
|
|
790
|
+
return session ? sessionPinId(session) : id
|
|
791
|
+
})
|
|
792
|
+
)
|
|
793
|
+
|
|
399
794
|
return (
|
|
400
795
|
<Sidebar
|
|
401
796
|
className={cn(
|
|
402
|
-
'relative h-full min-w-0 overflow-hidden border-
|
|
797
|
+
'relative h-full min-w-0 overflow-hidden border-t-0 border-b-0 text-foreground transition-none',
|
|
798
|
+
panesFlipped ? 'border-l border-r-0' : 'border-r border-l-0',
|
|
403
799
|
sidebarOpen
|
|
404
800
|
? 'border-(--sidebar-edge-border) bg-(--ui-sidebar-surface-background) opacity-100'
|
|
405
|
-
: 'pointer-events-none border-transparent bg-transparent opacity-0'
|
|
801
|
+
: 'pointer-events-none border-transparent bg-transparent opacity-0',
|
|
802
|
+
// While floated by PaneShell's hover-reveal, force visible + interactive
|
|
803
|
+
// — on hover (group-hover/reveal) or when keyboard-pinned (data-forced).
|
|
804
|
+
'in-data-[pane-hover-reveal=open]:pointer-events-auto in-data-[pane-hover-reveal=open]:border-(--sidebar-edge-border) in-data-[pane-hover-reveal=open]:bg-(--ui-sidebar-surface-background) in-data-[pane-hover-reveal=open]:opacity-100',
|
|
805
|
+
'group-hover/reveal:pointer-events-auto group-hover/reveal:border-(--sidebar-edge-border) group-hover/reveal:bg-(--ui-sidebar-surface-background) group-hover/reveal:opacity-100'
|
|
406
806
|
)}
|
|
407
807
|
collapsible="none"
|
|
408
808
|
>
|
|
@@ -418,27 +818,50 @@ export function ChatSidebar({
|
|
|
418
818
|
(item.id === 'messaging' && currentView === 'messaging') ||
|
|
419
819
|
(item.id === 'artifacts' && currentView === 'artifacts')
|
|
420
820
|
|
|
821
|
+
const isNewSession = item.id === 'new-session'
|
|
822
|
+
|
|
421
823
|
return (
|
|
422
824
|
<SidebarMenuItem key={item.id}>
|
|
423
825
|
<SidebarMenuButton
|
|
424
826
|
aria-disabled={!isInteractive}
|
|
425
827
|
className={cn(
|
|
426
|
-
|
|
828
|
+
// no-drag: these rows sit directly under the titlebar's
|
|
829
|
+
// [-webkit-app-region:drag] strips (app-shell.tsx), with only
|
|
830
|
+
// 6px of clearance. Drag regions win hit-testing over DOM
|
|
831
|
+
// (pointer-events can't override), and on Linux/WSLg the
|
|
832
|
+
// resolved region has been observed to swallow clicks on the
|
|
833
|
+
// top rows. Same carve-out as USER_BUBBLE_BASE_CLASS in
|
|
834
|
+
// thread.tsx.
|
|
835
|
+
'flex h-7 w-full justify-start gap-2 rounded-md border border-transparent px-2 text-left text-[0.8125rem] font-medium text-(--ui-text-secondary) transition-colors duration-100 ease-out [-webkit-app-region:no-drag] hover:bg-(--ui-control-hover-background) hover:text-foreground hover:transition-none',
|
|
427
836
|
active &&
|
|
428
837
|
'border-(--ui-stroke-tertiary) bg-(--ui-control-active-background) text-foreground shadow-none hover:border-(--ui-stroke-tertiary)!',
|
|
429
838
|
!isInteractive &&
|
|
430
839
|
'cursor-default hover:border-transparent hover:bg-transparent hover:text-inherit'
|
|
431
840
|
)}
|
|
432
|
-
onClick={() =>
|
|
433
|
-
|
|
841
|
+
onClick={() => {
|
|
842
|
+
// A plain new session lands in whatever profile the live
|
|
843
|
+
// gateway is on (= the active switcher context). null →
|
|
844
|
+
// no swap. The switcher header is the single place to
|
|
845
|
+
// change which profile that is.
|
|
846
|
+
if (isNewSession) {
|
|
847
|
+
$newChatProfile.set(null)
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
onNavigate(item)
|
|
851
|
+
}}
|
|
852
|
+
tooltip={s.nav[item.id] ?? item.label}
|
|
434
853
|
type="button"
|
|
435
854
|
>
|
|
436
855
|
<item.icon className="size-4 shrink-0 text-[color-mix(in_srgb,currentColor_72%,transparent)]" />
|
|
437
|
-
{
|
|
856
|
+
{contentVisible && (
|
|
438
857
|
<>
|
|
439
|
-
<span className="min-w-0 flex-1 truncate
|
|
440
|
-
{
|
|
441
|
-
<KbdGroup
|
|
858
|
+
<span className="min-w-0 flex-1 truncate">{s.nav[item.id] ?? item.label}</span>
|
|
859
|
+
{isNewSession && (
|
|
860
|
+
<KbdGroup
|
|
861
|
+
className={cn('ml-auto opacity-55', newSessionKbdFlash && 'opacity-100!')}
|
|
862
|
+
keys={[...NEW_SESSION_KBD]}
|
|
863
|
+
size="sm"
|
|
864
|
+
/>
|
|
442
865
|
)}
|
|
443
866
|
</>
|
|
444
867
|
)}
|
|
@@ -450,130 +873,215 @@ export function ChatSidebar({
|
|
|
450
873
|
</SidebarGroupContent>
|
|
451
874
|
</SidebarGroup>
|
|
452
875
|
|
|
453
|
-
{
|
|
454
|
-
<div className="shrink-0 pb-1 pt-1">
|
|
455
|
-
<
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
type="text"
|
|
463
|
-
value={searchQuery}
|
|
464
|
-
/>
|
|
465
|
-
{searchQuery && (
|
|
466
|
-
<button
|
|
467
|
-
aria-label="Clear search"
|
|
468
|
-
className="grid size-4 shrink-0 cursor-pointer place-items-center rounded-sm text-(--ui-text-tertiary) hover:bg-(--ui-control-active-background) hover:text-foreground"
|
|
469
|
-
onClick={() => setSearchQuery('')}
|
|
470
|
-
type="button"
|
|
471
|
-
>
|
|
472
|
-
<Codicon name="close" size="0.75rem" />
|
|
473
|
-
</button>
|
|
474
|
-
)}
|
|
475
|
-
</div>
|
|
876
|
+
{contentVisible && showSessionSections && (
|
|
877
|
+
<div className="shrink-0 px-2 pb-1 pt-1">
|
|
878
|
+
<SearchField
|
|
879
|
+
aria-label={s.searchAria}
|
|
880
|
+
inputRef={searchInputRef}
|
|
881
|
+
onChange={setSearchQuery}
|
|
882
|
+
placeholder={s.searchPlaceholder}
|
|
883
|
+
value={searchQuery}
|
|
884
|
+
/>
|
|
476
885
|
</div>
|
|
477
886
|
)}
|
|
478
887
|
|
|
479
|
-
{
|
|
480
|
-
<
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
888
|
+
{contentVisible && showSessionSections && (
|
|
889
|
+
<div className={cn('flex min-h-0 flex-1 flex-col pb-1.75', SCROLL_Y)}>
|
|
890
|
+
{trimmedQuery && (
|
|
891
|
+
<SidebarSessionsSection
|
|
892
|
+
activeSessionId={activeSidebarSessionId}
|
|
893
|
+
contentClassName={cn('flex min-h-0 flex-1 flex-col gap-px pb-1.75', SCROLL_Y)}
|
|
894
|
+
emptyState={
|
|
895
|
+
<div className="grid min-h-24 place-items-center rounded-lg px-2 text-center text-xs text-(--ui-text-tertiary)">
|
|
896
|
+
{s.noMatch(trimmedQuery)}
|
|
897
|
+
</div>
|
|
898
|
+
}
|
|
899
|
+
label={s.results}
|
|
900
|
+
labelMeta={String(searchResults.length)}
|
|
901
|
+
onArchiveSession={onArchiveSession}
|
|
902
|
+
onDeleteSession={onDeleteSession}
|
|
903
|
+
onResumeSession={onResumeSession}
|
|
904
|
+
onToggle={() => undefined}
|
|
905
|
+
onTogglePin={pinSession}
|
|
906
|
+
open
|
|
907
|
+
pinned={false}
|
|
908
|
+
rootClassName="min-h-32 flex-1 overflow-hidden p-0"
|
|
909
|
+
sessions={searchResults}
|
|
910
|
+
workingSessionIdSet={workingSessionIdSet}
|
|
911
|
+
/>
|
|
912
|
+
)}
|
|
913
|
+
|
|
914
|
+
{!trimmedQuery && (
|
|
915
|
+
<SidebarSessionsSection
|
|
916
|
+
activeSessionId={activeSidebarSessionId}
|
|
917
|
+
contentClassName={cn('flex max-h-44 flex-col gap-px rounded-lg pb-2 pt-1', GROUP_BODY)}
|
|
918
|
+
dndSensors={dndSensors}
|
|
919
|
+
emptyState={<SidebarPinnedEmptyState />}
|
|
920
|
+
label={s.pinned}
|
|
921
|
+
onArchiveSession={onArchiveSession}
|
|
922
|
+
onDeleteSession={onDeleteSession}
|
|
923
|
+
onReorderSessions={reorderPinned}
|
|
924
|
+
onResumeSession={onResumeSession}
|
|
925
|
+
onToggle={() => setSidebarPinsOpen(!pinsOpen)}
|
|
926
|
+
onTogglePin={unpinSession}
|
|
927
|
+
open={pinsOpen}
|
|
928
|
+
pinned
|
|
929
|
+
rootClassName="shrink-0 p-0 pb-1"
|
|
930
|
+
sessions={pinnedSessions}
|
|
931
|
+
sortable={pinnedSessions.length > 1}
|
|
932
|
+
workingSessionIdSet={workingSessionIdSet}
|
|
933
|
+
/>
|
|
934
|
+
)}
|
|
935
|
+
|
|
936
|
+
{!trimmedQuery && (
|
|
937
|
+
<SidebarSessionsSection
|
|
938
|
+
activeSessionId={activeSidebarSessionId}
|
|
939
|
+
contentClassName={cn(
|
|
940
|
+
'flex min-h-0 flex-1 flex-col pb-1.75',
|
|
941
|
+
SCROLL_Y,
|
|
942
|
+
// Separate profile sections clearly in the ALL view; rows inside
|
|
943
|
+
// each group keep their own tight gap-px rhythm.
|
|
944
|
+
showAllProfiles ? 'gap-3' : 'gap-px',
|
|
945
|
+
// Flatten into the single scroll when compact — unless this is the
|
|
946
|
+
// virtualized long list, which must keep its own scroller.
|
|
947
|
+
!recentsVirtualizes && COMPACT_FLAT
|
|
948
|
+
)}
|
|
949
|
+
dndSensors={dndSensors}
|
|
950
|
+
emptyState={showSessionSkeletons ? <SidebarSessionSkeletons /> : <SidebarAllPinnedState />}
|
|
951
|
+
footer={
|
|
952
|
+
// Hide "load more" only when workspace-grouped (those groups page
|
|
953
|
+
// themselves). ALL-profiles now pages per-profile from each profile
|
|
954
|
+
// header; the global footer only applies to non-ALL views.
|
|
955
|
+
!showAllProfiles && !agentsGrouped && !showSessionSkeletons && hasMoreSessions ? (
|
|
956
|
+
<SidebarLoadMoreRow
|
|
957
|
+
loading={sessionsLoading}
|
|
958
|
+
onClick={onLoadMoreSessions}
|
|
959
|
+
step={Math.min(SIDEBAR_SESSIONS_PAGE_SIZE, remainingSessionCount)}
|
|
960
|
+
/>
|
|
961
|
+
) : null
|
|
962
|
+
}
|
|
963
|
+
forceEmptyState={showSessionSkeletons}
|
|
964
|
+
groups={displayAgentGroups}
|
|
965
|
+
headerAction={
|
|
966
|
+
// Always reserve the icon-xs (size-6) slot so the header keeps the
|
|
967
|
+
// same height whether or not the toggle renders — otherwise the
|
|
968
|
+
// "Sessions" label jumps when switching to the ALL-profiles view.
|
|
969
|
+
// Grouping operates on unpinned recents; if everything is pinned
|
|
970
|
+
// the toggle does nothing, and it's irrelevant in the ALL-profiles
|
|
971
|
+
// view (always grouped by profile), so hide the button (not the slot).
|
|
972
|
+
<div className="grid size-6 shrink-0 place-items-center">
|
|
973
|
+
{!showAllProfiles && agentSessions.length > 0 ? (
|
|
974
|
+
<Tip label={agentsGrouped ? s.groupTitleGrouped : s.groupTitleUngrouped}>
|
|
975
|
+
<Button
|
|
976
|
+
aria-label={agentsGrouped ? s.groupAriaGrouped : s.groupAriaUngrouped}
|
|
977
|
+
className={cn(
|
|
978
|
+
'text-(--ui-text-tertiary) opacity-70 hover:bg-(--ui-control-hover-background) hover:text-foreground hover:opacity-100 focus-visible:opacity-100',
|
|
979
|
+
agentsGrouped && 'bg-(--ui-control-active-background) text-foreground opacity-100'
|
|
980
|
+
)}
|
|
981
|
+
onClick={event => {
|
|
982
|
+
event.stopPropagation()
|
|
983
|
+
setSidebarRecentsOpen(true)
|
|
984
|
+
setSidebarAgentsGrouped(!agentsGrouped)
|
|
985
|
+
}}
|
|
986
|
+
size="icon-xs"
|
|
987
|
+
variant="ghost"
|
|
988
|
+
>
|
|
989
|
+
<Codicon name={agentsGrouped ? 'list-unordered' : 'root-folder'} size="0.75rem" />
|
|
990
|
+
</Button>
|
|
991
|
+
</Tip>
|
|
992
|
+
) : null}
|
|
993
|
+
</div>
|
|
994
|
+
}
|
|
995
|
+
label={s.sessions}
|
|
996
|
+
labelMeta={recentsMeta}
|
|
997
|
+
onArchiveSession={onArchiveSession}
|
|
998
|
+
onDeleteSession={onDeleteSession}
|
|
999
|
+
onNewSessionInWorkspace={showAllProfiles ? undefined : onNewSessionInWorkspace}
|
|
1000
|
+
onReorderParents={showAllProfiles ? undefined : reorderParents}
|
|
1001
|
+
onReorderSessions={showAllProfiles ? undefined : reorderSessions}
|
|
1002
|
+
onReorderWorktree={showAllProfiles ? undefined : reorderWorktree}
|
|
1003
|
+
onResumeSession={onResumeSession}
|
|
1004
|
+
onToggle={() => setSidebarRecentsOpen(!agentsOpen)}
|
|
1005
|
+
onTogglePin={pinSession}
|
|
1006
|
+
open={agentsOpen}
|
|
1007
|
+
pinned={false}
|
|
1008
|
+
rootClassName={cn(
|
|
1009
|
+
'min-h-32 flex-1 overflow-hidden p-0',
|
|
1010
|
+
!recentsVirtualizes && 'compact:min-h-0 compact:flex-none compact:overflow-visible'
|
|
1011
|
+
)}
|
|
1012
|
+
sessions={displayAgentSessions}
|
|
1013
|
+
sortable={!showAllProfiles && agentSessions.length > 1}
|
|
1014
|
+
tree={agentTree}
|
|
1015
|
+
workingSessionIdSet={workingSessionIdSet}
|
|
1016
|
+
/>
|
|
1017
|
+
)}
|
|
502
1018
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
1019
|
+
{!trimmedQuery &&
|
|
1020
|
+
messagingGroups.map(group => {
|
|
1021
|
+
const visible = messagingVisible[group.sourceId] ?? NON_SESSION_INITIAL_ROWS
|
|
1022
|
+
const shownSessions = group.sessions.slice(0, visible)
|
|
1023
|
+
// More to show if rows are hidden behind the cap, or the backend
|
|
1024
|
+
// still has older threads on disk.
|
|
1025
|
+
const canRevealMore = visible < group.sessions.length || group.hasMore
|
|
1026
|
+
|
|
1027
|
+
return (
|
|
1028
|
+
<SidebarSessionsSection
|
|
1029
|
+
activeSessionId={activeSidebarSessionId}
|
|
1030
|
+
contentClassName={cn('flex max-h-56 flex-col gap-px pb-1.75', GROUP_BODY)}
|
|
1031
|
+
emptyState={null}
|
|
1032
|
+
footer={
|
|
1033
|
+
canRevealMore ? (
|
|
1034
|
+
<SidebarLoadMoreRow
|
|
1035
|
+
loading={Boolean(messagingLoadMorePending[group.sourceId])}
|
|
1036
|
+
onClick={() => revealMoreMessaging(group.sourceId, group.sessions.length, group.hasMore)}
|
|
1037
|
+
step={Math.min(NON_SESSION_LOAD_STEP, Math.max(0, group.total - shownSessions.length))}
|
|
1038
|
+
/>
|
|
1039
|
+
) : null
|
|
1040
|
+
}
|
|
1041
|
+
key={group.sourceId}
|
|
1042
|
+
label={group.label}
|
|
1043
|
+
labelIcon={
|
|
1044
|
+
<PlatformAvatar
|
|
1045
|
+
className="size-4 rounded-[4px] text-[0.5625rem] [&_svg]:size-3"
|
|
1046
|
+
platformId={group.sourceId}
|
|
1047
|
+
platformName={group.label}
|
|
1048
|
+
/>
|
|
1049
|
+
}
|
|
1050
|
+
labelMeta={countLabel(group.sessions.length, group.total)}
|
|
1051
|
+
onArchiveSession={onArchiveSession}
|
|
1052
|
+
onDeleteSession={onDeleteSession}
|
|
1053
|
+
onResumeSession={onResumeSession}
|
|
1054
|
+
onToggle={() => toggleSidebarMessagingOpen(group.sourceId)}
|
|
1055
|
+
onTogglePin={pinSession}
|
|
1056
|
+
open={messagingOpenIds.includes(group.sourceId)}
|
|
1057
|
+
pinned={false}
|
|
1058
|
+
rootClassName="shrink-0 p-0"
|
|
1059
|
+
sessions={shownSessions}
|
|
1060
|
+
workingSessionIdSet={workingSessionIdSet}
|
|
1061
|
+
/>
|
|
1062
|
+
)
|
|
1063
|
+
})}
|
|
1064
|
+
|
|
1065
|
+
{!trimmedQuery && cronJobs.length > 0 && (
|
|
1066
|
+
<SidebarCronJobsSection
|
|
1067
|
+
jobs={cronJobs}
|
|
1068
|
+
label={s.cronJobs}
|
|
1069
|
+
onManageJob={onManageCronJob}
|
|
1070
|
+
onOpenRun={onResumeSession}
|
|
1071
|
+
onToggle={() => setSidebarCronOpen(!cronOpen)}
|
|
1072
|
+
onTriggerJob={onTriggerCronJob}
|
|
1073
|
+
open={cronOpen}
|
|
1074
|
+
/>
|
|
1075
|
+
)}
|
|
1076
|
+
</div>
|
|
523
1077
|
)}
|
|
524
1078
|
|
|
525
|
-
{
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
footer={
|
|
532
|
-
!agentsGrouped && !showSessionSkeletons && hasMoreSessions ? (
|
|
533
|
-
<SidebarLoadMoreRow
|
|
534
|
-
loading={sessionsLoading}
|
|
535
|
-
onClick={onLoadMoreSessions}
|
|
536
|
-
step={Math.min(SIDEBAR_SESSIONS_PAGE_SIZE, remainingSessionCount)}
|
|
537
|
-
/>
|
|
538
|
-
) : null
|
|
539
|
-
}
|
|
540
|
-
forceEmptyState={showSessionSkeletons}
|
|
541
|
-
groups={agentsGrouped ? agentGroups : undefined}
|
|
542
|
-
headerAction={
|
|
543
|
-
<Button
|
|
544
|
-
aria-label={agentsGrouped ? 'Show sessions as a single list' : 'Group sessions by workspace'}
|
|
545
|
-
className={cn(
|
|
546
|
-
'cursor-pointer text-(--ui-text-tertiary) opacity-70 hover:bg-(--ui-control-hover-background) hover:text-foreground hover:opacity-100 focus-visible:opacity-100',
|
|
547
|
-
agentsGrouped && 'bg-(--ui-control-active-background) text-foreground opacity-100'
|
|
548
|
-
)}
|
|
549
|
-
onClick={event => {
|
|
550
|
-
event.stopPropagation()
|
|
551
|
-
setSidebarRecentsOpen(true)
|
|
552
|
-
setSidebarAgentsGrouped(!agentsGrouped)
|
|
553
|
-
}}
|
|
554
|
-
size="icon-xs"
|
|
555
|
-
title={agentsGrouped ? 'Ungroup sessions' : 'Group by workspace'}
|
|
556
|
-
variant="ghost"
|
|
557
|
-
>
|
|
558
|
-
<Codicon name={agentsGrouped ? 'list-unordered' : 'root-folder'} size="0.75rem" />
|
|
559
|
-
</Button>
|
|
560
|
-
}
|
|
561
|
-
label="Sessions"
|
|
562
|
-
labelMeta={countLabel(agentSessions.length, knownSessionTotal)}
|
|
563
|
-
onArchiveSession={onArchiveSession}
|
|
564
|
-
onDeleteSession={onDeleteSession}
|
|
565
|
-
onNewSessionInWorkspace={onNewSessionInWorkspace}
|
|
566
|
-
onReorder={handleAgentDragEnd}
|
|
567
|
-
onResumeSession={onResumeSession}
|
|
568
|
-
onToggle={() => setSidebarRecentsOpen(!agentsOpen)}
|
|
569
|
-
onTogglePin={pinSession}
|
|
570
|
-
open={agentsOpen}
|
|
571
|
-
pinned={false}
|
|
572
|
-
rootClassName="min-h-0 flex-1 p-0"
|
|
573
|
-
sessions={agentSessions}
|
|
574
|
-
sortable={agentSessions.length > 1}
|
|
575
|
-
workingSessionIdSet={workingSessionIdSet}
|
|
576
|
-
/>
|
|
1079
|
+
{contentVisible && !showSessionSections && <div className="min-h-0 flex-1" />}
|
|
1080
|
+
|
|
1081
|
+
{contentVisible && (
|
|
1082
|
+
<div className="shrink-0 px-0.5 pb-1 pt-0.5">
|
|
1083
|
+
<ProfileRail />
|
|
1084
|
+
</div>
|
|
577
1085
|
)}
|
|
578
1086
|
</SidebarContent>
|
|
579
1087
|
</Sidebar>
|
|
@@ -586,16 +1094,18 @@ interface SidebarSectionHeaderProps {
|
|
|
586
1094
|
onToggle: () => void
|
|
587
1095
|
action?: React.ReactNode
|
|
588
1096
|
meta?: React.ReactNode
|
|
1097
|
+
icon?: React.ReactNode
|
|
589
1098
|
}
|
|
590
1099
|
|
|
591
|
-
function SidebarSectionHeader({ label, open, onToggle, action, meta }: SidebarSectionHeaderProps) {
|
|
1100
|
+
function SidebarSectionHeader({ label, open, onToggle, action, meta, icon }: SidebarSectionHeaderProps) {
|
|
592
1101
|
return (
|
|
593
1102
|
<div className="group/section flex shrink-0 items-center justify-between pb-1 pt-1.5">
|
|
594
1103
|
<button
|
|
595
|
-
className="group/section-label flex w-fit
|
|
1104
|
+
className="group/section-label flex w-fit items-center gap-1 bg-transparent text-left leading-none"
|
|
596
1105
|
onClick={onToggle}
|
|
597
1106
|
type="button"
|
|
598
1107
|
>
|
|
1108
|
+
{icon}
|
|
599
1109
|
<SidebarPanelLabel>{label}</SidebarPanelLabel>
|
|
600
1110
|
{meta && <SidebarCount>{meta}</SidebarCount>}
|
|
601
1111
|
<DisclosureCaret
|
|
@@ -621,28 +1131,35 @@ function SidebarSessionSkeletons() {
|
|
|
621
1131
|
)
|
|
622
1132
|
}
|
|
623
1133
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
)
|
|
1134
|
+
function SidebarAllPinnedState() {
|
|
1135
|
+
const { t } = useI18n()
|
|
1136
|
+
|
|
1137
|
+
return (
|
|
1138
|
+
<div className="grid min-h-24 place-items-center rounded-lg text-center text-xs text-(--ui-text-tertiary)">
|
|
1139
|
+
{t.sidebar.allPinned}
|
|
1140
|
+
</div>
|
|
1141
|
+
)
|
|
1142
|
+
}
|
|
629
1143
|
|
|
630
1144
|
function SidebarPinnedEmptyState() {
|
|
1145
|
+
const { t } = useI18n()
|
|
1146
|
+
|
|
631
1147
|
return (
|
|
632
1148
|
<div className="flex min-h-7 items-center gap-1.5 rounded-lg pl-2 text-[0.75rem] text-(--ui-text-tertiary)">
|
|
633
1149
|
<span className="grid w-3.5 shrink-0 place-items-center text-(--ui-text-quaternary)">
|
|
634
1150
|
<Codicon name="pin" size="0.75rem" />
|
|
635
1151
|
</span>
|
|
636
|
-
<span>
|
|
1152
|
+
<span>{t.sidebar.shiftClickHint}</span>
|
|
637
1153
|
</div>
|
|
638
1154
|
)
|
|
639
1155
|
}
|
|
640
1156
|
|
|
641
|
-
interface
|
|
642
|
-
|
|
1157
|
+
interface MessagingSection {
|
|
1158
|
+
sourceId: string
|
|
643
1159
|
label: string
|
|
644
|
-
path: null | string
|
|
645
1160
|
sessions: SessionInfo[]
|
|
1161
|
+
total: number
|
|
1162
|
+
hasMore: boolean
|
|
646
1163
|
}
|
|
647
1164
|
|
|
648
1165
|
interface SidebarSessionsSectionProps {
|
|
@@ -665,9 +1182,16 @@ interface SidebarSessionsSectionProps {
|
|
|
665
1182
|
headerAction?: React.ReactNode
|
|
666
1183
|
footer?: React.ReactNode
|
|
667
1184
|
groups?: SidebarSessionGroup[]
|
|
1185
|
+
tree?: SidebarWorkspaceTree[]
|
|
668
1186
|
labelMeta?: React.ReactNode
|
|
1187
|
+
labelIcon?: React.ReactNode
|
|
669
1188
|
sortable?: boolean
|
|
670
|
-
|
|
1189
|
+
// Per-level reorder callbacks. Each is optional; a list is draggable iff its
|
|
1190
|
+
// callback is supplied. The flat session list, the repo parents, and a parent's
|
|
1191
|
+
// worktrees each own an independent ReorderableList, so nothing collides.
|
|
1192
|
+
onReorderSessions?: (ids: string[]) => void
|
|
1193
|
+
onReorderParents?: (ids: string[]) => void
|
|
1194
|
+
onReorderWorktree?: (parentId: string, ids: string[]) => void
|
|
671
1195
|
dndSensors?: ReturnType<typeof useSensors>
|
|
672
1196
|
}
|
|
673
1197
|
|
|
@@ -691,15 +1215,23 @@ function SidebarSessionsSection({
|
|
|
691
1215
|
headerAction,
|
|
692
1216
|
footer,
|
|
693
1217
|
groups,
|
|
1218
|
+
tree,
|
|
694
1219
|
labelMeta,
|
|
1220
|
+
labelIcon,
|
|
695
1221
|
sortable = false,
|
|
696
|
-
|
|
1222
|
+
onReorderSessions,
|
|
1223
|
+
onReorderParents,
|
|
1224
|
+
onReorderWorktree,
|
|
697
1225
|
dndSensors
|
|
698
1226
|
}: SidebarSessionsSectionProps) {
|
|
699
|
-
const
|
|
700
|
-
const
|
|
701
|
-
|
|
702
|
-
|
|
1227
|
+
const hasTreeSessions = Boolean(tree?.some(parent => parent.sessionCount > 0))
|
|
1228
|
+
const hasGroupedSessions = Boolean(groups?.some(group => group.sessions.length > 0))
|
|
1229
|
+
const showEmptyState = forceEmptyState || (!hasGroupedSessions && !hasTreeSessions && sessions.length === 0)
|
|
1230
|
+
// The flat recents/pinned list is the only place sessions reorder by hand;
|
|
1231
|
+
// grouped/tree views always sort by creation date and never drag.
|
|
1232
|
+
const sessionsDraggable = sortable && !!onReorderSessions
|
|
1233
|
+
|
|
1234
|
+
const renderRow = (session: SessionInfo, draggable: boolean) => {
|
|
703
1235
|
const rowProps = {
|
|
704
1236
|
isPinned: pinned,
|
|
705
1237
|
isSelected: session.id === activeSessionId,
|
|
@@ -711,82 +1243,89 @@ function SidebarSessionsSection({
|
|
|
711
1243
|
session
|
|
712
1244
|
}
|
|
713
1245
|
|
|
714
|
-
return
|
|
1246
|
+
return draggable ? (
|
|
715
1247
|
<SortableSidebarSessionRow key={session.id} {...rowProps} />
|
|
716
1248
|
) : (
|
|
717
1249
|
<SidebarSessionRow key={session.id} {...rowProps} />
|
|
718
1250
|
)
|
|
719
1251
|
}
|
|
720
1252
|
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
const renderSessionList = (items: SessionInfo[]) =>
|
|
724
|
-
dndActive ? (
|
|
725
|
-
<SortableContext items={items.map(s => s.id)} strategy={verticalListSortingStrategy}>
|
|
726
|
-
{renderRows(items)}
|
|
727
|
-
</SortableContext>
|
|
728
|
-
) : (
|
|
729
|
-
renderRows(items)
|
|
730
|
-
)
|
|
1253
|
+
// Sessions inside repos/worktrees are date-ordered and static.
|
|
1254
|
+
const renderRows = (items: SessionInfo[]) => items.map(session => renderRow(session, false))
|
|
731
1255
|
|
|
732
|
-
const flatVirtualized =
|
|
1256
|
+
const flatVirtualized =
|
|
1257
|
+
!showEmptyState && !groups?.length && !tree?.length && sessions.length >= VIRTUALIZE_THRESHOLD
|
|
733
1258
|
|
|
734
1259
|
let inner: React.ReactNode
|
|
735
1260
|
|
|
736
1261
|
if (showEmptyState) {
|
|
737
1262
|
inner = emptyState
|
|
738
|
-
} else if (
|
|
739
|
-
const
|
|
740
|
-
|
|
741
|
-
<
|
|
742
|
-
|
|
743
|
-
key={
|
|
1263
|
+
} else if (tree?.length) {
|
|
1264
|
+
const parentNodes = tree.map(parent =>
|
|
1265
|
+
onReorderParents ? (
|
|
1266
|
+
<SortableSidebarWorkspaceParent
|
|
1267
|
+
dndSensors={dndSensors}
|
|
1268
|
+
key={parent.id}
|
|
744
1269
|
onNewSession={onNewSessionInWorkspace}
|
|
745
|
-
|
|
1270
|
+
onReorderWorktree={onReorderWorktree}
|
|
1271
|
+
parent={parent}
|
|
1272
|
+
renderRows={renderRows}
|
|
746
1273
|
/>
|
|
747
1274
|
) : (
|
|
748
|
-
<
|
|
749
|
-
|
|
750
|
-
key={group.id}
|
|
1275
|
+
<SidebarWorkspaceParent
|
|
1276
|
+
key={parent.id}
|
|
751
1277
|
onNewSession={onNewSessionInWorkspace}
|
|
752
|
-
|
|
1278
|
+
parent={parent}
|
|
1279
|
+
renderRows={renderRows}
|
|
753
1280
|
/>
|
|
754
1281
|
)
|
|
755
1282
|
)
|
|
756
1283
|
|
|
757
|
-
inner =
|
|
758
|
-
<
|
|
759
|
-
{
|
|
760
|
-
</
|
|
1284
|
+
inner = onReorderParents ? (
|
|
1285
|
+
<ReorderableList ids={tree.map(parent => parent.id)} onReorder={onReorderParents} sensors={dndSensors}>
|
|
1286
|
+
{parentNodes}
|
|
1287
|
+
</ReorderableList>
|
|
761
1288
|
) : (
|
|
762
|
-
|
|
1289
|
+
parentNodes
|
|
763
1290
|
)
|
|
1291
|
+
} else if (groups?.length) {
|
|
1292
|
+
// Profile/source groups never reorder; render them flat with static rows.
|
|
1293
|
+
inner = groups.map(group => (
|
|
1294
|
+
<SidebarWorkspaceGroup group={group} key={group.id} onNewSession={onNewSessionInWorkspace} renderRows={renderRows} />
|
|
1295
|
+
))
|
|
764
1296
|
} else if (flatVirtualized) {
|
|
765
|
-
|
|
1297
|
+
const virtual = (
|
|
766
1298
|
<VirtualSessionList
|
|
767
1299
|
activeSessionId={activeSessionId}
|
|
1300
|
+
className={contentClassName}
|
|
768
1301
|
onArchiveSession={onArchiveSession}
|
|
769
1302
|
onDeleteSession={onDeleteSession}
|
|
770
1303
|
onResumeSession={onResumeSession}
|
|
771
1304
|
onTogglePin={onTogglePin}
|
|
772
1305
|
pinned={pinned}
|
|
773
1306
|
sessions={sessions}
|
|
774
|
-
sortable={
|
|
1307
|
+
sortable={sessionsDraggable}
|
|
775
1308
|
workingSessionIdSet={workingSessionIdSet}
|
|
776
1309
|
/>
|
|
777
1310
|
)
|
|
778
|
-
} else {
|
|
779
|
-
inner = renderSessionList(sessions)
|
|
780
|
-
}
|
|
781
1311
|
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
1312
|
+
inner =
|
|
1313
|
+
sessionsDraggable && onReorderSessions ? (
|
|
1314
|
+
<ReorderableList ids={sessions.map(s => s.id)} onReorder={onReorderSessions} sensors={dndSensors}>
|
|
1315
|
+
{virtual}
|
|
1316
|
+
</ReorderableList>
|
|
1317
|
+
) : (
|
|
1318
|
+
virtual
|
|
1319
|
+
)
|
|
1320
|
+
} else if (sessionsDraggable && onReorderSessions) {
|
|
1321
|
+
inner = (
|
|
1322
|
+
<ReorderableList ids={sessions.map(s => s.id)} onReorder={onReorderSessions} sensors={dndSensors}>
|
|
1323
|
+
{sessions.map(session => renderRow(session, true))}
|
|
1324
|
+
</ReorderableList>
|
|
789
1325
|
)
|
|
1326
|
+
} else {
|
|
1327
|
+
inner = renderRows(sessions)
|
|
1328
|
+
}
|
|
790
1329
|
|
|
791
1330
|
// The virtualizer owns its own scroller, so suppress the wrapper's overflow
|
|
792
1331
|
// to avoid a double scroll container.
|
|
@@ -794,10 +1333,17 @@ function SidebarSessionsSection({
|
|
|
794
1333
|
|
|
795
1334
|
return (
|
|
796
1335
|
<SidebarGroup className={rootClassName}>
|
|
797
|
-
<SidebarSectionHeader
|
|
1336
|
+
<SidebarSectionHeader
|
|
1337
|
+
action={headerAction}
|
|
1338
|
+
icon={labelIcon}
|
|
1339
|
+
label={label}
|
|
1340
|
+
meta={labelMeta}
|
|
1341
|
+
onToggle={onToggle}
|
|
1342
|
+
open={open}
|
|
1343
|
+
/>
|
|
798
1344
|
{open && (
|
|
799
1345
|
<SidebarGroupContent className={resolvedContentClassName}>
|
|
800
|
-
{
|
|
1346
|
+
{inner}
|
|
801
1347
|
{footer}
|
|
802
1348
|
</SidebarGroupContent>
|
|
803
1349
|
)}
|
|
@@ -826,71 +1372,104 @@ function SidebarWorkspaceGroup({
|
|
|
826
1372
|
ref,
|
|
827
1373
|
...rest
|
|
828
1374
|
}: SidebarWorkspaceGroupProps) {
|
|
1375
|
+
const { t } = useI18n()
|
|
1376
|
+
const s = t.sidebar
|
|
1377
|
+
const isProfileGroup = group.mode === 'profile'
|
|
1378
|
+
const isSourceGroup = group.mode === 'source'
|
|
1379
|
+
const pageStep = isProfileGroup ? PROFILE_INITIAL_PAGE : WORKSPACE_PAGE
|
|
829
1380
|
const [open, setOpen] = useState(true)
|
|
830
|
-
const [visibleCount, setVisibleCount] = useState(
|
|
1381
|
+
const [visibleCount, setVisibleCount] = useState(pageStep)
|
|
1382
|
+
|
|
1383
|
+
const loadedCount = group.sessions.length
|
|
1384
|
+
// Profile groups know their on-disk total (children excluded); workspace
|
|
1385
|
+
// groups only ever page within what's already loaded.
|
|
1386
|
+
const totalCount = isProfileGroup ? Math.max(group.totalCount ?? loadedCount, loadedCount) : loadedCount
|
|
831
1387
|
const visibleSessions = group.sessions.slice(0, visibleCount)
|
|
832
|
-
const hiddenCount = Math.max(0,
|
|
833
|
-
const nextCount = Math.min(
|
|
1388
|
+
const hiddenCount = Math.max(0, totalCount - visibleSessions.length)
|
|
1389
|
+
const nextCount = Math.min(pageStep, hiddenCount)
|
|
1390
|
+
|
|
1391
|
+
// Leading glyph: profile color dot, platform avatar, or a branch mark for a
|
|
1392
|
+
// worktree. When reorderable it doubles as the drag handle (icon ↔ grabber).
|
|
1393
|
+
const leadingIcon = group.color ? (
|
|
1394
|
+
<span aria-hidden="true" className="size-2 shrink-0 rounded-full" style={{ backgroundColor: group.color }} />
|
|
1395
|
+
) : isSourceGroup && group.sourceId ? (
|
|
1396
|
+
<PlatformAvatar
|
|
1397
|
+
className="size-4 rounded-[4px] text-[0.5625rem] [&_svg]:size-3"
|
|
1398
|
+
platformId={group.sourceId}
|
|
1399
|
+
platformName={group.label}
|
|
1400
|
+
/>
|
|
1401
|
+
) : (
|
|
1402
|
+
<Codicon className="shrink-0 text-(--ui-text-tertiary)" name="git-branch" size="0.75rem" />
|
|
1403
|
+
)
|
|
1404
|
+
|
|
1405
|
+
// Reveal already-loaded rows first; only hit the backend when the next page
|
|
1406
|
+
// crosses what's been fetched for this profile.
|
|
1407
|
+
const handleProfileLoadMore = () => {
|
|
1408
|
+
const target = visibleCount + pageStep
|
|
1409
|
+
|
|
1410
|
+
setVisibleCount(target)
|
|
1411
|
+
|
|
1412
|
+
if (target > loadedCount && loadedCount < totalCount) {
|
|
1413
|
+
group.onLoadMore?.()
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
834
1416
|
|
|
835
1417
|
return (
|
|
836
|
-
<div
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
{reorderable && (
|
|
863
|
-
<span
|
|
864
|
-
{...dragHandleProps}
|
|
865
|
-
aria-label={`Reorder workspace ${group.label}`}
|
|
866
|
-
className="ml-auto -my-0.5 grid w-4 shrink-0 cursor-grab touch-none place-items-center self-stretch overflow-hidden active:cursor-grabbing"
|
|
867
|
-
onClick={event => event.stopPropagation()}
|
|
868
|
-
>
|
|
869
|
-
<Codicon
|
|
870
|
-
className={cn(
|
|
871
|
-
'text-(--ui-text-quaternary) opacity-0 transition-opacity group-hover/workspace:opacity-80 hover:text-(--ui-text-secondary)',
|
|
872
|
-
dragging && 'text-(--ui-text-secondary) opacity-100'
|
|
873
|
-
)}
|
|
874
|
-
name="grabber"
|
|
875
|
-
size="0.75rem"
|
|
1418
|
+
<div
|
|
1419
|
+
className={cn(
|
|
1420
|
+
// While lifted, paint the opaque sidebar surface so the dragged group
|
|
1421
|
+
// erases the rows it floats over instead of ghosting them through a
|
|
1422
|
+
// translucent body.
|
|
1423
|
+
// minmax(0,1fr): pin the single column to the rail width. A bare `grid`
|
|
1424
|
+
// auto column sizes to the widest child's MAX-content (the full,
|
|
1425
|
+
// untruncated label), overflowing the rail so overflow-x-hidden clips the
|
|
1426
|
+
// +/grabber off-screen — the inner truncate never gets a bounded width.
|
|
1427
|
+
'grid grid-cols-[minmax(0,1fr)] gap-px data-[dragging=true]:z-10 data-[dragging=true]:rounded-md data-[dragging=true]:bg-(--ui-sidebar-surface-background) data-[dragging=true]:will-change-transform',
|
|
1428
|
+
className
|
|
1429
|
+
)}
|
|
1430
|
+
data-dragging={dragging ? 'true' : undefined}
|
|
1431
|
+
ref={ref}
|
|
1432
|
+
style={style}
|
|
1433
|
+
{...rest}
|
|
1434
|
+
>
|
|
1435
|
+
<WorkspaceHeader
|
|
1436
|
+
action={
|
|
1437
|
+
(onNewSession || isProfileGroup) && (
|
|
1438
|
+
<WorkspaceAddButton
|
|
1439
|
+
label={s.newSessionIn(group.label)}
|
|
1440
|
+
// Profile groups start a fresh session in that profile but keep the
|
|
1441
|
+
// all-profiles browse view (newSessionInProfile leaves the scope
|
|
1442
|
+
// alone); workspace groups seed the new session's cwd from the path.
|
|
1443
|
+
onClick={() => (isProfileGroup ? newSessionInProfile(group.id) : onNewSession?.(group.path))}
|
|
876
1444
|
/>
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
1445
|
+
)
|
|
1446
|
+
}
|
|
1447
|
+
count={isProfileGroup ? countLabel(visibleSessions.length, totalCount) : group.sessions.length}
|
|
1448
|
+
dragging={dragging}
|
|
1449
|
+
dragHandleProps={dragHandleProps}
|
|
1450
|
+
icon={leadingIcon}
|
|
1451
|
+
label={group.label}
|
|
1452
|
+
onToggle={() => setOpen(value => !value)}
|
|
1453
|
+
open={open}
|
|
1454
|
+
reorderable={reorderable}
|
|
1455
|
+
/>
|
|
880
1456
|
{open && (
|
|
881
1457
|
<>
|
|
882
1458
|
{renderRows(visibleSessions)}
|
|
883
|
-
{hiddenCount > 0 &&
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
<
|
|
892
|
-
|
|
893
|
-
|
|
1459
|
+
{hiddenCount > 0 &&
|
|
1460
|
+
(isProfileGroup ? (
|
|
1461
|
+
<SidebarLoadMoreRow
|
|
1462
|
+
loading={Boolean(group.loadingMore)}
|
|
1463
|
+
onClick={handleProfileLoadMore}
|
|
1464
|
+
step={nextCount}
|
|
1465
|
+
/>
|
|
1466
|
+
) : (
|
|
1467
|
+
<WorkspaceShowMoreButton
|
|
1468
|
+
count={nextCount}
|
|
1469
|
+
label={group.label}
|
|
1470
|
+
onClick={() => setVisibleCount(count => count + WORKSPACE_PAGE)}
|
|
1471
|
+
/>
|
|
1472
|
+
))}
|
|
894
1473
|
</>
|
|
895
1474
|
)}
|
|
896
1475
|
</div>
|
|
@@ -904,13 +1483,281 @@ interface SortableWorkspaceProps {
|
|
|
904
1483
|
}
|
|
905
1484
|
|
|
906
1485
|
function SortableSidebarWorkspaceGroup(props: SortableWorkspaceProps) {
|
|
907
|
-
return <SidebarWorkspaceGroup {...props} {...useSortableBindings(
|
|
1486
|
+
return <SidebarWorkspaceGroup {...props} {...useSortableBindings(props.group.id)} />
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
interface SidebarWorkspaceParentProps extends React.ComponentProps<'div'> {
|
|
1490
|
+
parent: SidebarWorkspaceTree
|
|
1491
|
+
renderRows: (sessions: SessionInfo[]) => React.ReactNode
|
|
1492
|
+
onNewSession?: (path: null | string) => void
|
|
1493
|
+
// When set, this parent's worktrees reorder inside their OWN ReorderableList, so a
|
|
1494
|
+
// worktree drag only ever collides with its siblings — never the repos around it.
|
|
1495
|
+
onReorderWorktree?: (parentId: string, ids: string[]) => void
|
|
1496
|
+
dndSensors?: ReturnType<typeof useSensors>
|
|
1497
|
+
// Whether this parent itself is draggable (set by useSortableBindings).
|
|
1498
|
+
reorderable?: boolean
|
|
1499
|
+
dragging?: boolean
|
|
1500
|
+
dragHandleProps?: React.HTMLAttributes<HTMLElement>
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
// Top level of the worktree tree: a repo header whose body is the repo's
|
|
1504
|
+
// worktrees (each a SidebarWorkspaceGroup), indented one step.
|
|
1505
|
+
function SidebarWorkspaceParent({
|
|
1506
|
+
parent,
|
|
1507
|
+
renderRows,
|
|
1508
|
+
onNewSession,
|
|
1509
|
+
onReorderWorktree,
|
|
1510
|
+
dndSensors,
|
|
1511
|
+
reorderable = false,
|
|
1512
|
+
dragging = false,
|
|
1513
|
+
dragHandleProps,
|
|
1514
|
+
className,
|
|
1515
|
+
style,
|
|
1516
|
+
ref,
|
|
1517
|
+
...rest
|
|
1518
|
+
}: SidebarWorkspaceParentProps) {
|
|
1519
|
+
const { t } = useI18n()
|
|
1520
|
+
const s = t.sidebar
|
|
1521
|
+
const [open, setOpen] = useState(true)
|
|
1522
|
+
const [visibleCount, setVisibleCount] = useState(WORKSPACE_PAGE)
|
|
1523
|
+
|
|
1524
|
+
// A repo with a single worktree has no second level worth showing: collapse it
|
|
1525
|
+
// to one row (repo header → its sessions directly), only nesting when there
|
|
1526
|
+
// are 2+ worktrees to choose between.
|
|
1527
|
+
const soleWorktree = parent.groups.length === 1 ? parent.groups[0] : null
|
|
1528
|
+
const newSessionPath = soleWorktree ? soleWorktree.path : parent.path
|
|
1529
|
+
const visibleSessions = soleWorktree ? soleWorktree.sessions.slice(0, visibleCount) : []
|
|
1530
|
+
const hiddenCount = soleWorktree ? Math.max(0, soleWorktree.sessions.length - visibleSessions.length) : 0
|
|
1531
|
+
|
|
1532
|
+
const groupNodes = parent.groups.map(group =>
|
|
1533
|
+
onReorderWorktree ? (
|
|
1534
|
+
<SortableSidebarWorkspaceGroup group={group} key={group.id} onNewSession={onNewSession} renderRows={renderRows} />
|
|
1535
|
+
) : (
|
|
1536
|
+
<SidebarWorkspaceGroup group={group} key={group.id} onNewSession={onNewSession} renderRows={renderRows} />
|
|
1537
|
+
)
|
|
1538
|
+
)
|
|
1539
|
+
|
|
1540
|
+
return (
|
|
1541
|
+
<div
|
|
1542
|
+
className={cn(
|
|
1543
|
+
'grid grid-cols-[minmax(0,1fr)] gap-px data-[dragging=true]:z-10 data-[dragging=true]:rounded-md data-[dragging=true]:bg-(--ui-sidebar-surface-background) data-[dragging=true]:will-change-transform',
|
|
1544
|
+
className
|
|
1545
|
+
)}
|
|
1546
|
+
data-dragging={dragging ? 'true' : undefined}
|
|
1547
|
+
ref={ref}
|
|
1548
|
+
style={style}
|
|
1549
|
+
{...rest}
|
|
1550
|
+
>
|
|
1551
|
+
<WorkspaceHeader
|
|
1552
|
+
action={
|
|
1553
|
+
onNewSession && (newSessionPath || soleWorktree) && (
|
|
1554
|
+
<WorkspaceAddButton label={s.newSessionIn(parent.label)} onClick={() => onNewSession?.(newSessionPath)} />
|
|
1555
|
+
)
|
|
1556
|
+
}
|
|
1557
|
+
count={parent.sessionCount}
|
|
1558
|
+
dragging={dragging}
|
|
1559
|
+
dragHandleProps={dragHandleProps}
|
|
1560
|
+
emphasis
|
|
1561
|
+
icon={<Codicon className="shrink-0 text-(--ui-text-tertiary)" name="repo" size="0.75rem" />}
|
|
1562
|
+
label={parent.label}
|
|
1563
|
+
onToggle={() => setOpen(value => !value)}
|
|
1564
|
+
open={open}
|
|
1565
|
+
reorderable={reorderable}
|
|
1566
|
+
/>
|
|
1567
|
+
{open &&
|
|
1568
|
+
(soleWorktree ? (
|
|
1569
|
+
// Collapsed: the repo's sessions hang straight off the header.
|
|
1570
|
+
<>
|
|
1571
|
+
{renderRows(visibleSessions)}
|
|
1572
|
+
{hiddenCount > 0 && (
|
|
1573
|
+
<WorkspaceShowMoreButton
|
|
1574
|
+
count={Math.min(WORKSPACE_PAGE, hiddenCount)}
|
|
1575
|
+
label={parent.label}
|
|
1576
|
+
onClick={() => setVisibleCount(count => count + WORKSPACE_PAGE)}
|
|
1577
|
+
/>
|
|
1578
|
+
)}
|
|
1579
|
+
</>
|
|
1580
|
+
) : (
|
|
1581
|
+
// Indent the worktrees under their repo; keep the column pinned to the
|
|
1582
|
+
// rail so long branch labels truncate instead of shoving controls off.
|
|
1583
|
+
<div className="grid grid-cols-[minmax(0,1fr)] gap-px pl-2.5">
|
|
1584
|
+
{onReorderWorktree ? (
|
|
1585
|
+
<ReorderableList
|
|
1586
|
+
ids={parent.groups.map(group => group.id)}
|
|
1587
|
+
onReorder={ids => onReorderWorktree(parent.id, ids)}
|
|
1588
|
+
sensors={dndSensors}
|
|
1589
|
+
>
|
|
1590
|
+
{groupNodes}
|
|
1591
|
+
</ReorderableList>
|
|
1592
|
+
) : (
|
|
1593
|
+
groupNodes
|
|
1594
|
+
)}
|
|
1595
|
+
</div>
|
|
1596
|
+
))}
|
|
1597
|
+
</div>
|
|
1598
|
+
)
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
interface SortableWorkspaceParentProps {
|
|
1602
|
+
parent: SidebarWorkspaceTree
|
|
1603
|
+
renderRows: (sessions: SessionInfo[]) => React.ReactNode
|
|
1604
|
+
onNewSession?: (path: null | string) => void
|
|
1605
|
+
onReorderWorktree?: (parentId: string, ids: string[]) => void
|
|
1606
|
+
dndSensors?: ReturnType<typeof useSensors>
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
function SortableSidebarWorkspaceParent(props: SortableWorkspaceParentProps) {
|
|
1610
|
+
return <SidebarWorkspaceParent {...props} {...useSortableBindings(props.parent.id)} />
|
|
908
1611
|
}
|
|
909
1612
|
|
|
910
1613
|
function SidebarCount({ children }: { children: React.ReactNode }) {
|
|
911
1614
|
return <span className="text-[0.6875rem] font-medium text-(--ui-text-quaternary)">{children}</span>
|
|
912
1615
|
}
|
|
913
1616
|
|
|
1617
|
+
// Reveals the next page of already-loaded rows within a workspace/worktree.
|
|
1618
|
+
function WorkspaceShowMoreButton({ count, label, onClick }: { count: number; label: string; onClick: () => void }) {
|
|
1619
|
+
const { t } = useI18n()
|
|
1620
|
+
const text = t.sidebar.showMoreIn(count, label)
|
|
1621
|
+
|
|
1622
|
+
return (
|
|
1623
|
+
<Tip label={text}>
|
|
1624
|
+
<button
|
|
1625
|
+
aria-label={text}
|
|
1626
|
+
className="ml-auto grid size-5 place-items-center rounded-sm bg-transparent text-(--ui-text-tertiary) transition-colors hover:bg-(--ui-control-hover-background) hover:text-foreground"
|
|
1627
|
+
onClick={onClick}
|
|
1628
|
+
type="button"
|
|
1629
|
+
>
|
|
1630
|
+
<Codicon name="ellipsis" size="0.75rem" />
|
|
1631
|
+
</button>
|
|
1632
|
+
</Tip>
|
|
1633
|
+
)
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
// Reorder handle that lives in the header's leading-icon slot: the resting icon
|
|
1637
|
+
// fades out and a grabber fades in on hover/drag (same swap as the session row),
|
|
1638
|
+
// so the drag affordance never eats header width on the right.
|
|
1639
|
+
function WorkspaceReorderHandle({
|
|
1640
|
+
dragHandleProps,
|
|
1641
|
+
dragging,
|
|
1642
|
+
icon,
|
|
1643
|
+
label
|
|
1644
|
+
}: {
|
|
1645
|
+
dragHandleProps?: React.HTMLAttributes<HTMLElement>
|
|
1646
|
+
dragging: boolean
|
|
1647
|
+
icon: React.ReactNode
|
|
1648
|
+
label: string
|
|
1649
|
+
}) {
|
|
1650
|
+
return (
|
|
1651
|
+
<span
|
|
1652
|
+
{...dragHandleProps}
|
|
1653
|
+
aria-label={label}
|
|
1654
|
+
className="group/handle relative -my-0.5 grid size-4 shrink-0 cursor-grab touch-none place-items-center self-stretch overflow-hidden active:cursor-grabbing"
|
|
1655
|
+
data-reorder-handle
|
|
1656
|
+
onClick={event => event.stopPropagation()}
|
|
1657
|
+
>
|
|
1658
|
+
<span
|
|
1659
|
+
className={cn(
|
|
1660
|
+
'grid place-items-center transition-opacity group-hover/handle:opacity-0 group-focus-within/handle:opacity-0',
|
|
1661
|
+
dragging && 'opacity-0'
|
|
1662
|
+
)}
|
|
1663
|
+
>
|
|
1664
|
+
{icon}
|
|
1665
|
+
</span>
|
|
1666
|
+
<Codicon
|
|
1667
|
+
className={cn(
|
|
1668
|
+
'absolute text-(--ui-text-quaternary) opacity-0 transition-opacity group-hover/handle:opacity-80 group-focus-within/handle:opacity-80 hover:text-(--ui-text-secondary)',
|
|
1669
|
+
dragging && 'text-(--ui-text-secondary) opacity-100'
|
|
1670
|
+
)}
|
|
1671
|
+
name="grabber"
|
|
1672
|
+
size="0.75rem"
|
|
1673
|
+
/>
|
|
1674
|
+
</span>
|
|
1675
|
+
)
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
// "+" affordance shared by repo and worktree headers — reveals on header hover.
|
|
1679
|
+
function WorkspaceAddButton({ label, onClick }: { label: string; onClick: () => void }) {
|
|
1680
|
+
return (
|
|
1681
|
+
<Tip label={label}>
|
|
1682
|
+
<button
|
|
1683
|
+
aria-label={label}
|
|
1684
|
+
className="grid size-4 shrink-0 place-items-center rounded-sm bg-transparent text-(--ui-text-quaternary) opacity-0 transition-opacity hover:bg-(--ui-control-hover-background) hover:text-foreground group-hover/workspace:opacity-100"
|
|
1685
|
+
onClick={onClick}
|
|
1686
|
+
type="button"
|
|
1687
|
+
>
|
|
1688
|
+
<Codicon name="add" size="0.75rem" />
|
|
1689
|
+
</button>
|
|
1690
|
+
</Tip>
|
|
1691
|
+
)
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
// Collapsible header shared by the repo (emphasis) and worktree levels: a
|
|
1695
|
+
// toggle button whose leading glyph doubles as the reorder handle, plus an
|
|
1696
|
+
// optional trailing action (the +).
|
|
1697
|
+
function WorkspaceHeader({
|
|
1698
|
+
action,
|
|
1699
|
+
count,
|
|
1700
|
+
dragHandleProps,
|
|
1701
|
+
dragging = false,
|
|
1702
|
+
emphasis = false,
|
|
1703
|
+
icon,
|
|
1704
|
+
label,
|
|
1705
|
+
onToggle,
|
|
1706
|
+
open,
|
|
1707
|
+
reorderable = false
|
|
1708
|
+
}: {
|
|
1709
|
+
action?: React.ReactNode
|
|
1710
|
+
count: React.ReactNode
|
|
1711
|
+
dragHandleProps?: React.HTMLAttributes<HTMLElement>
|
|
1712
|
+
dragging?: boolean
|
|
1713
|
+
emphasis?: boolean
|
|
1714
|
+
icon: React.ReactNode
|
|
1715
|
+
label: string
|
|
1716
|
+
onToggle: () => void
|
|
1717
|
+
open: boolean
|
|
1718
|
+
reorderable?: boolean
|
|
1719
|
+
}) {
|
|
1720
|
+
const { t } = useI18n()
|
|
1721
|
+
|
|
1722
|
+
return (
|
|
1723
|
+
<div
|
|
1724
|
+
className={cn(
|
|
1725
|
+
'group/workspace flex min-h-6 items-center gap-1 px-2 pt-1 text-[0.6875rem]',
|
|
1726
|
+
emphasis ? 'font-semibold text-(--ui-text-secondary)' : 'font-medium text-(--ui-text-tertiary)'
|
|
1727
|
+
)}
|
|
1728
|
+
>
|
|
1729
|
+
<button
|
|
1730
|
+
className={cn(
|
|
1731
|
+
'flex min-w-0 flex-1 items-center gap-1.5 bg-transparent text-left',
|
|
1732
|
+
emphasis ? 'hover:text-foreground' : 'hover:text-(--ui-text-secondary)'
|
|
1733
|
+
)}
|
|
1734
|
+
onClick={onToggle}
|
|
1735
|
+
type="button"
|
|
1736
|
+
>
|
|
1737
|
+
{reorderable ? (
|
|
1738
|
+
<WorkspaceReorderHandle
|
|
1739
|
+
dragging={dragging}
|
|
1740
|
+
dragHandleProps={dragHandleProps}
|
|
1741
|
+
icon={icon}
|
|
1742
|
+
label={t.sidebar.reorderWorkspace(label)}
|
|
1743
|
+
/>
|
|
1744
|
+
) : (
|
|
1745
|
+
icon
|
|
1746
|
+
)}
|
|
1747
|
+
<span className="min-w-0 truncate">{label}</span>
|
|
1748
|
+
<span className="shrink-0">
|
|
1749
|
+
<SidebarCount>{count}</SidebarCount>
|
|
1750
|
+
</span>
|
|
1751
|
+
<DisclosureCaret
|
|
1752
|
+
className="shrink-0 text-(--ui-text-tertiary) opacity-0 transition group-hover/workspace:opacity-100"
|
|
1753
|
+
open={open}
|
|
1754
|
+
/>
|
|
1755
|
+
</button>
|
|
1756
|
+
{action}
|
|
1757
|
+
</div>
|
|
1758
|
+
)
|
|
1759
|
+
}
|
|
1760
|
+
|
|
914
1761
|
interface SortableSessionRowProps {
|
|
915
1762
|
session: SessionInfo
|
|
916
1763
|
isPinned: boolean
|
|
@@ -925,25 +1772,3 @@ interface SortableSessionRowProps {
|
|
|
925
1772
|
function SortableSidebarSessionRow(props: SortableSessionRowProps) {
|
|
926
1773
|
return <SidebarSessionRow {...props} {...useSortableBindings(props.session.id)} />
|
|
927
1774
|
}
|
|
928
|
-
|
|
929
|
-
interface SidebarLoadMoreRowProps {
|
|
930
|
-
loading: boolean
|
|
931
|
-
onClick: () => void
|
|
932
|
-
step: number
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
function SidebarLoadMoreRow({ loading, onClick, step }: SidebarLoadMoreRowProps) {
|
|
936
|
-
const label = loading ? 'Loading…' : step > 0 ? `Load ${step} more` : 'Load more'
|
|
937
|
-
|
|
938
|
-
return (
|
|
939
|
-
<button
|
|
940
|
-
className="flex min-h-5 cursor-pointer items-center gap-1 self-start bg-transparent pl-2 text-left text-[0.6875rem] text-(--ui-text-tertiary) transition-colors duration-100 ease-out hover:text-foreground hover:transition-none disabled:cursor-default disabled:opacity-60 disabled:hover:text-(--ui-text-tertiary)"
|
|
941
|
-
disabled={loading}
|
|
942
|
-
onClick={onClick}
|
|
943
|
-
type="button"
|
|
944
|
-
>
|
|
945
|
-
<Codicon className="opacity-70" name={loading ? 'loading' : 'chevron-down'} size="0.75rem" spinning={loading} />
|
|
946
|
-
<span>{label}</span>
|
|
947
|
-
</button>
|
|
948
|
-
)
|
|
949
|
-
}
|