@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,702 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tools Context and State Management
|
|
3
|
+
*
|
|
4
|
+
* Provides tool execution state management for the Vellum TUI including
|
|
5
|
+
* tool approval workflow, execution tracking, and status updates.
|
|
6
|
+
*
|
|
7
|
+
* @module tui/context/ToolsContext
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type {
|
|
11
|
+
AskContext,
|
|
12
|
+
PermissionAskHandler,
|
|
13
|
+
PermissionInfo,
|
|
14
|
+
PermissionResponse,
|
|
15
|
+
} from "@vellum/core";
|
|
16
|
+
import React, {
|
|
17
|
+
createContext,
|
|
18
|
+
type Dispatch,
|
|
19
|
+
type ReactNode,
|
|
20
|
+
useCallback,
|
|
21
|
+
useContext,
|
|
22
|
+
useMemo,
|
|
23
|
+
useReducer,
|
|
24
|
+
useRef,
|
|
25
|
+
} from "react";
|
|
26
|
+
|
|
27
|
+
// =============================================================================
|
|
28
|
+
// Types
|
|
29
|
+
// =============================================================================
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Status of a tool execution
|
|
33
|
+
*/
|
|
34
|
+
export type ToolExecutionStatus =
|
|
35
|
+
| "pending"
|
|
36
|
+
| "approved"
|
|
37
|
+
| "rejected"
|
|
38
|
+
| "running"
|
|
39
|
+
| "complete"
|
|
40
|
+
| "error";
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* A single tool execution
|
|
44
|
+
*/
|
|
45
|
+
export interface ToolExecution {
|
|
46
|
+
/** Unique identifier for the execution */
|
|
47
|
+
readonly id: string;
|
|
48
|
+
/** Name of the tool being executed */
|
|
49
|
+
readonly toolName: string;
|
|
50
|
+
/** Parameters passed to the tool */
|
|
51
|
+
readonly params: Record<string, unknown>;
|
|
52
|
+
/** Current status of the execution */
|
|
53
|
+
readonly status: ToolExecutionStatus;
|
|
54
|
+
/** Result of the execution, if completed */
|
|
55
|
+
readonly result?: unknown;
|
|
56
|
+
/** Error that occurred during execution */
|
|
57
|
+
readonly error?: Error;
|
|
58
|
+
/** Timestamp when execution started */
|
|
59
|
+
readonly startedAt?: Date;
|
|
60
|
+
/** Timestamp when execution completed */
|
|
61
|
+
readonly completedAt?: Date;
|
|
62
|
+
/** Shell output lines for streaming display (max 10 lines) */
|
|
63
|
+
readonly shellOutput?: readonly string[];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Input shape for creating a new execution.
|
|
68
|
+
*
|
|
69
|
+
* Status defaults to "pending" for backwards compatibility with existing UI/tests.
|
|
70
|
+
*/
|
|
71
|
+
export interface NewToolExecution {
|
|
72
|
+
/** Name of the tool being executed */
|
|
73
|
+
readonly toolName: string;
|
|
74
|
+
/** Parameters passed to the tool */
|
|
75
|
+
readonly params: Record<string, unknown>;
|
|
76
|
+
/** Initial status (default: "pending") */
|
|
77
|
+
readonly status?: ToolExecutionStatus;
|
|
78
|
+
/** Result of the execution, if completed */
|
|
79
|
+
readonly result?: unknown;
|
|
80
|
+
/** Error that occurred during execution */
|
|
81
|
+
readonly error?: Error;
|
|
82
|
+
/** Timestamp when execution started */
|
|
83
|
+
readonly startedAt?: Date;
|
|
84
|
+
/** Timestamp when execution completed */
|
|
85
|
+
readonly completedAt?: Date;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Tools state interface
|
|
90
|
+
*/
|
|
91
|
+
export interface ToolsState {
|
|
92
|
+
/** List of all tool executions */
|
|
93
|
+
readonly executions: readonly ToolExecution[];
|
|
94
|
+
/** Tool executions pending approval */
|
|
95
|
+
readonly pendingApproval: readonly ToolExecution[];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Initial tools state
|
|
100
|
+
*/
|
|
101
|
+
const initialState: ToolsState = {
|
|
102
|
+
executions: [],
|
|
103
|
+
pendingApproval: [],
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// =============================================================================
|
|
107
|
+
// Actions (Discriminated Union)
|
|
108
|
+
// =============================================================================
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Add a new tool execution
|
|
112
|
+
*/
|
|
113
|
+
export interface AddExecutionAction {
|
|
114
|
+
readonly type: "ADD_EXECUTION";
|
|
115
|
+
readonly execution: ToolExecution;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Approve a pending tool execution
|
|
120
|
+
*/
|
|
121
|
+
export interface ApproveExecutionAction {
|
|
122
|
+
readonly type: "APPROVE_EXECUTION";
|
|
123
|
+
readonly id: string;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Reject a pending tool execution
|
|
128
|
+
*/
|
|
129
|
+
export interface RejectExecutionAction {
|
|
130
|
+
readonly type: "REJECT_EXECUTION";
|
|
131
|
+
readonly id: string;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Approve all pending tool executions
|
|
136
|
+
*/
|
|
137
|
+
export interface ApproveAllAction {
|
|
138
|
+
readonly type: "APPROVE_ALL";
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Update an existing tool execution
|
|
143
|
+
*/
|
|
144
|
+
export interface UpdateExecutionAction {
|
|
145
|
+
readonly type: "UPDATE_EXECUTION";
|
|
146
|
+
readonly id: string;
|
|
147
|
+
readonly updates: Partial<Omit<ToolExecution, "id">>;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Clear all tool executions
|
|
152
|
+
*/
|
|
153
|
+
export interface ClearExecutionsAction {
|
|
154
|
+
readonly type: "CLEAR_EXECUTIONS";
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Discriminated union of all tools actions
|
|
159
|
+
*/
|
|
160
|
+
export type ToolsAction =
|
|
161
|
+
| AddExecutionAction
|
|
162
|
+
| ApproveExecutionAction
|
|
163
|
+
| RejectExecutionAction
|
|
164
|
+
| ApproveAllAction
|
|
165
|
+
| UpdateExecutionAction
|
|
166
|
+
| ClearExecutionsAction;
|
|
167
|
+
|
|
168
|
+
// =============================================================================
|
|
169
|
+
// Helper Functions
|
|
170
|
+
// =============================================================================
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Compute pending approval list from executions
|
|
174
|
+
*/
|
|
175
|
+
function computePendingApproval(executions: readonly ToolExecution[]): readonly ToolExecution[] {
|
|
176
|
+
return executions.filter((e) => e.status === "pending");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Update a single execution in the list
|
|
181
|
+
*/
|
|
182
|
+
function updateExecutionInList(
|
|
183
|
+
executions: readonly ToolExecution[],
|
|
184
|
+
id: string,
|
|
185
|
+
updates: Partial<Omit<ToolExecution, "id">>
|
|
186
|
+
): readonly ToolExecution[] {
|
|
187
|
+
const index = executions.findIndex((e) => e.id === id);
|
|
188
|
+
if (index === -1) {
|
|
189
|
+
return executions;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const existing = executions[index];
|
|
193
|
+
if (!existing) {
|
|
194
|
+
return executions;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const updated = [...executions];
|
|
198
|
+
updated[index] = {
|
|
199
|
+
...existing,
|
|
200
|
+
...updates,
|
|
201
|
+
};
|
|
202
|
+
return updated;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// =============================================================================
|
|
206
|
+
// Reducer
|
|
207
|
+
// =============================================================================
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Tools state reducer
|
|
211
|
+
*
|
|
212
|
+
* @param state - Current tools state
|
|
213
|
+
* @param action - Action to apply
|
|
214
|
+
* @returns New tools state
|
|
215
|
+
*/
|
|
216
|
+
function toolsReducer(state: ToolsState, action: ToolsAction): ToolsState {
|
|
217
|
+
switch (action.type) {
|
|
218
|
+
case "ADD_EXECUTION": {
|
|
219
|
+
const newExecutions = [...state.executions, action.execution];
|
|
220
|
+
return {
|
|
221
|
+
executions: newExecutions,
|
|
222
|
+
pendingApproval: computePendingApproval(newExecutions),
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
case "APPROVE_EXECUTION": {
|
|
227
|
+
const updatedExecutions = updateExecutionInList(state.executions, action.id, {
|
|
228
|
+
status: "approved",
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
if (updatedExecutions === state.executions) {
|
|
232
|
+
return state;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
executions: updatedExecutions,
|
|
237
|
+
pendingApproval: computePendingApproval(updatedExecutions),
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
case "REJECT_EXECUTION": {
|
|
242
|
+
const updatedExecutions = updateExecutionInList(state.executions, action.id, {
|
|
243
|
+
status: "rejected",
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
if (updatedExecutions === state.executions) {
|
|
247
|
+
return state;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
executions: updatedExecutions,
|
|
252
|
+
pendingApproval: computePendingApproval(updatedExecutions),
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
case "APPROVE_ALL": {
|
|
257
|
+
if (state.pendingApproval.length === 0) {
|
|
258
|
+
return state;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const updatedExecutions = state.executions.map((execution) =>
|
|
262
|
+
execution.status === "pending" ? { ...execution, status: "approved" as const } : execution
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
executions: updatedExecutions,
|
|
267
|
+
pendingApproval: [],
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
case "UPDATE_EXECUTION": {
|
|
272
|
+
const updatedExecutions = updateExecutionInList(state.executions, action.id, action.updates);
|
|
273
|
+
|
|
274
|
+
if (updatedExecutions === state.executions) {
|
|
275
|
+
return state;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
executions: updatedExecutions,
|
|
280
|
+
pendingApproval: computePendingApproval(updatedExecutions),
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
case "CLEAR_EXECUTIONS":
|
|
285
|
+
return initialState;
|
|
286
|
+
|
|
287
|
+
default:
|
|
288
|
+
// Exhaustive check - TypeScript will error if a case is missing
|
|
289
|
+
return state;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// =============================================================================
|
|
294
|
+
// ID Generation
|
|
295
|
+
// =============================================================================
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Generate a unique tool execution ID
|
|
299
|
+
*
|
|
300
|
+
* Uses crypto.randomUUID() when available, falls back to timestamp-based ID
|
|
301
|
+
*/
|
|
302
|
+
function generateExecutionId(): string {
|
|
303
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
304
|
+
return crypto.randomUUID();
|
|
305
|
+
}
|
|
306
|
+
// Fallback for environments without crypto.randomUUID
|
|
307
|
+
return `tool-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// =============================================================================
|
|
311
|
+
// Context
|
|
312
|
+
// =============================================================================
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Context value interface
|
|
316
|
+
*/
|
|
317
|
+
export interface ToolsContextValue {
|
|
318
|
+
/** Current tools state */
|
|
319
|
+
readonly state: ToolsState;
|
|
320
|
+
/** Dispatch function for state updates */
|
|
321
|
+
readonly dispatch: Dispatch<ToolsAction>;
|
|
322
|
+
/** All tool executions */
|
|
323
|
+
readonly executions: readonly ToolExecution[];
|
|
324
|
+
/** Tool executions pending approval */
|
|
325
|
+
readonly pendingApproval: readonly ToolExecution[];
|
|
326
|
+
/** Add a new tool execution, returns the generated ID */
|
|
327
|
+
readonly addExecution: (tool: NewToolExecution) => string;
|
|
328
|
+
/** Register a tool callId -> executionId mapping for correlation */
|
|
329
|
+
readonly registerCallId: (callId: string, executionId: string) => void;
|
|
330
|
+
/** Approve a pending tool execution */
|
|
331
|
+
readonly approveExecution: (id: string) => void;
|
|
332
|
+
/** Reject a pending tool execution */
|
|
333
|
+
readonly rejectExecution: (id: string) => void;
|
|
334
|
+
/** Respond to a pending permission prompt associated with an execution */
|
|
335
|
+
readonly respondToPermissionRequest: (executionId: string, response: PermissionResponse) => void;
|
|
336
|
+
/** PermissionAskHandler that surfaces permission prompts through ToolsContext */
|
|
337
|
+
readonly permissionAskHandler: PermissionAskHandler;
|
|
338
|
+
/** Approve all pending tool executions */
|
|
339
|
+
readonly approveAll: () => void;
|
|
340
|
+
/** Update an existing tool execution */
|
|
341
|
+
readonly updateExecution: (id: string, updates: Partial<ToolExecution>) => void;
|
|
342
|
+
/** Update shell output for streaming display (appends new content, keeps last 10 lines) */
|
|
343
|
+
readonly updateShellOutput: (id: string, chunk: string) => void;
|
|
344
|
+
/** Clear all tool executions */
|
|
345
|
+
readonly clearExecutions: () => void;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* React context for tools state
|
|
350
|
+
*
|
|
351
|
+
* Initialized as undefined to detect usage outside provider
|
|
352
|
+
*/
|
|
353
|
+
const ToolsContext = createContext<ToolsContextValue | undefined>(undefined);
|
|
354
|
+
|
|
355
|
+
// =============================================================================
|
|
356
|
+
// Hook
|
|
357
|
+
// =============================================================================
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Hook to access the tools state and actions
|
|
361
|
+
*
|
|
362
|
+
* Must be used within a ToolsProvider component.
|
|
363
|
+
*
|
|
364
|
+
* @returns The current tools context value with state and actions
|
|
365
|
+
* @throws Error if used outside ToolsProvider
|
|
366
|
+
*
|
|
367
|
+
* @example
|
|
368
|
+
* ```tsx
|
|
369
|
+
* function ToolApprovalComponent() {
|
|
370
|
+
* const { executions, pendingApproval, approveExecution, rejectExecution, approveAll } = useTools();
|
|
371
|
+
*
|
|
372
|
+
* // Add a new tool execution
|
|
373
|
+
* const handleToolCall = (toolName: string, params: Record<string, unknown>) => {
|
|
374
|
+
* const id = addExecution({ toolName, params });
|
|
375
|
+
* console.log('Created execution:', id);
|
|
376
|
+
* };
|
|
377
|
+
*
|
|
378
|
+
* // Approve a single execution
|
|
379
|
+
* const handleApprove = (id: string) => approveExecution(id);
|
|
380
|
+
*
|
|
381
|
+
* // Reject a single execution
|
|
382
|
+
* const handleReject = (id: string) => rejectExecution(id);
|
|
383
|
+
*
|
|
384
|
+
* // Approve all pending
|
|
385
|
+
* const handleApproveAll = () => approveAll();
|
|
386
|
+
*
|
|
387
|
+
* return <Box>...</Box>;
|
|
388
|
+
* }
|
|
389
|
+
* ```
|
|
390
|
+
*/
|
|
391
|
+
export function useTools(): ToolsContextValue {
|
|
392
|
+
const context = useContext(ToolsContext);
|
|
393
|
+
|
|
394
|
+
if (context === undefined) {
|
|
395
|
+
throw new Error(
|
|
396
|
+
"useTools must be used within a ToolsProvider. " +
|
|
397
|
+
"Ensure your component is wrapped in <ToolsProvider>."
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return context;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// =============================================================================
|
|
405
|
+
// Provider Props
|
|
406
|
+
// =============================================================================
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Props for the ToolsProvider component
|
|
410
|
+
*/
|
|
411
|
+
export interface ToolsProviderProps {
|
|
412
|
+
/**
|
|
413
|
+
* Initial tool executions to populate the state
|
|
414
|
+
*/
|
|
415
|
+
readonly initialExecutions?: readonly ToolExecution[];
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Children to render within the tools context
|
|
419
|
+
*/
|
|
420
|
+
readonly children: ReactNode;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// =============================================================================
|
|
424
|
+
// Provider Component
|
|
425
|
+
// =============================================================================
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Tools state provider component
|
|
429
|
+
*
|
|
430
|
+
* Provides tools state context to all child components, enabling
|
|
431
|
+
* access to tool executions and approval workflow via the useTools hook.
|
|
432
|
+
*
|
|
433
|
+
* @example
|
|
434
|
+
* ```tsx
|
|
435
|
+
* // Using default initial state
|
|
436
|
+
* <ToolsProvider>
|
|
437
|
+
* <ToolApprovalUI />
|
|
438
|
+
* </ToolsProvider>
|
|
439
|
+
*
|
|
440
|
+
* // Using initial executions
|
|
441
|
+
* <ToolsProvider initialExecutions={[{ id: '1', toolName: 'read_file', params: {}, status: 'pending' }]}>
|
|
442
|
+
* <ToolApprovalUI />
|
|
443
|
+
* </ToolsProvider>
|
|
444
|
+
* ```
|
|
445
|
+
*/
|
|
446
|
+
export function ToolsProvider({
|
|
447
|
+
initialExecutions,
|
|
448
|
+
children,
|
|
449
|
+
}: ToolsProviderProps): React.JSX.Element {
|
|
450
|
+
// State management with useReducer
|
|
451
|
+
const [state, dispatch] = useReducer(
|
|
452
|
+
toolsReducer,
|
|
453
|
+
initialExecutions,
|
|
454
|
+
(executions): ToolsState => {
|
|
455
|
+
const executionList = executions ?? [];
|
|
456
|
+
return {
|
|
457
|
+
executions: executionList,
|
|
458
|
+
pendingApproval: computePendingApproval(executionList),
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
);
|
|
462
|
+
|
|
463
|
+
// Correlate core ToolContext.callId -> ToolExecution.id
|
|
464
|
+
const callIdMapRef = useRef<Map<string, string>>(new Map());
|
|
465
|
+
|
|
466
|
+
type PendingResolver = {
|
|
467
|
+
readonly resolve: (result: PermissionResponse | undefined) => void;
|
|
468
|
+
readonly signal: AbortSignal;
|
|
469
|
+
readonly abortHandler: () => void;
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
// Pending permission prompts keyed by ToolExecution.id
|
|
473
|
+
const pendingPermissionResolversRef = useRef<Map<string, PendingResolver>>(new Map());
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Add a new tool execution
|
|
477
|
+
* @returns The generated execution ID
|
|
478
|
+
*/
|
|
479
|
+
const addExecution = useCallback((tool: NewToolExecution): string => {
|
|
480
|
+
const id = generateExecutionId();
|
|
481
|
+
const status: ToolExecutionStatus = tool.status ?? "pending";
|
|
482
|
+
const fullExecution: ToolExecution = {
|
|
483
|
+
id,
|
|
484
|
+
toolName: tool.toolName,
|
|
485
|
+
params: tool.params,
|
|
486
|
+
status,
|
|
487
|
+
result: tool.result,
|
|
488
|
+
error: tool.error,
|
|
489
|
+
startedAt: tool.startedAt,
|
|
490
|
+
completedAt: tool.completedAt,
|
|
491
|
+
};
|
|
492
|
+
dispatch({ type: "ADD_EXECUTION", execution: fullExecution });
|
|
493
|
+
return id;
|
|
494
|
+
}, []);
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Register callId mapping for correlation.
|
|
498
|
+
*/
|
|
499
|
+
const registerCallId = useCallback((callId: string, executionId: string): void => {
|
|
500
|
+
callIdMapRef.current.set(callId, executionId);
|
|
501
|
+
}, []);
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Respond to an active permission request (if any) associated with a ToolExecution.
|
|
505
|
+
*/
|
|
506
|
+
const respondToPermissionRequest = useCallback(
|
|
507
|
+
(executionId: string, response: PermissionResponse): void => {
|
|
508
|
+
const pending = pendingPermissionResolversRef.current.get(executionId);
|
|
509
|
+
|
|
510
|
+
// Update UI state immediately.
|
|
511
|
+
dispatch({
|
|
512
|
+
type: response === "reject" ? "REJECT_EXECUTION" : "APPROVE_EXECUTION",
|
|
513
|
+
id: executionId,
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
if (!pending) {
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Clean up abort handler and resolve the ask promise.
|
|
521
|
+
pending.signal.removeEventListener("abort", pending.abortHandler);
|
|
522
|
+
pendingPermissionResolversRef.current.delete(executionId);
|
|
523
|
+
pending.resolve(response);
|
|
524
|
+
},
|
|
525
|
+
[]
|
|
526
|
+
);
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Approve a pending tool execution
|
|
530
|
+
*/
|
|
531
|
+
const approveExecution = useCallback((id: string): void => {
|
|
532
|
+
dispatch({ type: "APPROVE_EXECUTION", id });
|
|
533
|
+
}, []);
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Reject a pending tool execution
|
|
537
|
+
*/
|
|
538
|
+
const rejectExecution = useCallback((id: string): void => {
|
|
539
|
+
dispatch({ type: "REJECT_EXECUTION", id });
|
|
540
|
+
}, []);
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Ask handler implementation that drives permission prompts through ToolsContext.
|
|
544
|
+
*
|
|
545
|
+
* This allows core permission checks to block until the user responds in the TUI.
|
|
546
|
+
*/
|
|
547
|
+
const permissionAskHandler: PermissionAskHandler = useCallback(
|
|
548
|
+
async (info: PermissionInfo, context: AskContext): Promise<PermissionResponse | undefined> => {
|
|
549
|
+
const toolNameFromMeta =
|
|
550
|
+
typeof info.metadata?.toolName === "string"
|
|
551
|
+
? (info.metadata.toolName as string)
|
|
552
|
+
: undefined;
|
|
553
|
+
|
|
554
|
+
const toolName =
|
|
555
|
+
toolNameFromMeta ??
|
|
556
|
+
info.title
|
|
557
|
+
.replace(/^Allow\s+/i, "")
|
|
558
|
+
.replace(/\?$/, "")
|
|
559
|
+
.trim();
|
|
560
|
+
|
|
561
|
+
const paramsFromMeta =
|
|
562
|
+
info.metadata && typeof info.metadata.params === "object" && info.metadata.params !== null
|
|
563
|
+
? (info.metadata.params as Record<string, unknown>)
|
|
564
|
+
: {};
|
|
565
|
+
|
|
566
|
+
const mappedExecutionId = info.callId ? callIdMapRef.current.get(info.callId) : undefined;
|
|
567
|
+
const executionId =
|
|
568
|
+
mappedExecutionId ??
|
|
569
|
+
addExecution({
|
|
570
|
+
toolName,
|
|
571
|
+
params: paramsFromMeta,
|
|
572
|
+
status: "pending",
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
// Ensure the execution reflects the prompt state.
|
|
576
|
+
dispatch({
|
|
577
|
+
type: "UPDATE_EXECUTION",
|
|
578
|
+
id: executionId,
|
|
579
|
+
updates: {
|
|
580
|
+
toolName,
|
|
581
|
+
params: paramsFromMeta,
|
|
582
|
+
status: "pending",
|
|
583
|
+
},
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
return new Promise<PermissionResponse | undefined>((resolve) => {
|
|
587
|
+
const abortHandler = () => {
|
|
588
|
+
// Only resolve if this execution is still pending.
|
|
589
|
+
const pending = pendingPermissionResolversRef.current.get(executionId);
|
|
590
|
+
if (!pending || pending.resolve !== resolve) {
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
pendingPermissionResolversRef.current.delete(executionId);
|
|
595
|
+
resolve(undefined);
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
pendingPermissionResolversRef.current.set(executionId, {
|
|
599
|
+
resolve,
|
|
600
|
+
signal: context.signal,
|
|
601
|
+
abortHandler,
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
context.signal.addEventListener("abort", abortHandler, { once: true });
|
|
605
|
+
});
|
|
606
|
+
},
|
|
607
|
+
[addExecution]
|
|
608
|
+
);
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* Approve all pending tool executions
|
|
612
|
+
*/
|
|
613
|
+
const approveAll = useCallback((): void => {
|
|
614
|
+
dispatch({ type: "APPROVE_ALL" });
|
|
615
|
+
}, []);
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Update an existing tool execution
|
|
619
|
+
*/
|
|
620
|
+
const updateExecution = useCallback((id: string, updates: Partial<ToolExecution>): void => {
|
|
621
|
+
dispatch({ type: "UPDATE_EXECUTION", id, updates });
|
|
622
|
+
}, []);
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* Clear all tool executions
|
|
626
|
+
*/
|
|
627
|
+
const clearExecutions = useCallback((): void => {
|
|
628
|
+
dispatch({ type: "CLEAR_EXECUTIONS" });
|
|
629
|
+
}, []);
|
|
630
|
+
|
|
631
|
+
/** Maximum number of shell output lines to keep */
|
|
632
|
+
const MAX_SHELL_OUTPUT_LINES = 10;
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* Update shell output for streaming display
|
|
636
|
+
* Appends new content and keeps only the last MAX_SHELL_OUTPUT_LINES lines
|
|
637
|
+
*/
|
|
638
|
+
const updateShellOutput = useCallback(
|
|
639
|
+
(id: string, chunk: string): void => {
|
|
640
|
+
// Get current execution to read existing output
|
|
641
|
+
const execution = state.executions.find((e) => e.id === id);
|
|
642
|
+
const currentLines = execution?.shellOutput ?? [];
|
|
643
|
+
|
|
644
|
+
// Split chunk into lines and append
|
|
645
|
+
const newLines = chunk.split("\n").filter((line) => line.length > 0);
|
|
646
|
+
const allLines = [...currentLines, ...newLines];
|
|
647
|
+
|
|
648
|
+
// Keep only last MAX_SHELL_OUTPUT_LINES lines
|
|
649
|
+
const trimmedLines = allLines.slice(-MAX_SHELL_OUTPUT_LINES);
|
|
650
|
+
|
|
651
|
+
dispatch({
|
|
652
|
+
type: "UPDATE_EXECUTION",
|
|
653
|
+
id,
|
|
654
|
+
updates: { shellOutput: trimmedLines },
|
|
655
|
+
});
|
|
656
|
+
},
|
|
657
|
+
[state.executions]
|
|
658
|
+
);
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Memoized context value
|
|
662
|
+
*/
|
|
663
|
+
const contextValue = useMemo<ToolsContextValue>(
|
|
664
|
+
() => ({
|
|
665
|
+
state,
|
|
666
|
+
dispatch,
|
|
667
|
+
executions: state.executions,
|
|
668
|
+
pendingApproval: state.pendingApproval,
|
|
669
|
+
addExecution,
|
|
670
|
+
registerCallId,
|
|
671
|
+
approveExecution,
|
|
672
|
+
rejectExecution,
|
|
673
|
+
respondToPermissionRequest,
|
|
674
|
+
permissionAskHandler,
|
|
675
|
+
approveAll,
|
|
676
|
+
updateExecution,
|
|
677
|
+
updateShellOutput,
|
|
678
|
+
clearExecutions,
|
|
679
|
+
}),
|
|
680
|
+
[
|
|
681
|
+
state,
|
|
682
|
+
addExecution,
|
|
683
|
+
registerCallId,
|
|
684
|
+
approveExecution,
|
|
685
|
+
rejectExecution,
|
|
686
|
+
respondToPermissionRequest,
|
|
687
|
+
permissionAskHandler,
|
|
688
|
+
approveAll,
|
|
689
|
+
updateExecution,
|
|
690
|
+
updateShellOutput,
|
|
691
|
+
clearExecutions,
|
|
692
|
+
]
|
|
693
|
+
);
|
|
694
|
+
|
|
695
|
+
return <ToolsContext value={contextValue}>{children}</ToolsContext>;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// =============================================================================
|
|
699
|
+
// Exports
|
|
700
|
+
// =============================================================================
|
|
701
|
+
|
|
702
|
+
export { ToolsContext, initialState };
|