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