@nathapp/nax-agent 0.1.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 (398) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/LICENSE +21 -0
  3. package/README.md +49 -0
  4. package/dist/coding-tools/coding-tool-bash.d.ts +46 -0
  5. package/dist/coding-tools/coding-tool-bash.js +44 -0
  6. package/dist/coding-tools/coding-tool-extras.d.ts +35 -0
  7. package/dist/coding-tools/coding-tool-extras.js +56 -0
  8. package/dist/coding-tools/coding-tool-sandbox.d.ts +74 -0
  9. package/dist/coding-tools/coding-tool-sandbox.js +215 -0
  10. package/dist/coding-tools/coding-tool-support.d.ts +132 -0
  11. package/dist/coding-tools/coding-tool-support.js +159 -0
  12. package/dist/coding-tools/universal-coding-tools.d.ts +11 -0
  13. package/dist/coding-tools/universal-coding-tools.js +5 -0
  14. package/dist/command-interceptor/index.d.ts +55 -0
  15. package/dist/command-interceptor/index.js +60 -0
  16. package/dist/command-interceptor/shell.d.ts +36 -0
  17. package/dist/command-interceptor/shell.js +137 -0
  18. package/dist/command-safety/build.d.ts +19 -0
  19. package/dist/command-safety/build.js +47 -0
  20. package/dist/command-safety/guard.d.ts +32 -0
  21. package/dist/command-safety/guard.js +179 -0
  22. package/dist/command-safety/identifiers.d.ts +24 -0
  23. package/dist/command-safety/identifiers.js +20 -0
  24. package/dist/command-safety/index.d.ts +17 -0
  25. package/dist/command-safety/index.js +11 -0
  26. package/dist/command-safety/questions.d.ts +19 -0
  27. package/dist/command-safety/questions.js +60 -0
  28. package/dist/command-safety/row.d.ts +2 -0
  29. package/dist/command-safety/row.js +14 -0
  30. package/dist/command-safety/rule-scorer.d.ts +9 -0
  31. package/dist/command-safety/rule-scorer.js +391 -0
  32. package/dist/command-safety/shadow.d.ts +33 -0
  33. package/dist/command-safety/shadow.js +188 -0
  34. package/dist/command-safety/systemone-client.d.ts +16 -0
  35. package/dist/command-safety/systemone-client.js +92 -0
  36. package/dist/command-safety/tap.d.ts +37 -0
  37. package/dist/command-safety/tap.js +77 -0
  38. package/dist/command-safety/temp-only.d.ts +6 -0
  39. package/dist/command-safety/temp-only.js +215 -0
  40. package/dist/command-safety/tmp-write.d.ts +2 -0
  41. package/dist/command-safety/tmp-write.js +112 -0
  42. package/dist/command-safety/types.d.ts +185 -0
  43. package/dist/command-safety/types.js +21 -0
  44. package/dist/config/bash-approval.d.ts +33 -0
  45. package/dist/config/bash-approval.js +30 -0
  46. package/dist/config/catalog-overrides.d.ts +94 -0
  47. package/dist/config/catalog-overrides.js +7 -0
  48. package/dist/config/native-agent/index.d.ts +8 -0
  49. package/dist/config/native-agent/index.js +8 -0
  50. package/dist/config/schemas-sandbox.d.ts +31 -0
  51. package/dist/config/schemas-sandbox.js +45 -0
  52. package/dist/cost/core/index.d.ts +8 -0
  53. package/dist/cost/core/index.js +7 -0
  54. package/dist/cost/estimate.d.ts +34 -0
  55. package/dist/cost/estimate.js +65 -0
  56. package/dist/cost/model-spec.d.ts +28 -0
  57. package/dist/cost/model-spec.js +29 -0
  58. package/dist/cost/standard-types.d.ts +9 -0
  59. package/dist/cost/standard-types.js +1 -0
  60. package/dist/cost/usage-math.d.ts +18 -0
  61. package/dist/cost/usage-math.js +46 -0
  62. package/dist/index.d.ts +26 -0
  63. package/dist/index.js +21 -0
  64. package/dist/infra/agent-logger.d.ts +22 -0
  65. package/dist/infra/agent-logger.js +19 -0
  66. package/dist/infra/credentials-config.d.ts +20 -0
  67. package/dist/infra/credentials-config.js +15 -0
  68. package/dist/infra/errors.d.ts +9 -0
  69. package/dist/infra/errors.js +11 -0
  70. package/dist/infra/index.d.ts +7 -0
  71. package/dist/infra/index.js +7 -0
  72. package/dist/infra/nax-error.d.ts +10 -0
  73. package/dist/infra/nax-error.js +16 -0
  74. package/dist/infra/spin-breaker/hash.d.ts +2 -0
  75. package/dist/infra/spin-breaker/hash.js +5 -0
  76. package/dist/infra/spin-breaker/index.d.ts +120 -0
  77. package/dist/infra/spin-breaker/index.js +340 -0
  78. package/dist/internal/agent-output-env.d.ts +44 -0
  79. package/dist/internal/agent-output-env.js +57 -0
  80. package/dist/internal/argv-exec.d.ts +58 -0
  81. package/dist/internal/argv-exec.js +224 -0
  82. package/dist/internal/bounded-io.d.ts +24 -0
  83. package/dist/internal/bounded-io.js +51 -0
  84. package/dist/internal/command-spec/index.d.ts +29 -0
  85. package/dist/internal/command-spec/index.js +36 -0
  86. package/dist/internal/describe-value-type.d.ts +8 -0
  87. package/dist/internal/describe-value-type.js +14 -0
  88. package/dist/internal/exec-framing.d.ts +18 -0
  89. package/dist/internal/exec-framing.js +22 -0
  90. package/dist/internal/file-lock.d.ts +91 -0
  91. package/dist/internal/file-lock.js +212 -0
  92. package/dist/internal/file-size.d.ts +2 -0
  93. package/dist/internal/file-size.js +12 -0
  94. package/dist/internal/git-add.d.ts +43 -0
  95. package/dist/internal/git-add.js +59 -0
  96. package/dist/internal/git-env.d.ts +59 -0
  97. package/dist/internal/git-env.js +93 -0
  98. package/dist/internal/git-exec.d.ts +58 -0
  99. package/dist/internal/git-exec.js +115 -0
  100. package/dist/internal/path-file-lock.d.ts +23 -0
  101. package/dist/internal/path-file-lock.js +25 -0
  102. package/dist/internal/process-alive.d.ts +42 -0
  103. package/dist/internal/process-alive.js +56 -0
  104. package/dist/internal/process-kill.d.ts +27 -0
  105. package/dist/internal/process-kill.js +54 -0
  106. package/dist/internal/realpath.d.ts +29 -0
  107. package/dist/internal/realpath.js +52 -0
  108. package/dist/internal/redact.d.ts +40 -0
  109. package/dist/internal/redact.js +161 -0
  110. package/dist/internal/shell-quote.d.ts +2 -0
  111. package/dist/internal/shell-quote.js +4 -0
  112. package/dist/internal/sort.d.ts +20 -0
  113. package/dist/internal/sort.js +20 -0
  114. package/dist/internal/strip-control-chars.d.ts +19 -0
  115. package/dist/internal/strip-control-chars.js +38 -0
  116. package/dist/internal/thenable.d.ts +10 -0
  117. package/dist/internal/thenable.js +15 -0
  118. package/dist/internal.d.ts +91 -0
  119. package/dist/internal.js +91 -0
  120. package/dist/native/adapter-deps.d.ts +46 -0
  121. package/dist/native/adapter-deps.js +43 -0
  122. package/dist/native/auth-types.d.ts +70 -0
  123. package/dist/native/auth-types.js +13 -0
  124. package/dist/native/auth.d.ts +133 -0
  125. package/dist/native/auth.js +362 -0
  126. package/dist/native/client.d.ts +50 -0
  127. package/dist/native/client.js +174 -0
  128. package/dist/native/complete.d.ts +33 -0
  129. package/dist/native/complete.js +88 -0
  130. package/dist/native/credentials/chained-store.d.ts +30 -0
  131. package/dist/native/credentials/chained-store.js +68 -0
  132. package/dist/native/credentials/change-guard.d.ts +51 -0
  133. package/dist/native/credentials/change-guard.js +136 -0
  134. package/dist/native/credentials/exec-source.d.ts +50 -0
  135. package/dist/native/credentials/exec-source.js +222 -0
  136. package/dist/native/credentials/fingerprint.d.ts +26 -0
  137. package/dist/native/credentials/fingerprint.js +174 -0
  138. package/dist/native/credentials/helper-process.d.ts +79 -0
  139. package/dist/native/credentials/helper-process.js +259 -0
  140. package/dist/native/credentials/helper-protocol.d.ts +36 -0
  141. package/dist/native/credentials/helper-protocol.js +79 -0
  142. package/dist/native/credentials/index.d.ts +60 -0
  143. package/dist/native/credentials/index.js +184 -0
  144. package/dist/native/errors.d.ts +34 -0
  145. package/dist/native/errors.js +109 -0
  146. package/dist/native/index.d.ts +25 -0
  147. package/dist/native/index.js +23 -0
  148. package/dist/native/model-resolver.d.ts +57 -0
  149. package/dist/native/model-resolver.js +73 -0
  150. package/dist/native/models.d.ts +116 -0
  151. package/dist/native/models.js +183 -0
  152. package/dist/native/session/ask-human.d.ts +15 -0
  153. package/dist/native/session/ask-human.js +22 -0
  154. package/dist/native/session/compaction.d.ts +94 -0
  155. package/dist/native/session/compaction.js +170 -0
  156. package/dist/native/session/handle-invalid-tool-call.d.ts +111 -0
  157. package/dist/native/session/handle-invalid-tool-call.js +159 -0
  158. package/dist/native/session/loop-events/cache-boundary.d.ts +11 -0
  159. package/dist/native/session/loop-events/cache-boundary.js +29 -0
  160. package/dist/native/session/loop-events/external-handler.d.ts +38 -0
  161. package/dist/native/session/loop-events/external-handler.js +193 -0
  162. package/dist/native/session/loop-events/index.d.ts +16 -0
  163. package/dist/native/session/loop-events/index.js +15 -0
  164. package/dist/native/session/loop-events/payload-guard.d.ts +9 -0
  165. package/dist/native/session/loop-events/payload-guard.js +31 -0
  166. package/dist/native/session/loop-events/registry.d.ts +84 -0
  167. package/dist/native/session/loop-events/registry.js +305 -0
  168. package/dist/native/session/loop-events/types.d.ts +251 -0
  169. package/dist/native/session/loop-events/types.js +14 -0
  170. package/dist/native/session/loop-handlers.d.ts +73 -0
  171. package/dist/native/session/loop-handlers.js +137 -0
  172. package/dist/native/session/nudge.d.ts +29 -0
  173. package/dist/native/session/nudge.js +35 -0
  174. package/dist/native/session/rate-provenance.d.ts +17 -0
  175. package/dist/native/session/rate-provenance.js +39 -0
  176. package/dist/native/session/session.d.ts +139 -0
  177. package/dist/native/session/session.js +208 -0
  178. package/dist/native/session/tool-input-exemplar.d.ts +2 -0
  179. package/dist/native/session/tool-input-exemplar.js +66 -0
  180. package/dist/native/session/tool-input-validation.d.ts +15 -0
  181. package/dist/native/session/tool-input-validation.js +146 -0
  182. package/dist/native/session/tool-mapping.d.ts +18 -0
  183. package/dist/native/session/tool-mapping.js +27 -0
  184. package/dist/native/session/tool-result.d.ts +37 -0
  185. package/dist/native/session/tool-result.js +25 -0
  186. package/dist/native/session/transcript-store.d.ts +77 -0
  187. package/dist/native/session/transcript-store.js +200 -0
  188. package/dist/native/session/truncation-handler.d.ts +47 -0
  189. package/dist/native/session/truncation-handler.js +83 -0
  190. package/dist/native/session/turn-accumulator.d.ts +45 -0
  191. package/dist/native/session/turn-accumulator.js +67 -0
  192. package/dist/native/session/turn-ask-human.d.ts +23 -0
  193. package/dist/native/session/turn-ask-human.js +41 -0
  194. package/dist/native/session/turn-compaction-step.d.ts +74 -0
  195. package/dist/native/session/turn-compaction-step.js +128 -0
  196. package/dist/native/session/turn-complete-step.d.ts +77 -0
  197. package/dist/native/session/turn-complete-step.js +159 -0
  198. package/dist/native/session/turn-end-event.d.ts +3 -0
  199. package/dist/native/session/turn-end-event.js +17 -0
  200. package/dist/native/session/turn-events.d.ts +63 -0
  201. package/dist/native/session/turn-events.js +48 -0
  202. package/dist/native/session/turn-loop-round-trip.d.ts +116 -0
  203. package/dist/native/session/turn-loop-round-trip.js +318 -0
  204. package/dist/native/session/turn-loop.d.ts +23 -0
  205. package/dist/native/session/turn-loop.js +250 -0
  206. package/dist/native/session/turn-result.d.ts +39 -0
  207. package/dist/native/session/turn-result.js +64 -0
  208. package/dist/native/session/turn-retry.d.ts +120 -0
  209. package/dist/native/session/turn-retry.js +182 -0
  210. package/dist/native/session/turn-tool-batch.d.ts +61 -0
  211. package/dist/native/session/turn-tool-batch.js +259 -0
  212. package/dist/native/session/turn-types.d.ts +164 -0
  213. package/dist/native/session/turn-types.js +36 -0
  214. package/dist/native/session-adapter.d.ts +56 -0
  215. package/dist/native/session-adapter.js +369 -0
  216. package/dist/native/session-affinity.d.ts +36 -0
  217. package/dist/native/session-affinity.js +43 -0
  218. package/dist/native/tier-providers.d.ts +18 -0
  219. package/dist/native/tier-providers.js +20 -0
  220. package/dist/permissions/approval-audit.d.ts +10 -0
  221. package/dist/permissions/approval-audit.js +22 -0
  222. package/dist/permissions/approvals-link.d.ts +20 -0
  223. package/dist/permissions/approvals-link.js +62 -0
  224. package/dist/permissions/approvals-store.d.ts +168 -0
  225. package/dist/permissions/approvals-store.js +259 -0
  226. package/dist/permissions/approvals-taint.d.ts +33 -0
  227. package/dist/permissions/approvals-taint.js +138 -0
  228. package/dist/permissions/ask-chain.d.ts +69 -0
  229. package/dist/permissions/ask-chain.js +32 -0
  230. package/dist/permissions/ask.d.ts +23 -0
  231. package/dist/permissions/ask.js +25 -0
  232. package/dist/permissions/bash-lex.d.ts +59 -0
  233. package/dist/permissions/bash-lex.js +279 -0
  234. package/dist/permissions/grammar.d.ts +12 -0
  235. package/dist/permissions/grammar.js +24 -0
  236. package/dist/permissions/index.d.ts +11 -0
  237. package/dist/permissions/index.js +9 -0
  238. package/dist/permissions/secret-spans.d.ts +19 -0
  239. package/dist/permissions/secret-spans.js +88 -0
  240. package/dist/permissions/types.d.ts +85 -0
  241. package/dist/permissions/types.js +1 -0
  242. package/dist/runtime/index.d.ts +3 -0
  243. package/dist/runtime/index.js +2 -0
  244. package/dist/runtime/node-glob.d.ts +3 -0
  245. package/dist/runtime/node-glob.js +83 -0
  246. package/dist/runtime/node-runtime.d.ts +3 -0
  247. package/dist/runtime/node-runtime.js +126 -0
  248. package/dist/runtime/slot.d.ts +11 -0
  249. package/dist/runtime/slot.js +17 -0
  250. package/dist/runtime/types.d.ts +51 -0
  251. package/dist/runtime/types.js +1 -0
  252. package/dist/runtime/which.d.ts +8 -0
  253. package/dist/runtime/which.js +32 -0
  254. package/dist/sandbox/argv-quote.d.ts +11 -0
  255. package/dist/sandbox/argv-quote.js +13 -0
  256. package/dist/sandbox/defaults.d.ts +27 -0
  257. package/dist/sandbox/defaults.js +47 -0
  258. package/dist/sandbox/git-guards.d.ts +64 -0
  259. package/dist/sandbox/git-guards.js +161 -0
  260. package/dist/sandbox/index.d.ts +11 -0
  261. package/dist/sandbox/index.js +10 -0
  262. package/dist/sandbox/launcher.d.ts +37 -0
  263. package/dist/sandbox/launcher.js +160 -0
  264. package/dist/sandbox/messages.d.ts +17 -0
  265. package/dist/sandbox/messages.js +46 -0
  266. package/dist/sandbox/policy-builder.d.ts +44 -0
  267. package/dist/sandbox/policy-builder.js +149 -0
  268. package/dist/sandbox/policy-inputs.d.ts +53 -0
  269. package/dist/sandbox/policy-inputs.js +76 -0
  270. package/dist/sandbox/probe.d.ts +8 -0
  271. package/dist/sandbox/probe.js +66 -0
  272. package/dist/sandbox/registry.d.ts +17 -0
  273. package/dist/sandbox/registry.js +57 -0
  274. package/dist/sandbox/session-tmp.d.ts +20 -0
  275. package/dist/sandbox/session-tmp.js +95 -0
  276. package/dist/sandbox/srt-backend.d.ts +21 -0
  277. package/dist/sandbox/srt-backend.js +99 -0
  278. package/dist/sandbox/types.d.ts +102 -0
  279. package/dist/sandbox/types.js +1 -0
  280. package/dist/session/adapter-failure.d.ts +60 -0
  281. package/dist/session/adapter-failure.js +11 -0
  282. package/dist/session/agent-stream-events.d.ts +103 -0
  283. package/dist/session/agent-stream-events.js +6 -0
  284. package/dist/session/interaction-handler.d.ts +53 -0
  285. package/dist/session/interaction-handler.js +2 -0
  286. package/dist/session/no-op-interaction-handler.d.ts +9 -0
  287. package/dist/session/no-op-interaction-handler.js +11 -0
  288. package/dist/session/protocol-types.d.ts +12 -0
  289. package/dist/session/protocol-types.js +1 -0
  290. package/dist/session/session-types.d.ts +456 -0
  291. package/dist/session/session-types.js +64 -0
  292. package/dist/session/tool-descriptor.d.ts +23 -0
  293. package/dist/session/tool-descriptor.js +5 -0
  294. package/dist/session/turn-deadline.d.ts +17 -0
  295. package/dist/session/turn-deadline.js +23 -0
  296. package/dist/tools/ask-request.d.ts +35 -0
  297. package/dist/tools/ask-request.js +57 -0
  298. package/dist/tools/bash-cwd.d.ts +82 -0
  299. package/dist/tools/bash-cwd.js +55 -0
  300. package/dist/tools/bash.d.ts +69 -0
  301. package/dist/tools/bash.js +283 -0
  302. package/dist/tools/delete.d.ts +57 -0
  303. package/dist/tools/delete.js +154 -0
  304. package/dist/tools/denial-redirect.d.ts +42 -0
  305. package/dist/tools/denial-redirect.js +266 -0
  306. package/dist/tools/deny-paths.d.ts +35 -0
  307. package/dist/tools/deny-paths.js +76 -0
  308. package/dist/tools/edit-region.d.ts +50 -0
  309. package/dist/tools/edit-region.js +90 -0
  310. package/dist/tools/edit.d.ts +25 -0
  311. package/dist/tools/edit.js +114 -0
  312. package/dist/tools/exec-allowlist-text.d.ts +18 -0
  313. package/dist/tools/exec-allowlist-text.js +24 -0
  314. package/dist/tools/exec-guard.d.ts +69 -0
  315. package/dist/tools/exec-guard.js +151 -0
  316. package/dist/tools/git-commit.d.ts +59 -0
  317. package/dist/tools/git-commit.js +220 -0
  318. package/dist/tools/git-flags/index.d.ts +10 -0
  319. package/dist/tools/git-flags/index.js +10 -0
  320. package/dist/tools/git.d.ts +88 -0
  321. package/dist/tools/git.js +397 -0
  322. package/dist/tools/glob.d.ts +29 -0
  323. package/dist/tools/glob.js +153 -0
  324. package/dist/tools/grep.d.ts +21 -0
  325. package/dist/tools/grep.js +162 -0
  326. package/dist/tools/index.d.ts +36 -0
  327. package/dist/tools/index.js +28 -0
  328. package/dist/tools/narrow-grants.d.ts +19 -0
  329. package/dist/tools/narrow-grants.js +54 -0
  330. package/dist/tools/nax-owned-writes.d.ts +135 -0
  331. package/dist/tools/nax-owned-writes.js +259 -0
  332. package/dist/tools/package-managers-table.d.ts +146 -0
  333. package/dist/tools/package-managers-table.js +325 -0
  334. package/dist/tools/package-managers-types.d.ts +36 -0
  335. package/dist/tools/package-managers-types.js +6 -0
  336. package/dist/tools/package-managers.d.ts +85 -0
  337. package/dist/tools/package-managers.js +391 -0
  338. package/dist/tools/path-list.d.ts +38 -0
  339. package/dist/tools/path-list.js +62 -0
  340. package/dist/tools/policy-bash-raw.d.ts +19 -0
  341. package/dist/tools/policy-bash-raw.js +210 -0
  342. package/dist/tools/policy-bash.d.ts +64 -0
  343. package/dist/tools/policy-bash.js +232 -0
  344. package/dist/tools/policy-command-branch.d.ts +32 -0
  345. package/dist/tools/policy-command-branch.js +63 -0
  346. package/dist/tools/policy-input.d.ts +2 -0
  347. package/dist/tools/policy-input.js +10 -0
  348. package/dist/tools/policy-match.d.ts +75 -0
  349. package/dist/tools/policy-match.js +117 -0
  350. package/dist/tools/policy-paths-branch.d.ts +97 -0
  351. package/dist/tools/policy-paths-branch.js +314 -0
  352. package/dist/tools/policy.d.ts +65 -0
  353. package/dist/tools/policy.js +232 -0
  354. package/dist/tools/protected-paths.d.ts +26 -0
  355. package/dist/tools/protected-paths.js +8 -0
  356. package/dist/tools/provider-adapt.d.ts +4 -0
  357. package/dist/tools/provider-adapt.js +32 -0
  358. package/dist/tools/provider-advertise.d.ts +39 -0
  359. package/dist/tools/provider-advertise.js +50 -0
  360. package/dist/tools/provider-grants.d.ts +36 -0
  361. package/dist/tools/provider-grants.js +72 -0
  362. package/dist/tools/provider-sanitize.d.ts +19 -0
  363. package/dist/tools/provider-sanitize.js +53 -0
  364. package/dist/tools/provider-types.d.ts +41 -0
  365. package/dist/tools/provider-types.js +26 -0
  366. package/dist/tools/read-continuation.d.ts +147 -0
  367. package/dist/tools/read-continuation.js +176 -0
  368. package/dist/tools/read-file.d.ts +32 -0
  369. package/dist/tools/read-file.js +85 -0
  370. package/dist/tools/read.d.ts +25 -0
  371. package/dist/tools/read.js +182 -0
  372. package/dist/tools/registry.d.ts +125 -0
  373. package/dist/tools/registry.js +67 -0
  374. package/dist/tools/request-capability.d.ts +13 -0
  375. package/dist/tools/request-capability.js +23 -0
  376. package/dist/tools/run-command-exec.d.ts +14 -0
  377. package/dist/tools/run-command-exec.js +161 -0
  378. package/dist/tools/run-command.d.ts +107 -0
  379. package/dist/tools/run-command.js +398 -0
  380. package/dist/tools/runtime-calltool.d.ts +146 -0
  381. package/dist/tools/runtime-calltool.js +223 -0
  382. package/dist/tools/runtime.d.ts +149 -0
  383. package/dist/tools/runtime.js +345 -0
  384. package/dist/tools/scratchpad.d.ts +20 -0
  385. package/dist/tools/scratchpad.js +197 -0
  386. package/dist/tools/spill.d.ts +81 -0
  387. package/dist/tools/spill.js +251 -0
  388. package/dist/tools/tool-audit.d.ts +171 -0
  389. package/dist/tools/tool-audit.js +183 -0
  390. package/dist/tools/truncate.d.ts +98 -0
  391. package/dist/tools/truncate.js +297 -0
  392. package/dist/tools/types.d.ts +145 -0
  393. package/dist/tools/types.js +24 -0
  394. package/dist/tools/verb-denial-argv-hint.d.ts +35 -0
  395. package/dist/tools/verb-denial-argv-hint.js +40 -0
  396. package/dist/tools/write.d.ts +9 -0
  397. package/dist/tools/write.js +45 -0
  398. package/package.json +54 -0
@@ -0,0 +1,397 @@
1
+ /**
2
+ * Read-only git, for reviewers that need a real diff rather than one pushed
3
+ * into the prompt.
4
+ *
5
+ * This spawns a subprocess, which ADR-029 section 3 severed for Bash. The
6
+ * distinction, written down rather than assumed: git is a FIXED binary invoked
7
+ * with an argv nax constructs entirely, with no shell. The model supplies
8
+ * structure — a subcommand, refs, pathspecs — never a command string. Bash
9
+ * inverts that, which is why it needs a sandbox and a threat model instead of
10
+ * an allowlist.
11
+ *
12
+ * Reuses gitWithTimeout, which already provides the argv-array spawn, the
13
+ * explicit cwd, the SIGKILL timeout, and concurrent pipe draining — the last of
14
+ * which matters here because `git log -p` is exactly the output that fills a
15
+ * 64KB pipe buffer and deadlocks a naive implementation.
16
+ */
17
+ import { interceptArgv } from "#src/command-interceptor/index";
18
+ import { gitWithTimeout } from "#src/internal/git-exec";
19
+ import { gitExcludePathspecsOf } from "./protected-paths.js";
20
+ import { cutToByteCap, READ_CEILING } from "./truncate.js";
21
+ /**
22
+ * Read-only verbs. Mutating verbs are not representable in the input type.
23
+ *
24
+ * Note what bounds these: the argv shape below, not the verb list. A read-only
25
+ * verb still spans the whole repository unless its scope is stated.
26
+ */
27
+ export const GIT_READ_VERBS = ["diff", "log", "show", "status", "blame"];
28
+ /**
29
+ * Typed flag fields, and the verbs each one is valid on.
30
+ *
31
+ * nax emits every one of these flags itself: a boolean or a closed enum comes
32
+ * in, a fixed string goes out. The model never supplies flag text, so the
33
+ * property the header comment defends — the model supplies structure, nax
34
+ * constructs the argv — is unchanged by their existence. That is the whole
35
+ * reason they are typed fields rather than a passthrough list.
36
+ *
37
+ * They exist because `git diff --name-only --diff-filter=A` is step 1 of the
38
+ * adversarial reviewer's test-audit workflow and was not expressible at all:
39
+ * the only way to ask for it was to put the flags in `refs`/`paths`, where
40
+ * `looksLikeFlag` refuses them (#1818). A field is gated to the verbs it
41
+ * applies to so a wrong pairing is a nax error the model can act on, rather
42
+ * than a git usage error it has to interpret.
43
+ */
44
+ export const GIT_DIFF_FILTERS = ["A", "M", "D", "R"];
45
+ // `show` is deliberately absent: git itself refuses it — "options '--name-only',
46
+ // '--name-status', '--check', and '-s' cannot be used together" (git 2.50.1),
47
+ // because `show` already supplies a conflicting output selector. Verified by
48
+ // running it; a nax-side refusal here is the clearer of the two errors.
49
+ // `show` takes git's ordinary diff and log options, so it belongs in all three.
50
+ // It was omitted from every gate while sitting in GIT_READ_VERBS, which made
51
+ // `git show --name-only <ref>` inexpressible: 22 denials across 4 features in
52
+ // the tool audit, from verifier, implementer and test-writer alike (nax#1800).
53
+ // `status` stays out -- git has no --name-only there, and inventing an
54
+ // acceptance would only move the failure to a raw git error.
55
+ const GIT_NAME_ONLY_VERBS = ["diff", "log", "show"];
56
+ const GIT_DIFF_FILTER_VERBS = ["diff", "log", "show"];
57
+ const GIT_ONELINE_VERBS = ["log", "show"];
58
+ /**
59
+ * `log` alone takes a commit bound. Every other read verb already names what it
60
+ * operates on -- `show` and `blame` take a ref, `diff` a range, `status` the
61
+ * working tree -- so none of them is unbounded in the way `log` is, and
62
+ * defaulting them would invent a truncation nobody asked for.
63
+ */
64
+ const GIT_MAX_COUNT_VERBS = ["log"];
65
+ /**
66
+ * Commit bound applied to a `log` that does not supply its own.
67
+ *
68
+ * `log` was the one read verb with no bound on how much it returns: a pathspec
69
+ * selects WHICH commits to show, then each selected commit prints in full. With
70
+ * `nameOnly` that compounds, because `--name-only` lists every file in each
71
+ * matching commit rather than the path that selected it -- nax#2009 measured
72
+ * 79KB from 7 commits on a single-file pathspec, the largest call in a
73
+ * 267-call session.
74
+ *
75
+ * Applied ONLY to a `log` that names no `refs`. A ref range is a scope the
76
+ * caller already chose, and capping on top of it discards commits they asked
77
+ * for with no marker. A commit cap appends nothing, so the model cannot tell
78
+ * 12 commits from 200-capped-to-20. The reviewer prompt asks for a story's
79
+ * history as `log <ref>..HEAD --oneline`
80
+ * (`src/prompts/sections/protocol-region.ts`), and `--max-count` keeps the
81
+ * NEWEST n -- a default there would have silently dropped the initial
82
+ * implementation commits. An unscoped `log` walks the whole history of HEAD
83
+ * and is the shape with no bound at all.
84
+ */
85
+ export const DEFAULT_LOG_MAX_COUNT = 20;
86
+ /**
87
+ * Compact commit rendering for `log`.
88
+ *
89
+ * `format:` rather than `tformat:` or the bare form: it places the separator
90
+ * BETWEEN commits, so each --name-only file list stays grouped with the commit
91
+ * that produced it instead of being orphaned after a blank line. Observed
92
+ * against a real repo, `format:%h %ad %s --name-only` renders the file list on
93
+ * the line directly after its commit's header; `tformat:` (which is what a bare
94
+ * `--format=` means) leaves a blank line between the two.
95
+ *
96
+ * `--date=short` pairs with it: `%ad` renders in whatever form `--date`
97
+ * selects, and the default is the raw commit timestamp
98
+ * (`Thu Apr 7 15:13:13 2005 -0700`) -- more noise than the compact line is
99
+ * meant to carry. (`%ai`/`%as` would be the ISO-8601 spellings; neither is
100
+ * used here.)
101
+ */
102
+ export const DEFAULT_LOG_FORMAT = "format:%h %ad %s";
103
+ /**
104
+ * `fullMessage` opts back in to git's `medium` default by suppressing the
105
+ * compact `--format=`. It only makes sense for `log`: `show` renders a single
106
+ * commit whose message is usually the reason for the call, and the other
107
+ * read verbs do not render commit messages at all.
108
+ */
109
+ const GIT_FULL_MESSAGE_VERBS = ["log"];
110
+ /**
111
+ * git parses `--max-count` into a C int, so INT_MAX is the real ceiling.
112
+ *
113
+ * Above it the field would defeat its own purpose: `Number.isInteger(1e21)` is
114
+ * true and interpolation renders it `1e+21`, so `maxCount: 1e21` reached git as
115
+ * `fatal: '1e+21': not an integer` -- a git usage error the model has to
116
+ * interpret, which is the class this field refuses rather than coerces.
117
+ */
118
+ const GIT_MAX_COUNT_CEILING = 2_147_483_647;
119
+ /**
120
+ * A positive-integer field, gated to the verbs it applies to.
121
+ *
122
+ * Refused rather than coerced, for the reason `flagFromBoolean` gives: a
123
+ * non-integer would reach git as `--max-count=1.5` and come back as a git usage
124
+ * error the model then has to interpret. The `typeof` check rejects every
125
+ * non-number -- the numeric string `"5"`, `true`, `null`, objects -- and
126
+ * `Number.isInteger` rejects NaN and Infinity.
127
+ */
128
+ function flagFromPositiveInteger(input, field, subcommand, validVerbs, flag) {
129
+ const value = input[field];
130
+ if (value === undefined)
131
+ return null;
132
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 1 || value > GIT_MAX_COUNT_CEILING) {
133
+ return { error: `"${field}" must be a positive integer` };
134
+ }
135
+ if (!validVerbs.includes(subcommand)) {
136
+ return { error: `"${field}" is not valid for "${subcommand}" (valid for: ${validVerbs.join(", ")})` };
137
+ }
138
+ return `${flag}=${value}`;
139
+ }
140
+ /**
141
+ * A boolean flag field: absent or `false` emits nothing, `true` emits the flag.
142
+ *
143
+ * A non-boolean is refused rather than coerced — `nameOnly: "false"` is truthy
144
+ * in JavaScript, so coercion would turn a model's mistake into the opposite of
145
+ * what it asked for.
146
+ */
147
+ function flagFromBoolean(input, field, subcommand, validVerbs, flag) {
148
+ const value = input[field];
149
+ if (value === undefined)
150
+ return null;
151
+ if (typeof value !== "boolean")
152
+ return { error: `"${field}" must be a boolean` };
153
+ // Checked before the verb gate: `false` asks for nothing, so refusing it for
154
+ // the wrong verb would invent a refusal — the failure class this change
155
+ // exists to reduce.
156
+ if (!value)
157
+ return null;
158
+ if (!validVerbs.includes(subcommand)) {
159
+ return { error: `"${field}" is not valid for "${subcommand}" (valid for: ${validVerbs.join(", ")})` };
160
+ }
161
+ return flag;
162
+ }
163
+ export { GIT_ESCAPE_FLAGS } from "#src/tools/git-flags/index";
164
+ function looksLikeFlag(value) {
165
+ return value.startsWith("-");
166
+ }
167
+ /**
168
+ * `defaultExcludes`: pathspecs appended to an unscoped call (the host's own
169
+ * state, nax#2007); none by default.
170
+ */
171
+ export function buildGitArgv(input, defaultExcludes = []) {
172
+ const subcommand = input.subcommand;
173
+ if (typeof subcommand !== "string" || !GIT_READ_VERBS.includes(subcommand)) {
174
+ return { error: `subcommand must be one of: ${GIT_READ_VERBS.join(", ")}` };
175
+ }
176
+ const refs = Array.isArray(input.refs) ? input.refs : [];
177
+ const paths = Array.isArray(input.paths) ? input.paths : [];
178
+ // No `--relative` is injected. Since the single-frame redesign the permitted
179
+ // root is the repository root, so git's default repo-rooted path framing
180
+ // already agrees with Read/Grep/Glob. The flag only ever compensated for a
181
+ // package-subdir root (#1807); from the repo root it is wrong.
182
+ const argv = [subcommand];
183
+ // Flags precede the refs. Git accepts them in either position, but a flag
184
+ // placed after a revision list reads as a pathspec to anyone (model or
185
+ // human) scanning the argv, and this argv is written into the tool-audit
186
+ // ledger that #1818 was filed from.
187
+ const nameOnly = flagFromBoolean(input, "nameOnly", subcommand, GIT_NAME_ONLY_VERBS, "--name-only");
188
+ if (nameOnly !== null && typeof nameOnly === "object")
189
+ return nameOnly;
190
+ if (nameOnly !== null)
191
+ argv.push(nameOnly);
192
+ const diffFilter = input.diffFilter;
193
+ if (diffFilter !== undefined) {
194
+ if (typeof diffFilter !== "string" || !GIT_DIFF_FILTERS.includes(diffFilter)) {
195
+ return { error: `"diffFilter" must be one of: ${GIT_DIFF_FILTERS.join(", ")}` };
196
+ }
197
+ if (!GIT_DIFF_FILTER_VERBS.includes(subcommand)) {
198
+ return {
199
+ error: `"diffFilter" is not valid for "${subcommand}" (valid for: ${GIT_DIFF_FILTER_VERBS.join(", ")})`,
200
+ };
201
+ }
202
+ argv.push(`--diff-filter=${diffFilter}`);
203
+ }
204
+ // `fullMessage` is validated first, before `oneline` is pushed into argv, so
205
+ // the contradiction refusal is the actual return and the local argv is
206
+ // never built. The non-boolean check precedes the verb gate for the same
207
+ // reason `flagFromBoolean` does: a non-boolean still names the field it's
208
+ // about.
209
+ const fullMessage = input.fullMessage;
210
+ if (fullMessage !== undefined) {
211
+ if (typeof fullMessage !== "boolean") {
212
+ return { error: `"fullMessage" must be a boolean` };
213
+ }
214
+ if (fullMessage) {
215
+ if (input.oneline === true) {
216
+ // Both at once is a contradiction: `oneline` requests a one-line
217
+ // commit list, `fullMessage` requests git's medium default (subject,
218
+ // author, date, body). Refused by name rather than resolved silently.
219
+ return {
220
+ error: `"oneline" and "fullMessage" cannot both be true; pick one`,
221
+ };
222
+ }
223
+ if (!GIT_FULL_MESSAGE_VERBS.includes(subcommand)) {
224
+ return {
225
+ error: `"fullMessage" is not valid for "${subcommand}" (valid for: ${GIT_FULL_MESSAGE_VERBS.join(", ")})`,
226
+ };
227
+ }
228
+ }
229
+ }
230
+ const oneline = flagFromBoolean(input, "oneline", subcommand, GIT_ONELINE_VERBS, "--oneline");
231
+ if (oneline !== null && typeof oneline === "object")
232
+ return oneline;
233
+ if (oneline !== null)
234
+ argv.push(oneline);
235
+ // `--oneline` and `--format=` are last-wins on the same argv; `oneline`
236
+ // continuing to win is intentional — it is the caller's explicit request.
237
+ // `fullMessage` suppresses the default because its whole purpose is to
238
+ // restore git's medium rendering.
239
+ if (subcommand === "log" && oneline === null && fullMessage !== true) {
240
+ argv.push(`--format=${DEFAULT_LOG_FORMAT}`);
241
+ argv.push("--date=short");
242
+ }
243
+ const maxCount = flagFromPositiveInteger(input, "maxCount", subcommand, GIT_MAX_COUNT_VERBS, "--max-count");
244
+ if (maxCount !== null && typeof maxCount === "object")
245
+ return maxCount;
246
+ if (maxCount !== null)
247
+ argv.push(maxCount);
248
+ // Only an UNSCOPED log gets the default -- see DEFAULT_LOG_MAX_COUNT. A refs
249
+ // range is the caller's own scope, and capping it would drop commits they
250
+ // asked for with nothing in the output to say so.
251
+ else if (GIT_MAX_COUNT_VERBS.includes(subcommand) && refs.length === 0) {
252
+ argv.push(`--max-count=${DEFAULT_LOG_MAX_COUNT}`);
253
+ }
254
+ for (const ref of refs) {
255
+ if (typeof ref !== "string")
256
+ return { error: "refs must be strings" };
257
+ // A ref that begins with "-" would be parsed as an option, which is how an
258
+ // escape flag would arrive. Refuse rather than sanitise.
259
+ if (looksLikeFlag(ref))
260
+ return { error: `ref "${ref}" may not begin with "-"` };
261
+ argv.push(ref);
262
+ }
263
+ // `--` ALWAYS terminates the revision list, even with no paths to follow it.
264
+ // Without it git disambiguates an argument that is not a valid revision by
265
+ // checking whether it names a path, and silently reinterprets it as a
266
+ // pathspec -- so `show ../../outside/secret` read a file outside the root,
267
+ // never reaching resolveWithin because a colon-less ref was treated as "a
268
+ // pure revision, nothing to contain". With `--` git rejects it outright.
269
+ argv.push("--");
270
+ if (paths.length === 0) {
271
+ // Scope an unrestricted call to the root. A ref names a whole commit, whose
272
+ // diff spans the entire repository, so `show HEAD` returned outside-root
273
+ // content without naming a path at all -- nothing in the argv was wrong,
274
+ // the command's own scope was. `.` is resolved by git against the cwd,
275
+ // which gitWithTimeout sets to the permitted root.
276
+ argv.push(".");
277
+ // The host's own run state under .nax/ is git-tracked during a run, so an
278
+ // unscoped call reported it back as the agent's diff (#2007). Excluded only
279
+ // on this DEFAULT branch: a caller that names a path under .nax/ gets it --
280
+ // the tool is read-only, and silently returning nothing for an explicitly
281
+ // requested path would be a worse failure than the one being fixed.
282
+ // `blame` is exempt because git rejects exclude pathspecs on it and exits
283
+ // 128; do not "unify" it back in.
284
+ if (subcommand !== "blame")
285
+ argv.push(...defaultExcludes);
286
+ return argv;
287
+ }
288
+ for (const path of paths) {
289
+ if (typeof path !== "string")
290
+ return { error: "paths must be strings" };
291
+ if (looksLikeFlag(path))
292
+ return { error: `path "${path}" may not begin with "-"` };
293
+ argv.push(path);
294
+ }
295
+ return argv;
296
+ }
297
+ export const gitTool = {
298
+ name: "Git",
299
+ description: "Run a read-only git command (diff, log, show, status, blame) in the repository. Supply refs and pathspecs as arrays, not as a command line. Command-line flags are not accepted in any field; use the nameOnly, diffFilter, oneline, fullMessage and maxCount fields instead.",
300
+ inputSchema: {
301
+ type: "object",
302
+ properties: {
303
+ subcommand: { type: "string", enum: [...GIT_READ_VERBS], description: "Read-only git subcommand" },
304
+ refs: { type: "array", items: { type: "string" }, description: "Refs, e.g. ['HEAD~1','HEAD']" },
305
+ paths: { type: "array", items: { type: "string" }, description: "Pathspecs, relative to the permitted root" },
306
+ nameOnly: {
307
+ type: "boolean",
308
+ description: "List file names only, no content (diff, log, show). On log the file list is restricted to the pathspec; log renders a compact commit line by default — set fullMessage to restore full commit bodies.",
309
+ },
310
+ diffFilter: {
311
+ type: "string",
312
+ enum: [...GIT_DIFF_FILTERS],
313
+ description: "Select only files Added (A), Modified (M), Deleted (D) or Renamed (R) (diff)",
314
+ },
315
+ oneline: { type: "boolean", description: "One line per commit (log)" },
316
+ fullMessage: {
317
+ type: "boolean",
318
+ description: "Restore git's medium commit rendering on log (subject, author, date, body). The default is a compact one-line form; set this to opt back in to the full message.",
319
+ },
320
+ maxCount: {
321
+ type: "integer",
322
+ description: `Maximum commits to return, newest first (log). A log with no refs defaults to ${DEFAULT_LOG_MAX_COUNT}; a log with a ref range is left unbounded. Raise it to walk further back.`,
323
+ },
324
+ },
325
+ required: ["subcommand"],
326
+ },
327
+ // `paths` entries and the path-portion of `refs` entries (git's
328
+ // "<rev>:<path>" syntax) are checked for containment via arrayPathFields /
329
+ // refPathFields — see the ToolScope doc comment for why they carry no
330
+ // pattern matching. The verb gate above is still what bounds which git
331
+ // subcommands may run at all.
332
+ scope: {
333
+ pathFields: [],
334
+ arrayPathFields: ["paths"],
335
+ refPathFields: ["refs"],
336
+ verbField: "subcommand",
337
+ allowedVerbs: GIT_READ_VERBS,
338
+ },
339
+ async run(input, ctx) {
340
+ const built = buildGitArgv(input, gitExcludePathspecsOf(ctx));
341
+ if ("error" in built)
342
+ return { content: built.error, isError: true };
343
+ const interceptor = ctx.interceptor;
344
+ try {
345
+ // nax-git-env-allow: not a spawn; the (possibly rewritten) argv runs through gitWithTimeout below
346
+ const intercepted = await interceptArgv(["git", ...built], ctx.root, interceptor);
347
+ // The I/O ceiling, not the model-facing cap — see the `cutToByteCap`
348
+ // call below, which bounds the same body at the same value.
349
+ const ioCeiling = ctx.readCeiling ?? READ_CEILING;
350
+ const { stdout, stderr, exitCode } = await gitWithTimeout(built, ctx.root, undefined, ioCeiling, intercepted.argv);
351
+ // Computed before the error branch so a REWRITTEN command that ran and
352
+ // FAILED is still ledged — replay fidelity is exactly where the failure
353
+ // path matters (US-008). `InterceptOutcome.executed` is optional but
354
+ // `ToolResult.audit.executed` is required, so the narrowing stays.
355
+ const audit = intercepted.executed !== undefined ? { executed: intercepted.executed } : undefined;
356
+ if (exitCode !== 0 && stdout.trim() === "") {
357
+ return {
358
+ content: stderr.trim() || `git exited ${exitCode}`,
359
+ isError: true,
360
+ ...(audit !== undefined ? { audit } : {}),
361
+ };
362
+ }
363
+ // postProcess runs BEFORE trimEnd/truncate: stripping a hint changes what
364
+ // the trailing whitespace and the byte budget apply to, and truncating
365
+ // first would hand postProcess a hint that truncation cut in half. It is
366
+ // consulted only for output of a command this interceptor actually
367
+ // rewrote, and a throw degrades to the raw output (advisory, like the
368
+ // rewrite-time fail-open in interceptArgv).
369
+ let body = stdout;
370
+ if (intercepted.rewritten) {
371
+ // nax-git-env-allow: not a spawn; the postProcess request describing the argv already run
372
+ const req = { kind: "argv", argv: ["git", ...built], cwd: ctx.root, site: "git" };
373
+ try {
374
+ body = interceptor?.postProcess?.(stdout, req)?.output ?? stdout;
375
+ }
376
+ catch {
377
+ body = stdout;
378
+ }
379
+ }
380
+ // Known limitation: gitWithTimeout bounds stdout with the I/O ceiling
381
+ // BEFORE postProcess sees it, so a trailing hint on a very large output
382
+ // can be truncated mid-string and escape stripping. Fixing it means
383
+ // moving the bound after post-processing, which changes the drain
384
+ // contract — its own change.
385
+ //
386
+ // The model-facing cap is the session policy's, not this one: what is
387
+ // returned here is bounded only by the tool-layer ceiling, so a result
388
+ // the model cannot see whole still reaches the policy intact — and is
389
+ // spilled rather than silently replaced by a per-tool marker.
390
+ const content = cutToByteCap(body.trimEnd(), ioCeiling) || "(no output)";
391
+ return { content, ...(audit !== undefined ? { audit } : {}) };
392
+ }
393
+ catch (err) {
394
+ return { content: err instanceof Error ? err.message : String(err), isError: true };
395
+ }
396
+ },
397
+ };
@@ -0,0 +1,29 @@
1
+ /**
2
+ * List files matching a glob, always relative to and bounded by the root.
3
+ *
4
+ * The runtime scans from a cwd, so the root is the cwd and results are relative by
5
+ * construction. A pattern that tries to climb out ("../**") therefore matches
6
+ * nothing rather than escaping.
7
+ *
8
+ * Output is directory-grouped — one line per parent directory, basenames
9
+ * joined with single spaces, basenames containing whitespace or a quote
10
+ * wrapped in double quotes and escaped (`\\`, `\"`, `\n`, `\r`, `\t`). A
11
+ * match directly at the repository root is grouped under the prefix `./`, so
12
+ * every line has a directory prefix and there is no second shape to
13
+ * recognise. The format is lossless: parsing each line back into
14
+ * `<dir>/ <b1> <b2> ...`, unescaping each quoted basename, and joining each
15
+ * basename to its directory reconstructs the matched set exactly.
16
+ */
17
+ import type { CodingTool } from "./registry.ts";
18
+ /**
19
+ * @internal Injectable for tests — exercises the catch path without planting
20
+ * a malformed pattern on disk. Production wires this to `Bun.Glob`'s async
21
+ * `scan({ cwd, absolute })` iterator.
22
+ */
23
+ export declare const _globDeps: {
24
+ scan(pattern: string, opts: {
25
+ cwd: string;
26
+ absolute: boolean;
27
+ }): AsyncIterable<string>;
28
+ };
29
+ export declare const globTool: CodingTool;
@@ -0,0 +1,153 @@
1
+ /**
2
+ * List files matching a glob, always relative to and bounded by the root.
3
+ *
4
+ * The runtime scans from a cwd, so the root is the cwd and results are relative by
5
+ * construction. A pattern that tries to climb out ("../**") therefore matches
6
+ * nothing rather than escaping.
7
+ *
8
+ * Output is directory-grouped — one line per parent directory, basenames
9
+ * joined with single spaces, basenames containing whitespace or a quote
10
+ * wrapped in double quotes and escaped (`\\`, `\"`, `\n`, `\r`, `\t`). A
11
+ * match directly at the repository root is grouped under the prefix `./`, so
12
+ * every line has a directory prefix and there is no second shape to
13
+ * recognise. The format is lossless: parsing each line back into
14
+ * `<dir>/ <b1> <b2> ...`, unescaping each quoted basename, and joining each
15
+ * basename to its directory reconstructs the matched set exactly.
16
+ */
17
+ import { sep } from "node:path";
18
+ import { getAgentRuntime } from "#src/runtime/index";
19
+ import { resolveWithin } from "./policy.js";
20
+ const MAX_MATCHES = 500;
21
+ /**
22
+ * A pattern with a ".." segment can only match paths outside the root, so it
23
+ * is answered without scanning at all: the scanner would otherwise walk the
24
+ * parent tree (unbounded on real filesystems) just to yield hits that
25
+ * resolveWithin would discard. The result echoes no pattern, so no ".." can
26
+ * leak into the output.
27
+ */
28
+ function climbsOut(pattern) {
29
+ return pattern.split(/[\\/]/).includes("..");
30
+ }
31
+ /**
32
+ * @internal Injectable for tests — exercises the catch path without planting
33
+ * a malformed pattern on disk. Production wires this to `Bun.Glob`'s async
34
+ * `scan({ cwd, absolute })` iterator.
35
+ */
36
+ export const _globDeps = {
37
+ scan(pattern, opts) {
38
+ return getAgentRuntime().glob(pattern, opts);
39
+ },
40
+ };
41
+ /**
42
+ * Escapes applied inside a quoted basename. Four are required to keep the
43
+ * format lossless: `"` (would otherwise end the quoted form early), `\` (it
44
+ * introduces the escapes, so it must escape itself), and the line terminators
45
+ * `\n` / `\r` (group lines are joined by `\n`, so a literal one inside a
46
+ * basename would split a single line into two). `\t` is escaped as well, so a
47
+ * consumer that tokenises on whitespace rather than on this grammar still
48
+ * sees one basename. Everything else passes through unchanged; this is not a
49
+ * general-purpose escape.
50
+ */
51
+ const ESCAPED_BASENAME_CHARS = {
52
+ "\\": "\\\\",
53
+ '"': '\\"',
54
+ "\n": "\\n",
55
+ "\r": "\\r",
56
+ "\t": "\\t",
57
+ };
58
+ function escapeBasename(b) {
59
+ // The `?? c` arm is unreachable — the regex only matches the keys above —
60
+ // but it keeps the callback's return type `string` for `String.replace`.
61
+ return b.replace(/[\\"\n\r\t]/g, (c) => ESCAPED_BASENAME_CHARS[c] ?? c);
62
+ }
63
+ /**
64
+ * Bucket a flat match list by parent directory and render each bucket as one
65
+ * line: `<dir>/ <b1> <b2> ...`. The shape is identical whether the result
66
+ * holds one match or many, so a wide listing and an existence probe read the
67
+ * same way. A basename containing whitespace or a quote is wrapped in double
68
+ * quotes, with the characters above escaped inside the quotes; every other
69
+ * basename — including one that contains only a backslash — passes through
70
+ * unchanged. Group lines and basenames are both sorted ascending, so the
71
+ * output is deterministic for a given match set.
72
+ */
73
+ function renderGrouped(matches) {
74
+ const byDir = new Map();
75
+ for (const path of matches) {
76
+ const lastSlash = path.lastIndexOf("/");
77
+ const dir = lastSlash === -1 ? "./" : `${path.slice(0, lastSlash + 1)}`;
78
+ const basename = lastSlash === -1 ? path : path.slice(lastSlash + 1);
79
+ let bucket = byDir.get(dir);
80
+ if (bucket === undefined) {
81
+ bucket = [];
82
+ byDir.set(dir, bucket);
83
+ }
84
+ bucket.push(basename);
85
+ }
86
+ const dirs = [...byDir.keys()].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
87
+ const lines = [];
88
+ for (const dir of dirs) {
89
+ const bucket = byDir.get(dir) ?? [];
90
+ bucket.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
91
+ const rendered = bucket.map((b) => (needsQuoting(b) ? `"${escapeBasename(b)}"` : b));
92
+ // The dir prefix carries the same quoting rules a basename does: a dir
93
+ // containing whitespace or `"` is wrapped in double quotes with the same
94
+ // escapes, so `<dir>/ <b1> <b2> ...` stays mechanically splittable in
95
+ // either shape and the round-trip — AC-5 — survives a directory whose
96
+ // name contains a space (e.g. `my code/` would otherwise render as
97
+ // `my code/ a.ts`, ambiguous on whitespace).
98
+ const dirPrefix = needsQuoting(dir) ? `"${escapeBasename(dir)}"` : dir;
99
+ lines.push(`${dirPrefix} ${rendered.join(" ")}`);
100
+ }
101
+ return lines.join("\n");
102
+ }
103
+ /**
104
+ * True when a basename needs quoting to keep the format unambiguous: any
105
+ * whitespace (would split a single basename into multiple tokens), or the
106
+ * quote character itself (would terminate the quoted form early).
107
+ */
108
+ function needsQuoting(b) {
109
+ return /[\s"]/.test(b);
110
+ }
111
+ export const globTool = {
112
+ name: "Glob",
113
+ description: "List repository files matching a glob pattern, grouping matches by their parent directory. One line per parent directory, basenames sorted ascending, e.g. 'path/to/ a.ts b.ts'. Each line is lossless — concatenating the prefix and a basename reproduces the matched path. A basename is wrapped in double quotes when it contains whitespace or a double quote, and inside quotes the escapes \\\\, \\\", \\n, \\r and \\t stand for the literal character; outside quotes nothing is escaped, so a bare backslash is literal. The tool can also be used to check whether a path exists (call with a literal path like 'src/a.ts'; a hit returns 'src/ a.ts', a miss returns 'no matches for \"src/a.ts\"'). Patterns are matched relative to the repository root; a pattern that tries to climb out matches nothing.",
114
+ inputSchema: {
115
+ type: "object",
116
+ properties: { pattern: { type: "string", description: "Glob pattern, relative to the repository root" } },
117
+ required: ["pattern"],
118
+ },
119
+ // The pattern is not a path: it is matched inside the root by construction,
120
+ // so there is no path field for the policy to gate. Grant-level gating still
121
+ // applies, which is what decides whether Glob may run at all.
122
+ scope: { pathFields: [] },
123
+ async run(input, ctx) {
124
+ const pattern = input.pattern;
125
+ if (typeof pattern !== "string")
126
+ return { content: "pattern must be a string", isError: true };
127
+ if (climbsOut(pattern))
128
+ return { content: "no matches" };
129
+ const matches = [];
130
+ try {
131
+ // `absolute: false` is the repo-wide idiom (test-scanner.ts:318,
132
+ // fragments/store.ts:53, manifest-purge.ts:64) and yields root-relative
133
+ // paths directly. Each is still re-checked through resolveWithin, because
134
+ // a pattern that climbs out must produce nothing rather than escape.
135
+ // The scan itself goes through `_globDeps.scan` so a test can substitute
136
+ // a throwing iterator and exercise the catch branch without planting a
137
+ // malformed pattern on disk.
138
+ for await (const hit of _globDeps.scan(pattern, { cwd: ctx.root, absolute: false })) {
139
+ if (resolveWithin(ctx.root, hit) === null)
140
+ continue;
141
+ matches.push(hit.split(sep).join("/"));
142
+ if (matches.length >= MAX_MATCHES)
143
+ break;
144
+ }
145
+ }
146
+ catch (err) {
147
+ return { content: err instanceof Error ? err.message : String(err), isError: true };
148
+ }
149
+ if (matches.length === 0)
150
+ return { content: `no matches for "${pattern}"` };
151
+ return { content: renderGrouped(matches) };
152
+ },
153
+ };
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Search file contents, preferring ripgrep and falling back to grep.
3
+ *
4
+ * Both branches spawn a subprocess, so this tool is NOT evidence that the
5
+ * default tool set is in-process — it is not. What makes it safe is the same
6
+ * property that makes Git safe: a fixed binary, an argv nax constructs
7
+ * entirely, and no shell, so the model supplies data and never a command.
8
+ *
9
+ * The two binaries take different flags, so the argv builder is per-binary
10
+ * rather than shared, and the fallback is tested explicitly: a machine without
11
+ * ripgrep must produce the same matches, not a silent empty result.
12
+ */
13
+ import type { CodingTool } from "./registry.ts";
14
+ /** @internal Injectable for tests — exercises the fallback without uninstalling ripgrep. */
15
+ export declare const _grepDeps: {
16
+ which: (name: string) => string | null;
17
+ spawn: (cmd: readonly string[], opts: import("#src/runtime/index").AgentSpawnOptions) => import("#src/runtime/index").AgentSpawnResult;
18
+ };
19
+ export type GrepPatternType = "literal" | "regex";
20
+ export declare function buildGrepArgv(binary: "rg" | "grep", pattern: string, path: string | undefined, patternType?: GrepPatternType): string[];
21
+ export declare const grepTool: CodingTool;