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