@compass-ai/nova 1.0.74 → 1.0.76

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 (973) hide show
  1. package/dist/cli.js +557 -555
  2. package/dist/index.js +10 -8
  3. package/package.json +1 -1
  4. package/dist/acp/agent.d.ts +0 -318
  5. package/dist/acp/agent.d.ts.map +0 -1
  6. package/dist/acp/agent.js +0 -795
  7. package/dist/acp/agent.js.map +0 -1
  8. package/dist/acp/backpressure-writer.d.ts +0 -49
  9. package/dist/acp/backpressure-writer.d.ts.map +0 -1
  10. package/dist/acp/backpressure-writer.js +0 -153
  11. package/dist/acp/backpressure-writer.js.map +0 -1
  12. package/dist/acp/event-adapter.d.ts +0 -242
  13. package/dist/acp/event-adapter.d.ts.map +0 -1
  14. package/dist/acp/event-adapter.js +0 -456
  15. package/dist/acp/event-adapter.js.map +0 -1
  16. package/dist/acp/index.d.ts +0 -30
  17. package/dist/acp/index.d.ts.map +0 -1
  18. package/dist/acp/index.js +0 -36
  19. package/dist/acp/index.js.map +0 -1
  20. package/dist/acp/modes.d.ts +0 -56
  21. package/dist/acp/modes.d.ts.map +0 -1
  22. package/dist/acp/modes.js +0 -135
  23. package/dist/acp/modes.js.map +0 -1
  24. package/dist/acp/session-manager.d.ts +0 -170
  25. package/dist/acp/session-manager.d.ts.map +0 -1
  26. package/dist/acp/session-manager.js +0 -381
  27. package/dist/acp/session-manager.js.map +0 -1
  28. package/dist/acp/text-coalescer.d.ts +0 -45
  29. package/dist/acp/text-coalescer.d.ts.map +0 -1
  30. package/dist/acp/text-coalescer.js +0 -110
  31. package/dist/acp/text-coalescer.js.map +0 -1
  32. package/dist/acp/tool-bridge.d.ts +0 -156
  33. package/dist/acp/tool-bridge.d.ts.map +0 -1
  34. package/dist/acp/tool-bridge.js +0 -381
  35. package/dist/acp/tool-bridge.js.map +0 -1
  36. package/dist/acp/types.d.ts +0 -314
  37. package/dist/acp/types.d.ts.map +0 -1
  38. package/dist/acp/types.js +0 -8
  39. package/dist/acp/types.js.map +0 -1
  40. package/dist/cli.d.ts +0 -9
  41. package/dist/cli.d.ts.map +0 -1
  42. package/dist/cli.js.map +0 -1
  43. package/dist/commands/acp.d.ts +0 -26
  44. package/dist/commands/acp.d.ts.map +0 -1
  45. package/dist/commands/acp.js +0 -492
  46. package/dist/commands/acp.js.map +0 -1
  47. package/dist/commands/cache.d.ts +0 -33
  48. package/dist/commands/cache.d.ts.map +0 -1
  49. package/dist/commands/cache.js +0 -537
  50. package/dist/commands/cache.js.map +0 -1
  51. package/dist/commands/config.d.ts +0 -10
  52. package/dist/commands/config.d.ts.map +0 -1
  53. package/dist/commands/config.js +0 -367
  54. package/dist/commands/config.js.map +0 -1
  55. package/dist/commands/consent.d.ts +0 -21
  56. package/dist/commands/consent.d.ts.map +0 -1
  57. package/dist/commands/consent.js +0 -334
  58. package/dist/commands/consent.js.map +0 -1
  59. package/dist/commands/data.d.ts +0 -24
  60. package/dist/commands/data.d.ts.map +0 -1
  61. package/dist/commands/data.js +0 -586
  62. package/dist/commands/data.js.map +0 -1
  63. package/dist/commands/index.d.ts +0 -145
  64. package/dist/commands/index.d.ts.map +0 -1
  65. package/dist/commands/index.js +0 -210
  66. package/dist/commands/index.js.map +0 -1
  67. package/dist/commands/init.d.ts +0 -106
  68. package/dist/commands/init.d.ts.map +0 -1
  69. package/dist/commands/init.js +0 -349
  70. package/dist/commands/init.js.map +0 -1
  71. package/dist/commands/logs.d.ts +0 -13
  72. package/dist/commands/logs.d.ts.map +0 -1
  73. package/dist/commands/logs.js +0 -359
  74. package/dist/commands/logs.js.map +0 -1
  75. package/dist/commands/mcp.d.ts +0 -20
  76. package/dist/commands/mcp.d.ts.map +0 -1
  77. package/dist/commands/mcp.js +0 -687
  78. package/dist/commands/mcp.js.map +0 -1
  79. package/dist/commands/reset.d.ts +0 -20
  80. package/dist/commands/reset.d.ts.map +0 -1
  81. package/dist/commands/reset.js +0 -372
  82. package/dist/commands/reset.js.map +0 -1
  83. package/dist/commands/setup.d.ts +0 -74
  84. package/dist/commands/setup.d.ts.map +0 -1
  85. package/dist/commands/setup.js +0 -863
  86. package/dist/commands/setup.js.map +0 -1
  87. package/dist/commands/slash/agents.d.ts +0 -40
  88. package/dist/commands/slash/agents.d.ts.map +0 -1
  89. package/dist/commands/slash/agents.js +0 -519
  90. package/dist/commands/slash/agents.js.map +0 -1
  91. package/dist/commands/slash/approve.d.ts +0 -46
  92. package/dist/commands/slash/approve.d.ts.map +0 -1
  93. package/dist/commands/slash/approve.js +0 -239
  94. package/dist/commands/slash/approve.js.map +0 -1
  95. package/dist/commands/slash/attach.d.ts +0 -70
  96. package/dist/commands/slash/attach.d.ts.map +0 -1
  97. package/dist/commands/slash/attach.js +0 -333
  98. package/dist/commands/slash/attach.js.map +0 -1
  99. package/dist/commands/slash/clear.d.ts +0 -47
  100. package/dist/commands/slash/clear.d.ts.map +0 -1
  101. package/dist/commands/slash/clear.js +0 -261
  102. package/dist/commands/slash/clear.js.map +0 -1
  103. package/dist/commands/slash/commit.d.ts +0 -40
  104. package/dist/commands/slash/commit.d.ts.map +0 -1
  105. package/dist/commands/slash/commit.js +0 -337
  106. package/dist/commands/slash/commit.js.map +0 -1
  107. package/dist/commands/slash/compact.d.ts +0 -48
  108. package/dist/commands/slash/compact.d.ts.map +0 -1
  109. package/dist/commands/slash/compact.js +0 -288
  110. package/dist/commands/slash/compact.js.map +0 -1
  111. package/dist/commands/slash/config.d.ts +0 -20
  112. package/dist/commands/slash/config.d.ts.map +0 -1
  113. package/dist/commands/slash/config.js +0 -136
  114. package/dist/commands/slash/config.js.map +0 -1
  115. package/dist/commands/slash/context.d.ts +0 -49
  116. package/dist/commands/slash/context.d.ts.map +0 -1
  117. package/dist/commands/slash/context.js +0 -430
  118. package/dist/commands/slash/context.js.map +0 -1
  119. package/dist/commands/slash/cost.d.ts +0 -34
  120. package/dist/commands/slash/cost.d.ts.map +0 -1
  121. package/dist/commands/slash/cost.js +0 -312
  122. package/dist/commands/slash/cost.js.map +0 -1
  123. package/dist/commands/slash/custom.d.ts +0 -41
  124. package/dist/commands/slash/custom.d.ts.map +0 -1
  125. package/dist/commands/slash/custom.js +0 -126
  126. package/dist/commands/slash/custom.js.map +0 -1
  127. package/dist/commands/slash/exit.d.ts +0 -25
  128. package/dist/commands/slash/exit.d.ts.map +0 -1
  129. package/dist/commands/slash/exit.js +0 -186
  130. package/dist/commands/slash/exit.js.map +0 -1
  131. package/dist/commands/slash/export.d.ts +0 -27
  132. package/dist/commands/slash/export.d.ts.map +0 -1
  133. package/dist/commands/slash/export.js +0 -318
  134. package/dist/commands/slash/export.js.map +0 -1
  135. package/dist/commands/slash/files.d.ts +0 -47
  136. package/dist/commands/slash/files.d.ts.map +0 -1
  137. package/dist/commands/slash/files.js +0 -521
  138. package/dist/commands/slash/files.js.map +0 -1
  139. package/dist/commands/slash/help.d.ts +0 -50
  140. package/dist/commands/slash/help.d.ts.map +0 -1
  141. package/dist/commands/slash/help.js +0 -282
  142. package/dist/commands/slash/help.js.map +0 -1
  143. package/dist/commands/slash/index-cmd.d.ts +0 -41
  144. package/dist/commands/slash/index-cmd.d.ts.map +0 -1
  145. package/dist/commands/slash/index-cmd.js +0 -349
  146. package/dist/commands/slash/index-cmd.js.map +0 -1
  147. package/dist/commands/slash/index.d.ts +0 -97
  148. package/dist/commands/slash/index.d.ts.map +0 -1
  149. package/dist/commands/slash/index.js +0 -251
  150. package/dist/commands/slash/index.js.map +0 -1
  151. package/dist/commands/slash/login.d.ts +0 -23
  152. package/dist/commands/slash/login.d.ts.map +0 -1
  153. package/dist/commands/slash/login.js +0 -175
  154. package/dist/commands/slash/login.js.map +0 -1
  155. package/dist/commands/slash/logout.d.ts +0 -23
  156. package/dist/commands/slash/logout.d.ts.map +0 -1
  157. package/dist/commands/slash/logout.js +0 -153
  158. package/dist/commands/slash/logout.js.map +0 -1
  159. package/dist/commands/slash/logs.d.ts +0 -29
  160. package/dist/commands/slash/logs.d.ts.map +0 -1
  161. package/dist/commands/slash/logs.js +0 -423
  162. package/dist/commands/slash/logs.js.map +0 -1
  163. package/dist/commands/slash/mcp.d.ts +0 -29
  164. package/dist/commands/slash/mcp.d.ts.map +0 -1
  165. package/dist/commands/slash/mcp.js +0 -1026
  166. package/dist/commands/slash/mcp.js.map +0 -1
  167. package/dist/commands/slash/model.d.ts +0 -60
  168. package/dist/commands/slash/model.d.ts.map +0 -1
  169. package/dist/commands/slash/model.js +0 -466
  170. package/dist/commands/slash/model.js.map +0 -1
  171. package/dist/commands/slash/personality.d.ts +0 -40
  172. package/dist/commands/slash/personality.d.ts.map +0 -1
  173. package/dist/commands/slash/personality.js +0 -272
  174. package/dist/commands/slash/personality.js.map +0 -1
  175. package/dist/commands/slash/purge.d.ts +0 -42
  176. package/dist/commands/slash/purge.d.ts.map +0 -1
  177. package/dist/commands/slash/purge.js +0 -233
  178. package/dist/commands/slash/purge.js.map +0 -1
  179. package/dist/commands/slash/reset.d.ts +0 -44
  180. package/dist/commands/slash/reset.d.ts.map +0 -1
  181. package/dist/commands/slash/reset.js +0 -326
  182. package/dist/commands/slash/reset.js.map +0 -1
  183. package/dist/commands/slash/skills.d.ts +0 -40
  184. package/dist/commands/slash/skills.d.ts.map +0 -1
  185. package/dist/commands/slash/skills.js +0 -207
  186. package/dist/commands/slash/skills.js.map +0 -1
  187. package/dist/commands/slash/tokens.d.ts +0 -34
  188. package/dist/commands/slash/tokens.d.ts.map +0 -1
  189. package/dist/commands/slash/tokens.js +0 -205
  190. package/dist/commands/slash/tokens.js.map +0 -1
  191. package/dist/commands/slash/unleash.d.ts +0 -50
  192. package/dist/commands/slash/unleash.d.ts.map +0 -1
  193. package/dist/commands/slash/unleash.js +0 -262
  194. package/dist/commands/slash/unleash.js.map +0 -1
  195. package/dist/commands/slash/update.d.ts +0 -34
  196. package/dist/commands/slash/update.d.ts.map +0 -1
  197. package/dist/commands/slash/update.js +0 -364
  198. package/dist/commands/slash/update.js.map +0 -1
  199. package/dist/commands/slash/wrap.d.ts +0 -18
  200. package/dist/commands/slash/wrap.d.ts.map +0 -1
  201. package/dist/commands/slash/wrap.js +0 -21
  202. package/dist/commands/slash/wrap.js.map +0 -1
  203. package/dist/commands/tokens.d.ts +0 -26
  204. package/dist/commands/tokens.d.ts.map +0 -1
  205. package/dist/commands/tokens.js +0 -245
  206. package/dist/commands/tokens.js.map +0 -1
  207. package/dist/constants/builtin-agents.d.ts +0 -27
  208. package/dist/constants/builtin-agents.d.ts.map +0 -1
  209. package/dist/constants/builtin-agents.js +0 -710
  210. package/dist/constants/builtin-agents.js.map +0 -1
  211. package/dist/constants/builtin-skills.d.ts +0 -32
  212. package/dist/constants/builtin-skills.d.ts.map +0 -1
  213. package/dist/constants/builtin-skills.js +0 -389
  214. package/dist/constants/builtin-skills.js.map +0 -1
  215. package/dist/constants/defaults.d.ts +0 -448
  216. package/dist/constants/defaults.d.ts.map +0 -1
  217. package/dist/constants/defaults.js +0 -829
  218. package/dist/constants/defaults.js.map +0 -1
  219. package/dist/constants/index.d.ts +0 -27
  220. package/dist/constants/index.d.ts.map +0 -1
  221. package/dist/constants/index.js +0 -85
  222. package/dist/constants/index.js.map +0 -1
  223. package/dist/constants/install-hints.d.ts +0 -7
  224. package/dist/constants/install-hints.d.ts.map +0 -1
  225. package/dist/constants/install-hints.js +0 -123
  226. package/dist/constants/install-hints.js.map +0 -1
  227. package/dist/constants/models.d.ts +0 -255
  228. package/dist/constants/models.d.ts.map +0 -1
  229. package/dist/constants/models.js +0 -596
  230. package/dist/constants/models.js.map +0 -1
  231. package/dist/constants/schedule.d.ts +0 -43
  232. package/dist/constants/schedule.d.ts.map +0 -1
  233. package/dist/constants/schedule.js +0 -110
  234. package/dist/constants/schedule.js.map +0 -1
  235. package/dist/constants/system-utilities.d.ts +0 -57
  236. package/dist/constants/system-utilities.d.ts.map +0 -1
  237. package/dist/constants/system-utilities.js +0 -421
  238. package/dist/constants/system-utilities.js.map +0 -1
  239. package/dist/constants/token-limits.d.ts +0 -102
  240. package/dist/constants/token-limits.d.ts.map +0 -1
  241. package/dist/constants/token-limits.js +0 -286
  242. package/dist/constants/token-limits.js.map +0 -1
  243. package/dist/core/autocomplete.d.ts +0 -132
  244. package/dist/core/autocomplete.d.ts.map +0 -1
  245. package/dist/core/autocomplete.js +0 -653
  246. package/dist/core/autocomplete.js.map +0 -1
  247. package/dist/core/command-parser.d.ts +0 -301
  248. package/dist/core/command-parser.d.ts.map +0 -1
  249. package/dist/core/command-parser.js +0 -526
  250. package/dist/core/command-parser.js.map +0 -1
  251. package/dist/core/context-builder.d.ts +0 -264
  252. package/dist/core/context-builder.d.ts.map +0 -1
  253. package/dist/core/context-builder.js +0 -1018
  254. package/dist/core/context-builder.js.map +0 -1
  255. package/dist/core/event-emitter.d.ts +0 -411
  256. package/dist/core/event-emitter.d.ts.map +0 -1
  257. package/dist/core/event-emitter.js +0 -138
  258. package/dist/core/event-emitter.js.map +0 -1
  259. package/dist/core/history-manager.d.ts +0 -62
  260. package/dist/core/history-manager.d.ts.map +0 -1
  261. package/dist/core/history-manager.js +0 -151
  262. package/dist/core/history-manager.js.map +0 -1
  263. package/dist/core/slash-command-handler.d.ts +0 -352
  264. package/dist/core/slash-command-handler.d.ts.map +0 -1
  265. package/dist/core/slash-command-handler.js +0 -563
  266. package/dist/core/slash-command-handler.js.map +0 -1
  267. package/dist/core/task-processor.d.ts +0 -179
  268. package/dist/core/task-processor.d.ts.map +0 -1
  269. package/dist/core/task-processor.js +0 -519
  270. package/dist/core/task-processor.js.map +0 -1
  271. package/dist/index.d.ts +0 -90
  272. package/dist/index.d.ts.map +0 -1
  273. package/dist/index.js.map +0 -1
  274. package/dist/prompts/agent-prompt-generator.d.ts +0 -26
  275. package/dist/prompts/agent-prompt-generator.d.ts.map +0 -1
  276. package/dist/prompts/agent-prompt-generator.js +0 -244
  277. package/dist/prompts/agent-prompt-generator.js.map +0 -1
  278. package/dist/prompts/commit-message.d.ts +0 -35
  279. package/dist/prompts/commit-message.d.ts.map +0 -1
  280. package/dist/prompts/commit-message.js +0 -187
  281. package/dist/prompts/commit-message.js.map +0 -1
  282. package/dist/prompts/conversation.d.ts +0 -51
  283. package/dist/prompts/conversation.d.ts.map +0 -1
  284. package/dist/prompts/conversation.js +0 -115
  285. package/dist/prompts/conversation.js.map +0 -1
  286. package/dist/prompts/index.d.ts +0 -39
  287. package/dist/prompts/index.d.ts.map +0 -1
  288. package/dist/prompts/index.js +0 -74
  289. package/dist/prompts/index.js.map +0 -1
  290. package/dist/prompts/memory.d.ts +0 -6
  291. package/dist/prompts/memory.d.ts.map +0 -1
  292. package/dist/prompts/memory.js +0 -29
  293. package/dist/prompts/memory.js.map +0 -1
  294. package/dist/prompts/personality.d.ts +0 -77
  295. package/dist/prompts/personality.d.ts.map +0 -1
  296. package/dist/prompts/personality.js +0 -393
  297. package/dist/prompts/personality.js.map +0 -1
  298. package/dist/prompts/plan-generator.d.ts +0 -144
  299. package/dist/prompts/plan-generator.d.ts.map +0 -1
  300. package/dist/prompts/plan-generator.js +0 -553
  301. package/dist/prompts/plan-generator.js.map +0 -1
  302. package/dist/prompts/system.d.ts +0 -95
  303. package/dist/prompts/system.d.ts.map +0 -1
  304. package/dist/prompts/system.js +0 -461
  305. package/dist/prompts/system.js.map +0 -1
  306. package/dist/prompts/task-processor.d.ts +0 -94
  307. package/dist/prompts/task-processor.d.ts.map +0 -1
  308. package/dist/prompts/task-processor.js +0 -554
  309. package/dist/prompts/task-processor.js.map +0 -1
  310. package/dist/prompts/unguarded.d.ts +0 -78
  311. package/dist/prompts/unguarded.d.ts.map +0 -1
  312. package/dist/prompts/unguarded.js +0 -418
  313. package/dist/prompts/unguarded.js.map +0 -1
  314. package/dist/prompts/utils.d.ts +0 -73
  315. package/dist/prompts/utils.d.ts.map +0 -1
  316. package/dist/prompts/utils.js +0 -114
  317. package/dist/prompts/utils.js.map +0 -1
  318. package/dist/prompts/workflow.d.ts +0 -241
  319. package/dist/prompts/workflow.d.ts.map +0 -1
  320. package/dist/prompts/workflow.js +0 -608
  321. package/dist/prompts/workflow.js.map +0 -1
  322. package/dist/services/action-logger.d.ts +0 -383
  323. package/dist/services/action-logger.d.ts.map +0 -1
  324. package/dist/services/action-logger.js +0 -544
  325. package/dist/services/action-logger.js.map +0 -1
  326. package/dist/services/agent-budget-allocator.d.ts +0 -111
  327. package/dist/services/agent-budget-allocator.d.ts.map +0 -1
  328. package/dist/services/agent-budget-allocator.js +0 -278
  329. package/dist/services/agent-budget-allocator.js.map +0 -1
  330. package/dist/services/agent-manager.d.ts +0 -181
  331. package/dist/services/agent-manager.d.ts.map +0 -1
  332. package/dist/services/agent-manager.js +0 -749
  333. package/dist/services/agent-manager.js.map +0 -1
  334. package/dist/services/agent-spawner.d.ts +0 -138
  335. package/dist/services/agent-spawner.d.ts.map +0 -1
  336. package/dist/services/agent-spawner.js +0 -748
  337. package/dist/services/agent-spawner.js.map +0 -1
  338. package/dist/services/agent-state-service.d.ts +0 -145
  339. package/dist/services/agent-state-service.d.ts.map +0 -1
  340. package/dist/services/agent-state-service.js +0 -247
  341. package/dist/services/agent-state-service.js.map +0 -1
  342. package/dist/services/anthropic-client.d.ts +0 -357
  343. package/dist/services/anthropic-client.d.ts.map +0 -1
  344. package/dist/services/anthropic-client.js +0 -1451
  345. package/dist/services/anthropic-client.js.map +0 -1
  346. package/dist/services/approval-manager.d.ts +0 -385
  347. package/dist/services/approval-manager.d.ts.map +0 -1
  348. package/dist/services/approval-manager.js +0 -1044
  349. package/dist/services/approval-manager.js.map +0 -1
  350. package/dist/services/audit-logger.d.ts +0 -245
  351. package/dist/services/audit-logger.d.ts.map +0 -1
  352. package/dist/services/audit-logger.js +0 -324
  353. package/dist/services/audit-logger.js.map +0 -1
  354. package/dist/services/backup-manager.d.ts +0 -136
  355. package/dist/services/backup-manager.d.ts.map +0 -1
  356. package/dist/services/backup-manager.js +0 -260
  357. package/dist/services/backup-manager.js.map +0 -1
  358. package/dist/services/cache-service.d.ts +0 -247
  359. package/dist/services/cache-service.d.ts.map +0 -1
  360. package/dist/services/cache-service.js +0 -558
  361. package/dist/services/cache-service.js.map +0 -1
  362. package/dist/services/chat-archival-service.d.ts +0 -108
  363. package/dist/services/chat-archival-service.d.ts.map +0 -1
  364. package/dist/services/chat-archival-service.js +0 -465
  365. package/dist/services/chat-archival-service.js.map +0 -1
  366. package/dist/services/codebase-indexer.d.ts +0 -272
  367. package/dist/services/codebase-indexer.d.ts.map +0 -1
  368. package/dist/services/codebase-indexer.js +0 -863
  369. package/dist/services/codebase-indexer.js.map +0 -1
  370. package/dist/services/compass-auth-service.d.ts +0 -204
  371. package/dist/services/compass-auth-service.d.ts.map +0 -1
  372. package/dist/services/compass-auth-service.js +0 -391
  373. package/dist/services/compass-auth-service.js.map +0 -1
  374. package/dist/services/complexity-classifier.d.ts +0 -208
  375. package/dist/services/complexity-classifier.d.ts.map +0 -1
  376. package/dist/services/complexity-classifier.js +0 -1410
  377. package/dist/services/complexity-classifier.js.map +0 -1
  378. package/dist/services/config-manager.d.ts +0 -278
  379. package/dist/services/config-manager.d.ts.map +0 -1
  380. package/dist/services/config-manager.js +0 -651
  381. package/dist/services/config-manager.js.map +0 -1
  382. package/dist/services/consent-manager.d.ts +0 -239
  383. package/dist/services/consent-manager.d.ts.map +0 -1
  384. package/dist/services/consent-manager.js +0 -516
  385. package/dist/services/consent-manager.js.map +0 -1
  386. package/dist/services/conversation-compactor.d.ts +0 -223
  387. package/dist/services/conversation-compactor.d.ts.map +0 -1
  388. package/dist/services/conversation-compactor.js +0 -750
  389. package/dist/services/conversation-compactor.js.map +0 -1
  390. package/dist/services/cost-tracker.d.ts +0 -167
  391. package/dist/services/cost-tracker.d.ts.map +0 -1
  392. package/dist/services/cost-tracker.js +0 -199
  393. package/dist/services/cost-tracker.js.map +0 -1
  394. package/dist/services/credential-store.d.ts +0 -273
  395. package/dist/services/credential-store.d.ts.map +0 -1
  396. package/dist/services/credential-store.js +0 -877
  397. package/dist/services/credential-store.js.map +0 -1
  398. package/dist/services/custom-command-service.d.ts +0 -112
  399. package/dist/services/custom-command-service.d.ts.map +0 -1
  400. package/dist/services/custom-command-service.js +0 -464
  401. package/dist/services/custom-command-service.js.map +0 -1
  402. package/dist/services/default-statusline-renderer.d.ts +0 -60
  403. package/dist/services/default-statusline-renderer.d.ts.map +0 -1
  404. package/dist/services/default-statusline-renderer.js +0 -110
  405. package/dist/services/default-statusline-renderer.js.map +0 -1
  406. package/dist/services/enhanced-context-gatherer.d.ts +0 -116
  407. package/dist/services/enhanced-context-gatherer.d.ts.map +0 -1
  408. package/dist/services/enhanced-context-gatherer.js +0 -605
  409. package/dist/services/enhanced-context-gatherer.js.map +0 -1
  410. package/dist/services/file-hash-tracker.d.ts +0 -95
  411. package/dist/services/file-hash-tracker.d.ts.map +0 -1
  412. package/dist/services/file-hash-tracker.js +0 -199
  413. package/dist/services/file-hash-tracker.js.map +0 -1
  414. package/dist/services/file-service.d.ts +0 -274
  415. package/dist/services/file-service.d.ts.map +0 -1
  416. package/dist/services/file-service.js +0 -876
  417. package/dist/services/file-service.js.map +0 -1
  418. package/dist/services/git-service.d.ts +0 -536
  419. package/dist/services/git-service.d.ts.map +0 -1
  420. package/dist/services/git-service.js +0 -1215
  421. package/dist/services/git-service.js.map +0 -1
  422. package/dist/services/hook-service.d.ts +0 -148
  423. package/dist/services/hook-service.d.ts.map +0 -1
  424. package/dist/services/hook-service.js +0 -705
  425. package/dist/services/hook-service.js.map +0 -1
  426. package/dist/services/ide-state-service.d.ts +0 -114
  427. package/dist/services/ide-state-service.d.ts.map +0 -1
  428. package/dist/services/ide-state-service.js +0 -204
  429. package/dist/services/ide-state-service.js.map +0 -1
  430. package/dist/services/interactive-clarifier.d.ts +0 -90
  431. package/dist/services/interactive-clarifier.d.ts.map +0 -1
  432. package/dist/services/interactive-clarifier.js +0 -446
  433. package/dist/services/interactive-clarifier.js.map +0 -1
  434. package/dist/services/iteration-scoper.d.ts +0 -225
  435. package/dist/services/iteration-scoper.d.ts.map +0 -1
  436. package/dist/services/iteration-scoper.js +0 -387
  437. package/dist/services/iteration-scoper.js.map +0 -1
  438. package/dist/services/llm-plan-generator.d.ts +0 -44
  439. package/dist/services/llm-plan-generator.d.ts.map +0 -1
  440. package/dist/services/llm-plan-generator.js +0 -863
  441. package/dist/services/llm-plan-generator.js.map +0 -1
  442. package/dist/services/llm-system-prompt-generator.d.ts +0 -85
  443. package/dist/services/llm-system-prompt-generator.d.ts.map +0 -1
  444. package/dist/services/llm-system-prompt-generator.js +0 -257
  445. package/dist/services/llm-system-prompt-generator.js.map +0 -1
  446. package/dist/services/log-interpreter.d.ts +0 -190
  447. package/dist/services/log-interpreter.d.ts.map +0 -1
  448. package/dist/services/log-interpreter.js +0 -520
  449. package/dist/services/log-interpreter.js.map +0 -1
  450. package/dist/services/mcp-config-manager.d.ts +0 -141
  451. package/dist/services/mcp-config-manager.d.ts.map +0 -1
  452. package/dist/services/mcp-config-manager.js +0 -678
  453. package/dist/services/mcp-config-manager.js.map +0 -1
  454. package/dist/services/mcp-oauth-service.d.ts +0 -170
  455. package/dist/services/mcp-oauth-service.d.ts.map +0 -1
  456. package/dist/services/mcp-oauth-service.js +0 -892
  457. package/dist/services/mcp-oauth-service.js.map +0 -1
  458. package/dist/services/mcp-plugin-support.d.ts +0 -81
  459. package/dist/services/mcp-plugin-support.d.ts.map +0 -1
  460. package/dist/services/mcp-plugin-support.js +0 -305
  461. package/dist/services/mcp-plugin-support.js.map +0 -1
  462. package/dist/services/mcp-server-manager.d.ts +0 -134
  463. package/dist/services/mcp-server-manager.d.ts.map +0 -1
  464. package/dist/services/mcp-server-manager.js +0 -613
  465. package/dist/services/mcp-server-manager.js.map +0 -1
  466. package/dist/services/mcp-tool-integration.d.ts +0 -119
  467. package/dist/services/mcp-tool-integration.d.ts.map +0 -1
  468. package/dist/services/mcp-tool-integration.js +0 -381
  469. package/dist/services/mcp-tool-integration.js.map +0 -1
  470. package/dist/services/mcp-transport.d.ts +0 -105
  471. package/dist/services/mcp-transport.d.ts.map +0 -1
  472. package/dist/services/mcp-transport.js +0 -1316
  473. package/dist/services/mcp-transport.js.map +0 -1
  474. package/dist/services/memory-service.d.ts +0 -55
  475. package/dist/services/memory-service.d.ts.map +0 -1
  476. package/dist/services/memory-service.js +0 -251
  477. package/dist/services/memory-service.js.map +0 -1
  478. package/dist/services/model-availability.d.ts +0 -64
  479. package/dist/services/model-availability.d.ts.map +0 -1
  480. package/dist/services/model-availability.js +0 -114
  481. package/dist/services/model-availability.js.map +0 -1
  482. package/dist/services/plan-generator.d.ts +0 -98
  483. package/dist/services/plan-generator.d.ts.map +0 -1
  484. package/dist/services/plan-generator.js +0 -658
  485. package/dist/services/plan-generator.js.map +0 -1
  486. package/dist/services/plan-mode-fallback.d.ts +0 -80
  487. package/dist/services/plan-mode-fallback.d.ts.map +0 -1
  488. package/dist/services/plan-mode-fallback.js +0 -307
  489. package/dist/services/plan-mode-fallback.js.map +0 -1
  490. package/dist/services/plan-mode-handler.d.ts +0 -42
  491. package/dist/services/plan-mode-handler.d.ts.map +0 -1
  492. package/dist/services/plan-mode-handler.js +0 -388
  493. package/dist/services/plan-mode-handler.js.map +0 -1
  494. package/dist/services/plan-persistence.d.ts +0 -203
  495. package/dist/services/plan-persistence.d.ts.map +0 -1
  496. package/dist/services/plan-persistence.js +0 -538
  497. package/dist/services/plan-persistence.js.map +0 -1
  498. package/dist/services/prompt-preprocessor.d.ts +0 -73
  499. package/dist/services/prompt-preprocessor.d.ts.map +0 -1
  500. package/dist/services/prompt-preprocessor.js +0 -146
  501. package/dist/services/prompt-preprocessor.js.map +0 -1
  502. package/dist/services/rating-service.d.ts +0 -84
  503. package/dist/services/rating-service.d.ts.map +0 -1
  504. package/dist/services/rating-service.js +0 -171
  505. package/dist/services/rating-service.js.map +0 -1
  506. package/dist/services/rating-state-manager.d.ts +0 -131
  507. package/dist/services/rating-state-manager.d.ts.map +0 -1
  508. package/dist/services/rating-state-manager.js +0 -270
  509. package/dist/services/rating-state-manager.js.map +0 -1
  510. package/dist/services/sdk-runner.d.ts +0 -113
  511. package/dist/services/sdk-runner.d.ts.map +0 -1
  512. package/dist/services/sdk-runner.js +0 -1424
  513. package/dist/services/sdk-runner.js.map +0 -1
  514. package/dist/services/session-manager.d.ts +0 -528
  515. package/dist/services/session-manager.d.ts.map +0 -1
  516. package/dist/services/session-manager.js +0 -1184
  517. package/dist/services/session-manager.js.map +0 -1
  518. package/dist/services/shell-executor.d.ts +0 -337
  519. package/dist/services/shell-executor.d.ts.map +0 -1
  520. package/dist/services/shell-executor.js +0 -1201
  521. package/dist/services/shell-executor.js.map +0 -1
  522. package/dist/services/skill-service.d.ts +0 -149
  523. package/dist/services/skill-service.d.ts.map +0 -1
  524. package/dist/services/skill-service.js +0 -594
  525. package/dist/services/skill-service.js.map +0 -1
  526. package/dist/services/statusline-executor.d.ts +0 -102
  527. package/dist/services/statusline-executor.d.ts.map +0 -1
  528. package/dist/services/statusline-executor.js +0 -305
  529. package/dist/services/statusline-executor.js.map +0 -1
  530. package/dist/services/step-tracker.d.ts +0 -356
  531. package/dist/services/step-tracker.d.ts.map +0 -1
  532. package/dist/services/step-tracker.js +0 -634
  533. package/dist/services/step-tracker.js.map +0 -1
  534. package/dist/services/system-event-logger.d.ts +0 -473
  535. package/dist/services/system-event-logger.d.ts.map +0 -1
  536. package/dist/services/system-event-logger.js +0 -790
  537. package/dist/services/system-event-logger.js.map +0 -1
  538. package/dist/services/system-utility-detector.d.ts +0 -91
  539. package/dist/services/system-utility-detector.d.ts.map +0 -1
  540. package/dist/services/system-utility-detector.js +0 -238
  541. package/dist/services/system-utility-detector.js.map +0 -1
  542. package/dist/services/team-context-store.d.ts +0 -100
  543. package/dist/services/team-context-store.d.ts.map +0 -1
  544. package/dist/services/team-context-store.js +0 -513
  545. package/dist/services/team-context-store.js.map +0 -1
  546. package/dist/services/temp-file-service.d.ts +0 -164
  547. package/dist/services/temp-file-service.d.ts.map +0 -1
  548. package/dist/services/temp-file-service.js +0 -303
  549. package/dist/services/temp-file-service.js.map +0 -1
  550. package/dist/services/token-limit-enforcer.d.ts +0 -53
  551. package/dist/services/token-limit-enforcer.d.ts.map +0 -1
  552. package/dist/services/token-limit-enforcer.js +0 -90
  553. package/dist/services/token-limit-enforcer.js.map +0 -1
  554. package/dist/services/token-limit-store.d.ts +0 -105
  555. package/dist/services/token-limit-store.d.ts.map +0 -1
  556. package/dist/services/token-limit-store.js +0 -288
  557. package/dist/services/token-limit-store.js.map +0 -1
  558. package/dist/services/token-tracker.d.ts +0 -290
  559. package/dist/services/token-tracker.d.ts.map +0 -1
  560. package/dist/services/token-tracker.js +0 -751
  561. package/dist/services/token-tracker.js.map +0 -1
  562. package/dist/services/tool-registry.d.ts +0 -302
  563. package/dist/services/tool-registry.d.ts.map +0 -1
  564. package/dist/services/tool-registry.js +0 -606
  565. package/dist/services/tool-registry.js.map +0 -1
  566. package/dist/services/tools-logger.d.ts +0 -152
  567. package/dist/services/tools-logger.d.ts.map +0 -1
  568. package/dist/services/tools-logger.js +0 -222
  569. package/dist/services/tools-logger.js.map +0 -1
  570. package/dist/services/update-plan-handler.d.ts +0 -56
  571. package/dist/services/update-plan-handler.d.ts.map +0 -1
  572. package/dist/services/update-plan-handler.js +0 -372
  573. package/dist/services/update-plan-handler.js.map +0 -1
  574. package/dist/services/update-service.d.ts +0 -197
  575. package/dist/services/update-service.d.ts.map +0 -1
  576. package/dist/services/update-service.js +0 -749
  577. package/dist/services/update-service.js.map +0 -1
  578. package/dist/services/verifier.d.ts +0 -113
  579. package/dist/services/verifier.d.ts.map +0 -1
  580. package/dist/services/verifier.js +0 -541
  581. package/dist/services/verifier.js.map +0 -1
  582. package/dist/services/workflow-manager.d.ts +0 -277
  583. package/dist/services/workflow-manager.d.ts.map +0 -1
  584. package/dist/services/workflow-manager.js +0 -616
  585. package/dist/services/workflow-manager.js.map +0 -1
  586. package/dist/services/workflow-orchestrator.d.ts +0 -148
  587. package/dist/services/workflow-orchestrator.d.ts.map +0 -1
  588. package/dist/services/workflow-orchestrator.js +0 -617
  589. package/dist/services/workflow-orchestrator.js.map +0 -1
  590. package/dist/services/worktree-manager.d.ts +0 -36
  591. package/dist/services/worktree-manager.d.ts.map +0 -1
  592. package/dist/services/worktree-manager.js +0 -185
  593. package/dist/services/worktree-manager.js.map +0 -1
  594. package/dist/templates/ascii-art.d.ts +0 -136
  595. package/dist/templates/ascii-art.d.ts.map +0 -1
  596. package/dist/templates/ascii-art.js +0 -286
  597. package/dist/templates/ascii-art.js.map +0 -1
  598. package/dist/templates/help.d.ts +0 -186
  599. package/dist/templates/help.d.ts.map +0 -1
  600. package/dist/templates/help.js +0 -588
  601. package/dist/templates/help.js.map +0 -1
  602. package/dist/templates/prompts/workflow-prompts.d.ts +0 -9
  603. package/dist/templates/prompts/workflow-prompts.d.ts.map +0 -1
  604. package/dist/templates/prompts/workflow-prompts.js +0 -9
  605. package/dist/templates/prompts/workflow-prompts.js.map +0 -1
  606. package/dist/tools/agent-tools.d.ts +0 -9
  607. package/dist/tools/agent-tools.d.ts.map +0 -1
  608. package/dist/tools/agent-tools.js +0 -349
  609. package/dist/tools/agent-tools.js.map +0 -1
  610. package/dist/tools/edit-replacers.d.ts +0 -90
  611. package/dist/tools/edit-replacers.d.ts.map +0 -1
  612. package/dist/tools/edit-replacers.js +0 -553
  613. package/dist/tools/edit-replacers.js.map +0 -1
  614. package/dist/tools/file-tools.d.ts +0 -13
  615. package/dist/tools/file-tools.d.ts.map +0 -1
  616. package/dist/tools/file-tools.js +0 -954
  617. package/dist/tools/file-tools.js.map +0 -1
  618. package/dist/tools/git-tools.d.ts +0 -9
  619. package/dist/tools/git-tools.d.ts.map +0 -1
  620. package/dist/tools/git-tools.js +0 -261
  621. package/dist/tools/git-tools.js.map +0 -1
  622. package/dist/tools/index.d.ts +0 -13
  623. package/dist/tools/index.d.ts.map +0 -1
  624. package/dist/tools/index.js +0 -70
  625. package/dist/tools/index.js.map +0 -1
  626. package/dist/tools/network-tools.d.ts +0 -8
  627. package/dist/tools/network-tools.d.ts.map +0 -1
  628. package/dist/tools/network-tools.js +0 -261
  629. package/dist/tools/network-tools.js.map +0 -1
  630. package/dist/tools/openai-tools.d.ts +0 -16
  631. package/dist/tools/openai-tools.d.ts.map +0 -1
  632. package/dist/tools/openai-tools.js +0 -385
  633. package/dist/tools/openai-tools.js.map +0 -1
  634. package/dist/tools/plan-tools.d.ts +0 -9
  635. package/dist/tools/plan-tools.d.ts.map +0 -1
  636. package/dist/tools/plan-tools.js +0 -223
  637. package/dist/tools/plan-tools.js.map +0 -1
  638. package/dist/tools/schedule-tools.d.ts +0 -9
  639. package/dist/tools/schedule-tools.d.ts.map +0 -1
  640. package/dist/tools/schedule-tools.js +0 -405
  641. package/dist/tools/schedule-tools.js.map +0 -1
  642. package/dist/tools/search-tools.d.ts +0 -8
  643. package/dist/tools/search-tools.d.ts.map +0 -1
  644. package/dist/tools/search-tools.js +0 -357
  645. package/dist/tools/search-tools.js.map +0 -1
  646. package/dist/tools/shared-utils.d.ts +0 -91
  647. package/dist/tools/shared-utils.d.ts.map +0 -1
  648. package/dist/tools/shared-utils.js +0 -385
  649. package/dist/tools/shared-utils.js.map +0 -1
  650. package/dist/tools/shell-tools.d.ts +0 -9
  651. package/dist/tools/shell-tools.d.ts.map +0 -1
  652. package/dist/tools/shell-tools.js +0 -409
  653. package/dist/tools/shell-tools.js.map +0 -1
  654. package/dist/tools/skill-tools.d.ts +0 -13
  655. package/dist/tools/skill-tools.d.ts.map +0 -1
  656. package/dist/tools/skill-tools.js +0 -244
  657. package/dist/tools/skill-tools.js.map +0 -1
  658. package/dist/tools/swarm-tools.d.ts +0 -9
  659. package/dist/tools/swarm-tools.d.ts.map +0 -1
  660. package/dist/tools/swarm-tools.js +0 -422
  661. package/dist/tools/swarm-tools.js.map +0 -1
  662. package/dist/tools/user-tools.d.ts +0 -13
  663. package/dist/tools/user-tools.d.ts.map +0 -1
  664. package/dist/tools/user-tools.js +0 -232
  665. package/dist/tools/user-tools.js.map +0 -1
  666. package/dist/types/agent-process.d.ts +0 -244
  667. package/dist/types/agent-process.d.ts.map +0 -1
  668. package/dist/types/agent-process.js +0 -93
  669. package/dist/types/agent-process.js.map +0 -1
  670. package/dist/types/agent.d.ts +0 -358
  671. package/dist/types/agent.d.ts.map +0 -1
  672. package/dist/types/agent.js +0 -171
  673. package/dist/types/agent.js.map +0 -1
  674. package/dist/types/anthropic.d.ts +0 -438
  675. package/dist/types/anthropic.d.ts.map +0 -1
  676. package/dist/types/anthropic.js +0 -9
  677. package/dist/types/anthropic.js.map +0 -1
  678. package/dist/types/approval.d.ts +0 -332
  679. package/dist/types/approval.d.ts.map +0 -1
  680. package/dist/types/approval.js +0 -44
  681. package/dist/types/approval.js.map +0 -1
  682. package/dist/types/autocomplete.d.ts +0 -57
  683. package/dist/types/autocomplete.d.ts.map +0 -1
  684. package/dist/types/autocomplete.js +0 -7
  685. package/dist/types/autocomplete.js.map +0 -1
  686. package/dist/types/chat-archive.d.ts +0 -161
  687. package/dist/types/chat-archive.d.ts.map +0 -1
  688. package/dist/types/chat-archive.js +0 -36
  689. package/dist/types/chat-archive.js.map +0 -1
  690. package/dist/types/config.d.ts +0 -268
  691. package/dist/types/config.d.ts.map +0 -1
  692. package/dist/types/config.js +0 -188
  693. package/dist/types/config.js.map +0 -1
  694. package/dist/types/consent.d.ts +0 -191
  695. package/dist/types/consent.d.ts.map +0 -1
  696. package/dist/types/consent.js +0 -119
  697. package/dist/types/consent.js.map +0 -1
  698. package/dist/types/custom-command.d.ts +0 -139
  699. package/dist/types/custom-command.d.ts.map +0 -1
  700. package/dist/types/custom-command.js +0 -7
  701. package/dist/types/custom-command.js.map +0 -1
  702. package/dist/types/git.d.ts +0 -20
  703. package/dist/types/git.d.ts.map +0 -1
  704. package/dist/types/git.js +0 -2
  705. package/dist/types/git.js.map +0 -1
  706. package/dist/types/hook.d.ts +0 -342
  707. package/dist/types/hook.d.ts.map +0 -1
  708. package/dist/types/hook.js +0 -84
  709. package/dist/types/hook.js.map +0 -1
  710. package/dist/types/index.d.ts +0 -86
  711. package/dist/types/index.d.ts.map +0 -1
  712. package/dist/types/index.js +0 -71
  713. package/dist/types/index.js.map +0 -1
  714. package/dist/types/mcp.d.ts +0 -456
  715. package/dist/types/mcp.d.ts.map +0 -1
  716. package/dist/types/mcp.js +0 -94
  717. package/dist/types/mcp.js.map +0 -1
  718. package/dist/types/rating.d.ts +0 -110
  719. package/dist/types/rating.d.ts.map +0 -1
  720. package/dist/types/rating.js +0 -53
  721. package/dist/types/rating.js.map +0 -1
  722. package/dist/types/schedule.d.ts +0 -91
  723. package/dist/types/schedule.d.ts.map +0 -1
  724. package/dist/types/schedule.js +0 -8
  725. package/dist/types/schedule.js.map +0 -1
  726. package/dist/types/session.d.ts +0 -361
  727. package/dist/types/session.d.ts.map +0 -1
  728. package/dist/types/session.js +0 -9
  729. package/dist/types/session.js.map +0 -1
  730. package/dist/types/skill.d.ts +0 -258
  731. package/dist/types/skill.d.ts.map +0 -1
  732. package/dist/types/skill.js +0 -79
  733. package/dist/types/skill.js.map +0 -1
  734. package/dist/types/statusline.d.ts +0 -212
  735. package/dist/types/statusline.d.ts.map +0 -1
  736. package/dist/types/statusline.js +0 -8
  737. package/dist/types/statusline.js.map +0 -1
  738. package/dist/types/stream.d.ts +0 -61
  739. package/dist/types/stream.d.ts.map +0 -1
  740. package/dist/types/stream.js +0 -17
  741. package/dist/types/stream.js.map +0 -1
  742. package/dist/types/swarm.d.ts +0 -132
  743. package/dist/types/swarm.d.ts.map +0 -1
  744. package/dist/types/swarm.js +0 -21
  745. package/dist/types/swarm.js.map +0 -1
  746. package/dist/types/token-limits.d.ts +0 -107
  747. package/dist/types/token-limits.d.ts.map +0 -1
  748. package/dist/types/token-limits.js +0 -57
  749. package/dist/types/token-limits.js.map +0 -1
  750. package/dist/types/token.d.ts +0 -329
  751. package/dist/types/token.d.ts.map +0 -1
  752. package/dist/types/token.js +0 -9
  753. package/dist/types/token.js.map +0 -1
  754. package/dist/types/update.d.ts +0 -189
  755. package/dist/types/update.d.ts.map +0 -1
  756. package/dist/types/update.js +0 -55
  757. package/dist/types/update.js.map +0 -1
  758. package/dist/types/workflow.d.ts +0 -396
  759. package/dist/types/workflow.d.ts.map +0 -1
  760. package/dist/types/workflow.js +0 -46
  761. package/dist/types/workflow.js.map +0 -1
  762. package/dist/ui/App.d.ts +0 -62
  763. package/dist/ui/App.d.ts.map +0 -1
  764. package/dist/ui/App.js +0 -511
  765. package/dist/ui/App.js.map +0 -1
  766. package/dist/ui/InteractiveSession.d.ts +0 -34
  767. package/dist/ui/InteractiveSession.d.ts.map +0 -1
  768. package/dist/ui/InteractiveSession.js +0 -3611
  769. package/dist/ui/InteractiveSession.js.map +0 -1
  770. package/dist/ui/MCPApprovalPrompt.d.ts +0 -27
  771. package/dist/ui/MCPApprovalPrompt.d.ts.map +0 -1
  772. package/dist/ui/MCPApprovalPrompt.js +0 -51
  773. package/dist/ui/MCPApprovalPrompt.js.map +0 -1
  774. package/dist/ui/SetupWizard.d.ts +0 -26
  775. package/dist/ui/SetupWizard.d.ts.map +0 -1
  776. package/dist/ui/SetupWizard.js +0 -396
  777. package/dist/ui/SetupWizard.js.map +0 -1
  778. package/dist/ui/components/AgentCreationWizard.d.ts +0 -36
  779. package/dist/ui/components/AgentCreationWizard.d.ts.map +0 -1
  780. package/dist/ui/components/AgentCreationWizard.js +0 -619
  781. package/dist/ui/components/AgentCreationWizard.js.map +0 -1
  782. package/dist/ui/components/AgentManager.d.ts +0 -41
  783. package/dist/ui/components/AgentManager.d.ts.map +0 -1
  784. package/dist/ui/components/AgentManager.js +0 -343
  785. package/dist/ui/components/AgentManager.js.map +0 -1
  786. package/dist/ui/components/ApprovalDialog.d.ts +0 -18
  787. package/dist/ui/components/ApprovalDialog.d.ts.map +0 -1
  788. package/dist/ui/components/ApprovalDialog.js +0 -439
  789. package/dist/ui/components/ApprovalDialog.js.map +0 -1
  790. package/dist/ui/components/AsciiArt.d.ts +0 -54
  791. package/dist/ui/components/AsciiArt.d.ts.map +0 -1
  792. package/dist/ui/components/AsciiArt.js +0 -89
  793. package/dist/ui/components/AsciiArt.js.map +0 -1
  794. package/dist/ui/components/ClarificationWizard.d.ts +0 -36
  795. package/dist/ui/components/ClarificationWizard.d.ts.map +0 -1
  796. package/dist/ui/components/ClarificationWizard.js +0 -407
  797. package/dist/ui/components/ClarificationWizard.js.map +0 -1
  798. package/dist/ui/components/CompassSpinner.d.ts +0 -15
  799. package/dist/ui/components/CompassSpinner.d.ts.map +0 -1
  800. package/dist/ui/components/CompassSpinner.js +0 -50
  801. package/dist/ui/components/CompassSpinner.js.map +0 -1
  802. package/dist/ui/components/ConfirmationSelector.d.ts +0 -45
  803. package/dist/ui/components/ConfirmationSelector.d.ts.map +0 -1
  804. package/dist/ui/components/ConfirmationSelector.js +0 -106
  805. package/dist/ui/components/ConfirmationSelector.js.map +0 -1
  806. package/dist/ui/components/ContextUsage.d.ts +0 -76
  807. package/dist/ui/components/ContextUsage.d.ts.map +0 -1
  808. package/dist/ui/components/ContextUsage.js +0 -188
  809. package/dist/ui/components/ContextUsage.js.map +0 -1
  810. package/dist/ui/components/DiffPreview.d.ts +0 -13
  811. package/dist/ui/components/DiffPreview.d.ts.map +0 -1
  812. package/dist/ui/components/DiffPreview.js +0 -30
  813. package/dist/ui/components/DiffPreview.js.map +0 -1
  814. package/dist/ui/components/ExecutionModeSelector.d.ts +0 -45
  815. package/dist/ui/components/ExecutionModeSelector.d.ts.map +0 -1
  816. package/dist/ui/components/ExecutionModeSelector.js +0 -120
  817. package/dist/ui/components/ExecutionModeSelector.js.map +0 -1
  818. package/dist/ui/components/FileTree.d.ts +0 -47
  819. package/dist/ui/components/FileTree.d.ts.map +0 -1
  820. package/dist/ui/components/FileTree.js +0 -258
  821. package/dist/ui/components/FileTree.js.map +0 -1
  822. package/dist/ui/components/HelpMenu.d.ts +0 -49
  823. package/dist/ui/components/HelpMenu.d.ts.map +0 -1
  824. package/dist/ui/components/HelpMenu.js +0 -91
  825. package/dist/ui/components/HelpMenu.js.map +0 -1
  826. package/dist/ui/components/InterleavedStream.d.ts +0 -42
  827. package/dist/ui/components/InterleavedStream.d.ts.map +0 -1
  828. package/dist/ui/components/InterleavedStream.js +0 -1500
  829. package/dist/ui/components/InterleavedStream.js.map +0 -1
  830. package/dist/ui/components/ModelSelector.d.ts +0 -81
  831. package/dist/ui/components/ModelSelector.d.ts.map +0 -1
  832. package/dist/ui/components/ModelSelector.js +0 -305
  833. package/dist/ui/components/ModelSelector.js.map +0 -1
  834. package/dist/ui/components/PlanApprovalDialog.d.ts +0 -21
  835. package/dist/ui/components/PlanApprovalDialog.d.ts.map +0 -1
  836. package/dist/ui/components/PlanApprovalDialog.js +0 -189
  837. package/dist/ui/components/PlanApprovalDialog.js.map +0 -1
  838. package/dist/ui/components/PlanExecutionTracker.d.ts +0 -53
  839. package/dist/ui/components/PlanExecutionTracker.d.ts.map +0 -1
  840. package/dist/ui/components/PlanExecutionTracker.js +0 -113
  841. package/dist/ui/components/PlanExecutionTracker.js.map +0 -1
  842. package/dist/ui/components/ProgressIndicator.d.ts +0 -151
  843. package/dist/ui/components/ProgressIndicator.d.ts.map +0 -1
  844. package/dist/ui/components/ProgressIndicator.js +0 -171
  845. package/dist/ui/components/ProgressIndicator.js.map +0 -1
  846. package/dist/ui/components/Prompt.d.ts +0 -47
  847. package/dist/ui/components/Prompt.d.ts.map +0 -1
  848. package/dist/ui/components/Prompt.js +0 -632
  849. package/dist/ui/components/Prompt.js.map +0 -1
  850. package/dist/ui/components/RatingPanel.d.ts +0 -45
  851. package/dist/ui/components/RatingPanel.d.ts.map +0 -1
  852. package/dist/ui/components/RatingPanel.js +0 -119
  853. package/dist/ui/components/RatingPanel.js.map +0 -1
  854. package/dist/ui/components/StatusLine.d.ts +0 -43
  855. package/dist/ui/components/StatusLine.d.ts.map +0 -1
  856. package/dist/ui/components/StatusLine.js +0 -44
  857. package/dist/ui/components/StatusLine.js.map +0 -1
  858. package/dist/ui/components/StreamingResponse.d.ts +0 -45
  859. package/dist/ui/components/StreamingResponse.d.ts.map +0 -1
  860. package/dist/ui/components/StreamingResponse.js +0 -56
  861. package/dist/ui/components/StreamingResponse.js.map +0 -1
  862. package/dist/ui/components/TokenUsage.d.ts +0 -89
  863. package/dist/ui/components/TokenUsage.d.ts.map +0 -1
  864. package/dist/ui/components/TokenUsage.js +0 -99
  865. package/dist/ui/components/TokenUsage.js.map +0 -1
  866. package/dist/ui/components/ToolSummary.d.ts +0 -77
  867. package/dist/ui/components/ToolSummary.d.ts.map +0 -1
  868. package/dist/ui/components/ToolSummary.js +0 -162
  869. package/dist/ui/components/ToolSummary.js.map +0 -1
  870. package/dist/ui/components/UpdateNotification.d.ts +0 -65
  871. package/dist/ui/components/UpdateNotification.d.ts.map +0 -1
  872. package/dist/ui/components/UpdateNotification.js +0 -166
  873. package/dist/ui/components/UpdateNotification.js.map +0 -1
  874. package/dist/ui/diff-renderer.d.ts +0 -18
  875. package/dist/ui/diff-renderer.d.ts.map +0 -1
  876. package/dist/ui/diff-renderer.js +0 -206
  877. package/dist/ui/diff-renderer.js.map +0 -1
  878. package/dist/ui/themes/markdown-theme.d.ts +0 -48
  879. package/dist/ui/themes/markdown-theme.d.ts.map +0 -1
  880. package/dist/ui/themes/markdown-theme.js +0 -79
  881. package/dist/ui/themes/markdown-theme.js.map +0 -1
  882. package/dist/ui/themes/ui-theme.d.ts +0 -301
  883. package/dist/ui/themes/ui-theme.d.ts.map +0 -1
  884. package/dist/ui/themes/ui-theme.js +0 -204
  885. package/dist/ui/themes/ui-theme.js.map +0 -1
  886. package/dist/utils/attachment-handler.d.ts +0 -129
  887. package/dist/utils/attachment-handler.d.ts.map +0 -1
  888. package/dist/utils/attachment-handler.js +0 -280
  889. package/dist/utils/attachment-handler.js.map +0 -1
  890. package/dist/utils/backup-cleanup.d.ts +0 -28
  891. package/dist/utils/backup-cleanup.d.ts.map +0 -1
  892. package/dist/utils/backup-cleanup.js +0 -99
  893. package/dist/utils/backup-cleanup.js.map +0 -1
  894. package/dist/utils/clipboard-handler.d.ts +0 -82
  895. package/dist/utils/clipboard-handler.d.ts.map +0 -1
  896. package/dist/utils/clipboard-handler.js +0 -311
  897. package/dist/utils/clipboard-handler.js.map +0 -1
  898. package/dist/utils/cloud-detection.d.ts +0 -14
  899. package/dist/utils/cloud-detection.d.ts.map +0 -1
  900. package/dist/utils/cloud-detection.js +0 -92
  901. package/dist/utils/cloud-detection.js.map +0 -1
  902. package/dist/utils/command-parser.d.ts +0 -56
  903. package/dist/utils/command-parser.d.ts.map +0 -1
  904. package/dist/utils/command-parser.js +0 -206
  905. package/dist/utils/command-parser.js.map +0 -1
  906. package/dist/utils/console-capture.d.ts +0 -30
  907. package/dist/utils/console-capture.d.ts.map +0 -1
  908. package/dist/utils/console-capture.js +0 -88
  909. package/dist/utils/console-capture.js.map +0 -1
  910. package/dist/utils/cron-parser.d.ts +0 -52
  911. package/dist/utils/cron-parser.d.ts.map +0 -1
  912. package/dist/utils/cron-parser.js +0 -455
  913. package/dist/utils/cron-parser.js.map +0 -1
  914. package/dist/utils/crypto.d.ts +0 -351
  915. package/dist/utils/crypto.d.ts.map +0 -1
  916. package/dist/utils/crypto.js +0 -615
  917. package/dist/utils/crypto.js.map +0 -1
  918. package/dist/utils/diff.d.ts +0 -311
  919. package/dist/utils/diff.d.ts.map +0 -1
  920. package/dist/utils/diff.js +0 -566
  921. package/dist/utils/diff.js.map +0 -1
  922. package/dist/utils/editor.d.ts +0 -12
  923. package/dist/utils/editor.d.ts.map +0 -1
  924. package/dist/utils/editor.js +0 -30
  925. package/dist/utils/editor.js.map +0 -1
  926. package/dist/utils/file-system.d.ts +0 -512
  927. package/dist/utils/file-system.d.ts.map +0 -1
  928. package/dist/utils/file-system.js +0 -798
  929. package/dist/utils/file-system.js.map +0 -1
  930. package/dist/utils/format.d.ts +0 -318
  931. package/dist/utils/format.d.ts.map +0 -1
  932. package/dist/utils/format.js +0 -587
  933. package/dist/utils/format.js.map +0 -1
  934. package/dist/utils/ignore-patterns.d.ts +0 -93
  935. package/dist/utils/ignore-patterns.d.ts.map +0 -1
  936. package/dist/utils/ignore-patterns.js +0 -710
  937. package/dist/utils/ignore-patterns.js.map +0 -1
  938. package/dist/utils/log-cleanup.d.ts +0 -16
  939. package/dist/utils/log-cleanup.d.ts.map +0 -1
  940. package/dist/utils/log-cleanup.js +0 -51
  941. package/dist/utils/log-cleanup.js.map +0 -1
  942. package/dist/utils/logger.d.ts +0 -305
  943. package/dist/utils/logger.d.ts.map +0 -1
  944. package/dist/utils/logger.js +0 -447
  945. package/dist/utils/logger.js.map +0 -1
  946. package/dist/utils/path.d.ts +0 -406
  947. package/dist/utils/path.d.ts.map +0 -1
  948. package/dist/utils/path.js +0 -549
  949. package/dist/utils/path.js.map +0 -1
  950. package/dist/utils/schedule-file.d.ts +0 -63
  951. package/dist/utils/schedule-file.d.ts.map +0 -1
  952. package/dist/utils/schedule-file.js +0 -244
  953. package/dist/utils/schedule-file.js.map +0 -1
  954. package/dist/utils/task-id.d.ts +0 -29
  955. package/dist/utils/task-id.d.ts.map +0 -1
  956. package/dist/utils/task-id.js +0 -53
  957. package/dist/utils/task-id.js.map +0 -1
  958. package/dist/utils/temp-cleanup.d.ts +0 -46
  959. package/dist/utils/temp-cleanup.d.ts.map +0 -1
  960. package/dist/utils/temp-cleanup.js +0 -95
  961. package/dist/utils/temp-cleanup.js.map +0 -1
  962. package/dist/utils/terminal-output.d.ts +0 -34
  963. package/dist/utils/terminal-output.d.ts.map +0 -1
  964. package/dist/utils/terminal-output.js +0 -40
  965. package/dist/utils/terminal-output.js.map +0 -1
  966. package/dist/utils/token-counter.d.ts +0 -224
  967. package/dist/utils/token-counter.d.ts.map +0 -1
  968. package/dist/utils/token-counter.js +0 -332
  969. package/dist/utils/token-counter.js.map +0 -1
  970. package/dist/utils/tool-mapper.d.ts +0 -70
  971. package/dist/utils/tool-mapper.d.ts.map +0 -1
  972. package/dist/utils/tool-mapper.js +0 -234
  973. package/dist/utils/tool-mapper.js.map +0 -1
@@ -1,3611 +0,0 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- /**
3
- * React/ink component for main interactive session
4
- * Manages the prompt, streaming responses, slash command handling,
5
- * approval dialogs, and conversation state.
6
- */
7
- import React, { useState, useEffect, useCallback, useRef, useMemo } from 'react';
8
- import { Box, Text, useInput, Static } from 'ink';
9
- import chalk from 'chalk';
10
- import { Prompt } from './components/Prompt.js';
11
- import { Markdown } from './components/StreamingResponse.js';
12
- import { InterleavedStream } from './components/InterleavedStream.js';
13
- import { ApprovalDialog } from './components/ApprovalDialog.js';
14
- import { PlanApprovalDialog } from './components/PlanApprovalDialog.js';
15
- import { ClarificationWizard } from './components/ClarificationWizard.js';
16
- import { HelpMenu } from './components/HelpMenu.js';
17
- import { TokenUsage } from './components/TokenUsage.js';
18
- import { AgentManager } from './components/AgentManager.js';
19
- import { AgentCreationWizard } from './components/AgentCreationWizard.js';
20
- import { DiffPreview } from './components/DiffPreview.js';
21
- import { PlanExecutionTracker } from './components/PlanExecutionTracker.js';
22
- import { ExecutionModeSelector } from './components/ExecutionModeSelector.js';
23
- import { ConfirmationSelector } from './components/ConfirmationSelector.js';
24
- import { RatingPanel } from './components/RatingPanel.js';
25
- import { CompassSpinner } from './components/CompassSpinner.js';
26
- import { createTextBlock, createToolBlock } from '../types/stream.js';
27
- import { parseInput } from '../core/command-parser.js';
28
- import { executeSlashCommand, registerSlashCommand, getSlashCommandRegistry, CommandCategory, } from '../core/slash-command-handler.js';
29
- import { registerAllSlashCommands } from '../commands/slash/index.js';
30
- import { getSessionManager } from '../services/session-manager.js';
31
- import { getAnthropicClient } from '../services/anthropic-client.js';
32
- import { getApprovalManager } from '../services/approval-manager.js';
33
- import { getTokenTracker } from '../services/token-tracker.js';
34
- import { getCompassAuthService } from '../services/compass-auth-service.js';
35
- import { getEventEmitter } from '../core/event-emitter.js';
36
- import { getCodebaseIndexer } from '../services/codebase-indexer.js';
37
- import { getAuditLogger } from '../services/audit-logger.js';
38
- import { getSystemEventLogger } from '../services/system-event-logger.js';
39
- import { getContextBuilder } from '../core/context-builder.js';
40
- import { getToolRegistry } from '../services/tool-registry.js';
41
- import { registerBuiltInTools } from '../tools/index.js';
42
- import { getFileService } from '../services/file-service.js';
43
- import { getBackupManager } from '../services/backup-manager.js';
44
- import { getStatusLineExecutor } from '../services/statusline-executor.js';
45
- import { getConfigManager } from '../services/config-manager.js';
46
- import { getAgentStateService } from '../services/agent-state-service.js';
47
- import { checkPlanModeFallback } from '../services/plan-mode-fallback.js';
48
- import { pathExists } from '../utils/file-system.js';
49
- import { getProjectCompassDir } from '../utils/path.js';
50
- import { detectCloudSync } from '../utils/cloud-detection.js';
51
- import path from 'path';
52
- import { ApprovalMode, } from '../types/approval.js';
53
- import { logger } from '../utils/logger.js';
54
- import { getIdeStateService } from '../services/ide-state-service.js';
55
- import { getPromptPreprocessor } from '../services/prompt-preprocessor.js';
56
- import { DEFAULT_MAX_TOKENS, DEFAULT_MAX_AGENTIC_ITERATIONS, DEFAULT_MAX_ITERATIONS_PER_TASK, CONTEXT_WINDOW_SAFETY_MARGIN, SLIDING_WINDOW_MAX_MESSAGES, MAX_DYNAMIC_STREAM_BLOCKS } from '../constants/defaults.js';
57
- import { estimateTokens, estimateSystemPromptTokens } from '../utils/token-counter.js';
58
- import { buildSystemPrompt as buildSystemPromptFromModule } from '../prompts/index.js';
59
- import { getIterationScoper } from '../services/iteration-scoper.js';
60
- import { getPlanPersistence } from '../services/plan-persistence.js';
61
- import { captureConsoleOutput } from '../utils/console-capture.js';
62
- import { hash as sha256Hash } from '../utils/crypto.js';
63
- import { getConversationCompactor } from '../services/conversation-compactor.js';
64
- import { getRatingStateManager } from '../services/rating-state-manager.js';
65
- import { getRatingService } from '../services/rating-service.js';
66
- import { buildSkillDescriptionsForPrompt } from '../services/skill-service.js';
67
- import { loadAllMemory } from '../services/memory-service.js';
68
- import { getModelConfig } from '../constants/models.js';
69
- import { getSystemUtilitiesPromptSection } from '../services/system-utility-detector.js';
70
- import { hasSupportedClipboardContent, extractAttachmentsFromClipboard, getClipboardContent } from '../utils/clipboard-handler.js';
71
- import { buildMessageContent } from '../utils/attachment-handler.js';
72
- /**
73
- * Estimates token count for a messages array that may contain complex content blocks.
74
- * Handles both simple string content and arrays of tool_use/tool_result blocks.
75
- *
76
- * @param messages - Array of messages with role and content
77
- * @returns Estimated token count
78
- */
79
- function estimateMessagesArrayTokens(messages) {
80
- let totalTokens = 0;
81
- const MESSAGE_OVERHEAD = 4; // Overhead per message for role markers
82
- for (const message of messages) {
83
- totalTokens += MESSAGE_OVERHEAD;
84
- if (typeof message.content === 'string') {
85
- totalTokens += estimateTokens(message.content);
86
- }
87
- else if (Array.isArray(message.content)) {
88
- // Handle content blocks (tool_use, tool_result, text, etc.)
89
- for (const block of message.content) {
90
- if (typeof block === 'string') {
91
- totalTokens += estimateTokens(block);
92
- }
93
- else if (block && typeof block === 'object') {
94
- // Content block object
95
- const contentBlock = block;
96
- if (contentBlock.type === 'text' && typeof contentBlock.text === 'string') {
97
- totalTokens += estimateTokens(contentBlock.text);
98
- }
99
- else if (contentBlock.type === 'tool_use') {
100
- // Tool use block: name + input JSON
101
- totalTokens += estimateTokens(contentBlock.name || '');
102
- totalTokens += estimateTokens(JSON.stringify(contentBlock.input || {}));
103
- totalTokens += 10; // Overhead for tool_use structure
104
- }
105
- else if (contentBlock.type === 'tool_result') {
106
- // Tool result block: can have string content or nested content
107
- const resultContent = contentBlock.content;
108
- if (typeof resultContent === 'string') {
109
- totalTokens += estimateTokens(resultContent);
110
- }
111
- else if (resultContent && typeof resultContent === 'object') {
112
- totalTokens += estimateTokens(JSON.stringify(resultContent));
113
- }
114
- totalTokens += 10; // Overhead for tool_result structure
115
- }
116
- else {
117
- // Unknown block type - estimate from JSON
118
- totalTokens += estimateTokens(JSON.stringify(contentBlock));
119
- }
120
- }
121
- }
122
- }
123
- }
124
- return totalTokens;
125
- }
126
- /**
127
- * Formats a duration in milliseconds to a human-readable string.
128
- */
129
- function formatDuration(ms) {
130
- const totalSeconds = Math.round(ms / 1000);
131
- if (totalSeconds < 60)
132
- return `${totalSeconds}s`;
133
- const hours = Math.floor(totalSeconds / 3600);
134
- const minutes = Math.floor((totalSeconds % 3600) / 60);
135
- const seconds = totalSeconds % 60;
136
- if (hours > 0)
137
- return `${hours}h${minutes > 0 ? `${minutes}m` : ''}`;
138
- return seconds > 0 ? `${minutes}m${seconds}s` : `${minutes}m`;
139
- }
140
- /**
141
- * Extracts a short suggested reply when the assistant's response ends with a question.
142
- * Returns undefined when no actionable question is detected.
143
- */
144
- function deriveQuestionSuggestion(response) {
145
- if (!response)
146
- return undefined;
147
- // Get the last meaningful line (skip trailing whitespace / blank lines)
148
- const lines = response.trimEnd().split('\n');
149
- const lastLine = lines[lines.length - 1]?.trim();
150
- if (!lastLine || !lastLine.endsWith('?'))
151
- return undefined;
152
- const lower = lastLine.toLowerCase();
153
- // Yes/no style questions — suggest "yes"
154
- const yesNoPatterns = [
155
- /^(shall|should|do you want|would you like|want me to|can i|may i|ready to)\b/i,
156
- /\b(proceed|go ahead|continue|start|do that|make sense)\s*\?$/i,
157
- ];
158
- for (const pat of yesNoPatterns) {
159
- if (pat.test(lower))
160
- return 'yes';
161
- }
162
- // "Which" / "what option" questions with numbered or lettered choices above
163
- const hasChoices = lines.slice(-6).some(l => /^\s*(\d+[\.\):]|[a-c][\.\):]|-\s)/.test(l));
164
- if (hasChoices && /^(which|what)\b/i.test(lower))
165
- return '1';
166
- // Generic question — just offer "yes" as a safe default for action-oriented questions
167
- if (/\b(ok|okay|alright|right|correct)\s*\?$/i.test(lower))
168
- return 'yes';
169
- return undefined;
170
- }
171
- /**
172
- * InteractiveSession component manages the main interactive CLI experience.
173
- * It handles:
174
- * - User input via the Prompt component
175
- * - Streaming responses from Claude
176
- * - Slash command execution
177
- * - Approval dialogs for file modifications
178
- * - Token usage tracking and display
179
- * - Conversation history management
180
- *
181
- * @example
182
- * ```tsx
183
- * <InteractiveSession
184
- * onExit={() => process.exit(0)}
185
- * projectRoot={process.cwd()}
186
- * />
187
- * ```
188
- */
189
- /**
190
- * Renders a single conversation entry (user, assistant, or system message).
191
- * Extracted as a standalone component for use with both <Static> and dynamic rendering.
192
- */
193
- const ConversationEntryComponent = ({ entry, showToolBlocks, collapseUserPrompts, expandMCPResults, formatTime }) => {
194
- return (_jsxs(Box, { marginBottom: 1, flexDirection: "column", children: [entry.role !== 'system' && (_jsxs(Box, { marginBottom: 1, children: [entry.role === 'user' ? (_jsx(Text, { color: "cyan", bold: true, children: "\u276F You" })) : (_jsxs(_Fragment, { children: [_jsx(Text, { color: "#a855f7", bold: true, children: "\u2B21" }), _jsx(Text, { color: "#c4b5fd", bold: true, children: " Nova" })] })), _jsxs(Text, { dimColor: true, children: [" ", formatTime(entry.timestamp)] })] })), _jsx(Box, { paddingLeft: entry.role === 'system' ? 0 : 2, children: entry.role === 'assistant' ? (showToolBlocks && entry.blocks && entry.blocks.length > 0 ? (_jsx(InterleavedStream, { blocks: entry.blocks, isStreaming: false, showThinkingIndicator: false, expandMCPResults: expandMCPResults })) : (_jsx(Markdown, { children: entry.content }))) : entry.role === 'user' ? ((() => {
195
- const lines = entry.content.split('\n');
196
- const maxCollapsedLines = 3;
197
- const maxCollapsedChars = 300;
198
- const hasMultipleLines = lines.length > maxCollapsedLines;
199
- const isVeryLong = entry.content.length > maxCollapsedChars;
200
- const shouldCollapse = collapseUserPrompts && (hasMultipleLines || isVeryLong);
201
- let displayContent;
202
- let truncationHint = null;
203
- if (!shouldCollapse) {
204
- displayContent = entry.content;
205
- }
206
- else if (hasMultipleLines) {
207
- displayContent = lines.slice(0, maxCollapsedLines).join('\n');
208
- const hiddenLines = lines.length - maxCollapsedLines;
209
- truncationHint = `... +${hiddenLines} lines`;
210
- }
211
- else {
212
- displayContent = entry.content.slice(0, maxCollapsedChars);
213
- const hiddenChars = entry.content.length - maxCollapsedChars;
214
- truncationHint = `... +${hiddenChars} chars`;
215
- }
216
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { wrap: "wrap", dimColor: true, children: displayContent }), truncationHint && (_jsxs(Text, { dimColor: true, italic: true, children: [" ", truncationHint] }))] }));
217
- })()) : (_jsx(Text, { wrap: "wrap", children: entry.content })) }), entry.role === 'assistant' && entry.durationMs != null && entry.durationMs >= 60000 && (_jsx(Box, { paddingLeft: 2, marginTop: 0, children: _jsxs(Text, { dimColor: true, children: ["\uD83D\uDD50 ", formatDuration(entry.durationMs)] }) }))] }));
218
- };
219
- /**
220
- * Self-contained "Working..." indicator with pulsing dot.
221
- * Uses direct stdout writes for the dot animation to avoid React/Ink re-renders entirely.
222
- * The offset (lines from cursor to dot) is passed via a ref so it can be updated
223
- * without triggering re-renders.
224
- * Memoized so parent re-renders don't affect it either.
225
- */
226
- const WorkingIndicator = React.memo(({ linesBelowRef }) => {
227
- const dotRef = useRef(false);
228
- useEffect(() => {
229
- const timer = setInterval(() => {
230
- dotRef.current = !dotRef.current;
231
- const dot = dotRef.current ? '●' : '○';
232
- const n = linesBelowRef.current;
233
- const cyan = '\x1b[36m';
234
- const reset = '\x1b[0m';
235
- // Save cursor → move up N lines to dot position → write dot → restore cursor
236
- process.stdout.write(`\x1b7\x1b[${n}F\r ${cyan}${dot}${reset}\x1b8`);
237
- }, 700);
238
- return () => clearInterval(timer);
239
- }, [linesBelowRef]);
240
- return (_jsx(Box, { paddingX: 1 }));
241
- });
242
- export const InteractiveSession = ({ onExit, projectRoot = process.cwd(), showWelcome = true, smartMode = false, initialPrompt, continueSession = false, initialAttachments,
243
- // planStatus and orgName are rendered in the banner by App before session mounts
244
- planStatus: _planStatus, orgName: _orgName, }) => {
245
- // Conversation state
246
- const [conversation, setConversation] = useState([]);
247
- const [isProcessing, setIsProcessing] = useState(false);
248
- const [queuedMessages, setQueuedMessages] = useState([]);
249
- const queuedMessagesRef = useRef([]);
250
- // Lines between the working indicator dot and the terminal cursor (bottom of Ink output).
251
- // Layout: WorkingIndicator | queued msgs (N) | Prompt top hr | input | bottom hr | status line
252
- // = 4 fixed lines + N queued messages
253
- const workingDotOffsetRef = useRef(3);
254
- const [sessionPhase, setSessionPhase] = useState('idle');
255
- // NOTE: streamingContent state removed - was causing flicker by triggering renders on every chunk
256
- // The InterleavedStream component now handles all streaming display via streamBlocksRef
257
- const [error, setError] = useState(null);
258
- const [showHistory, setShowHistory] = useState(false);
259
- const [, setShouldShowWelcome] = useState(showWelcome);
260
- const [isInitialized, setIsInitialized] = useState(false);
261
- const initialPromptSubmittedRef = useRef(false);
262
- // Compass spinner state for visual feedback during operations
263
- const [compassSpinner, setCompassSpinner] = useState(null);
264
- // Interleaved stream blocks for Claude Code style display
265
- // Using ref + throttled render counter pattern for proper Ink re-rendering during rapid updates
266
- const streamBlocksRef = useRef([]);
267
- // Stable empty array for InterleavedStream when idle — same reference prevents React.memo re-renders
268
- const emptyBlocksRef = useRef([]);
269
- const [, forceRender] = useState(0);
270
- const renderScheduledRef = useRef(false);
271
- // Refs to track modal states for use in scheduleRender (avoids stale closures)
272
- const modalActiveRef = useRef(false);
273
- // Throttled render - batches multiple streaming updates into a single re-render
274
- // This prevents flickering by coalescing rapid chunk updates (~10fps for CLI)
275
- // Using 100ms provides smooth CLI updates without visible flicker
276
- const scheduleRender = useCallback(() => {
277
- // Skip scheduling when approval modals are active to prevent flicker
278
- if (modalActiveRef.current) {
279
- return;
280
- }
281
- if (!renderScheduledRef.current) {
282
- renderScheduledRef.current = true;
283
- setTimeout(() => {
284
- renderScheduledRef.current = false;
285
- // Double-check modal state before forcing render
286
- if (!modalActiveRef.current) {
287
- // Create a new array reference so React.memo on InterleavedStream
288
- // detects the change. In-place mutations (appendTextChunk, updateToolBlockById)
289
- // are batched between renders — this single shallow copy per frame (~10fps)
290
- // is cheaper than full JSON.parse and markdown parsing on every chunk.
291
- streamBlocksRef.current = [...streamBlocksRef.current];
292
- // Flush batched plan ref updates into React state in the same render frame.
293
- // This avoids separate re-renders for plan events vs streaming events.
294
- setActivePlan(activePlanRef.current);
295
- forceRender(n => n + 1);
296
- }
297
- }, 100); // ~10fps - optimal for CLI, eliminates flicker while staying responsive
298
- }
299
- }, []);
300
- // Lightweight plan-only render: updates plan state without copying stream blocks.
301
- // Used by plan event handlers to avoid triggering InterleavedStream re-renders
302
- // (React.memo on InterleavedStream checks array reference equality).
303
- const planRenderScheduledRef = useRef(false);
304
- const schedulePlanRender = useCallback(() => {
305
- if (modalActiveRef.current)
306
- return;
307
- if (!planRenderScheduledRef.current) {
308
- planRenderScheduledRef.current = true;
309
- setTimeout(() => {
310
- planRenderScheduledRef.current = false;
311
- if (!modalActiveRef.current) {
312
- setActivePlan(activePlanRef.current);
313
- }
314
- }, 100);
315
- }
316
- }, []);
317
- // Helper to update stream blocks and trigger throttled re-render
318
- const updateStreamBlocks = useCallback((updater) => {
319
- streamBlocksRef.current = updater(streamBlocksRef.current);
320
- scheduleRender();
321
- }, [scheduleRender]);
322
- // Optimized helper: append text to the last text block or create a new one.
323
- // Mutates the ref array in-place (replacing only the changed element) to avoid
324
- // creating a full new array on every streamed token (~100+ times per response).
325
- const appendTextChunk = useCallback((chunk) => {
326
- const blocks = streamBlocksRef.current;
327
- const lastBlock = blocks[blocks.length - 1];
328
- if (lastBlock && lastBlock.type === 'text') {
329
- // Replace last element in-place — no spread/slice needed
330
- blocks[blocks.length - 1] = { ...lastBlock, content: lastBlock.content + chunk };
331
- }
332
- else {
333
- blocks.push(createTextBlock(chunk));
334
- }
335
- scheduleRender();
336
- }, [scheduleRender]);
337
- // Optimized helper: update a single tool block by ID without mapping the entire array.
338
- // Finds the block by ID and replaces only that element in-place.
339
- const updateToolBlockById = useCallback((toolId, updates, statusFilter) => {
340
- const blocks = streamBlocksRef.current;
341
- for (let i = blocks.length - 1; i >= 0; i--) {
342
- const block = blocks[i];
343
- if (block.type === 'tool' && block.id === toolId && (!statusFilter || block.status === statusFilter)) {
344
- blocks[i] = { ...block, ...updates };
345
- break;
346
- }
347
- }
348
- scheduleRender();
349
- }, [scheduleRender]);
350
- // Helper to reset stream blocks (immediate render for clean state)
351
- const resetStreamBlocks = useCallback(() => {
352
- streamBlocksRef.current = [];
353
- forceRender(n => n + 1);
354
- }, []);
355
- // Track partial JSON input per tool during generation (for incremental param display)
356
- const toolPartialJsonRef = useRef(new Map());
357
- // Track last rendered param count per tool to avoid unnecessary updates
358
- const toolLastParamCountRef = useRef(new Map());
359
- // Track last parsed length per tool to debounce tryParsePartialJson calls
360
- const toolLastParsedLenRef = useRef(new Map());
361
- // Abort flag for breaking the agentic loop (Esc to cancel during processing)
362
- const abortLoopRef = useRef(false);
363
- // AbortController for cancelling API requests immediately when user presses Esc
364
- const abortControllerRef = useRef(null);
365
- // Per-tool AbortController for skipping individual execute_command calls (Ctrl+X)
366
- const toolAbortRef = useRef(null);
367
- const currentToolNameRef = useRef(null);
368
- // Checkpoint for conversation rollback on cancel
369
- // Stores the conversation state before current request, allowing clean rollback if user cancels
370
- const conversationCheckpointRef = useRef([]);
371
- /**
372
- * Attempts to parse partial JSON and extract any complete key-value pairs.
373
- * This allows showing params like file_path as they stream in during tool generation.
374
- * Also detects "likely complete" file paths by extension even without closing quote.
375
- */
376
- const tryParsePartialJson = useCallback((partialJson) => {
377
- const result = {};
378
- // Try full parse first (handles complete JSON)
379
- try {
380
- const parsed = JSON.parse(partialJson);
381
- if (typeof parsed === 'object' && parsed !== null) {
382
- return parsed;
383
- }
384
- }
385
- catch {
386
- // Not valid JSON yet, try to extract complete key-value pairs
387
- }
388
- // Extract complete string values: "key": "value"
389
- // Match key-value pairs where the value is a complete quoted string
390
- const stringPattern = /"([^"]+)"\s*:\s*"((?:[^"\\]|\\.)*)"/g;
391
- let match;
392
- while ((match = stringPattern.exec(partialJson)) !== null) {
393
- // Unescape the value
394
- const value = match[2].replace(/\\(.)/g, '$1');
395
- result[match[1]] = value;
396
- }
397
- // Extract complete number values: "key": 123
398
- const numberPattern = /"([^"]+)"\s*:\s*(-?\d+(?:\.\d+)?)/g;
399
- while ((match = numberPattern.exec(partialJson)) !== null) {
400
- result[match[1]] = parseFloat(match[2]);
401
- }
402
- // Extract complete boolean values: "key": true/false
403
- const boolPattern = /"([^"]+)"\s*:\s*(true|false)/g;
404
- while ((match = boolPattern.exec(partialJson)) !== null) {
405
- result[match[1]] = match[2] === 'true';
406
- }
407
- // Special handling: detect file paths that appear complete by extension
408
- // This catches cases like "path": "file.py (without closing quote yet)
409
- // Common file extensions that indicate a likely complete path
410
- const fileExtensions = /\.(py|js|ts|tsx|jsx|json|md|txt|yaml|yml|toml|rs|go|java|c|cpp|h|hpp|css|scss|html|xml|sql|sh|bash|rb|php|swift|kt|scala|vue|svelte)$/i;
411
- // Look for path-like keys with incomplete string values ending in file extension
412
- const incompletePathPattern = /"(file_path|path|file|filename)"\s*:\s*"([^"]+)/gi;
413
- while ((match = incompletePathPattern.exec(partialJson)) !== null) {
414
- const key = match[1];
415
- const value = match[2];
416
- // Only use if not already found (complete values take precedence) and looks like a file path
417
- if (!(key in result) && fileExtensions.test(value)) {
418
- result[key] = value;
419
- }
420
- }
421
- return result;
422
- }, []);
423
- // NOTE: Tool execution tracking state removed - was causing flicker by triggering renders
424
- // during tool execution. Audit logging is done directly through the audit logger.
425
- // Overlay state
426
- const [activeOverlay, setActiveOverlay] = useState('none');
427
- const [diffPreview] = useState(null);
428
- // Approval state
429
- const [approvalMode, setApprovalMode] = useState(ApprovalMode.MANUAL);
430
- const [pendingApproval, setPendingApproval] = useState(null);
431
- // Execution mode state
432
- const [executionMode, setExecutionMode] = useState('hybrid');
433
- // Tool blocks display in history (default: true, toggle with Ctrl+T)
434
- const [showToolBlocks, setShowToolBlocks] = useState(true);
435
- // Collapse long user prompts in history (default: true, toggle with Ctrl+O)
436
- const [collapseUserPrompts, setCollapseUserPrompts] = useState(true);
437
- // Expand MCP tool results (default: false, toggle with Ctrl+O)
438
- const [expandMCPResults, setExpandMCPResults] = useState(false);
439
- // Throttle for expand/collapse toggle to prevent yoga-layout WASM crash on rapid toggling
440
- const lastExpandToggleRef = useRef(0);
441
- // Note: Slash hint moved to Prompt component to avoid parent re-renders on keystroke
442
- // Logout confirmation state
443
- const [logoutDetails, setLogoutDetails] = useState([]);
444
- // Rating state
445
- const [showRatingPanel, setShowRatingPanel] = useState(false);
446
- const [hasCompassApiKey, setHasCompassApiKey] = useState(false);
447
- const ratingStateManagerRef = useRef(null);
448
- const ratingUsageIntervalRef = useRef(null);
449
- const ratingPanelShownRef = useRef(false); // Track if panel is shown to avoid stale closure
450
- // Plan approval state
451
- const [pendingPlanApproval, setPendingPlanApproval] = useState(null);
452
- const planApprovalClearContextRef = useRef(false);
453
- // Clarification wizard state
454
- const [pendingClarification, setPendingClarification] = useState(null);
455
- // Keep modalActiveRef in sync with approval states and session phase (for use in scheduleRender)
456
- useEffect(() => {
457
- const inModal = !!(pendingApproval || pendingPlanApproval || pendingClarification);
458
- const inTransition = sessionPhase === 'classifying';
459
- modalActiveRef.current = inModal || inTransition;
460
- }, [pendingApproval, pendingPlanApproval, pendingClarification, sessionPhase]);
461
- // Suggestion offer state — shown as ghost text when the LLM ends with a question
462
- const [suggestionOffer, setSuggestionOffer] = useState(undefined);
463
- // Plan execution state — uses ref + throttled render to avoid per-event re-renders.
464
- // Plan events (task-progress, task-completed, execution-completed) fire rapidly during
465
- // the agentic loop. Routing them through a ref batches multiple updates into a single
466
- // render via scheduleRender(), matching the streaming token pattern.
467
- const activePlanRef = useRef(null);
468
- // Snapshot for rendering — updated by scheduleRender alongside streamBlocks
469
- const [activePlan, setActivePlan] = useState(null);
470
- const [planTrackerMode, setPlanTrackerMode] = useState('compact');
471
- // Batched metrics state - single state object to prevent multiple re-renders per API call
472
- const [metrics, setMetrics] = useState({
473
- tokenUsage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
474
- currentContextTokens: 0,
475
- cost: { inputCost: 0, outputCost: 0, totalCost: 0, currency: 'USD' },
476
- });
477
- // Destructure for convenient access (stable references via same render)
478
- const { tokenUsage, currentContextTokens, cost } = metrics;
479
- // Model state
480
- const [currentModel, setCurrentModel] = useState('Claude Sonnet 4.5');
481
- const [contextWindowSize, setContextWindowSize] = useState(200000);
482
- // Project context state
483
- const [projectSummary, setProjectSummary] = useState(null);
484
- const [isContextLoading, setIsContextLoading] = useState(true);
485
- const [isContextStale, setIsContextStale] = useState(false);
486
- const [, setIsCloudPath] = useState(false);
487
- // Status line state
488
- const [statusLineText, setStatusLineText] = useState('');
489
- const [statusLineVisible, setStatusLineVisible] = useState(false);
490
- // Active agent state (for agent switching via /agents use)
491
- const [activeAgentName, setActiveAgentName] = useState(null);
492
- // Slash command output state (to display output from commands like /context, /commit)
493
- const [slashCommandOutput, setSlashCommandOutput] = useState(null);
494
- // Streaming command output state (for real-time event-based output)
495
- const [streamingCommandOutput, setStreamingCommandOutput] = useState([]);
496
- const streamingOutputRef = useRef([]);
497
- // Refs for cleanup
498
- const unsubscribersRef = useRef([]);
499
- /**
500
- * Initializes the session, registers commands, and sets up event listeners
501
- */
502
- useEffect(() => {
503
- async function initializeSession() {
504
- try {
505
- // Initialize audit logger
506
- const auditLogger = getAuditLogger(projectRoot);
507
- // Initialize session manager
508
- const sessionManager = getSessionManager();
509
- const existingSession = await sessionManager.load(projectRoot);
510
- if (!existingSession) {
511
- sessionManager.createSession(projectRoot);
512
- }
513
- else {
514
- // Restore state from existing session
515
- const state = sessionManager.getState();
516
- if (state) {
517
- setApprovalMode(state.approvalMode);
518
- setExecutionMode(state.executionMode || 'hybrid');
519
- setShowToolBlocks(state.showToolBlocks ?? true);
520
- // Restore model from session if one was persisted
521
- if (state.currentModel) {
522
- try {
523
- const anthropicClient = await getAnthropicClient();
524
- anthropicClient.setModel(state.currentModel);
525
- // Update React state so the UI reflects the restored model
526
- const restoredConfig = getModelConfig(state.currentModel);
527
- if (restoredConfig) {
528
- setCurrentModel(restoredConfig.name);
529
- setContextWindowSize(restoredConfig.contextWindow);
530
- }
531
- logger.debug(`Restored model from session: ${state.currentModel}`);
532
- }
533
- catch (modelError) {
534
- logger.warn('Failed to restore model from session', modelError);
535
- }
536
- }
537
- // Archive and handle conversation history based on -c flag
538
- const hasHistory = state.conversationHistory && state.conversationHistory.length > 0;
539
- const hasOperations = state.operations && state.operations.length > 0;
540
- if (hasHistory) {
541
- if (continueSession) {
542
- // -c flag: Archive, load into UI context, then clear session.json
543
- const { archive, result } = await sessionManager.archiveAndClearSession();
544
- if (result.success && result.filename) {
545
- logger.info(`Archived previous session to: ${result.filename}`);
546
- }
547
- // Load the archived messages into the UI for display
548
- if (archive && archive.messages.length > 0) {
549
- const restoredConversation = archive.messages.map((msg, idx) => ({
550
- id: `archived-${idx}-${Date.now()}`,
551
- role: msg.role,
552
- content: msg.content,
553
- timestamp: new Date(msg.timestamp || Date.now()),
554
- tokenCount: msg.tokenCount,
555
- }));
556
- setConversation(restoredConversation);
557
- setShowHistory(true);
558
- logger.info(`Loaded ${restoredConversation.length} messages from archived session`);
559
- }
560
- }
561
- else {
562
- // No -c flag: Archive and clear, don't load into UI
563
- const { result } = await sessionManager.archiveAndClearSession();
564
- if (result.success && result.filename) {
565
- logger.info(`Archived previous session to: ${result.filename}`);
566
- }
567
- }
568
- }
569
- else {
570
- // No conversation history — clear stale operations if any
571
- if (hasOperations) {
572
- sessionManager.clearHistory();
573
- }
574
- // -c flag: load latest archive even when session is clean
575
- if (continueSession) {
576
- const archivalService = sessionManager.getArchivalService();
577
- const archive = await archivalService.getLatestArchive();
578
- if (archive && archive.messages.length > 0) {
579
- const restoredConversation = archive.messages.map((msg, idx) => ({
580
- id: `archived-${idx}-${Date.now()}`,
581
- role: msg.role,
582
- content: msg.content,
583
- timestamp: new Date(msg.timestamp || Date.now()),
584
- tokenCount: msg.tokenCount,
585
- }));
586
- setConversation(restoredConversation);
587
- setShowHistory(true);
588
- logger.info(`Loaded ${restoredConversation.length} messages from latest archive`);
589
- }
590
- }
591
- }
592
- }
593
- // If projectPath was updated during load or history was cleared, save immediately
594
- if (sessionManager.isDirty()) {
595
- await sessionManager.save();
596
- }
597
- }
598
- // Log session start for audit trail
599
- const session = sessionManager.getSession();
600
- if (session) {
601
- // Initialize system event logger with session ID
602
- const systemEventLogger = getSystemEventLogger(projectRoot);
603
- systemEventLogger.setSessionId(session.id);
604
- await systemEventLogger.logSessionStart(session.id, projectRoot);
605
- // Initialize audit logger with session ID for LLM traces
606
- auditLogger.setSessionId(session.id);
607
- await auditLogger.logSessionStart(session.id, projectRoot);
608
- }
609
- // Initialize token tracker
610
- const tokenTracker = await getTokenTracker();
611
- tokenTracker.startSession();
612
- // Get current model from client
613
- const client = await getAnthropicClient();
614
- if (client.isInitialized()) {
615
- const model = client.getCurrentModel();
616
- setCurrentModel(model.name);
617
- setContextWindowSize(model.contextWindow);
618
- }
619
- // Initialize status line from config
620
- try {
621
- const configManager = getConfigManager();
622
- await configManager.load(projectRoot);
623
- const statusLineConfig = configManager.getValue('ui.statusLine');
624
- logger.debug('Status line config lookup', {
625
- projectRoot,
626
- statusLineConfig: statusLineConfig || 'not found',
627
- uiConfig: configManager.getValue('ui')
628
- });
629
- const statusLineExecutor = getStatusLineExecutor();
630
- if (statusLineConfig && statusLineConfig.enabled !== false) {
631
- // User has configured a status line
632
- if (statusLineConfig.type === 'default') {
633
- // Default type: built-in context display
634
- statusLineExecutor.configure(statusLineConfig);
635
- setStatusLineVisible(true);
636
- logger.info('Status line initialized (default type)');
637
- }
638
- else if (statusLineConfig.command) {
639
- // Command type: external script
640
- statusLineExecutor.configure(statusLineConfig);
641
- setStatusLineVisible(true);
642
- logger.info('Status line initialized (command type)', { command: statusLineConfig.command });
643
- }
644
- else {
645
- logger.debug('Status line config missing command for command type');
646
- }
647
- }
648
- else if (!statusLineConfig) {
649
- // No config: use default status line automatically
650
- const defaultConfig = {
651
- type: 'default',
652
- enabled: true,
653
- };
654
- statusLineExecutor.configure(defaultConfig);
655
- setStatusLineVisible(true);
656
- logger.debug('Status line initialized (auto default)');
657
- }
658
- else {
659
- logger.debug('Status line explicitly disabled');
660
- }
661
- }
662
- catch (err) {
663
- logger.error('Failed to initialize status line', err);
664
- }
665
- // Register slash commands (includes custom commands from .compass/commands/)
666
- await registerCommands();
667
- // Initialize SMART mode with session auto-approval if enabled via CLI
668
- if (smartMode) {
669
- const approvalManager = getApprovalManager();
670
- approvalManager.setMode(ApprovalMode.SMART);
671
- approvalManager.enableSessionAutoApproval();
672
- setApprovalMode(ApprovalMode.SMART);
673
- logger.info('SMART mode enabled via CLI - session auto-approval active');
674
- }
675
- // Initialize tool registry with built-in tools
676
- const toolRegistry = getToolRegistry();
677
- toolRegistry.setProjectRoot(projectRoot);
678
- // Initialize file service with project root
679
- const fileService = getFileService();
680
- fileService.setProjectRoot(projectRoot);
681
- logger.debug(`File service project root set to: ${projectRoot}`);
682
- // Initialize backup manager with project root
683
- const backupManager = await getBackupManager();
684
- backupManager.setProjectRoot(projectRoot);
685
- logger.debug(`Backup manager project root set to: ${projectRoot}`);
686
- registerBuiltInTools();
687
- logger.debug(`Registered ${toolRegistry.getAllTools().length} tools`);
688
- // Subscribe to events
689
- subscribeToEvents();
690
- // Emit history-cleared if we cleared on startup (ensures UI state is reset)
691
- if (!continueSession) {
692
- getEventEmitter().emit('history-cleared', { isStartup: true });
693
- }
694
- // Start auto-save
695
- sessionManager.startAutoSave(60000); // Auto-save every minute
696
- // Check if file index exists and trigger performant re-index in background
697
- await checkAndReindexIfNeeded();
698
- // Load project context summary for system prompt
699
- // On cloud-synced folders, defer to background to avoid blocking the UI
700
- const cloudInfo = detectCloudSync(projectRoot);
701
- if (cloudInfo.isCloud) {
702
- logger.info(`Cloud-synced folder detected (${cloudInfo.provider}), loading context in background`);
703
- setIsCloudPath(true);
704
- setIsContextLoading(false);
705
- loadProjectContext().catch(err => logger.debug('Background context load failed', err));
706
- }
707
- else {
708
- await loadProjectContext();
709
- }
710
- // Subscribe to file-modified events to mark context as stale
711
- const eventEmitter = getEventEmitter();
712
- const unsubscribeFileModified = eventEmitter.on('file-modified', ({ operation }) => {
713
- // Only mark as stale for create/delete operations (not modifications to existing files)
714
- if (operation === 'create' || operation === 'delete') {
715
- logger.debug(`File ${operation} detected - marking project context as stale`);
716
- setIsContextStale(true);
717
- }
718
- });
719
- unsubscribersRef.current.push(unsubscribeFileModified);
720
- // Subscribe to manual context refresh events (from /context refresh command)
721
- const unsubscribeContextRefreshed = eventEmitter.on('project-context-refreshed', ({ summary }) => {
722
- logger.debug('Project context manually refreshed via /context refresh');
723
- setProjectSummary(summary);
724
- setIsContextStale(false);
725
- });
726
- unsubscribersRef.current.push(unsubscribeContextRefreshed);
727
- // Initialize rating system if Compass API key is available
728
- try {
729
- const { getCredentialStore } = await import('../services/credential-store.js');
730
- const credentialStore = await getCredentialStore();
731
- const compassApiKey = await credentialStore.getCompassApiKey();
732
- if (compassApiKey) {
733
- setHasCompassApiKey(true);
734
- // Initialize rating state manager
735
- const ratingStateManager = await getRatingStateManager();
736
- ratingStateManagerRef.current = ratingStateManager;
737
- // Configure rating service with API key
738
- const ratingService = getRatingService();
739
- ratingService.setApiKey(compassApiKey);
740
- // Start periodic usage tracking (every 10 seconds)
741
- ratingUsageIntervalRef.current = setInterval(() => {
742
- if (ratingStateManagerRef.current && !ratingPanelShownRef.current) {
743
- ratingStateManagerRef.current.updateUsageTime();
744
- // Check if we should show rating
745
- if (ratingStateManagerRef.current.shouldShowRating()) {
746
- ratingPanelShownRef.current = true;
747
- setShowRatingPanel(true);
748
- ratingStateManagerRef.current.recordRatingShown();
749
- }
750
- }
751
- }, 10000);
752
- logger.debug('[Rating] Rating system initialized');
753
- }
754
- }
755
- catch (ratingError) {
756
- logger.debug('[Rating] Failed to initialize rating system', ratingError);
757
- }
758
- // Mark initialization as complete
759
- setIsInitialized(true);
760
- logger.debug('Session initialization complete');
761
- }
762
- catch (err) {
763
- logger.error('Failed to initialize session', err);
764
- setError('Failed to initialize session. Please try again.');
765
- }
766
- }
767
- initializeSession();
768
- // Cleanup on unmount
769
- return () => {
770
- // Clear rating usage interval
771
- if (ratingUsageIntervalRef.current) {
772
- clearInterval(ratingUsageIntervalRef.current);
773
- ratingUsageIntervalRef.current = null;
774
- }
775
- // Unsubscribe from all events
776
- for (const unsubscribe of unsubscribersRef.current) {
777
- unsubscribe();
778
- }
779
- // Disconnect MCP servers
780
- import('../services/mcp-server-manager.js')
781
- .then(({ getMCPServerManager }) => {
782
- const mcpServerManager = getMCPServerManager();
783
- return mcpServerManager.disconnectAllServers();
784
- })
785
- .then(() => {
786
- logger.debug('MCP servers disconnected on unmount');
787
- })
788
- .catch((err) => {
789
- logger.debug('Failed to disconnect MCP servers on unmount', err);
790
- });
791
- // Log session end for audit trail
792
- const sessionManager = getSessionManager();
793
- const session = sessionManager.getSession();
794
- if (session) {
795
- // Log to system event logger
796
- const systemEventLogger = getSystemEventLogger(projectRoot);
797
- systemEventLogger.logSessionEnd().catch((err) => {
798
- logger.debug('Failed to log system session end', err);
799
- });
800
- // Log to audit logger (LLM traces)
801
- const auditLogger = getAuditLogger(projectRoot);
802
- auditLogger.logSessionEnd(session.id).catch((err) => {
803
- logger.debug('Failed to log audit session end', err);
804
- });
805
- }
806
- // Stop auto-save and save final state
807
- sessionManager.stopAutoSave();
808
- sessionManager.save().catch((err) => {
809
- logger.error('Failed to save session on exit', err);
810
- });
811
- };
812
- }, [projectRoot, smartMode, continueSession]);
813
- /**
814
- * Auto-submits initial prompt when provided and session is initialized
815
- */
816
- useEffect(() => {
817
- if (isInitialized && initialPrompt && !initialPromptSubmittedRef.current && !isProcessing) {
818
- initialPromptSubmittedRef.current = true;
819
- logger.debug('Auto-submitting initial prompt', { promptLength: initialPrompt.length });
820
- // Use setTimeout to ensure React has finished rendering
821
- setTimeout(() => {
822
- handleSubmit(initialPrompt);
823
- }, 100);
824
- }
825
- }, [isInitialized, initialPrompt, isProcessing]);
826
- /**
827
- * Adds initial attachments to session manager when provided
828
- */
829
- useEffect(() => {
830
- if (isInitialized && initialAttachments && initialAttachments.length > 0) {
831
- const sessionManager = getSessionManager();
832
- sessionManager.addPendingAttachments(initialAttachments);
833
- logger.info(`Added ${initialAttachments.length} initial attachments to session`);
834
- }
835
- }, [isInitialized, initialAttachments]);
836
- /**
837
- * Updates the status line when conversation, tokens, or model changes.
838
- * Debounced to prevent excessive updates during rapid API calls.
839
- */
840
- const statusLineTimerRef = useRef(null);
841
- useEffect(() => {
842
- if (!statusLineVisible) {
843
- return;
844
- }
845
- // Debounce: wait 2s after last dependency change before updating
846
- if (statusLineTimerRef.current) {
847
- clearTimeout(statusLineTimerRef.current);
848
- }
849
- statusLineTimerRef.current = setTimeout(async () => {
850
- const executor = getStatusLineExecutor();
851
- if (!executor.isEnabled()) {
852
- return;
853
- }
854
- try {
855
- // Get current session info
856
- const sessionManager = getSessionManager();
857
- const session = sessionManager.getSession();
858
- // Get current model info
859
- const client = await getAnthropicClient();
860
- const model = client.isInitialized() ? client.getCurrentModel() : null;
861
- // Gather rolling window token limit info for the status line
862
- let tokenLimitUsed;
863
- let tokenLimitMax;
864
- let planType;
865
- try {
866
- const modelId = model?.id || 'unknown';
867
- const compassAuth = getCompassAuthService();
868
- const planStatus = compassAuth.getPlanStatus();
869
- planType = compassAuth.getPlanType();
870
- const tracker = await getTokenTracker();
871
- const { modelLimit } = tracker.resolveLimit(modelId, planStatus, planType);
872
- if (modelLimit !== Infinity) {
873
- tokenLimitUsed = await tracker.getUsageForModel(modelId);
874
- tokenLimitMax = modelLimit;
875
- }
876
- }
877
- catch {
878
- // Non-critical — skip token limit display on error
879
- }
880
- // Build status line context
881
- const data = executor.buildStatusData({
882
- sessionId: session?.id,
883
- projectRoot,
884
- cwd: process.cwd(),
885
- modelId: model?.id || 'unknown',
886
- modelDisplayName: model?.name || currentModel,
887
- totalCostUsd: cost.totalCost,
888
- totalDurationMs: 0, // TODO: Track session duration if needed
889
- totalApiDurationMs: 0,
890
- totalLinesAdded: 0,
891
- totalLinesRemoved: 0,
892
- totalInputTokens: currentContextTokens, // Use current context, not cumulative
893
- totalOutputTokens: tokenUsage.outputTokens,
894
- contextWindowSize: model?.contextWindow || 200000,
895
- transcriptPath: session?.id ? `${projectRoot}/.compass/sessions/${session.id}.json` : undefined,
896
- tokenLimitUsed,
897
- tokenLimitMax,
898
- planType: planType ?? undefined,
899
- });
900
- const result = await executor.execute(data);
901
- if (result.success || result.text) {
902
- setStatusLineText(result.text);
903
- }
904
- }
905
- catch (err) {
906
- logger.debug('Failed to update status line', err);
907
- }
908
- }, 2000);
909
- return () => {
910
- if (statusLineTimerRef.current) {
911
- clearTimeout(statusLineTimerRef.current);
912
- }
913
- };
914
- }, [conversation, metrics, currentModel, statusLineVisible, projectRoot]);
915
- /**
916
- * Checks if file index exists and triggers performant re-index in background
917
- */
918
- async function checkAndReindexIfNeeded() {
919
- try {
920
- const indexPath = path.join(getProjectCompassDir(projectRoot), 'index', 'file-index.json');
921
- // Check if index exists
922
- if (await pathExists(indexPath)) {
923
- logger.debug('File index found, starting background re-index for changed files...');
924
- // Trigger re-index in background (non-blocking)
925
- // This only re-indexes files that have changed since last index
926
- const indexer = await getCodebaseIndexer(projectRoot);
927
- indexer.indexProject(projectRoot, {
928
- force: false, // Only re-index changed files for performance
929
- onProgress: (current, total) => {
930
- // Log progress periodically (every 10%)
931
- if (current % Math.max(1, Math.floor(total / 10)) === 0) {
932
- logger.debug(`Re-indexing: ${current}/${total} files checked`);
933
- }
934
- },
935
- }).then((result) => {
936
- if (result.newFiles > 0 || result.updatedFiles > 0 || result.removedFiles > 0) {
937
- logger.debug(`Index updated: ${result.newFiles} new, ${result.updatedFiles} updated, ${result.removedFiles} removed`);
938
- }
939
- else {
940
- logger.debug('Index is up to date');
941
- }
942
- }).catch((err) => {
943
- logger.debug('Background re-index failed', err);
944
- });
945
- }
946
- }
947
- catch (err) {
948
- logger.debug('Failed to check for file index', err);
949
- }
950
- }
951
- /**
952
- * Loads project context summary for system prompt injection
953
- */
954
- async function loadProjectContext() {
955
- try {
956
- logger.debug('Loading project context...');
957
- const contextBuilder = getContextBuilder({ projectRoot });
958
- const summary = await contextBuilder.getProjectSummary();
959
- setProjectSummary(summary);
960
- logger.debug(`Project context loaded: ${summary.name} (${summary.projectType})`);
961
- logger.info(`Project: ${summary.name} (${summary.projectType}) - ${summary.fileCount} files`);
962
- }
963
- catch (err) {
964
- logger.debug('Failed to load project context', err);
965
- // Non-fatal - continue without project context
966
- }
967
- finally {
968
- setIsContextLoading(false);
969
- }
970
- }
971
- /**
972
- * Refreshes project context if marked as stale (e.g., after file creation/deletion).
973
- * This ensures the system prompt has up-to-date project structure information
974
- * for Anthropic's prompt caching to work effectively with fresh context.
975
- *
976
- * Note: Anthropic's prompt cache uses ephemeral TTL (5 minutes by default).
977
- * When the system prompt changes, a new cache entry is created and the old
978
- * one expires after the TTL - Anthropic doesn't keep historical versions.
979
- */
980
- async function refreshProjectContext() {
981
- if (!isContextStale) {
982
- logger.debug('Project context is not stale, skipping refresh');
983
- return;
984
- }
985
- try {
986
- logger.info('Refreshing project context (files changed)...');
987
- const contextBuilder = getContextBuilder({ projectRoot });
988
- const summary = await contextBuilder.getProjectSummary();
989
- setProjectSummary(summary);
990
- setIsContextStale(false);
991
- logger.info(`Project context refreshed: ${summary.name} - ${summary.fileCount} files`);
992
- }
993
- catch (err) {
994
- logger.debug('Failed to refresh project context', err);
995
- // Non-fatal - continue with existing context
996
- }
997
- }
998
- /**
999
- * Builds the system prompt with project context, model info, and tool guidance.
1000
- * Uses the centralized buildSystemPrompt from the prompts module.
1001
- * If an agent is active, uses the agent's system prompt instead.
1002
- */
1003
- async function buildSystemPrompt(summary, modelName, modelId) {
1004
- const agentState = getAgentStateService();
1005
- const activeAgent = agentState.getActiveAgent();
1006
- // Get skill descriptions for model invocation awareness
1007
- const skillDescriptions = await buildSkillDescriptionsForPrompt();
1008
- // Get effective personality mode and unguarded mode from session
1009
- const sessionMgr = getSessionManager();
1010
- const personalityMode = sessionMgr.getEffectivePersonalityMode();
1011
- const unguardedMode = sessionMgr.getUnguardedMode();
1012
- // Get system utilities section for unguarded mode
1013
- let systemUtilitiesSection;
1014
- if (unguardedMode) {
1015
- try {
1016
- systemUtilitiesSection = await getSystemUtilitiesPromptSection();
1017
- if (systemUtilitiesSection) {
1018
- logger.debug('System utilities section generated for unguarded mode');
1019
- }
1020
- }
1021
- catch (error) {
1022
- logger.debug('Failed to generate system utilities section', error);
1023
- }
1024
- }
1025
- // Load memory content (NOVA.md + auto memory)
1026
- let memoryContent = '';
1027
- try {
1028
- memoryContent = await loadAllMemory(projectRoot);
1029
- }
1030
- catch (error) {
1031
- logger.debug('Failed to load memory content', error);
1032
- }
1033
- // If an agent is active, use its system prompt
1034
- if (activeAgent) {
1035
- logger.debug(`Using active agent system prompt: ${activeAgent.name}`, {
1036
- standalone: activeAgent.standalone,
1037
- model: activeAgent.model,
1038
- });
1039
- // Standalone agents use ONLY their prompt, non-standalone agents
1040
- // get the base prompt with agent prompt appended
1041
- if (activeAgent.standalone) {
1042
- // Pure standalone: just the agent's prompt + project context + skills
1043
- let agentPrompt = activeAgent.systemPrompt;
1044
- if (skillDescriptions) {
1045
- agentPrompt = `${agentPrompt}\n${skillDescriptions}`;
1046
- }
1047
- if (summary) {
1048
- agentPrompt = `${agentPrompt}\n\n${summary.formatted}`;
1049
- }
1050
- if (memoryContent) {
1051
- agentPrompt = `${agentPrompt}\n\n${memoryContent}`;
1052
- }
1053
- return agentPrompt;
1054
- }
1055
- else {
1056
- // Non-standalone: base prompt + agent's additional instructions
1057
- const basePrompt = buildSystemPromptFromModule({
1058
- projectSummary: summary ? { formatted: summary.formatted } : null,
1059
- workingDirectory: projectRoot,
1060
- modelName,
1061
- modelId,
1062
- skillDescriptions,
1063
- personalityMode,
1064
- unguardedMode,
1065
- systemUtilitiesSection,
1066
- });
1067
- // Append agent's specialized instructions
1068
- let combinedPrompt = `# Active Agent: ${activeAgent.name}
1069
-
1070
- You are now operating as the "${activeAgent.name}" agent. Follow these specialized instructions:
1071
-
1072
- ${activeAgent.systemPrompt}
1073
-
1074
- # Fallback Instructions
1075
- If the above agent-specific instructions are insufficient to fully satisfy the user request, follow the general instructions below.
1076
-
1077
- ${basePrompt}
1078
- `;
1079
- if (memoryContent) {
1080
- combinedPrompt = `${combinedPrompt}\n\n${memoryContent}`;
1081
- }
1082
- return combinedPrompt;
1083
- }
1084
- }
1085
- // Default: use the standard Compass system prompt
1086
- let defaultPrompt = buildSystemPromptFromModule({
1087
- projectSummary: summary ? { formatted: summary.formatted } : null,
1088
- workingDirectory: projectRoot,
1089
- modelName,
1090
- modelId,
1091
- skillDescriptions,
1092
- personalityMode,
1093
- unguardedMode,
1094
- systemUtilitiesSection,
1095
- });
1096
- if (memoryContent) {
1097
- defaultPrompt = `${defaultPrompt}\n\n${memoryContent}`;
1098
- }
1099
- return defaultPrompt;
1100
- }
1101
- /**
1102
- * Registers all slash commands with the command handler
1103
- */
1104
- async function registerCommands() {
1105
- const registry = getSlashCommandRegistry();
1106
- // Register all slash commands from the central registry (includes custom commands)
1107
- await registerAllSlashCommands();
1108
- // Override specific commands that need UI-specific behavior
1109
- // (Re-registering will replace the centrally-registered versions)
1110
- // Help command - shows overlay instead of text output
1111
- registerSlashCommand({
1112
- name: 'help',
1113
- aliases: ['h', '?'],
1114
- description: 'Show help menu',
1115
- category: CommandCategory.INFO,
1116
- requiresLLM: false,
1117
- canRunOffline: true,
1118
- handler: () => {
1119
- setActiveOverlay((prev) => (prev === 'help' ? 'none' : 'help'));
1120
- },
1121
- });
1122
- // Tokens command - shows overlay instead of text output
1123
- registerSlashCommand({
1124
- name: 'tokens',
1125
- aliases: ['usage', 'cost'],
1126
- description: 'Show token usage statistics',
1127
- category: CommandCategory.INFO,
1128
- requiresLLM: false,
1129
- canRunOffline: true,
1130
- handler: () => {
1131
- setActiveOverlay((prev) => (prev === 'tokens' ? 'none' : 'tokens'));
1132
- },
1133
- });
1134
- // Agents command - shows overlay for list, delegates subcommands to original handler
1135
- registerSlashCommand({
1136
- name: 'agents',
1137
- aliases: ['agent', 'subagents'],
1138
- description: 'Manage sub-agents for task delegation',
1139
- category: CommandCategory.INFO,
1140
- requiresLLM: false,
1141
- canRunOffline: true,
1142
- handler: async (args) => {
1143
- // Show overlay for no args or list subcommand
1144
- if (args.length === 0 || args[0].toLowerCase() === 'list' || args[0].toLowerCase() === 'ls' || args[0] === '-l') {
1145
- setActiveOverlay((prev) => (prev === 'agents' ? 'none' : 'agents'));
1146
- return;
1147
- }
1148
- // For other subcommands, use the original handler from agents.ts
1149
- const { agentsHandler } = await import('../commands/slash/agents.js');
1150
- await agentsHandler(args);
1151
- },
1152
- });
1153
- // Clear command - updates React state
1154
- registerSlashCommand({
1155
- name: 'clear',
1156
- aliases: ['cls'],
1157
- description: 'Clear conversation history',
1158
- category: CommandCategory.SESSION,
1159
- requiresLLM: false,
1160
- canRunOffline: true,
1161
- handler: () => {
1162
- // Reset all UI conversation state
1163
- setConversation([]);
1164
- setShowHistory(false);
1165
- setSlashCommandOutput(null);
1166
- setStreamingCommandOutput([]);
1167
- streamingOutputRef.current = [];
1168
- setError(null);
1169
- setIsProcessing(false);
1170
- setSessionPhase('idle');
1171
- setCompassSpinner(null);
1172
- setActiveOverlay('none');
1173
- setPendingApproval(null);
1174
- setPendingPlanApproval(null);
1175
- setPendingClarification(null);
1176
- setSuggestionOffer(undefined);
1177
- activePlanRef.current = null;
1178
- setActivePlan(null);
1179
- setPlanTrackerMode('compact');
1180
- setMetrics((prev) => ({ ...prev, currentContextTokens: 0 }));
1181
- // Reset stream blocks ref (static ref is cleared by useMemo when conversation empties)
1182
- streamBlocksRef.current = [];
1183
- // Clear session history and stop plan monitoring
1184
- const sessionManager = getSessionManager();
1185
- sessionManager.clearHistory();
1186
- const planPersistence = getPlanPersistence(projectRoot);
1187
- planPersistence.stopFileMonitoring();
1188
- // Notify listeners and confirm
1189
- getEventEmitter().emit('history-cleared', {});
1190
- getEventEmitter().emit('slash-command-output', { command: 'clear', message: 'Conversation cleared.', isError: false });
1191
- },
1192
- });
1193
- // Exit command - calls onExit callback
1194
- registerSlashCommand({
1195
- name: 'exit',
1196
- aliases: ['quit', 'q'],
1197
- description: 'Exit Compass',
1198
- category: CommandCategory.SYSTEM,
1199
- requiresLLM: false,
1200
- canRunOffline: true,
1201
- executeOnComplete: true,
1202
- handler: async () => {
1203
- const sessionManager = getSessionManager();
1204
- await sessionManager.save();
1205
- // Trigger graceful cleanup, then force exit as a safety net
1206
- void onExit();
1207
- setTimeout(() => process.exit(0), 500);
1208
- await new Promise(() => { });
1209
- },
1210
- });
1211
- // Approve command - updates React state
1212
- registerSlashCommand({
1213
- name: 'approve',
1214
- aliases: ['approval', 'mode'],
1215
- description: 'Toggle or set approval mode',
1216
- category: CommandCategory.MODEL,
1217
- requiresLLM: false,
1218
- canRunOffline: true,
1219
- handler: async (args) => {
1220
- const approvalManager = getApprovalManager();
1221
- const sessionManager = getSessionManager();
1222
- const eventEmitter = getEventEmitter();
1223
- const emitOutput = (message, isError = false) => {
1224
- eventEmitter.emit('slash-command-output', { command: 'approve', message, isError });
1225
- };
1226
- if (args.length === 0) {
1227
- // Toggle mode
1228
- const newMode = approvalManager.toggleMode();
1229
- sessionManager.setApprovalMode(newMode);
1230
- setApprovalMode(newMode);
1231
- emitOutput(`Approval mode: ${newMode.toUpperCase()}`);
1232
- }
1233
- else {
1234
- const modeArg = args[0].toLowerCase();
1235
- let newMode;
1236
- switch (modeArg) {
1237
- case 'auto':
1238
- case 'a':
1239
- newMode = ApprovalMode.AUTO;
1240
- break;
1241
- case 'manual':
1242
- case 'm':
1243
- newMode = ApprovalMode.MANUAL;
1244
- break;
1245
- case 'strict':
1246
- case 's':
1247
- newMode = ApprovalMode.STRICT;
1248
- break;
1249
- case 'smart':
1250
- case 'sm':
1251
- newMode = ApprovalMode.SMART;
1252
- break;
1253
- default:
1254
- emitOutput(`Unknown mode: ${modeArg}. Use: auto, manual, strict, or smart`, true);
1255
- return;
1256
- }
1257
- approvalManager.setMode(newMode);
1258
- sessionManager.setApprovalMode(newMode);
1259
- setApprovalMode(newMode);
1260
- emitOutput(`Approval mode set to: ${newMode.toUpperCase()}`);
1261
- }
1262
- },
1263
- });
1264
- // Model command - updates React state via events
1265
- registerSlashCommand({
1266
- name: 'model',
1267
- description: 'Switch model',
1268
- category: CommandCategory.MODEL,
1269
- requiresLLM: false,
1270
- canRunOffline: true,
1271
- handler: async (args) => {
1272
- const client = await getAnthropicClient();
1273
- const eventEmitter = getEventEmitter();
1274
- // Helper to emit output that will be captured by the event listener
1275
- const emitOutput = (message) => {
1276
- eventEmitter.emit('slash-command-output', { command: 'model', message, isError: false });
1277
- };
1278
- if (args.length === 0) {
1279
- // Show current model
1280
- const model = client.getCurrentModel();
1281
- emitOutput(`Current model: ${model.name}`);
1282
- emitOutput('Available: sonnet, opus, haiku');
1283
- return;
1284
- }
1285
- const modelArg = args[0].toLowerCase();
1286
- const success = client.setModel(modelArg);
1287
- if (success) {
1288
- const model = client.getCurrentModel();
1289
- setCurrentModel(model.name);
1290
- setContextWindowSize(model.contextWindow);
1291
- const sessionManager = getSessionManager();
1292
- sessionManager.setModel(model.id);
1293
- emitOutput(`Switched to: ${model.name}`);
1294
- }
1295
- else {
1296
- eventEmitter.emit('slash-command-output', { command: 'model', message: `Unknown model: ${modelArg}. Use: sonnet, opus, or haiku`, isError: true });
1297
- }
1298
- },
1299
- });
1300
- // Switch-plan command - toggles execution mode
1301
- registerSlashCommand({
1302
- name: 'switch-plan',
1303
- aliases: ['sp', 'mode'],
1304
- description: 'Switch execution mode (hybrid/direct/plan)',
1305
- category: CommandCategory.MODEL,
1306
- requiresLLM: false,
1307
- canRunOffline: true,
1308
- executeOnComplete: true,
1309
- handler: async (args) => {
1310
- const sessionManager = getSessionManager();
1311
- const eventEmitter = getEventEmitter();
1312
- const emitOutput = (message) => {
1313
- eventEmitter.emit('slash-command-output', { command: 'switch-plan', message, isError: false });
1314
- };
1315
- const modeDescriptions = {
1316
- hybrid: 'LLM decides when to use planning based on complexity',
1317
- direct: 'Pure agentic execution, no planning',
1318
- plan: 'Always forces planning for every task'
1319
- };
1320
- const modeAliases = {
1321
- hybrid: 'h',
1322
- direct: 'd',
1323
- plan: 'p'
1324
- };
1325
- // No arguments - show interactive mode selector
1326
- if (args.length === 0) {
1327
- setActiveOverlay('mode-selector');
1328
- return;
1329
- }
1330
- // Argument provided - switch to specific mode
1331
- const modeArg = args[0].toLowerCase();
1332
- let newMode;
1333
- switch (modeArg) {
1334
- case 'hybrid':
1335
- case 'h':
1336
- newMode = 'hybrid';
1337
- break;
1338
- case 'direct':
1339
- case 'd':
1340
- newMode = 'direct';
1341
- break;
1342
- case 'plan':
1343
- case 'p':
1344
- newMode = 'plan';
1345
- break;
1346
- default:
1347
- emitOutput('');
1348
- emitOutput(chalk.red(`Unknown mode: ${modeArg}`));
1349
- emitOutput(chalk.gray('Available modes: hybrid, direct, plan'));
1350
- emitOutput('');
1351
- return;
1352
- }
1353
- setExecutionMode(newMode);
1354
- sessionManager.setExecutionMode(newMode);
1355
- const color = newMode === 'hybrid' ? chalk.cyan : newMode === 'direct' ? chalk.yellow : chalk.magenta;
1356
- emitOutput('');
1357
- emitOutput(chalk.green('✓') + ` Switched to ${color.bold(newMode.toUpperCase())} ${chalk.gray(`(${modeAliases[newMode]})`)}`);
1358
- emitOutput(` ${chalk.gray(modeDescriptions[newMode])}`);
1359
- emitOutput('');
1360
- },
1361
- });
1362
- // Logout command - shows interactive confirmation selector
1363
- registerSlashCommand({
1364
- name: 'logout',
1365
- aliases: ['signout'],
1366
- description: 'Clear stored API keys and logout from Compass',
1367
- category: CommandCategory.AUTH,
1368
- requiresLLM: false,
1369
- canRunOffline: true,
1370
- executeOnComplete: true,
1371
- handler: async (args) => {
1372
- const forceLogout = args.includes('--force') || args.includes('-f');
1373
- if (forceLogout) {
1374
- // Skip confirmation with --force flag
1375
- await performLogout();
1376
- return;
1377
- }
1378
- // Check what credentials exist
1379
- const { getCredentialStore } = await import('../services/credential-store.js');
1380
- const credentialStore = await getCredentialStore();
1381
- const hasCompassKey = await credentialStore.hasCompassApiKey();
1382
- const hasAnthropicKey = await credentialStore.hasApiKey();
1383
- // Check provider-specific keys
1384
- const providerNames = ['ollama', 'zai', 'minimax'];
1385
- const storedProviders = [];
1386
- for (const provider of providerNames) {
1387
- if (await credentialStore.hasProviderApiKey(provider)) {
1388
- storedProviders.push(provider);
1389
- }
1390
- }
1391
- if (!hasCompassKey && !hasAnthropicKey && storedProviders.length === 0) {
1392
- const eventEmitter = getEventEmitter();
1393
- eventEmitter.emit('slash-command-output', { command: 'logout', message: '', isError: false });
1394
- eventEmitter.emit('slash-command-output', { command: 'logout', message: chalk.yellow('⚠️ No API keys are currently stored.'), isError: false });
1395
- eventEmitter.emit('slash-command-output', { command: 'logout', message: chalk.gray(' You are already logged out.'), isError: false });
1396
- eventEmitter.emit('slash-command-output', { command: 'logout', message: '', isError: false });
1397
- return;
1398
- }
1399
- // Build details list
1400
- const details = ['The following credentials will be cleared:'];
1401
- if (hasCompassKey) {
1402
- details.push(' • Compass API Key');
1403
- }
1404
- if (hasAnthropicKey) {
1405
- details.push(' • Anthropic API Key');
1406
- }
1407
- for (const provider of storedProviders) {
1408
- details.push(` • ${provider.charAt(0).toUpperCase() + provider.slice(1)} API Key`);
1409
- }
1410
- setLogoutDetails(details);
1411
- setActiveOverlay('logout-confirm');
1412
- },
1413
- });
1414
- // Check if save command needs UI-specific handling
1415
- if (!registry.has('save')) {
1416
- registerSlashCommand({
1417
- name: 'save',
1418
- description: 'Save current session',
1419
- category: CommandCategory.SESSION,
1420
- requiresLLM: false,
1421
- canRunOffline: true,
1422
- handler: async (args) => {
1423
- const sessionManager = getSessionManager();
1424
- const name = args[0] || `session-${Date.now()}`;
1425
- try {
1426
- await sessionManager.saveAs(name);
1427
- logger.output(`Session saved as: ${name}`);
1428
- }
1429
- catch (err) {
1430
- logger.output(`Failed to save session: ${err}`);
1431
- }
1432
- },
1433
- });
1434
- }
1435
- if (!registry.has('context')) {
1436
- registerSlashCommand({
1437
- name: 'context',
1438
- description: 'Show context window usage',
1439
- category: CommandCategory.INFO,
1440
- requiresLLM: false,
1441
- canRunOffline: true,
1442
- handler: async () => {
1443
- const client = await getAnthropicClient();
1444
- const model = client.getCurrentModel();
1445
- const usagePercent = Math.round((tokenUsage.totalTokens / model.contextWindow) * 100);
1446
- logger.output(`Context Usage:`);
1447
- logger.output(` Model: ${model.name}`);
1448
- logger.output(` Window: ${model.contextWindow.toLocaleString()} tokens`);
1449
- logger.output(` Used: ${tokenUsage.totalTokens.toLocaleString()} tokens (${usagePercent}%)`);
1450
- logger.output(` Available: ${(model.contextWindow - tokenUsage.totalTokens).toLocaleString()} tokens`);
1451
- },
1452
- });
1453
- }
1454
- if (!registry.has('reset')) {
1455
- registerSlashCommand({
1456
- name: 'reset',
1457
- description: 'Reset session to clean state',
1458
- category: CommandCategory.SESSION,
1459
- requiresLLM: false,
1460
- canRunOffline: true,
1461
- handler: async () => {
1462
- setConversation([]);
1463
- setMetrics({
1464
- tokenUsage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
1465
- currentContextTokens: 0,
1466
- cost: { inputCost: 0, outputCost: 0, totalCost: 0, currency: 'USD' },
1467
- });
1468
- setError(null);
1469
- setShowHistory(false);
1470
- // Clear plan execution UI state and persistence
1471
- activePlanRef.current = null;
1472
- setActivePlan(null);
1473
- setPlanTrackerMode('compact');
1474
- setSessionPhase('idle');
1475
- setPendingApproval(null);
1476
- setPendingPlanApproval(null);
1477
- setPendingClarification(null);
1478
- setSuggestionOffer(undefined);
1479
- const planPersistence = getPlanPersistence(projectRoot);
1480
- planPersistence.stopFileMonitoring();
1481
- const sessionManager = getSessionManager();
1482
- sessionManager.createSession(projectRoot);
1483
- logger.output('Session reset to clean state.');
1484
- },
1485
- });
1486
- }
1487
- }
1488
- /**
1489
- * Subscribes to event emitter events for real-time updates
1490
- */
1491
- function subscribeToEvents() {
1492
- const eventEmitter = getEventEmitter();
1493
- // Token updates - single batched state update to prevent multiple re-renders
1494
- const unsubTokens = eventEmitter.on('token-update', ({ usage, cost: newCost }) => {
1495
- setMetrics((prev) => ({
1496
- tokenUsage: {
1497
- inputTokens: prev.tokenUsage.inputTokens + usage.inputTokens,
1498
- outputTokens: prev.tokenUsage.outputTokens + usage.outputTokens,
1499
- totalTokens: prev.tokenUsage.totalTokens + usage.totalTokens,
1500
- },
1501
- currentContextTokens: usage.inputTokens,
1502
- cost: {
1503
- inputCost: prev.cost.inputCost + newCost.inputCost,
1504
- outputCost: prev.cost.outputCost + newCost.outputCost,
1505
- totalCost: prev.cost.totalCost + newCost.totalCost,
1506
- currency: 'USD',
1507
- },
1508
- }));
1509
- });
1510
- unsubscribersRef.current.push(unsubTokens);
1511
- // History cleared (from /clear command) - token reset and terminal clear only.
1512
- // All UI state is already reset by the /clear handler itself; duplicating
1513
- // setConversation/setActivePlan/etc. here triggers a second React render cycle
1514
- // that can race yoga-layout node teardown and cause WASM OOB crashes.
1515
- const unsubHistoryCleared = eventEmitter.on('history-cleared', (data) => {
1516
- setMetrics((prev) => ({ ...prev, currentContextTokens: 0 }));
1517
- // Clear the terminal screen including Static scrollback buffer (skip on startup to preserve banner).
1518
- // Deferred via setTimeout so React state updates flush first — writing ANSI escape codes
1519
- // synchronously corrupts Ink's internal cursor tracking, causing duplicate renders.
1520
- if (!data?.isStartup) {
1521
- setTimeout(() => {
1522
- process.stdout.write('\x1B[2J\x1B[H');
1523
- }, 0);
1524
- }
1525
- });
1526
- unsubscribersRef.current.push(unsubHistoryCleared);
1527
- // Agent state changed (from /agents use and /agents reset)
1528
- const unsubAgentState = eventEmitter.on('agent-state-changed', ({ activeAgent }) => {
1529
- setActiveAgentName(activeAgent?.name ?? null);
1530
- });
1531
- unsubscribersRef.current.push(unsubAgentState);
1532
- // Approval requests
1533
- const unsubApproval = eventEmitter.on('approval-required', ({ request }) => {
1534
- setPendingApproval(request);
1535
- });
1536
- unsubscribersRef.current.push(unsubApproval);
1537
- // Plan approval requests
1538
- const unsubPlanApproval = eventEmitter.on('plan-approval-required', ({ request }) => {
1539
- setPendingPlanApproval(request);
1540
- });
1541
- unsubscribersRef.current.push(unsubPlanApproval);
1542
- // Clarification wizard requests
1543
- const unsubClarification = eventEmitter.on('clarification-required', ({ request }) => {
1544
- setPendingClarification(request);
1545
- });
1546
- unsubscribersRef.current.push(unsubClarification);
1547
- // Plan update started — hide the current plan tracker while a new plan is being generated
1548
- const unsubPlanUpdateStarted = eventEmitter.on('plan-update-started', () => {
1549
- activePlanRef.current = null;
1550
- setActivePlan(null);
1551
- setPlanTrackerMode('compact'); // Reset for when the new plan is approved
1552
- });
1553
- unsubscribersRef.current.push(unsubPlanUpdateStarted);
1554
- // Plan execution events - initialize tracker when plan approved.
1555
- // All plan events mutate activePlanRef and call schedulePlanRender() to update
1556
- // only the plan state without triggering InterleavedStream re-renders.
1557
- const unsubPlanExecStarted = eventEmitter.on('plan-execution-started', ({ planId, tasks }) => {
1558
- setPlanTrackerMode('compact'); // Reset visibility for new plan
1559
- activePlanRef.current = {
1560
- planId,
1561
- title: '',
1562
- tasks: tasks.map((task) => ({ task, status: 'pending' })),
1563
- isComplete: false,
1564
- success: false,
1565
- };
1566
- schedulePlanRender();
1567
- });
1568
- unsubscribersRef.current.push(unsubPlanExecStarted);
1569
- // Plan task completed - update tracker
1570
- const unsubPlanTaskCompleted = eventEmitter.on('plan-task-completed', ({ planId, taskId, status, summary: _summary }) => {
1571
- const prev = activePlanRef.current;
1572
- if (!prev || prev.planId !== planId)
1573
- return;
1574
- activePlanRef.current = {
1575
- ...prev,
1576
- tasks: prev.tasks.map((t) => t.task.id === taskId ? { ...t, status } : t),
1577
- };
1578
- schedulePlanRender();
1579
- });
1580
- unsubscribersRef.current.push(unsubPlanTaskCompleted);
1581
- // Plan task progress - update tracker for in-progress detection
1582
- const unsubPlanTaskProgress = eventEmitter.on('plan-task-progress', ({ planId, taskId }) => {
1583
- const prev = activePlanRef.current;
1584
- if (!prev || prev.planId !== planId)
1585
- return;
1586
- activePlanRef.current = {
1587
- ...prev,
1588
- tasks: prev.tasks.map((t) => t.task.id === taskId && t.status === 'pending'
1589
- ? { ...t, status: 'in_progress' }
1590
- : t),
1591
- };
1592
- schedulePlanRender();
1593
- });
1594
- unsubscribersRef.current.push(unsubPlanTaskProgress);
1595
- // Plan execution completed — mark complete synchronously, clear on next idle transition.
1596
- // No setTimeout: the deferred clear used to cause a second layout shift 500ms after
1597
- // processing ended. Instead, activePlanRef is cleared when isProcessing transitions to false.
1598
- const unsubPlanExecCompleted = eventEmitter.on('plan-execution-completed', ({ planId, success }) => {
1599
- const prev = activePlanRef.current;
1600
- if (!prev || prev.planId !== planId)
1601
- return;
1602
- activePlanRef.current = { ...prev, isComplete: true, success };
1603
- schedulePlanRender();
1604
- });
1605
- unsubscribersRef.current.push(unsubPlanExecCompleted);
1606
- // Model switches
1607
- const unsubModel = eventEmitter.on('model-switched', ({ model }) => {
1608
- setCurrentModel(model.name);
1609
- setContextWindowSize(model.contextWindow);
1610
- });
1611
- unsubscribersRef.current.push(unsubModel);
1612
- // Errors
1613
- const unsubError = eventEmitter.on('error', ({ message }) => {
1614
- setError(message);
1615
- });
1616
- unsubscribersRef.current.push(unsubError);
1617
- // Tool status messages (for granular progress during long-running tools)
1618
- const unsubToolStatus = eventEmitter.on('tool-status-message', ({ toolId, message }) => {
1619
- updateStreamBlocks((prev) => prev.map((block) => block.type === 'tool' && block.id === toolId
1620
- ? { ...block, statusMessage: message }
1621
- : block));
1622
- });
1623
- unsubscribersRef.current.push(unsubToolStatus);
1624
- // Tool complexity reasoning (persistent reasoning for plan mode)
1625
- const unsubComplexityReasoning = eventEmitter.on('tool-complexity-reasoning', ({ toolId, reasoning }) => {
1626
- updateStreamBlocks((prev) => prev.map((block) => block.type === 'tool' && block.id === toolId
1627
- ? { ...block, complexityReasoning: reasoning }
1628
- : block));
1629
- });
1630
- unsubscribersRef.current.push(unsubComplexityReasoning);
1631
- // Slash command output events (for real-time streaming output from commands like /commit)
1632
- // Accumulate synchronously - React batching is fine since we add to conversation at the end
1633
- const unsubSlashCommandOutput = eventEmitter.on('slash-command-output', ({ message }) => {
1634
- logger.debug(`[StreamingOutput Event] Received: ${message.substring(0, 50)}, total lines: ${streamingOutputRef.current.length + 1}`);
1635
- streamingOutputRef.current.push(message);
1636
- // Trigger state update for real-time display during command execution
1637
- setStreamingCommandOutput([...streamingOutputRef.current]);
1638
- });
1639
- unsubscribersRef.current.push(unsubSlashCommandOutput);
1640
- // Compass spinner events (for visual feedback during operations like /commit)
1641
- const unsubCompassSpinner = eventEmitter.on('compass-spinner', (data) => {
1642
- logger.debug(`[CompassSpinner Event] Received: active=${data.active}, message=${data.message}`);
1643
- setCompassSpinner(data.active ? data : null);
1644
- });
1645
- unsubscribersRef.current.push(unsubCompassSpinner);
1646
- // Agent wizard events (for /agents create and /agents edit)
1647
- const unsubAgentWizard = eventEmitter.on('open-agent-wizard', ({ mode, agentName }) => {
1648
- logger.debug(`[AgentWizard Event] Opening wizard: mode=${mode}, agentName=${agentName}`);
1649
- setActiveOverlay('agent-wizard');
1650
- });
1651
- unsubscribersRef.current.push(unsubAgentWizard);
1652
- }
1653
- /**
1654
- * Helper to update a tool block in stream blocks by ID
1655
- */
1656
- function updateToolBlock(toolId, updates) {
1657
- // Use optimized in-place update instead of full array map
1658
- updateToolBlockById(toolId, updates);
1659
- }
1660
- /**
1661
- * Executes tool calls and returns tool results with timing records.
1662
- * Updates UI state progressively as each tool completes.
1663
- */
1664
- async function executeToolCalls(toolUseBlocks) {
1665
- const toolRegistry = getToolRegistry();
1666
- const toolResults = [];
1667
- const toolRecords = [];
1668
- let userRejected = false;
1669
- let endTurn = false;
1670
- for (const toolUse of toolUseBlocks) {
1671
- // Check abort flag before each tool execution
1672
- if (abortLoopRef.current) {
1673
- logger.info('Tool execution aborted by user');
1674
- break;
1675
- }
1676
- logger.debug(`Executing tool: ${toolUse.name}`);
1677
- // Update existing tool block to "running" status (block was added during streaming)
1678
- updateToolBlockById(toolUse.id, { status: 'running', params: toolUse.input });
1679
- // Create per-tool AbortController for execute_command (allows Ctrl+X skip)
1680
- const isExecuteCommand = toolUse.name === 'execute_command';
1681
- if (isExecuteCommand) {
1682
- toolAbortRef.current = new AbortController();
1683
- currentToolNameRef.current = 'execute_command';
1684
- }
1685
- const startTime = Date.now();
1686
- try {
1687
- // Use per-tool signal for execute_command, loop-wide signal for others
1688
- const abortSignal = isExecuteCommand
1689
- ? toolAbortRef.current.signal
1690
- : abortControllerRef.current?.signal;
1691
- const result = await toolRegistry.executeTool(toolUse.name, toolUse.input, { projectRoot, toolCallId: toolUse.id, abortSignal });
1692
- const duration = Date.now() - startTime;
1693
- // Clear per-tool abort state
1694
- if (isExecuteCommand) {
1695
- toolAbortRef.current = null;
1696
- currentToolNameRef.current = null;
1697
- }
1698
- // Check if the tool was skipped by the user (Ctrl+X)
1699
- const toolData = result.data;
1700
- if (toolData?.skippedByUser) {
1701
- const partialOutput = (toolData.stdout || '').slice(0, 200) + (toolData.stderr || '').slice(0, 200);
1702
- updateToolBlock(toolUse.id, {
1703
- status: 'skipped',
1704
- result: JSON.stringify(result.data),
1705
- duration_ms: duration,
1706
- });
1707
- toolResults.push({
1708
- type: 'tool_result',
1709
- tool_use_id: toolUse.id,
1710
- content: `Command was skipped by the user.${partialOutput ? ` Partial output:\n${partialOutput}` : ''}`,
1711
- });
1712
- const record = {
1713
- name: toolUse.name,
1714
- input: toolUse.input,
1715
- result: result.data,
1716
- success: false,
1717
- error: 'Skipped by user',
1718
- duration_ms: duration,
1719
- };
1720
- toolRecords.push(record);
1721
- logger.debug(`Tool ${toolUse.name} skipped by user after ${duration}ms`);
1722
- continue;
1723
- }
1724
- if (result.success) {
1725
- // Check if the tool's return data indicates a user rejection
1726
- // (tool handler returns success: false but doesn't throw)
1727
- const isToolRejection = toolData?.success === false &&
1728
- (toolData?.error?.toLowerCase().includes('rejected by user') ||
1729
- toolData?.error?.toLowerCase().includes('rejected'));
1730
- // Check if a plan was cancelled — this signals the turn should end
1731
- if (toolData?.planCancelled === true) {
1732
- endTurn = true;
1733
- logger.info(`Plan cancelled via ${toolUse.name} — will end turn`);
1734
- }
1735
- if (isToolRejection) {
1736
- userRejected = true;
1737
- // Update tool block with error
1738
- updateToolBlock(toolUse.id, {
1739
- status: 'error',
1740
- error: toolData?.error || 'Operation rejected by user',
1741
- duration_ms: duration,
1742
- });
1743
- // Still add the result for the LLM to see, but mark as error
1744
- toolResults.push({
1745
- type: 'tool_result',
1746
- tool_use_id: toolUse.id,
1747
- content: `Error: ${toolData?.error || 'Operation rejected by user'}`,
1748
- is_error: true,
1749
- });
1750
- const record = {
1751
- name: toolUse.name,
1752
- input: toolUse.input,
1753
- result: null,
1754
- success: false,
1755
- error: toolData?.error || 'Operation rejected by user',
1756
- duration_ms: duration,
1757
- };
1758
- toolRecords.push(record);
1759
- logger.debug(`Tool ${toolUse.name} rejected by user`);
1760
- }
1761
- else {
1762
- // Update tool block with success.
1763
- // Store parsedResult to avoid re-parsing JSON in the render path.
1764
- updateToolBlock(toolUse.id, {
1765
- status: 'success',
1766
- result: JSON.stringify(result.data),
1767
- parsedResult: result.data,
1768
- duration_ms: duration,
1769
- });
1770
- toolResults.push({
1771
- type: 'tool_result',
1772
- tool_use_id: toolUse.id,
1773
- content: JSON.stringify(result.data, null, 2),
1774
- });
1775
- const record = {
1776
- name: toolUse.name,
1777
- input: toolUse.input,
1778
- result: result.data,
1779
- success: true,
1780
- duration_ms: duration,
1781
- };
1782
- toolRecords.push(record);
1783
- logger.debug(`Tool ${toolUse.name} succeeded in ${duration}ms`);
1784
- }
1785
- }
1786
- else {
1787
- // Check if this was a user rejection (tool threw an error)
1788
- const isUserRejection = result.error?.toLowerCase().includes('rejected by user') ||
1789
- result.error?.toLowerCase().includes('rejected');
1790
- if (isUserRejection) {
1791
- userRejected = true;
1792
- }
1793
- // Update tool block with error
1794
- updateToolBlock(toolUse.id, {
1795
- status: 'error',
1796
- error: result.error,
1797
- duration_ms: duration,
1798
- });
1799
- toolResults.push({
1800
- type: 'tool_result',
1801
- tool_use_id: toolUse.id,
1802
- content: `Error: ${result.error}`,
1803
- is_error: true,
1804
- });
1805
- const record = {
1806
- name: toolUse.name,
1807
- input: toolUse.input,
1808
- result: null,
1809
- success: false,
1810
- error: result.error,
1811
- duration_ms: duration,
1812
- };
1813
- toolRecords.push(record);
1814
- logger.debug(`Tool ${toolUse.name} failed: ${result.error}`);
1815
- }
1816
- }
1817
- catch (err) {
1818
- // Clear per-tool abort state on error
1819
- if (isExecuteCommand) {
1820
- toolAbortRef.current = null;
1821
- currentToolNameRef.current = null;
1822
- }
1823
- const duration = Date.now() - startTime;
1824
- const errorMsg = err instanceof Error ? err.message : 'Tool execution failed';
1825
- // Update tool block with error
1826
- updateToolBlock(toolUse.id, {
1827
- status: 'error',
1828
- error: errorMsg,
1829
- duration_ms: duration,
1830
- });
1831
- toolResults.push({
1832
- type: 'tool_result',
1833
- tool_use_id: toolUse.id,
1834
- content: `Error: ${errorMsg}`,
1835
- is_error: true,
1836
- });
1837
- const record = {
1838
- name: toolUse.name,
1839
- input: toolUse.input,
1840
- result: null,
1841
- success: false,
1842
- error: errorMsg,
1843
- duration_ms: duration,
1844
- };
1845
- toolRecords.push(record);
1846
- logger.error(`Tool ${toolUse.name} threw error`, err);
1847
- }
1848
- }
1849
- return { results: toolResults, records: toolRecords, userRejected, endTurn };
1850
- }
1851
- /**
1852
- * Handles clipboard paste (Ctrl+V)
1853
- * Extracts images and files from clipboard and adds them as pending attachments
1854
- */
1855
- const handlePaste = useCallback(async () => {
1856
- try {
1857
- // Check if clipboard contains supported content
1858
- const hasContent = await hasSupportedClipboardContent();
1859
- if (!hasContent) {
1860
- logger.output(chalk.yellow('⚠ No supported content (images or PDFs) in clipboard'));
1861
- return;
1862
- }
1863
- // Get clipboard content
1864
- const clipboardContent = await getClipboardContent();
1865
- if (clipboardContent.images.length === 0 && clipboardContent.files.length === 0) {
1866
- logger.output(chalk.yellow('⚠ Could not extract any attachments from clipboard'));
1867
- return;
1868
- }
1869
- // Extract attachments from clipboard content
1870
- const attachments = await extractAttachmentsFromClipboard(clipboardContent);
1871
- if (attachments.length === 0) {
1872
- logger.output(chalk.yellow('⚠ Could not extract any attachments from clipboard'));
1873
- return;
1874
- }
1875
- // Add attachments to session manager
1876
- const sessionManager = getSessionManager();
1877
- sessionManager.addPendingAttachments(attachments);
1878
- // Show feedback to user
1879
- const imageCount = attachments.filter(a => a.type === 'image').length;
1880
- const pdfCount = attachments.filter(a => a.type === "document").length;
1881
- let message = chalk.green(`✓ Pasted ${attachments.length} file${attachments.length > 1 ? 's' : ''}`);
1882
- if (imageCount > 0)
1883
- message += chalk.gray(` (${imageCount} image${imageCount > 1 ? 's' : ''})`);
1884
- if (pdfCount > 0)
1885
- message += chalk.gray(` (${pdfCount} PDF${pdfCount > 1 ? 's' : ''})`);
1886
- logger.output('');
1887
- logger.output(message);
1888
- logger.output(chalk.cyan(`💡 ${attachments.length} attachment${attachments.length > 1 ? 's' : ''} pending for next message`));
1889
- logger.output('');
1890
- // Emit event to update UI
1891
- const eventEmitter = getEventEmitter();
1892
- eventEmitter.emit('attachments-updated', {
1893
- attachments: sessionManager.getPendingAttachments(),
1894
- });
1895
- logger.info(`Pasted ${attachments.length} attachments from clipboard`);
1896
- }
1897
- catch (error) {
1898
- const errorMessage = error instanceof Error ? error.message : 'Failed to paste from clipboard';
1899
- logger.debug('Clipboard paste failed', { error });
1900
- logger.output(chalk.red(`❌ ${errorMessage}`));
1901
- }
1902
- }, []);
1903
- /**
1904
- * Handles user input submission from the Prompt component
1905
- * Implements an agentic loop with proper tool use
1906
- */
1907
- const handleSubmit = useCallback(async (input) => {
1908
- const parsed = parseInput(input);
1909
- // Hide welcome message after first input
1910
- setShouldShowWelcome(false);
1911
- // Record activity for rating tracking
1912
- if (ratingStateManagerRef.current) {
1913
- ratingStateManagerRef.current.recordActivity();
1914
- }
1915
- // Clear any previous suggestion offer when user submits new input
1916
- setSuggestionOffer(undefined);
1917
- // Initialize iteration scoper (accessible in finally block)
1918
- const iterationScoper = getIterationScoper();
1919
- let isPlanMode = false;
1920
- // Log user input for audit trail
1921
- const auditLogger = getAuditLogger(projectRoot);
1922
- // Handle slash commands
1923
- if (parsed.type === 'slash') {
1924
- await auditLogger.logUserInput(input, true);
1925
- // Clear any previous slash command output
1926
- setSlashCommandOutput(null);
1927
- // Clear streaming command output and reset the ref
1928
- streamingOutputRef.current = [];
1929
- setStreamingCommandOutput([]);
1930
- try {
1931
- // Only show "Thinking..." indicator for commands that require LLM interaction.
1932
- // Local commands (e.g. /model, /help, /clear) execute instantly and don't need it.
1933
- const registry = getSlashCommandRegistry();
1934
- const needsLLM = parsed.command ? registry.requiresLLM(parsed.command) : false;
1935
- if (needsLLM) {
1936
- setIsProcessing(true);
1937
- }
1938
- logger.debug(`[InteractiveSession] Starting slash command: ${parsed.command}, isProcessing=${needsLLM}`);
1939
- // Capture console output during slash command execution
1940
- // This supports both new-style (event emission) and old-style (console.log) commands
1941
- const { result, output: capturedConsoleOutput } = await captureConsoleOutput(async () => {
1942
- return await executeSlashCommand(parsed);
1943
- });
1944
- logger.debug(`[InteractiveSession] Slash command completed: ${parsed.command}, streamingOutputRef.length=${streamingOutputRef.current.length}, capturedConsoleOutput.length=${capturedConsoleOutput.length}`);
1945
- if (!result.handled) {
1946
- setError(`Unknown command: /${parsed.command}. Type /help for available commands.`);
1947
- setIsProcessing(false);
1948
- return;
1949
- }
1950
- // Check if this is a custom command that wants to continue as a prompt
1951
- if (result.continueAsPrompt) {
1952
- // Continue with LLM flow using the processed prompt
1953
- // Fall through to natural language handling with the processed content
1954
- logger.debug('Custom command continuing as prompt', {
1955
- command: parsed.command,
1956
- contentLength: result.continueAsPrompt.length,
1957
- });
1958
- // Override input for LLM processing
1959
- // The original command is shown to user, but processed content goes to LLM
1960
- // We need to continue below with the processed content
1961
- // Store the custom command result for use below
1962
- parsed.customCommandResult = result;
1963
- // Set back to true for LLM processing
1964
- setIsProcessing(true);
1965
- }
1966
- else {
1967
- // Regular slash command handled - display output ephemerally (NOT added to conversation/LLM context)
1968
- // Prefer streaming events (new-style), fall back to captured console output (old-style)
1969
- const hasStreamingOutput = streamingOutputRef.current.length > 0;
1970
- const hasCapturedOutput = capturedConsoleOutput.trim().length > 0;
1971
- logger.debug(`[InteractiveSession] Processing output: streaming=${streamingOutputRef.current.length} lines, captured=${capturedConsoleOutput.length} chars`);
1972
- if (hasStreamingOutput || hasCapturedOutput) {
1973
- // Prefer streaming output if available, otherwise use captured console output
1974
- const combinedOutput = hasStreamingOutput
1975
- ? streamingOutputRef.current.join('\n')
1976
- : capturedConsoleOutput;
1977
- logger.info(`[InteractiveSession] Setting ephemeral slash command output (${combinedOutput.length} chars, source=${hasStreamingOutput ? 'events' : 'console'})`);
1978
- // Clear streaming refs FIRST to prevent both streamingCommandOutput and slashCommandOutput
1979
- // from rendering simultaneously (which could show duplicate/stale content)
1980
- streamingOutputRef.current = [];
1981
- setStreamingCommandOutput([]);
1982
- // Use ephemeral state - this output is cleared when user types next command or prompt
1983
- // This prevents slash command output (like /help) from being sent to LLM as conversation context
1984
- setSlashCommandOutput(combinedOutput);
1985
- // Set isProcessing to false to display the output
1986
- setIsProcessing(false);
1987
- logger.debug('[InteractiveSession] Set ephemeral output and cleared streaming refs');
1988
- }
1989
- else {
1990
- logger.debug(`[InteractiveSession] No output for command: ${parsed.command} (silent command)`);
1991
- setIsProcessing(false);
1992
- }
1993
- return;
1994
- }
1995
- }
1996
- catch (err) {
1997
- const errorMessage = err instanceof Error ? err.message : 'Command failed';
1998
- setError(errorMessage);
1999
- logger.error(`Slash command error: ${parsed.command}`, err);
2000
- await auditLogger.logError(errorMessage, err);
2001
- setIsProcessing(false);
2002
- // Still clear streaming output even on error
2003
- streamingOutputRef.current = [];
2004
- setStreamingCommandOutput([]);
2005
- return;
2006
- }
2007
- }
2008
- // Clear slash command output when starting new LLM interaction
2009
- setSlashCommandOutput(null);
2010
- // Check if input is just "/" (incomplete slash command)
2011
- if (input.trim() === '/') {
2012
- setError('Incomplete command. Type /help to see available commands.');
2013
- return;
2014
- }
2015
- // Check if this is from a custom command that should continue as prompt
2016
- const customCommandResult = parsed.customCommandResult;
2017
- const isCustomCommand = !!customCommandResult?.continueAsPrompt;
2018
- // Log user input for audit trail (only if not a custom command, which was already logged)
2019
- if (!isCustomCommand) {
2020
- await auditLogger.logUserInput(input, false);
2021
- }
2022
- // Parse any system-reminders in the input for IDE state
2023
- const ideStateService = getIdeStateService();
2024
- ideStateService.updateFromMessage(input);
2025
- // For custom commands, use the processed prompt directly
2026
- // Otherwise, preprocess for IDE context injection
2027
- let inputForLLM;
2028
- if (isCustomCommand && customCommandResult?.continueAsPrompt) {
2029
- inputForLLM = customCommandResult.continueAsPrompt;
2030
- logger.debug('Using custom command processed content for LLM', {
2031
- contentLength: inputForLLM.length,
2032
- });
2033
- }
2034
- else {
2035
- const promptPreprocessor = getPromptPreprocessor();
2036
- const { processedInput, contextInjected, injectionDetails } = await promptPreprocessor.process(input);
2037
- if (contextInjected && injectionDetails) {
2038
- logger.info('IDE context injected into prompt', {
2039
- filePath: injectionDetails.filePath,
2040
- lineCount: injectionDetails.lineCount,
2041
- truncated: injectionDetails.truncated,
2042
- });
2043
- }
2044
- inputForLLM = processedInput;
2045
- }
2046
- // Enable history display on first user message
2047
- setShowHistory(true);
2048
- // Handle natural language input - send to Claude
2049
- setIsProcessing(true);
2050
- setSessionPhase('classifying');
2051
- setError(null);
2052
- resetStreamBlocks();
2053
- // Handle plan execution tracker state on new request
2054
- {
2055
- const prev = activePlanRef.current;
2056
- if (prev) {
2057
- if (prev.isComplete || executionMode === 'direct') {
2058
- activePlanRef.current = null;
2059
- }
2060
- else if (prev.isPaused) {
2061
- activePlanRef.current = { ...prev, isPaused: false };
2062
- }
2063
- }
2064
- setActivePlan(activePlanRef.current);
2065
- }
2066
- // Classify the request for SMART mode approval decisions
2067
- // This must happen BEFORE any tool execution so the ApprovalManager can use the context
2068
- const approvalManager = getApprovalManager();
2069
- approvalManager.classifyAndSetContext(input);
2070
- // Save checkpoint for potential rollback if user cancels mid-execution
2071
- conversationCheckpointRef.current = [...conversation];
2072
- // Add user message to conversation (show original input to user)
2073
- const userEntry = {
2074
- id: `user-${Date.now()}`,
2075
- role: 'user',
2076
- content: input,
2077
- timestamp: new Date(),
2078
- };
2079
- setConversation((prev) => [...prev, userEntry]);
2080
- // Hoisted so `finally` can read the last assistant response for question detection
2081
- let finalResponseForSuggestion = '';
2082
- try {
2083
- const client = await getAnthropicClient();
2084
- if (!client.isInitialized()) {
2085
- const initialized = await client.initialize();
2086
- if (!initialized) {
2087
- throw new Error('Failed to initialize API client. Please check your API key with: compass setup');
2088
- }
2089
- }
2090
- // Refresh project context if stale (files created/deleted since last call)
2091
- // This ensures Anthropic's prompt cache gets fresh context when project structure changes
2092
- await refreshProjectContext();
2093
- // Build system prompt with project context and model info
2094
- const model = client.getCurrentModel();
2095
- const systemPrompt = await buildSystemPrompt(projectSummary, model.name, model.id);
2096
- // Log system prompt for audit trail
2097
- await auditLogger.logSystemPrompt(systemPrompt);
2098
- // Get tool definitions from registry
2099
- const toolRegistry = getToolRegistry();
2100
- let tools = toolRegistry.getToolDefinitions();
2101
- // Filter tools based on execution mode
2102
- if (executionMode === 'direct') {
2103
- // In DIRECT mode, remove the enter_plan_mode tool
2104
- tools = tools.filter(tool => tool.name !== 'enter_plan_mode');
2105
- logger.debug('Execution mode: DIRECT - enter_plan_mode tool removed');
2106
- }
2107
- else {
2108
- logger.debug(`Execution mode: ${executionMode.toUpperCase()} - all tools available`);
2109
- }
2110
- // Build initial messages for API from conversation history
2111
- const messages = conversation.map((entry) => ({
2112
- role: entry.role === 'system' ? 'user' : entry.role,
2113
- content: entry.content,
2114
- }));
2115
- // ACTIVE PLAN CONTEXT INJECTION
2116
- // If there's an active plan being executed, inject the pending tasks context
2117
- // This ensures the LLM knows exactly what tasks remain when user says "continue"
2118
- const planPersistence = getPlanPersistence(projectRoot);
2119
- const activePlanId = planPersistence.getActivePlanId();
2120
- if (activePlanId) {
2121
- const pendingContext = await planPersistence.getPendingTasksContext(activePlanId);
2122
- if (pendingContext) {
2123
- logger.info('Injecting active plan context', {
2124
- planId: activePlanId,
2125
- completedTasks: pendingContext.completedTasks,
2126
- totalTasks: pendingContext.totalTasks,
2127
- pendingCount: pendingContext.pendingTasks.length,
2128
- });
2129
- // Insert plan context BEFORE the user's message
2130
- messages.push({
2131
- role: 'user',
2132
- content: pendingContext.formattedContext,
2133
- });
2134
- }
2135
- }
2136
- // Use processedInput (with potential IDE context) for LLM
2137
- // Check for pending attachments and build appropriate content
2138
- const sessionMgrForAttachments = getSessionManager();
2139
- let userMessageContent;
2140
- if (sessionMgrForAttachments.hasPendingAttachments()) {
2141
- const pendingAttachments = sessionMgrForAttachments.consumePendingAttachments();
2142
- userMessageContent = buildMessageContent(inputForLLM, pendingAttachments);
2143
- logger.info(`Sending message with ${pendingAttachments.length} attachment(s)`);
2144
- }
2145
- else {
2146
- userMessageContent = inputForLLM;
2147
- }
2148
- messages.push({ role: 'user', content: userMessageContent });
2149
- // EXECUTION MODE HANDLING
2150
- if (executionMode === 'plan' && !isCustomCommand) {
2151
- // PLAN MODE: Force planning for EVERY request (except custom commands/skills)
2152
- // Skills have their own execution logic and should not be forced into planning
2153
- // BUT: skip forcing if there's already an active plan that's been approved/is executing
2154
- // This prevents re-entering plan mode on follow-up confirmations like "yes, proceed"
2155
- const planPersistenceForMode = getPlanPersistence(projectRoot);
2156
- const activePlanIdForMode = planPersistenceForMode.getActivePlanId();
2157
- let hasActivePlan = false;
2158
- if (activePlanIdForMode) {
2159
- const activePlanRecord = await planPersistenceForMode.loadPlanRecord(activePlanIdForMode);
2160
- if (activePlanRecord && (activePlanRecord.phase === 'approved' || activePlanRecord.phase === 'executing')) {
2161
- hasActivePlan = true;
2162
- logger.info('PLAN mode: skipping forced planning - active plan already approved/executing', {
2163
- planId: activePlanIdForMode,
2164
- phase: activePlanRecord.phase,
2165
- });
2166
- }
2167
- }
2168
- if (!hasActivePlan) {
2169
- logger.info('PLAN mode active - forcing planning for this request');
2170
- const forcePlanningMessage = `[SYSTEM REQUIREMENT - MANDATORY]
2171
- The system is in PLAN mode. You MUST use the \`enter_plan_mode\` tool FIRST before taking any other actions.
2172
-
2173
- Do NOT directly execute commands, create files, or modify code without first creating an implementation plan.
2174
-
2175
- Call \`enter_plan_mode\` with:
2176
- - task_summary: Brief description of what needs to be done
2177
- - complexity_reason: Why planning is needed for this task
2178
-
2179
- After the plan is approved, proceed with execution following the plan tasks.`;
2180
- // Insert mandatory planning instruction before the user's message
2181
- messages.splice(messages.length - 1, 0, {
2182
- role: 'user',
2183
- content: forcePlanningMessage,
2184
- });
2185
- }
2186
- }
2187
- else if (executionMode === 'hybrid') {
2188
- // HYBRID MODE: Run complexity checks to suggest planning when appropriate
2189
- // Skip pre-flight for custom commands/skills that return continueAsPrompt
2190
- // These have already been processed and should execute directly
2191
- // Also skip for short follow-up messages when conversation has context
2192
- // This handles cases like "yes", "no", "please do" responses to assistant questions
2193
- const hasConversationContext = conversation.some(entry => entry.role === 'assistant');
2194
- const isShortFollowUp = input.trim().length < 50 && input.trim().split(/\s+/).length < 10;
2195
- if (isCustomCommand || (hasConversationContext && isShortFollowUp)) {
2196
- if (isCustomCommand) {
2197
- logger.debug('Skipping pre-flight check for custom command/skill');
2198
- }
2199
- else {
2200
- logger.debug('Skipping pre-flight check for short follow-up message', {
2201
- inputLength: input.length,
2202
- wordCount: input.trim().split(/\s+/).length,
2203
- });
2204
- }
2205
- // Skip pre-flight and proceed directly to agentic loop with full conversation
2206
- }
2207
- else {
2208
- // Create a fresh AbortController BEFORE pre-flight so a previously-aborted signal
2209
- // doesn't immediately cancel the complexity classifier request
2210
- abortControllerRef.current = new AbortController();
2211
- try {
2212
- // Get personality mode for consistent classifier tone
2213
- const sessionMgrPreflight = getSessionManager();
2214
- const preflightPersonality = sessionMgrPreflight.getEffectivePersonalityMode();
2215
- // Convert conversation history to Message format for analyzer context
2216
- const conversationForAnalyzer = conversation
2217
- .filter(entry => entry.role === 'user' || entry.role === 'assistant')
2218
- .map(entry => ({
2219
- role: entry.role,
2220
- content: entry.content
2221
- }));
2222
- const preflightResult = await checkPlanModeFallback(input, false, // LLM hasn't called plan mode yet
2223
- projectRoot, preflightPersonality, abortControllerRef.current?.signal, // Pass abort signal for cancellation
2224
- conversationForAnalyzer // Pass conversation history for context
2225
- );
2226
- // Handle NO_ACTION mode - display explanation and skip LLM call
2227
- if (preflightResult.noActionNeeded) {
2228
- logger.info('Pre-flight complexity check: no action needed', {
2229
- confidence: preflightResult.confidence,
2230
- explanation: preflightResult.noActionExplanation,
2231
- });
2232
- // Build a response message for the user
2233
- const noActionResponse = preflightResult.noActionExplanation || preflightResult.reasoning;
2234
- // Add assistant response to conversation
2235
- const assistantEntry = {
2236
- id: `assistant-${Date.now()}`,
2237
- role: 'assistant',
2238
- content: noActionResponse,
2239
- timestamp: new Date(),
2240
- };
2241
- setConversation((prev) => [...prev, assistantEntry]);
2242
- // Save to session manager
2243
- const sessionManager = getSessionManager();
2244
- sessionManager.addMessage({ role: 'user', content: input });
2245
- sessionManager.addMessage({ role: 'assistant', content: noActionResponse });
2246
- // Clear streaming state and stop processing
2247
- setIsProcessing(false);
2248
- setSessionPhase('idle');
2249
- // Skip the LLM call entirely
2250
- return;
2251
- }
2252
- // Handle CLARIFICATION mode - ask user questions before proceeding
2253
- if (preflightResult.needsClarification && preflightResult.clarificationQuestions && preflightResult.clarificationQuestions.length > 0) {
2254
- logger.info('Pre-flight complexity check: clarification needed', {
2255
- confidence: preflightResult.confidence,
2256
- questionCount: preflightResult.clarificationQuestions.length,
2257
- });
2258
- // Request clarification from user via the wizard
2259
- const clarificationResponse = await approvalManager.requestClarification(input.substring(0, 200), // Task summary (truncated)
2260
- preflightResult.clarificationQuestions, preflightResult.reasoning);
2261
- if (clarificationResponse.completed && clarificationResponse.answers && clarificationResponse.answers.length > 0) {
2262
- // Format answers for LLM context
2263
- const answersContext = clarificationResponse.answers
2264
- .map(a => `- ${a.questionId}: ${Array.isArray(a.answer) ? a.answer.join(', ') : a.answer}`)
2265
- .join('\n');
2266
- const clarificationContext = `[USER CLARIFICATION - IMPORTANT]
2267
- The user has answered the following clarification questions about their request:
2268
-
2269
- ${answersContext}
2270
-
2271
- Please incorporate these preferences into your implementation.`;
2272
- // Insert clarification context before the user's message
2273
- messages.splice(messages.length - 1, 0, {
2274
- role: 'user',
2275
- content: clarificationContext,
2276
- });
2277
- logger.info('Clarification answers injected into context', {
2278
- answerCount: clarificationResponse.answers.length,
2279
- });
2280
- }
2281
- else {
2282
- // User cancelled clarification - continue without answers
2283
- logger.info('Clarification cancelled by user, proceeding without');
2284
- }
2285
- }
2286
- // Handle SPAWN_AGENT mode - inject guidance so the agentic loop calls spawn_task
2287
- if (preflightResult.shouldSpawnAgent && preflightResult.spawnAgentName && preflightResult.spawnAgentTask) {
2288
- logger.info('Pre-flight complexity check: advising agent spawn via agentic loop', {
2289
- confidence: preflightResult.confidence,
2290
- agentName: preflightResult.spawnAgentName,
2291
- reasoning: preflightResult.reasoning,
2292
- });
2293
- const spawnAgentAdvice = `[SYSTEM GUIDANCE - IMPORTANT]
2294
- This task has been analyzed and should be delegated to a specialized sub-agent (confidence: ${preflightResult.confidence}%).
2295
- ${preflightResult.reasoning}
2296
-
2297
- You MUST use the \`spawn_task\` tool to delegate this task with the following parameters:
2298
- - agent_name: "${preflightResult.spawnAgentName}"
2299
- - task: "${preflightResult.spawnAgentTask.replace(/"/g, '\\"')}"
2300
-
2301
- Do NOT attempt to handle this task directly. Use spawn_task immediately.`;
2302
- messages.splice(messages.length - 1, 0, {
2303
- role: 'user',
2304
- content: spawnAgentAdvice,
2305
- });
2306
- }
2307
- if (preflightResult.shouldSuggestPlanMode && preflightResult.suggestedPrompt) {
2308
- logger.info('Pre-flight complexity check: advising plan mode', {
2309
- confidence: preflightResult.confidence,
2310
- detectedMode: preflightResult.detectedMode,
2311
- });
2312
- // Inject strong guidance BEFORE the user's request
2313
- const planModeAdvice = `[SYSTEM GUIDANCE - IMPORTANT]
2314
- This task has been analyzed as complex (confidence: ${preflightResult.confidence}%).
2315
- ${preflightResult.reasoning}
2316
-
2317
- You MUST use the \`enter_plan_mode\` tool FIRST before taking any other actions.
2318
- Do NOT directly create files or execute commands without planning first.
2319
-
2320
- After calling enter_plan_mode, wait for the plan to be approved before proceeding.`;
2321
- // Insert advice before the user's message
2322
- messages.splice(messages.length - 1, 0, {
2323
- role: 'user',
2324
- content: planModeAdvice,
2325
- });
2326
- }
2327
- }
2328
- catch (error) {
2329
- // Check if this was an abort (user cancelled)
2330
- const errorMessage = error instanceof Error ? error.message : String(error);
2331
- const isAbort = errorMessage === 'Request aborted' ||
2332
- (error instanceof Error && error.name === 'AbortError') ||
2333
- abortLoopRef.current;
2334
- if (isAbort) {
2335
- logger.debug('Pre-flight complexity check cancelled by user');
2336
- setIsProcessing(false);
2337
- setSessionPhase('idle');
2338
- return; // Exit early, cancellation message already shown
2339
- }
2340
- logger.warn('Pre-flight complexity check failed', { error });
2341
- // Continue without advice on error
2342
- }
2343
- } // end of else block for non-follow-up messages
2344
- }
2345
- else {
2346
- // DIRECT MODE: Skip all complexity checks and planning
2347
- logger.debug('DIRECT mode active - skipping all complexity checks');
2348
- }
2349
- // Track agentic loop data for logging
2350
- const loopStartTime = Date.now();
2351
- const agenticIterations = [];
2352
- let totalToolCalls = 0;
2353
- const totalUsage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
2354
- const totalCost = { inputCost: 0, outputCost: 0, totalCost: 0 };
2355
- // Load agentic configuration
2356
- const configManager = getConfigManager();
2357
- const agenticConfig = configManager.getValue('agentic');
2358
- const maxIterations = agenticConfig?.maxIterations ?? DEFAULT_MAX_AGENTIC_ITERATIONS;
2359
- const maxIterationsPerTask = agenticConfig?.maxIterationsPerTask ?? DEFAULT_MAX_ITERATIONS_PER_TASK;
2360
- const costWarningThreshold = agenticConfig?.costWarningThreshold ?? 5.0;
2361
- const costHardCap = agenticConfig?.costHardCap ?? 0;
2362
- const autoCompactPercent = agenticConfig?.autoCompactAtPercent ?? 80;
2363
- const stuckDetectionEnabled = agenticConfig?.stuckDetectionEnabled ?? true;
2364
- const stuckDetectionThreshold = agenticConfig?.stuckDetectionThreshold ?? 3;
2365
- // Get model context window for auto-compaction
2366
- const modelConfig = getModelConfig(client.getCurrentModelId());
2367
- const contextWindow = modelConfig?.contextWindow ?? 200000;
2368
- // Stuck detection tracking
2369
- const recentToolCalls = [];
2370
- let costWarningShown = false;
2371
- let lastProactiveCompactionIter = -Infinity; // Iteration when proactive compaction last ran
2372
- const PROACTIVE_COMPACT_MIN_INTERVAL = 10; // Minimum iterations between proactive compactions
2373
- let lastCompactionFailureTime = 0; // Cooldown tracking for compaction failures
2374
- const COMPACTION_COOLDOWN_MS = 30_000; // 30s cooldown after a compaction failure
2375
- // Helper: Re-inject active plan context after compaction so the LLM
2376
- // retains structured plan/task ID references that survive summarization.
2377
- const reinjectPlanContextAfterCompaction = async (msgs) => {
2378
- try {
2379
- const planPersistence = getPlanPersistence(projectRoot);
2380
- const activePlanId = planPersistence.getActivePlanId();
2381
- if (activePlanId) {
2382
- const pendingContext = await planPersistence.getPendingTasksContext(activePlanId);
2383
- if (pendingContext) {
2384
- // Append plan context to the compaction summary message to avoid
2385
- // message alternation issues. The summary is always the user message
2386
- // containing "[CONTEXT SUMMARY".
2387
- const summaryIdx = msgs.findIndex((m) => m.role === 'user' && typeof m.content === 'string' && m.content.includes('[CONTEXT SUMMARY'));
2388
- if (summaryIdx >= 0 && typeof msgs[summaryIdx].content === 'string') {
2389
- msgs[summaryIdx] = {
2390
- ...msgs[summaryIdx],
2391
- content: msgs[summaryIdx].content + '\n\n' + pendingContext.formattedContext,
2392
- };
2393
- }
2394
- else {
2395
- // No summary message found (e.g., crude truncation) — append as last user message
2396
- const lastUserIdx = msgs.reduce((acc, m, i) => m.role === 'user' ? i : acc, -1);
2397
- if (lastUserIdx >= 0 && typeof msgs[lastUserIdx].content === 'string') {
2398
- msgs[lastUserIdx] = {
2399
- ...msgs[lastUserIdx],
2400
- content: msgs[lastUserIdx].content + '\n\n' + pendingContext.formattedContext,
2401
- };
2402
- }
2403
- }
2404
- logger.info(`Re-injected plan context after compaction (plan: ${activePlanId}, ${pendingContext.completedTasks}/${pendingContext.totalTasks} done)`);
2405
- }
2406
- }
2407
- }
2408
- catch (planErr) {
2409
- logger.debug('Failed to re-inject plan context after compaction', { error: planErr });
2410
- }
2411
- };
2412
- // Agentic loop: call LLM, execute tools, feed results back
2413
- setSessionPhase('executing');
2414
- let iterations = 0;
2415
- let accumulatedText = '';
2416
- let finalResponse = '';
2417
- let reachedMaxIterations = false;
2418
- let planModeTriggered = false; // Track if enter_plan_mode tool was called
2419
- let fallbackCheckDone = false; // Only run fallback once
2420
- let stuckDetected = false;
2421
- let costCapExceeded = false;
2422
- let wasAborted = false;
2423
- while (iterations < maxIterations &&
2424
- (!isPlanMode || iterationScoper.shouldContinueIteration(iterations))) {
2425
- // Check abort flag at start of each iteration (set by Esc during clarification or double-Esc)
2426
- if (abortLoopRef.current) {
2427
- logger.info('Agentic loop aborted by user');
2428
- abortLoopRef.current = false; // Reset for next request
2429
- wasAborted = true;
2430
- break;
2431
- }
2432
- iterations++;
2433
- iterationScoper.incrementIteration();
2434
- logger.debug(`Agentic loop iteration ${iterations}`);
2435
- // SLIDING WINDOW CHECK: Compact if message count exceeds limit (first line of defense)
2436
- const compactionOnCooldown = lastCompactionFailureTime > 0 && (Date.now() - lastCompactionFailureTime) < COMPACTION_COOLDOWN_MS;
2437
- if (messages.length > SLIDING_WINDOW_MAX_MESSAGES) {
2438
- if (compactionOnCooldown) {
2439
- logger.debug('Sliding window compaction skipped (cooldown active)');
2440
- }
2441
- else {
2442
- logger.info(`Sliding window: ${messages.length} messages > ${SLIDING_WINDOW_MAX_MESSAGES}, compacting`);
2443
- try {
2444
- const compactor = await getConversationCompactor();
2445
- await compactor.initialize();
2446
- const compactionResult = await compactor.compactApiMessages(messages, 10, 6000);
2447
- if (compactionResult.messagesCompacted > 0) {
2448
- messages.length = 0;
2449
- messages.push(...compactionResult.compactedMessages);
2450
- const sessionManager = getSessionManager();
2451
- sessionManager.syncAfterCompaction(compactionResult.summary, compactionResult.compactedMessages.length - 2);
2452
- await reinjectPlanContextAfterCompaction(messages);
2453
- lastProactiveCompactionIter = iterations;
2454
- lastCompactionFailureTime = 0; // Reset on success
2455
- }
2456
- }
2457
- catch (compactError) {
2458
- logger.warn('Sliding window compaction failed', { error: compactError });
2459
- lastCompactionFailureTime = Date.now();
2460
- }
2461
- }
2462
- }
2463
- // PRE-API CHECK: Estimate message size and apply intelligent compaction if needed
2464
- // This prevents the 200k token limit error
2465
- const estimatedMessageTokens = estimateMessagesArrayTokens(messages);
2466
- const estimatedSystemTokens = estimateSystemPromptTokens(systemPrompt);
2467
- const estimatedTotalTokens = estimatedMessageTokens + estimatedSystemTokens + DEFAULT_MAX_TOKENS; // Reserve space for output
2468
- const maxAllowedTokens = Math.floor(contextWindow * (CONTEXT_WINDOW_SAFETY_MARGIN / 100));
2469
- const proactiveCompactThreshold = Math.floor(contextWindow * 0.70); // Trigger at 70% to avoid emergency
2470
- // Sync estimated context tokens with session for accurate compaction checks
2471
- const sessionManager = getSessionManager();
2472
- sessionManager.updateEstimatedContextTokens(estimatedMessageTokens);
2473
- // Log compaction evaluation on every 5th iteration or when approaching thresholds
2474
- const contextUsagePct = Math.round((estimatedTotalTokens / contextWindow) * 100);
2475
- if (iterations % 5 === 0 || contextUsagePct >= 50) {
2476
- logger.debug(`Compaction eval [iter ${iterations}]: ${estimatedTotalTokens.toLocaleString()} tokens (${contextUsagePct}% of ${contextWindow.toLocaleString()}) | ` +
2477
- `thresholds: proactive=${proactiveCompactThreshold.toLocaleString()} (70%), emergency=${maxAllowedTokens.toLocaleString()} (90%) | ` +
2478
- `messages=${messages.length}, itersSinceCompaction=${iterations - lastProactiveCompactionIter}`);
2479
- }
2480
- // Proactive compaction at 70% context usage (percentage-based only).
2481
- // Uses an iteration-based interval instead of a one-shot flag so compaction
2482
- // can re-trigger in long-running agentic loops that keep accumulating context.
2483
- const iterationsSinceLastCompaction = iterations - lastProactiveCompactionIter;
2484
- const shouldProactiveCompact = iterationsSinceLastCompaction >= PROACTIVE_COMPACT_MIN_INTERVAL &&
2485
- messages.length > 8 &&
2486
- estimatedTotalTokens > proactiveCompactThreshold;
2487
- if (shouldProactiveCompact) {
2488
- if (compactionOnCooldown) {
2489
- logger.debug('Proactive compaction skipped (cooldown active)');
2490
- }
2491
- else {
2492
- logger.info(`Proactive compaction triggered: ${contextUsagePct}% context usage`);
2493
- lastProactiveCompactionIter = iterations;
2494
- try {
2495
- const compactor = await getConversationCompactor();
2496
- await compactor.initialize();
2497
- const compactionResult = await compactor.compactApiMessages(messages, 10, 6000);
2498
- if (compactionResult.messagesCompacted > 0) {
2499
- logger.info(`Proactive compaction: ${compactionResult.tokensBefore} → ${compactionResult.tokensAfter} tokens (${compactionResult.messagesCompacted} messages compacted)`);
2500
- messages.length = 0;
2501
- messages.push(...compactionResult.compactedMessages);
2502
- // Sync with session to prevent state drift
2503
- const sessionManager = getSessionManager();
2504
- sessionManager.syncAfterCompaction(compactionResult.summary, compactionResult.compactedMessages.length - 2); // -2 for summary message and original
2505
- await reinjectPlanContextAfterCompaction(messages);
2506
- lastCompactionFailureTime = 0; // Reset on success
2507
- }
2508
- }
2509
- catch (compactError) {
2510
- logger.warn('Proactive compaction failed, will use emergency fallback if needed', { error: compactError });
2511
- lastCompactionFailureTime = Date.now();
2512
- }
2513
- }
2514
- }
2515
- // Emergency compaction at 90% - must succeed to continue
2516
- if (estimatedTotalTokens > maxAllowedTokens) {
2517
- logger.warn(`Pre-API check: estimated ${estimatedTotalTokens} tokens exceeds ${maxAllowedTokens} limit (${CONTEXT_WINDOW_SAFETY_MARGIN}% of ${contextWindow})`);
2518
- const minMessagesToKeep = 4;
2519
- if (messages.length > minMessagesToKeep) {
2520
- // Try intelligent compaction first
2521
- try {
2522
- const compactor = await getConversationCompactor();
2523
- await compactor.initialize();
2524
- const compactionResult = await compactor.compactApiMessages(messages, 6, 4000);
2525
- if (compactionResult.messagesCompacted > 0) {
2526
- const newEstimate = estimateMessagesArrayTokens(compactionResult.compactedMessages) + estimatedSystemTokens + DEFAULT_MAX_TOKENS;
2527
- if (newEstimate < maxAllowedTokens) {
2528
- logger.info(`Emergency intelligent compaction: ${compactionResult.tokensBefore} → ${compactionResult.tokensAfter} tokens`);
2529
- messages.length = 0;
2530
- messages.push(...compactionResult.compactedMessages);
2531
- // Sync with session to prevent state drift
2532
- const sessionManager = getSessionManager();
2533
- sessionManager.syncAfterCompaction(compactionResult.summary, compactionResult.compactedMessages.length - 2);
2534
- await reinjectPlanContextAfterCompaction(messages);
2535
- }
2536
- else {
2537
- // Compaction wasn't enough, fall back to crude truncation
2538
- throw new Error('Compaction insufficient');
2539
- }
2540
- }
2541
- else {
2542
- throw new Error('Nothing to compact');
2543
- }
2544
- }
2545
- catch (compactError) {
2546
- // Fall back to crude truncation as last resort
2547
- logger.warn('Emergency compaction failed, using crude truncation', { error: compactError });
2548
- const originalFirstMessage = messages[0];
2549
- const recentMessages = messages.slice(-Math.min(messages.length - 1, 4));
2550
- const truncatedMessages = [
2551
- originalFirstMessage,
2552
- {
2553
- role: 'user',
2554
- content: `[EMERGENCY: Context severely truncated. ${messages.length - recentMessages.length - 1} earlier messages removed. Key context may be lost - please ask if you need clarification on previous work.]`,
2555
- },
2556
- ...recentMessages,
2557
- ];
2558
- const newEstimate = estimateMessagesArrayTokens(truncatedMessages) + estimatedSystemTokens + DEFAULT_MAX_TOKENS;
2559
- if (newEstimate < maxAllowedTokens) {
2560
- logger.info(`Crude truncation: ${messages.length} -> ${truncatedMessages.length} messages`);
2561
- messages.length = 0;
2562
- messages.push(...truncatedMessages);
2563
- await reinjectPlanContextAfterCompaction(messages);
2564
- }
2565
- else {
2566
- // Absolute last resort - keep only original request
2567
- logger.error('All compaction strategies failed, keeping minimal context');
2568
- messages.length = 0;
2569
- messages.push(originalFirstMessage, {
2570
- role: 'user',
2571
- content: '[CRITICAL: All context removed due to size limits. Please summarize what you remember and ask for clarification.]',
2572
- });
2573
- await reinjectPlanContextAfterCompaction(messages);
2574
- }
2575
- }
2576
- }
2577
- }
2578
- // Create AbortController for this request so Esc can cancel immediately
2579
- abortControllerRef.current = new AbortController();
2580
- // Call Claude with tools
2581
- const response = await client.createAgenticMessage(messages, {
2582
- system: systemPrompt,
2583
- tools,
2584
- maxTokens: DEFAULT_MAX_TOKENS,
2585
- abortSignal: abortControllerRef.current.signal,
2586
- }, (chunk) => {
2587
- // Stream text chunks to UI (display handled by streamBlocksRef)
2588
- accumulatedText += chunk;
2589
- // Append text chunk using optimized in-place update (avoids full array copy)
2590
- appendTextChunk(chunk);
2591
- }, (toolEvent) => {
2592
- // Handle tool streaming events for real-time visibility
2593
- if (toolEvent.type === 'tool_start') {
2594
- // Initialize trackers for this tool
2595
- toolPartialJsonRef.current.set(toolEvent.id, '');
2596
- toolLastParamCountRef.current.set(toolEvent.id, 0);
2597
- // Add tool block in "generating" status as soon as we know the tool name
2598
- streamBlocksRef.current.push(createToolBlock(toolEvent.id, toolEvent.name, {}, 'generating'));
2599
- scheduleRender();
2600
- }
2601
- else if (toolEvent.type === 'tool_input_delta') {
2602
- // Accumulate partial JSON and extract available params for display
2603
- const currentJson = toolPartialJsonRef.current.get(toolEvent.id) || '';
2604
- const newJson = currentJson + toolEvent.partialInput;
2605
- toolPartialJsonRef.current.set(toolEvent.id, newJson);
2606
- // Debounce: only attempt parse every 50 chars to avoid calling
2607
- // JSON.parse() (which will throw) on every tiny delta event.
2608
- const lastParsedLen = toolLastParsedLenRef.current.get(toolEvent.id) || 0;
2609
- if (newJson.length - lastParsedLen < 50) {
2610
- // Skip parsing until enough new data accumulated
2611
- }
2612
- else {
2613
- toolLastParsedLenRef.current.set(toolEvent.id, newJson.length);
2614
- const partialParams = tryParsePartialJson(newJson);
2615
- const paramCount = Object.keys(partialParams).length;
2616
- const lastParamCount = toolLastParamCountRef.current.get(toolEvent.id) || 0;
2617
- // Only update if we have new params (param count increased)
2618
- if (paramCount > lastParamCount) {
2619
- toolLastParamCountRef.current.set(toolEvent.id, paramCount);
2620
- updateToolBlockById(toolEvent.id, { params: partialParams }, 'generating');
2621
- }
2622
- }
2623
- }
2624
- else if (toolEvent.type === 'tool_complete') {
2625
- // Clean up trackers
2626
- toolPartialJsonRef.current.delete(toolEvent.id);
2627
- toolLastParamCountRef.current.delete(toolEvent.id);
2628
- toolLastParsedLenRef.current.delete(toolEvent.id);
2629
- // Update tool block to "pending" with full params when generation complete
2630
- updateToolBlockById(toolEvent.id, { params: toolEvent.input, status: 'pending' });
2631
- }
2632
- });
2633
- logger.debug(`Response stop_reason: ${response.stopReason}, tool calls: ${response.toolUseBlocks.length}`);
2634
- // Accumulate usage and cost
2635
- totalUsage.inputTokens += response.usage.inputTokens;
2636
- totalUsage.outputTokens += response.usage.outputTokens;
2637
- totalUsage.totalTokens += response.usage.totalTokens;
2638
- totalCost.inputCost += response.cost.inputCost;
2639
- totalCost.outputCost += response.cost.outputCost;
2640
- totalCost.totalCost += response.cost.totalCost;
2641
- // Cost warning check (internal tracking only, not shown in UI)
2642
- if (!costWarningShown && totalCost.totalCost >= costWarningThreshold) {
2643
- logger.debug(`Agentic loop cost warning: $${totalCost.totalCost.toFixed(2)} exceeds threshold of $${costWarningThreshold.toFixed(2)}`);
2644
- costWarningShown = true;
2645
- }
2646
- // Cost hard cap check
2647
- if (costHardCap > 0 && totalCost.totalCost >= costHardCap) {
2648
- logger.warn(`Agentic loop stopped: cost hard cap of $${costHardCap.toFixed(2)} exceeded`);
2649
- costCapExceeded = true;
2650
- finalResponse = accumulatedText + `\n\n⛔ *Session stopped: Cost cap of $${costHardCap.toFixed(2)} reached*`;
2651
- break;
2652
- }
2653
- // Context window usage check for auto-compaction (post-API check using LAST call's input tokens, not cumulative)
2654
- const postApiContextUsagePct = (response.usage.inputTokens / contextWindow) * 100;
2655
- const postApiItersSinceCompaction = iterations - lastProactiveCompactionIter;
2656
- const shouldPostApiCompact = postApiItersSinceCompaction >= PROACTIVE_COMPACT_MIN_INTERVAL &&
2657
- postApiContextUsagePct >= autoCompactPercent;
2658
- if (shouldPostApiCompact) {
2659
- const postApiCooldown = lastCompactionFailureTime > 0 && (Date.now() - lastCompactionFailureTime) < COMPACTION_COOLDOWN_MS;
2660
- if (postApiCooldown) {
2661
- logger.debug('Post-API compaction skipped (cooldown active)');
2662
- }
2663
- else {
2664
- logger.info(`Post-API compaction triggered: ${postApiContextUsagePct.toFixed(1)}% context usage`);
2665
- lastProactiveCompactionIter = iterations;
2666
- try {
2667
- const compactor = await getConversationCompactor();
2668
- await compactor.initialize();
2669
- // Use API messages compaction directly since that's what's actually growing
2670
- const compactionResult = await compactor.compactApiMessages(messages, 10, 6000);
2671
- if (compactionResult.messagesCompacted > 0) {
2672
- logger.info(`Post-API compaction: ${compactionResult.tokensBefore} → ${compactionResult.tokensAfter} tokens`);
2673
- messages.length = 0;
2674
- messages.push(...compactionResult.compactedMessages);
2675
- // Sync with session
2676
- const sessionManager = getSessionManager();
2677
- sessionManager.syncAfterCompaction(compactionResult.summary, compactionResult.compactedMessages.length - 2);
2678
- await reinjectPlanContextAfterCompaction(messages);
2679
- lastCompactionFailureTime = 0; // Reset on success
2680
- }
2681
- }
2682
- catch (compactError) {
2683
- logger.warn('Post-API compaction failed', { error: compactError });
2684
- lastCompactionFailureTime = Date.now();
2685
- }
2686
- }
2687
- }
2688
- // Handle max_tokens - prompt continuation
2689
- if (response.stopReason === 'max_tokens') {
2690
- logger.warn('Response truncated due to max_tokens - prompting continuation');
2691
- agenticIterations.push({
2692
- iteration: iterations,
2693
- llm_call: {
2694
- messages_count: messages.length,
2695
- response_text: response.textContent,
2696
- tool_calls_count: 0,
2697
- usage: response.usage,
2698
- cost: response.cost,
2699
- },
2700
- tool_calls: [],
2701
- stop_reason: response.stopReason,
2702
- });
2703
- // Add the partial response to conversation and prompt to continue
2704
- messages.push({
2705
- role: 'assistant',
2706
- content: response.contentBlocks,
2707
- });
2708
- messages.push({
2709
- role: 'user',
2710
- content: 'Continue from where you left off. If you were about to use tools, please proceed with those tool calls now.',
2711
- });
2712
- continue; // Continue the loop to get the next part of the response
2713
- }
2714
- // If no tool calls, we're done
2715
- if (response.stopReason !== 'tool_use' || response.toolUseBlocks.length === 0) {
2716
- // Check queue before ending the loop — drain ALL queued messages at once
2717
- if (queuedMessagesRef.current.length > 0) {
2718
- // Separate slash commands from regular messages.
2719
- // Regular messages get batched; the first slash command (if any) breaks the loop after.
2720
- const allQueued = [...queuedMessagesRef.current];
2721
- const regularMessages = [];
2722
- let slashCommand = null;
2723
- for (const msg of allQueued) {
2724
- const parsed = parseInput(msg);
2725
- if (parsed.type === 'slash') {
2726
- slashCommand = msg;
2727
- break; // Stop — everything before this gets batched, slash runs after
2728
- }
2729
- regularMessages.push(msg);
2730
- }
2731
- // Remove consumed messages from the queue (regular + the slash command if found)
2732
- const consumed = regularMessages.length + (slashCommand ? 1 : 0);
2733
- queuedMessagesRef.current = queuedMessagesRef.current.slice(consumed);
2734
- setQueuedMessages([...queuedMessagesRef.current]);
2735
- // If we only got a slash command with no regular messages, handle it directly
2736
- if (regularMessages.length === 0 && slashCommand) {
2737
- logger.info(`Queued message is a slash command: ${slashCommand}, breaking loop to execute`);
2738
- agenticIterations.push({
2739
- iteration: iterations,
2740
- llm_call: {
2741
- messages_count: messages.length,
2742
- response_text: response.textContent,
2743
- tool_calls_count: 0,
2744
- usage: response.usage,
2745
- cost: response.cost,
2746
- },
2747
- tool_calls: [],
2748
- stop_reason: 'queue_drain_slash',
2749
- });
2750
- setTimeout(() => handleSubmit(slashCommand), 0);
2751
- break;
2752
- }
2753
- // Batch all regular messages into a single well-formatted prompt
2754
- const combinedMessage = regularMessages.length === 1
2755
- ? regularMessages[0]
2756
- : regularMessages.map((msg, i) => `${i + 1}. ${msg}`).join('\n');
2757
- // Add the LLM's response to messages
2758
- messages.push({
2759
- role: 'assistant',
2760
- content: response.contentBlocks,
2761
- });
2762
- // Add combined queued message to conversation display
2763
- const now = new Date();
2764
- setConversation((prev) => [...prev,
2765
- { id: `assistant-${Date.now()}`, role: 'assistant', content: response.textContent, timestamp: now },
2766
- { id: `user-queued-${Date.now()}`, role: 'user', content: combinedMessage, timestamp: now },
2767
- ]);
2768
- // Add user message to API messages with reinforcement framing
2769
- const preamble = regularMessages.length === 1
2770
- ? 'The user has provided additional input while you were working. This is likely related to your current task — treat it as a follow-up instruction, correction, or new insight. Integrate it into your ongoing work naturally.'
2771
- : `The user has provided ${regularMessages.length} additional messages while you were working. These are likely related to your current task — treat them as follow-up instructions, corrections, or new insights. Address all of them together in your ongoing work.`;
2772
- const framedMessage = `<system-reminder>${preamble}</system-reminder>\n\n${combinedMessage}`;
2773
- messages.push({ role: 'user', content: framedMessage });
2774
- // Reset stream blocks for the new response
2775
- resetStreamBlocks();
2776
- logger.info(`Injected ${regularMessages.length} queued message(s) as batch: "${combinedMessage.substring(0, 80)}..."`);
2777
- // Record iteration metrics before continuing
2778
- agenticIterations.push({
2779
- iteration: iterations,
2780
- llm_call: {
2781
- messages_count: messages.length,
2782
- response_text: response.textContent,
2783
- tool_calls_count: 0,
2784
- usage: response.usage,
2785
- cost: response.cost,
2786
- },
2787
- tool_calls: [],
2788
- stop_reason: 'queue_drain',
2789
- });
2790
- // If there's a slash command waiting after the regular messages,
2791
- // schedule it to run after this loop iteration completes
2792
- if (slashCommand) {
2793
- const cmd = slashCommand;
2794
- setTimeout(() => handleSubmit(cmd), 0);
2795
- // Don't break yet — continue so the LLM processes the batched messages first
2796
- }
2797
- continue; // Continue loop with the batched message
2798
- }
2799
- // Log final iteration (no tool calls)
2800
- agenticIterations.push({
2801
- iteration: iterations,
2802
- llm_call: {
2803
- messages_count: messages.length,
2804
- response_text: response.textContent,
2805
- tool_calls_count: 0,
2806
- usage: response.usage,
2807
- cost: response.cost,
2808
- },
2809
- tool_calls: [],
2810
- stop_reason: response.stopReason,
2811
- });
2812
- finalResponse = response.textContent;
2813
- break;
2814
- }
2815
- // Execute tool calls
2816
- const { results: toolResults, records: toolRecords, userRejected, endTurn } = await executeToolCalls(response.toolUseBlocks);
2817
- totalToolCalls += toolRecords.length;
2818
- // Stuck detection: check if we're calling the same tools with the same inputs repeatedly
2819
- if (stuckDetectionEnabled && response.toolUseBlocks.length > 0) {
2820
- for (const toolUse of response.toolUseBlocks) {
2821
- // Create a SHA-256 hash of the full tool input for accurate comparison
2822
- // Using full hash ensures different content in the same file is detected
2823
- const inputHash = sha256Hash(JSON.stringify(toolUse.input || {}));
2824
- recentToolCalls.push({ name: toolUse.name, inputHash });
2825
- }
2826
- // Keep only the last N * threshold calls for comparison
2827
- const maxTracked = stuckDetectionThreshold * 3;
2828
- while (recentToolCalls.length > maxTracked) {
2829
- recentToolCalls.shift();
2830
- }
2831
- // Check for stuck pattern: same tool call repeated consecutively
2832
- if (recentToolCalls.length >= stuckDetectionThreshold) {
2833
- const lastCalls = recentToolCalls.slice(-stuckDetectionThreshold);
2834
- const firstCall = lastCalls[0];
2835
- const isStuck = lastCalls.every((call) => call.name === firstCall.name && call.inputHash === firstCall.inputHash);
2836
- if (isStuck) {
2837
- logger.warn(`Stuck detection triggered: tool "${firstCall.name}" called ${stuckDetectionThreshold} times with same input`);
2838
- stuckDetected = true;
2839
- finalResponse = accumulatedText + `\n\n⚠️ *Stuck detected: The same operation was attempted ${stuckDetectionThreshold} times. Consider rephrasing your request or providing more context.*`;
2840
- break;
2841
- }
2842
- }
2843
- // Early warning: inject guidance on 2nd consecutive identical attempt (before stuck triggers)
2844
- // This gives the LLM a chance to try a different approach
2845
- const warningThreshold = stuckDetectionThreshold - 1;
2846
- if (warningThreshold >= 2 && recentToolCalls.length >= warningThreshold) {
2847
- const lastCallsForWarning = recentToolCalls.slice(-warningThreshold);
2848
- const firstCallForWarning = lastCallsForWarning[0];
2849
- const isRepeating = lastCallsForWarning.every((call) => call.name === firstCallForWarning.name && call.inputHash === firstCallForWarning.inputHash);
2850
- if (isRepeating) {
2851
- logger.info(`Early stuck warning: tool "${firstCallForWarning.name}" called ${warningThreshold} times with same input`);
2852
- // Inject warning message to the LLM in the next iteration
2853
- messages.push({
2854
- role: 'user',
2855
- content: `⚠️ SYSTEM WARNING: You have attempted the same operation (${firstCallForWarning.name}) ${warningThreshold} times with identical parameters. The operation appears to be failing repeatedly. Please try a DIFFERENT approach - consider: using different parameters, trying an alternative tool, or explaining what's blocking you so we can help. One more identical attempt will trigger stuck detection and terminate execution.`,
2856
- });
2857
- }
2858
- }
2859
- }
2860
- // Check if enter_plan_mode was called
2861
- let planJustApproved = false;
2862
- if (response.toolUseBlocks.some((tool) => tool.name === 'enter_plan_mode')) {
2863
- planModeTriggered = true;
2864
- logger.debug('Plan mode tool was called by LLM');
2865
- // Check if plan was approved and execution started
2866
- // activePlanId is only set when startExecution() is called (after user approval)
2867
- const planPersistence = getPlanPersistence(projectRoot);
2868
- const activePlanId = planPersistence.getActivePlanId();
2869
- if (activePlanId) {
2870
- isPlanMode = true;
2871
- iterationScoper.startPlanExecution(activePlanId, maxIterationsPerTask);
2872
- logger.info(`Started per-task iteration tracking for plan ${activePlanId} with ${maxIterationsPerTask} iterations per task`);
2873
- planJustApproved = true;
2874
- }
2875
- else {
2876
- // Expected when plan was clarified, edited, rejected, or errored
2877
- // The LLM may call enter_plan_mode again after clarification
2878
- logger.debug('Plan mode tool called but no active execution (plan may need clarification/modification)');
2879
- }
2880
- }
2881
- // If user rejected an operation, log it but continue the loop
2882
- // The LLM will see the rejection error in the tool result and can decide what to do
2883
- if (userRejected) {
2884
- logger.info('User rejected operation - continuing agentic loop so LLM can respond');
2885
- }
2886
- // If a plan was cancelled, end the turn immediately — don't let the LLM continue
2887
- if (endTurn) {
2888
- logger.info('End turn signalled (plan cancelled) — stopping agentic loop');
2889
- finalResponse = accumulatedText;
2890
- break;
2891
- }
2892
- // Log this iteration
2893
- agenticIterations.push({
2894
- iteration: iterations,
2895
- llm_call: {
2896
- messages_count: messages.length,
2897
- response_text: response.textContent,
2898
- tool_calls_count: response.toolUseBlocks.length,
2899
- usage: response.usage,
2900
- cost: response.cost,
2901
- },
2902
- tool_calls: toolRecords,
2903
- stop_reason: response.stopReason,
2904
- });
2905
- // Add assistant response (with tool use) to messages
2906
- messages.push({
2907
- role: 'assistant',
2908
- content: response.contentBlocks,
2909
- });
2910
- // Add tool results as user message
2911
- messages.push({
2912
- role: 'user',
2913
- content: toolResults,
2914
- });
2915
- // Add explicit continuation instruction after plan approval
2916
- // This ensures the LLM continues with tool calls instead of just responding with text
2917
- if (planJustApproved) {
2918
- if (planApprovalClearContextRef.current) {
2919
- // Full clear: keep only the last 2 messages (enter_plan_mode tool_use + plan result)
2920
- const lastTwo = messages.slice(-2);
2921
- messages.length = 0;
2922
- messages.push(...lastTwo);
2923
- planApprovalClearContextRef.current = false;
2924
- setMetrics((prev) => ({ ...prev, currentContextTokens: 0 }));
2925
- logger.info('Plan approval full context clear: messages reduced to 2');
2926
- }
2927
- else {
2928
- // Partial compaction: keep last 3 messages + LLM-generated summary
2929
- try {
2930
- const compactor = await getConversationCompactor();
2931
- await compactor.initialize();
2932
- const compactionResult = await compactor.compactForPlanApproval(messages, 3);
2933
- if (compactionResult.messagesCompacted > 0) {
2934
- messages.length = 0;
2935
- messages.push(...compactionResult.compactedMessages);
2936
- const sessionManager = getSessionManager();
2937
- sessionManager.syncAfterCompaction(compactionResult.summary, compactionResult.compactedMessages.length - 2);
2938
- await reinjectPlanContextAfterCompaction(messages);
2939
- logger.info(`Plan approval compaction: ${compactionResult.tokensBefore} → ${compactionResult.tokensAfter} tokens`);
2940
- }
2941
- }
2942
- catch (compactError) {
2943
- logger.warn('Plan approval compaction failed, continuing with full context', { error: compactError });
2944
- }
2945
- }
2946
- messages.push({
2947
- role: 'user',
2948
- content: 'Plan approved by user. Proceed immediately with executing Task 1 using the appropriate tools. Do not ask for confirmation - begin implementation now.',
2949
- });
2950
- logger.debug('Added plan continuation instruction to messages');
2951
- }
2952
- // Update accumulated text (display handled by streamBlocksRef)
2953
- accumulatedText = response.textContent;
2954
- // Plan mode fallback check: after first iteration, if LLM didn't call enter_plan_mode,
2955
- // run heuristic check and suggest plan mode if complexity is high
2956
- // Only run in HYBRID mode (PLAN mode already forces planning, DIRECT skips all planning)
2957
- if (executionMode === 'hybrid' && iterations === 1 && !planModeTriggered && !fallbackCheckDone) {
2958
- fallbackCheckDone = true;
2959
- try {
2960
- // Get personality mode for consistent classifier tone
2961
- const sessionMgrFallback = getSessionManager();
2962
- const fallbackPersonality = sessionMgrFallback.getEffectivePersonalityMode();
2963
- // Convert conversation history to Message format for analyzer context
2964
- const conversationForFallback = conversation
2965
- .filter(entry => entry.role === 'user' || entry.role === 'assistant')
2966
- .map(entry => ({
2967
- role: entry.role,
2968
- content: entry.content
2969
- }));
2970
- const fallbackResult = await checkPlanModeFallback(input, // Original user input
2971
- planModeTriggered, projectRoot, fallbackPersonality, abortControllerRef.current?.signal, // Pass abort signal for cancellation
2972
- conversationForFallback // Pass conversation history for context
2973
- );
2974
- if (fallbackResult.shouldSuggestPlanMode && fallbackResult.suggestedPrompt) {
2975
- logger.info('Plan mode fallback triggered - suggesting plan mode to LLM', {
2976
- confidence: fallbackResult.confidence,
2977
- detectedMode: fallbackResult.detectedMode,
2978
- });
2979
- // Inject system suggestion into the conversation
2980
- messages.push({
2981
- role: 'user',
2982
- content: fallbackResult.suggestedPrompt,
2983
- });
2984
- }
2985
- }
2986
- catch (error) {
2987
- // Check if this was an abort (user cancelled)
2988
- const errorMessage = error instanceof Error ? error.message : String(error);
2989
- const isAbort = errorMessage === 'Request aborted' ||
2990
- (error instanceof Error && error.name === 'AbortError') ||
2991
- abortLoopRef.current;
2992
- if (isAbort) {
2993
- logger.debug('Plan mode fallback check cancelled by user');
2994
- wasAborted = true;
2995
- break; // Exit the loop
2996
- }
2997
- logger.warn('Plan mode fallback check failed', { error });
2998
- // Continue without suggestion on error
2999
- }
3000
- }
3001
- // Check iteration limits at end of iteration
3002
- if (iterations >= maxIterations && !stuckDetected && !costCapExceeded) {
3003
- // Global iteration limit reached (not plan mode specific)
3004
- logger.warn('Agentic loop reached global maximum iterations');
3005
- finalResponse = accumulatedText + '\n\n*(Reached maximum tool iterations)*';
3006
- reachedMaxIterations = true;
3007
- break;
3008
- }
3009
- else if (isPlanMode && !iterationScoper.shouldContinueIteration(iterations)) {
3010
- // Task-specific iteration limit reached in plan mode
3011
- const status = iterationScoper.getIterationStatus();
3012
- logger.warn(`Task ${status.task} reached iteration limit (${status.iteration}/${status.max})`);
3013
- // Get plan details to check if there are more tasks
3014
- const planPersistence = getPlanPersistence(projectRoot);
3015
- const activePlanId = planPersistence.getActivePlanId();
3016
- const executionSummary = activePlanId ? await planPersistence.getExecutionSummary(activePlanId) : null;
3017
- if (executionSummary && executionSummary.pending > 0) {
3018
- // There are more tasks - prompt to continue with next task
3019
- logger.info(`${executionSummary.pending} tasks remaining after iteration limit`);
3020
- // Add continuation prompt to move to next task
3021
- const continuationPrompt = `\n\n*Task iteration limit reached (${status.iteration}/${status.max}). ${executionSummary.completed} of ${executionSummary.total} tasks completed.*\n\nPlease move to the next pending task and continue execution.`;
3022
- accumulatedText += continuationPrompt;
3023
- // Update stream blocks with continuation message
3024
- updateStreamBlocks((prev) => {
3025
- const lastBlock = prev[prev.length - 1];
3026
- if (lastBlock && lastBlock.type === 'text') {
3027
- return [...prev.slice(0, -1), { ...lastBlock, content: lastBlock.content + continuationPrompt }];
3028
- }
3029
- return [...prev, createTextBlock(continuationPrompt)];
3030
- });
3031
- // Note: response.contentBlocks was already added to messages earlier in the iteration
3032
- // Just add the continuation prompt as a user message
3033
- messages.push({
3034
- role: 'user',
3035
- content: `The current task has used its iteration budget. Please proceed with the next task from the plan. Use complete_plan_task to mark the current task status, then move to the next task.`,
3036
- });
3037
- // Reset task iterations for next task (will be done automatically by event)
3038
- // Continue the loop
3039
- continue;
3040
- }
3041
- else {
3042
- // No more tasks or couldn't get summary - end execution
3043
- const iterationLimitMessage = `\n\n*(Task "${status.task}" reached iteration limit: ${status.iteration}/${status.max}. Type "continue" to resume work if the task is not yet complete.)*`;
3044
- finalResponse = accumulatedText + iterationLimitMessage;
3045
- // Ensure the message is displayed to the user
3046
- updateStreamBlocks((prev) => {
3047
- const lastBlock = prev[prev.length - 1];
3048
- if (lastBlock && lastBlock.type === 'text') {
3049
- return [...prev.slice(0, -1), { ...lastBlock, content: lastBlock.content + iterationLimitMessage }];
3050
- }
3051
- return [...prev, createTextBlock(iterationLimitMessage)];
3052
- });
3053
- reachedMaxIterations = true;
3054
- break;
3055
- }
3056
- }
3057
- }
3058
- // Handle user cancellation: rollback conversation state and clean up UI
3059
- // This removes the user's prompt so they can submit a corrected version
3060
- if (wasAborted) {
3061
- logger.info('Rolling back conversation state after user cancellation');
3062
- setConversation(conversationCheckpointRef.current);
3063
- resetStreamBlocks();
3064
- // Exit without adding any messages - finally block still runs for cleanup
3065
- return;
3066
- }
3067
- // Log the complete agentic loop
3068
- const loopDuration = Date.now() - loopStartTime;
3069
- // Collect per-task iteration data if in plan mode
3070
- const taskIterationData = isPlanMode
3071
- ? Array.from(iterationScoper.getTaskIterationHistory().entries()).map(([taskId, record]) => ({
3072
- taskId,
3073
- taskName: taskId, // TODO: Could enrich with actual task name from plan
3074
- iterations: record.iterations,
3075
- toolCalls: 0, // TODO: Could track tool calls per task
3076
- }))
3077
- : undefined;
3078
- await auditLogger.logAgenticLoop({
3079
- user_input: input,
3080
- system_prompt: systemPrompt,
3081
- model: client.getCurrentModel().id,
3082
- iterations: agenticIterations,
3083
- final_response: finalResponse,
3084
- total_iterations: iterations,
3085
- total_tool_calls: totalToolCalls,
3086
- total_usage: totalUsage,
3087
- total_cost: totalCost,
3088
- duration_ms: loopDuration,
3089
- reached_max_iterations: reachedMaxIterations,
3090
- isPlanMode,
3091
- planId: isPlanMode ? iterationScoper.getCurrentPlanId() || undefined : undefined,
3092
- task_iterations: taskIterationData,
3093
- });
3094
- // Expose to `finally` for post-response question detection
3095
- finalResponseForSuggestion = finalResponse;
3096
- // Add final assistant response to conversation (skip if empty - e.g., user rejected)
3097
- if (finalResponse) {
3098
- // Capture stream blocks before they get reset (for history display)
3099
- const capturedBlocks = [...streamBlocksRef.current];
3100
- const assistantEntry = {
3101
- id: `assistant-${Date.now()}`,
3102
- role: 'assistant',
3103
- content: finalResponse,
3104
- timestamp: new Date(),
3105
- blocks: capturedBlocks.length > 0 ? capturedBlocks : undefined,
3106
- durationMs: loopDuration,
3107
- };
3108
- setConversation((prev) => [...prev, assistantEntry]);
3109
- // Save to session manager
3110
- const sessionManager = getSessionManager();
3111
- sessionManager.addMessage({ role: 'user', content: input });
3112
- sessionManager.addMessage({ role: 'assistant', content: finalResponse });
3113
- }
3114
- }
3115
- catch (err) {
3116
- // Handle abort errors gracefully - don't show as error since user cancelled
3117
- if (err instanceof Error && (err.message === 'Request aborted' || err.name === 'AbortError')) {
3118
- logger.info('Request cancelled by user');
3119
- // Rollback conversation state on abort
3120
- setConversation(conversationCheckpointRef.current);
3121
- resetStreamBlocks();
3122
- }
3123
- else {
3124
- const errorMessage = err instanceof Error ? err.message : 'Failed to get response';
3125
- setError(errorMessage);
3126
- logger.error('API request failed', err);
3127
- await auditLogger.logError(errorMessage, err);
3128
- }
3129
- }
3130
- finally {
3131
- setIsProcessing(false);
3132
- setSessionPhase('idle');
3133
- // Clear complexity context after request completes
3134
- approvalManager.clearComplexityContext();
3135
- // Detect if the LLM ended with a question and suggest a short reply
3136
- setSuggestionOffer(deriveQuestionSuggestion(finalResponseForSuggestion));
3137
- // Clear temporary personality mode after response completes
3138
- const sessionMgrForCleanup = getSessionManager();
3139
- if (sessionMgrForCleanup.hasTemporaryPersonalityMode()) {
3140
- sessionMgrForCleanup.clearTemporaryPersonalityMode();
3141
- logger.debug('Temporary personality mode cleared after response');
3142
- }
3143
- // Stop iteration scoping if plan mode was active
3144
- if (isPlanMode) {
3145
- iterationScoper.stopPlanExecution();
3146
- }
3147
- // Pause the plan execution tracker when processing ends but plan is not complete
3148
- // This hides the tracker when the LLM asks a follow-up question
3149
- {
3150
- const prev = activePlanRef.current;
3151
- if (prev && !prev.isComplete) {
3152
- activePlanRef.current = { ...prev, isPaused: true };
3153
- }
3154
- setActivePlan(activePlanRef.current);
3155
- }
3156
- }
3157
- }, [conversation, projectRoot, projectSummary, executionMode]);
3158
- /**
3159
- * Routes prompt submissions: queues during processing, submits directly when idle
3160
- * Uses refs to keep the callback identity stable across renders, preventing
3161
- * React.memo on Prompt from breaking during the agentic loop (which would cause
3162
- * full Prompt re-renders on every scheduleRender tick, blocking stdin events).
3163
- */
3164
- const isProcessingRef = useRef(isProcessing);
3165
- isProcessingRef.current = isProcessing;
3166
- const handleSubmitRef = useRef(handleSubmit);
3167
- handleSubmitRef.current = handleSubmit;
3168
- const handlePromptSubmit = useCallback((input) => {
3169
- if (isProcessingRef.current) {
3170
- const trimmed = input.trim();
3171
- if (!trimmed)
3172
- return;
3173
- // Slash commands cannot be queued — show feedback instead of silently dropping
3174
- if (trimmed.startsWith('/')) {
3175
- setError(`Slash commands cannot be queued while processing. Wait for the current operation to complete, then retry: ${trimmed.length > 60 ? trimmed.substring(0, 60) + '...' : trimmed}`);
3176
- return;
3177
- }
3178
- queuedMessagesRef.current = [...queuedMessagesRef.current, trimmed];
3179
- setQueuedMessages([...queuedMessagesRef.current]);
3180
- return;
3181
- }
3182
- handleSubmitRef.current(input);
3183
- }, []); // Stable identity — never changes
3184
- /**
3185
- * Handles approval dialog choices
3186
- */
3187
- const handleApprovalChoice = useCallback((choice, userFeedback) => {
3188
- const approvalManager = getApprovalManager();
3189
- const request = approvalManager.getPendingRequest();
3190
- // Update modalActiveRef synchronously BEFORE resolving the approval
3191
- // This ensures that scheduleRender won't skip renders for tool results
3192
- // that complete immediately after approval (before useEffect runs)
3193
- modalActiveRef.current = false;
3194
- // Handle approve_all_session: enable session auto-approval and treat as 'approve'
3195
- if (choice === 'approve_all_session') {
3196
- approvalManager.enableSessionAutoApproval();
3197
- // Also switch to SMART mode for consistency with /smart switch
3198
- const sessionManager = getSessionManager();
3199
- approvalManager.setMode(ApprovalMode.SMART);
3200
- sessionManager.setApprovalMode(ApprovalMode.SMART);
3201
- setApprovalMode(ApprovalMode.SMART);
3202
- approvalManager.resolveRequest('approve');
3203
- setPendingApproval(null);
3204
- logger.debug('Operation approved - Auto-approving all changes this session (SMART mode)');
3205
- return;
3206
- }
3207
- // Handle approve_command_type: approve this command prefix for the session
3208
- if (choice === 'approve_command_type') {
3209
- const command = request?.details.command;
3210
- if (command) {
3211
- approvalManager.approveCommandPrefix(command);
3212
- const prefix = approvalManager.getCommandPrefix(command);
3213
- approvalManager.resolveRequest('approve');
3214
- setPendingApproval(null);
3215
- logger.debug(`Operation approved - "${prefix}" commands will be auto-approved this session`);
3216
- return;
3217
- }
3218
- }
3219
- // Pass user feedback as reason if provided
3220
- const reason = userFeedback && userFeedback.trim()
3221
- ? `User feedback: ${userFeedback}`
3222
- : undefined;
3223
- approvalManager.resolveRequest(choice, reason);
3224
- setPendingApproval(null);
3225
- if (choice === 'approve') {
3226
- logger.debug('Operation approved');
3227
- }
3228
- else if (choice === 'reject') {
3229
- // Log rejection details at debug level - user already knows they rejected it
3230
- const operationType = request?.type || 'operation';
3231
- const affectedFiles = request?.details.filePaths?.join(', ') || 'unknown';
3232
- logger.debug(`Operation rejected - type: ${operationType}, files: ${affectedFiles}`);
3233
- if (userFeedback && userFeedback.trim()) {
3234
- logger.debug(`User feedback: ${userFeedback}`);
3235
- }
3236
- }
3237
- else if (choice === 'cancel') {
3238
- logger.debug('Operation cancelled - NO changes were made');
3239
- }
3240
- }, []);
3241
- /**
3242
- * Handles plan approval dialog choices
3243
- */
3244
- const handlePlanApprovalChoice = useCallback((choice, input) => {
3245
- const approvalManager = getApprovalManager();
3246
- // Update modalActiveRef synchronously to allow immediate renders after approval
3247
- modalActiveRef.current = false;
3248
- // Helper: build task list preserving completed status for plan updates
3249
- const buildActivePlan = (request) => {
3250
- if (!request)
3251
- return;
3252
- const completedSet = new Set(request.completedTaskIds || []);
3253
- setPlanTrackerMode('compact');
3254
- activePlanRef.current = {
3255
- planId: request.plan.id,
3256
- title: request.plan.problemStatement,
3257
- tasks: request.plan.tasks.map((task) => ({
3258
- task,
3259
- status: completedSet.has(task.id) ? 'completed' : 'pending',
3260
- })),
3261
- isComplete: false,
3262
- success: false,
3263
- };
3264
- setActivePlan(activePlanRef.current);
3265
- };
3266
- if (choice === 'approve') {
3267
- // Initialize plan execution tracker with the approved plan
3268
- buildActivePlan(pendingPlanApproval);
3269
- // Always clear context on plan approval so execution starts fresh with only the plan
3270
- planApprovalClearContextRef.current = true;
3271
- approvalManager.resolvePlanApproval('approve');
3272
- setPendingPlanApproval(null);
3273
- }
3274
- else if (choice === 'approve-compact') {
3275
- buildActivePlan(pendingPlanApproval);
3276
- planApprovalClearContextRef.current = true;
3277
- approvalManager.resolvePlanApproval('approve');
3278
- setPendingPlanApproval(null);
3279
- }
3280
- else if (choice === 'approve-auto-edits') {
3281
- buildActivePlan(pendingPlanApproval);
3282
- // Always clear context on plan approval so execution starts fresh with only the plan
3283
- planApprovalClearContextRef.current = true;
3284
- approvalManager.enableFileEditsAutoApproval();
3285
- approvalManager.resolvePlanApproval('approve');
3286
- setPendingPlanApproval(null);
3287
- }
3288
- else if (choice === 'clarify') {
3289
- approvalManager.resolvePlanApproval('clarify', undefined, input);
3290
- setPendingPlanApproval(null);
3291
- logger.output('Clarification requested - returning to planning');
3292
- }
3293
- else if (choice === 'edit') {
3294
- approvalManager.resolvePlanApproval('edit', input, undefined);
3295
- setPendingPlanApproval(null);
3296
- logger.output('Plan modification requested - updating plan');
3297
- }
3298
- else if (choice === 'reject') {
3299
- approvalManager.resolvePlanApproval('reject');
3300
- setPendingPlanApproval(null);
3301
- }
3302
- }, [pendingPlanApproval]);
3303
- /**
3304
- * Handles clarification wizard completion
3305
- */
3306
- const handleClarificationComplete = useCallback((answers) => {
3307
- const approvalManager = getApprovalManager();
3308
- // Update modalActiveRef synchronously to allow immediate renders after completion
3309
- modalActiveRef.current = false;
3310
- approvalManager.resolveClarificationWizard(answers);
3311
- setPendingClarification(null);
3312
- logger.debug('✅ Clarification completed');
3313
- }, []);
3314
- /**
3315
- * Handles clarification wizard cancellation
3316
- */
3317
- const handleClarificationCancel = useCallback(() => {
3318
- const approvalManager = getApprovalManager();
3319
- // Update modalActiveRef synchronously to allow immediate renders after cancellation
3320
- modalActiveRef.current = false;
3321
- approvalManager.resolveClarificationWizard(null);
3322
- setPendingClarification(null);
3323
- abortLoopRef.current = true; // Signal agentic loop to stop
3324
- logger.output('Clarification cancelled - returning to prompt');
3325
- }, []);
3326
- /**
3327
- * Handles execution mode selection from the mode selector
3328
- */
3329
- const handleModeSelect = useCallback((mode) => {
3330
- const sessionManager = getSessionManager();
3331
- setExecutionMode(mode);
3332
- sessionManager.setExecutionMode(mode);
3333
- setActiveOverlay('none');
3334
- // Show confirmation message
3335
- const modeDescriptions = {
3336
- hybrid: 'LLM decides when to use planning based on complexity',
3337
- direct: 'Pure agentic execution, no planning',
3338
- plan: 'Always forces planning for every task'
3339
- };
3340
- const modeAliases = {
3341
- hybrid: 'h',
3342
- direct: 'd',
3343
- plan: 'p'
3344
- };
3345
- const color = mode === 'hybrid' ? chalk.cyan : mode === 'direct' ? chalk.yellow : chalk.magenta;
3346
- logger.output('');
3347
- logger.output(chalk.green('✓') + ` Switched to ${color.bold(mode.toUpperCase())} ${chalk.gray(`(${modeAliases[mode]})`)}`);
3348
- logger.output(` ${chalk.gray(modeDescriptions[mode])}`);
3349
- logger.output('');
3350
- }, []);
3351
- /**
3352
- * Handles mode selector cancellation
3353
- */
3354
- const handleModeCancel = useCallback(() => {
3355
- setActiveOverlay('none');
3356
- }, []);
3357
- /**
3358
- * Performs the actual logout operation
3359
- */
3360
- const performLogout = useCallback(async () => {
3361
- try {
3362
- const { getCredentialStore } = await import('../services/credential-store.js');
3363
- const credentialStore = await getCredentialStore();
3364
- logger.output('');
3365
- logger.output(chalk.gray('Clearing credentials...'));
3366
- const hasCompassKey = await credentialStore.hasCompassApiKey();
3367
- const hasAnthropicKey = await credentialStore.hasApiKey();
3368
- let compassCleared = true;
3369
- let anthropicCleared = true;
3370
- if (hasCompassKey) {
3371
- compassCleared = await credentialStore.deleteCompassApiKey();
3372
- }
3373
- if (hasAnthropicKey) {
3374
- anthropicCleared = await credentialStore.deleteApiKey();
3375
- }
3376
- // Clear provider-specific keys
3377
- const providerNames = ['ollama', 'zai', 'minimax'];
3378
- const providerResults = {};
3379
- for (const provider of providerNames) {
3380
- if (await credentialStore.hasProviderApiKey(provider)) {
3381
- providerResults[provider] = await credentialStore.deleteProviderApiKey(provider);
3382
- }
3383
- }
3384
- const allProvidersCleared = Object.values(providerResults).every(v => v);
3385
- const allCleared = compassCleared && anthropicCleared && allProvidersCleared;
3386
- if (allCleared) {
3387
- logger.output('');
3388
- logger.output(chalk.green.bold('✅ Logged out successfully'));
3389
- logger.output(chalk.gray('All API keys have been cleared from secure storage.'));
3390
- logger.output('');
3391
- logger.output(chalk.gray('Exiting Compass...'));
3392
- logger.output('');
3393
- logger.info('User logged out - all API keys cleared - exiting');
3394
- // Exit via the proper Ink exit flow (avoids yoga WASM use-after-free)
3395
- await onExit();
3396
- }
3397
- else {
3398
- logger.output('');
3399
- logger.output(chalk.yellow('⚠️ Partial logout'));
3400
- logger.output(chalk.gray('Some credentials could not be cleared. See logs for details.'));
3401
- logger.output('');
3402
- logger.warn('Partial logout - some credentials may not have been cleared', {
3403
- compassCleared,
3404
- anthropicCleared,
3405
- providerResults,
3406
- });
3407
- // Still exit even on partial logout
3408
- logger.output(chalk.gray('Exiting Compass...'));
3409
- logger.output('');
3410
- await onExit();
3411
- }
3412
- }
3413
- catch (error) {
3414
- logger.output('');
3415
- logger.output(chalk.red('❌ Logout failed'));
3416
- if (error instanceof Error) {
3417
- logger.output(chalk.red(` ${error.message}`));
3418
- }
3419
- logger.output('');
3420
- logger.error('Logout failed', error);
3421
- }
3422
- }, []);
3423
- /**
3424
- * Handles logout confirmation from the selector
3425
- */
3426
- const handleLogoutConfirm = useCallback(() => {
3427
- setActiveOverlay('none');
3428
- performLogout();
3429
- }, [performLogout]);
3430
- /**
3431
- * Handles logout cancellation from the selector
3432
- */
3433
- const handleLogoutCancel = useCallback(() => {
3434
- setActiveOverlay('none');
3435
- logger.output('');
3436
- logger.output(chalk.gray('Logout cancelled.'));
3437
- logger.output('');
3438
- }, []);
3439
- /**
3440
- * Handles rating submission
3441
- */
3442
- const handleRatingSubmit = useCallback(async (rating) => {
3443
- const ratingService = getRatingService();
3444
- try {
3445
- const result = await ratingService.submitRating(rating);
3446
- if (result.success && ratingStateManagerRef.current) {
3447
- await ratingStateManagerRef.current.recordRatingSubmitted();
3448
- }
3449
- logger.debug('[Rating] Rating submitted', { rating, success: result.success });
3450
- }
3451
- catch (error) {
3452
- logger.debug('[Rating] Failed to submit rating', error);
3453
- }
3454
- setShowRatingPanel(false);
3455
- ratingPanelShownRef.current = false;
3456
- }, []);
3457
- /**
3458
- * Handles rating panel close without submission
3459
- */
3460
- const handleRatingClose = useCallback(() => {
3461
- setShowRatingPanel(false);
3462
- ratingPanelShownRef.current = false;
3463
- // Note: Not recording as "shown" here to allow it to appear again
3464
- // The rating will appear again after continued usage
3465
- }, []);
3466
- /**
3467
- * Handles keyboard input for overlay navigation and shortcuts
3468
- */
3469
- useInput((input, key) => {
3470
- // Escape handling
3471
- if (key.escape) {
3472
- // During processing (agentic loop), single Esc gracefully breaks the loop
3473
- if (isProcessing && !pendingClarification && !pendingApproval && !pendingPlanApproval) {
3474
- // Set abort flag to break the agentic loop
3475
- abortLoopRef.current = true;
3476
- // Clear queued messages on abort
3477
- queuedMessagesRef.current = [];
3478
- setQueuedMessages([]);
3479
- // Also abort any in-flight API request immediately
3480
- if (abortControllerRef.current) {
3481
- abortControllerRef.current.abort();
3482
- }
3483
- // Cancel any pending plan approval or clarification wizards
3484
- const approvalManager = getApprovalManager();
3485
- approvalManager.cancelPendingPlanApproval();
3486
- approvalManager.cancelPendingClarificationWizard();
3487
- logger.debug('Cancelled by the user');
3488
- return;
3489
- }
3490
- // Close overlays
3491
- if (activeOverlay !== 'none') {
3492
- setActiveOverlay('none');
3493
- return;
3494
- }
3495
- if (error) {
3496
- setError(null);
3497
- return;
3498
- }
3499
- }
3500
- // Ctrl+X to skip current execute_command (kill subprocess, continue agentic loop)
3501
- if (key.ctrl && input === 'x' && isProcessing && toolAbortRef.current && currentToolNameRef.current === 'execute_command') {
3502
- toolAbortRef.current.abort();
3503
- return;
3504
- }
3505
- // Ctrl+C to cancel current operation or exit
3506
- if (key.ctrl && input === 'c') {
3507
- if (isProcessing) {
3508
- queuedMessagesRef.current = [];
3509
- setQueuedMessages([]);
3510
- setIsProcessing(false);
3511
- resetStreamBlocks();
3512
- setError('Operation cancelled');
3513
- }
3514
- return;
3515
- }
3516
- // Ctrl+L to clear screen (including Static items in scrollback)
3517
- if (key.ctrl && input === 'l') {
3518
- process.stdout.write('\x1B[2J\x1B[H');
3519
- setConversation([]);
3520
- setError(null);
3521
- return;
3522
- }
3523
- // Ctrl+T to toggle tool blocks display in history
3524
- if (key.ctrl && input === 't') {
3525
- setShowToolBlocks((prev) => {
3526
- const newValue = !prev;
3527
- // Persist to session
3528
- const sessionManager = getSessionManager();
3529
- sessionManager.setShowToolBlocks(newValue);
3530
- logger.output(newValue ? 'Tool execution details enabled in history.' : 'Minimal history mode (text only).');
3531
- return newValue;
3532
- });
3533
- return;
3534
- }
3535
- // Ctrl+O to toggle user prompt collapse and MCP result expansion in history
3536
- // Throttled to prevent yoga-layout WASM crash on rapid toggling
3537
- if (key.ctrl && input === 'o') {
3538
- const now = Date.now();
3539
- if (now - lastExpandToggleRef.current < 150) {
3540
- return; // Throttle rapid toggling
3541
- }
3542
- lastExpandToggleRef.current = now;
3543
- setCollapseUserPrompts((prev) => {
3544
- const newValue = !prev;
3545
- // Also toggle MCP results (inverse logic: when prompts collapse, MCP results are trimmed)
3546
- setExpandMCPResults(!newValue);
3547
- return newValue;
3548
- });
3549
- return;
3550
- }
3551
- // Cycle plan tracker mode with 'p' key during plan execution: compact → full → hidden → compact
3552
- if (input === 'p' && activePlan && !activePlan.isComplete && isProcessing) {
3553
- setPlanTrackerMode((prev) => prev === 'compact' ? 'full' : prev === 'full' ? 'hidden' : 'compact');
3554
- return;
3555
- }
3556
- }, { isActive: !pendingApproval && !pendingPlanApproval && !pendingClarification });
3557
- // Conversation entries are split between <Static> (scrollback) and dynamic rendering.
3558
- // <Static> is FROZEN during active streaming to prevent layout shifts. Adding items to
3559
- // <Static> mid-stream forces Ink to write permanent content above the dynamic area,
3560
- // causing reflow/flickering. However, when the user is interacting with a modal
3561
- // (tool approval, clarification, plan approval), streaming has stopped and the dynamic
3562
- // area is minimal — safe to settle completed entries into Static. This prevents long
3563
- // agentic loops from accumulating unbounded dynamic entries that re-render on every update.
3564
- //
3565
- // Entries between the Static freeze point and the latest are rendered dynamically
3566
- // (outside <Static>) so they remain visible without causing scrollback writes.
3567
- // A ref ensures items are never re-committed to <Static> across re-renders.
3568
- const staticCommittedRef = useRef([]);
3569
- const { settledEntries, dynamicEntries } = useMemo(() => {
3570
- if (conversation.length === 0) {
3571
- staticCommittedRef.current = [];
3572
- return { settledEntries: [], dynamicEntries: [] };
3573
- }
3574
- // Settle into Static when idle OR when a modal is active (streaming is paused,
3575
- // user is focused on the modal, and all prior entries are finalized).
3576
- const inModal = !!(pendingApproval || pendingPlanApproval || pendingClarification);
3577
- if (!isProcessing || inModal) {
3578
- if (conversation.length > staticCommittedRef.current.length) {
3579
- staticCommittedRef.current = conversation;
3580
- }
3581
- return { settledEntries: staticCommittedRef.current, dynamicEntries: [] };
3582
- }
3583
- // During active streaming, freeze <Static> — don't commit new entries mid-stream.
3584
- // Any entries added since the freeze point are rendered dynamically.
3585
- const frozenCount = staticCommittedRef.current.length;
3586
- return {
3587
- settledEntries: staticCommittedRef.current,
3588
- dynamicEntries: frozenCount < conversation.length
3589
- ? conversation.slice(frozenCount)
3590
- : [],
3591
- };
3592
- }, [conversation, isProcessing, pendingApproval, pendingPlanApproval, pendingClarification]);
3593
- /**
3594
- * Formats a timestamp for display
3595
- */
3596
- function formatTime(date) {
3597
- return date.toLocaleTimeString('en-US', {
3598
- hour: '2-digit',
3599
- minute: '2-digit',
3600
- });
3601
- }
3602
- // Keep working-dot ANSI offset in sync: 3 fixed lines (top hr, input, bottom hr)
3603
- // plus one line per queued message. Status line shares the bottom hr line.
3604
- workingDotOffsetRef.current = 3 + queuedMessages.length;
3605
- return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Static, { items: showHistory ? settledEntries : [], children: (entry) => (_jsx(ConversationEntryComponent, { entry: entry, showToolBlocks: showToolBlocks, collapseUserPrompts: collapseUserPrompts, expandMCPResults: expandMCPResults, formatTime: formatTime }, entry.id)) }), showHistory && dynamicEntries.map((entry) => (_jsx(ConversationEntryComponent, { entry: entry, showToolBlocks: showToolBlocks, collapseUserPrompts: collapseUserPrompts, expandMCPResults: expandMCPResults, formatTime: formatTime }, entry.id))), isProcessing && streamingCommandOutput.length > 0 && (_jsx(Box, { marginBottom: 1, flexDirection: "column", children: streamingCommandOutput.map((line, idx) => (_jsx(Text, { children: line }, idx))) })), !isProcessing && slashCommandOutput && slashCommandOutput.trim().length > 0 && (_jsx(Box, { marginBottom: 1, flexDirection: "column", children: _jsx(Text, { children: slashCommandOutput }) })), sessionPhase === 'executing' && activePlan && !activePlan.isComplete && planTrackerMode !== 'hidden' && !pendingApproval && (_jsx(PlanExecutionTracker, { planId: activePlan.planId, title: activePlan.title, tasks: activePlan.tasks, isComplete: activePlan.isComplete, success: activePlan.success, compact: planTrackerMode === 'compact' })), sessionPhase === 'executing' && activePlan && !activePlan.isComplete && planTrackerMode === 'hidden' && !pendingApproval && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "blue", children: '\u2500'.repeat(Math.max(0, (process.stdout.columns || 80) - 4)) }), _jsx(Text, { dimColor: true, children: "Plan progress hidden (press p to show)" }), _jsx(Text, { color: "blue", children: '\u2500'.repeat(Math.max(0, (process.stdout.columns || 80) - 4)) })] })), _jsx(Box, { flexDirection: "column", children: _jsx(InterleavedStream, { blocks: isProcessing ? streamBlocksRef.current : emptyBlocksRef.current, isStreaming: isProcessing && !pendingApproval && !pendingPlanApproval && !pendingClarification, showThinkingIndicator: isProcessing && !pendingApproval && !pendingPlanApproval && !pendingClarification, expandMCPResults: expandMCPResults, maxVisibleBlocks: MAX_DYNAMIC_STREAM_BLOCKS }) }), error && !isProcessing && (_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: "red", children: ["\u274C ", error] }), _jsx(Text, { dimColor: true, children: " (Press Esc to dismiss)" })] })), pendingApproval && (_jsx(ApprovalDialog, { request: pendingApproval, onChoice: handleApprovalChoice })), pendingPlanApproval && (_jsx(PlanApprovalDialog, { request: pendingPlanApproval, onChoice: handlePlanApprovalChoice, contextTokens: currentContextTokens })), pendingClarification && (_jsx(ClarificationWizard, { questions: pendingClarification.questions, onComplete: handleClarificationComplete, onCancel: handleClarificationCancel })), activeOverlay === 'help' && (_jsx(Box, { flexDirection: "column", marginBottom: 1, children: _jsx(HelpMenu, {}) })), activeOverlay === 'tokens' && (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(TokenUsage, { usage: tokenUsage, cost: cost, contextWindow: contextWindowSize, showDetails: true }), _jsx(Text, { dimColor: true, children: "Press Esc to close" })] })), activeOverlay === 'agents' && (_jsx(Box, { flexDirection: "column", marginBottom: 1, children: _jsx(AgentManager, { onClose: () => setActiveOverlay('none'), isActive: true, projectRoot: projectRoot }) })), activeOverlay === 'agent-wizard' && (_jsx(Box, { flexDirection: "column", marginBottom: 1, children: _jsx(AgentCreationWizard, { onComplete: (agentPath) => {
3606
- logger.info(`Agent created: ${agentPath}`);
3607
- setActiveOverlay('none');
3608
- }, onCancel: () => setActiveOverlay('none'), isActive: true, projectRoot: projectRoot, enabledTools: getToolRegistry().getToolDefinitions().map(t => t.name) }) })), activeOverlay === 'diff' && diffPreview && (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(DiffPreview, { diff: diffPreview }), _jsx(Text, { dimColor: true, children: "Press Esc to close" })] })), activeOverlay === 'mode-selector' && (_jsx(Box, { flexDirection: "column", marginBottom: 1, children: _jsx(ExecutionModeSelector, { currentMode: executionMode, onSelect: handleModeSelect, onCancel: handleModeCancel, isActive: true }) })), activeOverlay === 'logout-confirm' && (_jsx(Box, { flexDirection: "column", marginBottom: 1, children: _jsx(ConfirmationSelector, { title: "\uD83D\uDEAA Logout", question: "Are you sure you want to logout and clear all stored API keys?", details: logoutDetails, onConfirm: handleLogoutConfirm, onCancel: handleLogoutCancel, isActive: true }) })), showRatingPanel && hasCompassApiKey && !pendingApproval && !pendingPlanApproval && !pendingClarification && !isProcessing && activeOverlay === 'none' && (_jsx(RatingPanel, { onClose: handleRatingClose, onSubmit: handleRatingSubmit, isActive: true })), _jsx(CompassSpinner, { isActive: compassSpinner?.active ?? false, message: compassSpinner?.message }), isProcessing && !pendingApproval && !pendingPlanApproval && !pendingClarification && streamBlocksRef.current.length > 0 && (_jsx(WorkingIndicator, { linesBelowRef: workingDotOffsetRef })), queuedMessages.length > 0 && (_jsx(Box, { flexDirection: "column", paddingX: 1, children: queuedMessages.map((msg, i) => (_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: " queued: " }), _jsx(Text, { dimColor: true, italic: true, children: msg.length > 60 ? msg.substring(0, 60) + '...' : msg })] }, i))) })), !isContextLoading && !pendingApproval && !pendingPlanApproval && !pendingClarification && activeOverlay !== 'mode-selector' && activeOverlay !== 'logout-confirm' && activeOverlay !== 'agents' && activeOverlay !== 'agent-wizard' && (_jsx(Prompt, { approvalMode: approvalMode, model: currentModel, activeAgentName: activeAgentName, onSubmit: handlePromptSubmit, onPaste: handlePaste, disabled: false, isProcessing: isProcessing, projectRoot: projectRoot, showStatusBar: false, suggestionOffer: suggestionOffer })), _jsxs(Box, { width: "100%", justifyContent: "space-between", children: [_jsxs(Box, { children: [executionMode === 'direct' && _jsx(Text, { color: "yellow", children: "direct mode" }), executionMode === 'plan' && _jsx(Text, { color: "magenta", children: "plan mode" }), activeAgentName && (_jsxs(_Fragment, { children: [_jsx(Text, { dimColor: true, children: " \u2502 " }), _jsxs(Text, { color: "magenta", bold: true, children: ["\uD83E\uDD16 ", activeAgentName] })] }))] }), statusLineVisible && statusLineText && (_jsx(Text, { children: statusLineText }))] })] }));
3609
- };
3610
- export default InteractiveSession;
3611
- //# sourceMappingURL=InteractiveSession.js.map