@bubblebrain-ai/bubble 0.0.51 → 0.0.53

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 (481) hide show
  1. package/README.md +4 -12
  2. package/dist/agent/routing-catalog.js +3 -2
  3. package/dist/agent/subagent/router.js +5 -0
  4. package/dist/agent/subagent/runtime.d.ts +1 -0
  5. package/dist/agent/subagent/runtime.js +13 -1
  6. package/dist/agent/subagent-control.d.ts +4 -0
  7. package/dist/agent/subagent-control.js +1 -0
  8. package/dist/agent/subagent-store.js +2 -0
  9. package/dist/agent/workflow/control.d.ts +2 -0
  10. package/dist/agent/workflow/runs.d.ts +1 -0
  11. package/dist/agent/workflow/runs.js +3 -0
  12. package/dist/agent.d.ts +11 -1
  13. package/dist/agent.js +158 -57
  14. package/dist/bin.js +2 -44
  15. package/dist/config.d.ts +14 -1
  16. package/dist/config.js +45 -4
  17. package/dist/context/budget.d.ts +7 -1
  18. package/dist/context/budget.js +25 -2
  19. package/dist/context/compact-llm.d.ts +1 -1
  20. package/dist/context/compact-llm.js +5 -2
  21. package/dist/context/overflow.js +2 -0
  22. package/dist/context/projector.d.ts +8 -0
  23. package/dist/context/projector.js +55 -6
  24. package/dist/context/tool-output-truncate.d.ts +17 -1
  25. package/dist/context/tool-output-truncate.js +171 -49
  26. package/dist/feishu/agent-host/run-driver.js +3 -0
  27. package/dist/feishu/serve.js +2 -0
  28. package/dist/goal/command.d.ts +1 -1
  29. package/dist/goal/command.js +7 -2
  30. package/dist/main.d.ts +1 -1
  31. package/dist/main.js +38 -8
  32. package/dist/memory/db.js +0 -7
  33. package/dist/model-catalog.js +25 -6
  34. package/dist/model-config.js +3 -2
  35. package/dist/model-pricing.d.ts +24 -5
  36. package/dist/model-pricing.js +0 -0
  37. package/dist/model-selection.js +13 -4
  38. package/dist/network/chatgpt-transport.js +0 -34
  39. package/dist/network/errors.d.ts +19 -0
  40. package/dist/network/errors.js +24 -0
  41. package/dist/network/provider-transport.d.ts +0 -4
  42. package/dist/network/provider-transport.js +3 -35
  43. package/dist/network/retry.d.ts +6 -1
  44. package/dist/network/retry.js +9 -0
  45. package/dist/provider-model-policy.d.ts +16 -0
  46. package/dist/provider-model-policy.js +33 -0
  47. package/dist/provider-registry.d.ts +1 -1
  48. package/dist/provider-registry.js +80 -11
  49. package/dist/provider-transform.js +32 -4
  50. package/dist/provider.d.ts +6 -2
  51. package/dist/provider.js +100 -11
  52. package/dist/question/controller.js +5 -4
  53. package/dist/question/normalize.d.ts +6 -0
  54. package/dist/question/normalize.js +11 -0
  55. package/dist/rewind.d.ts +20 -0
  56. package/dist/rewind.js +29 -0
  57. package/dist/sdk/index.d.ts +1 -1
  58. package/dist/sdk/index.js +11 -3
  59. package/dist/session.d.ts +7 -0
  60. package/dist/session.js +20 -0
  61. package/dist/skills/discovery.js +18 -0
  62. package/dist/skills/registry.d.ts +12 -2
  63. package/dist/skills/registry.js +40 -5
  64. package/dist/skills/types.d.ts +2 -0
  65. package/dist/slash-commands/commands.js +120 -81
  66. package/dist/slash-commands/registry.d.ts +2 -1
  67. package/dist/slash-commands/registry.js +3 -0
  68. package/dist/slash-commands/types.d.ts +22 -4
  69. package/dist/stats/usage.d.ts +3 -1
  70. package/dist/stats/usage.js +22 -13
  71. package/dist/tasks/manager.d.ts +4 -2
  72. package/dist/tasks/manager.js +34 -9
  73. package/dist/tasks/promotion.d.ts +2 -2
  74. package/dist/tasks/promotion.js +2 -2
  75. package/dist/tools/agent-lifecycle.js +4 -0
  76. package/dist/tools/bash.d.ts +3 -1
  77. package/dist/tools/bash.js +13 -2
  78. package/dist/tools/grep.d.ts +5 -1
  79. package/dist/tools/grep.js +318 -69
  80. package/dist/tools/index.d.ts +3 -1
  81. package/dist/tools/index.js +1 -0
  82. package/dist/tools/question.js +5 -4
  83. package/dist/tools/read.js +40 -21
  84. package/dist/tui/animation-clock.d.ts +14 -0
  85. package/dist/tui/animation-clock.js +34 -0
  86. package/dist/tui/app.d.ts +176 -0
  87. package/dist/tui/app.js +1647 -0
  88. package/dist/tui/components/approval-dialog.d.ts +43 -0
  89. package/dist/tui/components/approval-dialog.js +247 -0
  90. package/dist/tui/components/bottom-sheet.d.ts +9 -0
  91. package/dist/tui/components/bottom-sheet.js +32 -0
  92. package/dist/tui/components/context-info.d.ts +54 -0
  93. package/dist/tui/components/context-info.js +388 -0
  94. package/dist/tui/components/feedback-dialog.d.ts +56 -0
  95. package/dist/tui/components/feedback-dialog.js +262 -0
  96. package/dist/tui/components/feishu-setup-dialog.d.ts +65 -0
  97. package/dist/tui/components/feishu-setup-dialog.js +332 -0
  98. package/dist/tui/components/image-preview.d.ts +32 -0
  99. package/dist/tui/components/image-preview.js +130 -0
  100. package/dist/tui/components/plan-dialog.d.ts +34 -0
  101. package/dist/tui/components/plan-dialog.js +196 -0
  102. package/dist/tui/components/provider-key-input.d.ts +18 -0
  103. package/dist/tui/components/provider-key-input.js +49 -0
  104. package/dist/tui/components/question-dialog.d.ts +25 -0
  105. package/dist/tui/components/question-dialog.js +263 -0
  106. package/dist/tui/components/responsive-transcript.d.ts +31 -0
  107. package/dist/tui/components/responsive-transcript.js +77 -0
  108. package/dist/tui/components/rewind-picker.d.ts +62 -0
  109. package/dist/tui/components/rewind-picker.js +317 -0
  110. package/dist/tui/components/session-picker.d.ts +49 -0
  111. package/dist/tui/components/session-picker.js +303 -0
  112. package/dist/tui/components/skills-panel.d.ts +59 -0
  113. package/dist/tui/components/skills-panel.js +568 -0
  114. package/dist/tui/components/stats-panel.d.ts +40 -0
  115. package/dist/tui/components/stats-panel.js +301 -0
  116. package/dist/tui/components/streaming-message.d.ts +69 -0
  117. package/dist/tui/components/streaming-message.js +393 -0
  118. package/dist/tui/components/subagent-inspector.d.ts +28 -0
  119. package/dist/tui/components/subagent-inspector.js +127 -0
  120. package/dist/tui/components/task-inspector.d.ts +26 -0
  121. package/dist/tui/components/task-inspector.js +70 -0
  122. package/dist/tui/components/tasks-pane.d.ts +88 -0
  123. package/dist/tui/components/tasks-pane.js +416 -0
  124. package/dist/tui/components/transcript.d.ts +96 -0
  125. package/dist/tui/components/transcript.js +771 -0
  126. package/dist/tui/components/welcome.d.ts +20 -0
  127. package/dist/tui/components/welcome.js +133 -0
  128. package/dist/tui/components/workflow-inspector.d.ts +30 -0
  129. package/dist/tui/components/workflow-inspector.js +136 -0
  130. package/dist/tui/composer-autocomplete.d.ts +61 -0
  131. package/dist/tui/composer-autocomplete.js +383 -0
  132. package/dist/tui/composer-style.d.ts +7 -0
  133. package/dist/tui/composer-style.js +29 -0
  134. package/dist/tui/controller/agent-event-reducer.d.ts +75 -0
  135. package/dist/tui/controller/agent-event-reducer.js +238 -0
  136. package/dist/tui/controller/composer-controller.d.ts +95 -0
  137. package/dist/tui/controller/composer-controller.js +502 -0
  138. package/dist/tui/controller/controller.d.ts +194 -0
  139. package/dist/tui/controller/controller.js +1035 -0
  140. package/dist/tui/controller/effects.d.ts +71 -0
  141. package/dist/tui/controller/effects.js +1 -0
  142. package/dist/tui/controller/goal-runtime-controller.d.ts +47 -0
  143. package/dist/tui/controller/goal-runtime-controller.js +202 -0
  144. package/dist/tui/controller/input-queue-machine.d.ts +71 -0
  145. package/dist/tui/controller/input-queue-machine.js +121 -0
  146. package/dist/tui/controller/intents.d.ts +56 -0
  147. package/dist/tui/controller/intents.js +6 -0
  148. package/dist/tui/controller/overlay-controller.d.ts +64 -0
  149. package/dist/tui/controller/overlay-controller.js +129 -0
  150. package/dist/tui/controller/ports.d.ts +57 -0
  151. package/dist/tui/controller/ports.js +9 -0
  152. package/dist/tui/controller/session-transition.d.ts +54 -0
  153. package/dist/tui/controller/session-transition.js +68 -0
  154. package/dist/tui/controller/state.d.ts +14 -0
  155. package/dist/tui/controller/state.js +36 -0
  156. package/dist/tui/controller/task-runtime-controller.d.ts +66 -0
  157. package/dist/tui/controller/task-runtime-controller.js +158 -0
  158. package/dist/tui/footer.d.ts +37 -0
  159. package/dist/tui/footer.js +80 -0
  160. package/dist/{tui-ink → tui/formatting}/edit-diff.d.ts +1 -1
  161. package/dist/{tui-ink → tui/formatting}/edit-diff.js +1 -1
  162. package/dist/{tui-ink/app-helpers.d.ts → tui/formatting/summary.d.ts} +4 -4
  163. package/dist/{tui-ink/app-helpers.js → tui/formatting/summary.js} +2 -2
  164. package/dist/tui/fullscreen.d.ts +48 -0
  165. package/dist/tui/fullscreen.js +255 -0
  166. package/dist/tui/image-display.d.ts +4 -0
  167. package/dist/tui/image-display.js +30 -12
  168. package/dist/tui/markdown-style.d.ts +5 -0
  169. package/dist/tui/markdown-style.js +23 -0
  170. package/dist/{tui-ink → tui/model}/composer-buffer.js +1 -1
  171. package/dist/tui/model/composer-types.d.ts +17 -0
  172. package/dist/tui/model/composer-types.js +1 -0
  173. package/dist/{tui-ink → tui/model}/display-history.d.ts +7 -1
  174. package/dist/{tui-ink → tui/model}/display-reconstruct.d.ts +1 -1
  175. package/dist/{tui-ink → tui/model}/display-reconstruct.js +54 -6
  176. package/dist/{tui-ink → tui/model}/file-mentions.d.ts +2 -0
  177. package/dist/{tui-ink → tui/model}/file-mentions.js +4 -0
  178. package/dist/tui/model/image-attachment.d.ts +15 -0
  179. package/dist/tui/model/image-attachment.js +1 -0
  180. package/dist/{tui-ink → tui/model}/image-paste.d.ts +7 -1
  181. package/dist/{tui-ink → tui/model}/image-paste.js +46 -0
  182. package/dist/tui/model/input-history.d.ts +1 -0
  183. package/dist/tui/model/input-history.js +1 -0
  184. package/dist/{tui-ink → tui/model}/input-queue.d.ts +1 -1
  185. package/dist/{tui-ink → tui/model}/recent-activity.js +1 -1
  186. package/dist/{tui-ink → tui/model}/subagent-view.d.ts +17 -2
  187. package/dist/{tui-ink → tui/model}/subagent-view.js +125 -4
  188. package/dist/{tui-ink → tui/model}/submit-dedupe.d.ts +1 -1
  189. package/dist/tui/model/theme-style.d.ts +10 -0
  190. package/dist/tui/model/theme-style.js +79 -0
  191. package/dist/{tui-ink → tui/model}/theme.d.ts +11 -23
  192. package/dist/{tui-ink → tui/model}/theme.js +23 -38
  193. package/dist/{tui-ink → tui/model}/trace-groups.d.ts +2 -0
  194. package/dist/{tui-ink → tui/model}/trace-groups.js +91 -30
  195. package/dist/tui/model/trace-interaction.d.ts +63 -0
  196. package/dist/tui/model/trace-interaction.js +87 -0
  197. package/dist/tui/model-picker-data.d.ts +1 -2
  198. package/dist/tui/model-picker-data.js +8 -1
  199. package/dist/tui/model-switch.js +2 -0
  200. package/dist/{tui-ink → tui}/run-session-picker.d.ts +2 -3
  201. package/dist/tui/run-session-picker.js +64 -0
  202. package/dist/tui/run.d.ts +72 -0
  203. package/dist/tui/run.js +139 -0
  204. package/dist/tui/terminal/multiplexer.d.ts +5 -0
  205. package/dist/tui/terminal/multiplexer.js +14 -0
  206. package/dist/tui/testing/fake-agent.d.ts +24 -0
  207. package/dist/tui/testing/fake-agent.js +22 -0
  208. package/dist/tui/testing/spy-host.d.ts +21 -0
  209. package/dist/tui/testing/spy-host.js +55 -0
  210. package/dist/tui/trace-groups.js +27 -6
  211. package/dist/types.d.ts +7 -0
  212. package/node_modules/@bubblebrain-ai/pi-tui/LICENSE +21 -0
  213. package/node_modules/@bubblebrain-ai/pi-tui/README.md +854 -0
  214. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/alt-screen-search.d.ts +26 -0
  215. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/alt-screen-search.d.ts.map +1 -0
  216. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/alt-screen-search.js +121 -0
  217. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/alt-screen-search.js.map +1 -0
  218. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/autocomplete.d.ts +86 -0
  219. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/autocomplete.d.ts.map +1 -0
  220. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/autocomplete.js +649 -0
  221. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/autocomplete.js.map +1 -0
  222. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/alt-screen-flash.d.ts +13 -0
  223. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/alt-screen-flash.d.ts.map +1 -0
  224. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/alt-screen-flash.js +36 -0
  225. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/alt-screen-flash.js.map +1 -0
  226. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/box.d.ts +22 -0
  227. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/box.d.ts.map +1 -0
  228. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/box.js +99 -0
  229. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/box.js.map +1 -0
  230. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/cancellable-loader.d.ts +22 -0
  231. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/cancellable-loader.d.ts.map +1 -0
  232. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/cancellable-loader.js +36 -0
  233. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/cancellable-loader.js.map +1 -0
  234. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/editor.d.ts +330 -0
  235. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/editor.d.ts.map +1 -0
  236. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/editor.js +2344 -0
  237. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/editor.js.map +1 -0
  238. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/h-stack.d.ts +7 -0
  239. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/h-stack.d.ts.map +1 -0
  240. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/h-stack.js +43 -0
  241. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/h-stack.js.map +1 -0
  242. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/image.d.ts +28 -0
  243. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/image.d.ts.map +1 -0
  244. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/image.js +82 -0
  245. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/image.js.map +1 -0
  246. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/input.d.ts +48 -0
  247. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/input.d.ts.map +1 -0
  248. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/input.js +390 -0
  249. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/input.js.map +1 -0
  250. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/loader.d.ts +31 -0
  251. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/loader.d.ts.map +1 -0
  252. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/loader.js +67 -0
  253. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/loader.js.map +1 -0
  254. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/markdown.d.ts +103 -0
  255. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/markdown.d.ts.map +1 -0
  256. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/markdown.js +797 -0
  257. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/markdown.js.map +1 -0
  258. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/scroll-view.d.ts +56 -0
  259. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/scroll-view.d.ts.map +1 -0
  260. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/scroll-view.js +179 -0
  261. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/scroll-view.js.map +1 -0
  262. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/select-list.d.ts +53 -0
  263. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/select-list.d.ts.map +1 -0
  264. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/select-list.js +175 -0
  265. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/select-list.js.map +1 -0
  266. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/settings-list.d.ts +50 -0
  267. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/settings-list.d.ts.map +1 -0
  268. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/settings-list.js +174 -0
  269. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/settings-list.js.map +1 -0
  270. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/spacer.d.ts +12 -0
  271. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/spacer.d.ts.map +1 -0
  272. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/spacer.js +22 -0
  273. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/spacer.js.map +1 -0
  274. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/stack.d.ts +32 -0
  275. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/stack.d.ts.map +1 -0
  276. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/stack.js +109 -0
  277. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/stack.js.map +1 -0
  278. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/text.d.ts +19 -0
  279. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/text.d.ts.map +1 -0
  280. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/text.js +81 -0
  281. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/text.js.map +1 -0
  282. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/truncated-text.d.ts +13 -0
  283. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/truncated-text.d.ts.map +1 -0
  284. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/truncated-text.js +48 -0
  285. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/truncated-text.js.map +1 -0
  286. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/v-stack.d.ts +8 -0
  287. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/v-stack.d.ts.map +1 -0
  288. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/v-stack.js +26 -0
  289. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/v-stack.js.map +1 -0
  290. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/editor-component.d.ts +39 -0
  291. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/editor-component.d.ts.map +1 -0
  292. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/editor-component.js +2 -0
  293. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/editor-component.js.map +1 -0
  294. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/fuzzy.d.ts +16 -0
  295. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/fuzzy.d.ts.map +1 -0
  296. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/fuzzy.js +110 -0
  297. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/fuzzy.js.map +1 -0
  298. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/index.d.ts +31 -0
  299. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/index.d.ts.map +1 -0
  300. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/index.js +42 -0
  301. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/index.js.map +1 -0
  302. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keybindings.d.ts +273 -0
  303. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keybindings.d.ts.map +1 -0
  304. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keybindings.js +231 -0
  305. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keybindings.js.map +1 -0
  306. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keys.d.ts +184 -0
  307. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keys.d.ts.map +1 -0
  308. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keys.js +1174 -0
  309. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keys.js.map +1 -0
  310. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/kill-ring.d.ts +28 -0
  311. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/kill-ring.d.ts.map +1 -0
  312. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/kill-ring.js +46 -0
  313. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/kill-ring.js.map +1 -0
  314. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/latex.d.ts +10 -0
  315. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/latex.d.ts.map +1 -0
  316. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/latex.js +1268 -0
  317. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/latex.js.map +1 -0
  318. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout-node.d.ts +40 -0
  319. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout-node.d.ts.map +1 -0
  320. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout-node.js +6 -0
  321. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout-node.js.map +1 -0
  322. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout.d.ts +42 -0
  323. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout.d.ts.map +1 -0
  324. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout.js +335 -0
  325. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout.js.map +1 -0
  326. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/native-modifiers.d.ts +3 -0
  327. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/native-modifiers.d.ts.map +1 -0
  328. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/native-modifiers.js +60 -0
  329. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/native-modifiers.js.map +1 -0
  330. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/stdin-buffer.d.ts +56 -0
  331. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/stdin-buffer.d.ts.map +1 -0
  332. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/stdin-buffer.js +363 -0
  333. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/stdin-buffer.js.map +1 -0
  334. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-colors.d.ts +10 -0
  335. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-colors.d.ts.map +1 -0
  336. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-colors.js +59 -0
  337. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-colors.js.map +1 -0
  338. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-image.d.ts +116 -0
  339. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-image.d.ts.map +1 -0
  340. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-image.js +518 -0
  341. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-image.js.map +1 -0
  342. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal.d.ts +142 -0
  343. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal.d.ts.map +1 -0
  344. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal.js +544 -0
  345. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal.js.map +1 -0
  346. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/testing.d.ts +11 -0
  347. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/testing.d.ts.map +1 -0
  348. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/testing.js +11 -0
  349. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/testing.js.map +1 -0
  350. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.d.ts +140 -0
  351. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.d.ts.map +1 -0
  352. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.js +1334 -0
  353. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.js.map +1 -0
  354. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-main-screen.d.ts +39 -0
  355. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-main-screen.d.ts.map +1 -0
  356. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-main-screen.js +536 -0
  357. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-main-screen.js.map +1 -0
  358. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.d.ts +338 -0
  359. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.d.ts.map +1 -0
  360. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.js +1016 -0
  361. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.js.map +1 -0
  362. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/undo-stack.d.ts +17 -0
  363. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/undo-stack.d.ts.map +1 -0
  364. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/undo-stack.js +27 -0
  365. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/undo-stack.js.map +1 -0
  366. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/utils.d.ts +96 -0
  367. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/utils.d.ts.map +1 -0
  368. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/utils.js +1193 -0
  369. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/utils.js.map +1 -0
  370. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/word-navigation.d.ts +25 -0
  371. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/word-navigation.d.ts.map +1 -0
  372. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/word-navigation.js +96 -0
  373. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/word-navigation.js.map +1 -0
  374. package/node_modules/@bubblebrain-ai/pi-tui/dist/test/virtual-terminal.d.ts +73 -0
  375. package/node_modules/@bubblebrain-ai/pi-tui/dist/test/virtual-terminal.d.ts.map +1 -0
  376. package/node_modules/@bubblebrain-ai/pi-tui/dist/test/virtual-terminal.js +188 -0
  377. package/node_modules/@bubblebrain-ai/pi-tui/dist/test/virtual-terminal.js.map +1 -0
  378. package/node_modules/@bubblebrain-ai/pi-tui/native/darwin/README.md +20 -0
  379. package/node_modules/@bubblebrain-ai/pi-tui/native/darwin/build.sh +62 -0
  380. package/node_modules/@bubblebrain-ai/pi-tui/native/darwin/prebuilds/darwin-arm64/darwin-modifiers.node +0 -0
  381. package/node_modules/@bubblebrain-ai/pi-tui/native/darwin/prebuilds/darwin-x64/darwin-modifiers.node +0 -0
  382. package/node_modules/@bubblebrain-ai/pi-tui/native/darwin/src/darwin-modifiers.c +70 -0
  383. package/node_modules/@bubblebrain-ai/pi-tui/native/win32/README.md +22 -0
  384. package/node_modules/@bubblebrain-ai/pi-tui/native/win32/build.mjs +229 -0
  385. package/node_modules/@bubblebrain-ai/pi-tui/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
  386. package/node_modules/@bubblebrain-ai/pi-tui/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
  387. package/node_modules/@bubblebrain-ai/pi-tui/native/win32/src/win32-console-mode.c +119 -0
  388. package/node_modules/@bubblebrain-ai/pi-tui/package.json +66 -0
  389. package/node_modules/get-east-asian-width/index.d.ts +60 -0
  390. package/node_modules/get-east-asian-width/index.js +30 -0
  391. package/node_modules/get-east-asian-width/license +9 -0
  392. package/node_modules/get-east-asian-width/lookup-data.js +21 -0
  393. package/node_modules/get-east-asian-width/lookup.js +138 -0
  394. package/node_modules/get-east-asian-width/package.json +71 -0
  395. package/node_modules/get-east-asian-width/readme.md +65 -0
  396. package/node_modules/get-east-asian-width/utilities.js +24 -0
  397. package/package.json +23 -10
  398. package/packages/pi-tui/node_modules/marked/LICENSE +44 -0
  399. package/packages/pi-tui/node_modules/marked/README.md +115 -0
  400. package/packages/pi-tui/node_modules/marked/bin/main.js +284 -0
  401. package/packages/pi-tui/node_modules/marked/bin/marked.js +16 -0
  402. package/packages/pi-tui/node_modules/marked/lib/marked.d.ts +760 -0
  403. package/packages/pi-tui/node_modules/marked/lib/marked.esm.js +77 -0
  404. package/packages/pi-tui/node_modules/marked/lib/marked.esm.js.map +7 -0
  405. package/packages/pi-tui/node_modules/marked/lib/marked.umd.js +79 -0
  406. package/packages/pi-tui/node_modules/marked/lib/marked.umd.js.map +7 -0
  407. package/packages/pi-tui/node_modules/marked/man/marked.1 +113 -0
  408. package/packages/pi-tui/node_modules/marked/man/marked.1.md +93 -0
  409. package/packages/pi-tui/node_modules/marked/package.json +103 -0
  410. package/dist/tui-ink/app.d.ts +0 -76
  411. package/dist/tui-ink/app.js +0 -2377
  412. package/dist/tui-ink/approval/approval-dialog.d.ts +0 -13
  413. package/dist/tui-ink/approval/approval-dialog.js +0 -184
  414. package/dist/tui-ink/approval/diff-view.d.ts +0 -7
  415. package/dist/tui-ink/approval/diff-view.js +0 -44
  416. package/dist/tui-ink/approval/select.d.ts +0 -35
  417. package/dist/tui-ink/approval/select.js +0 -98
  418. package/dist/tui-ink/code-highlight.d.ts +0 -8
  419. package/dist/tui-ink/code-highlight.js +0 -121
  420. package/dist/tui-ink/command-palette.d.ts +0 -32
  421. package/dist/tui-ink/command-palette.js +0 -158
  422. package/dist/tui-ink/compaction-progress.d.ts +0 -19
  423. package/dist/tui-ink/compaction-progress.js +0 -74
  424. package/dist/tui-ink/detect-theme.d.ts +0 -1
  425. package/dist/tui-ink/detect-theme.js +0 -1
  426. package/dist/tui-ink/feedback-dialog.d.ts +0 -19
  427. package/dist/tui-ink/feedback-dialog.js +0 -133
  428. package/dist/tui-ink/feishu-setup-picker.d.ts +0 -5
  429. package/dist/tui-ink/feishu-setup-picker.js +0 -271
  430. package/dist/tui-ink/footer.d.ts +0 -26
  431. package/dist/tui-ink/footer.js +0 -51
  432. package/dist/tui-ink/input-box.d.ts +0 -189
  433. package/dist/tui-ink/input-box.js +0 -1279
  434. package/dist/tui-ink/input-history.d.ts +0 -1
  435. package/dist/tui-ink/input-history.js +0 -1
  436. package/dist/tui-ink/markdown.d.ts +0 -103
  437. package/dist/tui-ink/markdown.js +0 -747
  438. package/dist/tui-ink/message-list.d.ts +0 -49
  439. package/dist/tui-ink/message-list.js +0 -841
  440. package/dist/tui-ink/model-picker.d.ts +0 -143
  441. package/dist/tui-ink/model-picker.js +0 -966
  442. package/dist/tui-ink/plan-confirm.d.ts +0 -7
  443. package/dist/tui-ink/plan-confirm.js +0 -115
  444. package/dist/tui-ink/question-dialog.d.ts +0 -8
  445. package/dist/tui-ink/question-dialog.js +0 -135
  446. package/dist/tui-ink/rewind-picker.d.ts +0 -8
  447. package/dist/tui-ink/rewind-picker.js +0 -102
  448. package/dist/tui-ink/run-session-picker.js +0 -22
  449. package/dist/tui-ink/run.d.ts +0 -62
  450. package/dist/tui-ink/run.js +0 -103
  451. package/dist/tui-ink/session-picker.d.ts +0 -10
  452. package/dist/tui-ink/session-picker.js +0 -113
  453. package/dist/tui-ink/stats-panel.d.ts +0 -11
  454. package/dist/tui-ink/stats-panel.js +0 -65
  455. package/dist/tui-ink/subagent-inspector.d.ts +0 -21
  456. package/dist/tui-ink/subagent-inspector.js +0 -205
  457. package/dist/tui-ink/use-terminal-size.d.ts +0 -4
  458. package/dist/tui-ink/use-terminal-size.js +0 -48
  459. package/dist/tui-ink/waiting-indicator.d.ts +0 -12
  460. package/dist/tui-ink/waiting-indicator.js +0 -129
  461. package/dist/tui-ink/welcome.d.ts +0 -25
  462. package/dist/tui-ink/welcome.js +0 -88
  463. package/dist/tui-ink/wordmark.d.ts +0 -8
  464. package/dist/tui-ink/wordmark.js +0 -103
  465. /package/dist/{tui-ink → tui/model}/composer-buffer.d.ts +0 -0
  466. /package/dist/{tui-ink → tui/model}/display-history.js +0 -0
  467. /package/dist/{tui-ink → tui/model}/input-queue.js +0 -0
  468. /package/dist/{tui-ink → tui/model}/key-events.d.ts +0 -0
  469. /package/dist/{tui-ink → tui/model}/key-events.js +0 -0
  470. /package/dist/{tui-ink → tui/model}/paste-operation-tracker.d.ts +0 -0
  471. /package/dist/{tui-ink → tui/model}/paste-operation-tracker.js +0 -0
  472. /package/dist/{tui-ink → tui/model}/recent-activity.d.ts +0 -0
  473. /package/dist/{tui-ink → tui/model}/submit-dedupe.js +0 -0
  474. /package/dist/{tui-ink → tui/model}/sync-output.d.ts +0 -0
  475. /package/dist/{tui-ink → tui/model}/sync-output.js +0 -0
  476. /package/dist/{tui-ink → tui/model}/terminal-env.d.ts +0 -0
  477. /package/dist/{tui-ink → tui/model}/terminal-env.js +0 -0
  478. /package/dist/{tui-ink → tui/model}/terminal-mouse.d.ts +0 -0
  479. /package/dist/{tui-ink → tui/model}/terminal-mouse.js +0 -0
  480. /package/dist/{tui-ink → tui/model}/width.d.ts +0 -0
  481. /package/dist/{tui-ink → tui/model}/width.js +0 -0
@@ -15,7 +15,9 @@ export interface BashToolOptions {
15
15
  */
16
16
  processManager?: ProcessManager;
17
17
  allowBackgroundTasks?: boolean;
18
- /** Ctrl+G send-to-background requests, keyed by toolCall id (design §2.5). */
18
+ /** Ctrl+B send-to-background requests, keyed by toolCall id (design §2.5). */
19
19
  promotionChannel?: PromotionChannel;
20
+ /** Move a still-running foreground command into Tasks after this budget. */
21
+ autoBackgroundAfterMs?: number | false;
20
22
  }
21
23
  export declare function createBashTool(cwd: string, approval?: ApprovalController, _fileState?: FileStateTracker, options?: BashToolOptions): ToolRegistryEntry;
@@ -126,6 +126,7 @@ export function createBashTool(cwd, approval, _fileState, options = {}) {
126
126
  let terminalError;
127
127
  let resolved = false;
128
128
  let timeoutHandle;
129
+ let autoBackgroundHandle;
129
130
  let forceKillHandle;
130
131
  let settleHandle;
131
132
  let postExitHandle;
@@ -174,6 +175,8 @@ export function createBashTool(cwd, approval, _fileState, options = {}) {
174
175
  const cleanup = () => {
175
176
  if (timeoutHandle)
176
177
  clearTimeout(timeoutHandle);
178
+ if (autoBackgroundHandle)
179
+ clearTimeout(autoBackgroundHandle);
177
180
  if (forceKillHandle)
178
181
  clearTimeout(forceKillHandle);
179
182
  if (settleHandle)
@@ -208,7 +211,7 @@ export function createBashTool(cwd, approval, _fileState, options = {}) {
208
211
  resolved = true;
209
212
  cleanup();
210
213
  if (terminal === "promoted") {
211
- // Ctrl+G promotion (design §2.5): the process lives on under the
214
+ // Ctrl+B/automatic promotion (design §2.5): the process lives on under the
212
215
  // task manager — skip the group kill AND the stream destruction
213
216
  // (the manager attached its own data listeners in adoptTask).
214
217
  resolve({
@@ -347,7 +350,7 @@ export function createBashTool(cwd, approval, _fileState, options = {}) {
347
350
  terminal = "exit";
348
351
  finish();
349
352
  };
350
- // Ctrl+G promotion (design §2.5): flag-flip into the task manager.
353
+ // Ctrl+B/automatic promotion (design §2.5): flag-flip into the task manager.
351
354
  // Registered only for background-capable hosts; a no-op once any
352
355
  // terminal state is set (benign race with exit/timeout/cancel).
353
356
  const promoteToBackground = () => {
@@ -381,6 +384,14 @@ export function createBashTool(cwd, approval, _fileState, options = {}) {
381
384
  if (backgroundEnabled && options.promotionChannel && ctx.toolCall?.id) {
382
385
  unregisterPromotion = options.promotionChannel.register(ctx.toolCall.id, promoteToBackground);
383
386
  }
387
+ if (backgroundEnabled
388
+ && options.autoBackgroundAfterMs !== false
389
+ && typeof options.autoBackgroundAfterMs === "number"
390
+ && options.autoBackgroundAfterMs > 0
391
+ && options.autoBackgroundAfterMs < timeoutSec * 1000) {
392
+ autoBackgroundHandle = setTimeout(promoteToBackground, options.autoBackgroundAfterMs);
393
+ autoBackgroundHandle.unref?.();
394
+ }
384
395
  timeoutHandle = setTimeout(timeoutChild, timeoutSec * 1000);
385
396
  if (ctx.abortSignal?.aborted)
386
397
  abortChild();
@@ -1,5 +1,9 @@
1
1
  /**
2
- * Grep tool - search file contents using ripgrep.
2
+ * Grep tool - bounded, streaming search using ripgrep.
3
+ *
4
+ * The limits in this file are model-context safety boundaries, not UI-only
5
+ * truncation. We stop rg as soon as a global budget is filled so a repository
6
+ * search can never materialize a multi-megabyte tool result in memory/history.
3
7
  */
4
8
  import type { ToolRegistryEntry } from "../types.js";
5
9
  export declare function createGrepTool(cwd: string): ToolRegistryEntry;
@@ -1,30 +1,55 @@
1
1
  /**
2
- * Grep tool - search file contents using ripgrep.
2
+ * Grep tool - bounded, streaming search using ripgrep.
3
+ *
4
+ * The limits in this file are model-context safety boundaries, not UI-only
5
+ * truncation. We stop rg as soon as a global budget is filled so a repository
6
+ * search can never materialize a multi-megabyte tool result in memory/history.
3
7
  */
4
- import { execFile } from "node:child_process";
8
+ import { spawn } from "node:child_process";
5
9
  import { resolve as resolvePath } from "node:path";
10
+ import { StringDecoder } from "node:string_decoder";
6
11
  import { isSensitivePath } from "./sensitive-paths.js";
7
12
  import { analyzeToolIntent } from "../agent/tool-intent.js";
8
13
  import { resolveToolPath } from "./path-utils.js";
9
- const MAX_MATCHES = 100;
14
+ const DEFAULT_MATCH_LIMIT = 100;
15
+ const MAX_MATCH_LIMIT = 1_000;
16
+ const MAX_RENDERED_LINE_CODE_POINTS = 1_000;
17
+ const MAX_RESULT_BYTES = 40 * 1024;
18
+ const MAX_RAW_STDOUT_BYTES = 5 * 1024 * 1024;
19
+ const MAX_STDERR_BYTES = 64 * 1024;
20
+ const MAX_FILE_SIZE = "5M";
21
+ const MAX_FILE_SIZE_BYTES = 5 * 1024 * 1024;
22
+ const GREP_TIMEOUT_MS = 30_000;
23
+ const KILL_GRACE_MS = 250;
24
+ const RESULT_NOTICE_RESERVE_BYTES = 1_024;
10
25
  export function createGrepTool(cwd) {
11
26
  return {
12
27
  name: "grep",
13
28
  readOnly: true,
14
29
  effect: "read",
15
- description: `Search file contents using regex (via ripgrep). Use this instead of running grep, rg, or ripgrep through bash. Returns up to ${MAX_MATCHES} matches.`,
30
+ description: [
31
+ "Search file contents using regex (via ripgrep).",
32
+ `Returns at most ${DEFAULT_MATCH_LIMIT} matches by default (explicit limit is capped at ${MAX_MATCH_LIMIT}),`,
33
+ `${MAX_RESULT_BYTES / 1024}KiB total, and ${MAX_RENDERED_LINE_CODE_POINTS} Unicode characters per rendered match.`,
34
+ `Files larger than ${MAX_FILE_SIZE} are skipped. Narrow path/glob/pattern when a result is marked truncated.`,
35
+ ].join(" "),
16
36
  parameters: {
17
37
  type: "object",
18
38
  properties: {
19
39
  pattern: { type: "string", description: "Regex pattern to search for" },
20
40
  path: { type: "string", description: "Directory or file to search in (optional, default: cwd)" },
21
41
  glob: { type: "string", description: "Glob pattern to filter files (optional, e.g. '*.ts')" },
42
+ limit: {
43
+ type: "number",
44
+ description: `Maximum matches to return (default ${DEFAULT_MATCH_LIMIT}, hard maximum ${MAX_MATCH_LIMIT})`,
45
+ },
22
46
  },
23
47
  required: ["pattern"],
24
48
  },
25
- async execute(args) {
49
+ async execute(args, ctx) {
26
50
  const searchPath = args.path ? resolveToolPath(cwd, args.path) : cwd;
27
51
  const pattern = String(args.pattern);
52
+ const matchLimit = normalizeMatchLimit(args.limit);
28
53
  const intent = analyzeToolIntent({
29
54
  name: "grep",
30
55
  parsedArgs: {
@@ -48,72 +73,296 @@ export function createGrepTool(cwd) {
48
73
  },
49
74
  };
50
75
  }
51
- const rgArgs = ["--json", "-n", "--max-count", String(MAX_MATCHES), pattern];
52
- if (args.glob) {
76
+ const rgArgs = [
77
+ "--json",
78
+ "--line-number",
79
+ "--color=never",
80
+ "--max-columns",
81
+ String(MAX_RENDERED_LINE_CODE_POINTS),
82
+ "--max-columns-preview",
83
+ "--max-filesize",
84
+ MAX_FILE_SIZE,
85
+ ];
86
+ if (args.glob)
53
87
  rgArgs.push("--glob", String(args.glob));
54
- }
55
- rgArgs.push(searchPath);
56
- return new Promise((resolve) => {
57
- execFile("rg", rgArgs, { cwd, maxBuffer: 10 * 1024 * 1024 }, (_error, stdout, _stderr) => {
58
- // rg returns exit code 1 when no matches found, which is not an error for us
59
- const lines = stdout.split("\n").filter((l) => l.trim() !== "");
60
- const matches = [];
61
- const matchedPaths = new Set();
62
- for (const line of lines) {
63
- try {
64
- const obj = JSON.parse(line);
65
- if (obj.type === "match") {
66
- const path = obj.data.path.text;
67
- const lineNum = obj.data.line_number;
68
- const text = obj.data.lines.text?.trim() ?? "";
69
- if (typeof path === "string" && path.trim()) {
70
- matchedPaths.add(resolvePath(cwd, path));
71
- }
72
- matches.push(`${path}:${lineNum}: ${text}`);
73
- }
74
- }
75
- catch {
76
- // ignore parse errors
77
- }
78
- }
79
- if (matches.length === 0) {
80
- resolve({
81
- content: "No matches found.",
82
- status: "no_match",
83
- metadata: {
84
- kind: "search",
85
- path: searchPath,
86
- pattern,
87
- matches: 0,
88
- truncated: false,
89
- searchSignature: intent.search?.signature,
90
- searchFamily: intent.search?.familyKey,
91
- paths: [],
92
- },
93
- });
94
- return;
95
- }
96
- let output = matches.join("\n");
97
- const truncated = matches.length >= MAX_MATCHES;
98
- if (matches.length >= MAX_MATCHES) {
99
- output += `\n[More than ${MAX_MATCHES} matches, output truncated]`;
100
- }
101
- resolve({
102
- content: output,
103
- status: truncated ? "partial" : "success",
104
- metadata: {
105
- kind: "search",
106
- path: searchPath,
107
- pattern,
108
- matches: matches.length,
109
- truncated,
110
- searchSignature: intent.search?.signature,
111
- searchFamily: intent.search?.familyKey,
112
- paths: [...matchedPaths],
113
- },
114
- });
115
- });
88
+ rgArgs.push("--", pattern, searchPath);
89
+ return executeBoundedGrep({
90
+ cwd,
91
+ searchPath,
92
+ pattern,
93
+ rgArgs,
94
+ matchLimit,
95
+ abortSignal: ctx.abortSignal,
96
+ baseMetadata: {
97
+ kind: "search",
98
+ path: searchPath,
99
+ pattern,
100
+ searchSignature: intent.search?.signature,
101
+ searchFamily: intent.search?.familyKey,
102
+ maxFileSizeBytes: MAX_FILE_SIZE_BYTES,
103
+ },
116
104
  });
117
105
  },
118
106
  };
119
107
  }
108
+ function executeBoundedGrep(options) {
109
+ return new Promise((resolve) => {
110
+ const child = spawn("rg", options.rgArgs, {
111
+ cwd: options.cwd,
112
+ stdio: ["ignore", "pipe", "pipe"],
113
+ });
114
+ const decoder = new StringDecoder("utf8");
115
+ const matches = [];
116
+ let stdoutBuffer = "";
117
+ let stdoutBytes = 0;
118
+ let stderrBytes = 0;
119
+ let stderr = "";
120
+ let stopReason;
121
+ let spawnError;
122
+ let settled = false;
123
+ let killTimer;
124
+ const requestStop = (reason) => {
125
+ if (stopReason)
126
+ return;
127
+ stopReason = reason;
128
+ if (child.exitCode === null && child.signalCode === null) {
129
+ child.kill("SIGTERM");
130
+ killTimer = setTimeout(() => {
131
+ if (child.exitCode === null && child.signalCode === null)
132
+ child.kill("SIGKILL");
133
+ }, KILL_GRACE_MS);
134
+ killTimer.unref?.();
135
+ }
136
+ };
137
+ const processJsonLine = (line) => {
138
+ if (!line.trim() || stopReason)
139
+ return;
140
+ try {
141
+ const event = JSON.parse(line);
142
+ if (event.type !== "match")
143
+ return;
144
+ const rawPath = decodeRgField(event.data?.path);
145
+ const lineNumber = event.data?.line_number;
146
+ const rawText = decodeRgField(event.data?.lines, "[non-UTF-8 match bytes omitted]") ?? "";
147
+ const displayPath = rawPath || "<non-UTF-8 path>";
148
+ const normalizedText = rawText.replace(/\r\n/g, "\n").replace(/\r/g, "").replace(/\n$/, "");
149
+ const rendered = truncateCodePoints(`${displayPath}:${typeof lineNumber === "number" ? lineNumber : "?"}: ${normalizedText}`, MAX_RENDERED_LINE_CODE_POINTS);
150
+ matches.push({
151
+ rendered,
152
+ ...(rawPath ? { absolutePath: resolvePath(options.cwd, rawPath) } : {}),
153
+ });
154
+ // We stop at N rather than pretending we proved there are N+1 matches.
155
+ // The notice says "limit reached / may be incomplete", which is honest
156
+ // even for the exact-fit case and avoids continuing a huge tree walk.
157
+ if (matches.length >= options.matchLimit)
158
+ requestStop("match_limit");
159
+ }
160
+ catch {
161
+ // A truncated/invalid JSON event is not useful to the model. Raw byte
162
+ // accounting remains authoritative and will stop pathological lines.
163
+ }
164
+ };
165
+ const processDecodedText = (text) => {
166
+ stdoutBuffer += text;
167
+ while (!stopReason) {
168
+ const newline = stdoutBuffer.indexOf("\n");
169
+ if (newline < 0)
170
+ break;
171
+ const line = stdoutBuffer.slice(0, newline);
172
+ stdoutBuffer = stdoutBuffer.slice(newline + 1);
173
+ processJsonLine(line);
174
+ }
175
+ };
176
+ child.stdout.on("data", (value) => {
177
+ if (stopReason)
178
+ return;
179
+ const chunk = Buffer.isBuffer(value) ? value : Buffer.from(value);
180
+ const remaining = Math.max(0, MAX_RAW_STDOUT_BYTES - stdoutBytes);
181
+ const accepted = chunk.subarray(0, remaining);
182
+ stdoutBytes += accepted.length;
183
+ if (accepted.length > 0)
184
+ processDecodedText(decoder.write(accepted));
185
+ if (accepted.length < chunk.length || stdoutBytes >= MAX_RAW_STDOUT_BYTES) {
186
+ requestStop("stdout_limit");
187
+ }
188
+ });
189
+ child.stdout.on("end", () => {
190
+ if (stopReason)
191
+ return;
192
+ processDecodedText(decoder.end());
193
+ if (stdoutBuffer) {
194
+ processJsonLine(stdoutBuffer);
195
+ stdoutBuffer = "";
196
+ }
197
+ });
198
+ child.stderr.on("data", (value) => {
199
+ if (stopReason)
200
+ return;
201
+ const chunk = Buffer.isBuffer(value) ? value : Buffer.from(value);
202
+ const remaining = Math.max(0, MAX_STDERR_BYTES - stderrBytes);
203
+ const accepted = chunk.subarray(0, remaining);
204
+ stderrBytes += accepted.length;
205
+ if (accepted.length > 0)
206
+ stderr += accepted.toString("utf8");
207
+ if (accepted.length < chunk.length || stderrBytes >= MAX_STDERR_BYTES) {
208
+ requestStop("stderr_limit");
209
+ }
210
+ });
211
+ const timeout = setTimeout(() => requestStop("timeout"), GREP_TIMEOUT_MS);
212
+ timeout.unref?.();
213
+ const onAbort = () => requestStop("aborted");
214
+ options.abortSignal?.addEventListener("abort", onAbort, { once: true });
215
+ if (options.abortSignal?.aborted)
216
+ onAbort();
217
+ child.on("error", (error) => {
218
+ spawnError = error;
219
+ });
220
+ child.on("close", (code) => {
221
+ if (settled)
222
+ return;
223
+ settled = true;
224
+ clearTimeout(timeout);
225
+ if (killTimer)
226
+ clearTimeout(killTimer);
227
+ options.abortSignal?.removeEventListener("abort", onAbort);
228
+ if (stopReason === "aborted") {
229
+ resolve({
230
+ content: "Search cancelled.",
231
+ isError: true,
232
+ status: "cancelled",
233
+ metadata: { ...options.baseMetadata, matches: 0, truncated: false, paths: [] },
234
+ });
235
+ return;
236
+ }
237
+ if (spawnError) {
238
+ resolve({
239
+ content: `Error calling grep: ${spawnError.message}`,
240
+ isError: true,
241
+ status: "command_error",
242
+ metadata: { ...options.baseMetadata, matches: 0, truncated: false, paths: [] },
243
+ });
244
+ return;
245
+ }
246
+ const naturalCommandError = !stopReason && code !== 0 && code !== 1;
247
+ const stoppedWithoutMatches = !!stopReason && matches.length === 0;
248
+ if (matches.length === 0 && !stoppedWithoutMatches && !naturalCommandError) {
249
+ resolve({
250
+ content: "No matches found.",
251
+ status: "no_match",
252
+ metadata: { ...options.baseMetadata, matches: 0, truncated: false, paths: [] },
253
+ });
254
+ return;
255
+ }
256
+ if (naturalCommandError && matches.length === 0) {
257
+ resolve({
258
+ content: `Error calling grep: ${stderr.trim() || `ripgrep exited with code ${code}`}`,
259
+ isError: true,
260
+ status: "command_error",
261
+ metadata: { ...options.baseMetadata, matches: 0, truncated: false, paths: [] },
262
+ });
263
+ return;
264
+ }
265
+ const formatted = formatMatches(matches, options.matchLimit, stopReason, naturalCommandError ? stderr.trim() : "");
266
+ const truncated = !!stopReason || formatted.byteTruncated || naturalCommandError;
267
+ const paths = [...new Set(formatted.emittedMatches.flatMap((match) => match.absolutePath ? [match.absolutePath] : []))];
268
+ resolve({
269
+ content: formatted.content,
270
+ ...(naturalCommandError && matches.length === 0 ? { isError: true } : {}),
271
+ status: stopReason === "timeout" && matches.length === 0
272
+ ? "timeout"
273
+ : truncated
274
+ ? "partial"
275
+ : "success",
276
+ metadata: {
277
+ ...options.baseMetadata,
278
+ matches: formatted.emittedMatches.length,
279
+ collectedMatches: matches.length,
280
+ truncated,
281
+ truncationReason: stopReason ?? (formatted.byteTruncated ? "result_bytes" : naturalCommandError ? "command_error" : undefined),
282
+ rawStdoutBytes: stdoutBytes,
283
+ paths,
284
+ },
285
+ });
286
+ });
287
+ });
288
+ }
289
+ function formatMatches(matches, matchLimit, stopReason, commandWarning) {
290
+ const emitted = [];
291
+ let bodyBytes = 0;
292
+ const bodyBudget = MAX_RESULT_BYTES - RESULT_NOTICE_RESERVE_BYTES;
293
+ for (const match of matches) {
294
+ const lineBytes = Buffer.byteLength(match.rendered, "utf8") + (emitted.length > 0 ? 1 : 0);
295
+ if (bodyBytes + lineBytes > bodyBudget)
296
+ break;
297
+ emitted.push(match);
298
+ bodyBytes += lineBytes;
299
+ }
300
+ const byteTruncated = emitted.length < matches.length;
301
+ const notices = [];
302
+ if (stopReason === "match_limit") {
303
+ notices.push(`Search stopped after reaching the global limit of ${matchLimit} matches; results may be incomplete. Narrow path/glob/pattern to continue.`);
304
+ }
305
+ else if (stopReason === "stdout_limit") {
306
+ notices.push(`Raw ripgrep output reached ${formatBytes(MAX_RAW_STDOUT_BYTES)}; results are incomplete. Narrow the search.`);
307
+ }
308
+ else if (stopReason === "stderr_limit") {
309
+ notices.push(`ripgrep diagnostics reached ${formatBytes(MAX_STDERR_BYTES)}; results are incomplete.`);
310
+ }
311
+ else if (stopReason === "timeout") {
312
+ notices.push(`Search timed out after ${GREP_TIMEOUT_MS / 1000}s; results are incomplete. Narrow the search.`);
313
+ }
314
+ if (byteTruncated) {
315
+ notices.push(`Showing ${emitted.length} of ${matches.length} collected matches because tool output is capped at ${formatBytes(MAX_RESULT_BYTES)}.`);
316
+ }
317
+ if (commandWarning)
318
+ notices.push(`ripgrep also reported: ${truncateCodePoints(commandWarning, 300)}`);
319
+ let content = emitted.map((match) => match.rendered).join("\n");
320
+ if (notices.length > 0)
321
+ content += `${content ? "\n" : ""}[${notices.join(" ")}]`;
322
+ // The reserve above normally guarantees this. Keep an exact byte backstop so
323
+ // future notice copy cannot accidentally violate the model-facing contract.
324
+ if (Buffer.byteLength(content, "utf8") > MAX_RESULT_BYTES) {
325
+ content = truncateUtf8Prefix(content, MAX_RESULT_BYTES);
326
+ }
327
+ return { content, emittedMatches: emitted, byteTruncated };
328
+ }
329
+ function normalizeMatchLimit(value) {
330
+ if (typeof value !== "number" || !Number.isFinite(value))
331
+ return DEFAULT_MATCH_LIMIT;
332
+ return Math.min(MAX_MATCH_LIMIT, Math.max(1, Math.floor(value)));
333
+ }
334
+ function decodeRgField(value, invalidBytesFallback) {
335
+ if (!value || typeof value !== "object")
336
+ return undefined;
337
+ const record = value;
338
+ if (typeof record.text === "string")
339
+ return record.text;
340
+ if (typeof record.bytes === "string") {
341
+ try {
342
+ return new TextDecoder("utf-8", { fatal: true }).decode(Buffer.from(record.bytes, "base64"));
343
+ }
344
+ catch {
345
+ return invalidBytesFallback;
346
+ }
347
+ }
348
+ return undefined;
349
+ }
350
+ function truncateCodePoints(value, maxCodePoints) {
351
+ const points = Array.from(value);
352
+ if (points.length <= maxCodePoints)
353
+ return value;
354
+ const marker = "... [truncated]";
355
+ return `${points.slice(0, Math.max(0, maxCodePoints - Array.from(marker).length)).join("")}${marker}`;
356
+ }
357
+ function truncateUtf8Prefix(value, maxBytes) {
358
+ const bytes = Buffer.from(value, "utf8");
359
+ if (bytes.length <= maxBytes)
360
+ return value;
361
+ let end = maxBytes;
362
+ while (end > 0 && (bytes[end] & 0xc0) === 0x80)
363
+ end -= 1;
364
+ return bytes.subarray(0, end).toString("utf8");
365
+ }
366
+ function formatBytes(bytes) {
367
+ return bytes % 1024 === 0 ? `${bytes / 1024}KiB` : `${bytes} bytes`;
368
+ }
@@ -55,7 +55,9 @@ export interface CreateAllToolsOptions {
55
55
  */
56
56
  processManager?: ProcessManager;
57
57
  allowBackgroundTasks?: boolean;
58
- /** Ctrl+G send-to-background requests from the TUI (design §2.5). */
58
+ /** Ctrl+B send-to-background requests from the TUI (design §2.5). */
59
59
  promotionChannel?: PromotionChannel;
60
+ /** Grok-style foreground wait budget before a Bash Execute is demoted. */
61
+ autoBackgroundAfterMs?: number | false;
60
62
  }
61
63
  export declare function createAllTools(cwd: string, skillRegistry?: SkillRegistry, options?: CreateAllToolsOptions): ToolRegistryEntry[];
@@ -51,6 +51,7 @@ export function createAllTools(cwd, skillRegistry, options = {}) {
51
51
  processManager: options.processManager,
52
52
  allowBackgroundTasks: backgroundTasks,
53
53
  promotionChannel: backgroundTasks ? options.promotionChannel : undefined,
54
+ autoBackgroundAfterMs: backgroundTasks ? options.autoBackgroundAfterMs : false,
54
55
  }),
55
56
  ...(backgroundTasks ? createBackgroundTaskTools(options.processManager) : []),
56
57
  ...createManagedServerTools(cwd, approval),
@@ -1,4 +1,5 @@
1
1
  import { QuestionRejectedError } from "../question/index.js";
2
+ import { normalizeQuestionInlineText } from "../question/normalize.js";
2
3
  export function createQuestionTool(controller) {
3
4
  return {
4
5
  name: "question",
@@ -127,8 +128,8 @@ function normalizeQuestions(input) {
127
128
  return { error: `Error: question at index ${index} must be an object.` };
128
129
  }
129
130
  const q = raw;
130
- const header = typeof q.header === "string" ? q.header.trim() : "";
131
- const question = typeof q.question === "string" ? q.question.trim() : "";
131
+ const header = typeof q.header === "string" ? normalizeQuestionInlineText(q.header) : "";
132
+ const question = typeof q.question === "string" ? normalizeQuestionInlineText(q.question) : "";
132
133
  const options = q.options;
133
134
  if (!header)
134
135
  return { error: `Error: question at index ${index} has an empty header.` };
@@ -147,8 +148,8 @@ function normalizeQuestions(input) {
147
148
  return { error: `Error: option ${optIndex + 1} for "${header}" must be an object.` };
148
149
  }
149
150
  const value = opt;
150
- const label = typeof value.label === "string" ? value.label.trim() : "";
151
- const description = typeof value.description === "string" ? value.description.trim() : "";
151
+ const label = typeof value.label === "string" ? normalizeQuestionInlineText(value.label) : "";
152
+ const description = typeof value.description === "string" ? normalizeQuestionInlineText(value.description) : "";
152
153
  if (!label)
153
154
  return { error: `Error: option ${optIndex + 1} for "${header}" has empty label.` };
154
155
  if (!description)
@@ -7,8 +7,9 @@ import { basename, dirname, extname, join, relative } from "node:path";
7
7
  import { isSensitivePath } from "./sensitive-paths.js";
8
8
  import { FileStateTracker } from "./file-state.js";
9
9
  import { resolveToolPath } from "./path-utils.js";
10
+ import { utf8Prefix } from "../context/tool-output-truncate.js";
10
11
  const MAX_LINES = 2500;
11
- const MAX_BYTES = 256 * 1024;
12
+ const MAX_BYTES = 64 * 1024;
12
13
  const FILE_UNCHANGED_STUB = "File unchanged since last read. The earlier read tool_result in this conversation is still current — refer to that instead of re-reading. If you need a different range, call read again with explicit offset/limit; if the file has actually changed, edit or write will refresh this cache automatically.";
13
14
  const END_OF_FILE_STUB = (totalLines) => `End of file reached. All ${totalLines} lines of this file have already been returned by previous read tool_results in this conversation. Refer to those results, or pass an explicit offset to re-read a specific range.`;
14
15
  export function createReadTool(cwd, approval, lsp, fileState) {
@@ -126,32 +127,46 @@ export function createReadTool(cwd, approval, lsp, fileState) {
126
127
  const lines = content.split("\n");
127
128
  const totalLines = lines.length;
128
129
  const effectiveLimit = argLimit !== undefined ? argLimit : totalLines;
129
- let sliced = lines.slice(effectiveOffset, effectiveOffset + effectiveLimit);
130
- let truncated = false;
131
- if (sliced.length > MAX_LINES) {
132
- sliced = sliced.slice(0, MAX_LINES);
133
- truncated = true;
134
- }
135
- let result = sliced.join("\n");
136
- const byteLength = Buffer.byteLength(result, "utf-8");
137
- if (byteLength > MAX_BYTES) {
138
- result = Buffer.from(result, "utf-8").subarray(0, MAX_BYTES).toString("utf-8");
139
- truncated = true;
140
- }
141
- if (autoAdvanceNote) {
142
- result = `${autoAdvanceNote}\n${result}`;
143
- }
130
+ const requestedLines = lines.slice(effectiveOffset, effectiveOffset + effectiveLimit);
131
+ const lineLimited = requestedLines.length > MAX_LINES;
132
+ const candidates = requestedLines.slice(0, MAX_LINES);
133
+ const prefix = autoAdvanceNote ? `${autoAdvanceNote}\n` : "";
134
+ let returnedLines = candidates.length;
135
+ let partialFirstLine = false;
136
+ let body = candidates.join("\n");
137
+ let truncated = lineLimited || Buffer.byteLength(`${prefix}${body}`, "utf8") > MAX_BYTES;
144
138
  if (truncated) {
145
- const lastLine = effectiveOffset + sliced.length;
146
- result += `\n[Output truncated at line ${lastLine} of ${totalLines}. Call read again on the same path to auto-advance to the next page, or pass explicit offset/limit.]`;
139
+ const accepted = [];
140
+ for (const line of candidates) {
141
+ const next = accepted.length === 0 ? line : `${accepted.join("\n")}\n${line}`;
142
+ const nextReturnedLines = accepted.length + 1;
143
+ const marker = readTruncationMarker(effectiveOffset + nextReturnedLines, totalLines);
144
+ if (Buffer.byteLength(`${prefix}${next}${marker}`, "utf8") > MAX_BYTES)
145
+ break;
146
+ accepted.push(line);
147
+ }
148
+ returnedLines = accepted.length;
149
+ body = accepted.join("\n");
150
+ // A pathological single line can exceed the whole page. Preserve a
151
+ // UTF-8-safe prefix and advance by exactly that represented line so
152
+ // auto-pagination never loops forever or skips multiple unseen lines.
153
+ if (returnedLines === 0 && candidates.length > 0) {
154
+ returnedLines = 1;
155
+ partialFirstLine = true;
156
+ const marker = readTruncationMarker(effectiveOffset + returnedLines, totalLines);
157
+ const available = Math.max(0, MAX_BYTES - Buffer.byteLength(`${prefix}${marker}`, "utf8"));
158
+ body = utf8Prefix(candidates[0], available);
159
+ }
160
+ body += readTruncationMarker(effectiveOffset + returnedLines, totalLines);
147
161
  }
162
+ const result = `${prefix}${body}`;
148
163
  if (currentMtimeMs !== undefined) {
149
164
  setHistory(filePath, {
150
165
  argOffset,
151
166
  argLimit,
152
167
  effectiveOffset,
153
168
  effectiveLimit,
154
- returnedLines: sliced.length,
169
+ returnedLines,
155
170
  totalLines,
156
171
  mtimeMs: currentMtimeMs,
157
172
  truncated,
@@ -159,7 +174,7 @@ export function createReadTool(cwd, approval, lsp, fileState) {
159
174
  }
160
175
  const isFullRead = effectiveOffset === 0
161
176
  && !truncated
162
- && effectiveOffset + effectiveLimit >= totalLines;
177
+ && effectiveOffset + returnedLines >= totalLines;
163
178
  if (isFullRead) {
164
179
  await fileState?.observe(filePath, "read", content).catch(() => undefined);
165
180
  }
@@ -171,15 +186,19 @@ export function createReadTool(cwd, approval, lsp, fileState) {
171
186
  kind: "read",
172
187
  path: filePath,
173
188
  offset: effectiveOffset + 1,
174
- lines: sliced.length,
189
+ lines: returnedLines,
175
190
  total: totalLines,
176
191
  ...(autoAdvanceNote ? { autoAdvanced: true } : {}),
177
192
  ...(truncated ? { truncated: true } : {}),
193
+ ...(partialFirstLine ? { partialFirstLine: true } : {}),
178
194
  },
179
195
  };
180
196
  },
181
197
  };
182
198
  }
199
+ function readTruncationMarker(lastLine, totalLines) {
200
+ return `\n[Output truncated at line ${lastLine} of ${totalLines}. Call read again on the same path to auto-advance to the next page, or pass explicit offset/limit.]`;
201
+ }
183
202
  async function readFileNotFoundMessage(filePath, cwd, error) {
184
203
  const message = [`Error: Cannot read file: ${filePath}`];
185
204
  const code = typeof error?.code === "string" ? error.code : undefined;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * One clock owns all decorative TUI animation. Components expose pure frame
3
+ * advancement; they never create timers or request renders independently.
4
+ */
5
+ export declare class TuiAnimationClock {
6
+ private readonly onTick;
7
+ private readonly intervalMs;
8
+ private timer;
9
+ constructor(onTick: (elapsedMs: number) => void, intervalMs?: number);
10
+ setActive(active: boolean): void;
11
+ isActive(): boolean;
12
+ dispose(): void;
13
+ private stop;
14
+ }