@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,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TUI Adapters
|
|
3
|
+
*
|
|
4
|
+
* Adapters for integrating external systems with the Vellum TUI.
|
|
5
|
+
*
|
|
6
|
+
* @module tui/adapters
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Agent Adapter - AgentLoop ↔ Context integration
|
|
10
|
+
export {
|
|
11
|
+
type AdapterDispatchers,
|
|
12
|
+
type AgentAdapter,
|
|
13
|
+
createAgentAdapter,
|
|
14
|
+
type UseAgentAdapterOptions,
|
|
15
|
+
type UseAgentAdapterReturn,
|
|
16
|
+
useAgentAdapter,
|
|
17
|
+
} from "./agent-adapter.js";
|
|
18
|
+
|
|
19
|
+
// Message Adapter - Session ↔ UI message conversion
|
|
20
|
+
export {
|
|
21
|
+
createUIMessage,
|
|
22
|
+
getSessionToolIds,
|
|
23
|
+
sessionHasToolCalls,
|
|
24
|
+
sessionHasToolResults,
|
|
25
|
+
toSessionMessage,
|
|
26
|
+
toSessionMessages,
|
|
27
|
+
toUIMessage,
|
|
28
|
+
toUIMessages,
|
|
29
|
+
} from "./message-adapter.js";
|
|
30
|
+
// Persistence Bridge - Advanced persistence integration
|
|
31
|
+
export {
|
|
32
|
+
createPersistenceBridge,
|
|
33
|
+
PersistenceBridge,
|
|
34
|
+
type PersistenceBridgeCallbacks,
|
|
35
|
+
type PersistenceBridgeOptions,
|
|
36
|
+
type SyncResult,
|
|
37
|
+
type SyncState,
|
|
38
|
+
} from "./persistence-bridge.js";
|
|
39
|
+
// Session Adapter - Session persistence
|
|
40
|
+
export {
|
|
41
|
+
createMemorySessionStorage,
|
|
42
|
+
createSessionAdapter,
|
|
43
|
+
type SessionAdapter,
|
|
44
|
+
type SessionStorage,
|
|
45
|
+
type UseSessionAdapterOptions,
|
|
46
|
+
type UseSessionAdapterReturn,
|
|
47
|
+
useSessionAdapter,
|
|
48
|
+
} from "./session-adapter.js";
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message Adapter for Session ↔ UI Message Conversion
|
|
3
|
+
*
|
|
4
|
+
* Provides bidirectional conversion between @vellum/core SessionMessage
|
|
5
|
+
* and TUI Message types for seamless integration between the agent loop
|
|
6
|
+
* and the user interface.
|
|
7
|
+
*
|
|
8
|
+
* @module tui/adapters/message-adapter
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
SessionMessage,
|
|
13
|
+
SessionMessagePart,
|
|
14
|
+
SessionRole,
|
|
15
|
+
SessionTextPart,
|
|
16
|
+
SessionToolPart,
|
|
17
|
+
SessionToolResultPart,
|
|
18
|
+
} from "@vellum/core";
|
|
19
|
+
import { createId } from "@vellum/shared";
|
|
20
|
+
import type { Message, MessageRole, ToolCallInfo } from "../context/MessagesContext.js";
|
|
21
|
+
|
|
22
|
+
// =============================================================================
|
|
23
|
+
// Role Mapping
|
|
24
|
+
// =============================================================================
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Map from session role to UI message role
|
|
28
|
+
*/
|
|
29
|
+
const SESSION_TO_UI_ROLE: Record<SessionRole, MessageRole> = {
|
|
30
|
+
user: "user",
|
|
31
|
+
assistant: "assistant",
|
|
32
|
+
system: "system",
|
|
33
|
+
tool_result: "tool",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Map from UI message role to session role
|
|
38
|
+
*/
|
|
39
|
+
const UI_TO_SESSION_ROLE: Record<MessageRole, SessionRole> = {
|
|
40
|
+
user: "user",
|
|
41
|
+
assistant: "assistant",
|
|
42
|
+
system: "system",
|
|
43
|
+
tool: "tool_result",
|
|
44
|
+
tool_group: "tool_result", // tool_group maps to tool_result for session storage
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// =============================================================================
|
|
48
|
+
// Content Extraction Helpers
|
|
49
|
+
// =============================================================================
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Extract text content from session message parts
|
|
53
|
+
*
|
|
54
|
+
* @param parts - Array of session message parts
|
|
55
|
+
* @returns Combined text content
|
|
56
|
+
*/
|
|
57
|
+
function extractTextContent(parts: readonly SessionMessagePart[]): string {
|
|
58
|
+
return parts
|
|
59
|
+
.filter((part): part is SessionTextPart => part.type === "text")
|
|
60
|
+
.map((part) => part.text)
|
|
61
|
+
.join("\n");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Extract reasoning content from session message parts
|
|
66
|
+
*
|
|
67
|
+
* @param parts - Array of session message parts
|
|
68
|
+
* @returns Combined reasoning content or undefined
|
|
69
|
+
*/
|
|
70
|
+
function extractReasoningContent(parts: readonly SessionMessagePart[]): string | undefined {
|
|
71
|
+
const reasoningParts = parts.filter((part) => part.type === "reasoning");
|
|
72
|
+
if (reasoningParts.length === 0) {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
return reasoningParts.map((part) => part.text).join("");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Extract tool calls from session message parts
|
|
80
|
+
*
|
|
81
|
+
* @param parts - Array of session message parts
|
|
82
|
+
* @returns Array of tool call info objects
|
|
83
|
+
*/
|
|
84
|
+
function extractToolCalls(parts: readonly SessionMessagePart[]): readonly ToolCallInfo[] {
|
|
85
|
+
return parts
|
|
86
|
+
.filter((part): part is SessionToolPart => part.type === "tool")
|
|
87
|
+
.map((part) => ({
|
|
88
|
+
id: part.id,
|
|
89
|
+
name: part.name,
|
|
90
|
+
arguments: part.input,
|
|
91
|
+
status: "completed" as const,
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Extract tool results from session message parts
|
|
97
|
+
*
|
|
98
|
+
* @param parts - Array of session message parts
|
|
99
|
+
* @returns Array of tool call info with results
|
|
100
|
+
*/
|
|
101
|
+
function extractToolResults(parts: readonly SessionMessagePart[]): readonly ToolCallInfo[] {
|
|
102
|
+
return parts
|
|
103
|
+
.filter((part): part is SessionToolResultPart => part.type === "tool_result")
|
|
104
|
+
.map((part) => ({
|
|
105
|
+
id: part.toolId,
|
|
106
|
+
name: "tool_result",
|
|
107
|
+
arguments: {},
|
|
108
|
+
result: part.content,
|
|
109
|
+
status: part.isError ? ("error" as const) : ("completed" as const),
|
|
110
|
+
}));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// =============================================================================
|
|
114
|
+
// Session → UI Conversion
|
|
115
|
+
// =============================================================================
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Convert a single session message to UI message format
|
|
119
|
+
*
|
|
120
|
+
* @param sessionMessage - Message from session/core
|
|
121
|
+
* @returns UI message for display
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* ```typescript
|
|
125
|
+
* const sessionMsg = createUserMessage([SessionParts.text("Hello")]);
|
|
126
|
+
* const uiMsg = toUIMessage(sessionMsg);
|
|
127
|
+
* // { id: "...", role: "user", content: "Hello", timestamp: Date, ... }
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
export function toUIMessage(sessionMessage: SessionMessage): Message {
|
|
131
|
+
const textContent = extractTextContent(sessionMessage.parts);
|
|
132
|
+
const reasoningContent = extractReasoningContent(sessionMessage.parts);
|
|
133
|
+
const toolCalls = extractToolCalls(sessionMessage.parts);
|
|
134
|
+
const toolResults = extractToolResults(sessionMessage.parts);
|
|
135
|
+
|
|
136
|
+
// Combine tool calls and tool results
|
|
137
|
+
const allToolCalls = [...toolCalls, ...toolResults];
|
|
138
|
+
|
|
139
|
+
// Build content - for tool_result messages, stringify the result if no text
|
|
140
|
+
let content = textContent;
|
|
141
|
+
if (!content && sessionMessage.role === "tool_result" && toolResults.length > 0) {
|
|
142
|
+
content = toolResults
|
|
143
|
+
.map((tr) => (typeof tr.result === "string" ? tr.result : JSON.stringify(tr.result, null, 2)))
|
|
144
|
+
.join("\n");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
id: sessionMessage.id,
|
|
149
|
+
role: SESSION_TO_UI_ROLE[sessionMessage.role],
|
|
150
|
+
content,
|
|
151
|
+
timestamp: new Date(sessionMessage.metadata.createdAt),
|
|
152
|
+
isStreaming: false,
|
|
153
|
+
toolCalls: allToolCalls.length > 0 ? allToolCalls : undefined,
|
|
154
|
+
...(reasoningContent ? { thinking: reasoningContent } : {}),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Convert an array of session messages to UI messages
|
|
160
|
+
*
|
|
161
|
+
* @param sessionMessages - Array of session messages
|
|
162
|
+
* @returns Array of UI messages
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```typescript
|
|
166
|
+
* const session = { messages: [...] };
|
|
167
|
+
* const uiMessages = toUIMessages(session.messages);
|
|
168
|
+
* ```
|
|
169
|
+
*/
|
|
170
|
+
export function toUIMessages(sessionMessages: readonly SessionMessage[]): readonly Message[] {
|
|
171
|
+
return sessionMessages.map(toUIMessage);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// =============================================================================
|
|
175
|
+
// UI → Session Conversion
|
|
176
|
+
// =============================================================================
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Convert a UI message to session message format
|
|
180
|
+
*
|
|
181
|
+
* @param uiMessage - UI message from MessagesContext
|
|
182
|
+
* @returns Session message for core/agent loop
|
|
183
|
+
*
|
|
184
|
+
* @example
|
|
185
|
+
* ```typescript
|
|
186
|
+
* const uiMsg = { id: "1", role: "user", content: "Hello", timestamp: new Date() };
|
|
187
|
+
* const sessionMsg = toSessionMessage(uiMsg);
|
|
188
|
+
* ```
|
|
189
|
+
*/
|
|
190
|
+
export function toSessionMessage(uiMessage: Message): SessionMessage {
|
|
191
|
+
const parts: SessionMessagePart[] = [];
|
|
192
|
+
|
|
193
|
+
if (uiMessage.thinking) {
|
|
194
|
+
parts.push({
|
|
195
|
+
type: "reasoning",
|
|
196
|
+
text: uiMessage.thinking,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Add text content if present
|
|
201
|
+
if (uiMessage.content) {
|
|
202
|
+
parts.push({
|
|
203
|
+
type: "text",
|
|
204
|
+
text: uiMessage.content,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Add tool calls if present (for assistant messages)
|
|
209
|
+
if (uiMessage.toolCalls && uiMessage.role === "assistant") {
|
|
210
|
+
for (const toolCall of uiMessage.toolCalls) {
|
|
211
|
+
parts.push({
|
|
212
|
+
type: "tool",
|
|
213
|
+
id: toolCall.id,
|
|
214
|
+
name: toolCall.name,
|
|
215
|
+
input: toolCall.arguments,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Add tool results if present (for tool messages)
|
|
221
|
+
if (uiMessage.toolCalls && uiMessage.role === "tool") {
|
|
222
|
+
for (const toolCall of uiMessage.toolCalls) {
|
|
223
|
+
parts.push({
|
|
224
|
+
type: "tool_result",
|
|
225
|
+
toolId: toolCall.id,
|
|
226
|
+
content: toolCall.result ?? "",
|
|
227
|
+
isError: toolCall.status === "error",
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
id: uiMessage.id,
|
|
234
|
+
role: UI_TO_SESSION_ROLE[uiMessage.role],
|
|
235
|
+
parts,
|
|
236
|
+
metadata: {
|
|
237
|
+
createdAt: uiMessage.timestamp.getTime(),
|
|
238
|
+
completedAt: uiMessage.isStreaming ? undefined : uiMessage.timestamp.getTime(),
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Convert an array of UI messages to session messages
|
|
245
|
+
*
|
|
246
|
+
* @param uiMessages - Array of UI messages
|
|
247
|
+
* @returns Array of session messages
|
|
248
|
+
*
|
|
249
|
+
* @example
|
|
250
|
+
* ```typescript
|
|
251
|
+
* const chatHistory = messagesState.messages;
|
|
252
|
+
* const sessionMsgs = toSessionMessages(chatHistory);
|
|
253
|
+
* ```
|
|
254
|
+
*/
|
|
255
|
+
export function toSessionMessages(uiMessages: readonly Message[]): readonly SessionMessage[] {
|
|
256
|
+
return uiMessages.map(toSessionMessage);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// =============================================================================
|
|
260
|
+
// Utility Functions
|
|
261
|
+
// =============================================================================
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Create a new UI message with generated ID and timestamp
|
|
265
|
+
*
|
|
266
|
+
* @param role - Message role
|
|
267
|
+
* @param content - Message content
|
|
268
|
+
* @param options - Optional additional properties
|
|
269
|
+
* @returns New UI message
|
|
270
|
+
*/
|
|
271
|
+
export function createUIMessage(
|
|
272
|
+
role: MessageRole,
|
|
273
|
+
content: string,
|
|
274
|
+
options: Partial<Omit<Message, "id" | "role" | "content" | "timestamp">> = {}
|
|
275
|
+
): Message {
|
|
276
|
+
return {
|
|
277
|
+
id: createId(),
|
|
278
|
+
role,
|
|
279
|
+
content,
|
|
280
|
+
timestamp: new Date(),
|
|
281
|
+
...options,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Check if a session message contains tool calls
|
|
287
|
+
*
|
|
288
|
+
* @param sessionMessage - Session message to check
|
|
289
|
+
* @returns True if message has tool calls
|
|
290
|
+
*/
|
|
291
|
+
export function sessionHasToolCalls(sessionMessage: SessionMessage): boolean {
|
|
292
|
+
return sessionMessage.parts.some((part) => part.type === "tool");
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Check if a session message contains tool results
|
|
297
|
+
*
|
|
298
|
+
* @param sessionMessage - Session message to check
|
|
299
|
+
* @returns True if message has tool results
|
|
300
|
+
*/
|
|
301
|
+
export function sessionHasToolResults(sessionMessage: SessionMessage): boolean {
|
|
302
|
+
return sessionMessage.parts.some((part) => part.type === "tool_result");
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Get all tool IDs from a session message
|
|
307
|
+
*
|
|
308
|
+
* @param sessionMessage - Session message to extract from
|
|
309
|
+
* @returns Array of tool call IDs
|
|
310
|
+
*/
|
|
311
|
+
export function getSessionToolIds(sessionMessage: SessionMessage): readonly string[] {
|
|
312
|
+
return sessionMessage.parts
|
|
313
|
+
.filter((part): part is SessionToolPart => part.type === "tool")
|
|
314
|
+
.map((part) => part.id);
|
|
315
|
+
}
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistence Bridge
|
|
3
|
+
*
|
|
4
|
+
* Provides bidirectional conversion and synchronization between
|
|
5
|
+
* TUI Message types and @vellum/core SessionMessage types for
|
|
6
|
+
* advanced persistence operations.
|
|
7
|
+
*
|
|
8
|
+
* @module tui/adapters/persistence-bridge
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { PersistenceManager, Session, SessionMessage } from "@vellum/core";
|
|
12
|
+
import type { Message } from "../context/MessagesContext.js";
|
|
13
|
+
import { toSessionMessage, toUIMessage } from "./message-adapter.js";
|
|
14
|
+
|
|
15
|
+
// =============================================================================
|
|
16
|
+
// Types
|
|
17
|
+
// =============================================================================
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Synchronization state for incremental updates
|
|
21
|
+
*/
|
|
22
|
+
export interface SyncState {
|
|
23
|
+
/** Last synchronized message index */
|
|
24
|
+
lastSyncedIndex: number;
|
|
25
|
+
/** Session ID being tracked */
|
|
26
|
+
sessionId: string | null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Sync result from a synchronization operation
|
|
31
|
+
*/
|
|
32
|
+
export interface SyncResult {
|
|
33
|
+
/** Whether sync was successful */
|
|
34
|
+
success: boolean;
|
|
35
|
+
/** Number of messages synced */
|
|
36
|
+
syncedCount: number;
|
|
37
|
+
/** Error message if failed */
|
|
38
|
+
error?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Event callbacks for persistence bridge
|
|
43
|
+
*/
|
|
44
|
+
export interface PersistenceBridgeCallbacks {
|
|
45
|
+
/** Called when a save operation completes */
|
|
46
|
+
onSave?: (session: Session) => void;
|
|
47
|
+
/** Called when a save operation fails */
|
|
48
|
+
onError?: (error: Error) => void;
|
|
49
|
+
/** Called when a checkpoint is created */
|
|
50
|
+
onCheckpointCreated?: (checkpointId: string) => void;
|
|
51
|
+
/** Called when a rollback completes */
|
|
52
|
+
onRollbackComplete?: (success: boolean) => void;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Options for creating a persistence bridge
|
|
57
|
+
*/
|
|
58
|
+
export interface PersistenceBridgeOptions {
|
|
59
|
+
/** PersistenceManager instance */
|
|
60
|
+
persistence: PersistenceManager;
|
|
61
|
+
/** Optional callbacks */
|
|
62
|
+
callbacks?: PersistenceBridgeCallbacks;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// =============================================================================
|
|
66
|
+
// Persistence Bridge Class
|
|
67
|
+
// =============================================================================
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Bridge between TUI messages and PersistenceManager.
|
|
71
|
+
*
|
|
72
|
+
* Handles:
|
|
73
|
+
* - Message ↔ SessionMessage bidirectional conversion
|
|
74
|
+
* - EventEmitter → callback bridging
|
|
75
|
+
* - Incremental synchronization (only sync changes)
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```typescript
|
|
79
|
+
* const bridge = createPersistenceBridge({
|
|
80
|
+
* persistence: persistenceManager,
|
|
81
|
+
* callbacks: {
|
|
82
|
+
* onSave: (session) => console.log('Saved:', session.metadata.id),
|
|
83
|
+
* onError: (err) => console.error('Error:', err.message),
|
|
84
|
+
* },
|
|
85
|
+
* });
|
|
86
|
+
*
|
|
87
|
+
* // Sync messages incrementally
|
|
88
|
+
* await bridge.syncMessages(uiMessages);
|
|
89
|
+
*
|
|
90
|
+
* // Full sync from persistence to UI
|
|
91
|
+
* const uiMessages = bridge.loadAsUIMessages();
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
export class PersistenceBridge {
|
|
95
|
+
private readonly persistence: PersistenceManager;
|
|
96
|
+
private readonly callbacks: PersistenceBridgeCallbacks;
|
|
97
|
+
private syncState: SyncState;
|
|
98
|
+
private eventListenersAttached = false;
|
|
99
|
+
|
|
100
|
+
constructor(options: PersistenceBridgeOptions) {
|
|
101
|
+
this.persistence = options.persistence;
|
|
102
|
+
this.callbacks = options.callbacks ?? {};
|
|
103
|
+
this.syncState = {
|
|
104
|
+
lastSyncedIndex: 0,
|
|
105
|
+
sessionId: null,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ===========================================================================
|
|
110
|
+
// Event Bridging
|
|
111
|
+
// ===========================================================================
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Attach event listeners to bridge PersistenceManager events to callbacks.
|
|
115
|
+
*/
|
|
116
|
+
attachEventListeners(): void {
|
|
117
|
+
if (this.eventListenersAttached) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
this.persistence.on("save", (session) => {
|
|
122
|
+
this.callbacks.onSave?.(session);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
this.persistence.on("error", (error) => {
|
|
126
|
+
this.callbacks.onError?.(error);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
this.eventListenersAttached = true;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Detach event listeners.
|
|
134
|
+
*/
|
|
135
|
+
detachEventListeners(): void {
|
|
136
|
+
if (!this.eventListenersAttached) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
this.persistence.removeAllListeners("save");
|
|
141
|
+
this.persistence.removeAllListeners("error");
|
|
142
|
+
this.eventListenersAttached = false;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ===========================================================================
|
|
146
|
+
// Message Conversion
|
|
147
|
+
// ===========================================================================
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Convert UI messages to session messages.
|
|
151
|
+
*
|
|
152
|
+
* @param messages - Array of UI messages
|
|
153
|
+
* @returns Array of session messages
|
|
154
|
+
*/
|
|
155
|
+
toSessionMessages(messages: readonly Message[]): SessionMessage[] {
|
|
156
|
+
return messages
|
|
157
|
+
.filter((msg): msg is Message => msg.role !== "tool" && msg.role !== "tool_group")
|
|
158
|
+
.map((msg) => toSessionMessage(msg));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Convert session messages to UI messages.
|
|
163
|
+
*
|
|
164
|
+
* @param sessionMessages - Array of session messages
|
|
165
|
+
* @returns Array of UI messages
|
|
166
|
+
*/
|
|
167
|
+
toUIMessages(sessionMessages: readonly SessionMessage[]): Message[] {
|
|
168
|
+
return sessionMessages.map((msg) => toUIMessage(msg));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Load current session messages as UI messages.
|
|
173
|
+
*
|
|
174
|
+
* @returns Array of UI messages, or empty array if no session
|
|
175
|
+
*/
|
|
176
|
+
loadAsUIMessages(): Message[] {
|
|
177
|
+
const session = this.persistence.currentSession;
|
|
178
|
+
if (!session) {
|
|
179
|
+
return [];
|
|
180
|
+
}
|
|
181
|
+
return this.toUIMessages(session.messages);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ===========================================================================
|
|
185
|
+
// Incremental Synchronization
|
|
186
|
+
// ===========================================================================
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Sync UI messages to persistence incrementally.
|
|
190
|
+
*
|
|
191
|
+
* Only processes messages that haven't been synced yet.
|
|
192
|
+
*
|
|
193
|
+
* @param messages - Current UI messages
|
|
194
|
+
* @returns Sync result
|
|
195
|
+
*/
|
|
196
|
+
async syncMessages(messages: readonly Message[]): Promise<SyncResult> {
|
|
197
|
+
const session = this.persistence.currentSession;
|
|
198
|
+
if (!session) {
|
|
199
|
+
return {
|
|
200
|
+
success: false,
|
|
201
|
+
syncedCount: 0,
|
|
202
|
+
error: "No active session",
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Reset sync state if session changed
|
|
207
|
+
if (this.syncState.sessionId !== session.metadata.id) {
|
|
208
|
+
this.syncState = {
|
|
209
|
+
lastSyncedIndex: 0,
|
|
210
|
+
sessionId: session.metadata.id,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Get new messages since last sync
|
|
215
|
+
const newMessages = messages.slice(this.syncState.lastSyncedIndex);
|
|
216
|
+
if (newMessages.length === 0) {
|
|
217
|
+
return {
|
|
218
|
+
success: true,
|
|
219
|
+
syncedCount: 0,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
// Convert and add each new message
|
|
225
|
+
for (const msg of newMessages) {
|
|
226
|
+
if (msg.role === "tool" || msg.role === "tool_group") {
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
const sessionMsg = toSessionMessage(msg);
|
|
230
|
+
await this.persistence.onMessage(sessionMsg);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Update sync state
|
|
234
|
+
this.syncState.lastSyncedIndex = messages.length;
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
success: true,
|
|
238
|
+
syncedCount: newMessages.length,
|
|
239
|
+
};
|
|
240
|
+
} catch (error) {
|
|
241
|
+
return {
|
|
242
|
+
success: false,
|
|
243
|
+
syncedCount: 0,
|
|
244
|
+
error: error instanceof Error ? error.message : String(error),
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Reset sync state (e.g., after rollback or session change).
|
|
251
|
+
*/
|
|
252
|
+
resetSyncState(): void {
|
|
253
|
+
this.syncState = {
|
|
254
|
+
lastSyncedIndex: 0,
|
|
255
|
+
sessionId: this.persistence.currentSession?.metadata.id ?? null,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// ===========================================================================
|
|
260
|
+
// Checkpoint Operations
|
|
261
|
+
// ===========================================================================
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Create a checkpoint and notify via callback.
|
|
265
|
+
*
|
|
266
|
+
* @param description - Optional checkpoint description
|
|
267
|
+
* @returns Checkpoint ID or null if failed
|
|
268
|
+
*/
|
|
269
|
+
async createCheckpoint(description?: string): Promise<string | null> {
|
|
270
|
+
try {
|
|
271
|
+
const checkpointId = await this.persistence.createCheckpointAt(description);
|
|
272
|
+
this.callbacks.onCheckpointCreated?.(checkpointId);
|
|
273
|
+
return checkpointId;
|
|
274
|
+
} catch (error) {
|
|
275
|
+
this.callbacks.onError?.(error instanceof Error ? error : new Error(String(error)));
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Rollback to a checkpoint and notify via callback.
|
|
282
|
+
*
|
|
283
|
+
* @param checkpointId - ID of checkpoint to rollback to
|
|
284
|
+
* @returns Success status
|
|
285
|
+
*/
|
|
286
|
+
async rollbackToCheckpoint(checkpointId: string): Promise<boolean> {
|
|
287
|
+
try {
|
|
288
|
+
const success = await this.persistence.rollbackToCheckpoint(checkpointId);
|
|
289
|
+
this.callbacks.onRollbackComplete?.(success);
|
|
290
|
+
if (success) {
|
|
291
|
+
this.resetSyncState();
|
|
292
|
+
}
|
|
293
|
+
return success;
|
|
294
|
+
} catch (error) {
|
|
295
|
+
this.callbacks.onError?.(error instanceof Error ? error : new Error(String(error)));
|
|
296
|
+
this.callbacks.onRollbackComplete?.(false);
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// ===========================================================================
|
|
302
|
+
// Disposal
|
|
303
|
+
// ===========================================================================
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Dispose the bridge and clean up resources.
|
|
307
|
+
*/
|
|
308
|
+
dispose(): void {
|
|
309
|
+
this.detachEventListeners();
|
|
310
|
+
this.syncState = {
|
|
311
|
+
lastSyncedIndex: 0,
|
|
312
|
+
sessionId: null,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// =============================================================================
|
|
318
|
+
// Factory Function
|
|
319
|
+
// =============================================================================
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Create a new persistence bridge instance.
|
|
323
|
+
*
|
|
324
|
+
* @param options - Bridge configuration options
|
|
325
|
+
* @returns Configured PersistenceBridge instance
|
|
326
|
+
*/
|
|
327
|
+
export function createPersistenceBridge(options: PersistenceBridgeOptions): PersistenceBridge {
|
|
328
|
+
const bridge = new PersistenceBridge(options);
|
|
329
|
+
bridge.attachEventListeners();
|
|
330
|
+
return bridge;
|
|
331
|
+
}
|