@caupulican/pi-adaptative 0.81.32 → 0.81.37

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 (339) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/dist/bundled-resources/extensions/tmux-agent-manager/README.md +69 -0
  3. package/dist/bundled-resources/extensions/tmux-agent-manager/index.d.ts +53 -0
  4. package/dist/bundled-resources/extensions/tmux-agent-manager/index.d.ts.map +1 -0
  5. package/dist/bundled-resources/extensions/tmux-agent-manager/index.js +1282 -0
  6. package/dist/bundled-resources/extensions/tmux-agent-manager/index.js.map +1 -0
  7. package/dist/bundled-resources/extensions/tmux-agent-manager/index.ts +1453 -0
  8. package/dist/bundled-resources/extensions/tmux-agent-manager/templates/builder-validator.json +14 -0
  9. package/dist/bundled-resources/extensions/tmux-agent-manager/templates/claude-trio.json +32 -0
  10. package/dist/bundled-resources/extensions/tmux-agent-manager/templates/full-provider-review.json +15 -0
  11. package/dist/bundled-resources/extensions/tmux-agent-manager/templates/provider-prompt-smoke.json +14 -0
  12. package/dist/bundled-resources/prompts/extensionify.md +5 -4
  13. package/dist/bundled-resources/prompts/skillify.md +5 -5
  14. package/dist/bundled-resources/skills/pi-harness-learning/SKILL.md +6 -3
  15. package/dist/cli/args.d.ts.map +1 -1
  16. package/dist/cli/args.js +10 -8
  17. package/dist/cli/args.js.map +1 -1
  18. package/dist/config.d.ts +5 -1
  19. package/dist/config.d.ts.map +1 -1
  20. package/dist/config.js +10 -2
  21. package/dist/config.js.map +1 -1
  22. package/dist/core/agent-session.d.ts +12 -0
  23. package/dist/core/agent-session.d.ts.map +1 -1
  24. package/dist/core/agent-session.js +42 -2
  25. package/dist/core/agent-session.js.map +1 -1
  26. package/dist/core/autonomy/approval-gate.d.ts.map +1 -1
  27. package/dist/core/autonomy/approval-gate.js +2 -0
  28. package/dist/core/autonomy/approval-gate.js.map +1 -1
  29. package/dist/core/autonomy/foreground-envelope.d.ts.map +1 -1
  30. package/dist/core/autonomy/foreground-envelope.js +2 -0
  31. package/dist/core/autonomy/foreground-envelope.js.map +1 -1
  32. package/dist/core/autonomy/gates.d.ts.map +1 -1
  33. package/dist/core/autonomy/gates.js +9 -2
  34. package/dist/core/autonomy/gates.js.map +1 -1
  35. package/dist/core/autonomy/risk-assessment.d.ts.map +1 -1
  36. package/dist/core/autonomy/risk-assessment.js +2 -0
  37. package/dist/core/autonomy/risk-assessment.js.map +1 -1
  38. package/dist/core/background-lane-controller.d.ts +12 -3
  39. package/dist/core/background-lane-controller.d.ts.map +1 -1
  40. package/dist/core/background-lane-controller.js +67 -26
  41. package/dist/core/background-lane-controller.js.map +1 -1
  42. package/dist/core/bash-execution-controller.d.ts +9 -4
  43. package/dist/core/bash-execution-controller.d.ts.map +1 -1
  44. package/dist/core/bash-execution-controller.js +7 -6
  45. package/dist/core/bash-execution-controller.js.map +1 -1
  46. package/dist/core/bash-executor.d.ts +2 -0
  47. package/dist/core/bash-executor.d.ts.map +1 -1
  48. package/dist/core/bash-executor.js +10 -5
  49. package/dist/core/bash-executor.js.map +1 -1
  50. package/dist/core/context/context-store-retention.d.ts +4 -1
  51. package/dist/core/context/context-store-retention.d.ts.map +1 -1
  52. package/dist/core/context/context-store-retention.js +42 -151
  53. package/dist/core/context/context-store-retention.js.map +1 -1
  54. package/dist/core/context-pipeline.d.ts.map +1 -1
  55. package/dist/core/context-pipeline.js +6 -6
  56. package/dist/core/context-pipeline.js.map +1 -1
  57. package/dist/core/default-tool-surface.d.ts +7 -0
  58. package/dist/core/default-tool-surface.d.ts.map +1 -1
  59. package/dist/core/default-tool-surface.js +29 -10
  60. package/dist/core/default-tool-surface.js.map +1 -1
  61. package/dist/core/doctor.d.ts +10 -10
  62. package/dist/core/doctor.d.ts.map +1 -1
  63. package/dist/core/doctor.js +30 -20
  64. package/dist/core/doctor.js.map +1 -1
  65. package/dist/core/exec.d.ts +2 -0
  66. package/dist/core/exec.d.ts.map +1 -1
  67. package/dist/core/exec.js +26 -39
  68. package/dist/core/exec.js.map +1 -1
  69. package/dist/core/extensions/loader.d.ts +9 -2
  70. package/dist/core/extensions/loader.d.ts.map +1 -1
  71. package/dist/core/extensions/loader.js +105 -21
  72. package/dist/core/extensions/loader.js.map +1 -1
  73. package/dist/core/extensions/types.d.ts +13 -3
  74. package/dist/core/extensions/types.d.ts.map +1 -1
  75. package/dist/core/extensions/types.js +3 -0
  76. package/dist/core/extensions/types.js.map +1 -1
  77. package/dist/core/footer-data-provider.d.ts.map +1 -1
  78. package/dist/core/footer-data-provider.js +2 -0
  79. package/dist/core/footer-data-provider.js.map +1 -1
  80. package/dist/core/goals/goal-state.d.ts +10 -0
  81. package/dist/core/goals/goal-state.d.ts.map +1 -1
  82. package/dist/core/goals/goal-state.js +37 -0
  83. package/dist/core/goals/goal-state.js.map +1 -1
  84. package/dist/core/goals/goal-tool-core.d.ts +11 -0
  85. package/dist/core/goals/goal-tool-core.d.ts.map +1 -1
  86. package/dist/core/goals/goal-tool-core.js +51 -1
  87. package/dist/core/goals/goal-tool-core.js.map +1 -1
  88. package/dist/core/improvement-loop.d.ts.map +1 -1
  89. package/dist/core/improvement-loop.js +26 -5
  90. package/dist/core/improvement-loop.js.map +1 -1
  91. package/dist/core/memory/memory-manager.d.ts.map +1 -1
  92. package/dist/core/memory/memory-manager.js +1 -0
  93. package/dist/core/memory/memory-manager.js.map +1 -1
  94. package/dist/core/model-capability.d.ts.map +1 -1
  95. package/dist/core/model-capability.js +2 -0
  96. package/dist/core/model-capability.js.map +1 -1
  97. package/dist/core/model-router/tool-escalation.d.ts.map +1 -1
  98. package/dist/core/model-router/tool-escalation.js +17 -3
  99. package/dist/core/model-router/tool-escalation.js.map +1 -1
  100. package/dist/core/models/fitness-store.d.ts.map +1 -1
  101. package/dist/core/models/fitness-store.js +1 -1
  102. package/dist/core/models/fitness-store.js.map +1 -1
  103. package/dist/core/models/local-runtime.d.ts +6 -4
  104. package/dist/core/models/local-runtime.d.ts.map +1 -1
  105. package/dist/core/models/local-runtime.js +108 -36
  106. package/dist/core/models/local-runtime.js.map +1 -1
  107. package/dist/core/output-guard.d.ts.map +1 -1
  108. package/dist/core/output-guard.js +23 -8
  109. package/dist/core/output-guard.js.map +1 -1
  110. package/dist/core/package-manager.d.ts.map +1 -1
  111. package/dist/core/package-manager.js +44 -59
  112. package/dist/core/package-manager.js.map +1 -1
  113. package/dist/core/profile-resource-selection.d.ts +16 -0
  114. package/dist/core/profile-resource-selection.d.ts.map +1 -1
  115. package/dist/core/profile-resource-selection.js +41 -0
  116. package/dist/core/profile-resource-selection.js.map +1 -1
  117. package/dist/core/python-runtime.d.ts +46 -0
  118. package/dist/core/python-runtime.d.ts.map +1 -0
  119. package/dist/core/python-runtime.js +157 -0
  120. package/dist/core/python-runtime.js.map +1 -0
  121. package/dist/core/reflection-controller.d.ts.map +1 -1
  122. package/dist/core/reflection-controller.js +18 -1
  123. package/dist/core/reflection-controller.js.map +1 -1
  124. package/dist/core/reload-blockers.d.ts +1 -0
  125. package/dist/core/reload-blockers.d.ts.map +1 -1
  126. package/dist/core/reload-blockers.js +6 -2
  127. package/dist/core/reload-blockers.js.map +1 -1
  128. package/dist/core/resource-loader.d.ts +2 -0
  129. package/dist/core/resource-loader.d.ts.map +1 -1
  130. package/dist/core/resource-loader.js +50 -28
  131. package/dist/core/resource-loader.js.map +1 -1
  132. package/dist/core/runtime-builder.d.ts +4 -0
  133. package/dist/core/runtime-builder.d.ts.map +1 -1
  134. package/dist/core/runtime-builder.js +31 -9
  135. package/dist/core/runtime-builder.js.map +1 -1
  136. package/dist/core/security/untrusted-boundary.d.ts +2 -2
  137. package/dist/core/security/untrusted-boundary.d.ts.map +1 -1
  138. package/dist/core/security/untrusted-boundary.js +4 -3
  139. package/dist/core/security/untrusted-boundary.js.map +1 -1
  140. package/dist/core/settings-manager.d.ts.map +1 -1
  141. package/dist/core/settings-manager.js +3 -1
  142. package/dist/core/settings-manager.js.map +1 -1
  143. package/dist/core/slash-commands.d.ts.map +1 -1
  144. package/dist/core/slash-commands.js +9 -1
  145. package/dist/core/slash-commands.js.map +1 -1
  146. package/dist/core/system-prompt.d.ts.map +1 -1
  147. package/dist/core/system-prompt.js +13 -2
  148. package/dist/core/system-prompt.js.map +1 -1
  149. package/dist/core/tasks/session-task-state.d.ts +10 -0
  150. package/dist/core/tasks/session-task-state.d.ts.map +1 -0
  151. package/dist/core/tasks/session-task-state.js +29 -0
  152. package/dist/core/tasks/session-task-state.js.map +1 -0
  153. package/dist/core/tasks/task-command.d.ts +30 -0
  154. package/dist/core/tasks/task-command.d.ts.map +1 -0
  155. package/dist/core/tasks/task-command.js +79 -0
  156. package/dist/core/tasks/task-command.js.map +1 -0
  157. package/dist/core/tasks/task-state.d.ts +71 -0
  158. package/dist/core/tasks/task-state.d.ts.map +1 -0
  159. package/dist/core/tasks/task-state.js +372 -0
  160. package/dist/core/tasks/task-state.js.map +1 -0
  161. package/dist/core/tool-selection/tool-selection-controller.d.ts.map +1 -1
  162. package/dist/core/tool-selection/tool-selection-controller.js +1 -1
  163. package/dist/core/tool-selection/tool-selection-controller.js.map +1 -1
  164. package/dist/core/toolkit/script-runner.d.ts.map +1 -1
  165. package/dist/core/toolkit/script-runner.js +58 -24
  166. package/dist/core/toolkit/script-runner.js.map +1 -1
  167. package/dist/core/tools/bash.d.ts +20 -2
  168. package/dist/core/tools/bash.d.ts.map +1 -1
  169. package/dist/core/tools/bash.js +132 -220
  170. package/dist/core/tools/bash.js.map +1 -1
  171. package/dist/core/tools/extensionify.d.ts +2 -1
  172. package/dist/core/tools/extensionify.d.ts.map +1 -1
  173. package/dist/core/tools/extensionify.js +11 -8
  174. package/dist/core/tools/extensionify.js.map +1 -1
  175. package/dist/core/tools/find.d.ts.map +1 -1
  176. package/dist/core/tools/find.js +57 -48
  177. package/dist/core/tools/find.js.map +1 -1
  178. package/dist/core/tools/git-filter.d.ts.map +1 -1
  179. package/dist/core/tools/git-filter.js +20 -32
  180. package/dist/core/tools/git-filter.js.map +1 -1
  181. package/dist/core/tools/goal.d.ts +1 -1
  182. package/dist/core/tools/goal.d.ts.map +1 -1
  183. package/dist/core/tools/goal.js +10 -3
  184. package/dist/core/tools/goal.js.map +1 -1
  185. package/dist/core/tools/grep.d.ts.map +1 -1
  186. package/dist/core/tools/grep.js +92 -80
  187. package/dist/core/tools/grep.js.map +1 -1
  188. package/dist/core/tools/index.d.ts +9 -6
  189. package/dist/core/tools/index.d.ts.map +1 -1
  190. package/dist/core/tools/index.js +24 -9
  191. package/dist/core/tools/index.js.map +1 -1
  192. package/dist/core/tools/output-accumulator.d.ts +2 -0
  193. package/dist/core/tools/output-accumulator.d.ts.map +1 -1
  194. package/dist/core/tools/output-accumulator.js +7 -4
  195. package/dist/core/tools/output-accumulator.js.map +1 -1
  196. package/dist/core/tools/python.d.ts +64 -0
  197. package/dist/core/tools/python.d.ts.map +1 -0
  198. package/dist/core/tools/python.js +241 -0
  199. package/dist/core/tools/python.js.map +1 -0
  200. package/dist/core/tools/read.d.ts.map +1 -1
  201. package/dist/core/tools/read.js +4 -3
  202. package/dist/core/tools/read.js.map +1 -1
  203. package/dist/core/tools/shell-contract-router.d.ts +13 -0
  204. package/dist/core/tools/shell-contract-router.d.ts.map +1 -0
  205. package/dist/core/tools/shell-contract-router.js +295 -0
  206. package/dist/core/tools/shell-contract-router.js.map +1 -0
  207. package/dist/core/tools/task-steps.d.ts +46 -0
  208. package/dist/core/tools/task-steps.d.ts.map +1 -0
  209. package/dist/core/tools/task-steps.js +160 -0
  210. package/dist/core/tools/task-steps.js.map +1 -0
  211. package/dist/index.d.ts +2 -1
  212. package/dist/index.d.ts.map +1 -1
  213. package/dist/index.js +2 -1
  214. package/dist/index.js.map +1 -1
  215. package/dist/main.d.ts +2 -0
  216. package/dist/main.d.ts.map +1 -1
  217. package/dist/main.js +23 -4
  218. package/dist/main.js.map +1 -1
  219. package/dist/migrations.d.ts.map +1 -1
  220. package/dist/migrations.js +18 -4
  221. package/dist/migrations.js.map +1 -1
  222. package/dist/modes/interactive/auth-dialogs-controller.d.ts +10 -0
  223. package/dist/modes/interactive/auth-dialogs-controller.d.ts.map +1 -1
  224. package/dist/modes/interactive/auth-dialogs-controller.js +94 -27
  225. package/dist/modes/interactive/auth-dialogs-controller.js.map +1 -1
  226. package/dist/modes/interactive/auto-learn-controller.d.ts.map +1 -1
  227. package/dist/modes/interactive/auto-learn-controller.js +2 -1
  228. package/dist/modes/interactive/auto-learn-controller.js.map +1 -1
  229. package/dist/modes/interactive/components/bordered-loader.d.ts +1 -0
  230. package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -1
  231. package/dist/modes/interactive/components/bordered-loader.js +8 -0
  232. package/dist/modes/interactive/components/bordered-loader.js.map +1 -1
  233. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  234. package/dist/modes/interactive/components/extension-editor.js +6 -15
  235. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  236. package/dist/modes/interactive/components/login-dialog.d.ts +3 -1
  237. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  238. package/dist/modes/interactive/components/login-dialog.js +20 -8
  239. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  240. package/dist/modes/interactive/components/profile-resource-editor.d.ts +4 -1
  241. package/dist/modes/interactive/components/profile-resource-editor.d.ts.map +1 -1
  242. package/dist/modes/interactive/components/profile-resource-editor.js +22 -39
  243. package/dist/modes/interactive/components/profile-resource-editor.js.map +1 -1
  244. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  245. package/dist/modes/interactive/components/session-selector.js +1 -1
  246. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  247. package/dist/modes/interactive/config-backup.d.ts +1 -0
  248. package/dist/modes/interactive/config-backup.d.ts.map +1 -1
  249. package/dist/modes/interactive/config-backup.js +14 -3
  250. package/dist/modes/interactive/config-backup.js.map +1 -1
  251. package/dist/modes/interactive/editor-overlay-host.d.ts +12 -0
  252. package/dist/modes/interactive/editor-overlay-host.d.ts.map +1 -1
  253. package/dist/modes/interactive/editor-overlay-host.js +55 -0
  254. package/dist/modes/interactive/editor-overlay-host.js.map +1 -1
  255. package/dist/modes/interactive/extension-ui-host.d.ts +2 -1
  256. package/dist/modes/interactive/extension-ui-host.d.ts.map +1 -1
  257. package/dist/modes/interactive/extension-ui-host.js +161 -96
  258. package/dist/modes/interactive/extension-ui-host.js.map +1 -1
  259. package/dist/modes/interactive/external-editor.d.ts.map +1 -1
  260. package/dist/modes/interactive/external-editor.js +23 -28
  261. package/dist/modes/interactive/external-editor.js.map +1 -1
  262. package/dist/modes/interactive/interactive-mode.d.ts +1 -0
  263. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  264. package/dist/modes/interactive/interactive-mode.js +26 -8
  265. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  266. package/dist/modes/interactive/profile-menu-controller.d.ts +5 -1
  267. package/dist/modes/interactive/profile-menu-controller.d.ts.map +1 -1
  268. package/dist/modes/interactive/profile-menu-controller.js +118 -24
  269. package/dist/modes/interactive/profile-menu-controller.js.map +1 -1
  270. package/dist/modes/interactive/resource-shell-commands.d.ts +1 -0
  271. package/dist/modes/interactive/resource-shell-commands.d.ts.map +1 -1
  272. package/dist/modes/interactive/resource-shell-commands.js +5 -1
  273. package/dist/modes/interactive/resource-shell-commands.js.map +1 -1
  274. package/dist/modes/interactive/session-flow-commands.d.ts +10 -0
  275. package/dist/modes/interactive/session-flow-commands.d.ts.map +1 -1
  276. package/dist/modes/interactive/session-flow-commands.js +143 -7
  277. package/dist/modes/interactive/session-flow-commands.js.map +1 -1
  278. package/dist/modes/interactive/session-io-commands.d.ts.map +1 -1
  279. package/dist/modes/interactive/session-io-commands.js +80 -50
  280. package/dist/modes/interactive/session-io-commands.js.map +1 -1
  281. package/dist/modes/interactive/startup-checks.d.ts.map +1 -1
  282. package/dist/modes/interactive/startup-checks.js +14 -17
  283. package/dist/modes/interactive/startup-checks.js.map +1 -1
  284. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  285. package/dist/modes/rpc/rpc-client.js +20 -19
  286. package/dist/modes/rpc/rpc-client.js.map +1 -1
  287. package/dist/package-manager-cli.d.ts.map +1 -1
  288. package/dist/package-manager-cli.js +10 -19
  289. package/dist/package-manager-cli.js.map +1 -1
  290. package/dist/utils/child-process.d.ts +13 -7
  291. package/dist/utils/child-process.d.ts.map +1 -1
  292. package/dist/utils/child-process.js +56 -3
  293. package/dist/utils/child-process.js.map +1 -1
  294. package/dist/utils/clipboard-image.d.ts.map +1 -1
  295. package/dist/utils/clipboard-image.js +3 -2
  296. package/dist/utils/clipboard-image.js.map +1 -1
  297. package/dist/utils/clipboard.d.ts.map +1 -1
  298. package/dist/utils/clipboard.js +17 -10
  299. package/dist/utils/clipboard.js.map +1 -1
  300. package/dist/utils/external-editor-command.d.ts +8 -0
  301. package/dist/utils/external-editor-command.d.ts.map +1 -0
  302. package/dist/utils/external-editor-command.js +60 -0
  303. package/dist/utils/external-editor-command.js.map +1 -0
  304. package/dist/utils/paths.d.ts.map +1 -1
  305. package/dist/utils/paths.js +10 -2
  306. package/dist/utils/paths.js.map +1 -1
  307. package/dist/utils/shell.d.ts +6 -8
  308. package/dist/utils/shell.d.ts.map +1 -1
  309. package/dist/utils/shell.js +61 -70
  310. package/dist/utils/shell.js.map +1 -1
  311. package/dist/utils/tools-manager.d.ts +23 -3
  312. package/dist/utils/tools-manager.d.ts.map +1 -1
  313. package/dist/utils/tools-manager.js +117 -21
  314. package/dist/utils/tools-manager.js.map +1 -1
  315. package/dist/utils/work-directory.d.ts +36 -0
  316. package/dist/utils/work-directory.d.ts.map +1 -0
  317. package/dist/utils/work-directory.js +462 -0
  318. package/dist/utils/work-directory.js.map +1 -0
  319. package/docs/index.md +4 -0
  320. package/docs/python.md +43 -0
  321. package/docs/resources.md +2 -2
  322. package/docs/rpc.md +1 -1
  323. package/docs/settings.md +7 -3
  324. package/docs/task-steps.md +90 -0
  325. package/docs/termux.md +7 -0
  326. package/docs/tmux-agent-manager.md +48 -0
  327. package/docs/usage.md +5 -6
  328. package/docs/windows.md +104 -8
  329. package/docs/work-directory.md +41 -0
  330. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  331. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  332. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  333. package/examples/extensions/sandbox/package-lock.json +2 -2
  334. package/examples/extensions/sandbox/package.json +1 -1
  335. package/examples/extensions/with-deps/package-lock.json +2 -2
  336. package/examples/extensions/with-deps/package.json +1 -1
  337. package/npm-shrinkwrap.json +32 -37
  338. package/package.json +9 -7
  339. package/scripts/postinstall-python-runtime.mjs +20 -0
@@ -1,6 +1,5 @@
1
1
  import { constants } from "node:fs";
2
- import { access as fsAccess, readdir as fsReaddir, readFile as fsReadFile, stat as fsStat } from "node:fs/promises";
3
- import { relative as relativePath, resolve as resolvePath } from "node:path";
2
+ import { access as fsAccess } from "node:fs/promises";
4
3
  import { createSilenceWatchdog } from "@caupulican/pi-agent-core";
5
4
  import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, } from "@caupulican/pi-agent-core/node";
6
5
  import { Container, Text, truncateToWidth } from "@caupulican/pi-tui";
@@ -9,45 +8,54 @@ import { Type } from "typebox";
9
8
  import { keyHint } from "../../modes/interactive/components/keybinding-hints.js";
10
9
  import { truncateToVisualLines } from "../../modes/interactive/components/visual-truncate.js";
11
10
  import { theme } from "../../modes/interactive/theme/theme.js";
12
- import { waitForChildProcess } from "../../utils/child-process.js";
13
- import { getShellConfig, getShellEnv, killProcessTree, trackDetachedChildPid, untrackDetachedChildPid, } from "../../utils/shell.js";
11
+ import { waitForChildProcessWithTermination } from "../../utils/child-process.js";
12
+ import { getPlatformShellToolName, getShellConfig, getShellEnv, prefixPowerShellCommand, trackDetachedChildPid, untrackDetachedChildPid, } from "../../utils/shell.js";
14
13
  import { withExclusiveMutationBarrier } from "./file-mutation-queue.js";
15
14
  import { classifyGitCommand, executeFilteredGit } from "./git-filter.js";
16
15
  import { OutputAccumulator } from "./output-accumulator.js";
17
16
  import { getTextOutput, invalidArgText, str } from "./render-utils.js";
17
+ import { routeShellContract } from "./shell-contract-router.js";
18
18
  import { wrapToolDefinition } from "./tool-definition-wrapper.js";
19
- /** Default silence bound for foreground commands without an explicit timeout (spec §2: ~10 min). */
19
+ /** Low-level silence bound retained for direct shell-operation consumers. Agent tool calls always pass a wall-clock bound. */
20
20
  const DEFAULT_COMMAND_SILENCE_MS = 600_000;
21
+ /** Agent-facing wall-clock bound: continuously producing output must not make a command immortal. */
22
+ export const DEFAULT_COMMAND_TIMEOUT_SECONDS = 120;
23
+ export const MAX_COMMAND_TIMEOUT_SECONDS = 3600;
24
+ const MIN_COMMAND_TIMEOUT_SECONDS = 0.1;
21
25
  let commandSilenceMsOverride;
22
- /** Test hook: override the silence threshold. Pass undefined to restore the default. */
26
+ let commandTimeoutMsOverride;
27
+ /** Test hook: override the low-level silence threshold. Pass undefined to restore the default. */
23
28
  export function setCommandSilenceMsForTests(ms) {
24
29
  commandSilenceMsOverride = ms;
25
30
  }
31
+ /** Test hook: override the agent tool's default wall-clock bound. Pass undefined to restore it. */
32
+ export function setCommandTimeoutMsForTests(ms) {
33
+ commandTimeoutMsOverride = ms;
34
+ }
35
+ export function resolveCommandTimeoutSeconds(timeout) {
36
+ if (typeof timeout !== "number" || !Number.isFinite(timeout) || timeout <= 0) {
37
+ return DEFAULT_COMMAND_TIMEOUT_SECONDS;
38
+ }
39
+ return Math.max(MIN_COMMAND_TIMEOUT_SECONDS, Math.min(timeout, MAX_COMMAND_TIMEOUT_SECONDS));
40
+ }
26
41
  const bashSchema = Type.Object({
27
- command: Type.String({ description: "Bash command to execute" }),
42
+ command: Type.String({ description: "Shell command to execute" }),
28
43
  timeout: Type.Optional(Type.Number({
29
- description: "Timeout in seconds (optional). When set to a positive value, this wall-clock limit replaces the default silence watchdog. Zero or negative values are treated as unset, so the silence watchdog still applies.",
44
+ description: `Wall-clock timeout in seconds. Defaults to ${DEFAULT_COMMAND_TIMEOUT_SECONDS}; positive overrides are capped at ${MAX_COMMAND_TIMEOUT_SECONDS}. Zero or negative values use the default.`,
30
45
  })),
31
46
  });
32
- /**
33
- * Create bash operations using pi's built-in local shell execution backend.
34
- *
35
- * This is useful for extensions that intercept user_bash and still want pi's
36
- * standard local shell behavior while wrapping or rewriting commands.
37
- */
38
- export function createLocalBashOperations(options) {
47
+ function createLocalShellOperations(shellName, options) {
39
48
  return {
40
49
  exec: async (command, cwd, { onData, signal, timeout, env }) => {
41
- const { shell, args } = getShellConfig(options?.shellPath);
50
+ const { shell, args } = getShellConfig(options?.shellPath, shellName);
42
51
  try {
43
52
  await fsAccess(cwd, constants.F_OK);
44
53
  }
45
54
  catch {
46
- throw new Error(`Working directory does not exist: ${cwd}\nCannot execute bash commands.`);
55
+ throw new Error(`Working directory does not exist: ${cwd}\nCannot execute ${shellName} commands.`);
47
56
  }
48
- if (signal?.aborted) {
57
+ if (signal?.aborted)
49
58
  throw new Error("aborted");
50
- }
51
59
  const child = spawn(shell, [...args, command], {
52
60
  cwd,
53
61
  detached: process.platform !== "win32",
@@ -57,16 +65,8 @@ export function createLocalBashOperations(options) {
57
65
  });
58
66
  if (child.pid)
59
67
  trackDetachedChildPid(child.pid);
60
- let timedOut = false;
61
- let timeoutHandle;
62
- const onAbort = () => {
63
- if (child.pid)
64
- killProcessTree(child.pid);
65
- };
66
- // A command that keeps producing output must never be killed by this mechanism:
67
- // silence bounds mute-ness, not duration. It arms when the caller left `timeout`
68
- // unset or non-positive (treated as unset) — an explicit positive timeout means
69
- // the model took responsibility for the wall clock.
68
+ const terminationController = new AbortController();
69
+ const onAbort = () => terminationController.abort();
70
70
  let silenceKilled = false;
71
71
  const silenceMs = commandSilenceMsOverride ?? DEFAULT_COMMAND_SILENCE_MS;
72
72
  const silenceWatchdog = (timeout === undefined || timeout <= 0) && silenceMs > 0
@@ -74,8 +74,7 @@ export function createLocalBashOperations(options) {
74
74
  silenceMs,
75
75
  onSilence: () => {
76
76
  silenceKilled = true;
77
- if (child.pid)
78
- killProcessTree(child.pid);
77
+ terminationController.abort();
79
78
  },
80
79
  })
81
80
  : undefined;
@@ -84,50 +83,72 @@ export function createLocalBashOperations(options) {
84
83
  onData(data);
85
84
  };
86
85
  try {
87
- // Set timeout if provided.
88
- if (timeout !== undefined && timeout > 0) {
89
- timeoutHandle = setTimeout(() => {
90
- timedOut = true;
91
- if (child.pid)
92
- killProcessTree(child.pid);
93
- }, timeout * 1000);
94
- }
95
- // Stream stdout and stderr.
96
86
  child.stdout?.on("data", onChunk);
97
87
  child.stderr?.on("data", onChunk);
98
- // Handle abort signal by killing the entire process tree.
99
88
  if (signal) {
100
89
  if (signal.aborted)
101
90
  onAbort();
102
91
  else
103
92
  signal.addEventListener("abort", onAbort, { once: true });
104
93
  }
105
- // Handle shell spawn errors and wait for the process to terminate without hanging
106
- // on inherited stdio handles held by detached descendants.
107
- const exitCode = await waitForChildProcess(child);
108
- if (signal?.aborted) {
94
+ const terminal = await waitForChildProcessWithTermination(child, {
95
+ signal: terminationController.signal,
96
+ timeoutMs: timeout !== undefined && timeout > 0 ? timeout * 1000 : undefined,
97
+ killGraceMs: 2_000,
98
+ });
99
+ if (signal?.aborted)
109
100
  throw new Error("aborted");
110
- }
111
- if (timedOut) {
101
+ if (terminal.reason === "timeout")
112
102
  throw new Error(`timeout:${timeout}`);
113
- }
114
- if (silenceKilled) {
103
+ if (silenceKilled)
115
104
  throw new Error(`silence:${silenceMs / 1000}`);
116
- }
117
- return { exitCode };
105
+ return { exitCode: terminal.code };
118
106
  }
119
107
  finally {
120
108
  silenceWatchdog?.disarm();
121
109
  if (child.pid)
122
110
  untrackDetachedChildPid(child.pid);
123
- if (timeoutHandle)
124
- clearTimeout(timeoutHandle);
125
111
  if (signal)
126
112
  signal.removeEventListener("abort", onAbort);
127
113
  }
128
114
  },
129
115
  };
130
116
  }
117
+ /**
118
+ * Create bash operations using pi's built-in local shell execution backend.
119
+ *
120
+ * This is useful for extensions that intercept user_bash and still want pi's
121
+ * standard local shell behavior while wrapping or rewriting commands.
122
+ */
123
+ export function createLocalBashOperations(options) {
124
+ return createLocalShellOperations("bash", options);
125
+ }
126
+ /** Create PowerShell operations using pi's built-in local execution backend. */
127
+ export function createLocalPowerShellOperations(options) {
128
+ return createLocalShellOperations("powershell", options);
129
+ }
130
+ /** Create the platform shell backend without requiring callers or the model to choose a shell. */
131
+ export function createLocalPlatformShellOperations(options = {}, platform = process.platform) {
132
+ const operations = options.operations ??
133
+ createLocalShellOperations(getPlatformShellToolName(platform), { shellPath: options.shellPath });
134
+ return {
135
+ async exec(command, cwd, execOptions) {
136
+ let resolvedCommand = command;
137
+ if (platform === "win32") {
138
+ const route = routeShellContract(command, platform);
139
+ if (route.kind === "unsupported")
140
+ throw new Error(route.error);
141
+ if (route.kind === "powershell")
142
+ resolvedCommand = route.command;
143
+ }
144
+ if (options.commandPrefix)
145
+ resolvedCommand = `${options.commandPrefix}\n${resolvedCommand}`;
146
+ if (platform === "win32")
147
+ resolvedCommand = prefixPowerShellCommand(resolvedCommand);
148
+ return operations.exec(resolvedCommand, cwd, execOptions);
149
+ },
150
+ };
151
+ }
131
152
  function resolveSpawnContext(command, cwd, spawnHook) {
132
153
  const baseContext = { command, cwd, env: { ...getShellEnv() } };
133
154
  return spawnHook ? spawnHook(baseContext) : baseContext;
@@ -145,12 +166,13 @@ class BashResultRenderComponent extends Container {
145
166
  function formatDuration(ms) {
146
167
  return `${(ms / 1000).toFixed(1)}s`;
147
168
  }
148
- function formatBashCall(args) {
169
+ function formatBashCall(args, shellName) {
149
170
  const command = str(args?.command);
150
171
  const timeout = args?.timeout;
151
172
  const timeoutSuffix = timeout ? theme.fg("muted", ` (timeout ${timeout}s)`) : "";
152
173
  const commandDisplay = command === null ? invalidArgText(theme) : command ? command : theme.fg("toolOutput", "...");
153
- return theme.fg("toolTitle", theme.bold(`$ ${commandDisplay}`)) + timeoutSuffix;
174
+ const prompt = shellName === "powershell" ? "PS>" : "$";
175
+ return theme.fg("toolTitle", theme.bold(`${prompt} ${commandDisplay}`)) + timeoutSuffix;
154
176
  }
155
177
  function rebuildBashResultRenderComponent(component, result, options, showImages, startedAt, endedAt) {
156
178
  const state = component.state;
@@ -222,158 +244,40 @@ function rebuildBashResultRenderComponent(component, result, options, showImages
222
244
  component.addChild(new Text(`\n${theme.fg("muted", `${label} ${formatDuration(endTime - startedAt)}`)}`, 0, 0));
223
245
  }
224
246
  }
225
- async function tryOptimizeCommand(command, cwd) {
226
- if (process.env.PI_TOOL_OPTIMIZER_DISABLED === "1") {
227
- return { optimized: false };
228
- }
229
- const trimmed = command.trim();
230
- if (!trimmed) {
231
- return { optimized: false };
232
- }
233
- // Reject if there are shell operators or pipes/redirects
234
- const shellOperators = ["|", ">", "<", "&", ";", "\n", "\r", "$", "`", "(", ")", "*", "?", "[", "]"];
235
- if (shellOperators.some((op) => trimmed.includes(op))) {
236
- return { optimized: false };
237
- }
238
- // Simple tokenizer split by whitespace
239
- const args = trimmed.split(/\s+/);
240
- if (args.length === 0) {
241
- return { optimized: false };
242
- }
243
- const cmd = args[0];
244
- const unquote = (s) => {
245
- if ((s.startsWith('"') && s.endsWith('"')) || (s.startsWith("'") && s.endsWith("'"))) {
246
- return s.slice(1, -1);
247
- }
248
- return s;
249
- };
250
- const resolveToCwd = (p, base) => {
251
- const clean = unquote(p);
252
- if (clean.startsWith("/"))
253
- return clean;
254
- return resolvePath(base, clean);
255
- };
256
- try {
257
- if (cmd === "cat") {
258
- if (args.length === 2 && !args[1].startsWith("-")) {
259
- const filePath = resolveToCwd(args[1], cwd);
260
- const content = await fsReadFile(filePath, "utf-8");
261
- return { optimized: true, output: content, exitCode: 0 };
262
- }
263
- }
264
- else if (cmd === "head") {
265
- if (args.length === 2 && !args[1].startsWith("-")) {
266
- const filePath = resolveToCwd(args[1], cwd);
267
- const content = await fsReadFile(filePath, "utf-8");
268
- const lines = content.split("\n").slice(0, 10).join("\n");
269
- return { optimized: true, output: lines, exitCode: 0 };
270
- }
271
- else if (args.length === 4 && args[1] === "-n" && !args[3].startsWith("-")) {
272
- const count = parseInt(args[2], 10);
273
- if (!Number.isNaN(count) && count >= 0) {
274
- const filePath = resolveToCwd(args[3], cwd);
275
- const content = await fsReadFile(filePath, "utf-8");
276
- const lines = content.split("\n").slice(0, count).join("\n");
277
- return { optimized: true, output: lines, exitCode: 0 };
278
- }
279
- }
280
- }
281
- else if (cmd === "tail") {
282
- if (args.length === 2 && !args[1].startsWith("-")) {
283
- const filePath = resolveToCwd(args[1], cwd);
284
- const content = await fsReadFile(filePath, "utf-8");
285
- const allLines = content.split("\n");
286
- const lines = allLines.slice(Math.max(0, allLines.length - 10)).join("\n");
287
- return { optimized: true, output: lines, exitCode: 0 };
288
- }
289
- else if (args.length === 4 && args[1] === "-n" && !args[3].startsWith("-")) {
290
- const count = parseInt(args[2], 10);
291
- if (!Number.isNaN(count) && count >= 0) {
292
- const filePath = resolveToCwd(args[3], cwd);
293
- const content = await fsReadFile(filePath, "utf-8");
294
- const allLines = content.split("\n");
295
- const lines = allLines.slice(Math.max(0, allLines.length - count)).join("\n");
296
- return { optimized: true, output: lines, exitCode: 0 };
297
- }
298
- }
299
- }
300
- else if (cmd === "ls") {
301
- if (args.length === 1 || (args.length === 2 && !args[1].startsWith("-"))) {
302
- const targetDir = args.length === 2 ? resolveToCwd(args[1], cwd) : cwd;
303
- const entries = await fsReaddir(targetDir);
304
- entries.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
305
- const results = [];
306
- for (const entry of entries) {
307
- let suffix = "";
308
- try {
309
- const entryStat = await fsStat(resolvePath(targetDir, entry));
310
- if (entryStat.isDirectory())
311
- suffix = "/";
312
- }
313
- catch { }
314
- results.push(entry + suffix);
315
- }
316
- return { optimized: true, output: results.join("\n"), exitCode: 0 };
317
- }
318
- }
319
- else if (cmd === "grep" || cmd === "rg") {
320
- if (args.length === 3 && !args[1].startsWith("-") && !args[2].startsWith("-")) {
321
- const patternStr = unquote(args[1]);
322
- if (/[\\^$.|?*+()[\]{}]/.test(patternStr))
323
- return { optimized: false };
324
- const filePath = resolveToCwd(args[2], cwd);
325
- const content = await fsReadFile(filePath, "utf-8");
326
- const lines = content.split("\n");
327
- const matches = [];
328
- for (let i = 0; i < lines.length; i++) {
329
- if (lines[i].includes(patternStr)) {
330
- matches.push(lines[i]);
331
- }
332
- }
333
- return { optimized: true, output: matches.join("\n"), exitCode: matches.length > 0 ? 0 : 1 };
334
- }
335
- }
336
- else if (cmd === "find") {
337
- if (args.length === 1 || (args.length === 2 && !args[1].startsWith("-"))) {
338
- const searchPath = args.length === 2 ? resolveToCwd(args[1], cwd) : cwd;
339
- const walk = async (dir) => {
340
- let results = [];
341
- const entries = await fsReaddir(dir, { withFileTypes: true });
342
- for (const entry of entries) {
343
- const full = resolvePath(dir, entry.name);
344
- results.push(full);
345
- if (entry.isDirectory()) {
346
- results = results.concat(await walk(full));
347
- }
348
- }
349
- return results;
350
- };
351
- const all = (await walk(searchPath)).map((p) => relativePath(searchPath, p).replace(/\\/g, "/"));
352
- return { optimized: true, output: all.join("\n"), exitCode: 0 };
353
- }
354
- }
355
- }
356
- catch {
357
- return { optimized: false };
358
- }
359
- return { optimized: false };
360
- }
361
- export function createBashToolDefinition(cwd, options) {
362
- const ops = options?.operations ?? createLocalBashOperations({ shellPath: options?.shellPath });
247
+ function createShellToolDefinition(cwd, backendShell, contractPlatform, options) {
248
+ const toolName = "bash";
249
+ const ops = options?.operations ??
250
+ (backendShell === "powershell"
251
+ ? createLocalPowerShellOperations({ shellPath: options?.shellPath })
252
+ : createLocalBashOperations({ shellPath: options?.shellPath }));
363
253
  const commandPrefix = options?.commandPrefix;
364
254
  const spawnHook = options?.spawnHook;
365
- const canOptimizeCommand = !options?.operations && !options?.shellPath && !commandPrefix && !spawnHook;
366
- const canFilterCommand = canOptimizeCommand;
255
+ const hasExecutionOverrides = Boolean(options?.operations || options?.shellPath || commandPrefix || spawnHook);
256
+ const canFilterCommand = !hasExecutionOverrides;
257
+ const routesWindowsContract = contractPlatform === "win32";
258
+ const contractDescription = routesWindowsContract
259
+ ? "Execute Pi's stable Bash-like command contract in the current working directory. On Windows, a finite deterministic router converts supported simple commands to PowerShell; unsupported Bash constructs fail closed instead of being guessed."
260
+ : "Execute a Bash command in the current working directory.";
367
261
  return {
368
- name: "bash",
369
- label: "bash",
370
- description: `Execute a bash command in the current working directory. Returns stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file. Optionally provide a timeout in seconds. Without an explicit timeout, the command is killed after ${DEFAULT_COMMAND_SILENCE_MS / 1000}s of continuous silence (no stdout/stderr output) rather than being bounded by total runtime; commands that keep producing output are never killed by this. Provide an explicit timeout to replace the silence watchdog with a wall-clock limit, or background long, quiet work with '&'.`,
371
- promptSnippet: "Execute bash commands (ls, grep, find, etc.)",
262
+ name: toolName,
263
+ label: toolName,
264
+ description: `${contractDescription} Returns stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file. Commands have a ${DEFAULT_COMMAND_TIMEOUT_SECONDS}-second wall-clock default, including commands that keep producing output; use a positive timeout only when a scoped operation justifies a larger bound (maximum ${MAX_COMMAND_TIMEOUT_SECONDS} seconds).`,
265
+ promptSnippet: routesWindowsContract
266
+ ? "Execute simple Bash-like commands; Pi routes supported forms deterministically to PowerShell on Windows"
267
+ : "Execute Bash commands (ls, grep, find, etc.)",
268
+ promptGuidelines: routesWindowsContract
269
+ ? [
270
+ "Use the bash tool's portable simple-command contract on Windows; do not write PowerShell or ask the user to choose a shell.",
271
+ "Use one simple command per call. The deterministic router rejects pipelines, redirection, expansion, shell chaining, nested shells, and unsupported Bash forms; use dedicated read/edit/search tools or separate calls instead.",
272
+ "Supported Bash-like file commands are converted with literal-path PowerShell operations; verify targets before recursive rm, cp, or mv calls.",
273
+ "Keep searches scoped and purpose-driven: discover paths first, pass an explicit root and filters, prefer rg over broad find, and increase the timeout only for a justified bounded search.",
274
+ ]
275
+ : [
276
+ "Keep searches scoped and purpose-driven: discover paths first, pass an explicit root and filters, prefer rg over broad find, and increase the timeout only for a justified bounded search.",
277
+ ],
372
278
  parameters: bashSchema,
373
279
  async execute(_toolCallId, { command, timeout }, signal, onUpdate, _ctx) {
374
- const resolvedCommand = commandPrefix ? `${commandPrefix}\n${command}` : command;
375
- const spawnContext = resolveSpawnContext(resolvedCommand, cwd, spawnHook);
376
- const output = new OutputAccumulator({ tempFilePrefix: "pi-bash" });
280
+ const output = new OutputAccumulator({ tempFilePrefix: `pi-${toolName}` });
377
281
  let updateTimer;
378
282
  let updateDirty = false;
379
283
  let lastUpdateAt = 0;
@@ -476,11 +380,14 @@ export function createBashToolDefinition(cwd, options) {
476
380
  return { text, details };
477
381
  };
478
382
  const appendStatus = (text, status) => `${text ? `${text}\n\n` : ""}${status}`;
383
+ const effectiveTimeoutSeconds = typeof timeout === "number" && Number.isFinite(timeout) && timeout > 0
384
+ ? resolveCommandTimeoutSeconds(timeout)
385
+ : (commandTimeoutMsOverride ?? DEFAULT_COMMAND_TIMEOUT_SECONDS * 1000) / 1000;
479
386
  try {
480
387
  if (canFilterCommand) {
481
- const classification = classifyGitCommand(command, spawnContext.env);
388
+ const classification = classifyGitCommand(command, getShellEnv());
482
389
  if (classification.eligible && classification.subcommand) {
483
- const res = await executeFilteredGit(cwd, classification.subcommand, classification.globalOptions || [], classification.subcommandArgs || [], { signal, timeout });
390
+ const res = await executeFilteredGit(cwd, classification.subcommand, classification.globalOptions || [], classification.subcommandArgs || [], { signal, timeout: effectiveTimeoutSeconds });
484
391
  if (res.exitCode !== -100) {
485
392
  output.append(res.rawBytes ?? Buffer.from(res.rawOut, "utf-8"));
486
393
  const snapshot = await finishOutput();
@@ -501,27 +408,25 @@ export function createBashToolDefinition(cwd, options) {
501
408
  }
502
409
  }
503
410
  }
504
- if (canOptimizeCommand) {
505
- const optResult = await tryOptimizeCommand(command, cwd);
506
- if (optResult.optimized) {
507
- output.append(Buffer.from(optResult.output ?? "", "utf-8"));
508
- const snapshot = await finishOutput();
509
- const { text: outputText, details } = formatOutput(snapshot);
510
- if (optResult.exitCode !== 0 && optResult.exitCode !== undefined) {
511
- throw new Error(appendStatus(outputText, `Command exited with code ${optResult.exitCode}`));
512
- }
513
- return { content: [{ type: "text", text: outputText }], details };
514
- }
411
+ let backendCommand = command;
412
+ if (routesWindowsContract) {
413
+ const route = routeShellContract(command, contractPlatform);
414
+ if (route.kind === "unsupported")
415
+ throw new Error(route.error);
416
+ backendCommand = route.command;
515
417
  }
418
+ const commandWithPrefix = commandPrefix ? `${commandPrefix}\n${backendCommand}` : backendCommand;
419
+ const resolvedCommand = backendShell === "powershell" ? prefixPowerShellCommand(commandWithPrefix) : commandWithPrefix;
420
+ const spawnContext = resolveSpawnContext(resolvedCommand, cwd, spawnHook);
516
421
  let exitCode;
517
422
  try {
518
- // Bash cannot statically declare which files a command mutates, so the
519
- // actual shell execution takes the coarse exclusive barrier: it waits for
423
+ // Shell commands cannot statically declare which files they mutate, so the
424
+ // actual execution takes the coarse exclusive barrier: it waits for
520
425
  // in-flight edit/write mutations to drain and blocks new ones meanwhile.
521
426
  const result = await withExclusiveMutationBarrier(() => ops.exec(spawnContext.command, spawnContext.cwd, {
522
427
  onData: handleData,
523
428
  signal,
524
- timeout,
429
+ timeout: effectiveTimeoutSeconds,
525
430
  env: spawnContext.env,
526
431
  }));
527
432
  exitCode = result.exitCode;
@@ -538,7 +443,10 @@ export function createBashToolDefinition(cwd, options) {
538
443
  }
539
444
  if (err instanceof Error && err.message.startsWith("silence:")) {
540
445
  const secs = err.message.split(":")[1];
541
- throw new Error(appendStatus(text, `Command killed after ${secs}s of silence (no output). If the command is legitimately quiet for long stretches, re-run it with an explicit timeout, or run it in the background with '&'.`));
446
+ const recovery = backendShell === "bash"
447
+ ? "re-run it with an explicit timeout, or run it in the background with '&'."
448
+ : "re-run it with an explicit timeout.";
449
+ throw new Error(appendStatus(text, `Command killed after ${secs}s of silence (no output). If the command is legitimately quiet for long stretches, ${recovery}`));
542
450
  }
543
451
  throw err;
544
452
  }
@@ -561,7 +469,7 @@ export function createBashToolDefinition(cwd, options) {
561
469
  state.endedAt = undefined;
562
470
  }
563
471
  const text = context.lastComponent ?? new Text("", 0, 0);
564
- text.setText(formatBashCall(args));
472
+ text.setText(formatBashCall(args, toolName));
565
473
  return text;
566
474
  },
567
475
  renderResult(result, options, _theme, context) {
@@ -583,6 +491,10 @@ export function createBashToolDefinition(cwd, options) {
583
491
  },
584
492
  };
585
493
  }
494
+ export function createBashToolDefinition(cwd, options) {
495
+ const platform = options?.platform ?? process.platform;
496
+ return createShellToolDefinition(cwd, getPlatformShellToolName(platform), platform, options);
497
+ }
586
498
  export function createBashTool(cwd, options) {
587
499
  return wrapToolDefinition(createBashToolDefinition(cwd, options));
588
500
  }