@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,712 @@
1
+ /**
2
+ * useAgentLoop Hook (T038)
3
+ *
4
+ * React hook that wraps AgentLoop for use in React Ink applications.
5
+ * Provides reactive state, run/cancel methods, and automatic cleanup.
6
+ */
7
+
8
+ import {
9
+ type AgentLoop,
10
+ type AgentState,
11
+ createUserMessage,
12
+ type ExecutionResult,
13
+ SessionParts,
14
+ type StateContext,
15
+ } from "@vellum/core";
16
+ import { useCallback, useEffect, useRef, useState } from "react";
17
+
18
+ import { ICONS } from "../../utils/icons.js";
19
+
20
+ /**
21
+ * Message type for the hook's message list.
22
+ */
23
+ export interface AgentMessage {
24
+ id: string;
25
+ role: "user" | "assistant" | "system";
26
+ content: string;
27
+ timestamp: number;
28
+ /** If this message contains thinking content */
29
+ thinking?: string;
30
+ thinkingDuration?: number;
31
+ }
32
+
33
+ /**
34
+ * Delegation state for subagent tracking (T059).
35
+ */
36
+ export interface DelegationState {
37
+ /** Whether a delegation is currently active */
38
+ isActive: boolean;
39
+ /** Delegation ID for tracking */
40
+ delegationId: string | null;
41
+ /** Agent being delegated to */
42
+ delegatingTo: string | null;
43
+ /** Task being delegated */
44
+ task: string | null;
45
+ /** Latest subagent text chunk */
46
+ subagentText: string | null;
47
+ }
48
+
49
+ /**
50
+ * User prompt state for ask_followup_question tool.
51
+ */
52
+ export interface UserPromptState {
53
+ /** Whether the prompt is visible */
54
+ visible: boolean;
55
+ /** The question to display to the user */
56
+ question: string;
57
+ /** Optional suggestions for the user */
58
+ suggestions?: string[];
59
+ }
60
+
61
+ /**
62
+ * Completion state for attempt_completion tool.
63
+ */
64
+ export interface CompletionState {
65
+ /** Whether a completion was attempted */
66
+ attempted: boolean;
67
+ /** The result/summary of the completion */
68
+ result: string;
69
+ /** Whether verification was performed */
70
+ verified: boolean;
71
+ /** Whether verification passed (if verified) */
72
+ verificationPassed?: boolean;
73
+ }
74
+
75
+ /**
76
+ * Hook status derived from AgentLoop state.
77
+ */
78
+ export type HookStatus =
79
+ | "idle"
80
+ | "running"
81
+ | "waiting_permission"
82
+ | "waiting_input"
83
+ | "error"
84
+ | "cancelled";
85
+
86
+ /**
87
+ * Auto-approval status state for tracking consecutive approvals.
88
+ */
89
+ export interface AutoApprovalStatusState {
90
+ /** Number of consecutive auto-approved requests */
91
+ consecutiveRequests: number;
92
+ /** Request limit for auto-approvals */
93
+ requestLimit: number;
94
+ /** Cumulative cost of consecutive auto-approved operations in USD */
95
+ consecutiveCost: number;
96
+ /** Cost limit for auto-approvals in USD */
97
+ costLimit: number;
98
+ /** Percentage of request limit used (0-100) */
99
+ requestPercentUsed: number;
100
+ /** Percentage of cost limit used (0-100) */
101
+ costPercentUsed: number;
102
+ /** Whether any limit has been reached */
103
+ limitReached: boolean;
104
+ /** Which limit type was reached */
105
+ limitType?: "requests" | "cost";
106
+ }
107
+
108
+ /**
109
+ * Return value of useAgentLoop hook.
110
+ */
111
+ export interface UseAgentLoopReturn {
112
+ /** Current status of the agent loop */
113
+ status: HookStatus;
114
+ /** Raw AgentLoop state */
115
+ agentState: AgentState;
116
+ /** List of messages in the conversation */
117
+ messages: AgentMessage[];
118
+ /** Current thinking content being streamed */
119
+ thinking: string;
120
+ /** Whether an operation is in progress */
121
+ isLoading: boolean;
122
+ /** Last error encountered */
123
+ error: Error | null;
124
+ /** Current delegation state (T059) */
125
+ delegation: DelegationState;
126
+ /** Current user prompt state for ask_followup_question tool */
127
+ userPromptState: UserPromptState | null;
128
+ /** Submit user response to pending prompt */
129
+ submitUserResponse: (response: string) => void;
130
+ /** Current completion state for attempt_completion tool */
131
+ completionState: CompletionState | null;
132
+ /** Current auto-approval status (Phase 35+) */
133
+ autoApprovalStatus: AutoApprovalStatusState | null;
134
+ /** Run the agent with user input */
135
+ run: (input: string) => Promise<void>;
136
+ /** Cancel the current operation */
137
+ cancel: (reason?: string) => void;
138
+ /** Clear the conversation */
139
+ clear: () => void;
140
+ }
141
+
142
+ /**
143
+ * Maps AgentState to HookStatus.
144
+ */
145
+ function mapStateToStatus(state: AgentState): HookStatus {
146
+ switch (state) {
147
+ case "idle":
148
+ return "idle";
149
+ case "streaming":
150
+ case "tool_executing":
151
+ case "recovering":
152
+ case "retry":
153
+ return "running";
154
+ case "wait_permission":
155
+ return "waiting_permission";
156
+ case "wait_input":
157
+ return "waiting_input";
158
+ case "terminated":
159
+ case "shutdown":
160
+ return "cancelled";
161
+ case "paused":
162
+ return "idle";
163
+ default:
164
+ return "idle";
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Generates a unique message ID.
170
+ */
171
+ function generateMessageId(): string {
172
+ return `msg-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
173
+ }
174
+
175
+ /**
176
+ * React hook for managing AgentLoop in React Ink applications.
177
+ *
178
+ * Provides:
179
+ * - Reactive state (status, messages, thinking)
180
+ * - Methods (run, cancel, clear)
181
+ * - Automatic event subscription and cleanup
182
+ * - Ctrl+C and ESC cancellation integration
183
+ *
184
+ * Note: Tool execution state is managed via ToolsContext.
185
+ * Use useTools().executions for tool status display.
186
+ *
187
+ * @param loop - The AgentLoop instance to wrap
188
+ *
189
+ * @example
190
+ * ```tsx
191
+ * function App() {
192
+ * const loop = useMemo(() => new AgentLoop(config), [config]);
193
+ * const {
194
+ * status,
195
+ * messages,
196
+ * thinking,
197
+ * isLoading,
198
+ * run,
199
+ * cancel,
200
+ * } = useAgentLoop(loop);
201
+ *
202
+ * const handleSubmit = async (input: string) => {
203
+ * await run(input);
204
+ * };
205
+ *
206
+ * // Use useInput for Ctrl+C/ESC handling
207
+ * useInput((char, key) => {
208
+ * if (key.escape || (key.ctrl && char === 'c')) {
209
+ * cancel('user_interrupt');
210
+ * }
211
+ * });
212
+ *
213
+ * return (
214
+ * <Box flexDirection="column">
215
+ * <MessageList messages={messages} />
216
+ * {thinking && <ThinkingBlock thinking={thinking} duration={0} />}
217
+ * <Input onSubmit={handleSubmit} disabled={isLoading} />
218
+ * </Box>
219
+ * );
220
+ * }
221
+ * ```
222
+ */
223
+ export function useAgentLoop(loop: AgentLoop): UseAgentLoopReturn {
224
+ // Core state
225
+ const [agentState, setAgentState] = useState<AgentState>(loop.getState());
226
+ const [messages, setMessages] = useState<AgentMessage[]>([]);
227
+ const [thinking, setThinking] = useState("");
228
+ const [error, setError] = useState<Error | null>(null);
229
+
230
+ // Delegation state (T059)
231
+ const [delegation, setDelegation] = useState<DelegationState>({
232
+ isActive: false,
233
+ delegationId: null,
234
+ delegatingTo: null,
235
+ task: null,
236
+ subagentText: null,
237
+ });
238
+
239
+ // User prompt state for ask_followup_question tool
240
+ const [userPromptState, setUserPromptState] = useState<UserPromptState | null>(null);
241
+
242
+ // Completion state for attempt_completion tool
243
+ const [completionState, setCompletionState] = useState<CompletionState | null>(null);
244
+
245
+ // Auto-approval status state (Phase 35+)
246
+ const [autoApprovalStatus, setAutoApprovalStatus] = useState<AutoApprovalStatusState | null>(
247
+ () => {
248
+ // Initialize from current state if available
249
+ // Use type guard since method was added in Phase 35+
250
+ const loopWithStatus = loop as AgentLoop & {
251
+ getAutoApprovalStatus?: () => {
252
+ consecutiveRequests: number;
253
+ requestLimit: number;
254
+ consecutiveCost: number;
255
+ costLimit: number;
256
+ requestPercentUsed: number;
257
+ costPercentUsed: number;
258
+ requestLimitReached: boolean;
259
+ costLimitReached: boolean;
260
+ } | null;
261
+ };
262
+
263
+ if (typeof loopWithStatus.getAutoApprovalStatus !== "function") {
264
+ return null;
265
+ }
266
+ const status = loopWithStatus.getAutoApprovalStatus();
267
+ if (status) {
268
+ return {
269
+ consecutiveRequests: status.consecutiveRequests,
270
+ requestLimit: status.requestLimit,
271
+ consecutiveCost: status.consecutiveCost,
272
+ costLimit: status.costLimit,
273
+ requestPercentUsed: status.requestPercentUsed,
274
+ costPercentUsed: status.costPercentUsed,
275
+ limitReached: status.requestLimitReached || status.costLimitReached,
276
+ limitType: status.requestLimitReached
277
+ ? "requests"
278
+ : status.costLimitReached
279
+ ? "cost"
280
+ : undefined,
281
+ };
282
+ }
283
+ return null;
284
+ }
285
+ );
286
+
287
+ // Track thinking start time for duration calculation
288
+ const thinkingStartRef = useRef<number | null>(null);
289
+
290
+ // Track current streaming message for accumulation
291
+ const currentMessageRef = useRef<{
292
+ id: string;
293
+ content: string;
294
+ thinking: string;
295
+ thinkingDuration: number;
296
+ } | null>(null);
297
+
298
+ // Derived state
299
+ const status = mapStateToStatus(agentState);
300
+ const isLoading = status === "running" || status === "waiting_permission";
301
+
302
+ // Subscribe to AgentLoop events
303
+ useEffect(() => {
304
+ // State change handler
305
+ const handleStateChange = (from: AgentState, to: AgentState, _context: StateContext) => {
306
+ setAgentState(to);
307
+
308
+ // Clear error on successful state transition from recovering
309
+ if (from === "recovering" && to !== "terminated") {
310
+ setError(null);
311
+ }
312
+ };
313
+
314
+ // Text streaming handler
315
+ const handleText = (text: string) => {
316
+ if (!currentMessageRef.current) {
317
+ currentMessageRef.current = {
318
+ id: generateMessageId(),
319
+ content: "",
320
+ thinking: "",
321
+ thinkingDuration: 0,
322
+ };
323
+ }
324
+ currentMessageRef.current.content += text;
325
+
326
+ // Update messages with accumulated content
327
+ const current = currentMessageRef.current;
328
+ setMessages((prev) => {
329
+ const existingIndex = prev.findIndex((m) => m.id === current.id);
330
+ const message: AgentMessage = {
331
+ id: current.id,
332
+ role: "assistant",
333
+ content: current.content,
334
+ timestamp: Date.now(),
335
+ thinking: current.thinking || undefined,
336
+ thinkingDuration: current.thinkingDuration || undefined,
337
+ };
338
+
339
+ if (existingIndex >= 0) {
340
+ const updated = [...prev];
341
+ updated[existingIndex] = message;
342
+ return updated;
343
+ }
344
+ return [...prev, message];
345
+ });
346
+ };
347
+
348
+ // Thinking streaming handler
349
+ const handleThinking = (text: string) => {
350
+ if (thinkingStartRef.current === null) {
351
+ thinkingStartRef.current = Date.now();
352
+ }
353
+
354
+ // Create streaming message if not exists (same pattern as handleText)
355
+ // This ensures UI shows response even when thinking arrives before text
356
+ if (!currentMessageRef.current) {
357
+ currentMessageRef.current = {
358
+ id: generateMessageId(),
359
+ content: "",
360
+ thinking: "",
361
+ thinkingDuration: 0,
362
+ };
363
+
364
+ // Add initial message to UI so it knows there's a response
365
+ const current = currentMessageRef.current;
366
+ setMessages((prev) => [
367
+ ...prev,
368
+ {
369
+ id: current.id,
370
+ role: "assistant",
371
+ content: "",
372
+ timestamp: Date.now(),
373
+ thinking: text,
374
+ thinkingDuration: 0,
375
+ },
376
+ ]);
377
+ }
378
+
379
+ setThinking((prev) => prev + text);
380
+
381
+ // Accumulate in current message ref
382
+ currentMessageRef.current.thinking += text;
383
+ currentMessageRef.current.thinkingDuration =
384
+ Date.now() - (thinkingStartRef.current ?? Date.now());
385
+
386
+ // Update message with accumulated thinking
387
+ const current = currentMessageRef.current;
388
+ setMessages((prev) => {
389
+ const existingIndex = prev.findIndex((m) => m.id === current.id);
390
+ const existing = prev[existingIndex];
391
+ if (existingIndex >= 0 && existing) {
392
+ const updated = [...prev];
393
+ updated[existingIndex] = {
394
+ id: existing.id,
395
+ role: existing.role,
396
+ content: existing.content,
397
+ timestamp: existing.timestamp,
398
+ thinking: current.thinking,
399
+ thinkingDuration: current.thinkingDuration,
400
+ };
401
+ return updated;
402
+ }
403
+ return prev;
404
+ });
405
+ };
406
+
407
+ // Tool event handlers (events still fired but state managed by ToolsContext)
408
+ const handleToolStart = (_callId: string, _name: string, _input: Record<string, unknown>) => {
409
+ // Tool state is managed by ToolsContext, not here
410
+ };
411
+
412
+ const handleToolEnd = (_callId: string, _name: string, _result: ExecutionResult) => {
413
+ // Tool state is managed by ToolsContext, not here
414
+ };
415
+
416
+ // Error handler
417
+ const handleError = (err: Error) => {
418
+ setError(err);
419
+ // Surface error as assistant message for visibility
420
+ const errorMessage: AgentMessage = {
421
+ id: generateMessageId(),
422
+ role: "assistant",
423
+ content: `${ICONS.warning} ${err.message}`,
424
+ timestamp: Date.now(),
425
+ };
426
+ setMessages((prev) => [...prev, errorMessage]);
427
+ currentMessageRef.current = null;
428
+ };
429
+
430
+ // Complete handler
431
+ const handleComplete = () => {
432
+ // Finalize current message
433
+ if (currentMessageRef.current) {
434
+ currentMessageRef.current = null;
435
+ }
436
+ // Clear thinking
437
+ setThinking("");
438
+ thinkingStartRef.current = null;
439
+ };
440
+
441
+ // Message handler - log or store full message content
442
+ const handleMessage = (_content: string) => {
443
+ // Message events are handled via text accumulation
444
+ // This handler can be extended for message-level operations
445
+ };
446
+
447
+ // Tool call handler - events still fired but state managed by ToolsContext
448
+ const handleToolCall = (_id: string, _name: string, _input: Record<string, unknown>) => {
449
+ // Tool state is managed by ToolsContext, not here
450
+ };
451
+
452
+ // Usage handler - track token usage
453
+ const handleUsage = (_usage: { inputTokens: number; outputTokens: number }) => {
454
+ // Token usage can be tracked here for display
455
+ // This data is also available in ContextProgress via context
456
+ };
457
+
458
+ // Terminated handler - handle cancellation/termination
459
+ const handleTerminated = () => {
460
+ setThinking("");
461
+ thinkingStartRef.current = null;
462
+ };
463
+
464
+ // Loop detected handler - show warning to user
465
+ const handleLoopDetected = (_result: {
466
+ detected: boolean;
467
+ confidence: number;
468
+ reason?: string;
469
+ }) => {
470
+ // Loop detection can trigger UI feedback
471
+ // Consider emitting a warning message or status
472
+ };
473
+
474
+ // Retry handler - show retry feedback
475
+ const handleRetry = (_attempt: number, _error: Error, _delay: number) => {
476
+ // Retry events can be used for UI feedback
477
+ };
478
+
479
+ // Retry exhausted handler - show retry failure
480
+ const handleRetryExhausted = (err: Error, _attempts: number) => {
481
+ setError(err);
482
+ };
483
+
484
+ // Delegation start handler (T059)
485
+ const handleDelegationStart = (delegationId: string, agent: string, task: string) => {
486
+ setDelegation({
487
+ isActive: true,
488
+ delegationId,
489
+ delegatingTo: agent,
490
+ task,
491
+ subagentText: null,
492
+ });
493
+ };
494
+
495
+ // Delegation complete handler (T059)
496
+ const handleDelegationComplete = (_delegationId: string, _agent: string, _result: string) => {
497
+ setDelegation({
498
+ isActive: false,
499
+ delegationId: null,
500
+ delegatingTo: null,
501
+ task: null,
502
+ subagentText: null,
503
+ });
504
+ };
505
+
506
+ // Subagent text handler (T059)
507
+ const handleSubagentText = (_delegationId: string, agent: string, chunk: string) => {
508
+ setDelegation((prev) => ({
509
+ ...prev,
510
+ subagentText: chunk,
511
+ delegatingTo: agent,
512
+ }));
513
+ };
514
+
515
+ // Subagent tool handler (T059)
516
+ const handleSubagentTool = (_delegationId: string, agent: string, toolName: string) => {
517
+ setDelegation((prev) => ({
518
+ ...prev,
519
+ subagentText: `${agent}: ${toolName}`,
520
+ }));
521
+ };
522
+
523
+ // User prompt handler for ask_followup_question tool
524
+ const handleUserPromptRequired = (prompt: { question: string; suggestions?: string[] }) => {
525
+ setUserPromptState({
526
+ visible: true,
527
+ question: prompt.question,
528
+ suggestions: prompt.suggestions,
529
+ });
530
+ };
531
+
532
+ // Completion handler for attempt_completion tool
533
+ const handleCompletionAttempted = (completion: {
534
+ result: string;
535
+ verified: boolean;
536
+ verificationPassed?: boolean;
537
+ }) => {
538
+ setCompletionState({
539
+ attempted: true,
540
+ result: completion.result,
541
+ verified: completion.verified,
542
+ verificationPassed: completion.verificationPassed,
543
+ });
544
+ };
545
+
546
+ // Auto-approval status handler (Phase 35+)
547
+ const handleAutoApprovalStatus = (status: {
548
+ consecutiveRequests: number;
549
+ requestLimit: number;
550
+ consecutiveCost: number;
551
+ costLimit: number;
552
+ requestPercentUsed: number;
553
+ costPercentUsed: number;
554
+ limitReached: boolean;
555
+ limitType?: "requests" | "cost";
556
+ }) => {
557
+ setAutoApprovalStatus(status);
558
+ };
559
+
560
+ // Subscribe to events
561
+ loop.on("stateChange", handleStateChange);
562
+ loop.on("text", handleText);
563
+ loop.on("thinking", handleThinking);
564
+ loop.on("toolStart", handleToolStart);
565
+ loop.on("toolEnd", handleToolEnd);
566
+ loop.on("error", handleError);
567
+ loop.on("complete", handleComplete);
568
+ loop.on("message", handleMessage);
569
+ loop.on("toolCall", handleToolCall);
570
+ loop.on("usage", handleUsage);
571
+ loop.on("terminated", handleTerminated);
572
+ loop.on("loopDetected", handleLoopDetected);
573
+ loop.on("retry", handleRetry);
574
+ loop.on("retryExhausted", handleRetryExhausted);
575
+ // Delegation events (T059)
576
+ loop.on("delegationStart", handleDelegationStart);
577
+ loop.on("delegationComplete", handleDelegationComplete);
578
+ loop.on("subagentText", handleSubagentText);
579
+ loop.on("subagentTool", handleSubagentTool);
580
+ // User prompt and completion events
581
+ loop.on("userPrompt:required", handleUserPromptRequired);
582
+ loop.on("completion:attempted", handleCompletionAttempted);
583
+ // Auto-approval status event (Phase 35+)
584
+ // Use type cast since event was added in Phase 35+
585
+ const loopWithAutoApproval = loop as unknown as {
586
+ on(event: "autoApproval:statusChange", handler: typeof handleAutoApprovalStatus): void;
587
+ off(event: "autoApproval:statusChange", handler: typeof handleAutoApprovalStatus): void;
588
+ };
589
+ try {
590
+ loopWithAutoApproval.on("autoApproval:statusChange", handleAutoApprovalStatus);
591
+ } catch {
592
+ // Event may not exist in older versions
593
+ }
594
+
595
+ // Cleanup subscriptions
596
+ return () => {
597
+ // Unsubscribe from all events
598
+ loop.off("stateChange", handleStateChange);
599
+ loop.off("text", handleText);
600
+ loop.off("thinking", handleThinking);
601
+ loop.off("toolStart", handleToolStart);
602
+ loop.off("toolEnd", handleToolEnd);
603
+ loop.off("error", handleError);
604
+ loop.off("complete", handleComplete);
605
+ loop.off("message", handleMessage);
606
+ loop.off("toolCall", handleToolCall);
607
+ loop.off("usage", handleUsage);
608
+ loop.off("terminated", handleTerminated);
609
+ loop.off("loopDetected", handleLoopDetected);
610
+ loop.off("retry", handleRetry);
611
+ loop.off("retryExhausted", handleRetryExhausted);
612
+ // Delegation events cleanup (T059)
613
+ loop.off("delegationStart", handleDelegationStart);
614
+ loop.off("delegationComplete", handleDelegationComplete);
615
+ loop.off("subagentText", handleSubagentText);
616
+ loop.off("subagentTool", handleSubagentTool);
617
+ // User prompt and completion events cleanup
618
+ loop.off("userPrompt:required", handleUserPromptRequired);
619
+ loop.off("completion:attempted", handleCompletionAttempted);
620
+ // Auto-approval status event cleanup (Phase 35+)
621
+ try {
622
+ loopWithAutoApproval.off("autoApproval:statusChange", handleAutoApprovalStatus);
623
+ } catch {
624
+ // Event may not exist in older versions
625
+ }
626
+ };
627
+ }, [loop]);
628
+
629
+ // Run method - adds user message and runs the loop
630
+ const run = useCallback(
631
+ async (input: string) => {
632
+ if (!input.trim()) return;
633
+
634
+ // Reset state for new run
635
+ setError(null);
636
+ setThinking("");
637
+ thinkingStartRef.current = null;
638
+ currentMessageRef.current = null;
639
+
640
+ // Add user message
641
+ const userMessage: AgentMessage = {
642
+ id: generateMessageId(),
643
+ role: "user",
644
+ content: input,
645
+ timestamp: Date.now(),
646
+ };
647
+ setMessages((prev) => [...prev, userMessage]);
648
+
649
+ // Add to AgentLoop messages
650
+ loop.addMessage(createUserMessage([SessionParts.text(input)]));
651
+
652
+ // Run the loop
653
+ try {
654
+ await loop.run();
655
+ } catch (err) {
656
+ const error = err instanceof Error ? err : new Error(String(err));
657
+ setError(error);
658
+ throw error; // Re-throw for caller to handle
659
+ }
660
+ },
661
+ [loop]
662
+ );
663
+
664
+ // Cancel method
665
+ const cancel = useCallback(
666
+ (reason?: string) => {
667
+ loop.cancel(reason);
668
+ setThinking("");
669
+ thinkingStartRef.current = null;
670
+ },
671
+ [loop]
672
+ );
673
+
674
+ // Clear conversation
675
+ const clear = useCallback(() => {
676
+ setMessages([]);
677
+ setThinking("");
678
+ setError(null);
679
+ thinkingStartRef.current = null;
680
+ currentMessageRef.current = null;
681
+ setUserPromptState(null);
682
+ setCompletionState(null);
683
+ }, []);
684
+
685
+ // Submit user response to pending prompt
686
+ const submitUserResponse = useCallback(
687
+ (response: string) => {
688
+ loop.submitUserResponse(response);
689
+ setUserPromptState(null);
690
+ },
691
+ [loop]
692
+ );
693
+
694
+ return {
695
+ status,
696
+ agentState,
697
+ messages,
698
+ thinking,
699
+ isLoading,
700
+ error,
701
+ delegation,
702
+ userPromptState,
703
+ submitUserResponse,
704
+ completionState,
705
+ autoApprovalStatus,
706
+ run,
707
+ cancel,
708
+ clear,
709
+ };
710
+ }
711
+
712
+ export default useAgentLoop;