@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,335 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Resume Command - REQ-034)
|
|
3
|
+
*
|
|
4
|
+
* Implements the `vellum resume [chainId]` CLI command
|
|
5
|
+
* for resuming interrupted task chains.
|
|
6
|
+
*
|
|
7
|
+
* @module cli/agents/commands/resume
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Command } from "commander";
|
|
11
|
+
import { getOrCreateOrchestrator, getOrchestrator } from "../../orchestrator-singleton.js";
|
|
12
|
+
import { ICONS } from "../../utils/icons.js";
|
|
13
|
+
|
|
14
|
+
import { createTaskPersistence, type TaskPersistence } from "../task-persistence.js";
|
|
15
|
+
import {
|
|
16
|
+
createTaskResumption,
|
|
17
|
+
type ResumeOptions,
|
|
18
|
+
type ResumeResult,
|
|
19
|
+
type TaskResumption,
|
|
20
|
+
} from "../task-resumption.js";
|
|
21
|
+
|
|
22
|
+
// =============================================================================
|
|
23
|
+
// Types
|
|
24
|
+
// =============================================================================
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Options for the resume command.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* const options: ResumeCommandOptions = {
|
|
32
|
+
* skipFailed: true,
|
|
33
|
+
* retryFailed: false,
|
|
34
|
+
* from: 'task-123',
|
|
35
|
+
* };
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export interface ResumeCommandOptions {
|
|
39
|
+
/** Skip tasks that previously failed */
|
|
40
|
+
skipFailed?: boolean;
|
|
41
|
+
/** Retry failed tasks instead of skipping */
|
|
42
|
+
retryFailed?: boolean;
|
|
43
|
+
/** Resume from a specific task ID */
|
|
44
|
+
from?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// =============================================================================
|
|
48
|
+
// Helpers
|
|
49
|
+
// =============================================================================
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Format a date for display in the CLI.
|
|
53
|
+
*
|
|
54
|
+
* @param date - Date to format
|
|
55
|
+
* @returns Formatted date string
|
|
56
|
+
*/
|
|
57
|
+
function formatDate(date: Date): string {
|
|
58
|
+
return date.toLocaleString();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Format a chain status for display with emoji indicator.
|
|
63
|
+
*
|
|
64
|
+
* @param status - Chain status
|
|
65
|
+
* @returns Formatted status string with emoji
|
|
66
|
+
*/
|
|
67
|
+
function formatStatus(status: string): string {
|
|
68
|
+
switch (status) {
|
|
69
|
+
case "paused":
|
|
70
|
+
return "[PAUSE] paused";
|
|
71
|
+
case "running":
|
|
72
|
+
return `${ICONS.interrupted} interrupted`;
|
|
73
|
+
case "completed":
|
|
74
|
+
return `${ICONS.success} completed`;
|
|
75
|
+
case "failed":
|
|
76
|
+
return `${ICONS.error} failed`;
|
|
77
|
+
default:
|
|
78
|
+
return status;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Display a list of resumable chains in the terminal.
|
|
84
|
+
*
|
|
85
|
+
* @param chains - Array of resumable chain metadata
|
|
86
|
+
*/
|
|
87
|
+
function displayResumableChains(
|
|
88
|
+
chains: Array<{ chainId: string; savedAt: Date; status: string }>
|
|
89
|
+
): void {
|
|
90
|
+
if (chains.length === 0) {
|
|
91
|
+
console.log("\nNo resumable task chains found.");
|
|
92
|
+
console.log(" Task chains become resumable when paused or interrupted.\n");
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
console.log(`\n${ICONS.workflow} Resumable Task Chains:\n`);
|
|
97
|
+
console.log(" %-40s %-20s %s", "Chain ID", "Status", "Saved At");
|
|
98
|
+
console.log(` ${"-".repeat(80)}`);
|
|
99
|
+
|
|
100
|
+
for (const chain of chains) {
|
|
101
|
+
console.log(
|
|
102
|
+
" %-40s %-20s %s",
|
|
103
|
+
chain.chainId,
|
|
104
|
+
formatStatus(chain.status),
|
|
105
|
+
formatDate(chain.savedAt)
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
console.log("\n Use 'vellum resume <chainId>' to resume a specific chain.\n");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Display detailed resume options for a chain.
|
|
114
|
+
*
|
|
115
|
+
* @param chainId - The chain ID
|
|
116
|
+
* @param options - Resume options for the chain
|
|
117
|
+
*/
|
|
118
|
+
function displayResumeOptions(
|
|
119
|
+
chainId: string,
|
|
120
|
+
options: {
|
|
121
|
+
lastCompletedTask: string;
|
|
122
|
+
pendingTasks: string[];
|
|
123
|
+
failedTasks: string[];
|
|
124
|
+
}
|
|
125
|
+
): void {
|
|
126
|
+
console.log(`\n📊 Chain Status: ${chainId}\n`);
|
|
127
|
+
|
|
128
|
+
if (options.lastCompletedTask) {
|
|
129
|
+
console.log(` Last Completed: ${options.lastCompletedTask}`);
|
|
130
|
+
} else {
|
|
131
|
+
console.log(" Last Completed: (none)");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
console.log(` Pending Tasks: ${options.pendingTasks.length}`);
|
|
135
|
+
if (options.pendingTasks.length > 0 && options.pendingTasks.length <= 5) {
|
|
136
|
+
for (const task of options.pendingTasks) {
|
|
137
|
+
console.log(` - ${task}`);
|
|
138
|
+
}
|
|
139
|
+
} else if (options.pendingTasks.length > 5) {
|
|
140
|
+
for (const task of options.pendingTasks.slice(0, 3)) {
|
|
141
|
+
console.log(` - ${task}`);
|
|
142
|
+
}
|
|
143
|
+
console.log(` ... and ${options.pendingTasks.length - 3} more`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
console.log(` Failed Tasks: ${options.failedTasks.length}`);
|
|
147
|
+
if (options.failedTasks.length > 0) {
|
|
148
|
+
for (const task of options.failedTasks) {
|
|
149
|
+
console.log(` - ${task}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
console.log("");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Display the result of a resume operation.
|
|
158
|
+
*
|
|
159
|
+
* @param result - Resume operation result
|
|
160
|
+
*/
|
|
161
|
+
function displayResumeResult(result: ResumeResult): void {
|
|
162
|
+
if (!result.resumed) {
|
|
163
|
+
console.error(`\n${ICONS.error} Failed to resume chain: ${result.chainId}`);
|
|
164
|
+
console.error(" Chain may not exist or is not in a resumable state.\n");
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
console.log(`\n${ICONS.success} Successfully resumed chain: ${result.chainId}`);
|
|
169
|
+
console.log(` From Task: ${result.fromTaskId || "(start)"}`);
|
|
170
|
+
console.log(` Tasks Remaining: ${result.totalRemaining}`);
|
|
171
|
+
|
|
172
|
+
if (result.skippedCount > 0) {
|
|
173
|
+
console.log(` Tasks Skipped: ${result.skippedCount}`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
console.log("");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// =============================================================================
|
|
180
|
+
// Command Actions
|
|
181
|
+
// =============================================================================
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* List all resumable task chains.
|
|
185
|
+
*
|
|
186
|
+
* @param persistence - Task persistence instance
|
|
187
|
+
*/
|
|
188
|
+
async function listResumableChains(persistence: TaskPersistence): Promise<void> {
|
|
189
|
+
const chains = await persistence.listResumable();
|
|
190
|
+
displayResumableChains(chains);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Resume a specific task chain.
|
|
195
|
+
*
|
|
196
|
+
* @param chainId - The chain ID to resume
|
|
197
|
+
* @param options - Resume command options
|
|
198
|
+
* @param persistence - Task persistence instance
|
|
199
|
+
* @param resumption - Task resumption instance
|
|
200
|
+
*/
|
|
201
|
+
async function resumeChain(
|
|
202
|
+
chainId: string,
|
|
203
|
+
options: ResumeCommandOptions,
|
|
204
|
+
_persistence: TaskPersistence,
|
|
205
|
+
resumption: TaskResumption
|
|
206
|
+
): Promise<void> {
|
|
207
|
+
// First, check if the chain can be resumed
|
|
208
|
+
const canResume = await resumption.canResume(chainId);
|
|
209
|
+
|
|
210
|
+
if (!canResume) {
|
|
211
|
+
console.error(`\n${ICONS.error} Chain '${chainId}' cannot be resumed.`);
|
|
212
|
+
console.error(" It may not exist or is not in a resumable state (paused/interrupted).\n");
|
|
213
|
+
process.exit(1);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Get and display resume options
|
|
217
|
+
const resumeOptions = await resumption.getResumeOptions(chainId);
|
|
218
|
+
|
|
219
|
+
if (resumeOptions) {
|
|
220
|
+
displayResumeOptions(chainId, resumeOptions);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Build resume options
|
|
224
|
+
const resumeParams: ResumeOptions = {
|
|
225
|
+
skipFailed: options.skipFailed,
|
|
226
|
+
retryFailed: options.retryFailed,
|
|
227
|
+
fromTask: options.from,
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
// Validate options
|
|
231
|
+
if (options.skipFailed && options.retryFailed) {
|
|
232
|
+
console.error(`\n${ICONS.error} Cannot use both --skip-failed and --retry-failed options.\n`);
|
|
233
|
+
process.exit(1);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// If --from is specified, validate it exists in pending or failed tasks
|
|
237
|
+
if (options.from && resumeOptions) {
|
|
238
|
+
const allTasks = [...resumeOptions.pendingTasks, ...resumeOptions.failedTasks];
|
|
239
|
+
if (!allTasks.includes(options.from)) {
|
|
240
|
+
console.error(
|
|
241
|
+
`\n${ICONS.error} Task '${options.from}' not found in pending or failed tasks.\n`
|
|
242
|
+
);
|
|
243
|
+
console.error(" Available tasks:");
|
|
244
|
+
for (const task of allTasks.slice(0, 10)) {
|
|
245
|
+
console.error(` - ${task}`);
|
|
246
|
+
}
|
|
247
|
+
if (allTasks.length > 10) {
|
|
248
|
+
console.error(` ... and ${allTasks.length - 10} more`);
|
|
249
|
+
}
|
|
250
|
+
console.log("");
|
|
251
|
+
process.exit(1);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Execute resume
|
|
256
|
+
console.log(`${ICONS.running} Resuming task chain...`);
|
|
257
|
+
const result = await resumption.resume(chainId, resumeParams);
|
|
258
|
+
|
|
259
|
+
displayResumeResult(result);
|
|
260
|
+
|
|
261
|
+
if (!result.resumed) {
|
|
262
|
+
process.exit(1);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// =============================================================================
|
|
267
|
+
// Command Registration
|
|
268
|
+
// =============================================================================
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Register the resume command with Commander.js.
|
|
272
|
+
*
|
|
273
|
+
* Command: `vellum resume [chainId]`
|
|
274
|
+
*
|
|
275
|
+
* If no chainId is provided, lists all resumable chains.
|
|
276
|
+
*
|
|
277
|
+
* Options:
|
|
278
|
+
* --skip-failed Skip previously failed tasks
|
|
279
|
+
* --retry-failed Retry previously failed tasks
|
|
280
|
+
* --from <taskId> Resume from specific task
|
|
281
|
+
*
|
|
282
|
+
* @example
|
|
283
|
+
* ```bash
|
|
284
|
+
* # List all resumable chains
|
|
285
|
+
* vellum resume
|
|
286
|
+
*
|
|
287
|
+
* # Resume a specific chain
|
|
288
|
+
* vellum resume chain-abc123
|
|
289
|
+
*
|
|
290
|
+
* # Resume and skip failed tasks
|
|
291
|
+
* vellum resume chain-abc123 --skip-failed
|
|
292
|
+
*
|
|
293
|
+
* # Resume and retry failed tasks
|
|
294
|
+
* vellum resume chain-abc123 --retry-failed
|
|
295
|
+
*
|
|
296
|
+
* # Resume from a specific task
|
|
297
|
+
* vellum resume chain-abc123 --from task-456
|
|
298
|
+
* ```
|
|
299
|
+
*
|
|
300
|
+
* @param program - Commander program instance
|
|
301
|
+
*/
|
|
302
|
+
export function registerResumeCommand(program: Command): void {
|
|
303
|
+
program
|
|
304
|
+
.command("resume [chainId]")
|
|
305
|
+
.description("Resume an interrupted task chain")
|
|
306
|
+
.option("--skip-failed", "Skip previously failed tasks")
|
|
307
|
+
.option("--retry-failed", "Retry previously failed tasks")
|
|
308
|
+
.option("--from <taskId>", "Resume from specific task")
|
|
309
|
+
.action(async (chainId: string | undefined, options: Record<string, unknown>) => {
|
|
310
|
+
try {
|
|
311
|
+
// Parse options
|
|
312
|
+
const commandOptions: ResumeCommandOptions = {
|
|
313
|
+
skipFailed: options.skipFailed === true,
|
|
314
|
+
retryFailed: options.retryFailed === true,
|
|
315
|
+
from: options.from as string | undefined,
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
// Create persistence and resumption instances
|
|
319
|
+
const persistence = createTaskPersistence();
|
|
320
|
+
const orchestrator = getOrchestrator() ?? getOrCreateOrchestrator();
|
|
321
|
+
const resumption = createTaskResumption(persistence, orchestrator);
|
|
322
|
+
|
|
323
|
+
if (!chainId) {
|
|
324
|
+
// List all resumable chains
|
|
325
|
+
await listResumableChains(persistence);
|
|
326
|
+
} else {
|
|
327
|
+
// Resume specific chain
|
|
328
|
+
await resumeChain(chainId, commandOptions, persistence, resumption);
|
|
329
|
+
}
|
|
330
|
+
} catch (error) {
|
|
331
|
+
console.error(`\n${ICONS.error} Error:`, error instanceof Error ? error.message : error);
|
|
332
|
+
process.exit(1);
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Agent Utilities
|
|
3
|
+
*
|
|
4
|
+
* Re-exports all agent-related CLI utilities including:
|
|
5
|
+
* - Task persistence for saving/loading agent task state
|
|
6
|
+
* - Task resumption for continuing interrupted tasks
|
|
7
|
+
* - CLI commands for delegate and resume operations
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// CLI commands
|
|
11
|
+
export {
|
|
12
|
+
type DelegateCommandOptions,
|
|
13
|
+
type ResumeCommandOptions,
|
|
14
|
+
registerDelegateCommand,
|
|
15
|
+
registerResumeCommand,
|
|
16
|
+
} from "./commands/index.js";
|
|
17
|
+
// Task persistence utilities
|
|
18
|
+
export {
|
|
19
|
+
createTaskPersistence,
|
|
20
|
+
type PersistedTaskState,
|
|
21
|
+
type TaskPersistence,
|
|
22
|
+
} from "./task-persistence.js";
|
|
23
|
+
// Task resumption utilities
|
|
24
|
+
export {
|
|
25
|
+
createTaskResumption,
|
|
26
|
+
type ResumeOptions,
|
|
27
|
+
type ResumeResult,
|
|
28
|
+
type TaskResumption,
|
|
29
|
+
} from "./task-resumption.js";
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task Persistence for Resumable Workflows
|
|
3
|
+
*
|
|
4
|
+
* Enables saving and loading task chain state to support
|
|
5
|
+
* pausing and resuming multi-agent workflows.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as fs from "node:fs/promises";
|
|
9
|
+
import * as path from "node:path";
|
|
10
|
+
import type { TaskChain } from "@vellum/core";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Serializable version of TaskChain for JSON persistence
|
|
14
|
+
*/
|
|
15
|
+
interface SerializedTaskChain {
|
|
16
|
+
chainId: string;
|
|
17
|
+
rootTaskId: string;
|
|
18
|
+
nodes: Array<{
|
|
19
|
+
taskId: string;
|
|
20
|
+
parentTaskId?: string;
|
|
21
|
+
agentSlug: string;
|
|
22
|
+
depth: number;
|
|
23
|
+
createdAt: string;
|
|
24
|
+
status: "pending" | "running" | "completed" | "failed";
|
|
25
|
+
}>;
|
|
26
|
+
maxDepth: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Represents the persisted state of a task chain
|
|
31
|
+
*/
|
|
32
|
+
export interface PersistedTaskState {
|
|
33
|
+
chainId: string;
|
|
34
|
+
chain: TaskChain;
|
|
35
|
+
status: "running" | "paused" | "completed" | "failed";
|
|
36
|
+
lastTaskId: string;
|
|
37
|
+
checkpoint: {
|
|
38
|
+
completedTasks: string[];
|
|
39
|
+
pendingTasks: string[];
|
|
40
|
+
failedTasks: string[];
|
|
41
|
+
};
|
|
42
|
+
savedAt: Date;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Serialized format for JSON storage
|
|
47
|
+
*/
|
|
48
|
+
interface SerializedPersistedTaskState {
|
|
49
|
+
chainId: string;
|
|
50
|
+
chain: SerializedTaskChain;
|
|
51
|
+
status: "running" | "paused" | "completed" | "failed";
|
|
52
|
+
lastTaskId: string;
|
|
53
|
+
checkpoint: {
|
|
54
|
+
completedTasks: string[];
|
|
55
|
+
pendingTasks: string[];
|
|
56
|
+
failedTasks: string[];
|
|
57
|
+
};
|
|
58
|
+
savedAt: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Interface for task state persistence operations
|
|
63
|
+
*/
|
|
64
|
+
export interface TaskPersistence {
|
|
65
|
+
/**
|
|
66
|
+
* Save task chain state to persistent storage
|
|
67
|
+
*/
|
|
68
|
+
saveTaskState(state: PersistedTaskState): Promise<void>;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Load task chain state from persistent storage
|
|
72
|
+
* @returns The persisted state, or null if not found
|
|
73
|
+
*/
|
|
74
|
+
loadTaskState(chainId: string): Promise<PersistedTaskState | null>;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* List all resumable task states
|
|
78
|
+
*/
|
|
79
|
+
listResumable(): Promise<{ chainId: string; savedAt: Date; status: string }[]>;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Delete a persisted task state
|
|
83
|
+
* @returns true if deleted, false if not found
|
|
84
|
+
*/
|
|
85
|
+
deleteTaskState(chainId: string): Promise<boolean>;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Convert TaskChain to serializable format
|
|
90
|
+
*/
|
|
91
|
+
function serializeTaskChain(chain: TaskChain): SerializedTaskChain {
|
|
92
|
+
const nodesArray = Array.from(chain.nodes.values()).map((node) => ({
|
|
93
|
+
taskId: node.taskId,
|
|
94
|
+
parentTaskId: node.parentTaskId,
|
|
95
|
+
agentSlug: node.agentSlug,
|
|
96
|
+
depth: node.depth,
|
|
97
|
+
createdAt: node.createdAt.toISOString(),
|
|
98
|
+
status: node.status,
|
|
99
|
+
}));
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
chainId: chain.chainId,
|
|
103
|
+
rootTaskId: chain.rootTaskId,
|
|
104
|
+
nodes: nodesArray,
|
|
105
|
+
maxDepth: chain.maxDepth,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Convert serialized format back to TaskChain
|
|
111
|
+
*/
|
|
112
|
+
function deserializeTaskChain(serialized: SerializedTaskChain): TaskChain {
|
|
113
|
+
const nodes = new Map(
|
|
114
|
+
serialized.nodes.map((node) => [
|
|
115
|
+
node.taskId,
|
|
116
|
+
{
|
|
117
|
+
taskId: node.taskId,
|
|
118
|
+
parentTaskId: node.parentTaskId,
|
|
119
|
+
agentSlug: node.agentSlug,
|
|
120
|
+
depth: node.depth,
|
|
121
|
+
createdAt: new Date(node.createdAt),
|
|
122
|
+
status: node.status,
|
|
123
|
+
},
|
|
124
|
+
])
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
chainId: serialized.chainId,
|
|
129
|
+
rootTaskId: serialized.rootTaskId,
|
|
130
|
+
nodes,
|
|
131
|
+
maxDepth: serialized.maxDepth,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Convert PersistedTaskState to JSON-serializable format
|
|
137
|
+
*/
|
|
138
|
+
function serializeState(state: PersistedTaskState): SerializedPersistedTaskState {
|
|
139
|
+
return {
|
|
140
|
+
chainId: state.chainId,
|
|
141
|
+
chain: serializeTaskChain(state.chain),
|
|
142
|
+
status: state.status,
|
|
143
|
+
lastTaskId: state.lastTaskId,
|
|
144
|
+
checkpoint: state.checkpoint,
|
|
145
|
+
savedAt: state.savedAt.toISOString(),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Convert JSON data back to PersistedTaskState
|
|
151
|
+
*/
|
|
152
|
+
function deserializeState(data: SerializedPersistedTaskState): PersistedTaskState {
|
|
153
|
+
return {
|
|
154
|
+
chainId: data.chainId,
|
|
155
|
+
chain: deserializeTaskChain(data.chain),
|
|
156
|
+
status: data.status,
|
|
157
|
+
lastTaskId: data.lastTaskId,
|
|
158
|
+
checkpoint: data.checkpoint,
|
|
159
|
+
savedAt: new Date(data.savedAt),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Sanitize chainId to prevent path traversal attacks
|
|
165
|
+
*/
|
|
166
|
+
function sanitizeChainId(chainId: string): string {
|
|
167
|
+
// Remove any path separators and dots that could cause traversal
|
|
168
|
+
return chainId.replace(/[/\\.:]/g, "_");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Create a TaskPersistence instance
|
|
173
|
+
* @param baseDir Base directory for task storage (default: '.vellum/tasks/')
|
|
174
|
+
*/
|
|
175
|
+
export function createTaskPersistence(baseDir = ".vellum/tasks/"): TaskPersistence {
|
|
176
|
+
const resolvedBaseDir = path.resolve(baseDir);
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Ensure the storage directory exists
|
|
180
|
+
*/
|
|
181
|
+
async function ensureDirectory(): Promise<void> {
|
|
182
|
+
await fs.mkdir(resolvedBaseDir, { recursive: true });
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Get the file path for a chain ID
|
|
187
|
+
*/
|
|
188
|
+
function getFilePath(chainId: string): string {
|
|
189
|
+
const safeChainId = sanitizeChainId(chainId);
|
|
190
|
+
return path.join(resolvedBaseDir, `${safeChainId}.json`);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
async saveTaskState(state: PersistedTaskState): Promise<void> {
|
|
195
|
+
await ensureDirectory();
|
|
196
|
+
const filePath = getFilePath(state.chainId);
|
|
197
|
+
const serialized = serializeState(state);
|
|
198
|
+
await fs.writeFile(filePath, JSON.stringify(serialized, null, 2), "utf-8");
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
async loadTaskState(chainId: string): Promise<PersistedTaskState | null> {
|
|
202
|
+
const filePath = getFilePath(chainId);
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
206
|
+
const data = JSON.parse(content) as SerializedPersistedTaskState;
|
|
207
|
+
return deserializeState(data);
|
|
208
|
+
} catch (error) {
|
|
209
|
+
// Return null if file doesn't exist
|
|
210
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
throw error;
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
async listResumable(): Promise<{ chainId: string; savedAt: Date; status: string }[]> {
|
|
218
|
+
try {
|
|
219
|
+
await ensureDirectory();
|
|
220
|
+
const files = await fs.readdir(resolvedBaseDir);
|
|
221
|
+
const jsonFiles = files.filter((f) => f.endsWith(".json"));
|
|
222
|
+
|
|
223
|
+
const results: { chainId: string; savedAt: Date; status: string }[] = [];
|
|
224
|
+
|
|
225
|
+
for (const file of jsonFiles) {
|
|
226
|
+
try {
|
|
227
|
+
const filePath = path.join(resolvedBaseDir, file);
|
|
228
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
229
|
+
const data = JSON.parse(content) as SerializedPersistedTaskState;
|
|
230
|
+
|
|
231
|
+
// Only include resumable states (running or paused)
|
|
232
|
+
if (data.status === "running" || data.status === "paused") {
|
|
233
|
+
results.push({
|
|
234
|
+
chainId: data.chainId,
|
|
235
|
+
savedAt: new Date(data.savedAt),
|
|
236
|
+
status: data.status,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
} catch {
|
|
240
|
+
// Skip files that can't be parsed
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Sort by savedAt descending (most recent first)
|
|
245
|
+
results.sort((a, b) => b.savedAt.getTime() - a.savedAt.getTime());
|
|
246
|
+
|
|
247
|
+
return results;
|
|
248
|
+
} catch (error) {
|
|
249
|
+
// Return empty list if directory doesn't exist
|
|
250
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
|
|
251
|
+
return [];
|
|
252
|
+
}
|
|
253
|
+
throw error;
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
|
|
257
|
+
async deleteTaskState(chainId: string): Promise<boolean> {
|
|
258
|
+
const filePath = getFilePath(chainId);
|
|
259
|
+
|
|
260
|
+
try {
|
|
261
|
+
await fs.unlink(filePath);
|
|
262
|
+
return true;
|
|
263
|
+
} catch (error) {
|
|
264
|
+
// Return false if file doesn't exist
|
|
265
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
throw error;
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
}
|