@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.
Files changed (446) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +411 -0
  3. package/__fixtures__/responses/code-generation.json +42 -0
  4. package/__fixtures__/responses/error-response.json +20 -0
  5. package/__fixtures__/responses/hello-world.json +32 -0
  6. package/dist/auth-6MCXESOH.js +26 -0
  7. package/dist/chunk-SECXJGWA.js +597 -0
  8. package/dist/index.js +34023 -0
  9. package/package.json +67 -0
  10. package/src/__tests__/commands.e2e.test.ts +728 -0
  11. package/src/__tests__/credentials.test.ts +713 -0
  12. package/src/__tests__/mode-e2e.test.ts +391 -0
  13. package/src/__tests__/tui-integration.test.tsx +1271 -0
  14. package/src/agents/__tests__/task-persistence.test.ts +235 -0
  15. package/src/agents/commands/delegate.ts +240 -0
  16. package/src/agents/commands/index.ts +10 -0
  17. package/src/agents/commands/resume.ts +335 -0
  18. package/src/agents/index.ts +29 -0
  19. package/src/agents/task-persistence.ts +272 -0
  20. package/src/agents/task-resumption.ts +242 -0
  21. package/src/app.tsx +4737 -0
  22. package/src/commands/__tests__/.gitkeep +1 -0
  23. package/src/commands/__tests__/agents.test.ts +606 -0
  24. package/src/commands/__tests__/auth.test.ts +626 -0
  25. package/src/commands/__tests__/autocomplete.test.ts +683 -0
  26. package/src/commands/__tests__/batch.test.ts +287 -0
  27. package/src/commands/__tests__/chain-pipe-parser.test.ts +654 -0
  28. package/src/commands/__tests__/completion.test.ts +238 -0
  29. package/src/commands/__tests__/core.test.ts +363 -0
  30. package/src/commands/__tests__/executor.test.ts +496 -0
  31. package/src/commands/__tests__/exit-codes.test.ts +220 -0
  32. package/src/commands/__tests__/init.test.ts +243 -0
  33. package/src/commands/__tests__/language.test.ts +353 -0
  34. package/src/commands/__tests__/mode-cli.test.ts +667 -0
  35. package/src/commands/__tests__/model.test.ts +277 -0
  36. package/src/commands/__tests__/parser.test.ts +493 -0
  37. package/src/commands/__tests__/performance.bench.ts +380 -0
  38. package/src/commands/__tests__/registry.test.ts +534 -0
  39. package/src/commands/__tests__/resume.test.ts +449 -0
  40. package/src/commands/__tests__/security.test.ts +845 -0
  41. package/src/commands/__tests__/stream-json.test.ts +372 -0
  42. package/src/commands/__tests__/user-commands.test.ts +597 -0
  43. package/src/commands/adapters.ts +267 -0
  44. package/src/commands/agent.ts +395 -0
  45. package/src/commands/agents/generate.ts +506 -0
  46. package/src/commands/agents/index.ts +272 -0
  47. package/src/commands/agents/show.ts +271 -0
  48. package/src/commands/agents/validate.ts +387 -0
  49. package/src/commands/auth.ts +883 -0
  50. package/src/commands/autocomplete.ts +480 -0
  51. package/src/commands/batch/command.ts +388 -0
  52. package/src/commands/batch/executor.ts +361 -0
  53. package/src/commands/batch/index.ts +12 -0
  54. package/src/commands/commit.ts +235 -0
  55. package/src/commands/completion/index.ts +371 -0
  56. package/src/commands/condense.ts +191 -0
  57. package/src/commands/config.ts +344 -0
  58. package/src/commands/context-provider.ts +173 -0
  59. package/src/commands/copy.ts +329 -0
  60. package/src/commands/core/clear.ts +38 -0
  61. package/src/commands/core/exit.ts +43 -0
  62. package/src/commands/core/help.ts +354 -0
  63. package/src/commands/core/index.ts +15 -0
  64. package/src/commands/cost.ts +179 -0
  65. package/src/commands/credentials.tsx +618 -0
  66. package/src/commands/custom-agents/__tests__/custom-agents.test.ts +709 -0
  67. package/src/commands/custom-agents/create.ts +377 -0
  68. package/src/commands/custom-agents/export.ts +135 -0
  69. package/src/commands/custom-agents/import.ts +199 -0
  70. package/src/commands/custom-agents/index.ts +372 -0
  71. package/src/commands/custom-agents/info.ts +318 -0
  72. package/src/commands/custom-agents/list.ts +267 -0
  73. package/src/commands/custom-agents/validate.ts +388 -0
  74. package/src/commands/diff-mode.ts +241 -0
  75. package/src/commands/env.ts +53 -0
  76. package/src/commands/executor.ts +579 -0
  77. package/src/commands/exit-codes.ts +202 -0
  78. package/src/commands/index.ts +701 -0
  79. package/src/commands/init/index.ts +15 -0
  80. package/src/commands/init/prompts.ts +366 -0
  81. package/src/commands/init/templates/commands-readme.md +80 -0
  82. package/src/commands/init/templates/example-command.md +79 -0
  83. package/src/commands/init/templates/example-skill.md +168 -0
  84. package/src/commands/init/templates/example-workflow.md +101 -0
  85. package/src/commands/init/templates/prompts-readme.md +52 -0
  86. package/src/commands/init/templates/rules-readme.md +63 -0
  87. package/src/commands/init/templates/skills-readme.md +83 -0
  88. package/src/commands/init/templates/workflows-readme.md +94 -0
  89. package/src/commands/init.ts +391 -0
  90. package/src/commands/install.ts +90 -0
  91. package/src/commands/language.ts +191 -0
  92. package/src/commands/loaders/.gitkeep +1 -0
  93. package/src/commands/lsp.ts +199 -0
  94. package/src/commands/markdown-commands.ts +253 -0
  95. package/src/commands/mcp.ts +588 -0
  96. package/src/commands/memory/export.ts +341 -0
  97. package/src/commands/memory/index.ts +148 -0
  98. package/src/commands/memory/list.ts +261 -0
  99. package/src/commands/memory/search.ts +346 -0
  100. package/src/commands/memory/utils.ts +15 -0
  101. package/src/commands/metrics.ts +75 -0
  102. package/src/commands/migrate/index.ts +16 -0
  103. package/src/commands/migrate/prompts.ts +477 -0
  104. package/src/commands/mode.ts +331 -0
  105. package/src/commands/model.ts +298 -0
  106. package/src/commands/onboard.ts +205 -0
  107. package/src/commands/open.ts +169 -0
  108. package/src/commands/output/stream-json.ts +373 -0
  109. package/src/commands/parser/chain-parser.ts +370 -0
  110. package/src/commands/parser/index.ts +29 -0
  111. package/src/commands/parser/pipe-parser.ts +480 -0
  112. package/src/commands/parser.ts +588 -0
  113. package/src/commands/persistence.ts +355 -0
  114. package/src/commands/progress.ts +18 -0
  115. package/src/commands/prompt/index.ts +17 -0
  116. package/src/commands/prompt/validate.ts +621 -0
  117. package/src/commands/prompt-priority.ts +401 -0
  118. package/src/commands/registry.ts +374 -0
  119. package/src/commands/sandbox/index.ts +131 -0
  120. package/src/commands/security/index.ts +21 -0
  121. package/src/commands/security/input-sanitizer.ts +168 -0
  122. package/src/commands/security/permission-checker.ts +456 -0
  123. package/src/commands/security/sensitive-data.ts +350 -0
  124. package/src/commands/session/delete.ts +38 -0
  125. package/src/commands/session/export.ts +39 -0
  126. package/src/commands/session/index.ts +26 -0
  127. package/src/commands/session/list.ts +26 -0
  128. package/src/commands/session/resume.ts +562 -0
  129. package/src/commands/session/search.ts +434 -0
  130. package/src/commands/session/show.ts +26 -0
  131. package/src/commands/settings.ts +368 -0
  132. package/src/commands/setup.ts +23 -0
  133. package/src/commands/shell/index.ts +16 -0
  134. package/src/commands/shell/setup.ts +422 -0
  135. package/src/commands/shell-init.ts +50 -0
  136. package/src/commands/shell-integration/index.ts +194 -0
  137. package/src/commands/skill.ts +1220 -0
  138. package/src/commands/spec.ts +558 -0
  139. package/src/commands/status.ts +246 -0
  140. package/src/commands/theme.ts +211 -0
  141. package/src/commands/think.ts +551 -0
  142. package/src/commands/trust.ts +211 -0
  143. package/src/commands/tutorial.ts +522 -0
  144. package/src/commands/types.ts +512 -0
  145. package/src/commands/update.ts +274 -0
  146. package/src/commands/usage.ts +213 -0
  147. package/src/commands/user-commands.ts +630 -0
  148. package/src/commands/utils.ts +142 -0
  149. package/src/commands/vim.ts +152 -0
  150. package/src/commands/workflow.ts +257 -0
  151. package/src/components/header.tsx +25 -0
  152. package/src/components/input.tsx +25 -0
  153. package/src/components/message-list.tsx +32 -0
  154. package/src/components/status-bar.tsx +23 -0
  155. package/src/index.tsx +614 -0
  156. package/src/onboarding/__tests__/tutorial.test.ts +740 -0
  157. package/src/onboarding/index.ts +69 -0
  158. package/src/onboarding/tips/index.ts +9 -0
  159. package/src/onboarding/tips/tip-engine.ts +459 -0
  160. package/src/onboarding/tutorial/index.ts +88 -0
  161. package/src/onboarding/tutorial/lessons/basics.ts +151 -0
  162. package/src/onboarding/tutorial/lessons/index.ts +151 -0
  163. package/src/onboarding/tutorial/lessons/modes.ts +230 -0
  164. package/src/onboarding/tutorial/lessons/tools.ts +172 -0
  165. package/src/onboarding/tutorial/progress-tracker.ts +350 -0
  166. package/src/onboarding/tutorial/storage.ts +249 -0
  167. package/src/onboarding/tutorial/tutorial-system.ts +462 -0
  168. package/src/onboarding/tutorial/types.ts +310 -0
  169. package/src/orchestrator-singleton.ts +129 -0
  170. package/src/shutdown.ts +33 -0
  171. package/src/test/e2e/assertions.ts +267 -0
  172. package/src/test/e2e/fixtures.ts +204 -0
  173. package/src/test/e2e/harness.ts +575 -0
  174. package/src/test/e2e/index.ts +57 -0
  175. package/src/test/e2e/types.ts +228 -0
  176. package/src/test/fixtures/__tests__/fake-response-loader.test.ts +314 -0
  177. package/src/test/fixtures/fake-response-loader.ts +314 -0
  178. package/src/test/fixtures/index.ts +20 -0
  179. package/src/tui/__tests__/mcp-panel.test.tsx +82 -0
  180. package/src/tui/__tests__/mcp-wiring.test.tsx +78 -0
  181. package/src/tui/__tests__/mode-components.test.tsx +395 -0
  182. package/src/tui/__tests__/permission-ask-flow.test.tsx +138 -0
  183. package/src/tui/__tests__/sidebar-panel-data.test.tsx +148 -0
  184. package/src/tui/__tests__/tools-panel-hotkeys.test.tsx +41 -0
  185. package/src/tui/adapters/agent-adapter.ts +1008 -0
  186. package/src/tui/adapters/index.ts +48 -0
  187. package/src/tui/adapters/message-adapter.ts +315 -0
  188. package/src/tui/adapters/persistence-bridge.ts +331 -0
  189. package/src/tui/adapters/session-adapter.ts +419 -0
  190. package/src/tui/buffered-stdout.ts +223 -0
  191. package/src/tui/components/AgentProgress.tsx +424 -0
  192. package/src/tui/components/Banner/AsciiArt.ts +160 -0
  193. package/src/tui/components/Banner/Banner.tsx +355 -0
  194. package/src/tui/components/Banner/ShimmerContext.tsx +131 -0
  195. package/src/tui/components/Banner/ShimmerText.tsx +193 -0
  196. package/src/tui/components/Banner/TypeWriterGradient.tsx +321 -0
  197. package/src/tui/components/Banner/index.ts +61 -0
  198. package/src/tui/components/Banner/useShimmer.ts +241 -0
  199. package/src/tui/components/ChatView.tsx +11 -0
  200. package/src/tui/components/Checkpoint/CheckpointDiffView.tsx +371 -0
  201. package/src/tui/components/Checkpoint/SnapshotCheckpointPanel.tsx +440 -0
  202. package/src/tui/components/Checkpoint/index.ts +19 -0
  203. package/src/tui/components/CostDisplay.tsx +226 -0
  204. package/src/tui/components/InitErrorBanner.tsx +122 -0
  205. package/src/tui/components/Input/Autocomplete.tsx +603 -0
  206. package/src/tui/components/Input/EnhancedCommandInput.tsx +471 -0
  207. package/src/tui/components/Input/HighlightedText.tsx +236 -0
  208. package/src/tui/components/Input/MentionAutocomplete.tsx +375 -0
  209. package/src/tui/components/Input/TextInput.tsx +1002 -0
  210. package/src/tui/components/Input/__tests__/Autocomplete.test.tsx +374 -0
  211. package/src/tui/components/Input/__tests__/TextInput.test.tsx +241 -0
  212. package/src/tui/components/Input/__tests__/highlight.test.ts +219 -0
  213. package/src/tui/components/Input/__tests__/slash-command-utils.test.ts +104 -0
  214. package/src/tui/components/Input/highlight.ts +362 -0
  215. package/src/tui/components/Input/index.ts +36 -0
  216. package/src/tui/components/Input/slash-command-utils.ts +135 -0
  217. package/src/tui/components/Layout.tsx +432 -0
  218. package/src/tui/components/McpPanel.tsx +137 -0
  219. package/src/tui/components/MemoryPanel.tsx +448 -0
  220. package/src/tui/components/Messages/CodeBlock.tsx +527 -0
  221. package/src/tui/components/Messages/DiffView.tsx +679 -0
  222. package/src/tui/components/Messages/ImageReference.tsx +89 -0
  223. package/src/tui/components/Messages/MarkdownBlock.tsx +228 -0
  224. package/src/tui/components/Messages/MarkdownRenderer.tsx +498 -0
  225. package/src/tui/components/Messages/MessageBubble.tsx +270 -0
  226. package/src/tui/components/Messages/MessageList.tsx +1719 -0
  227. package/src/tui/components/Messages/StreamingText.tsx +216 -0
  228. package/src/tui/components/Messages/ThinkingBlock.tsx +408 -0
  229. package/src/tui/components/Messages/ToolResultPreview.tsx +243 -0
  230. package/src/tui/components/Messages/__tests__/CodeBlock.test.tsx +296 -0
  231. package/src/tui/components/Messages/__tests__/DiffView.test.tsx +239 -0
  232. package/src/tui/components/Messages/__tests__/MarkdownRenderer.test.tsx +303 -0
  233. package/src/tui/components/Messages/__tests__/MessageBubble.test.tsx +268 -0
  234. package/src/tui/components/Messages/__tests__/MessageList.test.tsx +324 -0
  235. package/src/tui/components/Messages/__tests__/StreamingText.test.tsx +215 -0
  236. package/src/tui/components/Messages/index.ts +25 -0
  237. package/src/tui/components/ModeIndicator.tsx +177 -0
  238. package/src/tui/components/ModeSelector.tsx +216 -0
  239. package/src/tui/components/ModelSelector.tsx +339 -0
  240. package/src/tui/components/OnboardingWizard.tsx +670 -0
  241. package/src/tui/components/PhaseProgressIndicator.tsx +270 -0
  242. package/src/tui/components/RateLimitIndicator.tsx +82 -0
  243. package/src/tui/components/ScreenReaderLayout.tsx +295 -0
  244. package/src/tui/components/SettingsPanel.tsx +643 -0
  245. package/src/tui/components/Sidebar/SystemStatusPanel.tsx +284 -0
  246. package/src/tui/components/Sidebar/index.ts +9 -0
  247. package/src/tui/components/Status/ModelStatusBar.tsx +270 -0
  248. package/src/tui/components/Status/index.ts +12 -0
  249. package/src/tui/components/StatusBar/AgentModeIndicator.tsx +257 -0
  250. package/src/tui/components/StatusBar/ContextProgress.tsx +167 -0
  251. package/src/tui/components/StatusBar/FileChangesIndicator.tsx +62 -0
  252. package/src/tui/components/StatusBar/GitIndicator.tsx +89 -0
  253. package/src/tui/components/StatusBar/HeaderBar.tsx +126 -0
  254. package/src/tui/components/StatusBar/ModelIndicator.tsx +157 -0
  255. package/src/tui/components/StatusBar/PersistenceStatusIndicator.tsx +210 -0
  256. package/src/tui/components/StatusBar/ResilienceIndicator.tsx +106 -0
  257. package/src/tui/components/StatusBar/SandboxIndicator.tsx +167 -0
  258. package/src/tui/components/StatusBar/StatusBar.tsx +368 -0
  259. package/src/tui/components/StatusBar/ThinkingModeIndicator.tsx +170 -0
  260. package/src/tui/components/StatusBar/TokenBreakdown.tsx +246 -0
  261. package/src/tui/components/StatusBar/TokenCounter.tsx +135 -0
  262. package/src/tui/components/StatusBar/TrustModeIndicator.tsx +130 -0
  263. package/src/tui/components/StatusBar/WorkspaceIndicator.tsx +86 -0
  264. package/src/tui/components/StatusBar/__tests__/AgentModeIndicator.test.tsx +193 -0
  265. package/src/tui/components/StatusBar/__tests__/StatusBar.test.tsx +729 -0
  266. package/src/tui/components/StatusBar/index.ts +60 -0
  267. package/src/tui/components/TipBanner.tsx +115 -0
  268. package/src/tui/components/TodoItem.tsx +208 -0
  269. package/src/tui/components/TodoPanel.tsx +455 -0
  270. package/src/tui/components/Tools/ApprovalQueue.tsx +407 -0
  271. package/src/tui/components/Tools/OptionSelector.tsx +160 -0
  272. package/src/tui/components/Tools/PermissionDialog.tsx +286 -0
  273. package/src/tui/components/Tools/ToolParams.tsx +483 -0
  274. package/src/tui/components/Tools/ToolsPanel.tsx +178 -0
  275. package/src/tui/components/Tools/__tests__/PermissionDialog.test.tsx +510 -0
  276. package/src/tui/components/Tools/__tests__/ToolParams.test.tsx +432 -0
  277. package/src/tui/components/Tools/index.ts +21 -0
  278. package/src/tui/components/TrustPrompt.tsx +279 -0
  279. package/src/tui/components/UpdateBanner.tsx +166 -0
  280. package/src/tui/components/VimModeIndicator.tsx +112 -0
  281. package/src/tui/components/backtrack/BacktrackControls.tsx +402 -0
  282. package/src/tui/components/backtrack/index.ts +13 -0
  283. package/src/tui/components/common/AutoApprovalStatus.tsx +251 -0
  284. package/src/tui/components/common/CostWarning.tsx +294 -0
  285. package/src/tui/components/common/DynamicShortcutHints.tsx +209 -0
  286. package/src/tui/components/common/EnhancedLoadingIndicator.tsx +305 -0
  287. package/src/tui/components/common/ErrorBoundary.tsx +140 -0
  288. package/src/tui/components/common/GradientText.tsx +224 -0
  289. package/src/tui/components/common/HotkeyHelpModal.tsx +193 -0
  290. package/src/tui/components/common/HotkeyHints.tsx +70 -0
  291. package/src/tui/components/common/MaxSizedBox.tsx +354 -0
  292. package/src/tui/components/common/NewMessagesBadge.tsx +65 -0
  293. package/src/tui/components/common/ProtectedFileLegend.tsx +89 -0
  294. package/src/tui/components/common/ScrollIndicator.tsx +160 -0
  295. package/src/tui/components/common/Spinner.tsx +342 -0
  296. package/src/tui/components/common/StreamingIndicator.tsx +316 -0
  297. package/src/tui/components/common/VirtualizedList/VirtualizedList.tsx +428 -0
  298. package/src/tui/components/common/VirtualizedList/hooks/index.ts +19 -0
  299. package/src/tui/components/common/VirtualizedList/hooks/useBatchedScroll.ts +64 -0
  300. package/src/tui/components/common/VirtualizedList/hooks/useScrollAnchor.ts +290 -0
  301. package/src/tui/components/common/VirtualizedList/hooks/useVirtualization.ts +340 -0
  302. package/src/tui/components/common/VirtualizedList/index.ts +30 -0
  303. package/src/tui/components/common/VirtualizedList/types.ts +107 -0
  304. package/src/tui/components/common/__tests__/NewMessagesBadge.test.tsx +74 -0
  305. package/src/tui/components/common/__tests__/ScrollIndicator.test.tsx +193 -0
  306. package/src/tui/components/common/index.ts +110 -0
  307. package/src/tui/components/index.ts +79 -0
  308. package/src/tui/components/session/CheckpointPanel.tsx +323 -0
  309. package/src/tui/components/session/RollbackDialog.tsx +169 -0
  310. package/src/tui/components/session/SessionItem.tsx +136 -0
  311. package/src/tui/components/session/SessionListPanel.tsx +252 -0
  312. package/src/tui/components/session/SessionPicker.tsx +449 -0
  313. package/src/tui/components/session/SessionPreview.tsx +240 -0
  314. package/src/tui/components/session/__tests__/session.test.tsx +408 -0
  315. package/src/tui/components/session/index.ts +28 -0
  316. package/src/tui/components/session/types.ts +116 -0
  317. package/src/tui/components/theme/__tests__/tokens.test.ts +471 -0
  318. package/src/tui/components/theme/index.ts +227 -0
  319. package/src/tui/components/theme/tokens.ts +484 -0
  320. package/src/tui/config/defaults.ts +134 -0
  321. package/src/tui/config/index.ts +17 -0
  322. package/src/tui/context/AnimationContext.tsx +284 -0
  323. package/src/tui/context/AppContext.tsx +349 -0
  324. package/src/tui/context/BracketedPasteContext.tsx +372 -0
  325. package/src/tui/context/LspContext.tsx +192 -0
  326. package/src/tui/context/McpContext.tsx +325 -0
  327. package/src/tui/context/MessagesContext.tsx +870 -0
  328. package/src/tui/context/OverflowContext.tsx +213 -0
  329. package/src/tui/context/RateLimitContext.tsx +108 -0
  330. package/src/tui/context/ResilienceContext.tsx +275 -0
  331. package/src/tui/context/RootProvider.tsx +136 -0
  332. package/src/tui/context/ScrollContext.tsx +331 -0
  333. package/src/tui/context/ToolsContext.tsx +702 -0
  334. package/src/tui/context/__tests__/BracketedPasteContext.test.tsx +416 -0
  335. package/src/tui/context/index.ts +140 -0
  336. package/src/tui/enterprise-integration.ts +282 -0
  337. package/src/tui/hooks/__tests__/useBacktrack.test.tsx +138 -0
  338. package/src/tui/hooks/__tests__/useBracketedPaste.test.tsx +222 -0
  339. package/src/tui/hooks/__tests__/useCopyMode.test.tsx +336 -0
  340. package/src/tui/hooks/__tests__/useHotkeys.ctrl-input.test.tsx +96 -0
  341. package/src/tui/hooks/__tests__/useHotkeys.test.tsx +454 -0
  342. package/src/tui/hooks/__tests__/useInputHistory.test.tsx +660 -0
  343. package/src/tui/hooks/__tests__/useLineBuffer.test.ts +295 -0
  344. package/src/tui/hooks/__tests__/useModeController.test.ts +137 -0
  345. package/src/tui/hooks/__tests__/useModeShortcuts.test.tsx +142 -0
  346. package/src/tui/hooks/__tests__/useScrollController.test.ts +464 -0
  347. package/src/tui/hooks/__tests__/useVim.test.tsx +531 -0
  348. package/src/tui/hooks/index.ts +252 -0
  349. package/src/tui/hooks/useAgentLoop.ts +712 -0
  350. package/src/tui/hooks/useAlternateBuffer.ts +398 -0
  351. package/src/tui/hooks/useAnimatedScrollbar.ts +241 -0
  352. package/src/tui/hooks/useBacktrack.ts +443 -0
  353. package/src/tui/hooks/useBracketedPaste.ts +104 -0
  354. package/src/tui/hooks/useCollapsible.ts +240 -0
  355. package/src/tui/hooks/useCopyMode.ts +382 -0
  356. package/src/tui/hooks/useCostSummary.ts +75 -0
  357. package/src/tui/hooks/useDesktopNotification.ts +414 -0
  358. package/src/tui/hooks/useDiffMode.ts +44 -0
  359. package/src/tui/hooks/useFileChangeStats.ts +110 -0
  360. package/src/tui/hooks/useFileSuggestions.ts +284 -0
  361. package/src/tui/hooks/useFlickerDetector.ts +250 -0
  362. package/src/tui/hooks/useGitStatus.ts +200 -0
  363. package/src/tui/hooks/useHotkeys.ts +579 -0
  364. package/src/tui/hooks/useImagePaste.ts +114 -0
  365. package/src/tui/hooks/useInputHighlight.ts +145 -0
  366. package/src/tui/hooks/useInputHistory.ts +246 -0
  367. package/src/tui/hooks/useKeyboardScroll.ts +209 -0
  368. package/src/tui/hooks/useLineBuffer.ts +356 -0
  369. package/src/tui/hooks/useMentionAutocomplete.ts +235 -0
  370. package/src/tui/hooks/useModeController.ts +167 -0
  371. package/src/tui/hooks/useModeShortcuts.ts +196 -0
  372. package/src/tui/hooks/usePermissionHandler.ts +146 -0
  373. package/src/tui/hooks/usePersistence.ts +480 -0
  374. package/src/tui/hooks/usePersistenceShortcuts.ts +225 -0
  375. package/src/tui/hooks/usePlaceholderRotation.ts +143 -0
  376. package/src/tui/hooks/useProviderStatus.ts +270 -0
  377. package/src/tui/hooks/useRateLimitStatus.ts +90 -0
  378. package/src/tui/hooks/useScreenReader.ts +315 -0
  379. package/src/tui/hooks/useScrollController.ts +450 -0
  380. package/src/tui/hooks/useScrollEventBatcher.ts +185 -0
  381. package/src/tui/hooks/useSidebarPanelData.ts +115 -0
  382. package/src/tui/hooks/useSmoothScroll.ts +202 -0
  383. package/src/tui/hooks/useSnapshots.ts +300 -0
  384. package/src/tui/hooks/useStateAndRef.ts +50 -0
  385. package/src/tui/hooks/useTerminalSize.ts +206 -0
  386. package/src/tui/hooks/useToolApprovalController.ts +91 -0
  387. package/src/tui/hooks/useVim.ts +334 -0
  388. package/src/tui/hooks/useWorkspace.ts +56 -0
  389. package/src/tui/i18n/__tests__/init.test.ts +278 -0
  390. package/src/tui/i18n/__tests__/language-config.test.ts +199 -0
  391. package/src/tui/i18n/__tests__/locale-detection.test.ts +250 -0
  392. package/src/tui/i18n/__tests__/settings-integration.test.ts +262 -0
  393. package/src/tui/i18n/index.ts +72 -0
  394. package/src/tui/i18n/init.ts +131 -0
  395. package/src/tui/i18n/language-config.ts +106 -0
  396. package/src/tui/i18n/locale-detection.ts +173 -0
  397. package/src/tui/i18n/settings-integration.ts +557 -0
  398. package/src/tui/i18n/tui-namespace.ts +538 -0
  399. package/src/tui/i18n/types.ts +312 -0
  400. package/src/tui/index.ts +43 -0
  401. package/src/tui/lsp-integration.ts +409 -0
  402. package/src/tui/metrics-integration.ts +366 -0
  403. package/src/tui/plugins.ts +383 -0
  404. package/src/tui/resilience.ts +342 -0
  405. package/src/tui/sandbox-integration.ts +317 -0
  406. package/src/tui/services/clipboard.ts +348 -0
  407. package/src/tui/services/fuzzy-search.ts +441 -0
  408. package/src/tui/services/index.ts +72 -0
  409. package/src/tui/services/markdown-renderer.ts +565 -0
  410. package/src/tui/services/open-external.ts +247 -0
  411. package/src/tui/services/syntax-highlighter.ts +483 -0
  412. package/src/tui/slash-commands.ts +12 -0
  413. package/src/tui/theme/index.ts +15 -0
  414. package/src/tui/theme/provider.tsx +206 -0
  415. package/src/tui/tip-integration.ts +300 -0
  416. package/src/tui/types/__tests__/ink-extended.test.ts +121 -0
  417. package/src/tui/types/ink-extended.ts +87 -0
  418. package/src/tui/utils/__tests__/bracketedPaste.test.ts +231 -0
  419. package/src/tui/utils/__tests__/heightEstimator.test.ts +157 -0
  420. package/src/tui/utils/__tests__/text-width.test.ts +158 -0
  421. package/src/tui/utils/__tests__/textSanitizer.test.ts +266 -0
  422. package/src/tui/utils/__tests__/ui-sizing.test.ts +169 -0
  423. package/src/tui/utils/bracketedPaste.ts +107 -0
  424. package/src/tui/utils/cursor-manager.ts +131 -0
  425. package/src/tui/utils/detectTerminal.ts +596 -0
  426. package/src/tui/utils/findLastSafeSplitPoint.ts +92 -0
  427. package/src/tui/utils/heightEstimator.ts +198 -0
  428. package/src/tui/utils/index.ts +91 -0
  429. package/src/tui/utils/isNarrowWidth.ts +52 -0
  430. package/src/tui/utils/stdoutGuard.ts +90 -0
  431. package/src/tui/utils/synchronized-update.ts +70 -0
  432. package/src/tui/utils/text-width.ts +225 -0
  433. package/src/tui/utils/textSanitizer.ts +225 -0
  434. package/src/tui/utils/textUtils.ts +114 -0
  435. package/src/tui/utils/ui-sizing.ts +192 -0
  436. package/src/tui-blessed/app.ts +160 -0
  437. package/src/tui-blessed/index.ts +2 -0
  438. package/src/tui-blessed/neo-blessed.d.ts +6 -0
  439. package/src/tui-blessed/test.ts +21 -0
  440. package/src/tui-blessed/types.ts +14 -0
  441. package/src/utils/icons.ts +130 -0
  442. package/src/utils/index.ts +33 -0
  443. package/src/utils/resume-hint.ts +86 -0
  444. package/src/version.ts +1 -0
  445. package/tsconfig.json +8 -0
  446. 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
+ }