@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,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Overflow Context
|
|
3
|
+
*
|
|
4
|
+
* Tracks which components are currently overflowing their containers.
|
|
5
|
+
* This enables smart truncation and "Show More" functionality.
|
|
6
|
+
*
|
|
7
|
+
* Ported from Gemini CLI for Vellum TUI.
|
|
8
|
+
*
|
|
9
|
+
* @module tui/context/OverflowContext
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import React, {
|
|
13
|
+
createContext,
|
|
14
|
+
type ReactNode,
|
|
15
|
+
useCallback,
|
|
16
|
+
useContext,
|
|
17
|
+
useMemo,
|
|
18
|
+
useState,
|
|
19
|
+
} from "react";
|
|
20
|
+
|
|
21
|
+
// =============================================================================
|
|
22
|
+
// Types
|
|
23
|
+
// =============================================================================
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* State tracking which component IDs are currently overflowing
|
|
27
|
+
*/
|
|
28
|
+
export interface OverflowState {
|
|
29
|
+
/** Set of component IDs that are currently overflowing */
|
|
30
|
+
readonly overflowingIds: ReadonlySet<string>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Context value providing overflow state and management functions
|
|
35
|
+
*/
|
|
36
|
+
export interface OverflowContextValue {
|
|
37
|
+
/** Current overflow state */
|
|
38
|
+
readonly state: OverflowState;
|
|
39
|
+
/**
|
|
40
|
+
* Register a component as overflowing
|
|
41
|
+
* @param id - Unique identifier for the component
|
|
42
|
+
*/
|
|
43
|
+
readonly registerOverflow: (id: string) => void;
|
|
44
|
+
/**
|
|
45
|
+
* Unregister a component from the overflow set
|
|
46
|
+
* @param id - Unique identifier for the component
|
|
47
|
+
*/
|
|
48
|
+
readonly unregisterOverflow: (id: string) => void;
|
|
49
|
+
/**
|
|
50
|
+
* Check if a specific component is overflowing
|
|
51
|
+
* @param id - Unique identifier for the component
|
|
52
|
+
* @returns true if the component is currently overflowing
|
|
53
|
+
*/
|
|
54
|
+
readonly isOverflowing: (id: string) => boolean;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Props for the OverflowProvider component
|
|
59
|
+
*/
|
|
60
|
+
export interface OverflowProviderProps {
|
|
61
|
+
/** Children to render within the overflow context */
|
|
62
|
+
readonly children: ReactNode;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// =============================================================================
|
|
66
|
+
// Context
|
|
67
|
+
// =============================================================================
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Initial overflow state
|
|
71
|
+
*/
|
|
72
|
+
const initialState: OverflowState = {
|
|
73
|
+
overflowingIds: new Set<string>(),
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* React context for overflow state management
|
|
78
|
+
*/
|
|
79
|
+
export const OverflowContext = createContext<OverflowContextValue | null>(null);
|
|
80
|
+
|
|
81
|
+
// Set display name for debugging
|
|
82
|
+
OverflowContext.displayName = "OverflowContext";
|
|
83
|
+
|
|
84
|
+
// =============================================================================
|
|
85
|
+
// Provider Component
|
|
86
|
+
// =============================================================================
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* OverflowProvider manages tracking of overflowing components.
|
|
90
|
+
*
|
|
91
|
+
* Components can register themselves as overflowing when their content
|
|
92
|
+
* exceeds their container bounds, enabling parent components to respond
|
|
93
|
+
* appropriately (e.g., showing truncation indicators or "Show More" buttons).
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```tsx
|
|
97
|
+
* <OverflowProvider>
|
|
98
|
+
* <App />
|
|
99
|
+
* </OverflowProvider>
|
|
100
|
+
*
|
|
101
|
+
* // In a child component:
|
|
102
|
+
* const { registerOverflow, unregisterOverflow, isOverflowing } = useOverflow();
|
|
103
|
+
*
|
|
104
|
+
* useEffect(() => {
|
|
105
|
+
* if (contentHeight > containerHeight) {
|
|
106
|
+
* registerOverflow('my-component-id');
|
|
107
|
+
* } else {
|
|
108
|
+
* unregisterOverflow('my-component-id');
|
|
109
|
+
* }
|
|
110
|
+
* }, [contentHeight, containerHeight]);
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
export function OverflowProvider({ children }: OverflowProviderProps): React.JSX.Element {
|
|
114
|
+
const [overflowingIds, setOverflowingIds] = useState<ReadonlySet<string>>(
|
|
115
|
+
initialState.overflowingIds
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Register a component as overflowing
|
|
120
|
+
*/
|
|
121
|
+
const registerOverflow = useCallback((id: string) => {
|
|
122
|
+
setOverflowingIds((prev) => {
|
|
123
|
+
if (prev.has(id)) {
|
|
124
|
+
return prev;
|
|
125
|
+
}
|
|
126
|
+
const next = new Set(prev);
|
|
127
|
+
next.add(id);
|
|
128
|
+
return next;
|
|
129
|
+
});
|
|
130
|
+
}, []);
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Unregister a component from the overflow set
|
|
134
|
+
*/
|
|
135
|
+
const unregisterOverflow = useCallback((id: string) => {
|
|
136
|
+
setOverflowingIds((prev) => {
|
|
137
|
+
if (!prev.has(id)) {
|
|
138
|
+
return prev;
|
|
139
|
+
}
|
|
140
|
+
const next = new Set(prev);
|
|
141
|
+
next.delete(id);
|
|
142
|
+
return next;
|
|
143
|
+
});
|
|
144
|
+
}, []);
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Check if a specific component is overflowing
|
|
148
|
+
*/
|
|
149
|
+
const isOverflowing = useCallback(
|
|
150
|
+
(id: string): boolean => {
|
|
151
|
+
return overflowingIds.has(id);
|
|
152
|
+
},
|
|
153
|
+
[overflowingIds]
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Memoized context value to prevent unnecessary re-renders
|
|
158
|
+
*/
|
|
159
|
+
const contextValue = useMemo<OverflowContextValue>(
|
|
160
|
+
() => ({
|
|
161
|
+
state: { overflowingIds },
|
|
162
|
+
registerOverflow,
|
|
163
|
+
unregisterOverflow,
|
|
164
|
+
isOverflowing,
|
|
165
|
+
}),
|
|
166
|
+
[overflowingIds, registerOverflow, unregisterOverflow, isOverflowing]
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
return <OverflowContext.Provider value={contextValue}>{children}</OverflowContext.Provider>;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// =============================================================================
|
|
173
|
+
// Hook
|
|
174
|
+
// =============================================================================
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Hook to access the overflow context.
|
|
178
|
+
*
|
|
179
|
+
* @returns The overflow context value
|
|
180
|
+
* @throws Error if used outside of an OverflowProvider
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* ```tsx
|
|
184
|
+
* function MyComponent() {
|
|
185
|
+
* const { registerOverflow, isOverflowing } = useOverflow();
|
|
186
|
+
*
|
|
187
|
+
* if (isOverflowing('my-id')) {
|
|
188
|
+
* return <Text>Content is overflowing!</Text>;
|
|
189
|
+
* }
|
|
190
|
+
*
|
|
191
|
+
* return <Text>Content fits</Text>;
|
|
192
|
+
* }
|
|
193
|
+
* ```
|
|
194
|
+
*/
|
|
195
|
+
export function useOverflow(): OverflowContextValue {
|
|
196
|
+
const context = useContext(OverflowContext);
|
|
197
|
+
|
|
198
|
+
if (context === null) {
|
|
199
|
+
throw new Error("useOverflow must be used within an OverflowProvider");
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return context;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Optional hook that returns null instead of throwing when used outside provider.
|
|
207
|
+
* Useful for components that may be used with or without overflow tracking.
|
|
208
|
+
*
|
|
209
|
+
* @returns The overflow context value or null if not within a provider
|
|
210
|
+
*/
|
|
211
|
+
export function useOverflowOptional(): OverflowContextValue | null {
|
|
212
|
+
return useContext(OverflowContext);
|
|
213
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate Limit Context
|
|
3
|
+
*
|
|
4
|
+
* Provides rate limiting state and notifications for the TUI.
|
|
5
|
+
*
|
|
6
|
+
* @module tui/context/RateLimitContext
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { EventBus, RateLimiterConfig } from "@vellum/core";
|
|
10
|
+
import type { JSX } from "react";
|
|
11
|
+
import { createContext, type ReactNode, useCallback, useContext, useMemo, useState } from "react";
|
|
12
|
+
// =============================================================================
|
|
13
|
+
// Types
|
|
14
|
+
// =============================================================================
|
|
15
|
+
|
|
16
|
+
export interface RateLimitState {
|
|
17
|
+
/** Whether rate limiting is currently active */
|
|
18
|
+
isRateLimited: boolean;
|
|
19
|
+
/** Current retry delay in milliseconds */
|
|
20
|
+
retryAfterMs?: number;
|
|
21
|
+
/** Number of retries performed */
|
|
22
|
+
retryCount: number;
|
|
23
|
+
/** Last rate limit error message */
|
|
24
|
+
lastError?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface RateLimitContextValue {
|
|
28
|
+
/** Current rate limit state */
|
|
29
|
+
state: RateLimitState;
|
|
30
|
+
/** Set rate limited state */
|
|
31
|
+
setRateLimited: (limited: boolean, retryAfterMs?: number) => void;
|
|
32
|
+
/** Increment retry count */
|
|
33
|
+
incrementRetry: () => void;
|
|
34
|
+
/** Reset rate limit state */
|
|
35
|
+
reset: () => void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// =============================================================================
|
|
39
|
+
// Context
|
|
40
|
+
// =============================================================================
|
|
41
|
+
|
|
42
|
+
const defaultState: RateLimitState = {
|
|
43
|
+
isRateLimited: false,
|
|
44
|
+
retryCount: 0,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const RateLimitContext = createContext<RateLimitContextValue | null>(null);
|
|
48
|
+
|
|
49
|
+
// =============================================================================
|
|
50
|
+
// Provider
|
|
51
|
+
// =============================================================================
|
|
52
|
+
|
|
53
|
+
export interface RateLimitProviderProps {
|
|
54
|
+
children: ReactNode;
|
|
55
|
+
config?: RateLimiterConfig;
|
|
56
|
+
eventBus?: EventBus;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function RateLimitProvider({
|
|
60
|
+
children,
|
|
61
|
+
config: _config,
|
|
62
|
+
eventBus: _eventBus,
|
|
63
|
+
}: RateLimitProviderProps): JSX.Element {
|
|
64
|
+
const [state, setState] = useState<RateLimitState>(defaultState);
|
|
65
|
+
|
|
66
|
+
const setRateLimited = useCallback((limited: boolean, retryAfterMs?: number) => {
|
|
67
|
+
setState((prev) => ({
|
|
68
|
+
...prev,
|
|
69
|
+
isRateLimited: limited,
|
|
70
|
+
retryAfterMs: limited ? retryAfterMs : undefined,
|
|
71
|
+
}));
|
|
72
|
+
}, []);
|
|
73
|
+
|
|
74
|
+
const incrementRetry = useCallback(() => {
|
|
75
|
+
setState((prev) => ({
|
|
76
|
+
...prev,
|
|
77
|
+
retryCount: prev.retryCount + 1,
|
|
78
|
+
}));
|
|
79
|
+
}, []);
|
|
80
|
+
|
|
81
|
+
const reset = useCallback(() => {
|
|
82
|
+
setState(defaultState);
|
|
83
|
+
}, []);
|
|
84
|
+
|
|
85
|
+
const value = useMemo<RateLimitContextValue>(
|
|
86
|
+
() => ({
|
|
87
|
+
state,
|
|
88
|
+
setRateLimited,
|
|
89
|
+
incrementRetry,
|
|
90
|
+
reset,
|
|
91
|
+
}),
|
|
92
|
+
[state, setRateLimited, incrementRetry, reset]
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
return <RateLimitContext.Provider value={value}>{children}</RateLimitContext.Provider>;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// =============================================================================
|
|
99
|
+
// Hook
|
|
100
|
+
// =============================================================================
|
|
101
|
+
|
|
102
|
+
export function useRateLimit(): RateLimitContextValue {
|
|
103
|
+
const context = useContext(RateLimitContext);
|
|
104
|
+
if (!context) {
|
|
105
|
+
throw new Error("useRateLimit must be used within a RateLimitProvider");
|
|
106
|
+
}
|
|
107
|
+
return context;
|
|
108
|
+
}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resilience Context
|
|
3
|
+
*
|
|
4
|
+
* React context for subscribing to resilience events (rate limiting, retry)
|
|
5
|
+
* and displaying feedback in the TUI.
|
|
6
|
+
*
|
|
7
|
+
* @module tui/context/ResilienceContext
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
getResilienceEventBus,
|
|
12
|
+
type RateLimitThrottleEvent,
|
|
13
|
+
type ResilienceEventBus,
|
|
14
|
+
type RetryAttemptEvent,
|
|
15
|
+
} from "@vellum/core";
|
|
16
|
+
import React, {
|
|
17
|
+
createContext,
|
|
18
|
+
type ReactNode,
|
|
19
|
+
useCallback,
|
|
20
|
+
useContext,
|
|
21
|
+
useEffect,
|
|
22
|
+
useMemo,
|
|
23
|
+
useState,
|
|
24
|
+
} from "react";
|
|
25
|
+
|
|
26
|
+
// =============================================================================
|
|
27
|
+
// Types
|
|
28
|
+
// =============================================================================
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Active resilience status to display in UI
|
|
32
|
+
*/
|
|
33
|
+
export interface ResilienceStatus {
|
|
34
|
+
/** Type of resilience event */
|
|
35
|
+
readonly type: "rate-limit" | "retry" | "idle";
|
|
36
|
+
/** Status message to display */
|
|
37
|
+
readonly message: string;
|
|
38
|
+
/** Wait time in seconds (if applicable) */
|
|
39
|
+
readonly waitSeconds?: number;
|
|
40
|
+
/** Current attempt (for retry) */
|
|
41
|
+
readonly attempt?: number;
|
|
42
|
+
/** Max attempts (for retry) */
|
|
43
|
+
readonly maxAttempts?: number;
|
|
44
|
+
/** Timestamp when status was set */
|
|
45
|
+
readonly timestamp: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Resilience context state
|
|
50
|
+
*/
|
|
51
|
+
export interface ResilienceContextState {
|
|
52
|
+
/** Current resilience status */
|
|
53
|
+
readonly status: ResilienceStatus;
|
|
54
|
+
/** Whether feedback is enabled */
|
|
55
|
+
readonly feedbackEnabled: boolean;
|
|
56
|
+
/** Enable/disable feedback */
|
|
57
|
+
readonly setFeedbackEnabled: (enabled: boolean) => void;
|
|
58
|
+
/** Clear current status */
|
|
59
|
+
readonly clearStatus: () => void;
|
|
60
|
+
/** Event bus instance (for advanced usage) */
|
|
61
|
+
readonly eventBus: ResilienceEventBus;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Props for ResilienceProvider
|
|
66
|
+
*/
|
|
67
|
+
export interface ResilienceProviderProps {
|
|
68
|
+
/** Child components */
|
|
69
|
+
readonly children: ReactNode;
|
|
70
|
+
/** Whether feedback is initially enabled (default: true) */
|
|
71
|
+
readonly initialEnabled?: boolean;
|
|
72
|
+
/** Custom event bus instance (optional, uses global by default) */
|
|
73
|
+
readonly eventBus?: ResilienceEventBus;
|
|
74
|
+
/** Auto-clear timeout in milliseconds (default: 5000) */
|
|
75
|
+
readonly autoClearMs?: number;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// =============================================================================
|
|
79
|
+
// Context
|
|
80
|
+
// =============================================================================
|
|
81
|
+
|
|
82
|
+
const ResilienceContext = createContext<ResilienceContextState | null>(null);
|
|
83
|
+
|
|
84
|
+
// =============================================================================
|
|
85
|
+
// Constants
|
|
86
|
+
// =============================================================================
|
|
87
|
+
|
|
88
|
+
/** Default idle status */
|
|
89
|
+
const IDLE_STATUS: ResilienceStatus = {
|
|
90
|
+
type: "idle",
|
|
91
|
+
message: "",
|
|
92
|
+
timestamp: 0,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/** Default auto-clear timeout */
|
|
96
|
+
const DEFAULT_AUTO_CLEAR_MS = 5000;
|
|
97
|
+
|
|
98
|
+
// =============================================================================
|
|
99
|
+
// Provider Component
|
|
100
|
+
// =============================================================================
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Resilience provider component.
|
|
104
|
+
*
|
|
105
|
+
* Subscribes to resilience events and provides status to child components.
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```tsx
|
|
109
|
+
* <ResilienceProvider>
|
|
110
|
+
* <App />
|
|
111
|
+
* </ResilienceProvider>
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
export function ResilienceProvider({
|
|
115
|
+
children,
|
|
116
|
+
initialEnabled = true,
|
|
117
|
+
eventBus: customEventBus,
|
|
118
|
+
autoClearMs = DEFAULT_AUTO_CLEAR_MS,
|
|
119
|
+
}: ResilienceProviderProps): React.JSX.Element {
|
|
120
|
+
const [status, setStatus] = useState<ResilienceStatus>(IDLE_STATUS);
|
|
121
|
+
const [feedbackEnabled, setFeedbackEnabled] = useState(initialEnabled);
|
|
122
|
+
|
|
123
|
+
// Get event bus (custom or global)
|
|
124
|
+
const eventBus = useMemo(() => customEventBus ?? getResilienceEventBus(), [customEventBus]);
|
|
125
|
+
|
|
126
|
+
// Clear status function
|
|
127
|
+
const clearStatus = useCallback(() => {
|
|
128
|
+
setStatus(IDLE_STATUS);
|
|
129
|
+
}, []);
|
|
130
|
+
|
|
131
|
+
// Auto-clear timer
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
if (status.type === "idle" || !feedbackEnabled) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const timer = setTimeout(() => {
|
|
138
|
+
setStatus(IDLE_STATUS);
|
|
139
|
+
}, autoClearMs);
|
|
140
|
+
|
|
141
|
+
return () => clearTimeout(timer);
|
|
142
|
+
}, [status, feedbackEnabled, autoClearMs]);
|
|
143
|
+
|
|
144
|
+
// Subscribe to rate limit events
|
|
145
|
+
useEffect(() => {
|
|
146
|
+
if (!feedbackEnabled) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const unsubThrottle = eventBus.on("rateLimitThrottle", (event: RateLimitThrottleEvent) => {
|
|
151
|
+
const waitSeconds = Math.ceil(event.waitTimeMs / 1000);
|
|
152
|
+
setStatus({
|
|
153
|
+
type: "rate-limit",
|
|
154
|
+
message: `Rate limited, waiting ${waitSeconds}s...`,
|
|
155
|
+
waitSeconds,
|
|
156
|
+
timestamp: event.timestamp,
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
const unsubExceeded = eventBus.on("rateLimitExceeded", (event) => {
|
|
161
|
+
const waitSeconds = Math.ceil(event.waitTimeMs / 1000);
|
|
162
|
+
const reason = event.reason === "max_wait" ? "max wait exceeded" : "limit exceeded";
|
|
163
|
+
setStatus({
|
|
164
|
+
type: "rate-limit",
|
|
165
|
+
message: `Rate limit ${reason} (${waitSeconds}s)`,
|
|
166
|
+
waitSeconds,
|
|
167
|
+
timestamp: event.timestamp,
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
return () => {
|
|
172
|
+
unsubThrottle();
|
|
173
|
+
unsubExceeded();
|
|
174
|
+
};
|
|
175
|
+
}, [eventBus, feedbackEnabled]);
|
|
176
|
+
|
|
177
|
+
// Subscribe to retry events
|
|
178
|
+
useEffect(() => {
|
|
179
|
+
if (!feedbackEnabled) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const unsubAttempt = eventBus.on("retryAttempt", (event: RetryAttemptEvent) => {
|
|
184
|
+
const waitSeconds = Math.ceil(event.delayMs / 1000);
|
|
185
|
+
setStatus({
|
|
186
|
+
type: "retry",
|
|
187
|
+
message: `Retry ${event.attempt}/${event.maxAttempts}, waiting ${waitSeconds}s...`,
|
|
188
|
+
waitSeconds,
|
|
189
|
+
attempt: event.attempt,
|
|
190
|
+
maxAttempts: event.maxAttempts,
|
|
191
|
+
timestamp: event.timestamp,
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
const unsubCompleted = eventBus.on("retryCompleted", (event) => {
|
|
196
|
+
if (event.success) {
|
|
197
|
+
// Clear on success
|
|
198
|
+
setStatus(IDLE_STATUS);
|
|
199
|
+
} else {
|
|
200
|
+
// Show failure briefly
|
|
201
|
+
setStatus({
|
|
202
|
+
type: "retry",
|
|
203
|
+
message: `Retry failed after ${event.totalAttempts} attempts`,
|
|
204
|
+
timestamp: event.timestamp,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
return () => {
|
|
210
|
+
unsubAttempt();
|
|
211
|
+
unsubCompleted();
|
|
212
|
+
};
|
|
213
|
+
}, [eventBus, feedbackEnabled]);
|
|
214
|
+
|
|
215
|
+
// Build context value
|
|
216
|
+
const value = useMemo<ResilienceContextState>(
|
|
217
|
+
() => ({
|
|
218
|
+
status,
|
|
219
|
+
feedbackEnabled,
|
|
220
|
+
setFeedbackEnabled,
|
|
221
|
+
clearStatus,
|
|
222
|
+
eventBus,
|
|
223
|
+
}),
|
|
224
|
+
[status, feedbackEnabled, clearStatus, eventBus]
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
return <ResilienceContext.Provider value={value}>{children}</ResilienceContext.Provider>;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// =============================================================================
|
|
231
|
+
// Hooks
|
|
232
|
+
// =============================================================================
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Hook to access resilience context.
|
|
236
|
+
*
|
|
237
|
+
* @throws Error if used outside ResilienceProvider
|
|
238
|
+
* @returns ResilienceContextState
|
|
239
|
+
*/
|
|
240
|
+
export function useResilience(): ResilienceContextState {
|
|
241
|
+
const context = useContext(ResilienceContext);
|
|
242
|
+
if (!context) {
|
|
243
|
+
throw new Error("useResilience must be used within a ResilienceProvider");
|
|
244
|
+
}
|
|
245
|
+
return context;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Hook to access resilience context optionally.
|
|
250
|
+
*
|
|
251
|
+
* @returns ResilienceContextState or null if outside provider
|
|
252
|
+
*/
|
|
253
|
+
export function useResilienceOptional(): ResilienceContextState | null {
|
|
254
|
+
return useContext(ResilienceContext);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Hook to get current resilience status.
|
|
259
|
+
*
|
|
260
|
+
* @returns Current ResilienceStatus
|
|
261
|
+
*/
|
|
262
|
+
export function useResilienceStatus(): ResilienceStatus {
|
|
263
|
+
const context = useResilience();
|
|
264
|
+
return context.status;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Hook to check if any resilience event is active.
|
|
269
|
+
*
|
|
270
|
+
* @returns true if rate limiting or retry is in progress
|
|
271
|
+
*/
|
|
272
|
+
export function useIsResilienceActive(): boolean {
|
|
273
|
+
const context = useResilienceOptional();
|
|
274
|
+
return context?.status.type !== "idle" && context?.status.type !== undefined;
|
|
275
|
+
}
|