@compilr-dev/cli 0.4.0 → 0.5.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 (315) hide show
  1. package/README.md +30 -12
  2. package/dist/agent.d.ts +74 -1
  3. package/dist/agent.js +259 -76
  4. package/dist/anchors/index.d.ts +9 -0
  5. package/dist/anchors/index.js +9 -0
  6. package/dist/anchors/project-anchors.d.ts +79 -0
  7. package/dist/anchors/project-anchors.js +202 -0
  8. package/dist/commands/handler-types.d.ts +68 -0
  9. package/dist/commands/handler-types.js +8 -0
  10. package/dist/commands/handlers/agent-commands.d.ts +13 -0
  11. package/dist/commands/handlers/agent-commands.js +305 -0
  12. package/dist/commands/handlers/design-commands.d.ts +15 -0
  13. package/dist/commands/handlers/design-commands.js +334 -0
  14. package/dist/commands/handlers/index.d.ts +20 -0
  15. package/dist/commands/handlers/index.js +43 -0
  16. package/dist/commands/handlers/overlay-commands.d.ts +21 -0
  17. package/dist/commands/handlers/overlay-commands.js +287 -0
  18. package/dist/commands/handlers/project-commands.d.ts +11 -0
  19. package/dist/commands/handlers/project-commands.js +167 -0
  20. package/dist/commands/handlers/simple-commands.d.ts +19 -0
  21. package/dist/commands/handlers/simple-commands.js +144 -0
  22. package/dist/commands/index.d.ts +2 -1
  23. package/dist/commands/registry.d.ts +50 -0
  24. package/dist/commands/registry.js +75 -0
  25. package/dist/commands-v2/handlers/context.d.ts +13 -0
  26. package/dist/commands-v2/handlers/context.js +348 -0
  27. package/dist/commands-v2/handlers/core.d.ts +13 -0
  28. package/dist/commands-v2/handlers/core.js +165 -0
  29. package/dist/commands-v2/handlers/debug.d.ts +11 -0
  30. package/dist/commands-v2/handlers/debug.js +159 -0
  31. package/dist/commands-v2/handlers/index.d.ts +12 -0
  32. package/dist/commands-v2/handlers/index.js +24 -0
  33. package/dist/commands-v2/handlers/project.d.ts +22 -0
  34. package/dist/commands-v2/handlers/project.js +814 -0
  35. package/dist/commands-v2/handlers/settings.d.ts +15 -0
  36. package/dist/commands-v2/handlers/settings.js +235 -0
  37. package/dist/commands-v2/index.d.ts +13 -0
  38. package/dist/commands-v2/index.js +15 -0
  39. package/dist/commands-v2/registry.d.ts +37 -0
  40. package/dist/commands-v2/registry.js +80 -0
  41. package/dist/commands-v2/types.d.ts +75 -0
  42. package/dist/commands-v2/types.js +7 -0
  43. package/dist/commands.js +110 -7
  44. package/dist/index.js +288 -29
  45. package/dist/input-handlers/index.d.ts +7 -0
  46. package/dist/input-handlers/index.js +7 -0
  47. package/dist/input-handlers/memory-handler.d.ts +26 -0
  48. package/dist/input-handlers/memory-handler.js +68 -0
  49. package/dist/repl-helpers.d.ts +63 -0
  50. package/dist/repl-helpers.js +318 -0
  51. package/dist/repl-v2.d.ts +155 -0
  52. package/dist/repl-v2.js +774 -0
  53. package/dist/repl.d.ts +32 -4
  54. package/dist/repl.js +250 -977
  55. package/dist/settings/index.d.ts +23 -0
  56. package/dist/settings/index.js +48 -0
  57. package/dist/settings/paths.d.ts +110 -0
  58. package/dist/settings/paths.js +264 -0
  59. package/dist/templates/compilr-md.js +7 -4
  60. package/dist/templates/index.js +3 -4
  61. package/dist/themes/colors.js +3 -1
  62. package/dist/themes/registry.d.ts +5 -36
  63. package/dist/themes/registry.js +11 -95
  64. package/dist/themes/types.d.ts +3 -38
  65. package/dist/themes/types.js +2 -2
  66. package/dist/tools/anchor-tools.d.ts +31 -0
  67. package/dist/tools/anchor-tools.js +255 -0
  68. package/dist/tools/backlog-wrappers.d.ts +54 -0
  69. package/dist/tools/backlog-wrappers.js +338 -0
  70. package/dist/tools/backlog.js +1 -1
  71. package/dist/tools/db-tools.d.ts +65 -0
  72. package/dist/tools/db-tools.js +19 -0
  73. package/dist/tools/document-db.d.ts +43 -0
  74. package/dist/tools/document-db.js +220 -0
  75. package/dist/tools/project-db.d.ts +102 -0
  76. package/dist/tools/project-db.js +370 -0
  77. package/dist/tools/workitem-db.d.ts +103 -0
  78. package/dist/tools/workitem-db.js +549 -0
  79. package/dist/tools.js +13 -3
  80. package/dist/ui/agents-overlay-v2.d.ts +43 -0
  81. package/dist/ui/agents-overlay-v2.js +809 -0
  82. package/dist/ui/agents-overlay.d.ts +5 -5
  83. package/dist/ui/agents-overlay.js +782 -420
  84. package/dist/ui/anchors-overlay.d.ts +12 -0
  85. package/dist/ui/anchors-overlay.js +775 -0
  86. package/dist/ui/arch-type-overlay.d.ts +1 -6
  87. package/dist/ui/arch-type-overlay.js +175 -203
  88. package/dist/ui/ask-user-overlay-v2.d.ts +26 -0
  89. package/dist/ui/ask-user-overlay-v2.js +555 -0
  90. package/dist/ui/ask-user-overlay.d.ts +2 -2
  91. package/dist/ui/ask-user-overlay.js +443 -535
  92. package/dist/ui/ask-user-simple-overlay-v2.d.ts +25 -0
  93. package/dist/ui/ask-user-simple-overlay-v2.js +215 -0
  94. package/dist/ui/ask-user-simple-overlay.d.ts +2 -2
  95. package/dist/ui/ask-user-simple-overlay.js +182 -209
  96. package/dist/ui/backlog-overlay.d.ts +16 -1
  97. package/dist/ui/backlog-overlay.js +525 -659
  98. package/dist/ui/base/index.d.ts +26 -0
  99. package/dist/ui/base/index.js +33 -0
  100. package/dist/ui/base/inline-overlay-utils.d.ts +217 -0
  101. package/dist/ui/base/inline-overlay-utils.js +320 -0
  102. package/dist/ui/base/inline-overlay.d.ts +159 -0
  103. package/dist/ui/base/inline-overlay.js +257 -0
  104. package/dist/ui/base/key-utils.d.ts +15 -0
  105. package/dist/ui/base/key-utils.js +30 -0
  106. package/dist/ui/base/overlay-base-v2.d.ts +193 -0
  107. package/dist/ui/base/overlay-base-v2.js +246 -0
  108. package/dist/ui/base/overlay-base.d.ts +156 -0
  109. package/dist/ui/base/overlay-base.js +238 -0
  110. package/dist/ui/base/overlay-lifecycle.d.ts +65 -0
  111. package/dist/ui/base/overlay-lifecycle.js +159 -0
  112. package/dist/ui/base/overlay-types.d.ts +185 -0
  113. package/dist/ui/base/overlay-types.js +7 -0
  114. package/dist/ui/base/render-utils.d.ts +8 -0
  115. package/dist/ui/base/render-utils.js +11 -0
  116. package/dist/ui/base/screen-stack.d.ts +148 -0
  117. package/dist/ui/base/screen-stack.js +184 -0
  118. package/dist/ui/base/tabbed-list-overlay-v2.d.ts +103 -0
  119. package/dist/ui/base/tabbed-list-overlay-v2.js +317 -0
  120. package/dist/ui/base/tabbed-list-overlay.d.ts +153 -0
  121. package/dist/ui/base/tabbed-list-overlay.js +369 -0
  122. package/dist/ui/commands-overlay-v2.d.ts +33 -0
  123. package/dist/ui/commands-overlay-v2.js +441 -0
  124. package/dist/ui/commands-overlay.d.ts +7 -2
  125. package/dist/ui/commands-overlay.js +384 -355
  126. package/dist/ui/config-overlay.d.ts +5 -4
  127. package/dist/ui/config-overlay.js +243 -513
  128. package/dist/ui/conversation.d.ts +75 -4
  129. package/dist/ui/conversation.js +374 -161
  130. package/dist/ui/docs-overlay.d.ts +17 -0
  131. package/dist/ui/docs-overlay.js +303 -0
  132. package/dist/ui/ephemeral.d.ts +1 -1
  133. package/dist/ui/ephemeral.js +1 -1
  134. package/dist/ui/features/index.d.ts +34 -0
  135. package/dist/ui/features/index.js +34 -0
  136. package/dist/ui/features/input-feature.d.ts +85 -0
  137. package/dist/ui/features/input-feature.js +238 -0
  138. package/dist/ui/features/list-feature.d.ts +155 -0
  139. package/dist/ui/features/list-feature.js +244 -0
  140. package/dist/ui/features/pagination-feature.d.ts +154 -0
  141. package/dist/ui/features/pagination-feature.js +238 -0
  142. package/dist/ui/features/search-feature.d.ts +148 -0
  143. package/dist/ui/features/search-feature.js +185 -0
  144. package/dist/ui/features/tab-feature.d.ts +194 -0
  145. package/dist/ui/features/tab-feature.js +307 -0
  146. package/dist/ui/footer-v2.d.ts +222 -0
  147. package/dist/ui/footer-v2.js +1349 -0
  148. package/dist/ui/footer.d.ts +107 -0
  149. package/dist/ui/footer.js +359 -67
  150. package/dist/ui/guardrail-overlay.d.ts +29 -0
  151. package/dist/ui/guardrail-overlay.js +145 -0
  152. package/dist/ui/help-overlay-v2.d.ts +34 -0
  153. package/dist/ui/help-overlay-v2.js +309 -0
  154. package/dist/ui/help-overlay.d.ts +16 -0
  155. package/dist/ui/help-overlay.js +316 -0
  156. package/dist/ui/index.d.ts +1 -1
  157. package/dist/ui/index.js +1 -3
  158. package/dist/ui/init-overlay-v2.d.ts +34 -0
  159. package/dist/ui/init-overlay-v2.js +600 -0
  160. package/dist/ui/init-overlay.d.ts +12 -2
  161. package/dist/ui/init-overlay.js +349 -270
  162. package/dist/ui/input-prompt-v2.d.ts +1 -0
  163. package/dist/ui/input-prompt-v2.js +14 -6
  164. package/dist/ui/input-prompt.d.ts +116 -33
  165. package/dist/ui/input-prompt.js +536 -337
  166. package/dist/ui/iteration-limit-overlay-v2.d.ts +21 -0
  167. package/dist/ui/iteration-limit-overlay-v2.js +114 -0
  168. package/dist/ui/iteration-limit-overlay.d.ts +2 -2
  169. package/dist/ui/iteration-limit-overlay.js +92 -128
  170. package/dist/ui/keys-overlay-v2.d.ts +41 -0
  171. package/dist/ui/keys-overlay-v2.js +248 -0
  172. package/dist/ui/keys-overlay.d.ts +1 -0
  173. package/dist/ui/keys-overlay.js +203 -141
  174. package/dist/ui/line-utils.d.ts +88 -0
  175. package/dist/ui/line-utils.js +150 -0
  176. package/dist/ui/live-region.d.ts +161 -0
  177. package/dist/ui/live-region.js +387 -0
  178. package/dist/ui/mascot/expressions.d.ts +32 -0
  179. package/dist/ui/mascot/expressions.js +213 -0
  180. package/dist/ui/mascot/index.d.ts +8 -0
  181. package/dist/ui/mascot/index.js +8 -0
  182. package/dist/ui/mascot/renderer.d.ts +19 -0
  183. package/dist/ui/mascot/renderer.js +97 -0
  184. package/dist/ui/mascot-overlay-v2.d.ts +41 -0
  185. package/dist/ui/mascot-overlay-v2.js +138 -0
  186. package/dist/ui/mascot-overlay.d.ts +21 -0
  187. package/dist/ui/mascot-overlay.js +146 -0
  188. package/dist/ui/model-overlay-v2.d.ts +49 -0
  189. package/dist/ui/model-overlay-v2.js +118 -0
  190. package/dist/ui/model-overlay.d.ts +27 -0
  191. package/dist/ui/model-overlay.js +221 -0
  192. package/dist/ui/model-warning-overlay.js +3 -5
  193. package/dist/ui/new-overlay.d.ts +34 -0
  194. package/dist/ui/new-overlay.js +604 -0
  195. package/dist/ui/overlay/impl/agents-overlay-v2.d.ts +45 -0
  196. package/dist/ui/overlay/impl/agents-overlay-v2.js +825 -0
  197. package/dist/ui/overlay/impl/anchors-overlay-v2.d.ts +47 -0
  198. package/dist/ui/overlay/impl/anchors-overlay-v2.js +783 -0
  199. package/dist/ui/overlay/impl/arch-type-overlay-v2.d.ts +37 -0
  200. package/dist/ui/overlay/impl/arch-type-overlay-v2.js +240 -0
  201. package/dist/ui/overlay/impl/ask-user-overlay-v2.d.ts +72 -0
  202. package/dist/ui/overlay/impl/ask-user-overlay-v2.js +584 -0
  203. package/dist/ui/overlay/impl/ask-user-simple-overlay-v2.d.ts +46 -0
  204. package/dist/ui/overlay/impl/ask-user-simple-overlay-v2.js +204 -0
  205. package/dist/ui/overlay/impl/backlog-overlay-v2.d.ts +49 -0
  206. package/dist/ui/overlay/impl/backlog-overlay-v2.js +642 -0
  207. package/dist/ui/overlay/impl/commands-overlay-v2.d.ts +33 -0
  208. package/dist/ui/overlay/impl/commands-overlay-v2.js +441 -0
  209. package/dist/ui/overlay/impl/config-overlay-v2.d.ts +100 -0
  210. package/dist/ui/overlay/impl/config-overlay-v2.js +654 -0
  211. package/dist/ui/overlay/impl/dashboard-overlay-v2.d.ts +55 -0
  212. package/dist/ui/overlay/impl/dashboard-overlay-v2.js +359 -0
  213. package/dist/ui/overlay/impl/docs-overlay-v2.d.ts +45 -0
  214. package/dist/ui/overlay/impl/docs-overlay-v2.js +114 -0
  215. package/dist/ui/overlay/impl/document-detail-overlay-v2.d.ts +77 -0
  216. package/dist/ui/overlay/impl/document-detail-overlay-v2.js +1071 -0
  217. package/dist/ui/overlay/impl/guardrail-overlay-v2.d.ts +43 -0
  218. package/dist/ui/overlay/impl/guardrail-overlay-v2.js +114 -0
  219. package/dist/ui/overlay/impl/help-overlay-v2.d.ts +34 -0
  220. package/dist/ui/overlay/impl/help-overlay-v2.js +309 -0
  221. package/dist/ui/overlay/impl/init-overlay-v2.d.ts +77 -0
  222. package/dist/ui/overlay/impl/init-overlay-v2.js +593 -0
  223. package/dist/ui/overlay/impl/init-setup-overlay-v2.d.ts +25 -0
  224. package/dist/ui/overlay/impl/init-setup-overlay-v2.js +97 -0
  225. package/dist/ui/overlay/impl/iteration-limit-overlay-v2.d.ts +35 -0
  226. package/dist/ui/overlay/impl/iteration-limit-overlay-v2.js +105 -0
  227. package/dist/ui/overlay/impl/keys-overlay-v2.d.ts +41 -0
  228. package/dist/ui/overlay/impl/keys-overlay-v2.js +248 -0
  229. package/dist/ui/overlay/impl/mascot-overlay-v2.d.ts +41 -0
  230. package/dist/ui/overlay/impl/mascot-overlay-v2.js +138 -0
  231. package/dist/ui/overlay/impl/model-overlay-v2.d.ts +49 -0
  232. package/dist/ui/overlay/impl/model-overlay-v2.js +118 -0
  233. package/dist/ui/overlay/impl/model-warning-overlay-v2.d.ts +46 -0
  234. package/dist/ui/overlay/impl/model-warning-overlay-v2.js +132 -0
  235. package/dist/ui/overlay/impl/new-overlay-v2.d.ts +77 -0
  236. package/dist/ui/overlay/impl/new-overlay-v2.js +593 -0
  237. package/dist/ui/overlay/impl/permission-overlay-v2.d.ts +36 -0
  238. package/dist/ui/overlay/impl/permission-overlay-v2.js +380 -0
  239. package/dist/ui/overlay/impl/projects-overlay-v2.d.ts +36 -0
  240. package/dist/ui/overlay/impl/projects-overlay-v2.js +499 -0
  241. package/dist/ui/overlay/impl/theme-overlay-v2.d.ts +42 -0
  242. package/dist/ui/overlay/impl/theme-overlay-v2.js +135 -0
  243. package/dist/ui/overlay/impl/tools-overlay-v2.d.ts +47 -0
  244. package/dist/ui/overlay/impl/tools-overlay-v2.js +218 -0
  245. package/dist/ui/overlay/impl/tutorial-overlay-v2.d.ts +31 -0
  246. package/dist/ui/overlay/impl/tutorial-overlay-v2.js +1035 -0
  247. package/dist/ui/overlay/impl/workflow-overlay-v2.d.ts +80 -0
  248. package/dist/ui/overlay/impl/workflow-overlay-v2.js +637 -0
  249. package/dist/ui/overlay/index.d.ts +33 -0
  250. package/dist/ui/overlay/index.js +35 -0
  251. package/dist/ui/overlay/key-utils.d.ts +6 -0
  252. package/dist/ui/overlay/key-utils.js +6 -0
  253. package/dist/ui/overlay/overlay-types.d.ts +128 -0
  254. package/dist/ui/overlay/overlay-types.js +22 -0
  255. package/dist/ui/overlay/types.d.ts +135 -0
  256. package/dist/ui/overlay/types.js +22 -0
  257. package/dist/ui/overlays/help-overlay-v2.d.ts +28 -0
  258. package/dist/ui/overlays/help-overlay-v2.js +198 -0
  259. package/dist/ui/overlays/index.d.ts +11 -0
  260. package/dist/ui/overlays/index.js +11 -0
  261. package/dist/ui/overlays.d.ts +0 -4
  262. package/dist/ui/overlays.js +0 -444
  263. package/dist/ui/permission-overlay-v2.d.ts +36 -0
  264. package/dist/ui/permission-overlay-v2.js +380 -0
  265. package/dist/ui/permission-overlay.d.ts +1 -1
  266. package/dist/ui/permission-overlay.js +186 -298
  267. package/dist/ui/projects-overlay.d.ts +19 -0
  268. package/dist/ui/projects-overlay.js +484 -0
  269. package/dist/ui/providers/types.d.ts +178 -0
  270. package/dist/ui/providers/types.js +9 -0
  271. package/dist/ui/render-modes.d.ts +36 -0
  272. package/dist/ui/render-modes.js +44 -0
  273. package/dist/ui/startup-menu.d.ts +36 -0
  274. package/dist/ui/startup-menu.js +236 -0
  275. package/dist/ui/subagent-renderer.d.ts +117 -0
  276. package/dist/ui/subagent-renderer.js +334 -0
  277. package/dist/ui/terminal-codes.d.ts +94 -0
  278. package/dist/ui/terminal-codes.js +124 -0
  279. package/dist/ui/terminal-renderer.d.ts +221 -0
  280. package/dist/ui/terminal-renderer.js +751 -0
  281. package/dist/ui/terminal-ui.d.ts +463 -0
  282. package/dist/ui/terminal-ui.js +2296 -0
  283. package/dist/ui/terminal.d.ts +20 -0
  284. package/dist/ui/terminal.js +72 -0
  285. package/dist/ui/theme-overlay-v2.d.ts +42 -0
  286. package/dist/ui/theme-overlay-v2.js +135 -0
  287. package/dist/ui/theme-overlay.d.ts +24 -0
  288. package/dist/ui/theme-overlay.js +127 -0
  289. package/dist/ui/todo-zone.js +53 -25
  290. package/dist/ui/tool-formatters.d.ts +16 -0
  291. package/dist/ui/tool-formatters.js +516 -0
  292. package/dist/ui/tools-overlay-v2.d.ts +47 -0
  293. package/dist/ui/tools-overlay-v2.js +218 -0
  294. package/dist/ui/tools-overlay.d.ts +10 -2
  295. package/dist/ui/tools-overlay.js +172 -220
  296. package/dist/ui/tutorial-overlay-v2.d.ts +31 -0
  297. package/dist/ui/tutorial-overlay-v2.js +1035 -0
  298. package/dist/ui/tutorial-overlay.d.ts +1 -0
  299. package/dist/ui/tutorial-overlay.js +400 -302
  300. package/dist/ui/workflow-overlay.d.ts +22 -0
  301. package/dist/ui/workflow-overlay.js +636 -0
  302. package/dist/utils/debug-log.d.ts +28 -0
  303. package/dist/utils/debug-log.js +57 -0
  304. package/dist/utils/model-tiers.js +1 -1
  305. package/dist/utils/path-safety.d.ts +56 -0
  306. package/dist/utils/path-safety.js +239 -0
  307. package/dist/workflow/guided-mode-injector.d.ts +42 -0
  308. package/dist/workflow/guided-mode-injector.js +191 -0
  309. package/dist/workflow/index.d.ts +8 -0
  310. package/dist/workflow/index.js +8 -0
  311. package/dist/workflow/step-criteria.d.ts +62 -0
  312. package/dist/workflow/step-criteria.js +150 -0
  313. package/dist/workflow/step-tracker.d.ts +92 -0
  314. package/dist/workflow/step-tracker.js +141 -0
  315. package/package.json +12 -5
@@ -0,0 +1,774 @@
1
+ /**
2
+ * REPL V2 - New Terminal UI based REPL
3
+ *
4
+ * Uses TerminalUI with:
5
+ * - Persistent footer (todo list + input prompt)
6
+ * - Overlay system for commands
7
+ * - Real agent integration (or simulation mode for testing)
8
+ *
9
+ * Can be run standalone: npx tsx src/repl-v2.ts
10
+ * Or imported and used with a real agent from index.ts
11
+ */
12
+ import { TerminalUI } from './ui/terminal-ui.js';
13
+ import * as terminal from './ui/terminal.js';
14
+ import { getStyles } from './themes/index.js';
15
+ import { getSettings, getStartupMode } from './settings/index.js';
16
+ import { renderMascotWithLogo } from './ui/mascot/renderer.js';
17
+ import { registerCommands, executeCommand, allCommands, } from './commands-v2/index.js';
18
+ import { getCustomCommandRegistry } from './commands/custom-registry.js';
19
+ import { PermissionOverlayV2, } from './ui/overlay/impl/permission-overlay-v2.js';
20
+ import { AskUserSimpleOverlayV2, } from './ui/overlay/impl/ask-user-simple-overlay-v2.js';
21
+ import { GuardrailOverlayV2, } from './ui/overlay/impl/guardrail-overlay-v2.js';
22
+ import { AskUserOverlayV2, } from './ui/overlay/impl/ask-user-overlay-v2.js';
23
+ import { IterationLimitOverlayV2, } from './ui/overlay/impl/iteration-limit-overlay-v2.js';
24
+ import { formatToolResult } from './ui/tool-formatters.js';
25
+ import { isMemoryInput } from './input-handlers/index.js';
26
+ import { addAnchor } from './anchors/index.js';
27
+ import { getActiveProject } from './tools/project-db.js';
28
+ // Version for display (when running standalone)
29
+ const STANDALONE_VERSION = '0.4.0-v2';
30
+ // Register all commands on module load
31
+ registerCommands(allCommands);
32
+ // =============================================================================
33
+ // REPL V2 Class
34
+ // =============================================================================
35
+ export class ReplV2 {
36
+ agent;
37
+ model;
38
+ provider;
39
+ version;
40
+ projectName;
41
+ currentSession = null;
42
+ ui;
43
+ onUIReady;
44
+ onTextBufferReady;
45
+ onAskUserSimpleReady;
46
+ onGuardrailReady;
47
+ onAskUserReady;
48
+ onIterationLimitReady;
49
+ onAgentFinish;
50
+ onSuggestionReady;
51
+ onSubagentReady;
52
+ clearSubagentTracking;
53
+ // Session stats
54
+ startTime = new Date();
55
+ sessionInputTokens = 0;
56
+ sessionOutputTokens = 0;
57
+ sessionRequests = 0;
58
+ // Text accumulator for agent output (class-level so it can be flushed externally)
59
+ textAccumulator = '';
60
+ // Note: Subagent tracking is now simplified - callbacks receive toolUseId directly
61
+ // from agent.ts, so no more FIFO queue correlation needed!
62
+ constructor(options = {}) {
63
+ this.agent = options.agent;
64
+ this.model = options.model ?? 'simulation';
65
+ this.provider = options.provider ?? 'simulation';
66
+ this.version = options.version ?? STANDALONE_VERSION;
67
+ this.projectName = options.projectName ?? process.cwd().split('/').pop() ?? 'Project';
68
+ this.onUIReady = options.onUIReady;
69
+ this.onTextBufferReady = options.onTextBufferReady;
70
+ this.onAskUserSimpleReady = options.onAskUserSimpleReady;
71
+ this.onGuardrailReady = options.onGuardrailReady;
72
+ this.onAskUserReady = options.onAskUserReady;
73
+ this.onIterationLimitReady = options.onIterationLimitReady;
74
+ this.onAgentFinish = options.onAgentFinish;
75
+ this.onSuggestionReady = options.onSuggestionReady;
76
+ this.onSubagentReady = options.onSubagentReady;
77
+ this.clearSubagentTracking = options.clearSubagentTracking;
78
+ }
79
+ /**
80
+ * Flush accumulated agent text to the UI.
81
+ * Called externally (e.g., before showing permission overlay) to ensure
82
+ * agent's "I'll do X" message appears before the overlay.
83
+ */
84
+ flushTextBuffer() {
85
+ if (this.textAccumulator.trim()) {
86
+ this.ui.print({ type: 'agent-text', text: this.textAccumulator.trim() });
87
+ this.textAccumulator = '';
88
+ }
89
+ }
90
+ /**
91
+ * Start the REPL.
92
+ */
93
+ start() {
94
+ // Create TerminalUI
95
+ this.ui = new TerminalUI({
96
+ initialMode: 'normal',
97
+ });
98
+ // Print welcome screen
99
+ this.printWelcome();
100
+ // Set initial state
101
+ this.ui.setProjectName(this.projectName);
102
+ // Notify caller that UI is ready and provide the V2 permission overlay function
103
+ if (this.onUIReady) {
104
+ const ui = this.ui;
105
+ this.onUIReady(async (options) => {
106
+ const overlay = new PermissionOverlayV2(options);
107
+ return ui.showOverlay(overlay);
108
+ });
109
+ }
110
+ // Provide the text buffer flush function to the caller
111
+ if (this.onTextBufferReady) {
112
+ this.onTextBufferReady(() => {
113
+ this.flushTextBuffer();
114
+ });
115
+ }
116
+ // Provide the ask_user_simple overlay function to the caller
117
+ if (this.onAskUserSimpleReady) {
118
+ const ui = this.ui;
119
+ this.onAskUserSimpleReady(async (options) => {
120
+ const overlay = new AskUserSimpleOverlayV2(options);
121
+ const result = await ui.showOverlay(overlay);
122
+ return result ?? { answer: '', skipped: true };
123
+ });
124
+ }
125
+ // Provide the guardrail overlay function to the caller
126
+ if (this.onGuardrailReady) {
127
+ const ui = this.ui;
128
+ this.onGuardrailReady(async (options) => {
129
+ const overlay = new GuardrailOverlayV2(options);
130
+ const result = await ui.showOverlay(overlay);
131
+ return result ?? { approved: false };
132
+ });
133
+ }
134
+ // Provide the ask_user overlay function to the caller
135
+ if (this.onAskUserReady) {
136
+ const ui = this.ui;
137
+ this.onAskUserReady(async (options) => {
138
+ const overlay = new AskUserOverlayV2(options);
139
+ const result = await ui.showOverlay(overlay);
140
+ return result ?? { answers: {}, skipped: options.questions.map((q) => q.id) };
141
+ });
142
+ }
143
+ // Provide the iteration_limit overlay function to the caller
144
+ if (this.onIterationLimitReady) {
145
+ const ui = this.ui;
146
+ this.onIterationLimitReady(async (options) => {
147
+ const overlay = new IterationLimitOverlayV2(options);
148
+ const result = await ui.showOverlay(overlay);
149
+ return result ?? { continue: false };
150
+ });
151
+ }
152
+ // Provide the suggestion setter function to the caller
153
+ if (this.onSuggestionReady) {
154
+ const ui = this.ui;
155
+ this.onSuggestionReady((action) => {
156
+ ui.setSuggestion(action);
157
+ });
158
+ }
159
+ // Provide subagent tracking callbacks to the caller for LiveRegion rendering
160
+ // Now using toolUseId directly - no more FIFO queue correlation needed!
161
+ if (this.onSubagentReady) {
162
+ const ui = this.ui;
163
+ this.onSubagentReady({
164
+ onStart: (toolUseId, _agentType, _description) => {
165
+ // The subagent entry was already added in tool_start with toolUseId
166
+ // Just mark it as "started" (no-op for now, already rendering)
167
+ void toolUseId; // Used for correlation, entry already exists
168
+ },
169
+ onToolUse: (toolUseId, toolName, summary) => {
170
+ // Direct update using toolUseId - no mapping needed!
171
+ ui.updateSubagentTool(toolUseId, toolName, summary ?? '');
172
+ },
173
+ onEnd: (toolUseId, _success, tokenCount, _error) => {
174
+ // DON'T mark as complete here! That changes the line count in LiveRegion,
175
+ // and if render loop fires before tool_end, lastRenderHeight becomes wrong.
176
+ // Just store the info for tool_end to use when committing.
177
+ const item = ui.getLiveRegion().getItem(toolUseId);
178
+ if (item && item.type === 'subagent') {
179
+ ui.getLiveRegion().updateItem(toolUseId, {
180
+ tokenCount,
181
+ endTime: Date.now(),
182
+ });
183
+ }
184
+ },
185
+ });
186
+ }
187
+ // Event handlers
188
+ this.ui.on('submit', (input) => {
189
+ void this.processInput(input);
190
+ });
191
+ this.ui.on('command', (cmd, args) => {
192
+ void (async () => {
193
+ await this.handleCommand(cmd, args);
194
+ await this.processQueue();
195
+ })();
196
+ });
197
+ this.ui.on('escape', () => {
198
+ // Single Esc when no agent - do nothing
199
+ });
200
+ this.ui.on('interrupt', () => {
201
+ if (this.currentSession) {
202
+ this.cancelSession();
203
+ }
204
+ else {
205
+ this.ui.stop();
206
+ console.log('\nGoodbye!\n');
207
+ process.exit(0);
208
+ }
209
+ });
210
+ this.ui.on('cancel', () => {
211
+ if (this.currentSession) {
212
+ this.cancelSession();
213
+ }
214
+ });
215
+ this.ui.on('modeChange', () => {
216
+ const modes = ['normal', 'auto-accept', 'plan'];
217
+ const currentIndex = modes.indexOf(this.ui.getMode());
218
+ const nextMode = modes[(currentIndex + 1) % modes.length];
219
+ this.ui.setMode(nextMode);
220
+ this.ui.print({ type: 'info', message: `Mode: ${nextMode}` });
221
+ });
222
+ // Start UI
223
+ this.ui.start();
224
+ // If startup mode is 'menu', immediately execute /menu command
225
+ const startupMode = getStartupMode();
226
+ if (startupMode === 'menu') {
227
+ void this.handleCommand('menu', '');
228
+ }
229
+ }
230
+ /**
231
+ * Stop the REPL.
232
+ */
233
+ stop() {
234
+ if (this.currentSession) {
235
+ this.currentSession.abortController.abort();
236
+ this.currentSession = null;
237
+ }
238
+ this.ui.stop();
239
+ }
240
+ // ===========================================================================
241
+ // Welcome Message
242
+ // ===========================================================================
243
+ /**
244
+ * Print just the logo to the scrolling area.
245
+ * Used for dashboard mode where hints are not needed.
246
+ */
247
+ printLogo() {
248
+ const settings = getSettings();
249
+ const s = getStyles();
250
+ terminal.clearScreen();
251
+ // Render mascot with logo (includes tagline)
252
+ const logoLines = renderMascotWithLogo(settings.mascot, this.version, null);
253
+ console.log('');
254
+ for (const line of logoLines) {
255
+ console.log(line);
256
+ }
257
+ console.log('');
258
+ // Show agent status
259
+ if (this.agent) {
260
+ console.log(s.success(' ✓ Agent connected'));
261
+ }
262
+ else {
263
+ console.log(s.warning(' ⚠ Simulation mode (no agent)'));
264
+ }
265
+ console.log('');
266
+ // Show model info
267
+ console.log(s.muted('Model: ') + s.secondary(this.model));
268
+ console.log(s.muted('Provider: ') + s.secondary(this.provider));
269
+ console.log('');
270
+ }
271
+ /**
272
+ * Print full welcome message for REPL mode.
273
+ */
274
+ printWelcome() {
275
+ const s = getStyles();
276
+ // Print logo with agent/model info
277
+ this.printLogo();
278
+ // Hints
279
+ console.log(s.muted('Type a message to start'));
280
+ console.log(s.muted('Type /help for commands, /exit to quit'));
281
+ console.log('');
282
+ }
283
+ // ===========================================================================
284
+ // Command Handling
285
+ // ===========================================================================
286
+ createCommandContext() {
287
+ return {
288
+ ui: this.ui,
289
+ version: this.version,
290
+ printWelcome: () => { this.printWelcome(); },
291
+ printLogo: () => { this.printLogo(); },
292
+ // Agent integration
293
+ agent: this.agent,
294
+ model: this.model,
295
+ provider: this.provider,
296
+ getContextManager: () => {
297
+ return this.agent?.getContextManager() ?? null;
298
+ },
299
+ getHistory: () => {
300
+ return this.agent?.getHistory() ?? [];
301
+ },
302
+ // Queue agent message (for commands that invoke the agent)
303
+ queueAgentMessage: (options) => {
304
+ this.ui.queueAgentMessage(options);
305
+ },
306
+ // Session stats
307
+ startTime: this.startTime,
308
+ sessionInputTokens: this.sessionInputTokens,
309
+ sessionOutputTokens: this.sessionOutputTokens,
310
+ sessionRequests: this.sessionRequests,
311
+ };
312
+ }
313
+ async handleCommand(cmd, args) {
314
+ const ctx = this.createCommandContext();
315
+ const result = await executeCommand(cmd, args, ctx);
316
+ if (result === null) {
317
+ // Check if it's a custom command
318
+ const customRegistry = getCustomCommandRegistry();
319
+ if (customRegistry.has(cmd)) {
320
+ // Expand custom command and send as message
321
+ const customArgs = args ? args.split(' ').filter(a => a.trim()) : [];
322
+ const expanded = customRegistry.expand(cmd, customArgs);
323
+ if (expanded) {
324
+ // Show user what command is being run
325
+ this.ui.print({ type: 'user-message', text: `/${cmd}${args ? ' ' + args : ''}` });
326
+ // Send expanded prompt to agent
327
+ if (this.agent) {
328
+ await this.runAgentReal(expanded);
329
+ }
330
+ else {
331
+ await this.runAgentSimulation(expanded);
332
+ }
333
+ return;
334
+ }
335
+ }
336
+ this.ui.print({ type: 'warning', message: `Unknown command: /${cmd}` });
337
+ this.ui.print({ type: 'info', message: 'Type /help to see available commands' });
338
+ }
339
+ else if (!result) {
340
+ // result === false means exit
341
+ process.exit(0);
342
+ }
343
+ // Process any queued agent messages (from commands like /arch, /design)
344
+ await this.processAgentMessageQueue();
345
+ }
346
+ /**
347
+ * Process queued agent messages.
348
+ * Commands can queue messages to invoke the agent programmatically.
349
+ */
350
+ async processAgentMessageQueue() {
351
+ while (this.ui.hasAgentMessage()) {
352
+ const agentMsg = this.ui.popAgentMessage();
353
+ if (agentMsg) {
354
+ // Show display message to user (or full message if no display message)
355
+ this.ui.print({
356
+ type: 'user-message',
357
+ text: agentMsg.displayMessage ?? agentMsg.message,
358
+ });
359
+ // Send full message to agent
360
+ if (this.agent) {
361
+ await this.runAgentReal(agentMsg.message);
362
+ }
363
+ else {
364
+ await this.runAgentSimulation(agentMsg.message);
365
+ }
366
+ }
367
+ }
368
+ }
369
+ // ===========================================================================
370
+ // Input Processing
371
+ // ===========================================================================
372
+ async processInput(input) {
373
+ if (input.startsWith('/')) {
374
+ const spaceIndex = input.indexOf(' ');
375
+ const cmd = spaceIndex > 0 ? input.slice(1, spaceIndex) : input.slice(1);
376
+ const args = spaceIndex > 0 ? input.slice(spaceIndex + 1) : '';
377
+ await this.handleCommand(cmd, args);
378
+ await this.processQueue();
379
+ }
380
+ else if (isMemoryInput(input)) {
381
+ // Handle memory note ("# note")
382
+ this.handleMemoryInputV2(input);
383
+ }
384
+ else {
385
+ // Print user message
386
+ this.ui.print({ type: 'user-message', text: input });
387
+ // Run agent (real or simulation)
388
+ if (this.agent) {
389
+ await this.runAgentReal(input);
390
+ }
391
+ else {
392
+ await this.runAgentSimulation(input);
393
+ }
394
+ await this.processQueue();
395
+ }
396
+ }
397
+ /**
398
+ * Handle memory note input ("# note") - V2 compatible version
399
+ */
400
+ handleMemoryInputV2(input) {
401
+ const note = input.slice(1).trim();
402
+ // Get active project to determine scope
403
+ const activeProject = getActiveProject();
404
+ const projectId = activeProject?.id;
405
+ // Determine priority based on keywords
406
+ let priority = 'info';
407
+ const lowerNote = note.toLowerCase();
408
+ if (lowerNote.includes('never') || lowerNote.includes('always') || lowerNote.includes('must')) {
409
+ priority = 'safety';
410
+ }
411
+ if (lowerNote.includes('critical') || lowerNote.includes('important')) {
412
+ priority = 'critical';
413
+ }
414
+ // Create the anchor (persisted to file)
415
+ const anchor = addAnchor({
416
+ content: note,
417
+ priority,
418
+ scope: 'persistent',
419
+ projectId: projectId?.toString(),
420
+ tags: ['user-note'],
421
+ });
422
+ // Show confirmation
423
+ const scope = activeProject ? `project "${activeProject.displayName || activeProject.name}"` : 'global';
424
+ const priorityLabel = priority === 'info' ? '' : ` (${priority})`;
425
+ const truncatedNote = note.length > 50 ? note.slice(0, 47) + '...' : note;
426
+ this.ui.print({ type: 'success', message: `Saved${priorityLabel}: "${truncatedNote}" → ${scope}` });
427
+ // Also add to agent's anchor manager if enabled (for current session)
428
+ if (this.agent?.getAnchorManager()) {
429
+ this.agent.addAnchor({
430
+ id: anchor.id,
431
+ content: note,
432
+ priority,
433
+ scope: 'persistent',
434
+ tags: ['user-note'],
435
+ });
436
+ }
437
+ }
438
+ async processQueue() {
439
+ while (this.ui.hasQueuedInput()) {
440
+ const queued = this.ui.popQueuedInput();
441
+ if (queued) {
442
+ await this.processInput(queued);
443
+ }
444
+ }
445
+ }
446
+ // ===========================================================================
447
+ // Real Agent Execution
448
+ // ===========================================================================
449
+ async runAgentReal(userMessage) {
450
+ if (!this.agent)
451
+ return;
452
+ const abortController = new AbortController();
453
+ const signal = abortController.signal;
454
+ const setAction = (action) => {
455
+ if (this.currentSession) {
456
+ this.currentSession.currentAction = action;
457
+ }
458
+ };
459
+ // Clear subagent tracking state from previous runs
460
+ // - Agent.ts tracking (activeSubagents map)
461
+ this.clearSubagentTracking?.();
462
+ // - LiveRegion entries (visual display of running tools)
463
+ this.ui.clearLiveRegion();
464
+ this.currentSession = {
465
+ abortController,
466
+ currentAction: null,
467
+ promise: (async () => {
468
+ try {
469
+ this.ui.setAgentRunning(true);
470
+ setAction('Thinking...');
471
+ this.ui.setSpinnerText('Thinking...');
472
+ // Reset text accumulator for this run (class-level so it can be flushed externally)
473
+ this.textAccumulator = '';
474
+ let lastToolInput = null;
475
+ // Stream agent events (agent is guaranteed to exist in this method)
476
+ const agent = this.agent;
477
+ if (!agent)
478
+ return; // Type guard
479
+ for await (const event of agent.stream(userMessage, {
480
+ signal,
481
+ chatOptions: { model: this.model },
482
+ })) {
483
+ if (signal.aborted)
484
+ break;
485
+ if (event.type === 'llm_chunk') {
486
+ if (event.chunk.type === 'text' && event.chunk.text) {
487
+ this.textAccumulator += event.chunk.text;
488
+ }
489
+ else if (event.chunk.type === 'done' && event.chunk.usage) {
490
+ this.sessionInputTokens += event.chunk.usage.inputTokens;
491
+ this.sessionOutputTokens += event.chunk.usage.outputTokens;
492
+ this.sessionRequests++;
493
+ }
494
+ }
495
+ else if (event.type === 'tool_start') {
496
+ // Flush accumulated text before tool
497
+ if (this.textAccumulator.trim()) {
498
+ this.ui.print({ type: 'agent-text', text: this.textAccumulator.trim() });
499
+ this.textAccumulator = '';
500
+ }
501
+ lastToolInput = event.input;
502
+ // For bash tool, add to live region for streaming output
503
+ if (event.name === 'bash' && event.toolUseId) {
504
+ const command = typeof lastToolInput.command === 'string'
505
+ ? lastToolInput.command
506
+ : '';
507
+ this.ui.addBashCommand(event.toolUseId, command);
508
+ }
509
+ // Update spinner with tool name
510
+ if (event.name !== 'todo_read' && event.name !== 'todo_write' && event.name !== 'suggest') {
511
+ if (event.name === 'task' && event.toolUseId) {
512
+ const subagentType = typeof lastToolInput.subagent_type === 'string'
513
+ ? lastToolInput.subagent_type
514
+ : 'general';
515
+ const description = typeof lastToolInput.description === 'string'
516
+ ? lastToolInput.description
517
+ : '';
518
+ setAction(`task(${subagentType}): ${description}`);
519
+ this.ui.setSpinnerText(`task(${subagentType}): ${description}`);
520
+ // Add subagent to LiveRegion with toolUseId
521
+ // No queue needed - callbacks now receive toolUseId directly!
522
+ this.ui.addSubagent(event.toolUseId, subagentType, description);
523
+ }
524
+ else if (event.name !== 'task') {
525
+ setAction(event.name);
526
+ this.ui.setSpinnerText(`Running ${event.name}...`);
527
+ }
528
+ }
529
+ }
530
+ else if (event.type === 'tool_output') {
531
+ // Stream bash output to live region
532
+ if (event.toolUseId) {
533
+ this.ui.updateBashOutput(event.toolUseId, event.output);
534
+ }
535
+ }
536
+ else if (event.type === 'tool_end') {
537
+ const toolName = event.name;
538
+ const toolInput = lastToolInput;
539
+ const result = event.result;
540
+ lastToolInput = null;
541
+ // Complete bash command in live region
542
+ if (toolName === 'bash' && event.toolUseId) {
543
+ let exitCode = result.success ? 0 : 1;
544
+ if (result.success && typeof result.result === 'object' && result.result !== null) {
545
+ const res = result.result;
546
+ if (typeof res.exitCode === 'number') {
547
+ exitCode = res.exitCode;
548
+ }
549
+ }
550
+ this.ui.completeBashCommand(event.toolUseId, exitCode);
551
+ this.ui.commitBashCommand(event.toolUseId);
552
+ this.ui.setCurrentTool(null);
553
+ continue;
554
+ }
555
+ // Handle todo_write - update UI state
556
+ if (toolName === 'todo_write' && toolInput && Array.isArray(toolInput.todos)) {
557
+ const todos = toolInput.todos.map((t) => {
558
+ const content = typeof t.content === 'string' ? t.content :
559
+ typeof t.title === 'string' ? t.title :
560
+ typeof t.task === 'string' ? t.task :
561
+ typeof t.description === 'string' ? t.description :
562
+ typeof t.text === 'string' ? t.text : 'Untitled task';
563
+ const status = (typeof t.status === 'string' && ['pending', 'in_progress', 'completed'].includes(t.status)
564
+ ? t.status
565
+ : 'pending');
566
+ const activeForm = typeof t.activeForm === 'string' ? t.activeForm : undefined;
567
+ return { content, status, activeForm };
568
+ });
569
+ this.ui.setTodos(todos);
570
+ this.ui.setCurrentTool(null);
571
+ continue;
572
+ }
573
+ // Skip silent tools (no output needed)
574
+ if (toolName === 'todo_read' || toolName === 'suggest' ||
575
+ toolName === 'ask_user' || toolName === 'ask_user_simple') {
576
+ this.ui.setCurrentTool(null);
577
+ continue;
578
+ }
579
+ // Handle task tool like bash - use atomic commitSubagent
580
+ if (toolName === 'task' && event.toolUseId) {
581
+ // Extract result content
582
+ const { content, success } = this.extractToolResult(toolName, result);
583
+ // Use atomic commitSubagent (same pattern as commitBashCommand)
584
+ // This removes from LiveRegion and prints in one atomic operation
585
+ this.ui.commitSubagent(event.toolUseId, content, success);
586
+ this.ui.setCurrentTool(null);
587
+ continue; // Skip generic handling
588
+ }
589
+ // Extract and format tool result
590
+ const { content, summary, success } = this.extractToolResult(toolName, result);
591
+ this.ui.print({
592
+ type: 'tool-result',
593
+ name: toolName,
594
+ params: this.formatToolParams(toolInput),
595
+ summary,
596
+ content: content.length > 200 ? content : undefined,
597
+ success,
598
+ });
599
+ this.ui.setCurrentTool(null);
600
+ }
601
+ }
602
+ // Flush remaining text
603
+ if (this.textAccumulator.trim()) {
604
+ this.ui.print({ type: 'agent-text', text: this.textAccumulator.trim(), expression: 'success' });
605
+ }
606
+ this.ui.setAgentRunning(false);
607
+ // NOTE: Don't clear todos here - they should persist after agent finishes
608
+ // to show the final state. Only clear on explicit user action or new conversation.
609
+ // Notify that agent has finished (for applying deferred suggestions)
610
+ this.onAgentFinish?.();
611
+ }
612
+ catch (err) {
613
+ if (!signal.aborted) {
614
+ this.ui.print({ type: 'error', message: String(err) });
615
+ }
616
+ this.ui.setAgentRunning(false);
617
+ // Still notify on error so suggestions can be applied
618
+ this.onAgentFinish?.();
619
+ }
620
+ })(),
621
+ };
622
+ await this.currentSession.promise;
623
+ this.currentSession = null;
624
+ }
625
+ formatToolParams(input) {
626
+ if (!input)
627
+ return '';
628
+ // Format common params
629
+ if ('path' in input)
630
+ return String(input.path);
631
+ if ('file_path' in input)
632
+ return String(input.file_path);
633
+ if ('pattern' in input)
634
+ return String(input.pattern);
635
+ if ('command' in input)
636
+ return String(input.command).slice(0, 50);
637
+ // Default: show first param
638
+ const keys = Object.keys(input);
639
+ if (keys.length > 0) {
640
+ const val = String(input[keys[0]]);
641
+ return val.length > 50 ? val.slice(0, 47) + '...' : val;
642
+ }
643
+ return '';
644
+ }
645
+ /**
646
+ * Extract content and summary from tool result.
647
+ * Delegates to tool-formatters module.
648
+ */
649
+ extractToolResult(toolName, result) {
650
+ return formatToolResult(toolName, result);
651
+ }
652
+ // ===========================================================================
653
+ // Agent Simulation (for standalone testing)
654
+ // ===========================================================================
655
+ async runAgentSimulation(userMessage) {
656
+ const abortController = new AbortController();
657
+ const signal = abortController.signal;
658
+ const setAction = (action) => {
659
+ if (this.currentSession) {
660
+ this.currentSession.currentAction = action;
661
+ }
662
+ };
663
+ this.currentSession = {
664
+ abortController,
665
+ currentAction: null,
666
+ promise: (async () => {
667
+ try {
668
+ this.ui.setAgentRunning(true);
669
+ setAction('Thinking...');
670
+ this.ui.setSpinnerText('Thinking...');
671
+ // 1. Initial response
672
+ await this.sleep(1000, signal);
673
+ if (signal.aborted)
674
+ return;
675
+ const response = `I understand you want me to help with "${userMessage}". Let me analyze this.`;
676
+ this.ui.print({ type: 'agent-text', text: response });
677
+ // 2. Add todos
678
+ this.ui.setTodos([
679
+ { content: 'Analyze the request', status: 'completed' },
680
+ { content: 'Search for relevant files', status: 'in_progress' },
681
+ { content: 'Make changes', status: 'pending' },
682
+ ]);
683
+ // 3. Simulate tool
684
+ await this.sleep(800, signal);
685
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- signal can be aborted externally
686
+ if (signal.aborted)
687
+ return;
688
+ setAction('Glob(**/*.ts)');
689
+ this.ui.setSpinnerText('Running Glob...');
690
+ await this.sleep(600, signal);
691
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- signal can be aborted externally
692
+ if (signal.aborted)
693
+ return;
694
+ this.ui.print({
695
+ type: 'tool-result',
696
+ name: 'Glob',
697
+ params: '**/*.ts',
698
+ summary: 'Found 42 files',
699
+ content: 'src/index.ts\nsrc/config.ts\nsrc/utils/helpers.ts\n... and 39 more',
700
+ });
701
+ this.ui.setTodos([
702
+ { content: 'Analyze the request', status: 'completed' },
703
+ { content: 'Search for relevant files', status: 'completed' },
704
+ { content: 'Make changes', status: 'in_progress' },
705
+ ]);
706
+ // 4. Conclusion
707
+ await this.sleep(500, signal);
708
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- signal can be aborted externally
709
+ if (signal.aborted)
710
+ return;
711
+ this.ui.setTodos([
712
+ { content: 'Analyze the request', status: 'completed' },
713
+ { content: 'Search for relevant files', status: 'completed' },
714
+ { content: 'Make changes', status: 'completed' },
715
+ ]);
716
+ this.ui.print({
717
+ type: 'agent-text',
718
+ text: 'Done! I have completed the analysis.',
719
+ expression: 'success',
720
+ });
721
+ this.ui.setAgentRunning(false);
722
+ // NOTE: Don't clear todos - let them persist to show final state
723
+ // Notify that agent has finished (for applying deferred suggestions)
724
+ this.onAgentFinish?.();
725
+ }
726
+ catch (err) {
727
+ if (!signal.aborted) {
728
+ this.ui.print({ type: 'error', message: String(err) });
729
+ }
730
+ this.ui.setAgentRunning(false);
731
+ this.onAgentFinish?.();
732
+ }
733
+ })(),
734
+ };
735
+ await this.currentSession.promise;
736
+ this.currentSession = null;
737
+ }
738
+ async sleep(ms, signal) {
739
+ return new Promise((resolve) => {
740
+ const timeout = setTimeout(resolve, ms);
741
+ signal?.addEventListener('abort', () => {
742
+ clearTimeout(timeout);
743
+ resolve();
744
+ });
745
+ });
746
+ }
747
+ // ===========================================================================
748
+ // Session Management
749
+ // ===========================================================================
750
+ cancelSession() {
751
+ const action = this.currentSession?.currentAction ?? undefined;
752
+ if (this.currentSession) {
753
+ this.currentSession.abortController.abort();
754
+ this.currentSession = null;
755
+ }
756
+ this.ui.setAgentRunning(false);
757
+ this.ui.setTodos([]);
758
+ this.ui.clearQueue();
759
+ this.ui.print({ type: 'interrupted', action, suggestion: 'what should I do instead?' });
760
+ }
761
+ }
762
+ // =============================================================================
763
+ // Standalone Entry Point
764
+ // =============================================================================
765
+ // Only run main() when executed directly (not when imported)
766
+ if (import.meta.url === `file://${process.argv[1]}`) {
767
+ const repl = new ReplV2();
768
+ repl.start();
769
+ // Handle SIGINT
770
+ process.on('SIGINT', () => {
771
+ console.log('\n\nInterrupted\n');
772
+ process.exit(0);
773
+ });
774
+ }