@butlerw/vellum 0.1.0
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/LICENSE +21 -0
- package/README.md +411 -0
- package/__fixtures__/responses/code-generation.json +42 -0
- package/__fixtures__/responses/error-response.json +20 -0
- package/__fixtures__/responses/hello-world.json +32 -0
- package/dist/auth-6MCXESOH.js +26 -0
- package/dist/chunk-SECXJGWA.js +597 -0
- package/dist/index.js +34023 -0
- package/package.json +67 -0
- package/src/__tests__/commands.e2e.test.ts +728 -0
- package/src/__tests__/credentials.test.ts +713 -0
- package/src/__tests__/mode-e2e.test.ts +391 -0
- package/src/__tests__/tui-integration.test.tsx +1271 -0
- package/src/agents/__tests__/task-persistence.test.ts +235 -0
- package/src/agents/commands/delegate.ts +240 -0
- package/src/agents/commands/index.ts +10 -0
- package/src/agents/commands/resume.ts +335 -0
- package/src/agents/index.ts +29 -0
- package/src/agents/task-persistence.ts +272 -0
- package/src/agents/task-resumption.ts +242 -0
- package/src/app.tsx +4737 -0
- package/src/commands/__tests__/.gitkeep +1 -0
- package/src/commands/__tests__/agents.test.ts +606 -0
- package/src/commands/__tests__/auth.test.ts +626 -0
- package/src/commands/__tests__/autocomplete.test.ts +683 -0
- package/src/commands/__tests__/batch.test.ts +287 -0
- package/src/commands/__tests__/chain-pipe-parser.test.ts +654 -0
- package/src/commands/__tests__/completion.test.ts +238 -0
- package/src/commands/__tests__/core.test.ts +363 -0
- package/src/commands/__tests__/executor.test.ts +496 -0
- package/src/commands/__tests__/exit-codes.test.ts +220 -0
- package/src/commands/__tests__/init.test.ts +243 -0
- package/src/commands/__tests__/language.test.ts +353 -0
- package/src/commands/__tests__/mode-cli.test.ts +667 -0
- package/src/commands/__tests__/model.test.ts +277 -0
- package/src/commands/__tests__/parser.test.ts +493 -0
- package/src/commands/__tests__/performance.bench.ts +380 -0
- package/src/commands/__tests__/registry.test.ts +534 -0
- package/src/commands/__tests__/resume.test.ts +449 -0
- package/src/commands/__tests__/security.test.ts +845 -0
- package/src/commands/__tests__/stream-json.test.ts +372 -0
- package/src/commands/__tests__/user-commands.test.ts +597 -0
- package/src/commands/adapters.ts +267 -0
- package/src/commands/agent.ts +395 -0
- package/src/commands/agents/generate.ts +506 -0
- package/src/commands/agents/index.ts +272 -0
- package/src/commands/agents/show.ts +271 -0
- package/src/commands/agents/validate.ts +387 -0
- package/src/commands/auth.ts +883 -0
- package/src/commands/autocomplete.ts +480 -0
- package/src/commands/batch/command.ts +388 -0
- package/src/commands/batch/executor.ts +361 -0
- package/src/commands/batch/index.ts +12 -0
- package/src/commands/commit.ts +235 -0
- package/src/commands/completion/index.ts +371 -0
- package/src/commands/condense.ts +191 -0
- package/src/commands/config.ts +344 -0
- package/src/commands/context-provider.ts +173 -0
- package/src/commands/copy.ts +329 -0
- package/src/commands/core/clear.ts +38 -0
- package/src/commands/core/exit.ts +43 -0
- package/src/commands/core/help.ts +354 -0
- package/src/commands/core/index.ts +15 -0
- package/src/commands/cost.ts +179 -0
- package/src/commands/credentials.tsx +618 -0
- package/src/commands/custom-agents/__tests__/custom-agents.test.ts +709 -0
- package/src/commands/custom-agents/create.ts +377 -0
- package/src/commands/custom-agents/export.ts +135 -0
- package/src/commands/custom-agents/import.ts +199 -0
- package/src/commands/custom-agents/index.ts +372 -0
- package/src/commands/custom-agents/info.ts +318 -0
- package/src/commands/custom-agents/list.ts +267 -0
- package/src/commands/custom-agents/validate.ts +388 -0
- package/src/commands/diff-mode.ts +241 -0
- package/src/commands/env.ts +53 -0
- package/src/commands/executor.ts +579 -0
- package/src/commands/exit-codes.ts +202 -0
- package/src/commands/index.ts +701 -0
- package/src/commands/init/index.ts +15 -0
- package/src/commands/init/prompts.ts +366 -0
- package/src/commands/init/templates/commands-readme.md +80 -0
- package/src/commands/init/templates/example-command.md +79 -0
- package/src/commands/init/templates/example-skill.md +168 -0
- package/src/commands/init/templates/example-workflow.md +101 -0
- package/src/commands/init/templates/prompts-readme.md +52 -0
- package/src/commands/init/templates/rules-readme.md +63 -0
- package/src/commands/init/templates/skills-readme.md +83 -0
- package/src/commands/init/templates/workflows-readme.md +94 -0
- package/src/commands/init.ts +391 -0
- package/src/commands/install.ts +90 -0
- package/src/commands/language.ts +191 -0
- package/src/commands/loaders/.gitkeep +1 -0
- package/src/commands/lsp.ts +199 -0
- package/src/commands/markdown-commands.ts +253 -0
- package/src/commands/mcp.ts +588 -0
- package/src/commands/memory/export.ts +341 -0
- package/src/commands/memory/index.ts +148 -0
- package/src/commands/memory/list.ts +261 -0
- package/src/commands/memory/search.ts +346 -0
- package/src/commands/memory/utils.ts +15 -0
- package/src/commands/metrics.ts +75 -0
- package/src/commands/migrate/index.ts +16 -0
- package/src/commands/migrate/prompts.ts +477 -0
- package/src/commands/mode.ts +331 -0
- package/src/commands/model.ts +298 -0
- package/src/commands/onboard.ts +205 -0
- package/src/commands/open.ts +169 -0
- package/src/commands/output/stream-json.ts +373 -0
- package/src/commands/parser/chain-parser.ts +370 -0
- package/src/commands/parser/index.ts +29 -0
- package/src/commands/parser/pipe-parser.ts +480 -0
- package/src/commands/parser.ts +588 -0
- package/src/commands/persistence.ts +355 -0
- package/src/commands/progress.ts +18 -0
- package/src/commands/prompt/index.ts +17 -0
- package/src/commands/prompt/validate.ts +621 -0
- package/src/commands/prompt-priority.ts +401 -0
- package/src/commands/registry.ts +374 -0
- package/src/commands/sandbox/index.ts +131 -0
- package/src/commands/security/index.ts +21 -0
- package/src/commands/security/input-sanitizer.ts +168 -0
- package/src/commands/security/permission-checker.ts +456 -0
- package/src/commands/security/sensitive-data.ts +350 -0
- package/src/commands/session/delete.ts +38 -0
- package/src/commands/session/export.ts +39 -0
- package/src/commands/session/index.ts +26 -0
- package/src/commands/session/list.ts +26 -0
- package/src/commands/session/resume.ts +562 -0
- package/src/commands/session/search.ts +434 -0
- package/src/commands/session/show.ts +26 -0
- package/src/commands/settings.ts +368 -0
- package/src/commands/setup.ts +23 -0
- package/src/commands/shell/index.ts +16 -0
- package/src/commands/shell/setup.ts +422 -0
- package/src/commands/shell-init.ts +50 -0
- package/src/commands/shell-integration/index.ts +194 -0
- package/src/commands/skill.ts +1220 -0
- package/src/commands/spec.ts +558 -0
- package/src/commands/status.ts +246 -0
- package/src/commands/theme.ts +211 -0
- package/src/commands/think.ts +551 -0
- package/src/commands/trust.ts +211 -0
- package/src/commands/tutorial.ts +522 -0
- package/src/commands/types.ts +512 -0
- package/src/commands/update.ts +274 -0
- package/src/commands/usage.ts +213 -0
- package/src/commands/user-commands.ts +630 -0
- package/src/commands/utils.ts +142 -0
- package/src/commands/vim.ts +152 -0
- package/src/commands/workflow.ts +257 -0
- package/src/components/header.tsx +25 -0
- package/src/components/input.tsx +25 -0
- package/src/components/message-list.tsx +32 -0
- package/src/components/status-bar.tsx +23 -0
- package/src/index.tsx +614 -0
- package/src/onboarding/__tests__/tutorial.test.ts +740 -0
- package/src/onboarding/index.ts +69 -0
- package/src/onboarding/tips/index.ts +9 -0
- package/src/onboarding/tips/tip-engine.ts +459 -0
- package/src/onboarding/tutorial/index.ts +88 -0
- package/src/onboarding/tutorial/lessons/basics.ts +151 -0
- package/src/onboarding/tutorial/lessons/index.ts +151 -0
- package/src/onboarding/tutorial/lessons/modes.ts +230 -0
- package/src/onboarding/tutorial/lessons/tools.ts +172 -0
- package/src/onboarding/tutorial/progress-tracker.ts +350 -0
- package/src/onboarding/tutorial/storage.ts +249 -0
- package/src/onboarding/tutorial/tutorial-system.ts +462 -0
- package/src/onboarding/tutorial/types.ts +310 -0
- package/src/orchestrator-singleton.ts +129 -0
- package/src/shutdown.ts +33 -0
- package/src/test/e2e/assertions.ts +267 -0
- package/src/test/e2e/fixtures.ts +204 -0
- package/src/test/e2e/harness.ts +575 -0
- package/src/test/e2e/index.ts +57 -0
- package/src/test/e2e/types.ts +228 -0
- package/src/test/fixtures/__tests__/fake-response-loader.test.ts +314 -0
- package/src/test/fixtures/fake-response-loader.ts +314 -0
- package/src/test/fixtures/index.ts +20 -0
- package/src/tui/__tests__/mcp-panel.test.tsx +82 -0
- package/src/tui/__tests__/mcp-wiring.test.tsx +78 -0
- package/src/tui/__tests__/mode-components.test.tsx +395 -0
- package/src/tui/__tests__/permission-ask-flow.test.tsx +138 -0
- package/src/tui/__tests__/sidebar-panel-data.test.tsx +148 -0
- package/src/tui/__tests__/tools-panel-hotkeys.test.tsx +41 -0
- package/src/tui/adapters/agent-adapter.ts +1008 -0
- package/src/tui/adapters/index.ts +48 -0
- package/src/tui/adapters/message-adapter.ts +315 -0
- package/src/tui/adapters/persistence-bridge.ts +331 -0
- package/src/tui/adapters/session-adapter.ts +419 -0
- package/src/tui/buffered-stdout.ts +223 -0
- package/src/tui/components/AgentProgress.tsx +424 -0
- package/src/tui/components/Banner/AsciiArt.ts +160 -0
- package/src/tui/components/Banner/Banner.tsx +355 -0
- package/src/tui/components/Banner/ShimmerContext.tsx +131 -0
- package/src/tui/components/Banner/ShimmerText.tsx +193 -0
- package/src/tui/components/Banner/TypeWriterGradient.tsx +321 -0
- package/src/tui/components/Banner/index.ts +61 -0
- package/src/tui/components/Banner/useShimmer.ts +241 -0
- package/src/tui/components/ChatView.tsx +11 -0
- package/src/tui/components/Checkpoint/CheckpointDiffView.tsx +371 -0
- package/src/tui/components/Checkpoint/SnapshotCheckpointPanel.tsx +440 -0
- package/src/tui/components/Checkpoint/index.ts +19 -0
- package/src/tui/components/CostDisplay.tsx +226 -0
- package/src/tui/components/InitErrorBanner.tsx +122 -0
- package/src/tui/components/Input/Autocomplete.tsx +603 -0
- package/src/tui/components/Input/EnhancedCommandInput.tsx +471 -0
- package/src/tui/components/Input/HighlightedText.tsx +236 -0
- package/src/tui/components/Input/MentionAutocomplete.tsx +375 -0
- package/src/tui/components/Input/TextInput.tsx +1002 -0
- package/src/tui/components/Input/__tests__/Autocomplete.test.tsx +374 -0
- package/src/tui/components/Input/__tests__/TextInput.test.tsx +241 -0
- package/src/tui/components/Input/__tests__/highlight.test.ts +219 -0
- package/src/tui/components/Input/__tests__/slash-command-utils.test.ts +104 -0
- package/src/tui/components/Input/highlight.ts +362 -0
- package/src/tui/components/Input/index.ts +36 -0
- package/src/tui/components/Input/slash-command-utils.ts +135 -0
- package/src/tui/components/Layout.tsx +432 -0
- package/src/tui/components/McpPanel.tsx +137 -0
- package/src/tui/components/MemoryPanel.tsx +448 -0
- package/src/tui/components/Messages/CodeBlock.tsx +527 -0
- package/src/tui/components/Messages/DiffView.tsx +679 -0
- package/src/tui/components/Messages/ImageReference.tsx +89 -0
- package/src/tui/components/Messages/MarkdownBlock.tsx +228 -0
- package/src/tui/components/Messages/MarkdownRenderer.tsx +498 -0
- package/src/tui/components/Messages/MessageBubble.tsx +270 -0
- package/src/tui/components/Messages/MessageList.tsx +1719 -0
- package/src/tui/components/Messages/StreamingText.tsx +216 -0
- package/src/tui/components/Messages/ThinkingBlock.tsx +408 -0
- package/src/tui/components/Messages/ToolResultPreview.tsx +243 -0
- package/src/tui/components/Messages/__tests__/CodeBlock.test.tsx +296 -0
- package/src/tui/components/Messages/__tests__/DiffView.test.tsx +239 -0
- package/src/tui/components/Messages/__tests__/MarkdownRenderer.test.tsx +303 -0
- package/src/tui/components/Messages/__tests__/MessageBubble.test.tsx +268 -0
- package/src/tui/components/Messages/__tests__/MessageList.test.tsx +324 -0
- package/src/tui/components/Messages/__tests__/StreamingText.test.tsx +215 -0
- package/src/tui/components/Messages/index.ts +25 -0
- package/src/tui/components/ModeIndicator.tsx +177 -0
- package/src/tui/components/ModeSelector.tsx +216 -0
- package/src/tui/components/ModelSelector.tsx +339 -0
- package/src/tui/components/OnboardingWizard.tsx +670 -0
- package/src/tui/components/PhaseProgressIndicator.tsx +270 -0
- package/src/tui/components/RateLimitIndicator.tsx +82 -0
- package/src/tui/components/ScreenReaderLayout.tsx +295 -0
- package/src/tui/components/SettingsPanel.tsx +643 -0
- package/src/tui/components/Sidebar/SystemStatusPanel.tsx +284 -0
- package/src/tui/components/Sidebar/index.ts +9 -0
- package/src/tui/components/Status/ModelStatusBar.tsx +270 -0
- package/src/tui/components/Status/index.ts +12 -0
- package/src/tui/components/StatusBar/AgentModeIndicator.tsx +257 -0
- package/src/tui/components/StatusBar/ContextProgress.tsx +167 -0
- package/src/tui/components/StatusBar/FileChangesIndicator.tsx +62 -0
- package/src/tui/components/StatusBar/GitIndicator.tsx +89 -0
- package/src/tui/components/StatusBar/HeaderBar.tsx +126 -0
- package/src/tui/components/StatusBar/ModelIndicator.tsx +157 -0
- package/src/tui/components/StatusBar/PersistenceStatusIndicator.tsx +210 -0
- package/src/tui/components/StatusBar/ResilienceIndicator.tsx +106 -0
- package/src/tui/components/StatusBar/SandboxIndicator.tsx +167 -0
- package/src/tui/components/StatusBar/StatusBar.tsx +368 -0
- package/src/tui/components/StatusBar/ThinkingModeIndicator.tsx +170 -0
- package/src/tui/components/StatusBar/TokenBreakdown.tsx +246 -0
- package/src/tui/components/StatusBar/TokenCounter.tsx +135 -0
- package/src/tui/components/StatusBar/TrustModeIndicator.tsx +130 -0
- package/src/tui/components/StatusBar/WorkspaceIndicator.tsx +86 -0
- package/src/tui/components/StatusBar/__tests__/AgentModeIndicator.test.tsx +193 -0
- package/src/tui/components/StatusBar/__tests__/StatusBar.test.tsx +729 -0
- package/src/tui/components/StatusBar/index.ts +60 -0
- package/src/tui/components/TipBanner.tsx +115 -0
- package/src/tui/components/TodoItem.tsx +208 -0
- package/src/tui/components/TodoPanel.tsx +455 -0
- package/src/tui/components/Tools/ApprovalQueue.tsx +407 -0
- package/src/tui/components/Tools/OptionSelector.tsx +160 -0
- package/src/tui/components/Tools/PermissionDialog.tsx +286 -0
- package/src/tui/components/Tools/ToolParams.tsx +483 -0
- package/src/tui/components/Tools/ToolsPanel.tsx +178 -0
- package/src/tui/components/Tools/__tests__/PermissionDialog.test.tsx +510 -0
- package/src/tui/components/Tools/__tests__/ToolParams.test.tsx +432 -0
- package/src/tui/components/Tools/index.ts +21 -0
- package/src/tui/components/TrustPrompt.tsx +279 -0
- package/src/tui/components/UpdateBanner.tsx +166 -0
- package/src/tui/components/VimModeIndicator.tsx +112 -0
- package/src/tui/components/backtrack/BacktrackControls.tsx +402 -0
- package/src/tui/components/backtrack/index.ts +13 -0
- package/src/tui/components/common/AutoApprovalStatus.tsx +251 -0
- package/src/tui/components/common/CostWarning.tsx +294 -0
- package/src/tui/components/common/DynamicShortcutHints.tsx +209 -0
- package/src/tui/components/common/EnhancedLoadingIndicator.tsx +305 -0
- package/src/tui/components/common/ErrorBoundary.tsx +140 -0
- package/src/tui/components/common/GradientText.tsx +224 -0
- package/src/tui/components/common/HotkeyHelpModal.tsx +193 -0
- package/src/tui/components/common/HotkeyHints.tsx +70 -0
- package/src/tui/components/common/MaxSizedBox.tsx +354 -0
- package/src/tui/components/common/NewMessagesBadge.tsx +65 -0
- package/src/tui/components/common/ProtectedFileLegend.tsx +89 -0
- package/src/tui/components/common/ScrollIndicator.tsx +160 -0
- package/src/tui/components/common/Spinner.tsx +342 -0
- package/src/tui/components/common/StreamingIndicator.tsx +316 -0
- package/src/tui/components/common/VirtualizedList/VirtualizedList.tsx +428 -0
- package/src/tui/components/common/VirtualizedList/hooks/index.ts +19 -0
- package/src/tui/components/common/VirtualizedList/hooks/useBatchedScroll.ts +64 -0
- package/src/tui/components/common/VirtualizedList/hooks/useScrollAnchor.ts +290 -0
- package/src/tui/components/common/VirtualizedList/hooks/useVirtualization.ts +340 -0
- package/src/tui/components/common/VirtualizedList/index.ts +30 -0
- package/src/tui/components/common/VirtualizedList/types.ts +107 -0
- package/src/tui/components/common/__tests__/NewMessagesBadge.test.tsx +74 -0
- package/src/tui/components/common/__tests__/ScrollIndicator.test.tsx +193 -0
- package/src/tui/components/common/index.ts +110 -0
- package/src/tui/components/index.ts +79 -0
- package/src/tui/components/session/CheckpointPanel.tsx +323 -0
- package/src/tui/components/session/RollbackDialog.tsx +169 -0
- package/src/tui/components/session/SessionItem.tsx +136 -0
- package/src/tui/components/session/SessionListPanel.tsx +252 -0
- package/src/tui/components/session/SessionPicker.tsx +449 -0
- package/src/tui/components/session/SessionPreview.tsx +240 -0
- package/src/tui/components/session/__tests__/session.test.tsx +408 -0
- package/src/tui/components/session/index.ts +28 -0
- package/src/tui/components/session/types.ts +116 -0
- package/src/tui/components/theme/__tests__/tokens.test.ts +471 -0
- package/src/tui/components/theme/index.ts +227 -0
- package/src/tui/components/theme/tokens.ts +484 -0
- package/src/tui/config/defaults.ts +134 -0
- package/src/tui/config/index.ts +17 -0
- package/src/tui/context/AnimationContext.tsx +284 -0
- package/src/tui/context/AppContext.tsx +349 -0
- package/src/tui/context/BracketedPasteContext.tsx +372 -0
- package/src/tui/context/LspContext.tsx +192 -0
- package/src/tui/context/McpContext.tsx +325 -0
- package/src/tui/context/MessagesContext.tsx +870 -0
- package/src/tui/context/OverflowContext.tsx +213 -0
- package/src/tui/context/RateLimitContext.tsx +108 -0
- package/src/tui/context/ResilienceContext.tsx +275 -0
- package/src/tui/context/RootProvider.tsx +136 -0
- package/src/tui/context/ScrollContext.tsx +331 -0
- package/src/tui/context/ToolsContext.tsx +702 -0
- package/src/tui/context/__tests__/BracketedPasteContext.test.tsx +416 -0
- package/src/tui/context/index.ts +140 -0
- package/src/tui/enterprise-integration.ts +282 -0
- package/src/tui/hooks/__tests__/useBacktrack.test.tsx +138 -0
- package/src/tui/hooks/__tests__/useBracketedPaste.test.tsx +222 -0
- package/src/tui/hooks/__tests__/useCopyMode.test.tsx +336 -0
- package/src/tui/hooks/__tests__/useHotkeys.ctrl-input.test.tsx +96 -0
- package/src/tui/hooks/__tests__/useHotkeys.test.tsx +454 -0
- package/src/tui/hooks/__tests__/useInputHistory.test.tsx +660 -0
- package/src/tui/hooks/__tests__/useLineBuffer.test.ts +295 -0
- package/src/tui/hooks/__tests__/useModeController.test.ts +137 -0
- package/src/tui/hooks/__tests__/useModeShortcuts.test.tsx +142 -0
- package/src/tui/hooks/__tests__/useScrollController.test.ts +464 -0
- package/src/tui/hooks/__tests__/useVim.test.tsx +531 -0
- package/src/tui/hooks/index.ts +252 -0
- package/src/tui/hooks/useAgentLoop.ts +712 -0
- package/src/tui/hooks/useAlternateBuffer.ts +398 -0
- package/src/tui/hooks/useAnimatedScrollbar.ts +241 -0
- package/src/tui/hooks/useBacktrack.ts +443 -0
- package/src/tui/hooks/useBracketedPaste.ts +104 -0
- package/src/tui/hooks/useCollapsible.ts +240 -0
- package/src/tui/hooks/useCopyMode.ts +382 -0
- package/src/tui/hooks/useCostSummary.ts +75 -0
- package/src/tui/hooks/useDesktopNotification.ts +414 -0
- package/src/tui/hooks/useDiffMode.ts +44 -0
- package/src/tui/hooks/useFileChangeStats.ts +110 -0
- package/src/tui/hooks/useFileSuggestions.ts +284 -0
- package/src/tui/hooks/useFlickerDetector.ts +250 -0
- package/src/tui/hooks/useGitStatus.ts +200 -0
- package/src/tui/hooks/useHotkeys.ts +579 -0
- package/src/tui/hooks/useImagePaste.ts +114 -0
- package/src/tui/hooks/useInputHighlight.ts +145 -0
- package/src/tui/hooks/useInputHistory.ts +246 -0
- package/src/tui/hooks/useKeyboardScroll.ts +209 -0
- package/src/tui/hooks/useLineBuffer.ts +356 -0
- package/src/tui/hooks/useMentionAutocomplete.ts +235 -0
- package/src/tui/hooks/useModeController.ts +167 -0
- package/src/tui/hooks/useModeShortcuts.ts +196 -0
- package/src/tui/hooks/usePermissionHandler.ts +146 -0
- package/src/tui/hooks/usePersistence.ts +480 -0
- package/src/tui/hooks/usePersistenceShortcuts.ts +225 -0
- package/src/tui/hooks/usePlaceholderRotation.ts +143 -0
- package/src/tui/hooks/useProviderStatus.ts +270 -0
- package/src/tui/hooks/useRateLimitStatus.ts +90 -0
- package/src/tui/hooks/useScreenReader.ts +315 -0
- package/src/tui/hooks/useScrollController.ts +450 -0
- package/src/tui/hooks/useScrollEventBatcher.ts +185 -0
- package/src/tui/hooks/useSidebarPanelData.ts +115 -0
- package/src/tui/hooks/useSmoothScroll.ts +202 -0
- package/src/tui/hooks/useSnapshots.ts +300 -0
- package/src/tui/hooks/useStateAndRef.ts +50 -0
- package/src/tui/hooks/useTerminalSize.ts +206 -0
- package/src/tui/hooks/useToolApprovalController.ts +91 -0
- package/src/tui/hooks/useVim.ts +334 -0
- package/src/tui/hooks/useWorkspace.ts +56 -0
- package/src/tui/i18n/__tests__/init.test.ts +278 -0
- package/src/tui/i18n/__tests__/language-config.test.ts +199 -0
- package/src/tui/i18n/__tests__/locale-detection.test.ts +250 -0
- package/src/tui/i18n/__tests__/settings-integration.test.ts +262 -0
- package/src/tui/i18n/index.ts +72 -0
- package/src/tui/i18n/init.ts +131 -0
- package/src/tui/i18n/language-config.ts +106 -0
- package/src/tui/i18n/locale-detection.ts +173 -0
- package/src/tui/i18n/settings-integration.ts +557 -0
- package/src/tui/i18n/tui-namespace.ts +538 -0
- package/src/tui/i18n/types.ts +312 -0
- package/src/tui/index.ts +43 -0
- package/src/tui/lsp-integration.ts +409 -0
- package/src/tui/metrics-integration.ts +366 -0
- package/src/tui/plugins.ts +383 -0
- package/src/tui/resilience.ts +342 -0
- package/src/tui/sandbox-integration.ts +317 -0
- package/src/tui/services/clipboard.ts +348 -0
- package/src/tui/services/fuzzy-search.ts +441 -0
- package/src/tui/services/index.ts +72 -0
- package/src/tui/services/markdown-renderer.ts +565 -0
- package/src/tui/services/open-external.ts +247 -0
- package/src/tui/services/syntax-highlighter.ts +483 -0
- package/src/tui/slash-commands.ts +12 -0
- package/src/tui/theme/index.ts +15 -0
- package/src/tui/theme/provider.tsx +206 -0
- package/src/tui/tip-integration.ts +300 -0
- package/src/tui/types/__tests__/ink-extended.test.ts +121 -0
- package/src/tui/types/ink-extended.ts +87 -0
- package/src/tui/utils/__tests__/bracketedPaste.test.ts +231 -0
- package/src/tui/utils/__tests__/heightEstimator.test.ts +157 -0
- package/src/tui/utils/__tests__/text-width.test.ts +158 -0
- package/src/tui/utils/__tests__/textSanitizer.test.ts +266 -0
- package/src/tui/utils/__tests__/ui-sizing.test.ts +169 -0
- package/src/tui/utils/bracketedPaste.ts +107 -0
- package/src/tui/utils/cursor-manager.ts +131 -0
- package/src/tui/utils/detectTerminal.ts +596 -0
- package/src/tui/utils/findLastSafeSplitPoint.ts +92 -0
- package/src/tui/utils/heightEstimator.ts +198 -0
- package/src/tui/utils/index.ts +91 -0
- package/src/tui/utils/isNarrowWidth.ts +52 -0
- package/src/tui/utils/stdoutGuard.ts +90 -0
- package/src/tui/utils/synchronized-update.ts +70 -0
- package/src/tui/utils/text-width.ts +225 -0
- package/src/tui/utils/textSanitizer.ts +225 -0
- package/src/tui/utils/textUtils.ts +114 -0
- package/src/tui/utils/ui-sizing.ts +192 -0
- package/src/tui-blessed/app.ts +160 -0
- package/src/tui-blessed/index.ts +2 -0
- package/src/tui-blessed/neo-blessed.d.ts +6 -0
- package/src/tui-blessed/test.ts +21 -0
- package/src/tui-blessed/types.ts +14 -0
- package/src/utils/icons.ts +130 -0
- package/src/utils/index.ts +33 -0
- package/src/utils/resume-hint.ts +86 -0
- package/src/version.ts +1 -0
- package/tsconfig.json +8 -0
- package/vitest.config.ts +35 -0
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mode Components Tests (T048)
|
|
3
|
+
*
|
|
4
|
+
* Tests for TUI mode components:
|
|
5
|
+
* - ModeIndicator: Mode display with icons and colors
|
|
6
|
+
* - ModeSelector: Interactive mode selection
|
|
7
|
+
* - PhaseProgressIndicator: Spec phase progress display
|
|
8
|
+
*
|
|
9
|
+
* @module tui/__tests__/mode-components.test
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { CodingMode } from "@vellum/core";
|
|
13
|
+
import { getIcons, type IconSet } from "@vellum/shared";
|
|
14
|
+
import { render } from "ink-testing-library";
|
|
15
|
+
import type React from "react";
|
|
16
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
17
|
+
import { ModeIndicator } from "../components/ModeIndicator.js";
|
|
18
|
+
import { ModeSelector } from "../components/ModeSelector.js";
|
|
19
|
+
import { PhaseProgressIndicator } from "../components/PhaseProgressIndicator.js";
|
|
20
|
+
import { ThemeProvider } from "../theme/index.js";
|
|
21
|
+
|
|
22
|
+
// Icons are fetched in beforeEach to ensure setup has run first
|
|
23
|
+
let icons: IconSet;
|
|
24
|
+
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
// Get icons after setup has configured Unicode mode
|
|
27
|
+
icons = getIcons();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// =============================================================================
|
|
31
|
+
// Test Helpers
|
|
32
|
+
// =============================================================================
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Wrapper to provide theme context for tests
|
|
36
|
+
*/
|
|
37
|
+
function renderWithTheme(element: React.ReactElement) {
|
|
38
|
+
return render(<ThemeProvider>{element}</ThemeProvider>);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// =============================================================================
|
|
42
|
+
// ModeIndicator Tests
|
|
43
|
+
// =============================================================================
|
|
44
|
+
|
|
45
|
+
describe("ModeIndicator", () => {
|
|
46
|
+
describe("Mode Icons", () => {
|
|
47
|
+
it("should render vibe mode with icon", () => {
|
|
48
|
+
const { lastFrame } = renderWithTheme(<ModeIndicator mode="vibe" />);
|
|
49
|
+
const frame = lastFrame() ?? "";
|
|
50
|
+
expect(frame).toContain(icons.vibe);
|
|
51
|
+
expect(frame).toContain("vibe");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("should render plan mode with icon", () => {
|
|
55
|
+
const { lastFrame } = renderWithTheme(<ModeIndicator mode="plan" />);
|
|
56
|
+
const frame = lastFrame() ?? "";
|
|
57
|
+
expect(frame).toContain(icons.plan);
|
|
58
|
+
expect(frame).toContain("plan");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("should render spec mode with icon", () => {
|
|
62
|
+
const { lastFrame } = renderWithTheme(<ModeIndicator mode="spec" />);
|
|
63
|
+
const frame = lastFrame() ?? "";
|
|
64
|
+
expect(frame).toContain(icons.spec);
|
|
65
|
+
expect(frame).toContain("spec");
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe("Spec Phase Display", () => {
|
|
70
|
+
it("should show phase progress when in spec mode", () => {
|
|
71
|
+
const { lastFrame } = renderWithTheme(<ModeIndicator mode="spec" specPhase={3} />);
|
|
72
|
+
const frame = lastFrame() ?? "";
|
|
73
|
+
expect(frame).toContain(icons.spec);
|
|
74
|
+
expect(frame).toContain("spec");
|
|
75
|
+
expect(frame).toContain("(3/6");
|
|
76
|
+
expect(frame).toContain("Design");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("should show phase 1 (Research) correctly", () => {
|
|
80
|
+
const { lastFrame } = renderWithTheme(<ModeIndicator mode="spec" specPhase={1} />);
|
|
81
|
+
const frame = lastFrame() ?? "";
|
|
82
|
+
expect(frame).toContain("(1/6");
|
|
83
|
+
expect(frame).toContain("Research");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("should show phase 6 (Validation) correctly", () => {
|
|
87
|
+
const { lastFrame } = renderWithTheme(<ModeIndicator mode="spec" specPhase={6} />);
|
|
88
|
+
const frame = lastFrame() ?? "";
|
|
89
|
+
expect(frame).toContain("(6/6");
|
|
90
|
+
expect(frame).toContain("Validation");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("should not show phase for non-spec modes", () => {
|
|
94
|
+
const { lastFrame } = renderWithTheme(<ModeIndicator mode="vibe" specPhase={3} />);
|
|
95
|
+
const frame = lastFrame() ?? "";
|
|
96
|
+
expect(frame).not.toContain("(3/6)");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("should clamp invalid phase numbers", () => {
|
|
100
|
+
const { lastFrame } = renderWithTheme(<ModeIndicator mode="spec" specPhase={10} />);
|
|
101
|
+
const frame = lastFrame() ?? "";
|
|
102
|
+
expect(frame).toContain("(6/6");
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe("Compact Mode", () => {
|
|
107
|
+
it("should show only icon in compact mode", () => {
|
|
108
|
+
const { lastFrame } = renderWithTheme(<ModeIndicator mode="vibe" compact />);
|
|
109
|
+
const frame = lastFrame() ?? "";
|
|
110
|
+
expect(frame).toContain(icons.vibe);
|
|
111
|
+
expect(frame).not.toContain("vibe ");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("should show phase numbers in compact spec mode", () => {
|
|
115
|
+
const { lastFrame } = renderWithTheme(<ModeIndicator mode="spec" specPhase={4} compact />);
|
|
116
|
+
const frame = lastFrame() ?? "";
|
|
117
|
+
expect(frame).toContain(icons.spec);
|
|
118
|
+
expect(frame).toContain("(4/6)");
|
|
119
|
+
// Should NOT show the phase name in compact mode
|
|
120
|
+
expect(frame).not.toContain("Tasks");
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// =============================================================================
|
|
126
|
+
// ModeSelector Tests
|
|
127
|
+
// =============================================================================
|
|
128
|
+
|
|
129
|
+
describe("ModeSelector", () => {
|
|
130
|
+
describe("Rendering", () => {
|
|
131
|
+
it("should render all three modes", () => {
|
|
132
|
+
const onSelect = vi.fn();
|
|
133
|
+
const { lastFrame } = renderWithTheme(
|
|
134
|
+
<ModeSelector currentMode="vibe" onSelect={onSelect} isActive={false} />
|
|
135
|
+
);
|
|
136
|
+
const frame = lastFrame() ?? "";
|
|
137
|
+
|
|
138
|
+
expect(frame).toContain(icons.vibe);
|
|
139
|
+
expect(frame).toContain("vibe");
|
|
140
|
+
expect(frame).toContain(icons.plan);
|
|
141
|
+
expect(frame).toContain("plan");
|
|
142
|
+
expect(frame).toContain(icons.spec);
|
|
143
|
+
expect(frame).toContain("spec");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("should show shortcut keys", () => {
|
|
147
|
+
const onSelect = vi.fn();
|
|
148
|
+
const { lastFrame } = renderWithTheme(
|
|
149
|
+
<ModeSelector currentMode="vibe" onSelect={onSelect} isActive={false} />
|
|
150
|
+
);
|
|
151
|
+
const frame = lastFrame() ?? "";
|
|
152
|
+
|
|
153
|
+
expect(frame).toContain("[1]");
|
|
154
|
+
expect(frame).toContain("[2]");
|
|
155
|
+
expect(frame).toContain("[3]");
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("should indicate current mode", () => {
|
|
159
|
+
const onSelect = vi.fn();
|
|
160
|
+
const { lastFrame } = renderWithTheme(
|
|
161
|
+
<ModeSelector currentMode="plan" onSelect={onSelect} isActive={false} />
|
|
162
|
+
);
|
|
163
|
+
const frame = lastFrame() ?? "";
|
|
164
|
+
|
|
165
|
+
// Translation key is used in tests - checks correct key is applied
|
|
166
|
+
expect(frame).toContain("modeSelector.current");
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("should show mode descriptions by default", () => {
|
|
170
|
+
const onSelect = vi.fn();
|
|
171
|
+
const { lastFrame } = renderWithTheme(
|
|
172
|
+
<ModeSelector currentMode="vibe" onSelect={onSelect} isActive={false} />
|
|
173
|
+
);
|
|
174
|
+
const frame = lastFrame() ?? "";
|
|
175
|
+
|
|
176
|
+
expect(frame).toContain("Fast autonomous coding");
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("should hide descriptions when showDescriptions is false", () => {
|
|
180
|
+
const onSelect = vi.fn();
|
|
181
|
+
const { lastFrame } = renderWithTheme(
|
|
182
|
+
<ModeSelector
|
|
183
|
+
currentMode="vibe"
|
|
184
|
+
onSelect={onSelect}
|
|
185
|
+
isActive={false}
|
|
186
|
+
showDescriptions={false}
|
|
187
|
+
/>
|
|
188
|
+
);
|
|
189
|
+
const frame = lastFrame() ?? "";
|
|
190
|
+
|
|
191
|
+
expect(frame).not.toContain("Fast autonomous coding");
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
describe("Keyboard Navigation", () => {
|
|
196
|
+
it("should show help text", () => {
|
|
197
|
+
const onSelect = vi.fn();
|
|
198
|
+
const { lastFrame } = renderWithTheme(
|
|
199
|
+
<ModeSelector currentMode="vibe" onSelect={onSelect} isActive />
|
|
200
|
+
);
|
|
201
|
+
const frame = lastFrame() ?? "";
|
|
202
|
+
|
|
203
|
+
// Translation key is used in tests - checks correct key is applied
|
|
204
|
+
expect(frame).toContain("modeSelector.keybindings");
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Note: Testing stdin/keyboard input with ink-testing-library is complex
|
|
208
|
+
// because useInput handlers are called asynchronously. The keyboard
|
|
209
|
+
// shortcuts (1, 2, 3) are tested implicitly through the component
|
|
210
|
+
// implementation and manual testing. The rendering tests above
|
|
211
|
+
// verify the UI elements are present.
|
|
212
|
+
|
|
213
|
+
it("should show focus indicator when active", () => {
|
|
214
|
+
const onSelect = vi.fn();
|
|
215
|
+
const { lastFrame } = renderWithTheme(
|
|
216
|
+
<ModeSelector currentMode="vibe" onSelect={onSelect} isActive />
|
|
217
|
+
);
|
|
218
|
+
const frame = lastFrame() ?? "";
|
|
219
|
+
|
|
220
|
+
// Focus indicator should be present
|
|
221
|
+
expect(frame).toContain("❯");
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("should not call onSelect when inactive", () => {
|
|
225
|
+
const onSelect = vi.fn();
|
|
226
|
+
renderWithTheme(<ModeSelector currentMode="vibe" onSelect={onSelect} isActive={false} />);
|
|
227
|
+
|
|
228
|
+
// With isActive=false, the selector should not process input
|
|
229
|
+
expect(onSelect).not.toHaveBeenCalled();
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// =============================================================================
|
|
235
|
+
// PhaseProgressIndicator Tests
|
|
236
|
+
// =============================================================================
|
|
237
|
+
|
|
238
|
+
describe("PhaseProgressIndicator", () => {
|
|
239
|
+
describe("Horizontal Progress Bar", () => {
|
|
240
|
+
it("should render progress segments", () => {
|
|
241
|
+
const { lastFrame } = renderWithTheme(<PhaseProgressIndicator currentPhase={3} />);
|
|
242
|
+
const frame = lastFrame() ?? "";
|
|
243
|
+
|
|
244
|
+
// Should contain progress bar characters
|
|
245
|
+
expect(frame).toContain("█"); // Completed
|
|
246
|
+
expect(frame).toContain("▓"); // Current
|
|
247
|
+
expect(frame).toContain("░"); // Pending
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it("should show first phase correctly", () => {
|
|
251
|
+
const { lastFrame } = renderWithTheme(<PhaseProgressIndicator currentPhase={1} showLabels />);
|
|
252
|
+
const frame = lastFrame() ?? "";
|
|
253
|
+
|
|
254
|
+
expect(frame).toContain("Research");
|
|
255
|
+
expect(frame).toContain("(1/6)");
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it("should show last phase correctly", () => {
|
|
259
|
+
const { lastFrame } = renderWithTheme(<PhaseProgressIndicator currentPhase={6} showLabels />);
|
|
260
|
+
const frame = lastFrame() ?? "";
|
|
261
|
+
|
|
262
|
+
expect(frame).toContain("Validation");
|
|
263
|
+
expect(frame).toContain("(6/6)");
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it("should show percentage when enabled", () => {
|
|
267
|
+
const { lastFrame } = renderWithTheme(
|
|
268
|
+
<PhaseProgressIndicator currentPhase={4} showPercentage />
|
|
269
|
+
);
|
|
270
|
+
const frame = lastFrame() ?? "";
|
|
271
|
+
|
|
272
|
+
// Phase 4 = 3 completed = 50%
|
|
273
|
+
expect(frame).toContain("50%");
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it("should show 0% for phase 1", () => {
|
|
277
|
+
const { lastFrame } = renderWithTheme(
|
|
278
|
+
<PhaseProgressIndicator currentPhase={1} showPercentage />
|
|
279
|
+
);
|
|
280
|
+
const frame = lastFrame() ?? "";
|
|
281
|
+
|
|
282
|
+
expect(frame).toContain("0%");
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it("should clamp phase to valid range", () => {
|
|
286
|
+
const { lastFrame } = renderWithTheme(
|
|
287
|
+
<PhaseProgressIndicator currentPhase={10} showLabels />
|
|
288
|
+
);
|
|
289
|
+
const frame = lastFrame() ?? "";
|
|
290
|
+
|
|
291
|
+
expect(frame).toContain("Validation");
|
|
292
|
+
expect(frame).toContain("(6/6)");
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
describe("Vertical Progress List", () => {
|
|
297
|
+
it("should render all phases in vertical mode", () => {
|
|
298
|
+
const { lastFrame } = renderWithTheme(
|
|
299
|
+
<PhaseProgressIndicator currentPhase={3} orientation="vertical" />
|
|
300
|
+
);
|
|
301
|
+
const frame = lastFrame() ?? "";
|
|
302
|
+
|
|
303
|
+
expect(frame).toContain("1. Research");
|
|
304
|
+
expect(frame).toContain("2. Requirements");
|
|
305
|
+
expect(frame).toContain("3. Design");
|
|
306
|
+
expect(frame).toContain("4. Tasks");
|
|
307
|
+
expect(frame).toContain("5. Implementation");
|
|
308
|
+
expect(frame).toContain("6. Validation");
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it("should show completed phases with checkmark", () => {
|
|
312
|
+
const { lastFrame } = renderWithTheme(
|
|
313
|
+
<PhaseProgressIndicator currentPhase={3} orientation="vertical" />
|
|
314
|
+
);
|
|
315
|
+
const frame = lastFrame() ?? "";
|
|
316
|
+
|
|
317
|
+
// Phases 1 and 2 should be completed
|
|
318
|
+
expect(frame).toContain(icons.check);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it("should show current phase with bullet", () => {
|
|
322
|
+
const { lastFrame } = renderWithTheme(
|
|
323
|
+
<PhaseProgressIndicator currentPhase={3} orientation="vertical" />
|
|
324
|
+
);
|
|
325
|
+
const frame = lastFrame() ?? "";
|
|
326
|
+
|
|
327
|
+
expect(frame).toContain(icons.bullet);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it("should show pending phases with circle", () => {
|
|
331
|
+
const { lastFrame } = renderWithTheme(
|
|
332
|
+
<PhaseProgressIndicator currentPhase={3} orientation="vertical" />
|
|
333
|
+
);
|
|
334
|
+
const frame = lastFrame() ?? "";
|
|
335
|
+
|
|
336
|
+
expect(frame).toContain(icons.pending);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it("should show progress summary with percentage", () => {
|
|
340
|
+
const { lastFrame } = renderWithTheme(
|
|
341
|
+
<PhaseProgressIndicator currentPhase={4} orientation="vertical" showPercentage />
|
|
342
|
+
);
|
|
343
|
+
const frame = lastFrame() ?? "";
|
|
344
|
+
|
|
345
|
+
expect(frame).toContain("Progress:");
|
|
346
|
+
expect(frame).toContain("50%");
|
|
347
|
+
expect(frame).toContain("3/6 completed");
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
describe("Custom Width", () => {
|
|
352
|
+
it("should respect custom width", () => {
|
|
353
|
+
const { lastFrame } = renderWithTheme(<PhaseProgressIndicator currentPhase={3} width={12} />);
|
|
354
|
+
const frame = lastFrame() ?? "";
|
|
355
|
+
|
|
356
|
+
// Width 12 / 6 phases = 2 chars per segment
|
|
357
|
+
// Just verify it renders without errors
|
|
358
|
+
expect(frame).toBeTruthy();
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
// =============================================================================
|
|
364
|
+
// Accessibility Tests
|
|
365
|
+
// =============================================================================
|
|
366
|
+
|
|
367
|
+
describe("Accessibility", () => {
|
|
368
|
+
describe("ModeIndicator", () => {
|
|
369
|
+
it("should use semantic colors for modes", () => {
|
|
370
|
+
// Each mode should have a distinct color
|
|
371
|
+
const modes: CodingMode[] = ["vibe", "plan", "spec"];
|
|
372
|
+
|
|
373
|
+
for (const mode of modes) {
|
|
374
|
+
const { lastFrame } = renderWithTheme(<ModeIndicator mode={mode} />);
|
|
375
|
+
const frame = lastFrame() ?? "";
|
|
376
|
+
// Just verify it renders without errors
|
|
377
|
+
expect(frame).toBeTruthy();
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
describe("PhaseProgressIndicator", () => {
|
|
383
|
+
it("should provide textual alternatives in vertical mode", () => {
|
|
384
|
+
const { lastFrame } = renderWithTheme(
|
|
385
|
+
<PhaseProgressIndicator currentPhase={3} orientation="vertical" showPercentage />
|
|
386
|
+
);
|
|
387
|
+
const frame = lastFrame() ?? "";
|
|
388
|
+
|
|
389
|
+
// Should have textual descriptions
|
|
390
|
+
expect(frame).toContain("Research");
|
|
391
|
+
expect(frame).toContain("Requirements");
|
|
392
|
+
expect(frame).toContain("Design");
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
});
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Permission Ask Flow Integration Tests
|
|
3
|
+
*
|
|
4
|
+
* Verifies that the core permission "ask" flow can be driven by the TUI layer:
|
|
5
|
+
* - A permission prompt creates a pending tool execution in ToolsContext
|
|
6
|
+
* - PermissionDialog can approve (once/always) or reject
|
|
7
|
+
* - Approving resolves the underlying ask promise (allowing execution to resume)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { AskContext, PermissionInfo } from "@vellum/core";
|
|
11
|
+
import { Text } from "ink";
|
|
12
|
+
import { render } from "ink-testing-library";
|
|
13
|
+
import { act, useEffect, useRef, useState } from "react";
|
|
14
|
+
import { describe, expect, it } from "vitest";
|
|
15
|
+
import { PermissionDialog, RootProvider, useTools } from "../index.js";
|
|
16
|
+
|
|
17
|
+
describe("Permission ask flow", () => {
|
|
18
|
+
it("shows PermissionDialog for an ask decision and resumes execution on approve", async () => {
|
|
19
|
+
function TestComponent() {
|
|
20
|
+
const { pendingApproval, permissionAskHandler, respondToPermissionRequest } = useTools();
|
|
21
|
+
|
|
22
|
+
const [response, setResponse] = useState<string | null>(null);
|
|
23
|
+
const startedRef = useRef(false);
|
|
24
|
+
const approvedRef = useRef(false);
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
let cancelled = false;
|
|
28
|
+
|
|
29
|
+
if (startedRef.current) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
startedRef.current = true;
|
|
33
|
+
|
|
34
|
+
void (async () => {
|
|
35
|
+
const abortController = new AbortController();
|
|
36
|
+
|
|
37
|
+
const createdAt = Date.now();
|
|
38
|
+
|
|
39
|
+
const info: PermissionInfo = {
|
|
40
|
+
id: "perm-1",
|
|
41
|
+
type: "tool",
|
|
42
|
+
sessionId: "sess-1",
|
|
43
|
+
messageId: "msg-1",
|
|
44
|
+
title: "Allow shell?",
|
|
45
|
+
callId: "call-1",
|
|
46
|
+
metadata: {
|
|
47
|
+
toolName: "shell",
|
|
48
|
+
params: {
|
|
49
|
+
command: "echo hi",
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
time: {
|
|
53
|
+
created: createdAt,
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const ctx: AskContext = {
|
|
58
|
+
timeoutMs: 5_000,
|
|
59
|
+
signal: abortController.signal,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const result = await permissionAskHandler(info, ctx);
|
|
63
|
+
|
|
64
|
+
if (cancelled) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
setResponse(result ?? null);
|
|
69
|
+
})();
|
|
70
|
+
|
|
71
|
+
return () => {
|
|
72
|
+
cancelled = true;
|
|
73
|
+
};
|
|
74
|
+
}, [permissionAskHandler]);
|
|
75
|
+
|
|
76
|
+
const pending = pendingApproval[0];
|
|
77
|
+
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (!pending) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (approvedRef.current) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
approvedRef.current = true;
|
|
88
|
+
|
|
89
|
+
// Delay approval so the dialog has time to render and be asserted against.
|
|
90
|
+
const timer = setTimeout(() => {
|
|
91
|
+
respondToPermissionRequest(pending.id, "once");
|
|
92
|
+
}, 100);
|
|
93
|
+
|
|
94
|
+
return () => {
|
|
95
|
+
clearTimeout(timer);
|
|
96
|
+
};
|
|
97
|
+
}, [pending, respondToPermissionRequest]);
|
|
98
|
+
|
|
99
|
+
if (pending) {
|
|
100
|
+
return (
|
|
101
|
+
<PermissionDialog
|
|
102
|
+
execution={pending}
|
|
103
|
+
riskLevel="high"
|
|
104
|
+
onApprove={() => respondToPermissionRequest(pending.id, "once")}
|
|
105
|
+
onApproveAlways={() => respondToPermissionRequest(pending.id, "always")}
|
|
106
|
+
onReject={() => respondToPermissionRequest(pending.id, "reject")}
|
|
107
|
+
/>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return <Text>{response ? `response: ${response}` : "waiting"}</Text>;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const { lastFrame } = render(
|
|
115
|
+
<RootProvider theme="dark">
|
|
116
|
+
<TestComponent />
|
|
117
|
+
</RootProvider>
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
// Let the permission prompt surface (before auto-approve fires).
|
|
121
|
+
await act(async () => {
|
|
122
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const before = lastFrame() ?? "";
|
|
126
|
+
expect(before).toContain("shell");
|
|
127
|
+
expect(before).toContain("High Risk");
|
|
128
|
+
expect(before).toContain("echo hi");
|
|
129
|
+
|
|
130
|
+
// Let approval + ask resolution complete.
|
|
131
|
+
await act(async () => {
|
|
132
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const after = lastFrame() ?? "";
|
|
136
|
+
expect(after).toContain("response: once");
|
|
137
|
+
}, 10_000);
|
|
138
|
+
});
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sidebar panel data refresh tests
|
|
3
|
+
*
|
|
4
|
+
* Verifies the data loader hook that backs MemoryPanel and TodoPanel:
|
|
5
|
+
* - initial load
|
|
6
|
+
* - refresh on panel open
|
|
7
|
+
* - refresh after relevant tool execution completion
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { MemoryEntry } from "@vellum/core";
|
|
11
|
+
import { Text } from "ink";
|
|
12
|
+
import { render } from "ink-testing-library";
|
|
13
|
+
import type React from "react";
|
|
14
|
+
import { describe, expect, it, vi } from "vitest";
|
|
15
|
+
import type { TodoItemData } from "../components/TodoItem.js";
|
|
16
|
+
import type { ToolExecution } from "../context/ToolsContext.js";
|
|
17
|
+
import {
|
|
18
|
+
getLastCompletedToolExecutionId,
|
|
19
|
+
shouldRefreshFromToolExecution,
|
|
20
|
+
useSidebarPanelData,
|
|
21
|
+
} from "../hooks/useSidebarPanelData.js";
|
|
22
|
+
|
|
23
|
+
function Harness(props: {
|
|
24
|
+
readonly sidebarVisible: boolean;
|
|
25
|
+
readonly sidebarContent: "todo" | "memory" | "tools" | "mcp";
|
|
26
|
+
readonly executions: readonly ToolExecution[];
|
|
27
|
+
readonly loadTodos: () => Promise<readonly TodoItemData[]>;
|
|
28
|
+
readonly loadMemories: () => Promise<readonly MemoryEntry[]>;
|
|
29
|
+
}): React.JSX.Element {
|
|
30
|
+
const { todoItems, memoryEntries } = useSidebarPanelData({
|
|
31
|
+
sidebarVisible: props.sidebarVisible,
|
|
32
|
+
sidebarContent: props.sidebarContent,
|
|
33
|
+
executions: props.executions,
|
|
34
|
+
loadTodos: props.loadTodos,
|
|
35
|
+
loadMemories: props.loadMemories,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
return <Text>{`todos:${todoItems.length} memories:${memoryEntries.length}`}</Text>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
describe("sidebar panel data", () => {
|
|
42
|
+
it("should identify the last completed tool execution id", () => {
|
|
43
|
+
const executions: ToolExecution[] = [
|
|
44
|
+
{ id: "1", toolName: "read_file", params: {}, status: "running" },
|
|
45
|
+
{ id: "2", toolName: "todo_manage", params: {}, status: "complete" },
|
|
46
|
+
{ id: "3", toolName: "save_memory", params: {}, status: "pending" },
|
|
47
|
+
{ id: "4", toolName: "todo_manage", params: {}, status: "complete" },
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
expect(getLastCompletedToolExecutionId(executions)).toBe("4");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("should decide which panels to refresh from a tool execution", () => {
|
|
54
|
+
const todoExec: ToolExecution = {
|
|
55
|
+
id: "1",
|
|
56
|
+
toolName: "todo_manage",
|
|
57
|
+
params: {},
|
|
58
|
+
status: "complete",
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const memoryExec: ToolExecution = {
|
|
62
|
+
id: "2",
|
|
63
|
+
toolName: "save_memory",
|
|
64
|
+
params: {},
|
|
65
|
+
status: "complete",
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
expect(shouldRefreshFromToolExecution(todoExec)).toEqual({
|
|
69
|
+
refreshTodos: true,
|
|
70
|
+
refreshMemories: false,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
expect(shouldRefreshFromToolExecution(memoryExec)).toEqual({
|
|
74
|
+
refreshTodos: false,
|
|
75
|
+
refreshMemories: true,
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("should refresh on panel open and after relevant tool completion", async () => {
|
|
80
|
+
const loadTodos = vi.fn<() => Promise<readonly TodoItemData[]>>().mockResolvedValue([
|
|
81
|
+
{
|
|
82
|
+
id: 1,
|
|
83
|
+
title: "Task A",
|
|
84
|
+
status: "pending",
|
|
85
|
+
createdAt: new Date().toISOString(),
|
|
86
|
+
},
|
|
87
|
+
]);
|
|
88
|
+
|
|
89
|
+
const loadMemories = vi.fn<() => Promise<readonly MemoryEntry[]>>().mockResolvedValue([
|
|
90
|
+
{
|
|
91
|
+
key: "k1",
|
|
92
|
+
type: "context",
|
|
93
|
+
content: "c1",
|
|
94
|
+
createdAt: new Date("2024-01-01T00:00:00.000Z"),
|
|
95
|
+
updatedAt: new Date("2024-01-01T00:00:00.000Z"),
|
|
96
|
+
metadata: { tags: [], importance: 0.5 },
|
|
97
|
+
},
|
|
98
|
+
]);
|
|
99
|
+
|
|
100
|
+
const { lastFrame, rerender } = render(
|
|
101
|
+
<Harness
|
|
102
|
+
sidebarVisible={false}
|
|
103
|
+
sidebarContent="tools"
|
|
104
|
+
executions={[]}
|
|
105
|
+
loadTodos={loadTodos}
|
|
106
|
+
loadMemories={loadMemories}
|
|
107
|
+
/>
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
// Initial load is best-effort; allow microtasks to flush.
|
|
111
|
+
await Promise.resolve();
|
|
112
|
+
|
|
113
|
+
expect(loadTodos).toHaveBeenCalledTimes(1);
|
|
114
|
+
expect(loadMemories).toHaveBeenCalledTimes(1);
|
|
115
|
+
|
|
116
|
+
// Opening the todo panel should trigger a refresh.
|
|
117
|
+
rerender(
|
|
118
|
+
<Harness
|
|
119
|
+
sidebarVisible={true}
|
|
120
|
+
sidebarContent="todo"
|
|
121
|
+
executions={[]}
|
|
122
|
+
loadTodos={loadTodos}
|
|
123
|
+
loadMemories={loadMemories}
|
|
124
|
+
/>
|
|
125
|
+
);
|
|
126
|
+
await Promise.resolve();
|
|
127
|
+
expect(loadTodos).toHaveBeenCalledTimes(2);
|
|
128
|
+
|
|
129
|
+
// Completing todo_manage should trigger a refresh.
|
|
130
|
+
rerender(
|
|
131
|
+
<Harness
|
|
132
|
+
sidebarVisible={true}
|
|
133
|
+
sidebarContent="todo"
|
|
134
|
+
executions={[{ id: "e1", toolName: "todo_manage", params: {}, status: "complete" }]}
|
|
135
|
+
loadTodos={loadTodos}
|
|
136
|
+
loadMemories={loadMemories}
|
|
137
|
+
/>
|
|
138
|
+
);
|
|
139
|
+
await Promise.resolve();
|
|
140
|
+
|
|
141
|
+
expect(loadTodos).toHaveBeenCalledTimes(3);
|
|
142
|
+
|
|
143
|
+
// Should have rendered counts at least once.
|
|
144
|
+
const frame = lastFrame() ?? "";
|
|
145
|
+
expect(frame).toContain("todos:");
|
|
146
|
+
expect(frame).toContain("memories:");
|
|
147
|
+
});
|
|
148
|
+
});
|