@namzu/sdk 35.0.0 → 37.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (360) hide show
  1. package/CHANGELOG.md +218 -0
  2. package/README.md +68 -41
  3. package/dist/bridge/a2a/mapper.d.ts.map +1 -1
  4. package/dist/bridge/a2a/mapper.js +2 -0
  5. package/dist/bridge/a2a/mapper.js.map +1 -1
  6. package/dist/bridge/sse/mapper.d.ts.map +1 -1
  7. package/dist/bridge/sse/mapper.js +2 -0
  8. package/dist/bridge/sse/mapper.js.map +1 -1
  9. package/dist/compaction/extractor.d.ts +1 -1
  10. package/dist/compaction/extractor.d.ts.map +1 -1
  11. package/dist/compaction/extractor.js +15 -7
  12. package/dist/compaction/extractor.js.map +1 -1
  13. package/dist/compaction/manager.d.ts +2 -0
  14. package/dist/compaction/manager.d.ts.map +1 -1
  15. package/dist/compaction/manager.js +12 -1
  16. package/dist/compaction/manager.js.map +1 -1
  17. package/dist/compaction/verifier.d.ts.map +1 -1
  18. package/dist/compaction/verifier.js +82 -15
  19. package/dist/compaction/verifier.js.map +1 -1
  20. package/dist/contracts/schemas.d.ts +10 -10
  21. package/dist/eval/harness-verification.d.ts +63 -0
  22. package/dist/eval/harness-verification.d.ts.map +1 -0
  23. package/dist/eval/harness-verification.js +165 -0
  24. package/dist/eval/harness-verification.js.map +1 -0
  25. package/dist/eval/index.d.ts +2 -0
  26. package/dist/eval/index.d.ts.map +1 -1
  27. package/dist/eval/index.js +1 -0
  28. package/dist/eval/index.js.map +1 -1
  29. package/dist/execution/code-runtime/json-value.d.ts +10 -0
  30. package/dist/execution/code-runtime/json-value.d.ts.map +1 -0
  31. package/dist/execution/code-runtime/json-value.js +89 -0
  32. package/dist/execution/code-runtime/json-value.js.map +1 -0
  33. package/dist/execution/code-runtime/worker-program.d.ts +8 -0
  34. package/dist/execution/code-runtime/worker-program.d.ts.map +1 -0
  35. package/dist/execution/code-runtime/worker-program.js +240 -0
  36. package/dist/execution/code-runtime/worker-program.js.map +1 -0
  37. package/dist/execution/code-runtime/worker.d.ts +22 -0
  38. package/dist/execution/code-runtime/worker.d.ts.map +1 -1
  39. package/dist/execution/code-runtime/worker.js +148 -111
  40. package/dist/execution/code-runtime/worker.js.map +1 -1
  41. package/dist/manager/agent/lifecycle.d.ts +12 -0
  42. package/dist/manager/agent/lifecycle.d.ts.map +1 -1
  43. package/dist/manager/agent/lifecycle.js +306 -37
  44. package/dist/manager/agent/lifecycle.js.map +1 -1
  45. package/dist/manager/run/persistence.d.ts +2 -0
  46. package/dist/manager/run/persistence.d.ts.map +1 -1
  47. package/dist/manager/run/persistence.js +6 -0
  48. package/dist/manager/run/persistence.js.map +1 -1
  49. package/dist/provider/capabilities.d.ts +3 -1
  50. package/dist/provider/capabilities.d.ts.map +1 -1
  51. package/dist/provider/capabilities.js +13 -0
  52. package/dist/provider/capabilities.js.map +1 -1
  53. package/dist/provider/fallback.d.ts.map +1 -1
  54. package/dist/provider/fallback.js +2 -0
  55. package/dist/provider/fallback.js.map +1 -1
  56. package/dist/provider/token-budget.d.ts.map +1 -1
  57. package/dist/provider/token-budget.js +56 -8
  58. package/dist/provider/token-budget.js.map +1 -1
  59. package/dist/public-runtime.d.ts +5 -3
  60. package/dist/public-runtime.d.ts.map +1 -1
  61. package/dist/public-runtime.js +3 -2
  62. package/dist/public-runtime.js.map +1 -1
  63. package/dist/public-tools.d.ts +1 -1
  64. package/dist/public-tools.d.ts.map +1 -1
  65. package/dist/public-tools.js +1 -1
  66. package/dist/public-tools.js.map +1 -1
  67. package/dist/public-types.d.ts +1 -0
  68. package/dist/public-types.d.ts.map +1 -1
  69. package/dist/registry/tool/execute.d.ts +3 -0
  70. package/dist/registry/tool/execute.d.ts.map +1 -1
  71. package/dist/registry/tool/execute.js +11 -80
  72. package/dist/registry/tool/execute.js.map +1 -1
  73. package/dist/run/command-gate.d.ts.map +1 -1
  74. package/dist/run/command-gate.js +65 -12
  75. package/dist/run/command-gate.js.map +1 -1
  76. package/dist/run/index.d.ts +2 -0
  77. package/dist/run/index.d.ts.map +1 -1
  78. package/dist/run/index.js +1 -0
  79. package/dist/run/index.js.map +1 -1
  80. package/dist/run/memory-promoter.d.ts +3 -35
  81. package/dist/run/memory-promoter.d.ts.map +1 -1
  82. package/dist/run/memory-promoter.js +32 -40
  83. package/dist/run/memory-promoter.js.map +1 -1
  84. package/dist/run/memory-recall.d.ts +25 -0
  85. package/dist/run/memory-recall.d.ts.map +1 -0
  86. package/dist/run/memory-recall.js +228 -0
  87. package/dist/run/memory-recall.js.map +1 -0
  88. package/dist/run/reporter.d.ts.map +1 -1
  89. package/dist/run/reporter.js +1 -0
  90. package/dist/run/reporter.js.map +1 -1
  91. package/dist/run/token-budget.d.ts +2 -0
  92. package/dist/run/token-budget.d.ts.map +1 -1
  93. package/dist/run/token-budget.js +4 -0
  94. package/dist/run/token-budget.js.map +1 -1
  95. package/dist/run/workspace-fingerprint.d.ts +10 -3
  96. package/dist/run/workspace-fingerprint.d.ts.map +1 -1
  97. package/dist/run/workspace-fingerprint.js +17 -8
  98. package/dist/run/workspace-fingerprint.js.map +1 -1
  99. package/dist/runtime/query/checkpoint.d.ts +14 -0
  100. package/dist/runtime/query/checkpoint.d.ts.map +1 -1
  101. package/dist/runtime/query/checkpoint.js +82 -1
  102. package/dist/runtime/query/checkpoint.js.map +1 -1
  103. package/dist/runtime/query/executor.d.ts +6 -0
  104. package/dist/runtime/query/executor.d.ts.map +1 -1
  105. package/dist/runtime/query/executor.js +120 -24
  106. package/dist/runtime/query/executor.js.map +1 -1
  107. package/dist/runtime/query/index.d.ts +12 -8
  108. package/dist/runtime/query/index.d.ts.map +1 -1
  109. package/dist/runtime/query/index.js +55 -7
  110. package/dist/runtime/query/index.js.map +1 -1
  111. package/dist/runtime/query/iteration/index.d.ts +13 -3
  112. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  113. package/dist/runtime/query/iteration/index.js +356 -42
  114. package/dist/runtime/query/iteration/index.js.map +1 -1
  115. package/dist/runtime/query/iteration/native-output.d.ts +10 -0
  116. package/dist/runtime/query/iteration/native-output.d.ts.map +1 -0
  117. package/dist/runtime/query/iteration/native-output.js +32 -0
  118. package/dist/runtime/query/iteration/native-output.js.map +1 -0
  119. package/dist/runtime/query/iteration/phases/advisory.d.ts.map +1 -1
  120. package/dist/runtime/query/iteration/phases/advisory.js +18 -13
  121. package/dist/runtime/query/iteration/phases/advisory.js.map +1 -1
  122. package/dist/runtime/query/iteration/phases/compaction.d.ts +2 -0
  123. package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
  124. package/dist/runtime/query/iteration/phases/compaction.js +32 -11
  125. package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
  126. package/dist/runtime/query/iteration/phases/context.d.ts +11 -0
  127. package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
  128. package/dist/runtime/query/iteration/phases/context.js.map +1 -1
  129. package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
  130. package/dist/runtime/query/iteration/phases/tool-review.js +1 -1
  131. package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
  132. package/dist/runtime/query/iteration/phases/working-memory.d.ts +1 -1
  133. package/dist/runtime/query/iteration/phases/working-memory.d.ts.map +1 -1
  134. package/dist/runtime/query/iteration/phases/working-memory.js +23 -6
  135. package/dist/runtime/query/iteration/phases/working-memory.js.map +1 -1
  136. package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -1
  137. package/dist/runtime/query/iteration/stream-turn.js +12 -5
  138. package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
  139. package/dist/runtime/query/prompt.d.ts.map +1 -1
  140. package/dist/runtime/query/prompt.js +14 -4
  141. package/dist/runtime/query/prompt.js.map +1 -1
  142. package/dist/runtime/query/steering.d.ts +6 -2
  143. package/dist/runtime/query/steering.d.ts.map +1 -1
  144. package/dist/runtime/query/steering.js +17 -2
  145. package/dist/runtime/query/steering.js.map +1 -1
  146. package/dist/runtime/query/tool-call-budget.d.ts +19 -0
  147. package/dist/runtime/query/tool-call-budget.d.ts.map +1 -0
  148. package/dist/runtime/query/tool-call-budget.js +110 -0
  149. package/dist/runtime/query/tool-call-budget.js.map +1 -0
  150. package/dist/runtime/query/tooling.d.ts +2 -0
  151. package/dist/runtime/query/tooling.d.ts.map +1 -1
  152. package/dist/runtime/query/tooling.js +4 -0
  153. package/dist/runtime/query/tooling.js.map +1 -1
  154. package/dist/sandbox/file-walk-program.d.ts +3 -0
  155. package/dist/sandbox/file-walk-program.d.ts.map +1 -0
  156. package/dist/sandbox/file-walk-program.js +94 -0
  157. package/dist/sandbox/file-walk-program.js.map +1 -0
  158. package/dist/sandbox/file-walk.d.ts +12 -0
  159. package/dist/sandbox/file-walk.d.ts.map +1 -0
  160. package/dist/sandbox/file-walk.js +429 -0
  161. package/dist/sandbox/file-walk.js.map +1 -0
  162. package/dist/sandbox/index.d.ts +2 -0
  163. package/dist/sandbox/index.d.ts.map +1 -1
  164. package/dist/sandbox/index.js +1 -0
  165. package/dist/sandbox/index.js.map +1 -1
  166. package/dist/sandbox/provider/local.d.ts.map +1 -1
  167. package/dist/sandbox/provider/local.js +9 -0
  168. package/dist/sandbox/provider/local.js.map +1 -1
  169. package/dist/scheduler/completion-inbox.d.ts +3 -2
  170. package/dist/scheduler/completion-inbox.d.ts.map +1 -1
  171. package/dist/scheduler/completion-inbox.js +29 -15
  172. package/dist/scheduler/completion-inbox.js.map +1 -1
  173. package/dist/scheduler/local.d.ts.map +1 -1
  174. package/dist/scheduler/local.js +1 -0
  175. package/dist/scheduler/local.js.map +1 -1
  176. package/dist/session/handoff/capacity.d.ts +2 -2
  177. package/dist/session/handoff/capacity.d.ts.map +1 -1
  178. package/dist/session/handoff/capacity.js +3 -2
  179. package/dist/session/handoff/capacity.js.map +1 -1
  180. package/dist/store/memory/disk.d.ts +6 -2
  181. package/dist/store/memory/disk.d.ts.map +1 -1
  182. package/dist/store/memory/disk.js +42 -25
  183. package/dist/store/memory/disk.js.map +1 -1
  184. package/dist/store/memory/index.d.ts +4 -0
  185. package/dist/store/memory/index.d.ts.map +1 -1
  186. package/dist/store/memory/index.js +55 -19
  187. package/dist/store/memory/index.js.map +1 -1
  188. package/dist/store/memory/memory.d.ts +3 -2
  189. package/dist/store/memory/memory.d.ts.map +1 -1
  190. package/dist/store/memory/memory.js +11 -2
  191. package/dist/store/memory/memory.js.map +1 -1
  192. package/dist/store/memory/operation-lock.d.ts +11 -0
  193. package/dist/store/memory/operation-lock.d.ts.map +1 -0
  194. package/dist/store/memory/operation-lock.js +94 -0
  195. package/dist/store/memory/operation-lock.js.map +1 -0
  196. package/dist/tools/builtins/bash.d.ts.map +1 -1
  197. package/dist/tools/builtins/bash.js +215 -59
  198. package/dist/tools/builtins/bash.js.map +1 -1
  199. package/dist/tools/builtins/computer-use.d.ts.map +1 -1
  200. package/dist/tools/builtins/computer-use.js +60 -10
  201. package/dist/tools/builtins/computer-use.js.map +1 -1
  202. package/dist/tools/builtins/glob.d.ts +2 -1
  203. package/dist/tools/builtins/glob.d.ts.map +1 -1
  204. package/dist/tools/builtins/glob.js +95 -103
  205. package/dist/tools/builtins/glob.js.map +1 -1
  206. package/dist/tools/builtins/grep.d.ts.map +1 -1
  207. package/dist/tools/builtins/grep.js +155 -111
  208. package/dist/tools/builtins/grep.js.map +1 -1
  209. package/dist/tools/builtins/ls.js +2 -2
  210. package/dist/tools/builtins/ls.js.map +1 -1
  211. package/dist/tools/builtins/run-code.d.ts +6 -0
  212. package/dist/tools/builtins/run-code.d.ts.map +1 -1
  213. package/dist/tools/builtins/run-code.js +15 -4
  214. package/dist/tools/builtins/run-code.js.map +1 -1
  215. package/dist/tools/builtins/search-tools.d.ts.map +1 -1
  216. package/dist/tools/builtins/search-tools.js +14 -2
  217. package/dist/tools/builtins/search-tools.js.map +1 -1
  218. package/dist/tools/defineTool.d.ts +2 -0
  219. package/dist/tools/defineTool.d.ts.map +1 -1
  220. package/dist/tools/defineTool.js +3 -0
  221. package/dist/tools/defineTool.js.map +1 -1
  222. package/dist/tools/memory/delete.d.ts +4 -0
  223. package/dist/tools/memory/delete.d.ts.map +1 -0
  224. package/dist/tools/memory/delete.js +26 -0
  225. package/dist/tools/memory/delete.js.map +1 -0
  226. package/dist/tools/memory/index.d.ts +2 -0
  227. package/dist/tools/memory/index.d.ts.map +1 -1
  228. package/dist/tools/memory/index.js +6 -0
  229. package/dist/tools/memory/index.js.map +1 -1
  230. package/dist/tools/memory/save.d.ts.map +1 -1
  231. package/dist/tools/memory/save.js +3 -2
  232. package/dist/tools/memory/save.js.map +1 -1
  233. package/dist/tools/memory/search.d.ts.map +1 -1
  234. package/dist/tools/memory/search.js +12 -5
  235. package/dist/tools/memory/search.js.map +1 -1
  236. package/dist/tools/memory/update.d.ts +4 -0
  237. package/dist/tools/memory/update.d.ts.map +1 -0
  238. package/dist/tools/memory/update.js +47 -0
  239. package/dist/tools/memory/update.js.map +1 -0
  240. package/dist/types/agent/scheduler.d.ts +5 -0
  241. package/dist/types/agent/scheduler.d.ts.map +1 -1
  242. package/dist/types/agent/task.d.ts +10 -0
  243. package/dist/types/agent/task.d.ts.map +1 -1
  244. package/dist/types/computer-use/index.d.ts +6 -0
  245. package/dist/types/computer-use/index.d.ts.map +1 -1
  246. package/dist/types/computer-use/index.js.map +1 -1
  247. package/dist/types/errors/catalog.js +1 -1
  248. package/dist/types/errors/catalog.js.map +1 -1
  249. package/dist/types/hitl/index.d.ts +11 -1
  250. package/dist/types/hitl/index.d.ts.map +1 -1
  251. package/dist/types/hitl/index.js.map +1 -1
  252. package/dist/types/memory/index.d.ts +14 -1
  253. package/dist/types/memory/index.d.ts.map +1 -1
  254. package/dist/types/project/entity.d.ts +1 -0
  255. package/dist/types/project/entity.d.ts.map +1 -1
  256. package/dist/types/provider/config.d.ts +2 -0
  257. package/dist/types/provider/config.d.ts.map +1 -1
  258. package/dist/types/provider/model.d.ts +4 -0
  259. package/dist/types/provider/model.d.ts.map +1 -1
  260. package/dist/types/run/answer-review.d.ts +2 -0
  261. package/dist/types/run/answer-review.d.ts.map +1 -1
  262. package/dist/types/run/events.d.ts +9 -0
  263. package/dist/types/run/events.d.ts.map +1 -1
  264. package/dist/types/run/events.js.map +1 -1
  265. package/dist/types/run/prepare-step.d.ts +19 -1
  266. package/dist/types/run/prepare-step.d.ts.map +1 -1
  267. package/dist/types/sandbox/index.d.ts +20 -0
  268. package/dist/types/sandbox/index.d.ts.map +1 -1
  269. package/dist/types/sandbox/index.js.map +1 -1
  270. package/dist/types/structured-output/index.d.ts +8 -2
  271. package/dist/types/structured-output/index.d.ts.map +1 -1
  272. package/dist/types/tool/index.d.ts +15 -0
  273. package/dist/types/tool/index.d.ts.map +1 -1
  274. package/dist/types/tool/index.js.map +1 -1
  275. package/dist/utils/json-snapshot.d.ts +3 -0
  276. package/dist/utils/json-snapshot.d.ts.map +1 -0
  277. package/dist/utils/json-snapshot.js +79 -0
  278. package/dist/utils/json-snapshot.js.map +1 -0
  279. package/package.json +8 -1
  280. package/src/bridge/a2a/mapper.ts +2 -0
  281. package/src/bridge/sse/mapper.ts +2 -0
  282. package/src/compaction/extractor.ts +21 -8
  283. package/src/compaction/manager.ts +11 -1
  284. package/src/compaction/verifier.ts +80 -18
  285. package/src/eval/harness-verification.ts +270 -0
  286. package/src/eval/index.ts +10 -0
  287. package/src/execution/code-runtime/json-value.ts +82 -0
  288. package/src/execution/code-runtime/worker-program.ts +239 -0
  289. package/src/execution/code-runtime/worker.ts +164 -111
  290. package/src/manager/agent/lifecycle.ts +340 -45
  291. package/src/manager/run/persistence.ts +7 -0
  292. package/src/provider/capabilities.ts +25 -1
  293. package/src/provider/fallback.ts +2 -0
  294. package/src/provider/token-budget.ts +52 -8
  295. package/src/public-runtime.ts +6 -1
  296. package/src/public-tools.ts +5 -1
  297. package/src/public-types.ts +1 -0
  298. package/src/registry/tool/execute.ts +17 -79
  299. package/src/run/command-gate.ts +61 -13
  300. package/src/run/index.ts +2 -0
  301. package/src/run/memory-promoter.ts +32 -40
  302. package/src/run/memory-recall.ts +254 -0
  303. package/src/run/reporter.ts +1 -0
  304. package/src/run/token-budget.ts +4 -0
  305. package/src/run/workspace-fingerprint.ts +17 -8
  306. package/src/runtime/query/checkpoint.ts +96 -2
  307. package/src/runtime/query/executor.ts +131 -28
  308. package/src/runtime/query/index.ts +81 -16
  309. package/src/runtime/query/iteration/index.ts +406 -53
  310. package/src/runtime/query/iteration/native-output.ts +35 -0
  311. package/src/runtime/query/iteration/phases/advisory.ts +21 -11
  312. package/src/runtime/query/iteration/phases/compaction.ts +36 -14
  313. package/src/runtime/query/iteration/phases/context.ts +11 -0
  314. package/src/runtime/query/iteration/phases/tool-review.ts +5 -1
  315. package/src/runtime/query/iteration/phases/working-memory.ts +23 -5
  316. package/src/runtime/query/iteration/stream-turn.ts +11 -5
  317. package/src/runtime/query/prompt.ts +21 -4
  318. package/src/runtime/query/steering.ts +19 -2
  319. package/src/runtime/query/tool-call-budget.ts +115 -0
  320. package/src/runtime/query/tooling.ts +6 -0
  321. package/src/sandbox/file-walk-program.ts +93 -0
  322. package/src/sandbox/file-walk.ts +463 -0
  323. package/src/sandbox/index.ts +2 -0
  324. package/src/sandbox/provider/local.ts +13 -0
  325. package/src/scheduler/completion-inbox.ts +29 -19
  326. package/src/scheduler/local.ts +1 -0
  327. package/src/session/handoff/capacity.ts +7 -3
  328. package/src/store/memory/disk.ts +52 -30
  329. package/src/store/memory/index.ts +75 -27
  330. package/src/store/memory/memory.ts +18 -6
  331. package/src/store/memory/operation-lock.ts +99 -0
  332. package/src/tools/builtins/bash.ts +230 -58
  333. package/src/tools/builtins/computer-use.ts +61 -7
  334. package/src/tools/builtins/glob.ts +104 -122
  335. package/src/tools/builtins/grep.ts +166 -132
  336. package/src/tools/builtins/ls.ts +2 -2
  337. package/src/tools/builtins/run-code.ts +24 -5
  338. package/src/tools/builtins/search-tools.ts +18 -3
  339. package/src/tools/defineTool.ts +5 -0
  340. package/src/tools/memory/delete.ts +29 -0
  341. package/src/tools/memory/index.ts +6 -0
  342. package/src/tools/memory/save.ts +4 -2
  343. package/src/tools/memory/search.ts +12 -5
  344. package/src/tools/memory/update.ts +50 -0
  345. package/src/types/agent/scheduler.ts +6 -0
  346. package/src/types/agent/task.ts +13 -0
  347. package/src/types/computer-use/index.ts +6 -0
  348. package/src/types/errors/catalog.ts +1 -1
  349. package/src/types/hitl/index.ts +11 -1
  350. package/src/types/memory/index.ts +16 -1
  351. package/src/types/project/entity.ts +1 -0
  352. package/src/types/provider/config.ts +2 -0
  353. package/src/types/provider/model.ts +4 -0
  354. package/src/types/run/answer-review.ts +2 -0
  355. package/src/types/run/events.ts +10 -0
  356. package/src/types/run/prepare-step.ts +19 -1
  357. package/src/types/sandbox/index.ts +21 -0
  358. package/src/types/structured-output/index.ts +15 -2
  359. package/src/types/tool/index.ts +16 -0
  360. package/src/utils/json-snapshot.ts +76 -0
@@ -1,11 +1,13 @@
1
- import { exec } from 'node:child_process'
2
- import { promisify } from 'node:util'
1
+ import { spawn } from 'node:child_process'
2
+ import { StringDecoder } from 'node:string_decoder'
3
3
  import { z } from 'zod'
4
+ import { SANDBOX_KILL_GRACE_MS } from '../../constants/sandbox/index.js'
4
5
  import { DANGEROUS_PATTERNS } from '../../constants/tools/index.js'
6
+ import { killTree } from '../../process/kill-tree.js'
7
+ import { subscribeToAbort } from '../../utils/abort.js'
5
8
  import { defineTool } from '../defineTool.js'
6
9
  import { scrubInheritedEnv } from '../env-scrub.js'
7
10
 
8
- const execAsync = promisify(exec)
9
11
  // Namzu owns its own bash timeout knob — `NAMZU_BASH_TIMEOUT_MS`.
10
12
  // The Vandal fallback (`VANDAL_NAMZU_TIMEOUT_MS`) lived here as a
11
13
  // historical bridge while Namzu was carved out of the Vandal repo,
@@ -67,25 +69,205 @@ const inputSchema = z.object({
67
69
 
68
70
  type BashInput = z.infer<typeof inputSchema>
69
71
 
72
+ const MAX_SHELL_PROGRESS_CHARS = 160
73
+
74
+ /** Keep a bounded tail even when a process never writes a newline. */
75
+ function appendProgressTail(previous: string, chunk: string, start: number, end: number): string {
76
+ return (previous + chunk.slice(Math.max(start, end - MAX_SHELL_PROGRESS_CHARS), end)).slice(
77
+ -MAX_SHELL_PROGRESS_CHARS,
78
+ )
79
+ }
80
+
81
+ /** Clipping a UTF-16 string must not display half of a surrogate pair. */
82
+ function progressLine(value: string): string {
83
+ let line = value.trim()
84
+ const first = line.charCodeAt(0)
85
+ if (first >= 0xdc00 && first <= 0xdfff) line = line.slice(1)
86
+ const last = line.charCodeAt(line.length - 1)
87
+ if (last >= 0xd800 && last <= 0xdbff) line = line.slice(0, -1)
88
+ return line
89
+ }
90
+
70
91
  /**
71
- * The last line worth showing from one chunk of streamed output.
72
- *
73
- * A progress line is a status, not a log: the host renders one line and
74
- * replaces it as the next arrives, so sending a whole chunk sends a wall
75
- * of text into a slot that shows one line of it. A chunk usually ends
76
- * mid-line and usually ends with a newline, so the last NON-EMPTY line is
77
- * the most recent complete thing the command actually said.
78
- *
79
- * Progress is capped rather than truncated with an ellipsis: this is
80
- * glanced at, and a marker in a line nobody reads to the end is noise.
92
+ * One latest line per stream, independent of transport chunk boundaries.
93
+ * Reports stay synchronous: the executor owns coalescing and backpressure.
94
+ * This projection adds neither an output log nor a queue of pending reports.
81
95
  */
82
- function lastNonEmptyLine(chunk: string): string | undefined {
83
- const lines = chunk.split('\n')
84
- for (let i = lines.length - 1; i >= 0; i--) {
85
- const line = lines[i]?.trim()
86
- if (line) return line.length > 160 ? line.slice(0, 160) : line
96
+ function shellProgress(report?: (message: string) => void) {
97
+ if (!report) return undefined
98
+ const partial = { stdout: '', stderr: '' }
99
+ let lastMessage = ''
100
+ return ({ stream, data }: { stream: 'stdout' | 'stderr'; data: string }): void => {
101
+ let line = partial[stream]
102
+ let latest = ''
103
+ let start = 0
104
+ // Scan delimiters without allocating an array for arbitrarily chatty output.
105
+ for (const match of data.matchAll(/[\r\n]/g)) {
106
+ line = appendProgressTail(line, data, start, match.index)
107
+ latest = progressLine(line) || latest
108
+ line = ''
109
+ start = match.index + 1
110
+ }
111
+ line = appendProgressTail(line, data, start, data.length)
112
+ partial[stream] = line
113
+ const message = progressLine(line) || latest
114
+ if (!message || message === lastMessage) return
115
+ lastMessage = message
116
+ try {
117
+ report(message)
118
+ } catch {
119
+ // A diagnostic observer cannot break the process output reader.
120
+ }
87
121
  }
88
- return undefined
122
+ }
123
+
124
+ /**
125
+ * Keep the shell's process group until inherited pipes close. Node's exec
126
+ * timeout/AbortSignal kills only the wrapper and closes its pipes immediately,
127
+ * leaving the command and its descendants running after the promise settles.
128
+ */
129
+ function execHostShell(
130
+ command: string,
131
+ options: {
132
+ cwd: string
133
+ env: NodeJS.ProcessEnv
134
+ timeout: number
135
+ maxBuffer: number
136
+ signal?: AbortSignal
137
+ onOutput?: ReturnType<typeof shellProgress>
138
+ },
139
+ ): Promise<{ stdout: string; stderr: string }> {
140
+ options.signal?.throwIfAborted()
141
+ return new Promise((resolve, reject) => {
142
+ const child = spawn(command, {
143
+ cwd: options.cwd,
144
+ env: options.env,
145
+ shell: true,
146
+ // killTree's negative PID must never target the caller's own group.
147
+ detached: process.platform !== 'win32',
148
+ })
149
+ const captures = {
150
+ stdout: {
151
+ chunks: [] as Buffer[],
152
+ bytes: 0,
153
+ truncated: false,
154
+ decoder: new StringDecoder('utf8'),
155
+ },
156
+ stderr: {
157
+ chunks: [] as Buffer[],
158
+ bytes: 0,
159
+ truncated: false,
160
+ decoder: new StringDecoder('utf8'),
161
+ },
162
+ }
163
+ let cause: 'caller' | 'timeout' | 'maxBuffer' | undefined
164
+ let failure: (Error & { code?: string | number }) | undefined
165
+ let escalation: ReturnType<typeof setTimeout> | undefined
166
+ let disposeAbort: (() => void) | undefined
167
+ let closed = false
168
+ const cancel = (origin: NonNullable<typeof cause>) => {
169
+ if (closed || cause !== undefined) return
170
+ cause = origin
171
+ escalation = setTimeout(() => {
172
+ escalation = undefined
173
+ killTree(child, 'SIGKILL')
174
+ // A descendant can deliberately start a separate session. It is
175
+ // outside this group, but its inherited pipes must not hold the
176
+ // cancelled call forever. Keep what was captured during grace.
177
+ child.stdin?.destroy()
178
+ child.stdout?.destroy()
179
+ child.stderr?.destroy()
180
+ }, SANDBOX_KILL_GRACE_MS)
181
+ escalation.unref?.()
182
+ killTree(child, 'SIGTERM')
183
+ }
184
+ const capture = (stream: 'stdout' | 'stderr', chunk: Buffer) => {
185
+ const state = captures[stream]
186
+ const kept = Math.min(chunk.length, options.maxBuffer - state.bytes)
187
+ if (kept > 0) {
188
+ state.chunks.push(Buffer.from(chunk.subarray(0, kept)))
189
+ state.bytes += kept
190
+ }
191
+ if (kept < chunk.length) {
192
+ state.truncated = true
193
+ if (cause === undefined) {
194
+ failure = Object.assign(new RangeError(`${stream} maxBuffer length exceeded`), {
195
+ code: 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER',
196
+ })
197
+ cancel('maxBuffer')
198
+ }
199
+ }
200
+ if (options.onOutput) {
201
+ const data = state.decoder.write(chunk)
202
+ if (data) options.onOutput({ stream, data })
203
+ }
204
+ }
205
+ const onStdout = (chunk: Buffer) => capture('stdout', chunk)
206
+ const onStderr = (chunk: Buffer) => capture('stderr', chunk)
207
+ child.stdout?.on('data', onStdout)
208
+ child.stderr?.on('data', onStderr)
209
+ child.once('error', (error: NodeJS.ErrnoException) => {
210
+ failure ??= error
211
+ })
212
+ child.once('close', (code, signal) => {
213
+ closed = true
214
+ disposeAbort?.()
215
+ clearTimeout(deadline)
216
+ if (escalation !== undefined) {
217
+ clearTimeout(escalation)
218
+ // Pipes closing can precede a descendant which closed its copies.
219
+ // Force the owned group now instead of retaining its PID in a timer.
220
+ killTree(child, 'SIGKILL')
221
+ }
222
+ child.stdout?.off('data', onStdout)
223
+ child.stderr?.off('data', onStderr)
224
+ for (const stream of ['stdout', 'stderr'] as const) {
225
+ const data = captures[stream].decoder.end()
226
+ if (data) options.onOutput?.({ stream, data })
227
+ }
228
+ const decodeCapture = (stream: 'stdout' | 'stderr') => {
229
+ const state = captures[stream]
230
+ const bytes = Buffer.concat(state.chunks, state.bytes)
231
+ // Do not flush an incomplete UTF-8 character cut by our byte cap.
232
+ return state.truncated ? new StringDecoder('utf8').write(bytes) : bytes.toString('utf8')
233
+ }
234
+ const stdout = decodeCapture('stdout')
235
+ const stderr = decodeCapture('stderr')
236
+ if (!failure && cause === undefined && code === 0 && signal === null) {
237
+ resolve({ stdout, stderr })
238
+ return
239
+ }
240
+ const error =
241
+ failure ??
242
+ (cause === 'caller'
243
+ ? Object.assign(
244
+ new Error('The operation was aborted', { cause: options.signal?.reason }),
245
+ {
246
+ name: 'AbortError',
247
+ code: 'ABORT_ERR',
248
+ },
249
+ )
250
+ : new Error(`Command failed: ${command}\n${stderr}`))
251
+ reject(
252
+ Object.assign(error, {
253
+ stdout,
254
+ stderr,
255
+ stdoutTruncated: captures.stdout.truncated,
256
+ stderrTruncated: captures.stderr.truncated,
257
+ code: failure?.code ?? (cause === 'caller' ? 'ABORT_ERR' : code),
258
+ killed: cause !== undefined,
259
+ signal,
260
+ timedOut: cause === 'timeout',
261
+ }),
262
+ )
263
+ })
264
+ const deadline = setTimeout(() => cancel('timeout'), options.timeout)
265
+ deadline.unref?.()
266
+ if (options.signal) {
267
+ disposeAbort = subscribeToAbort(options.signal, () => cancel('caller'))
268
+ if (options.signal.aborted) cancel('caller')
269
+ }
270
+ })
89
271
  }
90
272
 
91
273
  function isDangerousCommand(command: string): boolean {
@@ -192,6 +374,7 @@ export const BashTool = defineTool({
192
374
  // sandbox can be added later as an explicit
193
375
  // `SandboxExecOptions.workspaceRelativeCwd` field; the bash
194
376
  // builtin doesn't have that requirement today.
377
+ const onOutput = shellProgress(context.report)
195
378
  if (context.sandbox) {
196
379
  const result = await context.sandbox.exec('/bin/sh', ['-c', input.command], {
197
380
  timeout: input.timeout,
@@ -206,22 +389,9 @@ export const BashTool = defineTool({
206
389
  // durable transcript — so this is a progress signal, not a
207
390
  // second copy of the output. `result.stdout` remains the
208
391
  // answer the model is given.
209
- onOutput: context.report
210
- ? ({ data }) => {
211
- const line = lastNonEmptyLine(data)
212
- if (line) context.report?.(line)
213
- }
214
- : undefined,
392
+ onOutput,
215
393
  })
216
394
 
217
- if (result.timedOut) {
218
- return {
219
- success: false,
220
- output: '',
221
- error: `Command timed out after ${input.timeout}ms`,
222
- }
223
- }
224
-
225
395
  // The sandbox reports when IT clipped a stream. Dropping those
226
396
  // flags meant the model saw a complete-looking result that had
227
397
  // silently lost its tail — and the kernel's own convention is
@@ -235,41 +405,32 @@ export const BashTool = defineTool({
235
405
  result.stdout ? `STDOUT:\n${result.stdout}` : '',
236
406
  result.stderr ? `STDERR:\n${result.stderr}` : '',
237
407
  clipped.length > 0
238
- ? `[${clipped.join(' and ')} was truncated by the sandbox output cap — re-run with a filter (grep/head/tail) to see the rest]`
408
+ ? `[${clipped.join(' and ')} was truncated by the sandbox output cap. The omitted output is unavailable in this result. Use a saved artifact or a read-only observation; do not repeat a state-changing action to recover its output.]`
239
409
  : '',
240
410
  ]
241
411
  .filter(Boolean)
242
412
  .join('\n\n')
243
413
 
244
414
  return {
245
- success: result.exitCode === 0,
415
+ success: !result.timedOut && result.exitCode === 0,
246
416
  output: output || '(no output)',
247
417
  data: {
248
418
  exitCode: result.exitCode,
249
419
  sandboxed: true,
420
+ timedOut: result.timedOut,
250
421
  stdoutTruncated: result.stdoutTruncated ?? false,
251
422
  stderrTruncated: result.stderrTruncated ?? false,
252
423
  },
253
- error: result.exitCode !== 0 ? `Command exited with code ${result.exitCode}` : undefined,
424
+ error: result.timedOut
425
+ ? `Command timed out after ${input.timeout}ms. Any captured output before the deadline is above.`
426
+ : result.exitCode !== 0
427
+ ? `Command exited with code ${result.exitCode}`
428
+ : undefined,
254
429
  }
255
430
  }
256
431
 
257
- // Thread the run/deadline signal into the child process. Without it
258
- // a Stop tore down the model stream and left the command running,
259
- // and the executor's deadline could only ever DETACH from the tool
260
- // rather than end the work it started.
261
- // `exec` REJECTS on a non-zero exit, on its own timeout, and on a
262
- // kill — and the rejection carries `stdout`, `stderr`, `code` and
263
- // `killed`. Letting it propagate threw all of that away: the registry
264
- // turned the throw into a structured failure, so the model was told a
265
- // command failed and not one word about how.
266
- //
267
- // That is the common case, not an edge one. A failing test run and a
268
- // failing build are the two things an agent runs bash for most, and
269
- // both exit non-zero WITH the output that explains why. The sandbox
270
- // branch above already reports all of it; this branch did not, so the
271
- // same command told the model two different amounts depending on where
272
- // it happened to run.
432
+ // The owned runner retains stdout/stderr on non-zero exit and timeout,
433
+ // so a failed command still returns the evidence explaining its failure.
273
434
  // The inherited half is scrubbed; `context.env` is not. Inheritance is
274
435
  // implicit — nobody decided this command should see `process.env` — while
275
436
  // a `context.env` key is one a host wrote on purpose. See
@@ -278,14 +439,14 @@ export const BashTool = defineTool({
278
439
  const inherited = scrubInheritedEnv()
279
440
 
280
441
  try {
281
- const { stdout, stderr } = await execAsync(input.command, {
442
+ const { stdout, stderr } = await execHostShell(input.command, {
282
443
  cwd: context.workingDirectory,
283
444
  timeout: input.timeout,
284
445
  env: { ...inherited.env, ...context.env },
285
446
  maxBuffer: DEFAULT_BASH_MAX_BUFFER_BYTES,
286
447
  signal: context.abortSignal,
448
+ onOutput,
287
449
  })
288
-
289
450
  return {
290
451
  success: true,
291
452
  output: formatShellOutput(stdout, stderr) || '(no output)',
@@ -295,25 +456,34 @@ export const BashTool = defineTool({
295
456
  const failure = err as NodeJS.ErrnoException & {
296
457
  stdout?: string
297
458
  stderr?: string
459
+ stdoutTruncated?: boolean
460
+ stderrTruncated?: boolean
298
461
  code?: number | string
299
462
  killed?: boolean
300
463
  signal?: string
464
+ timedOut?: boolean
301
465
  }
302
466
 
303
467
  // A caller-owned Stop is the caller's, not a command failure.
304
468
  if (context.abortSignal?.aborted) throw err
305
469
 
306
- // `exec` reports its own timeout as a kill, and the distinction
307
- // matters to the model: "ran out of time" is a different next move
308
- // from "exited 1".
309
- const timedOut = failure.killed === true && failure.signal === 'SIGTERM'
470
+ // A maxBuffer stop or an ordinary signal is not a deadline. The
471
+ // runner latches the first cancellation cause before signalling.
472
+ const timedOut = failure.timedOut === true
310
473
  const exitCode = typeof failure.code === 'number' ? failure.code : undefined
474
+ const clipped = [
475
+ failure.stdoutTruncated ? 'stdout' : '',
476
+ failure.stderrTruncated ? 'stderr' : '',
477
+ ].filter(Boolean)
311
478
  // Only on the failure path. A successful command did not need to know,
312
479
  // and appending this to every result would make the common case noisy
313
480
  // to buy nothing. A failing one is exactly where "authentication
314
481
  // failed" has to be distinguishable from "the variable was withheld".
315
482
  const output = [
316
483
  formatShellOutput(failure.stdout, failure.stderr),
484
+ clipped.length > 0
485
+ ? `[${clipped.join(' and ')} ${clipped.length === 1 ? 'was' : 'were'} truncated by the host output cap. The omitted output is unavailable in this result.]`
486
+ : '',
317
487
  describeWithheldEnv(inherited.dropped),
318
488
  ]
319
489
  .filter(Boolean)
@@ -325,6 +495,8 @@ export const BashTool = defineTool({
325
495
  data: {
326
496
  ...(exitCode !== undefined ? { exitCode } : {}),
327
497
  timedOut,
498
+ stdoutTruncated: failure.stdoutTruncated ?? false,
499
+ stderrTruncated: failure.stderrTruncated ?? false,
328
500
  ...(failure.signal ? { signal: failure.signal } : {}),
329
501
  },
330
502
  error: timedOut
@@ -63,7 +63,7 @@ const pointModelInputSchema = {
63
63
  additionalProperties: false,
64
64
  } as const
65
65
 
66
- const modelInputSchema: Record<string, unknown> = {
66
+ const modelInputSchema = {
67
67
  type: 'object',
68
68
  properties: {
69
69
  type: {
@@ -136,16 +136,17 @@ function requiredCapability(type: ComputerUseAction['type']): keyof ComputerUseC
136
136
 
137
137
  function buildDescription(host: ComputerUseHost): string {
138
138
  const caps = host.capabilities
139
- const available: string[] = []
140
- if (caps.screenshot) available.push('screenshot')
141
- if (caps.cursorPosition) available.push('cursor_position')
142
- if (caps.mouse) available.push('mouse_move, mouse_click, mouse_drag, scroll')
143
- if (caps.keyboard) available.push('type_text, key')
139
+ const available = availableActions(caps)
144
140
  const unavailable: string[] = []
145
141
  if (!caps.screenshot) unavailable.push('screenshot')
146
142
  if (!caps.cursorPosition) unavailable.push('cursor_position')
147
143
  if (!caps.mouse) unavailable.push('mouse')
148
144
  if (!caps.keyboard) unavailable.push('keyboard')
145
+ if (caps.supportedActions) {
146
+ for (const action of actionSchema.options.map((option) => option.shape.type.value)) {
147
+ if (!available.includes(action)) unavailable.push(action)
148
+ }
149
+ }
149
150
 
150
151
  const lines = [
151
152
  `Controls the user's desktop on a ${caps.displayServer} host. Use to take screenshots and drive mouse/keyboard input for GUI tasks.`,
@@ -161,9 +162,42 @@ function buildDescription(host: ComputerUseHost): string {
161
162
  lines.push(
162
163
  'Coordinates are in logical pixels from the top-left of the primary display. Call getDisplayGeometry through screenshot output before clicking to confirm bounds.',
163
164
  )
165
+ if (caps.mouseClickButtons && available.includes('mouse_click'))
166
+ lines.push(`Click buttons: ${caps.mouseClickButtons.join(', ') || 'none'}.`)
167
+ if (caps.mouseDragButtons && available.includes('mouse_drag'))
168
+ lines.push(`Drag buttons: ${caps.mouseDragButtons.join(', ') || 'none'}.`)
164
169
  return lines.join(' ')
165
170
  }
166
171
 
172
+ function availableActions(caps: ComputerUseCapabilities): ComputerUseAction['type'][] {
173
+ return actionSchema.options
174
+ .map((option) => option.shape.type.value)
175
+ .filter((action) => {
176
+ const required = requiredCapability(action)
177
+ return (
178
+ (required === null || caps[required] === true) &&
179
+ (caps.supportedActions === undefined || caps.supportedActions.includes(action)) &&
180
+ (action !== 'mouse_click' || caps.mouseClickButtons?.length !== 0) &&
181
+ (action !== 'mouse_drag' || caps.mouseDragButtons?.length !== 0)
182
+ )
183
+ })
184
+ }
185
+
186
+ function hostModelSchema(caps: ComputerUseCapabilities): Record<string, unknown> {
187
+ const schema = structuredClone(modelInputSchema)
188
+ const actions = availableActions(caps)
189
+ // An unavailable host remains a diagnostic tool. Avoid invalid empty enums
190
+ // on provider wires; every execution is still refused before host access.
191
+ if (actions.length > 0) schema.properties.type.enum = actions
192
+ const buttons = new Set<string>()
193
+ if (actions.includes('mouse_click'))
194
+ for (const button of caps.mouseClickButtons ?? ['left', 'right', 'middle']) buttons.add(button)
195
+ if (actions.includes('mouse_drag'))
196
+ for (const button of caps.mouseDragButtons ?? ['left', 'right', 'middle']) buttons.add(button)
197
+ if (buttons.size > 0) schema.properties.button.enum = [...buttons]
198
+ return schema
199
+ }
200
+
167
201
  function pointLabel(point: { readonly x: number; readonly y: number }): string {
168
202
  return `(${point.x}, ${point.y})`
169
203
  }
@@ -283,7 +317,7 @@ export function createComputerUseTool(host: ComputerUseHost): ToolDefinition<Act
283
317
  name: COMPUTER_USE_TOOL_NAME,
284
318
  description: buildDescription(host),
285
319
  inputSchema: actionSchema,
286
- modelInputSchema: structuredClone(modelInputSchema),
320
+ modelInputSchema: hostModelSchema(host.capabilities),
287
321
  validationErrorHint:
288
322
  'Action requirements: mouse_move needs "to"; mouse_click needs "at" and "button"; mouse_drag needs "from", "to", and "button"; scroll needs "at", "direction", and positive "amount"; type_text needs "text"; key needs "keys".',
289
323
  category: 'custom',
@@ -310,6 +344,26 @@ export function createComputerUseTool(host: ComputerUseHost): ToolDefinition<Act
310
344
  error: `computer_use: action "${input.type}" requires capability "${required}" which is not available on this host (displayServer=${host.capabilities.displayServer}).${host.capabilities.unavailableReason ? ` ${host.capabilities.unavailableReason} Do not retry; tell the user.` : ''}`,
311
345
  }
312
346
  }
347
+ if (!availableActions(host.capabilities).includes(input.type)) {
348
+ return {
349
+ success: false,
350
+ output: '',
351
+ error: `computer_use: action "${input.type}" is not supported on this host.`,
352
+ }
353
+ }
354
+ const buttons =
355
+ input.type === 'mouse_click'
356
+ ? host.capabilities.mouseClickButtons
357
+ : input.type === 'mouse_drag'
358
+ ? host.capabilities.mouseDragButtons
359
+ : undefined
360
+ if (buttons && 'button' in input && !buttons.includes(input.button)) {
361
+ return {
362
+ success: false,
363
+ output: '',
364
+ error: `computer_use: action "${input.type}" does not support button "${input.button}" on this host.`,
365
+ }
366
+ }
313
367
  try {
314
368
  const result = await host.execute(input as ComputerUseAction)
315
369
  return resultToToolResult(result)