@nodetool-ai/cli 0.7.0-rc.22 → 0.8.1

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 (488) hide show
  1. package/README.md +79 -0
  2. package/dist/.nodetool-build-stamp +1 -0
  3. package/dist/ExecutionTree.d.ts +2 -1
  4. package/dist/ExecutionTree.d.ts.map +1 -1
  5. package/dist/ExecutionTree.js +1 -1
  6. package/dist/ExecutionTree.js.map +1 -1
  7. package/dist/affected/affected.d.ts +50 -0
  8. package/dist/affected/affected.d.ts.map +1 -0
  9. package/dist/affected/affected.js +118 -0
  10. package/dist/affected/affected.js.map +1 -0
  11. package/dist/agent-toolbelt.d.ts +15 -0
  12. package/dist/agent-toolbelt.d.ts.map +1 -0
  13. package/dist/agent-toolbelt.js +28 -0
  14. package/dist/agent-toolbelt.js.map +1 -0
  15. package/dist/api-client.d.ts +12 -0
  16. package/dist/api-client.d.ts.map +1 -0
  17. package/dist/api-client.js +21 -0
  18. package/dist/api-client.js.map +1 -0
  19. package/dist/app-build/bundle.d.ts +15 -0
  20. package/dist/app-build/bundle.d.ts.map +1 -0
  21. package/dist/app-build/bundle.js +45 -0
  22. package/dist/app-build/bundle.js.map +1 -0
  23. package/dist/app-build/watch.d.ts +13 -0
  24. package/dist/app-build/watch.d.ts.map +1 -0
  25. package/dist/app-build/watch.js +25 -0
  26. package/dist/app-build/watch.js.map +1 -0
  27. package/dist/app-debug/app-target.d.ts +14 -0
  28. package/dist/app-debug/app-target.d.ts.map +1 -0
  29. package/dist/app-debug/app-target.js +120 -0
  30. package/dist/app-debug/app-target.js.map +1 -0
  31. package/dist/app-debug/harness.d.ts +27 -0
  32. package/dist/app-debug/harness.d.ts.map +1 -0
  33. package/dist/app-debug/harness.js +93 -0
  34. package/dist/app-debug/harness.js.map +1 -0
  35. package/dist/app-debug/index.d.ts +12 -0
  36. package/dist/app-debug/index.d.ts.map +1 -0
  37. package/dist/app-debug/index.js +9 -0
  38. package/dist/app-debug/index.js.map +1 -0
  39. package/dist/app.d.ts +38 -48
  40. package/dist/app.d.ts.map +1 -1
  41. package/dist/app.js +979 -677
  42. package/dist/app.js.map +1 -1
  43. package/dist/bash-tool.d.ts +13 -0
  44. package/dist/bash-tool.d.ts.map +1 -0
  45. package/dist/bash-tool.js +85 -0
  46. package/dist/bash-tool.js.map +1 -0
  47. package/dist/chat-codeact.d.ts +85 -0
  48. package/dist/chat-codeact.d.ts.map +1 -0
  49. package/dist/chat-codeact.js +141 -0
  50. package/dist/chat-codeact.js.map +1 -0
  51. package/dist/chat-context.d.ts +18 -0
  52. package/dist/chat-context.d.ts.map +1 -0
  53. package/dist/chat-context.js +27 -0
  54. package/dist/chat-context.js.map +1 -0
  55. package/dist/chat-media.d.ts +3 -0
  56. package/dist/chat-media.d.ts.map +1 -0
  57. package/dist/chat-media.js +46 -0
  58. package/dist/chat-media.js.map +1 -0
  59. package/dist/chat-prompts.d.ts +20 -0
  60. package/dist/chat-prompts.d.ts.map +1 -0
  61. package/dist/chat-prompts.js +42 -0
  62. package/dist/chat-prompts.js.map +1 -0
  63. package/dist/chat-sessions.d.ts +25 -0
  64. package/dist/chat-sessions.d.ts.map +1 -0
  65. package/dist/chat-sessions.js +153 -0
  66. package/dist/chat-sessions.js.map +1 -0
  67. package/dist/command-errors.d.ts +10 -0
  68. package/dist/command-errors.d.ts.map +1 -0
  69. package/dist/command-errors.js +15 -0
  70. package/dist/command-errors.js.map +1 -0
  71. package/dist/commands/__tests__/jtbd.test.d.ts +2 -0
  72. package/dist/commands/__tests__/jtbd.test.d.ts.map +1 -0
  73. package/dist/commands/__tests__/jtbd.test.js +120 -0
  74. package/dist/commands/__tests__/jtbd.test.js.map +1 -0
  75. package/dist/commands/__tests__/timeline-render-audio.test.d.ts +2 -0
  76. package/dist/commands/__tests__/timeline-render-audio.test.d.ts.map +1 -0
  77. package/dist/commands/__tests__/timeline-render-audio.test.js +53 -0
  78. package/dist/commands/__tests__/timeline-render-audio.test.js.map +1 -0
  79. package/dist/commands/__tests__/timeline-render.test.d.ts +2 -0
  80. package/dist/commands/__tests__/timeline-render.test.d.ts.map +1 -0
  81. package/dist/commands/__tests__/timeline-render.test.js +115 -0
  82. package/dist/commands/__tests__/timeline-render.test.js.map +1 -0
  83. package/dist/commands/affected.d.ts +9 -0
  84. package/dist/commands/affected.d.ts.map +1 -0
  85. package/dist/commands/affected.js +122 -0
  86. package/dist/commands/affected.js.map +1 -0
  87. package/dist/commands/agent.d.ts +26 -4
  88. package/dist/commands/agent.d.ts.map +1 -1
  89. package/dist/commands/agent.js +327 -306
  90. package/dist/commands/agent.js.map +1 -1
  91. package/dist/commands/app.d.ts +3 -0
  92. package/dist/commands/app.d.ts.map +1 -0
  93. package/dist/commands/app.js +381 -0
  94. package/dist/commands/app.js.map +1 -0
  95. package/dist/commands/apps.d.ts +15 -0
  96. package/dist/commands/apps.d.ts.map +1 -0
  97. package/dist/commands/apps.js +180 -0
  98. package/dist/commands/apps.js.map +1 -0
  99. package/dist/commands/auth.d.ts +12 -0
  100. package/dist/commands/auth.d.ts.map +1 -0
  101. package/dist/commands/auth.js +154 -0
  102. package/dist/commands/auth.js.map +1 -0
  103. package/dist/commands/collections.d.ts +10 -0
  104. package/dist/commands/collections.d.ts.map +1 -0
  105. package/dist/commands/collections.js +257 -0
  106. package/dist/commands/collections.js.map +1 -0
  107. package/dist/commands/costs.d.ts +15 -0
  108. package/dist/commands/costs.d.ts.map +1 -0
  109. package/dist/commands/costs.js +201 -0
  110. package/dist/commands/costs.js.map +1 -0
  111. package/dist/commands/db.d.ts +3 -0
  112. package/dist/commands/db.d.ts.map +1 -0
  113. package/dist/commands/db.js +176 -0
  114. package/dist/commands/db.js.map +1 -0
  115. package/dist/commands/debug.d.ts +13 -0
  116. package/dist/commands/debug.d.ts.map +1 -0
  117. package/dist/commands/debug.js +138 -0
  118. package/dist/commands/debug.js.map +1 -0
  119. package/dist/commands/deploy-helpers.d.ts +49 -18
  120. package/dist/commands/deploy-helpers.d.ts.map +1 -1
  121. package/dist/commands/deploy-helpers.js +10 -276
  122. package/dist/commands/deploy-helpers.js.map +1 -1
  123. package/dist/commands/deploy.d.ts +0 -1
  124. package/dist/commands/deploy.d.ts.map +1 -1
  125. package/dist/commands/deploy.js +21 -217
  126. package/dist/commands/deploy.js.map +1 -1
  127. package/dist/commands/eval.d.ts +152 -0
  128. package/dist/commands/eval.d.ts.map +1 -0
  129. package/dist/commands/eval.js +688 -0
  130. package/dist/commands/eval.js.map +1 -0
  131. package/dist/commands/game.d.ts +4 -0
  132. package/dist/commands/game.d.ts.map +1 -0
  133. package/dist/commands/game.js +395 -0
  134. package/dist/commands/game.js.map +1 -0
  135. package/dist/commands/generate.d.ts +17 -0
  136. package/dist/commands/generate.d.ts.map +1 -0
  137. package/dist/commands/generate.js +324 -0
  138. package/dist/commands/generate.js.map +1 -0
  139. package/dist/commands/generations.d.ts +17 -0
  140. package/dist/commands/generations.d.ts.map +1 -0
  141. package/dist/commands/generations.js +408 -0
  142. package/dist/commands/generations.js.map +1 -0
  143. package/dist/commands/harness.d.ts +19 -0
  144. package/dist/commands/harness.d.ts.map +1 -0
  145. package/dist/commands/harness.js +361 -0
  146. package/dist/commands/harness.js.map +1 -0
  147. package/dist/commands/js-script-validation-output.d.ts +7 -0
  148. package/dist/commands/js-script-validation-output.d.ts.map +1 -0
  149. package/dist/commands/js-script-validation-output.js +5 -0
  150. package/dist/commands/js-script-validation-output.js.map +1 -0
  151. package/dist/commands/js-script-versions.d.ts +53 -0
  152. package/dist/commands/js-script-versions.d.ts.map +1 -0
  153. package/dist/commands/js-script-versions.js +89 -0
  154. package/dist/commands/js-script-versions.js.map +1 -0
  155. package/dist/commands/js-script.d.ts +29 -0
  156. package/dist/commands/js-script.d.ts.map +1 -0
  157. package/dist/commands/js-script.js +227 -0
  158. package/dist/commands/js-script.js.map +1 -0
  159. package/dist/commands/jtbd.d.ts +38 -0
  160. package/dist/commands/jtbd.d.ts.map +1 -0
  161. package/dist/commands/jtbd.js +261 -0
  162. package/dist/commands/jtbd.js.map +1 -0
  163. package/dist/commands/local-db.d.ts +16 -0
  164. package/dist/commands/local-db.d.ts.map +1 -0
  165. package/dist/commands/local-db.js +33 -0
  166. package/dist/commands/local-db.js.map +1 -0
  167. package/dist/commands/migrate-code-inputs.d.ts +24 -0
  168. package/dist/commands/migrate-code-inputs.d.ts.map +1 -0
  169. package/dist/commands/migrate-code-inputs.js +120 -0
  170. package/dist/commands/migrate-code-inputs.js.map +1 -0
  171. package/dist/commands/models-hf.js +4 -4
  172. package/dist/commands/models-hf.js.map +1 -1
  173. package/dist/commands/models-recommended.d.ts.map +1 -1
  174. package/dist/commands/models-recommended.js +16 -24
  175. package/dist/commands/models-recommended.js.map +1 -1
  176. package/dist/commands/node.d.ts +10 -0
  177. package/dist/commands/node.d.ts.map +1 -0
  178. package/dist/commands/node.js +72 -0
  179. package/dist/commands/node.js.map +1 -0
  180. package/dist/commands/output.d.ts +24 -0
  181. package/dist/commands/output.d.ts.map +1 -0
  182. package/dist/commands/output.js +132 -0
  183. package/dist/commands/output.js.map +1 -0
  184. package/dist/commands/package-helpers.d.ts +1 -7
  185. package/dist/commands/package-helpers.d.ts.map +1 -1
  186. package/dist/commands/package-helpers.js +3 -26
  187. package/dist/commands/package-helpers.js.map +1 -1
  188. package/dist/commands/package.js +2 -2
  189. package/dist/commands/package.js.map +1 -1
  190. package/dist/commands/packs.d.ts +13 -0
  191. package/dist/commands/packs.d.ts.map +1 -0
  192. package/dist/commands/packs.js +60 -0
  193. package/dist/commands/packs.js.map +1 -0
  194. package/dist/commands/reliability.d.ts +14 -0
  195. package/dist/commands/reliability.d.ts.map +1 -0
  196. package/dist/commands/reliability.js +80 -0
  197. package/dist/commands/reliability.js.map +1 -0
  198. package/dist/commands/resource-read.d.ts +28 -0
  199. package/dist/commands/resource-read.d.ts.map +1 -0
  200. package/dist/commands/resource-read.js +316 -0
  201. package/dist/commands/resource-read.js.map +1 -0
  202. package/dist/commands/sketch-validation-output.d.ts +7 -0
  203. package/dist/commands/sketch-validation-output.d.ts.map +1 -0
  204. package/dist/commands/sketch-validation-output.js +5 -0
  205. package/dist/commands/sketch-validation-output.js.map +1 -0
  206. package/dist/commands/sketch-versions.d.ts +66 -0
  207. package/dist/commands/sketch-versions.d.ts.map +1 -0
  208. package/dist/commands/sketch-versions.js +108 -0
  209. package/dist/commands/sketch-versions.js.map +1 -0
  210. package/dist/commands/sketch.d.ts +21 -0
  211. package/dist/commands/sketch.d.ts.map +1 -0
  212. package/dist/commands/sketch.js +117 -0
  213. package/dist/commands/sketch.js.map +1 -0
  214. package/dist/commands/storage.d.ts +24 -0
  215. package/dist/commands/storage.d.ts.map +1 -0
  216. package/dist/commands/storage.js +112 -0
  217. package/dist/commands/storage.js.map +1 -0
  218. package/dist/commands/telegram.d.ts +11 -0
  219. package/dist/commands/telegram.d.ts.map +1 -0
  220. package/dist/commands/telegram.js +66 -0
  221. package/dist/commands/telegram.js.map +1 -0
  222. package/dist/commands/timeline-render.d.ts +80 -0
  223. package/dist/commands/timeline-render.d.ts.map +1 -0
  224. package/dist/commands/timeline-render.js +465 -0
  225. package/dist/commands/timeline-render.js.map +1 -0
  226. package/dist/commands/timeline-validation-output.d.ts +7 -0
  227. package/dist/commands/timeline-validation-output.d.ts.map +1 -0
  228. package/dist/commands/timeline-validation-output.js +9 -0
  229. package/dist/commands/timeline-validation-output.js.map +1 -0
  230. package/dist/commands/timeline-versions.d.ts +72 -0
  231. package/dist/commands/timeline-versions.d.ts.map +1 -0
  232. package/dist/commands/timeline-versions.js +108 -0
  233. package/dist/commands/timeline-versions.js.map +1 -0
  234. package/dist/commands/timeline.d.ts +22 -0
  235. package/dist/commands/timeline.d.ts.map +1 -0
  236. package/dist/commands/timeline.js +119 -0
  237. package/dist/commands/timeline.js.map +1 -0
  238. package/dist/commands/validate.d.ts +22 -0
  239. package/dist/commands/validate.d.ts.map +1 -0
  240. package/dist/commands/validate.js +127 -0
  241. package/dist/commands/validate.js.map +1 -0
  242. package/dist/commands/validation-output.d.ts +25 -0
  243. package/dist/commands/validation-output.d.ts.map +1 -0
  244. package/dist/commands/validation-output.js +29 -0
  245. package/dist/commands/validation-output.js.map +1 -0
  246. package/dist/commands/version-commands.d.ts +94 -0
  247. package/dist/commands/version-commands.d.ts.map +1 -0
  248. package/dist/commands/version-commands.js +227 -0
  249. package/dist/commands/version-commands.js.map +1 -0
  250. package/dist/commands/worker-models.d.ts +13 -0
  251. package/dist/commands/worker-models.d.ts.map +1 -0
  252. package/dist/commands/worker-models.js +207 -0
  253. package/dist/commands/worker-models.js.map +1 -0
  254. package/dist/commands/worker.d.ts +23 -0
  255. package/dist/commands/worker.d.ts.map +1 -0
  256. package/dist/commands/worker.js +374 -0
  257. package/dist/commands/worker.js.map +1 -0
  258. package/dist/debug/browser-runner.d.ts +21 -0
  259. package/dist/debug/browser-runner.d.ts.map +1 -0
  260. package/dist/debug/browser-runner.js +196 -0
  261. package/dist/debug/browser-runner.js.map +1 -0
  262. package/dist/debug/collector.d.ts +9 -0
  263. package/dist/debug/collector.d.ts.map +1 -0
  264. package/dist/debug/collector.js +9 -0
  265. package/dist/debug/collector.js.map +1 -0
  266. package/dist/debug/diff.d.ts +48 -0
  267. package/dist/debug/diff.d.ts.map +1 -0
  268. package/dist/debug/diff.js +68 -0
  269. package/dist/debug/diff.js.map +1 -0
  270. package/dist/debug/harness.d.ts +11 -0
  271. package/dist/debug/harness.d.ts.map +1 -0
  272. package/dist/debug/harness.js +103 -0
  273. package/dist/debug/harness.js.map +1 -0
  274. package/dist/debug/index.d.ts +17 -0
  275. package/dist/debug/index.d.ts.map +1 -0
  276. package/dist/debug/index.js +16 -0
  277. package/dist/debug/index.js.map +1 -0
  278. package/dist/debug/markdown.d.ts +3 -0
  279. package/dist/debug/markdown.d.ts.map +1 -0
  280. package/dist/debug/markdown.js +127 -0
  281. package/dist/debug/markdown.js.map +1 -0
  282. package/dist/debug/server-runner.d.ts +22 -0
  283. package/dist/debug/server-runner.d.ts.map +1 -0
  284. package/dist/debug/server-runner.js +164 -0
  285. package/dist/debug/server-runner.js.map +1 -0
  286. package/dist/debug/target.d.ts +34 -0
  287. package/dist/debug/target.d.ts.map +1 -0
  288. package/dist/debug/target.js +104 -0
  289. package/dist/debug/target.js.map +1 -0
  290. package/dist/debug/trace.d.ts +30 -0
  291. package/dist/debug/trace.d.ts.map +1 -0
  292. package/dist/debug/trace.js +87 -0
  293. package/dist/debug/trace.js.map +1 -0
  294. package/dist/debug/types.d.ts +82 -0
  295. package/dist/debug/types.d.ts.map +1 -0
  296. package/dist/debug/types.js +13 -0
  297. package/dist/debug/types.js.map +1 -0
  298. package/dist/debug/verdict.d.ts +3 -0
  299. package/dist/debug/verdict.d.ts.map +1 -0
  300. package/dist/debug/verdict.js +78 -0
  301. package/dist/debug/verdict.js.map +1 -0
  302. package/dist/debug/watch.d.ts +23 -0
  303. package/dist/debug/watch.d.ts.map +1 -0
  304. package/dist/debug/watch.js +68 -0
  305. package/dist/debug/watch.js.map +1 -0
  306. package/dist/diagnose.d.ts +139 -0
  307. package/dist/diagnose.d.ts.map +1 -0
  308. package/dist/diagnose.js +418 -0
  309. package/dist/diagnose.js.map +1 -0
  310. package/dist/evals/graph-runner.d.ts +8 -0
  311. package/dist/evals/graph-runner.d.ts.map +1 -0
  312. package/dist/evals/graph-runner.js +136 -0
  313. package/dist/evals/graph-runner.js.map +1 -0
  314. package/dist/harness/capability-coverage.d.ts +161 -0
  315. package/dist/harness/capability-coverage.d.ts.map +1 -0
  316. package/dist/harness/capability-coverage.js +266 -0
  317. package/dist/harness/capability-coverage.js.map +1 -0
  318. package/dist/harness/capability-table.d.ts +17 -0
  319. package/dist/harness/capability-table.d.ts.map +1 -0
  320. package/dist/harness/capability-table.js +3576 -0
  321. package/dist/harness/capability-table.js.map +1 -0
  322. package/dist/harness/changed-files.d.ts +18 -0
  323. package/dist/harness/changed-files.d.ts.map +1 -0
  324. package/dist/harness/changed-files.js +76 -0
  325. package/dist/harness/changed-files.js.map +1 -0
  326. package/dist/harness/declared-capabilities.d.ts +12 -0
  327. package/dist/harness/declared-capabilities.d.ts.map +1 -0
  328. package/dist/harness/declared-capabilities.js +21 -0
  329. package/dist/harness/declared-capabilities.js.map +1 -0
  330. package/dist/harness/registry.d.ts +162 -0
  331. package/dist/harness/registry.d.ts.map +1 -0
  332. package/dist/harness/registry.js +1738 -0
  333. package/dist/harness/registry.js.map +1 -0
  334. package/dist/index.js +121 -130
  335. package/dist/index.js.map +1 -1
  336. package/dist/interaction-script.d.ts +17 -0
  337. package/dist/interaction-script.d.ts.map +1 -0
  338. package/dist/interaction-script.js +47 -0
  339. package/dist/interaction-script.js.map +1 -0
  340. package/dist/js-script-debug/harness.d.ts +114 -0
  341. package/dist/js-script-debug/harness.d.ts.map +1 -0
  342. package/dist/js-script-debug/harness.js +252 -0
  343. package/dist/js-script-debug/harness.js.map +1 -0
  344. package/dist/js-script-debug/index.d.ts +9 -0
  345. package/dist/js-script-debug/index.d.ts.map +1 -0
  346. package/dist/js-script-debug/index.js +9 -0
  347. package/dist/js-script-debug/index.js.map +1 -0
  348. package/dist/js-script-debug/interactions.d.ts +19 -0
  349. package/dist/js-script-debug/interactions.d.ts.map +1 -0
  350. package/dist/js-script-debug/interactions.js +17 -0
  351. package/dist/js-script-debug/interactions.js.map +1 -0
  352. package/dist/js-script-debug/target.d.ts +20 -0
  353. package/dist/js-script-debug/target.d.ts.map +1 -0
  354. package/dist/js-script-debug/target.js +78 -0
  355. package/dist/js-script-debug/target.js.map +1 -0
  356. package/dist/local-model-interfaces.d.ts +20 -0
  357. package/dist/local-model-interfaces.d.ts.map +1 -0
  358. package/dist/local-model-interfaces.js +64 -0
  359. package/dist/local-model-interfaces.js.map +1 -0
  360. package/dist/local-secrets.d.ts +3 -0
  361. package/dist/local-secrets.d.ts.map +1 -0
  362. package/dist/local-secrets.js +16 -0
  363. package/dist/local-secrets.js.map +1 -0
  364. package/dist/markdown.d.ts.map +1 -1
  365. package/dist/markdown.js +7 -2
  366. package/dist/markdown.js.map +1 -1
  367. package/dist/node/run-node.d.ts +28 -0
  368. package/dist/node/run-node.d.ts.map +1 -0
  369. package/dist/node/run-node.js +76 -0
  370. package/dist/node/run-node.js.map +1 -0
  371. package/dist/node-registry.d.ts +13 -0
  372. package/dist/node-registry.d.ts.map +1 -0
  373. package/dist/node-registry.js +36 -0
  374. package/dist/node-registry.js.map +1 -0
  375. package/dist/nodetool.js +845 -309
  376. package/dist/nodetool.js.map +1 -1
  377. package/dist/numeric-options.d.ts +22 -0
  378. package/dist/numeric-options.d.ts.map +1 -0
  379. package/dist/numeric-options.js +45 -0
  380. package/dist/numeric-options.js.map +1 -0
  381. package/dist/permission-gate.d.ts +55 -0
  382. package/dist/permission-gate.d.ts.map +1 -0
  383. package/dist/permission-gate.js +118 -0
  384. package/dist/permission-gate.js.map +1 -0
  385. package/dist/predicates.d.ts +6 -0
  386. package/dist/predicates.d.ts.map +1 -0
  387. package/dist/predicates.js +6 -0
  388. package/dist/predicates.js.map +1 -0
  389. package/dist/providers.d.ts +73 -10
  390. package/dist/providers.d.ts.map +1 -1
  391. package/dist/providers.js +329 -97
  392. package/dist/providers.js.map +1 -1
  393. package/dist/readline-input.d.ts +13 -19
  394. package/dist/readline-input.d.ts.map +1 -1
  395. package/dist/readline-input.js +201 -190
  396. package/dist/readline-input.js.map +1 -1
  397. package/dist/run-budget.d.ts +40 -0
  398. package/dist/run-budget.d.ts.map +1 -0
  399. package/dist/run-budget.js +119 -0
  400. package/dist/run-budget.js.map +1 -0
  401. package/dist/run-dsl-supervised.d.ts +11 -0
  402. package/dist/run-dsl-supervised.d.ts.map +1 -0
  403. package/dist/run-dsl-supervised.js +82 -0
  404. package/dist/run-dsl-supervised.js.map +1 -0
  405. package/dist/run-dsl.d.ts +23 -0
  406. package/dist/run-dsl.d.ts.map +1 -1
  407. package/dist/run-dsl.js +40 -9
  408. package/dist/run-dsl.js.map +1 -1
  409. package/dist/run-json.d.ts +24 -0
  410. package/dist/run-json.d.ts.map +1 -0
  411. package/dist/run-json.js +132 -0
  412. package/dist/run-json.js.map +1 -0
  413. package/dist/sandbox-catalog.d.ts +12 -0
  414. package/dist/sandbox-catalog.d.ts.map +1 -0
  415. package/dist/sandbox-catalog.js +27 -0
  416. package/dist/sandbox-catalog.js.map +1 -0
  417. package/dist/settings.d.ts +11 -10
  418. package/dist/settings.d.ts.map +1 -1
  419. package/dist/settings.js +71 -13
  420. package/dist/settings.js.map +1 -1
  421. package/dist/sketch-debug/harness.d.ts +87 -0
  422. package/dist/sketch-debug/harness.d.ts.map +1 -0
  423. package/dist/sketch-debug/harness.js +178 -0
  424. package/dist/sketch-debug/harness.js.map +1 -0
  425. package/dist/sketch-debug/index.d.ts +9 -0
  426. package/dist/sketch-debug/index.d.ts.map +1 -0
  427. package/dist/sketch-debug/index.js +9 -0
  428. package/dist/sketch-debug/index.js.map +1 -0
  429. package/dist/sketch-debug/interactions.d.ts +19 -0
  430. package/dist/sketch-debug/interactions.d.ts.map +1 -0
  431. package/dist/sketch-debug/interactions.js +17 -0
  432. package/dist/sketch-debug/interactions.js.map +1 -0
  433. package/dist/sketch-debug/target.d.ts +48 -0
  434. package/dist/sketch-debug/target.d.ts.map +1 -0
  435. package/dist/sketch-debug/target.js +138 -0
  436. package/dist/sketch-debug/target.js.map +1 -0
  437. package/dist/stdin.d.ts +18 -19
  438. package/dist/stdin.d.ts.map +1 -1
  439. package/dist/stdin.js +159 -157
  440. package/dist/stdin.js.map +1 -1
  441. package/dist/supervisor.d.ts +97 -0
  442. package/dist/supervisor.d.ts.map +1 -0
  443. package/dist/supervisor.js +194 -0
  444. package/dist/supervisor.js.map +1 -0
  445. package/dist/terminal-clipboard.d.ts +3 -0
  446. package/dist/terminal-clipboard.d.ts.map +1 -0
  447. package/dist/terminal-clipboard.js +24 -0
  448. package/dist/terminal-clipboard.js.map +1 -0
  449. package/dist/terminal-mouse.d.ts +24 -0
  450. package/dist/terminal-mouse.d.ts.map +1 -0
  451. package/dist/terminal-mouse.js +84 -0
  452. package/dist/terminal-mouse.js.map +1 -0
  453. package/dist/terminal-screen.d.ts +41 -0
  454. package/dist/terminal-screen.d.ts.map +1 -0
  455. package/dist/terminal-screen.js +228 -0
  456. package/dist/terminal-screen.js.map +1 -0
  457. package/dist/timeline-debug/harness.d.ts +79 -0
  458. package/dist/timeline-debug/harness.d.ts.map +1 -0
  459. package/dist/timeline-debug/harness.js +155 -0
  460. package/dist/timeline-debug/harness.js.map +1 -0
  461. package/dist/timeline-debug/index.d.ts +9 -0
  462. package/dist/timeline-debug/index.d.ts.map +1 -0
  463. package/dist/timeline-debug/index.js +9 -0
  464. package/dist/timeline-debug/index.js.map +1 -0
  465. package/dist/timeline-debug/interactions.d.ts +19 -0
  466. package/dist/timeline-debug/interactions.d.ts.map +1 -0
  467. package/dist/timeline-debug/interactions.js +17 -0
  468. package/dist/timeline-debug/interactions.js.map +1 -0
  469. package/dist/timeline-debug/target.d.ts +34 -0
  470. package/dist/timeline-debug/target.d.ts.map +1 -0
  471. package/dist/timeline-debug/target.js +132 -0
  472. package/dist/timeline-debug/target.js.map +1 -0
  473. package/dist/tool-format.d.ts +54 -0
  474. package/dist/tool-format.d.ts.map +1 -0
  475. package/dist/tool-format.js +360 -0
  476. package/dist/tool-format.js.map +1 -0
  477. package/dist/useExecutionState.d.ts.map +1 -1
  478. package/dist/useExecutionState.js +8 -4
  479. package/dist/useExecutionState.js.map +1 -1
  480. package/dist/validate/index.d.ts +28 -0
  481. package/dist/validate/index.d.ts.map +1 -0
  482. package/dist/validate/index.js +51 -0
  483. package/dist/validate/index.js.map +1 -0
  484. package/dist/websocket-client.d.ts +78 -15
  485. package/dist/websocket-client.d.ts.map +1 -1
  486. package/dist/websocket-client.js +497 -171
  487. package/dist/websocket-client.js.map +1 -1
  488. package/package.json +62 -31
package/dist/app.js CHANGED
@@ -1,767 +1,1069 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- /**
3
- * NodeTool Chat CLI — Ink-based terminal UI.
4
- *
5
- * Layout:
6
- * ╭─ nodetool ─────────────────── provider · model ─╮
7
- * │ │
8
- * │ You: message │
9
- * │ │
10
- * │ response with markdown │
11
- * │ │
12
- * │ ◆ tool_name result preview │
13
- * │ │
14
- * ├──────────────────────────────────────────────────┤
15
- * │ › input │
16
- * ╰──────────────────────────────────────────────────╯
17
- */
18
- import { useState, useEffect, useCallback, useRef } from "react";
19
- import { Box, Text, Static, useApp, useInput } from "ink";
20
- import ReadlineInput from "./readline-input.js";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect, useRef } from "react";
3
+ import { Box, Text, useApp, useInput } from "ink";
21
4
  import Spinner from "ink-spinner";
22
- import { ExecutionTree } from "./ExecutionTree.js";
23
- import { useExecutionState } from "./useExecutionState.js";
24
- import { ProcessingContext } from "@nodetool-ai/runtime";
5
+ import { writeFile } from "node:fs/promises";
6
+ import { resolve } from "node:path";
7
+ import { PERMISSION_GATE_CONTEXT_KEY, RUN_BUDGET_CONTEXT_KEY, estimatePromptTokens } from "@nodetool-ai/runtime";
25
8
  import { processChat } from "@nodetool-ai/chat";
26
- import { MultiModeAgent } from "@nodetool-ai/agents";
27
- import { ReadFileTool, WriteFileTool, ListDirectoryTool, EditFileTool, GlobTool, GrepTool, DownloadFileTool, HttpRequestTool, GoogleSearchTool, GoogleNewsTool, GoogleImagesTool, BrowserTool, ScreenshotTool, RunCodeTool, CalculatorTool, ExtractPDFTextTool, ConvertPDFToMarkdownTool, ConvertDocumentTool, StatisticsTool, GeometryTool, ConversionTool, OpenAIWebSearchTool, OpenAIImageGenerationTool, OpenAITextToSpeechTool, DataForSEOSearchTool, DataForSEONewsTool, SearchEmailTool, ArchiveEmailTool, getAllMcpTools, } from "@nodetool-ai/agents";
28
- import { createProvider, DEFAULT_MODELS, KNOWN_PROVIDERS, WebSocketProvider } from "./providers.js";
9
+ import { availableProviders, createProvider, listConfiguredLanguageModels } from "./providers.js";
29
10
  import { WebSocketChatClient } from "./websocket-client.js";
11
+ import { budgetStopReason, budgetSummaryLine, createCliRunBudget } from "./run-budget.js";
30
12
  import { renderMarkdown } from "./markdown.js";
13
+ import { friendlyToolName, formatToolResult, isFormattedTool } from "./tool-format.js";
31
14
  import { saveSettings } from "./settings.js";
32
- import { getSecret } from "@nodetool-ai/models";
33
- // ---------------------------------------------------------------------------
34
- // Command definitions
35
- // ---------------------------------------------------------------------------
36
- const COMMANDS = {
37
- "/help": "Show available commands",
38
- "/clear": "Clear conversation history",
39
- "/model": "Set model: /model <model-id>",
40
- "/provider": "Set provider: /provider <name>",
41
- "/agent": "Toggle agent mode",
42
- "/planner": "Set agent planner: /planner graph|multi",
43
- "/tools": "List enabled tools",
44
- "/exit": "Exit the chat",
45
- "/quit": "Exit the chat",
15
+ import { buildCliToolbelt } from "./agent-toolbelt.js";
16
+ import { BashTool } from "./bash-tool.js";
17
+ import { applySystemPrompt, buildCliAgentBelt, createCliCodeActTurn } from "./chat-codeact.js";
18
+ import { createChatContext } from "./chat-context.js";
19
+ import { isNumber, isRecord, isString } from "./predicates.js";
20
+ import { parsePermissionMode } from "./permission-gate.js";
21
+ import ReadlineInput from "./readline-input.js";
22
+ import { Transcript, terminalLines, terminalText, useTerminalSize } from "./terminal-screen.js";
23
+ import { ChatSessionStore, newSessionId, exportTranscript } from "./chat-sessions.js";
24
+ import { ChatPrompts } from "./chat-prompts.js";
25
+ import { attachmentLines } from "./chat-media.js";
26
+ export const CHAT_COMMANDS = {
27
+ "/help": "Commands and keyboard shortcuts",
28
+ "/new": "Start a new conversation",
29
+ "/clear": "Clear the screen, keep conversation context",
30
+ "/compact": "Summarize retained context",
31
+ "/model": "Choose a model",
32
+ "/agent": "Inspect sub-agent threads: /agent [id|main]",
33
+ "/mode": "Permissions: default, auto, plan",
34
+ "/sessions": "Browse saved conversations",
35
+ "/resume": "Resume a session: /resume <id>",
36
+ "/export": "Save transcript: /export <path.md>",
37
+ "/tools": "Show available tools",
38
+ "/details": "Toggle tool arguments, code and diffs",
39
+ "/exit": "Save and quit",
40
+ "/quit": "Save and quit"
46
41
  };
47
- // ---------------------------------------------------------------------------
48
- // Individual message rendering
49
- // ---------------------------------------------------------------------------
50
- function UserMessage({ content }) {
51
- return (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "magenta", dimColor: true, bold: true, children: "❯ " }), _jsx(Text, { bold: true, inverse: true, children: " " + content + " " })] }));
52
- }
53
- function AssistantMessage({ content, rendered }) {
54
- return (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "green", children: "● " }), _jsx(Text, { children: rendered ?? content })] }));
55
- }
56
- function ToolMessage({ toolName, toolArgs, content }) {
57
- const argsStr = toolArgs
58
- ? Object.entries(toolArgs)
59
- .map(([k, v]) => `${k}: ${JSON.stringify(v)}`)
60
- .join(", ")
61
- : "";
62
- const preview = content.split("\n").slice(0, 3).join(" ").slice(0, 200);
63
- return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Box, { children: [_jsx(Text, { color: "green", children: "● " }), _jsx(Text, { bold: true, children: toolName }), argsStr ? _jsxs(Text, { color: "gray", dimColor: true, children: ["(", argsStr, ")"] }) : null] }), preview ? (_jsxs(Box, { marginLeft: 2, children: [_jsx(Text, { color: "gray", dimColor: true, children: "└ " }), _jsx(Text, { color: "gray", dimColor: true, children: preview })] })) : null] }));
64
- }
65
- function SystemMessage({ content }) {
66
- return (_jsx(Box, { children: _jsx(Text, { color: "gray", dimColor: true, children: " " + content }) }));
67
- }
68
- function ChatMessageItem({ msg }) {
69
- switch (msg.role) {
70
- case "user": return _jsx(UserMessage, { content: msg.content });
71
- case "assistant": return _jsx(AssistantMessage, { content: msg.content, rendered: msg.rendered });
72
- case "tool": return _jsx(ToolMessage, { toolName: msg.toolName ?? "tool", toolArgs: msg.toolArgs, content: msg.content });
73
- case "system": return _jsx(SystemMessage, { content: msg.content });
42
+ const APPROVAL_CHOICES = [
43
+ { key: "y", label: "Allow once", value: "allow" },
44
+ { key: "a", label: "Allow for chat", value: "allow_for_chat" },
45
+ { key: "n", label: "Deny", value: "deny" }
46
+ ];
47
+ function parseSubAgentEventTags(message) {
48
+ if (!isRecord(message) || !isString(message.parent_tool_call_id)) {
49
+ return null;
74
50
  }
51
+ return {
52
+ parentId: message.parent_tool_call_id,
53
+ depth: isNumber(message.subtask_depth) ? message.subtask_depth : 1
54
+ };
75
55
  }
76
- // ---------------------------------------------------------------------------
77
- // Autocomplete menu
78
- // ---------------------------------------------------------------------------
79
- const CMD_WIDTH = 14;
80
- function AutocompleteMenu({ matches, selectedIndex, }) {
81
- return (_jsx(Box, { flexDirection: "column", marginLeft: 2, children: matches.map(({ cmd, desc }, i) => {
82
- const selected = i === selectedIndex;
83
- return (_jsxs(Box, { children: [_jsxs(Text, { color: selected ? "cyan" : "gray", bold: selected, children: [selected ? "› " : " ", cmd.padEnd(CMD_WIDTH)] }), _jsx(Text, { color: "gray", dimColor: true, children: desc })] }, cmd));
84
- }) }));
56
+ const DELEGATION_TOOLS = new Set([
57
+ "run_subtask",
58
+ "start_subtask",
59
+ "run_search"
60
+ ]);
61
+ function errorText(error) {
62
+ return error instanceof Error ? error.message : String(error);
85
63
  }
86
- // ---------------------------------------------------------------------------
87
- // Help panel
88
- // ---------------------------------------------------------------------------
89
- function HelpPanel() {
90
- return (_jsxs(Box, { flexDirection: "column", marginLeft: 2, marginTop: 1, marginBottom: 1, children: [_jsx(Text, { color: "gray", dimColor: true, bold: true, children: "Commands" }), Object.entries(COMMANDS).map(([cmd, desc]) => (_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: (" " + cmd).padEnd(16) }), _jsx(Text, { color: "gray", dimColor: true, children: desc })] }, cmd))), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: " \u2191\u2193 history \u00B7 Tab complete \u00B7 Ctrl+C exit" }) })] }));
64
+ function displayResult(name, args, result) {
65
+ return isFormattedTool(name)
66
+ ? formatToolResult(name, args, result)
67
+ : isString(result)
68
+ ? result
69
+ : (JSON.stringify(result, null, 2) ?? "Done");
91
70
  }
92
- // ---------------------------------------------------------------------------
93
- // Main App
94
- // ---------------------------------------------------------------------------
95
- export function App({ initialProvider, initialModel, initialAgentMode, initialAgentPlanner = "graph", enabledTools, workspaceDir, wsUrl, extraTools, registry, agentProviders, }) {
71
+ export function App({ initialProvider, initialModel, enabledTools, workspaceDir, wsUrl, registry, agentProviders, costCap, timeout, permissionMode = "default", enableReadOnlySearch = true, resume, sessionStore, mouseEvents }) {
96
72
  const { exit } = useApp();
97
- // --- State ---
73
+ const { columns, rows } = useTerminalSize();
98
74
  const [provider, setProvider] = useState(initialProvider);
99
75
  const [model, setModel] = useState(initialModel);
100
- const [agentMode, setAgentMode] = useState(initialAgentMode);
101
- const [agentPlanner, setAgentPlanner] = useState(initialAgentPlanner);
76
+ const [mode, setMode] = useState(permissionMode);
102
77
  const [messages, setMessages] = useState([]);
103
- const [chatHistory, setChatHistory] = useState([]);
104
- const [inputValue, setInputValue] = useState("");
105
- const [, setInputHistory] = useState([]);
106
- const [, setHistoryIndex] = useState(-1);
107
- const [historyDraft, setHistoryDraft] = useState("");
108
- const [streaming, setStreaming] = useState(false);
109
- const [streamContent, setStreamContent] = useState("");
110
- const [streamLabel, setStreamLabel] = useState("");
111
- // Buffer messages during streaming to avoid Ink <Static> artifacts
112
- const pendingMessagesRef = useRef([]);
113
- const streamingRef = useRef(false);
114
- const [error, setError] = useState(null);
115
- const [showHelp, setShowHelp] = useState(false);
116
- const execState = useExecutionState();
117
- const [acIndex, setAcIndex] = useState(0);
118
- const [modelList, setModelList] = useState([]);
119
- // Fetch available models when provider changes
78
+ const [clearedCount, setClearedCount] = useState(0);
79
+ const [input, setInput] = useState("");
80
+ const [busy, setBusy] = useState(false);
81
+ const [status, setStatus] = useState("Ready");
82
+ const [live, setLive] = useState("");
83
+ const [details, setDetails] = useState(false);
84
+ const [prompt, setPrompt] = useState(null);
85
+ const [completionIndex, setCompletionIndex] = useState(0);
86
+ const [models, setModels] = useState([]);
87
+ const [configuredProviders, setConfiguredProviders] = useState(() => new Set(availableProviders()));
88
+ const [agentThreads, setAgentThreads] = useState({});
89
+ const [selectedAgentId, setSelectedAgentId] = useState(null);
90
+ const [sessions, setSessions] = useState([]);
91
+ const [todos, setTodos] = useState([]);
92
+ const [tasks, setTasks] = useState({});
93
+ const [activeTools, setActiveTools] = useState({});
94
+ const [elapsed, setElapsed] = useState(0);
95
+ const [usage, setUsage] = useState("");
96
+ const [contextTokens, setContextTokens] = useState(null);
97
+ const refreshContextTokens = () => {
98
+ setContextTokens(wsUrl ? null : estimatePromptTokens(history.current));
99
+ };
100
+ const [connection, setConnection] = useState(wsUrl ? "Connecting" : "Local");
101
+ const [sessionId, setSessionId] = useState(newSessionId);
102
+ const [historyIndex, setHistoryIndex] = useState(-1);
103
+ const [inputHistory, setInputHistory] = useState([]);
104
+ const [draft, setDraft] = useState("");
105
+ const store = useRef(sessionStore ?? new ChatSessionStore());
106
+ const prompts = useRef(new ChatPrompts(setPrompt));
107
+ const controller = useRef(null);
108
+ const client = useRef(null);
109
+ const history = useRef([]);
110
+ const transcript = useRef([]);
111
+ const agentThreadsRef = useRef({});
112
+ const stream = useRef("");
113
+ const thread = useRef(newSessionId());
114
+ const providerSession = useRef(null);
115
+ const sessionAllow = useRef(new Set());
116
+ const active = useRef(false);
117
+ const mounted = useRef(true);
118
+ const scrollGeneration = useRef(0);
119
+ const [scrollKey, setScrollKey] = useState("0");
120
+ function updateMessages(next) {
121
+ transcript.current = next;
122
+ setMessages(next);
123
+ }
124
+ function updateAgentThread(id, update) {
125
+ const next = {
126
+ ...agentThreadsRef.current,
127
+ [id]: update(agentThreadsRef.current[id])
128
+ };
129
+ agentThreadsRef.current = next;
130
+ setAgentThreads(next);
131
+ }
132
+ function startAgentThread(id, name, args, parentId, depth = 1) {
133
+ if (!DELEGATION_TOOLS.has(name) || agentThreadsRef.current[id]) {
134
+ return;
135
+ }
136
+ const title = [args.description, args.query, args.task].find(isString);
137
+ const thread = {
138
+ id,
139
+ title: title || friendlyToolName(name),
140
+ depth,
141
+ status: "running",
142
+ messages: []
143
+ };
144
+ if (parentId) {
145
+ Object.assign(thread, { parentId });
146
+ }
147
+ updateAgentThread(id, () => thread);
148
+ }
149
+ function appendAgentMessage(id, message, status) {
150
+ updateAgentThread(id, (thread) => {
151
+ const next = {
152
+ ...(thread ?? {
153
+ id,
154
+ title: "Sub-agent",
155
+ depth: 1,
156
+ status: "running",
157
+ messages: []
158
+ }),
159
+ messages: [...(thread?.messages ?? []), message]
160
+ };
161
+ if (status) {
162
+ Object.assign(next, { status });
163
+ }
164
+ return next;
165
+ });
166
+ }
167
+ function agentEvent(message) {
168
+ const tags = parseSubAgentEventTags(message);
169
+ if (!tags) {
170
+ return;
171
+ }
172
+ const { parentId, depth } = tags;
173
+ if (message.type === "tool_call_update" &&
174
+ message.tool_call_id &&
175
+ DELEGATION_TOOLS.has(message.name)) {
176
+ startAgentThread(message.tool_call_id, message.name, message.args, parentId, depth + 1);
177
+ }
178
+ let content;
179
+ let role = "system";
180
+ let status;
181
+ if (message.type === "chunk" && isString(message.content)) {
182
+ if (!message.thinking && message.content) {
183
+ content = message.content;
184
+ role = "assistant";
185
+ }
186
+ }
187
+ else if (message.type === "tool_call_update") {
188
+ content = `${friendlyToolName(message.name)}\n${JSON.stringify(message.args, null, 2)}`;
189
+ role = "tool";
190
+ }
191
+ else if (message.type === "tool_result_update") {
192
+ content = displayResult(message.name ?? "tool", undefined, message.result);
193
+ role = "tool";
194
+ }
195
+ else if (message.type === "planning_update") {
196
+ content = message.content ?? `Planning: ${message.status}`;
197
+ }
198
+ else if (message.type === "task_update") {
199
+ content = `${message.task.title ?? message.task.id}: ${message.event}`;
200
+ }
201
+ else if (message.type === "step_result") {
202
+ content = message.error
203
+ ? `Error: ${message.error}`
204
+ : displayResult("result", undefined, message.result);
205
+ if (message.is_task_result) {
206
+ status = message.error ? "failed" : "completed";
207
+ }
208
+ }
209
+ else if (message.type === "error") {
210
+ content = `Error: ${message.message}`;
211
+ status = "failed";
212
+ }
213
+ if (content) {
214
+ appendAgentMessage(parentId, { id: newSessionId(), role, content }, status);
215
+ }
216
+ }
217
+ function add(role, content, toolName, toolArgs) {
218
+ const message = {
219
+ id: newSessionId(),
220
+ role,
221
+ content,
222
+ toolName,
223
+ toolArgs
224
+ };
225
+ updateMessages([...transcript.current, message]);
226
+ if (role === "assistant") {
227
+ void renderMarkdown(terminalText(content))
228
+ .then((rendered) => {
229
+ if (mounted.current) {
230
+ updateMessages(transcript.current.map((item) => item.id === message.id ? { ...item, rendered } : item));
231
+ }
232
+ })
233
+ .catch(() => {
234
+ /* Raw content stays visible when formatting fails. */
235
+ });
236
+ }
237
+ }
238
+ function flush() {
239
+ if (stream.current.trim()) {
240
+ add("assistant", stream.current);
241
+ }
242
+ stream.current = "";
243
+ setLive("");
244
+ }
245
+ function append(text) {
246
+ stream.current += text;
247
+ setLive(stream.current);
248
+ }
249
+ function latest() {
250
+ setScrollKey(String(++scrollGeneration.current));
251
+ }
120
252
  useEffect(() => {
121
- let cancelled = false;
122
- createProvider(provider).then(async (prov) => {
123
- try {
124
- const models = await prov.getAvailableLanguageModels();
125
- if (!cancelled) {
126
- setModelList(models.map((m) => ({ id: m.id, name: m.name })));
253
+ mounted.current = true;
254
+ return () => {
255
+ mounted.current = false;
256
+ controller.current?.abort();
257
+ };
258
+ }, []);
259
+ useEffect(() => {
260
+ const abort = new AbortController();
261
+ setModels([]);
262
+ if (!wsUrl) {
263
+ void listConfiguredLanguageModels()
264
+ .then((catalog) => {
265
+ if (!abort.signal.aborted) {
266
+ setConfiguredProviders(new Set(catalog.providers));
267
+ setModels(catalog.models);
127
268
  }
269
+ })
270
+ .catch(() => {
271
+ /* Explicit provider/model specs remain available. */
272
+ });
273
+ }
274
+ return () => abort.abort();
275
+ }, [wsUrl]);
276
+ useEffect(() => {
277
+ if (!wsUrl) {
278
+ return;
279
+ }
280
+ const socket = new WebSocketChatClient(wsUrl);
281
+ client.current = socket;
282
+ let cancelled = false;
283
+ void socket
284
+ .connect()
285
+ .then(() => {
286
+ if (!cancelled) {
287
+ setConnection("Connected");
128
288
  }
129
- catch {
130
- if (!cancelled)
131
- setModelList([]);
289
+ })
290
+ .catch((error) => {
291
+ if (!cancelled) {
292
+ setConnection("Disconnected");
293
+ setStatus(`Connection failed: ${errorText(error)}`);
132
294
  }
133
- }).catch(() => {
134
- if (!cancelled)
135
- setModelList([]);
136
295
  });
137
- return () => { cancelled = true; };
138
- }, [provider]);
139
- // WebSocket client state (when --url is passed)
140
- const wsClientRef = useRef(null);
141
- const [threadId, setThreadId] = useState(() => crypto.randomUUID());
142
- // Refs to hold latest values without causing re-renders in async callbacks
143
- const chatHistoryRef = useRef(chatHistory);
144
- const providerRef = useRef(provider);
145
- const modelRef = useRef(model);
146
- const agentModeRef = useRef(agentMode);
147
- const agentPlannerRef = useRef(agentPlanner);
148
- const abortRef = useRef(false);
149
- const abortControllerRef = useRef(null);
150
- // Guard against double-submit: useInput autocomplete Enter + TextInput onSubmit fire for the same keypress
151
- const submittingRef = useRef(false);
152
- useEffect(() => { chatHistoryRef.current = chatHistory; }, [chatHistory]);
153
- useEffect(() => { providerRef.current = provider; }, [provider]);
154
- useEffect(() => { modelRef.current = model; }, [model]);
155
- useEffect(() => { agentModeRef.current = agentMode; }, [agentMode]);
156
- useEffect(() => { agentPlannerRef.current = agentPlanner; }, [agentPlanner]);
157
- useEffect(() => { setAcIndex(0); }, [inputValue]);
158
- // Connect WebSocket when --url is provided
296
+ return () => {
297
+ cancelled = true;
298
+ socket.disconnect();
299
+ client.current = null;
300
+ };
301
+ }, [wsUrl]);
159
302
  useEffect(() => {
160
- if (!wsUrl)
303
+ if (!busy) {
161
304
  return;
162
- const client = new WebSocketChatClient(wsUrl);
163
- client.connect().then(() => {
164
- wsClientRef.current = client;
165
- }).catch((err) => {
166
- const msg = err instanceof Error ? err.message : String(err);
167
- addMessage("system", `WebSocket connection failed: ${msg}`);
305
+ }
306
+ const started = Date.now();
307
+ setElapsed(0);
308
+ const timer = setInterval(() => setElapsed(Math.floor((Date.now() - started) / 1000)), 1000);
309
+ return () => clearInterval(timer);
310
+ }, [busy]);
311
+ useEffect(() => {
312
+ if (!resume) {
313
+ return;
314
+ }
315
+ let cancelled = false;
316
+ active.current = true;
317
+ setBusy(true);
318
+ void store.current
319
+ .list(workspaceDir, wsUrl)
320
+ .then((saved) => {
321
+ if (cancelled) {
322
+ return;
323
+ }
324
+ const selected = resume === true ? saved[0] : saved.find((item) => item.id === resume);
325
+ if (!selected) {
326
+ throw new Error("No matching saved session in this workspace.");
327
+ }
328
+ restore(selected);
329
+ })
330
+ .catch((error) => {
331
+ if (!cancelled) {
332
+ setStatus(errorText(error));
333
+ }
334
+ })
335
+ .finally(() => {
336
+ if (!cancelled) {
337
+ active.current = false;
338
+ setBusy(false);
339
+ }
168
340
  });
169
341
  return () => {
170
- client.disconnect();
171
- wsClientRef.current = null;
342
+ cancelled = true;
172
343
  };
173
- }, [wsUrl]);
174
- // Unique ID generator
175
- const nextId = useRef(0);
176
- const genId = () => `msg-${++nextId.current}`;
177
- // Add a message to the display history.
178
- // During streaming, tool/system messages are buffered to avoid Ink <Static> artifacts.
179
- // User and assistant messages (which mark start/end of a turn) are added immediately.
180
- const addMessage = useCallback(async (role, content, opts) => {
181
- let rendered;
182
- if (role === "assistant") {
183
- rendered = await renderMarkdown(content);
344
+ }, [resume, workspaceDir, wsUrl]);
345
+ function restore(session) {
346
+ history.current = session.history;
347
+ refreshContextTokens();
348
+ providerSession.current = session.providerSession ?? null;
349
+ thread.current = session.threadId;
350
+ setSessionId(session.id);
351
+ setProvider(session.provider);
352
+ setModel(session.model);
353
+ updateMessages(session.messages);
354
+ setClearedCount(0);
355
+ setInputHistory(session.messages
356
+ .filter((item) => item.role === "user")
357
+ .map((item) => item.content)
358
+ .reverse());
359
+ sessionAllow.current.clear();
360
+ setTodos([]);
361
+ setTasks({});
362
+ setUsage("");
363
+ latest();
364
+ setStatus(`Resumed ${session.title}`);
365
+ }
366
+ async function persist() {
367
+ if (!transcript.current.length) {
368
+ return;
184
369
  }
185
- const msg = {
186
- id: genId(),
187
- role,
188
- content,
189
- rendered,
190
- toolName: opts?.toolName,
191
- toolArgs: opts?.toolArgs,
192
- };
193
- if (streamingRef.current && role !== "user" && role !== "assistant") {
194
- pendingMessagesRef.current.push(msg);
370
+ await store.current.save({
371
+ version: 1,
372
+ id: sessionId,
373
+ threadId: thread.current,
374
+ title: transcript.current
375
+ .find((item) => item.role === "user")
376
+ ?.content.slice(0, 80) ?? "Conversation",
377
+ updatedAt: new Date().toISOString(),
378
+ workspace: resolve(workspaceDir),
379
+ server: wsUrl,
380
+ provider,
381
+ model,
382
+ history: history.current,
383
+ messages: transcript.current,
384
+ providerSession: providerSession.current
385
+ });
386
+ }
387
+ async function quit() {
388
+ try {
389
+ await persist();
390
+ await saveSettings({ provider, model });
391
+ exit();
195
392
  }
196
- else {
197
- setMessages(prev => [...prev, msg]);
393
+ catch (error) {
394
+ setStatus(`Could not save session: ${errorText(error)}. Try /export.`);
198
395
  }
199
- }, []);
200
- // Flush buffered messages when streaming ends
201
- const flushPendingMessages = useCallback(() => {
202
- if (pendingMessagesRef.current.length > 0) {
203
- const pending = pendingMessagesRef.current;
204
- pendingMessagesRef.current = [];
205
- setMessages(prev => [...prev, ...pending]);
396
+ }
397
+ function tools() {
398
+ const belt = [
399
+ ...buildCliToolbelt(agentProviders ?? {}, registry),
400
+ new BashTool(workspaceDir)
401
+ ];
402
+ return enabledTools
403
+ ? belt.filter((tool) => enabledTools.includes(tool.name))
404
+ : belt;
405
+ }
406
+ function processEvent(message) {
407
+ agentEvent(message);
408
+ if (message.type === "todo_update") {
409
+ setTodos(message.todos);
206
410
  }
207
- }, []);
208
- // Create tools from enabled list
209
- function buildTools() {
210
- const toolMap = {
211
- // Filesystem
212
- read_file: new ReadFileTool(),
213
- write_file: new WriteFileTool(),
214
- edit_file: new EditFileTool(),
215
- list_directory: new ListDirectoryTool(),
216
- glob: new GlobTool(),
217
- grep: new GrepTool(),
218
- // HTTP
219
- download_file: new DownloadFileTool(),
220
- http_request: new HttpRequestTool(),
221
- // Search (SERPAPI)
222
- google_search: new GoogleSearchTool(),
223
- google_news: new GoogleNewsTool(),
224
- google_images: new GoogleImagesTool(),
225
- // Browser
226
- browser: new BrowserTool(),
227
- screenshot: new ScreenshotTool(),
228
- // Code & math
229
- run_code: new RunCodeTool(),
230
- calculator: new CalculatorTool(),
231
- statistics: new StatisticsTool(),
232
- geometry: new GeometryTool(),
233
- conversion: new ConversionTool(),
234
- // PDF & documents
235
- extract_pdf_text: new ExtractPDFTextTool(),
236
- convert_pdf_to_markdown: new ConvertPDFToMarkdownTool(),
237
- convert_document: new ConvertDocumentTool(),
238
- // OpenAI tools
239
- openai_web_search: new OpenAIWebSearchTool(),
240
- openai_image_generation: new OpenAIImageGenerationTool(),
241
- openai_text_to_speech: new OpenAITextToSpeechTool(),
242
- // DataForSEO
243
- dataseo_search: new DataForSEOSearchTool(),
244
- dataseo_news: new DataForSEONewsTool(),
245
- // Email (IMAP)
246
- search_email: new SearchEmailTool(),
247
- archive_email: new ArchiveEmailTool(),
248
- };
249
- // NodeTool MCP tools (workflows, nodes, jobs, assets, models).
250
- // When a NodeRegistry is in process, this swaps the REST node-search
251
- // tools for the local biased versions (and adds `find_model`) so any
252
- // agent loop gets the same node-selection bias as the GraphPlanner.
253
- for (const tool of getAllMcpTools({
254
- registry,
255
- providers: agentProviders,
411
+ else if (message.type === "task_update" && message.task.id) {
412
+ const id = message.task.id;
413
+ setTasks((previous) => ({
414
+ ...previous,
415
+ [id]: `${message.task.title ?? id}: ${message.event}`
416
+ }));
417
+ }
418
+ else if (message.type === "planning_update") {
419
+ setStatus(message.content ?? `Planning: ${message.status}`);
420
+ }
421
+ else if (message.type === "output_update") {
422
+ add("system", `Output ${message.node_id}: ${displayResult("output", undefined, message.value)}`);
423
+ }
424
+ else if (message.type === "prediction") {
425
+ setStatus(`Generation: ${message.status ?? "running"}`);
426
+ }
427
+ else if (message.type === "chunk" &&
428
+ isString(message.content) &&
429
+ !message.thinking) {
430
+ if (message.parent_tool_call_id || message.subtask_depth) {
431
+ setStatus(`Subtask: ${message.content.slice(-100)}`);
432
+ }
433
+ }
434
+ }
435
+ function startTool(id, name, args) {
436
+ flush();
437
+ startAgentThread(id, name, args);
438
+ setActiveTools((previous) => ({ ...previous, [id]: { name, args } }));
439
+ setStatus(friendlyToolName(name));
440
+ }
441
+ function finishTool(id, name, args, result) {
442
+ if (DELEGATION_TOOLS.has(name) && name !== "start_subtask") {
443
+ const failed = isRecord(result) && isString(result.error);
444
+ updateAgentThread(id, (thread) => ({
445
+ ...(thread ?? {
446
+ id,
447
+ title: friendlyToolName(name),
448
+ depth: 1,
449
+ messages: []
450
+ }),
451
+ status: failed ? "failed" : "completed"
452
+ }));
453
+ }
454
+ add("tool", displayResult(name, args, result), name, args);
455
+ setActiveTools((previous) => {
456
+ const next = { ...previous };
457
+ delete next[id];
458
+ return next;
459
+ });
460
+ setStatus("Thinking");
461
+ }
462
+ async function remoteEvent(event, socket, signal) {
463
+ if (event.type === "chunk") {
464
+ append(event.content);
465
+ setStatus("Responding");
466
+ }
467
+ else if (event.type === "assistant_message") {
468
+ if (event.text) {
469
+ append(event.text);
470
+ }
471
+ const attachments = attachmentLines(event.content);
472
+ if (attachments.length) {
473
+ flush();
474
+ add("system", attachments.join("\n"));
475
+ }
476
+ }
477
+ else if (event.type === "processing") {
478
+ processEvent(event.message);
479
+ }
480
+ else if (event.type === "tool_approval_request") {
481
+ const answer = await prompts.current.ask({
482
+ title: `Approve ${event.toolName}`,
483
+ body: `${event.description || event.message}\n\n${JSON.stringify(event.args, null, 2)}`,
484
+ choices: APPROVAL_CHOICES
485
+ }, signal);
486
+ socket.respondToolApproval(event.approvalId, answer === "allow" || answer === "allow_for_chat" ? answer : "deny");
487
+ }
488
+ else if (event.type === "plan_approval_request") {
489
+ const answer = await prompts.current.ask({
490
+ title: event.plan.title,
491
+ body: event.plan.tasks
492
+ .map((task) => `${task.title}\n${task.steps.map((step) => ` ${step.instructions}`).join("\n")}`)
493
+ .join("\n\n"),
494
+ choices: [
495
+ { key: "y", label: "Execute plan", value: "approve" },
496
+ { key: "n", label: "Reject", value: "reject" }
497
+ ]
498
+ }, signal);
499
+ socket.respondPlanApproval(event.approvalId, answer === "approve" ? "approve" : "reject");
500
+ }
501
+ else if (event.type === "secret_request") {
502
+ const answer = await prompts.current.ask({
503
+ title: `Configure ${event.key}`,
504
+ body: `${event.description}\n${event.reason}\nConfigure this secret on the connected server, then continue.\n${event.helpUrl ?? ""}`,
505
+ choices: [
506
+ { key: "y", label: "Configured, retry", value: "provided" },
507
+ { key: "n", label: "Cancel", value: "cancelled" }
508
+ ]
509
+ }, signal);
510
+ socket.respondSecretRequest(event.approvalId, answer === "provided" ? "saved" : "declined");
511
+ }
512
+ else if (event.type === "client_tool_call") {
513
+ socket.respondToolResult(event.id, event.threadId, {
514
+ error: `The terminal does not provide browser tool ${event.name}. Use a server tool.`
515
+ }, false);
516
+ }
517
+ else if (event.type === "output_update") {
518
+ add("system", `Output ${event.node_id}: ${displayResult("output", undefined, event.value)}`);
519
+ }
520
+ else if (event.type === "error") {
521
+ throw new Error(event.message);
522
+ }
523
+ }
524
+ async function compact(instructions, signal) {
525
+ if (wsUrl) {
526
+ throw new Error("Remote context is managed by the server. Ask the agent to summarize, or use /new for a fresh thread.");
527
+ }
528
+ if (!history.current.length) {
529
+ setStatus("Nothing to compact");
530
+ return;
531
+ }
532
+ setStatus("Compacting");
533
+ const prov = await createProvider(provider);
534
+ let summary = "";
535
+ for await (const item of prov.generateMessagesTraced({
536
+ model,
537
+ signal,
538
+ messages: [
539
+ {
540
+ role: "user",
541
+ content: `Summarize decisions, files, tool results and outstanding work to continue this conversation. ${instructions}\n\n${JSON.stringify(history.current)}`
542
+ }
543
+ ]
256
544
  })) {
257
- toolMap[tool.name] = tool;
258
- }
259
- // When sandbox tools are present, exclude host tools that have sandbox
260
- // equivalents so the agent is forced to execute inside the sandbox.
261
- const sandboxMode = extraTools && extraTools.length > 0;
262
- const hostToolsToExclude = sandboxMode
263
- ? new Set([
264
- "read_file",
265
- "write_file",
266
- "edit_file",
267
- "list_directory",
268
- "glob",
269
- "grep",
270
- "run_code",
271
- "browser",
272
- "screenshot",
273
- "google_search",
274
- "google_news",
275
- "google_images",
276
- "dataseo_search",
277
- "dataseo_news"
278
- ])
279
- : null;
280
- const enabled = enabledTools
281
- .filter(name => name in toolMap && !(hostToolsToExclude?.has(name)))
282
- .map(name => toolMap[name]);
283
- return extraTools && extraTools.length > 0
284
- ? [...enabled, ...extraTools]
285
- : enabled;
545
+ if (signal.aborted) {
546
+ return;
547
+ }
548
+ if ("type" in item && item.type === "chunk" && isString(item.content)) {
549
+ summary += item.content;
550
+ }
551
+ }
552
+ if (!summary.trim()) {
553
+ throw new Error("The model returned an empty summary. Context was kept.");
554
+ }
555
+ // The first system message is replaced by applySystemPrompt on each turn.
556
+ history.current = [
557
+ { role: "system", content: "" },
558
+ { role: "system", content: `Retained conversation context:\n${summary}` }
559
+ ];
560
+ providerSession.current = null;
561
+ refreshContextTokens();
562
+ add("system", "Conversation compacted. Context retained.");
286
563
  }
287
- // ---------------------------------------------------------------------------
288
- // Command handler
289
- // ---------------------------------------------------------------------------
290
- const handleCommand = useCallback(async (raw) => {
291
- const parts = raw.trim().split(/\s+/);
292
- const cmd = parts[0].toLowerCase();
293
- const args = parts.slice(1);
294
- switch (cmd) {
564
+ async function command(value, signal) {
565
+ const [name = "", ...words] = value.split(/\s+/);
566
+ const argument = words.join(" ");
567
+ switch (name.toLowerCase()) {
295
568
  case "/help":
296
- setShowHelp(prev => !prev);
297
- return true;
569
+ add("system", Object.entries(CHAT_COMMANDS)
570
+ .map(([cmd, description]) => `${cmd.padEnd(12)} ${description}`)
571
+ .join("\n") +
572
+ "\n\nEnter send · Alt+Enter or Ctrl+J newline · Tab complete\n↑↓ history · PgUp/PgDn or wheel scroll · Ctrl+G latest\nDrag transcript text to copy · Ctrl+O tool details\nEsc cancel/dismiss · Ctrl+C cancel/clear/quit");
573
+ break;
298
574
  case "/clear":
299
- setMessages([{ id: genId(), role: "system", content: "History cleared." }]);
300
- setChatHistory([]);
301
- setShowHelp(false);
302
- if (wsClientRef.current) {
303
- setThreadId(crypto.randomUUID()); // next message starts a fresh server thread
304
- }
305
- return true;
306
- case "/exit":
307
- case "/quit":
308
- await saveSettings({ provider, model, agentMode, agentPlanner });
309
- exit();
310
- return true;
311
- case "/agent":
312
- setAgentMode(prev => {
313
- const next = !prev;
314
- addMessage("system", `Agent mode ${next ? "ON" : "OFF"}.`);
315
- return next;
316
- });
317
- return true;
318
- case "/planner": {
319
- const arg = args[0]?.toLowerCase();
320
- if (arg === "graph" || arg === "multi") {
321
- setAgentPlanner(arg);
322
- await saveSettings({ agentPlanner: arg });
323
- addMessage("system", arg === "graph"
324
- ? "Planner set to graph (workflow builder)."
325
- : "Planner set to multi (parallel task plan).");
575
+ setClearedCount(transcript.current.length);
576
+ latest();
577
+ break;
578
+ case "/new":
579
+ await persist();
580
+ history.current = [];
581
+ refreshContextTokens();
582
+ providerSession.current = null;
583
+ updateMessages([]);
584
+ thread.current = newSessionId();
585
+ setSessionId(newSessionId());
586
+ sessionAllow.current.clear();
587
+ setClearedCount(0);
588
+ setTodos([]);
589
+ setTasks({});
590
+ agentThreadsRef.current = {};
591
+ setAgentThreads({});
592
+ setSelectedAgentId(null);
593
+ setUsage("");
594
+ latest();
595
+ break;
596
+ case "/compact":
597
+ await compact(argument, signal);
598
+ break;
599
+ case "/model":
600
+ if (!argument) {
601
+ setInput("/model ");
602
+ break;
326
603
  }
327
- else {
328
- addMessage("system", `Current planner: ${agentPlanner}. Usage: /planner graph|multi`);
604
+ {
605
+ const qualified = models.find((item) => `${item.provider}/${item.id}` === argument);
606
+ const matchingIds = models.filter((item) => item.id === argument);
607
+ if (!qualified && matchingIds.length > 1) {
608
+ throw new Error(`Model "${argument}" is available from multiple providers. Choose a provider/model entry.`);
609
+ }
610
+ const inferred = qualified ?? matchingIds[0];
611
+ const separator = argument.indexOf("/");
612
+ const explicitProvider = separator > 0 &&
613
+ configuredProviders.has(argument.slice(0, separator))
614
+ ? argument.slice(0, separator)
615
+ : undefined;
616
+ const nextProvider = inferred?.provider ?? explicitProvider ?? provider;
617
+ const nextModel = inferred
618
+ ? inferred.id
619
+ : explicitProvider
620
+ ? argument.slice(separator + 1)
621
+ : argument;
622
+ setProvider(nextProvider);
623
+ setModel(nextModel);
624
+ providerSession.current = null;
625
+ await saveSettings({ provider: nextProvider, model: nextModel });
329
626
  }
330
- return true;
627
+ break;
628
+ case "/agent": {
629
+ inspectAgent(argument);
630
+ break;
331
631
  }
332
- case "/model":
333
- if (args[0]) {
334
- setModel(args[0]);
335
- await saveSettings({ model: args[0] });
336
- addMessage("system", `Model set to: ${args[0]}`);
632
+ case "/mode":
633
+ setMode(parsePermissionMode(argument) ?? "default");
634
+ break;
635
+ case "/sessions": {
636
+ const saved = await store.current.list(workspaceDir, wsUrl);
637
+ setSessions(saved);
638
+ if (!saved.length) {
639
+ add("system", "No saved conversations in this workspace yet.");
337
640
  }
338
641
  else {
339
- addMessage("system", `Current model: ${model}. Usage: /model <model-id>`);
340
- }
341
- return true;
342
- case "/provider": {
343
- if (args[0]) {
344
- const newProvider = args[0].toLowerCase();
345
- const newModel = DEFAULT_MODELS[newProvider] ?? model;
346
- setProvider(newProvider);
347
- setModel(newModel);
348
- await saveSettings({ provider: newProvider, model: newModel });
349
- addMessage("system", `Provider: ${newProvider} • Model: ${newModel}`);
642
+ setInput("/resume ");
350
643
  }
351
- else {
352
- addMessage("system", `Current provider: ${provider}. Usage: /provider <name>`);
644
+ break;
645
+ }
646
+ case "/resume": {
647
+ const saved = await store.current.list(workspaceDir, wsUrl);
648
+ const selected = argument
649
+ ? saved.find((item) => item.id === argument)
650
+ : saved[0];
651
+ if (!selected) {
652
+ throw new Error("Session not found. Use /sessions to choose a saved conversation.");
353
653
  }
354
- return true;
654
+ await persist();
655
+ restore(selected);
656
+ break;
657
+ }
658
+ case "/export": {
659
+ const path = resolve(workspaceDir, argument || `nodetool-chat-${sessionId}.md`);
660
+ await writeFile(path, exportTranscript(transcript.current), {
661
+ encoding: "utf8",
662
+ flag: "wx",
663
+ mode: 0o600
664
+ });
665
+ add("system", `Exported ${path}`);
666
+ break;
355
667
  }
356
668
  case "/tools":
357
- addMessage("system", `Enabled tools: ${enabledTools.join(", ") || "(none)"}`);
358
- return true;
669
+ add("system", wsUrl
670
+ ? "Connected server manages the toolbelt. Ask the agent to list its available tools."
671
+ : tools()
672
+ .map((tool) => `${tool.name} — ${tool.description.split("\n")[0]}`)
673
+ .join("\n"));
674
+ break;
675
+ case "/details":
676
+ setDetails((previous) => !previous);
677
+ break;
678
+ case "/exit":
679
+ case "/quit":
680
+ await quit();
681
+ break;
359
682
  default:
360
- if (cmd.startsWith("/")) {
361
- addMessage("system", `Unknown command: ${cmd}. Type /help for commands.`);
362
- return true;
363
- }
364
- return false;
683
+ throw new Error(`Unknown command: ${name}. Use /help.`);
365
684
  }
366
- }, [provider, model, agentMode, enabledTools, addMessage, exit]);
367
- // ---------------------------------------------------------------------------
368
- // Chat submission
369
- // ---------------------------------------------------------------------------
370
- const handleSubmit = useCallback(async (value) => {
371
- const trimmed = value.trim();
372
- if (!trimmed)
685
+ }
686
+ function inspectAgent(argument) {
687
+ if (!argument) {
688
+ const threads = Object.values(agentThreadsRef.current);
689
+ if (!threads.length) {
690
+ add("system", "No sub-agent threads in this conversation yet.");
691
+ }
692
+ else {
693
+ setInput("/agent ");
694
+ }
373
695
  return;
374
- // Deduplicate: useInput autocomplete Enter + TextInput onSubmit both fire for the same keypress
375
- if (submittingRef.current)
696
+ }
697
+ if (argument === "main") {
698
+ setSelectedAgentId(null);
699
+ latest();
376
700
  return;
377
- submittingRef.current = true;
378
- // Add to history (deduplicated)
379
- setInputHistory(prev => {
380
- const filtered = prev.filter(h => h !== trimmed);
381
- return [trimmed, ...filtered].slice(0, 100);
382
- });
383
- setHistoryIndex(-1);
384
- setHistoryDraft("");
385
- setInputValue("");
386
- setError(null);
387
- setShowHelp(false);
388
- // Handle commands
389
- if (trimmed.startsWith("/")) {
390
- await handleCommand(trimmed);
391
- submittingRef.current = false;
701
+ }
702
+ const selectedThread = agentThreadsRef.current[argument];
703
+ if (!selectedThread) {
704
+ throw new Error(`Unknown sub-agent thread: ${argument}. Use /agent.`);
705
+ }
706
+ setSelectedAgentId(selectedThread.id);
707
+ latest();
708
+ }
709
+ async function submit(value) {
710
+ const trimmed = value.trim();
711
+ if (!trimmed) {
392
712
  return;
393
713
  }
394
- // Add user message to display
395
- await addMessage("user", trimmed);
396
- abortRef.current = false;
397
- const abortController = new AbortController();
398
- abortControllerRef.current = abortController;
399
- streamingRef.current = true;
400
- setStreaming(true);
401
- setStreamContent("");
402
- setStreamLabel("thinking");
403
- try {
404
- const ctx = new ProcessingContext({ jobId: crypto.randomUUID(), userId: "1", workspaceDir, secretResolver: getSecret });
405
- const tools = buildTools();
406
- if (agentModeRef.current) {
407
- // --- Agent mode ---
408
- setStreamLabel("planning");
409
- execState.reset();
410
- const prov = wsClientRef.current
411
- ? new WebSocketProvider(wsClientRef.current, modelRef.current, providerRef.current)
412
- : await createProvider(providerRef.current);
413
- // Pick the planner. The "graph" planner needs a registry; without
414
- // one we silently downgrade to the multi-task planner.
415
- const useGraphPlanner = agentPlannerRef.current === "graph" && !!registry;
416
- const markdownOutputSchema = {
417
- type: "object",
418
- properties: {
419
- markdown: {
420
- type: "string",
421
- description: "The markdown content of the response",
422
- },
423
- },
424
- required: ["markdown"],
425
- };
426
- const agent = new MultiModeAgent({
427
- name: "chat-agent",
428
- objective: trimmed,
429
- provider: prov,
430
- model: modelRef.current,
431
- mode: "plan",
432
- tools,
433
- useGraphPlanner,
434
- registry: useGraphPlanner ? registry : undefined,
435
- providers: useGraphPlanner ? agentProviders : undefined,
436
- maxStepIterations: 20,
437
- outputSchema: markdownOutputSchema,
438
- });
439
- // Feed all messages into the execution tree state.
440
- let synthesisContent = "";
441
- for await (const msg of agent.execute(ctx)) {
442
- if (abortRef.current)
443
- break;
444
- execState.processMessage(msg);
445
- // Stream the agent's final synthesis so the user sees the answer
446
- // forming in real time. Other chunks (step thinking, planner
447
- // commentary) are noise in the chat pane and stay in the tree.
448
- if (msg.type === "chunk") {
449
- const ch = msg;
450
- if (ch.node_id === "agent_synthesizer" && ch.content) {
451
- synthesisContent += ch.content;
452
- setStreamContent(synthesisContent);
453
- setStreamLabel("finalizing");
454
- }
455
- }
714
+ if (active.current) {
715
+ const [name = "", ...words] = trimmed.split(/\s+/);
716
+ if (name.toLowerCase() === "/agent") {
717
+ setInput("");
718
+ try {
719
+ inspectAgent(words.join(" "));
456
720
  }
457
- execState.markDone();
458
- setStreamContent("");
459
- const finalResults = agent.getResults();
460
- const finalText = typeof finalResults === "string"
461
- ? finalResults
462
- : finalResults &&
463
- typeof finalResults === "object" &&
464
- "markdown" in finalResults &&
465
- typeof finalResults.markdown ===
466
- "string"
467
- ? finalResults.markdown
468
- : finalResults != null
469
- ? JSON.stringify(finalResults, null, 2)
470
- : "";
471
- if (finalText) {
472
- await addMessage("assistant", finalText);
721
+ catch (error) {
722
+ setStatus(errorText(error));
473
723
  }
474
724
  }
475
- else if (wsClientRef.current && !extraTools?.length) {
476
- // --- Regular chat via WebSocket (server handles everything) ---
477
- const wsClient = wsClientRef.current;
478
- let assistantContent = "";
479
- const toolSchemas = tools.map(t => t.toProviderTool());
480
- const pendingToolArgs = new Map();
481
- for await (const event of wsClient.chat(trimmed, threadId, modelRef.current, providerRef.current, toolSchemas)) {
482
- if (abortRef.current)
725
+ return;
726
+ }
727
+ active.current = true;
728
+ const abort = new AbortController();
729
+ controller.current = abort;
730
+ setBusy(true);
731
+ setInput("");
732
+ setStatus("Thinking");
733
+ latest();
734
+ setHistoryIndex(-1);
735
+ setDraft("");
736
+ setInputHistory((previous) => [value, ...previous.filter((item) => item !== value)].slice(0, 100));
737
+ let budget;
738
+ try {
739
+ if (value.startsWith("/")) {
740
+ await command(value.trim(), abort.signal);
741
+ return;
742
+ }
743
+ add("user", value);
744
+ if (wsUrl) {
745
+ const socket = client.current;
746
+ if (!socket || connection !== "Connected") {
747
+ throw new Error("Server is not connected. Restart chat to reconnect.");
748
+ }
749
+ const calls = new Map();
750
+ for await (const event of socket.chat(value, thread.current, model, provider, undefined, { permissionMode: mode, signal: abort.signal })) {
751
+ if (abort.signal.aborted) {
483
752
  break;
484
- if (event.type === "chunk") {
485
- assistantContent += event.content;
486
- setStreamContent(assistantContent);
487
- setStreamLabel("streaming");
488
753
  }
489
- else if (event.type === "tool_call") {
490
- pendingToolArgs.set(event.id, event.args);
491
- setStreamLabel(`tool: ${event.name}`);
754
+ if (event.type === "tool_call") {
755
+ calls.set(event.id, event);
756
+ startTool(event.id, event.name, event.args);
492
757
  }
493
758
  else if (event.type === "tool_result") {
494
- const preview = event.content.length > 100 ? event.content.slice(0, 100) + "…" : event.content;
495
- const args = pendingToolArgs.get(event.id);
496
- pendingToolArgs.delete(event.id);
497
- await addMessage("tool", preview, { toolName: event.name, toolArgs: args });
498
- setStreamLabel("thinking");
499
- }
500
- else if (event.type === "error") {
501
- throw new Error(event.message);
759
+ const call = calls.get(event.id);
760
+ finishTool(event.id, event.name || call?.name || "tool", call?.args, event.content);
761
+ calls.delete(event.id);
502
762
  }
503
- else if (event.type === "done") {
504
- break;
763
+ else {
764
+ await remoteEvent(event, socket, abort.signal);
505
765
  }
506
766
  }
507
- if (assistantContent) {
508
- await addMessage("assistant", assistantContent);
509
- }
510
767
  }
511
- else if (wsClientRef.current && extraTools?.length) {
512
- // --- Regular chat via WebSocket inference + local sandbox tool execution ---
513
- const prov = new WebSocketProvider(wsClientRef.current, modelRef.current, providerRef.current);
514
- let assistantContent = "";
515
- const updatedHistory = [...chatHistoryRef.current];
516
- await processChat({
517
- userInput: trimmed,
518
- messages: updatedHistory,
519
- model: modelRef.current,
520
- provider: prov,
521
- context: ctx,
522
- tools,
523
- signal: abortController.signal,
524
- callbacks: {
525
- onChunk: (text) => {
526
- if (abortRef.current)
527
- throw new Error("aborted");
528
- assistantContent += text;
529
- setStreamContent(assistantContent);
530
- setStreamLabel("streaming");
531
- },
532
- onToolCall: (tc) => {
533
- setStreamLabel(`tool: ${tc.name}`);
534
- },
535
- onToolResult: (tc, result) => {
536
- const preview = typeof result === "string"
537
- ? result
538
- : JSON.stringify(result).slice(0, 100);
539
- addMessage("tool", preview, { toolName: tc.name, toolArgs: tc.args });
540
- },
541
- },
542
- });
543
- setChatHistory(updatedHistory);
544
- if (assistantContent) {
545
- await addMessage("assistant", assistantContent);
768
+ else {
769
+ const ctx = await createChatContext({ workspaceDir });
770
+ const unsubscribe = ctx.addMessageListener(processEvent);
771
+ try {
772
+ budget = await createCliRunBudget({ costCap, timeout });
773
+ const gate = {
774
+ mode,
775
+ sessionAllow: sessionAllow.current,
776
+ requestApproval: async (request) => {
777
+ const answer = await prompts.current.ask({
778
+ title: `Approve ${request.toolName}`,
779
+ body: `${request.description || request.message}\n\n${JSON.stringify(request.args, null, 2)}`,
780
+ choices: APPROVAL_CHOICES
781
+ }, abort.signal);
782
+ return answer === "allow" || answer === "allow_for_chat"
783
+ ? answer
784
+ : "deny";
785
+ }
786
+ };
787
+ ctx.set(PERMISSION_GATE_CONTEXT_KEY, gate);
788
+ ctx.set(RUN_BUDGET_CONTEXT_KEY, budget);
789
+ const prov = await createProvider(provider);
790
+ const belt = buildCliAgentBelt({
791
+ baseTools: tools(),
792
+ provider: prov,
793
+ model,
794
+ forwardMessage: processEvent,
795
+ gate,
796
+ budget,
797
+ readOnlySearch: enableReadOnlySearch,
798
+ planning: true
799
+ });
800
+ const turn = createCliCodeActTurn({
801
+ tools: belt,
802
+ context: ctx,
803
+ signal: abort.signal,
804
+ onToolCall: ({ name }) => setStatus(friendlyToolName(name))
805
+ });
806
+ applySystemPrompt(history.current, turn.systemPrompt);
807
+ refreshContextTokens();
808
+ const historyStart = history.current.length;
809
+ let displayedText = "";
810
+ await processChat({
811
+ userInput: value,
812
+ messages: history.current,
813
+ provider: prov,
814
+ model,
815
+ threadId: thread.current,
816
+ providerSession: providerSession.current,
817
+ context: ctx,
818
+ tools: turn.tools,
819
+ signal: abort.signal,
820
+ turnBudget: budget,
821
+ callbacks: {
822
+ onChunk: (text) => {
823
+ if (!abort.signal.aborted) {
824
+ displayedText += text;
825
+ append(text);
826
+ setStatus("Responding");
827
+ }
828
+ },
829
+ onToolCall: (call) => startTool(call.id, call.name, call.args),
830
+ onToolResult: (call, result) => finishTool(call.id, call.name, call.args, result),
831
+ onProviderSession: (session) => {
832
+ providerSession.current = session;
833
+ }
834
+ }
835
+ });
836
+ const answers = history.current
837
+ .slice(historyStart)
838
+ .filter((message) => message.role === "assistant");
839
+ const finalText = answers
840
+ .map((message) => isString(message.content)
841
+ ? message.content
842
+ : (message.content
843
+ ?.flatMap((part) => part.type === "text" ? [part.text] : [])
844
+ .join("") ?? ""))
845
+ .join("");
846
+ if (finalText.startsWith(displayedText)) {
847
+ append(finalText.slice(displayedText.length));
848
+ }
849
+ else if (finalText && !abort.signal.aborted) {
850
+ flush();
851
+ add("assistant", finalText);
852
+ }
853
+ const attachments = answers.flatMap((message) => attachmentLines(message.content));
854
+ if (attachments.length) {
855
+ flush();
856
+ add("system", attachments.join("\n"));
857
+ }
858
+ }
859
+ finally {
860
+ unsubscribe();
546
861
  }
547
862
  }
548
- else {
549
- // --- Regular chat mode (direct provider) ---
550
- const prov = await createProvider(providerRef.current);
551
- let assistantContent = "";
552
- const updatedHistory = [...chatHistoryRef.current];
553
- await processChat({
554
- userInput: trimmed,
555
- messages: updatedHistory,
556
- model: modelRef.current,
557
- provider: prov,
558
- context: ctx,
559
- tools,
560
- signal: abortController.signal,
561
- callbacks: {
562
- onChunk: (text) => {
563
- if (abortRef.current)
564
- throw new Error("aborted");
565
- assistantContent += text;
566
- setStreamContent(assistantContent);
567
- setStreamLabel("streaming");
568
- },
569
- onToolCall: (tc) => {
570
- setStreamLabel(`tool: ${tc.name}`);
571
- },
572
- onToolResult: (tc, result) => {
573
- const preview = typeof result === "string"
574
- ? result
575
- : JSON.stringify(result).slice(0, 100);
576
- addMessage("tool", preview, { toolName: tc.name, toolArgs: tc.args });
577
- },
578
- },
579
- });
580
- setChatHistory(updatedHistory);
581
- if (assistantContent) {
582
- await addMessage("assistant", assistantContent);
863
+ flush();
864
+ refreshContextTokens();
865
+ if (budget) {
866
+ setUsage(budgetSummaryLine(budget));
867
+ const reason = budgetStopReason(budget);
868
+ if (reason) {
869
+ add("system", `Stopped: ${reason}`);
583
870
  }
584
871
  }
585
872
  }
586
- catch (err) {
587
- if (!abortRef.current) {
588
- const msg = err instanceof Error ? err.message : String(err);
589
- setError(msg);
590
- await addMessage("system", `Error: ${msg}`);
873
+ catch (error) {
874
+ if (!abort.signal.aborted) {
875
+ add("system", `Error: ${errorText(error)}`);
591
876
  }
592
877
  }
593
878
  finally {
594
- streamingRef.current = false;
595
- setStreaming(false);
596
- setStreamContent("");
597
- setStreamLabel("");
598
- flushPendingMessages();
599
- submittingRef.current = false;
600
- abortControllerRef.current = null;
601
- }
602
- }, [handleCommand, addMessage, flushPendingMessages, workspaceDir, enabledTools]);
603
- let acMatches = [];
604
- if (!streaming && inputValue.startsWith("/")) {
605
- const lower = inputValue.toLowerCase();
606
- const spaceIdx = lower.indexOf(" ");
607
- if (spaceIdx === -1) {
608
- // Command completion: /pro → /provider
609
- acMatches = Object.entries(COMMANDS)
610
- .filter(([cmd]) => cmd.startsWith(lower))
611
- .map(([cmd, desc]) => ({ cmd, desc }));
612
- }
613
- else {
614
- // Argument completion for specific commands
615
- const cmd = lower.slice(0, spaceIdx);
616
- const arg = lower.slice(spaceIdx + 1);
617
- if (cmd === "/provider") {
618
- acMatches = KNOWN_PROVIDERS
619
- .filter((p) => p.startsWith(arg))
620
- .map((p) => ({
621
- cmd: p,
622
- desc: DEFAULT_MODELS[p] ?? "",
623
- replaceAll: `/provider ${p}`,
624
- }));
625
- }
626
- else if (cmd === "/model") {
627
- acMatches = modelList
628
- .filter((m) => m.id.toLowerCase().startsWith(arg))
629
- .map((m) => ({
630
- cmd: m.id,
631
- desc: m.name !== m.id ? m.name : "",
632
- replaceAll: `/model ${m.id}`,
633
- }));
879
+ flush();
880
+ refreshContextTokens();
881
+ if (abort.signal.aborted) {
882
+ add("system", "Stopped. You can continue this conversation.");
883
+ }
884
+ setActiveTools({});
885
+ if (!value.startsWith("/") || value.startsWith("/compact")) {
886
+ try {
887
+ await persist();
888
+ }
889
+ catch (error) {
890
+ add("system", `Session could not be saved: ${errorText(error)}`);
891
+ }
634
892
  }
893
+ controller.current = null;
894
+ active.current = false;
895
+ setBusy(false);
896
+ setStatus("Ready");
635
897
  }
636
898
  }
637
- const acOpen = acMatches.length > 0;
638
- useInput((input, key) => {
639
- // Escape or Ctrl+C: cancel streaming, or exit when idle
640
- if (key.escape || (key.ctrl && input === "c")) {
641
- if (streaming) {
642
- abortRef.current = true;
643
- abortControllerRef.current?.abort();
644
- abortControllerRef.current = null;
645
- if (wsClientRef.current) {
646
- wsClientRef.current.stop(agentModeRef.current ? undefined : threadId);
899
+ const completions = [];
900
+ if (input.startsWith("/") && (!busy || input.startsWith("/agent"))) {
901
+ const space = input.indexOf(" ");
902
+ const name = space < 0 ? input : input.slice(0, space);
903
+ const argument = space < 0 ? "" : input.slice(space + 1).toLowerCase();
904
+ if (space < 0) {
905
+ for (const [value, description] of Object.entries(CHAT_COMMANDS)) {
906
+ if (value.startsWith(name.toLowerCase())) {
907
+ completions.push({ value, description });
647
908
  }
648
- // Immediately reset UI — don't wait for async cleanup
649
- setStreaming(false);
650
- setStreamContent("");
651
- setStreamLabel("");
652
- submittingRef.current = false;
653
909
  }
654
- else {
655
- saveSettings({ provider, model, agentMode }).then(() => exit());
910
+ }
911
+ else if (name === "/model") {
912
+ for (const item of models) {
913
+ if (`${item.provider} ${item.id} ${item.name}`
914
+ .toLowerCase()
915
+ .includes(argument)) {
916
+ completions.push({
917
+ value: `/model ${item.provider}/${item.id}`,
918
+ description: item.name
919
+ });
920
+ }
656
921
  }
657
- return;
658
922
  }
659
- // During agent streaming, allow tree navigation
660
- if (streaming && agentModeRef.current) {
661
- if (key.upArrow) {
662
- execState.navigate("up");
663
- return;
923
+ else if (name === "/agent") {
924
+ if (selectedAgentId && "main".includes(argument)) {
925
+ completions.push({
926
+ value: "/agent main",
927
+ description: "Return to the main conversation"
928
+ });
664
929
  }
665
- if (key.downArrow) {
666
- execState.navigate("down");
667
- return;
930
+ for (const thread of Object.values(agentThreads)) {
931
+ if (`${thread.id} ${thread.title}`.toLowerCase().includes(argument)) {
932
+ completions.push({
933
+ value: `/agent ${thread.id}`,
934
+ description: `${thread.status} · ${thread.title}`
935
+ });
936
+ }
668
937
  }
669
- if (key.return || key.rightArrow) {
670
- execState.toggleExpand();
671
- return;
938
+ }
939
+ else if (name === "/mode") {
940
+ for (const modeName of ["default", "auto", "plan"]) {
941
+ if (modeName.startsWith(argument)) {
942
+ completions.push({
943
+ value: `/mode ${modeName}`,
944
+ description: "Permission mode"
945
+ });
946
+ }
672
947
  }
673
- if (key.leftArrow) {
674
- execState.toggleExpand();
675
- return;
948
+ }
949
+ else if (name === "/resume") {
950
+ for (const session of sessions) {
951
+ if (`${session.id} ${session.title}`.toLowerCase().includes(argument)) {
952
+ completions.push({
953
+ value: `/resume ${session.id}`,
954
+ description: session.title
955
+ });
956
+ }
676
957
  }
958
+ }
959
+ }
960
+ const selected = Math.min(completionIndex, Math.max(0, completions.length - 1));
961
+ function changeInput(value) {
962
+ setInput(value);
963
+ setCompletionIndex(0);
964
+ }
965
+ function complete(direction) {
966
+ if (direction === "up" || direction === "down") {
967
+ setCompletionIndex((selected + (direction === "up" ? -1 : 1) + completions.length) %
968
+ completions.length);
677
969
  return;
678
970
  }
679
- if (streaming)
680
- return; // block other input while streaming
681
- // Autocomplete navigation
682
- if (acOpen) {
683
- if (key.upArrow) {
684
- setAcIndex(i => (i <= 0 ? acMatches.length - 1 : i - 1));
685
- return;
686
- }
687
- if (key.downArrow) {
688
- setAcIndex(i => (i >= acMatches.length - 1 ? 0 : i + 1));
689
- return;
971
+ const item = completions[selected];
972
+ if (!item) {
973
+ return;
974
+ }
975
+ if (direction === "accept") {
976
+ changeInput(item.value + (item.value.includes(" ") ? "" : " "));
977
+ return;
978
+ }
979
+ if (item.value.includes(" ")) {
980
+ void submit(item.value);
981
+ return;
982
+ }
983
+ if (["/model", "/agent", "/mode", "/resume", "/export"].includes(item.value)) {
984
+ changeInput(`${item.value} `);
985
+ }
986
+ else {
987
+ void submit(item.value);
988
+ }
989
+ }
990
+ function recall(direction) {
991
+ if (busy || !inputHistory.length) {
992
+ return;
993
+ }
994
+ if (historyIndex < 0) {
995
+ setDraft(input);
996
+ }
997
+ const next = direction === "up"
998
+ ? Math.min(historyIndex + 1, inputHistory.length - 1)
999
+ : Math.max(-1, historyIndex - 1);
1000
+ setHistoryIndex(next);
1001
+ changeInput(next < 0 ? draft : (inputHistory[next] ?? ""));
1002
+ }
1003
+ useInput((keyInput, key) => {
1004
+ // Ink re-registers this handler in an effect after each render, so a key
1005
+ // pressed as a prompt opens can reach the handler from before it. Read the
1006
+ // live prompt rather than the rendered state.
1007
+ const pending = prompts.current.current;
1008
+ if (pending) {
1009
+ const choice = pending.choices.find((item) => item.key === keyInput.toLowerCase());
1010
+ if (choice) {
1011
+ prompts.current.answer(choice.value);
690
1012
  }
691
- if (key.tab) {
692
- const selected = acMatches[acIndex] ?? acMatches[0];
693
- if (selected)
694
- setInputValue(selected.replaceAll ?? selected.cmd + " ");
695
- setAcIndex(0);
696
- return;
1013
+ if (key.escape) {
1014
+ prompts.current.answer("cancel");
697
1015
  }
698
- if (key.return) {
699
- const selected = acMatches[acIndex] ?? acMatches[0];
700
- if (selected) {
701
- const completed = selected.replaceAll ?? selected.cmd + " ";
702
- // Submit complete commands directly; partial ones go into input for editing
703
- if (completed.trimEnd() in COMMANDS) {
704
- handleSubmit(completed);
705
- }
706
- else {
707
- setInputValue(completed);
708
- }
709
- setAcIndex(0);
710
- }
1016
+ if (!(key.ctrl && keyInput === "c")) {
711
1017
  return;
712
1018
  }
713
1019
  }
714
- // History navigation (only when autocomplete is closed)
715
- if (!acOpen) {
716
- if (key.upArrow) {
717
- setInputHistory(hist => {
718
- if (hist.length === 0)
719
- return hist;
720
- setHistoryIndex(prev => {
721
- if (prev === -1)
722
- setHistoryDraft(inputValue);
723
- const next = Math.min(prev + 1, hist.length - 1);
724
- setInputValue(hist[next] ?? "");
725
- return next;
726
- });
727
- return hist;
728
- });
729
- return;
1020
+ if (key.ctrl && keyInput === "o") {
1021
+ setDetails((previous) => !previous);
1022
+ return;
1023
+ }
1024
+ if (key.escape || (key.ctrl && keyInput === "c")) {
1025
+ if (active.current) {
1026
+ controller.current?.abort();
1027
+ setStatus("Stopping…");
730
1028
  }
731
- if (key.downArrow) {
732
- setHistoryIndex(prev => {
733
- if (prev <= 0) {
734
- setInputValue(historyDraft);
735
- return -1;
736
- }
737
- const next = prev - 1;
738
- setInputHistory(hist => {
739
- setInputValue(hist[next] ?? "");
740
- return hist;
741
- });
742
- return next;
743
- });
744
- return;
1029
+ else if (input) {
1030
+ changeInput("");
1031
+ }
1032
+ else if (key.ctrl) {
1033
+ void quit();
745
1034
  }
746
1035
  }
747
1036
  });
748
- // ---------------------------------------------------------------------------
749
- // Render
750
- // ---------------------------------------------------------------------------
751
- const statusParts = [
752
- provider,
753
- model,
754
- agentMode ? `agent:${agentPlanner}` : null,
755
- wsUrl ? "ws" : null,
756
- ].filter(Boolean).join(" ");
757
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Static, { items: messages, children: (msg) => (_jsx(Box, { children: _jsx(ChatMessageItem, { msg: msg }) }, msg.id)) }), showHelp && _jsx(HelpPanel, {}), streaming && agentMode && (_jsx(ExecutionTree, { state: execState.state, treeActive: true })), streaming && !agentMode && (() => {
758
- // Truncate streaming preview to avoid overflowing the terminal's dynamic area.
759
- const maxPreviewLines = Math.max((process.stdout.rows ?? 24) - 4, 5);
760
- const lines = streamContent ? streamContent.split("\n") : [];
761
- const truncated = lines.length > maxPreviewLines
762
- ? lines.slice(-maxPreviewLines).join("\n")
763
- : streamContent;
764
- return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [truncated ? (_jsxs(Box, { children: [_jsx(Text, { color: "green", children: "● " }), _jsx(Text, { children: truncated })] })) : null, _jsx(Box, { children: _jsxs(Text, { color: "gray", dimColor: true, children: [" ", _jsx(Spinner, { type: "dots" }), " ", streamLabel || "thinking"] }) })] }));
765
- })(), error && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "red", children: "● " }), _jsx(Text, { color: "red", children: error })] })), acOpen && (_jsx(AutocompleteMenu, { matches: acMatches, selectedIndex: Math.min(acIndex, acMatches.length - 1) })), !streaming && (_jsxs(_Fragment, { children: [_jsx(Box, { children: _jsx(Text, { color: "gray", dimColor: true, children: "─".repeat(process.stdout.columns ?? 80) }) }), _jsxs(Box, { children: [_jsx(Text, { color: "magenta", dimColor: true, bold: true, children: "❯ " }), _jsx(ReadlineInput, { value: inputValue, onChange: setInputValue, onSubmit: handleSubmit })] }), _jsx(Box, { children: _jsx(Text, { color: "gray", dimColor: true, children: "─".repeat(process.stdout.columns ?? 80) }) }), _jsx(Box, { children: _jsxs(Text, { color: "gray", dimColor: true, children: [" ", statusParts] }) })] }))] }));
1037
+ const sidebarWidth = columns >= 112 ? 30 : 0;
1038
+ const width = Math.max(1, columns - 4 - sidebarWidth);
1039
+ const editorRows = Math.min(5, Math.max(1, terminalLines(input || " ", Math.max(1, columns - 6)).length));
1040
+ const menuRows = Math.min(5, completions.length, Math.max(0, rows - 14));
1041
+ const bodyHeight = Math.max(1, rows - editorRows - menuRows - 8);
1042
+ const menuStart = Math.max(0, selected - menuRows + 1);
1043
+ const work = [
1044
+ ...todos.map((todo) => `${todo.status === "completed" ? "✓" : todo.status === "in_progress" ? "›" : "○"} ${todo.content}`),
1045
+ ...Object.values(tasks),
1046
+ ...Object.values(activeTools).map((tool) => `› ${friendlyToolName(tool.name)}`)
1047
+ ];
1048
+ const selectedAgent = selectedAgentId
1049
+ ? agentThreads[selectedAgentId]
1050
+ : undefined;
1051
+ return (_jsxs(Box, { flexDirection: "column", width: columns, height: rows, overflow: "hidden", paddingX: 1, children: [_jsxs(Box, { height: 1, justifyContent: "space-between", children: [_jsx(Text, { bold: true, color: "cyan", children: "NodeTool" }), _jsxs(Text, { dimColor: true, wrap: "truncate", children: [terminalText(provider), " / ", terminalText(model)] })] }), _jsxs(Text, { dimColor: true, wrap: "truncate", children: [terminalText(workspaceDir), " \u00B7 ", connection, " \u00B7 ", mode] }), _jsx(Text, { dimColor: true, children: "─".repeat(Math.max(1, columns - 2)) }), _jsxs(Box, { height: bodyHeight, flexShrink: 0, children: [prompt ? (_jsxs(Box, { width: width, height: bodyHeight, flexDirection: "column", overflow: "hidden", children: [_jsx(Text, { bold: true, color: "yellow", children: terminalText(prompt.title) }), _jsx(Transcript, { mouseEvents: mouseEvents, screenY: 5, messages: [
1052
+ { id: "prompt", role: "system", content: prompt.body }
1053
+ ], live: "", width: width, height: Math.max(1, bodyHeight - 3), details: true, resetKey: prompt.title, startAtTop: true }), _jsx(Text, { color: "cyan", wrap: "truncate", children: prompt.choices
1054
+ .map((choice) => `[${choice.key}] ${choice.label}`)
1055
+ .join(" ") }), _jsx(Text, { dimColor: true, children: "Esc dismiss \u00B7 Ctrl+C stop" })] })) : selectedAgent ? (_jsxs(Box, { width: width, height: bodyHeight, flexDirection: "column", overflow: "hidden", children: [_jsxs(Text, { bold: true, color: "cyan", wrap: "truncate", children: [terminalText(selectedAgent.title), " \u00B7 ", selectedAgent.status] }), _jsx(Transcript, { mouseEvents: mouseEvents, screenY: 5, messages: selectedAgent.messages, live: "", width: width, height: Math.max(1, bodyHeight - 2), details: details, resetKey: `${selectedAgent.id}-${scrollKey}` }), _jsx(Text, { dimColor: true, children: "/agent main returns to the conversation" })] })) : (_jsx(Transcript, { mouseEvents: mouseEvents, messages: messages.slice(clearedCount), live: live, width: width, height: bodyHeight, details: details, resetKey: `${sessionId}-${scrollKey}` })), sidebarWidth > 0 && (_jsxs(Box, { width: sidebarWidth, paddingLeft: 2, flexDirection: "column", overflow: "hidden", children: [_jsx(Text, { bold: true, children: "Session" }), _jsx(Text, { dimColor: true, children: sessionId.slice(0, 12) }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: "Context" }), _jsx(Text, { dimColor: true, children: wsUrl
1056
+ ? "Managed by server"
1057
+ : contextTokens === null
1058
+ ? "Not measured yet"
1059
+ : `~${contextTokens.toLocaleString()} tokens` }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: "Work" }), _jsx(Text, { dimColor: true, children: terminalLines(terminalText(work.join("\n") || "No active tasks"), sidebarWidth - 2)
1060
+ .slice(0, Math.max(1, bodyHeight - 9))
1061
+ .join("\n") }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: "Agents" }), _jsx(Text, { dimColor: true, children: terminalLines(terminalText(Object.values(agentThreads)
1062
+ .map((thread) => `${thread.status === "running" ? "›" : thread.status === "completed" ? "✓" : "×"} ${thread.title}`)
1063
+ .join("\n") || "No sub-agents"), sidebarWidth - 2)
1064
+ .slice(0, 5)
1065
+ .join("\n") })] }))] }), _jsx(Box, { height: 1, children: _jsxs(Text, { color: prompt ? "yellow" : busy ? "cyan" : "gray", wrap: "truncate", children: [busy && _jsx(Spinner, { type: "dots" }), " ", terminalText(status), busy ? ` · ${elapsed}s · Esc stop` : usage ? ` · ${usage}` : ""] }) }), menuRows > 0 && (_jsx(Box, { height: menuRows, flexDirection: "column", children: completions.slice(menuStart, menuStart + menuRows).map((item, i) => (_jsxs(Text, { color: menuStart + i === selected ? "cyan" : "gray", wrap: "truncate", children: [menuStart + i === selected ? "› " : " ", terminalText(item.value), " ", terminalText(item.description)] }, item.value))) })), _jsx(Text, { dimColor: true, children: "─".repeat(Math.max(1, columns - 2)) }), _jsxs(Box, { height: editorRows, children: [_jsx(Text, { color: "cyan", children: "\u203A " }), _jsx(ReadlineInput, { value: input, onChange: changeInput, onSubmit: submit, onHistory: recall, onComplete: completions.length ? complete : undefined, width: Math.max(1, columns - 6), maxRows: editorRows, focus: !prompt, placeholder: busy ? "Draft your next message…" : "Ask NodeTool…" })] }), _jsx(Text, { dimColor: true, children: "─".repeat(Math.max(1, columns - 2)) }), _jsx(Text, { dimColor: true, wrap: "truncate", children: columns < 70
1066
+ ? "Enter send · Ctrl+J newline · /help"
1067
+ : "Enter send · Alt+Enter newline · Wheel scroll · Drag to copy · /help" })] }));
766
1068
  }
767
1069
  //# sourceMappingURL=app.js.map