@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,132 @@
1
+ /**
2
+ * Turn resolved grants plus an operation's declaration into a live runtime.
3
+ *
4
+ * nax-permission-mode-allow: consumes permissions already resolved by
5
+ * resolvePermissions; decides none.
6
+ *
7
+ * Callers reach it through `resolveCodingToolSupport`
8
+ * (`coding-tool-support-resolve.ts`, nax side), the single entry point both
9
+ * dispatch hops use.
10
+ */
11
+ import type { CommandInterceptor } from "#src/command-interceptor/index";
12
+ import type { CommandShadow } from "#src/command-safety/index";
13
+ import type { BashApprovalMode } from "#src/config/bash-approval";
14
+ import type { QualityCommandSpec } from "#src/internal/command-spec/index";
15
+ import type { AskResolver } from "#src/permissions/index";
16
+ import type { CommandLauncher } from "#src/sandbox/index";
17
+ import { type CodingTool, type CodingToolName, type CodingToolRuntime, type DeclaredCommandRunner, type ProtectedPathsPolicy, type ToolAuditSink, type ToolGrant, type ToolPatternNarrowing } from "#src/tools/index";
18
+ export interface CodingToolSupport {
19
+ readonly runtime: CodingToolRuntime;
20
+ readonly tools: readonly CodingTool[];
21
+ readonly auditSink: ToolAuditSink;
22
+ }
23
+ export declare function buildCodingToolSupport(args: {
24
+ root?: string;
25
+ /**
26
+ * Execution root for Exec's `target: "repoRoot"` form.
27
+ *
28
+ * Under story worktree isolation this is the story's worktree root
29
+ * (`<repo>/.nax-wt/<storyId>`), NOT the main checkout — a repo-scoped command
30
+ * that used `PackageView.repoRoot` here wrote the user's real working tree
31
+ * (nax#2093). Falls back to `root` when absent. Post single-frame redesign
32
+ * (PR2) `root` is already `storyExecRoot`, so `resolveCodingToolSupport`
33
+ * passes no separate value and the fallback supplies it.
34
+ */
35
+ repoRoot?: string;
36
+ /**
37
+ * The story's ABSOLUTE package dir for Exec's `target: "package"` cwd.
38
+ *
39
+ * Post-root-move, `codingToolRoot` is `storyExecRoot` (the repo/worktree
40
+ * root), so `args.root` can no longer
41
+ * stand in for the package dir: `run-command-exec.ts` computes
42
+ * `relative(repoRoot, packageWorkdir)`, which would always be "" and make
43
+ * `package-managers.ts`'s `effectiveTarget` collapse EVERY Exec call —
44
+ * `target: "package"` included — onto the repo root. It MUST be an
45
+ * absolute path: `packageWorkdir` is compared against the absolute
46
+ * `repoRoot`, so a relative value yields garbage. Falls back to `root`
47
+ * when absent (single-package repos, legacy callers, tests).
48
+ */
49
+ packageWorkdir?: string;
50
+ /**
51
+ * Execution cwd for RunCommand's DECLARED (non-Exec) branch, independent
52
+ * of `root` (tool containment). Falls back to `root` when absent.
53
+ *
54
+ * PRODUCER: resolveCodingToolSupport below, computed from
55
+ * `codingToolPackageDir` + `projectDir` (docs/superpowers/specs/2026-09-18-single-frame-redesign-design.md
56
+ * PR 1) so it stays pointed at the story's package dir even after PR2
57
+ * repoints `codingToolRoot`/`root` at the repo root.
58
+ */
59
+ commandCwd?: string;
60
+ grants?: readonly ToolGrant[];
61
+ declared: readonly CodingToolName[];
62
+ /** Provider-supplied tools for this hop, looked up before the global registry. */
63
+ extraTools?: readonly CodingTool[];
64
+ /** Advertised provider tool name -> owning provider id, for the ledger. */
65
+ providerIdByTool?: ReadonlyMap<string, string>;
66
+ storyId?: string;
67
+ declaredCommands?: ReadonlyMap<string, QualityCommandSpec>;
68
+ /** Port 7, forwarded to RunCommand. Supplied by `resolveCodingToolSupport`. */
69
+ runDeclaredCommand?: DeclaredCommandRunner;
70
+ /** Port 7: the run's interceptor, placed on every tool context. */
71
+ interceptor?: CommandInterceptor;
72
+ /** Port 6: host-owned paths, placed on every tool context. */
73
+ protectedPaths?: ProtectedPathsPolicy;
74
+ stripEnvVars?: readonly string[];
75
+ /** `quality.shell` — the shell the Bash tool spawns. Defaults to /bin/sh. */
76
+ shell?: string;
77
+ auditDir?: string;
78
+ sessionName?: string;
79
+ header?: import("../tools/tool-audit.ts").ToolAuditHeader;
80
+ callId?: string;
81
+ scopeId?: string;
82
+ /**
83
+ * Manifest name of the workspace member at the story's package dir
84
+ * (`packageWorkdir`/`commandCwd`), NOT at `root` — post-PR2 `root` is the
85
+ * repo root, so reading its manifest would scope workspace installs with the
86
+ * root name. See `resolvePackageName`.
87
+ */
88
+ packageName?: string;
89
+ /** `config.install.allowScripts` (Task 8 adds the field); defaults to false. */
90
+ allowScripts?: boolean;
91
+ /** `config.execution.denyPaths` (nax#1972); forwarded to createCodingToolRuntime verbatim. */
92
+ denyPaths?: readonly string[];
93
+ /** Per-tool narrowing from the op's `toolPatterns` (nax#2013). */
94
+ toolPatterns?: ToolPatternNarrowing;
95
+ /** Stage deny rules (spec R6); forwarded to `compileToolPolicy`. Bypasses `narrowGrants`. */
96
+ denyRules?: readonly ToolGrant[];
97
+ /** Stage ask rules (spec R1/R6); forwarded to `compileToolPolicy`. Bypasses `narrowGrants`. */
98
+ askRules?: readonly ToolGrant[];
99
+ /** PipelineStage this support is built for; carried into every `AskRequest`. */
100
+ pipelineStage?: string;
101
+ /**
102
+ * Absolute path the dispatching op declared as its `fileOutput` (nax#2115);
103
+ * forwarded to `compileToolPolicy` as its `ownedWriteExemption`.
104
+ */
105
+ fileOutputPath?: string;
106
+ /** nax#2260: `execution.sandbox.filesystem.allowWrite`, forwarded to `compileToolPolicy`. */
107
+ naxAllowWrite?: readonly string[];
108
+ bashApproval?: BashApprovalMode;
109
+ /** Injectable ask resolver (Task 3); defaults to the headless deny resolver. */
110
+ askResolver?: AskResolver;
111
+ /**
112
+ * P5 shadow classifier. Observational, except its optional `guard`
113
+ * (US-004): a flagged allowed `Bash`/`Exec` call becomes an ask.
114
+ */
115
+ commandShadow?: CommandShadow;
116
+ /** P4: resolved by resolveCodingToolSupport (async); data only here. */
117
+ launcher?: CommandLauncher;
118
+ /** US-001: forwarded into ToolRunContext so Bash/Exec can SIGKILL on cancel. */
119
+ abortSignal?: AbortSignal;
120
+ }): CodingToolSupport | undefined;
121
+ /**
122
+ * Ledger session name.
123
+ *
124
+ * Story-only names collide across the three TDD roles, which all write to one
125
+ * directory -- so a ledger could not answer which session made a call, and that
126
+ * is the evidence ADR-029 parity claims are read from.
127
+ */
128
+ export declare function buildLedgerSessionName(opts: {
129
+ storyId?: string;
130
+ sessionRole?: string;
131
+ featureName?: string;
132
+ }): string;
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Turn resolved grants plus an operation's declaration into a live runtime.
3
+ *
4
+ * nax-permission-mode-allow: consumes permissions already resolved by
5
+ * resolvePermissions; decides none.
6
+ *
7
+ * Callers reach it through `resolveCodingToolSupport`
8
+ * (`coding-tool-support-resolve.ts`, nax side), the single entry point both
9
+ * dispatch hops use.
10
+ */
11
+ import { getSafeLogger, NaxError } from "#src/infra/index";
12
+ import { advertisedSchemaBytes, BASH_TOOL_NAME, compileToolPolicy, createCodingToolRuntime, createNoOpToolAuditSink, createToolAuditSink, EXEC_TOOL_NAME, } from "#src/tools/index";
13
+ import { resolveBashSupport } from "./coding-tool-bash.js";
14
+ import { buildDeclaredCommandTools } from "./coding-tool-extras.js";
15
+ import { isTempConfined, rawScreenOptionsFor } from "./coding-tool-sandbox.js";
16
+ export function buildCodingToolSupport(args) {
17
+ if (args.declared.length === 0)
18
+ return undefined;
19
+ const grants = args.grants ?? [];
20
+ const bashApproval = args.bashApproval ?? "gated";
21
+ // ADR-030 / F1: under `raw`, a declared Bash gets a SYNTHETIC grant further
22
+ // down (resolveBashSupport) even with zero real grants -- `scoped` with no
23
+ // stage allow rules is the only path there, and must not be
24
+ // indistinguishable from `scoped` + an unrelated allow list. Computed
25
+ // ahead of the guard so the guard can special-case it.
26
+ const rawSyntheticBash = bashApproval === "raw" && args.declared.includes(BASH_TOOL_NAME);
27
+ if (grants.length === 0 && !rawSyntheticBash)
28
+ return undefined;
29
+ // An empty root passed to a spawn or a path join silently means
30
+ // process.cwd() — the directory nax was launched from, which under `-d` is a
31
+ // different repository. That is the #1794 defect; refuse instead. Callers
32
+ // pass packageWorkdir(view), which never yields "".
33
+ //
34
+ // F1 side effect: this throw now also fires for raw + declared Bash + an
35
+ // empty root, where the old guard returned `undefined` first. Correct --
36
+ // the #1794 guard should fire there -- and pinned by a test.
37
+ if (args.root === undefined || args.root.trim() === "") {
38
+ throw new NaxError("Cannot enable coding tools: no working directory was supplied, so the permitted root is unknown.", "CODING_TOOL_ROOT_MISSING", { stage: "tools" });
39
+ }
40
+ // `Exec` is a capability marker in an operation's `tools` declaration, not
41
+ // a registered tool — nothing in registerBuiltinCodingTools carries this
42
+ // name. It decides whether RunCommand gets its argv branch (Task 6); it
43
+ // must never reach runtime.advertised() itself, or the lookup for a tool
44
+ // named "Exec" would simply fail and the marker would vanish from the
45
+ // advertised set without a trace of why.
46
+ const execGrant = grants.findLast((grant) => grant.tool === EXEC_TOOL_NAME);
47
+ const allowExec = args.declared.includes(EXEC_TOOL_NAME) && execGrant !== undefined;
48
+ const advertised = args.declared.filter((name) => name !== EXEC_TOOL_NAME);
49
+ // Gated on DECLARATION ALONE — deliberately not on the grant.
50
+ //
51
+ // Declaration is the ceiling: a tool the runtime can LOOK UP is callable even
52
+ // when `advertised()` never returned it (callTool resolves the name before it
53
+ // consults advertisement), so a stage that grants Bash must not hand it to a
54
+ // reviewer op that never declared it (spec §6 row 11).
55
+ //
56
+ // But an op that DID declare it and holds no grant must be refused by the
57
+ // POLICY, not by a failed name lookup: "unknown tool" carries no redirect,
58
+ // and spec §6 row 1 requires the ungranted case to offer an alternative. So
59
+ // the tool is constructed either way; with no grant, `grantedTools()` still
60
+ // excludes it (never advertised, no schema cost in the prompt), yet the
61
+ // tool's EXISTENCE is what lets the call reach `policy.check` and be denied
62
+ // there -- and only that denial path (in `runtime.callTool`, using
63
+ // `denial-redirect.ts`) can attach a redirect.
64
+ const { effectiveGrants, allowBash, bashDescriptionPatterns } = resolveBashSupport({
65
+ declared: args.declared,
66
+ grants,
67
+ toolPatterns: args.toolPatterns,
68
+ bashApproval,
69
+ });
70
+ const declaredCommands = args.declaredCommands ?? new Map();
71
+ // P4/S5: under `raw`, an UNAVAILABLE launcher refuses every Bash call.
72
+ // US-002: an AVAILABLE one marks the screen sandbox-wrapped, so a PRD it only reads is allowed.
73
+ const rawScreenOptions = rawScreenOptionsFor(args.launcher);
74
+ const sink = args.auditDir !== undefined
75
+ ? createToolAuditSink({
76
+ dir: args.auditDir,
77
+ sessionName: args.sessionName ?? "unattached",
78
+ ...(args.header ? { header: args.header } : {}),
79
+ })
80
+ : createNoOpToolAuditSink();
81
+ const runtime = createCodingToolRuntime({
82
+ policy: compileToolPolicy(effectiveGrants, args.root, {
83
+ bashApproval,
84
+ ...rawScreenOptions,
85
+ ...(args.denyRules !== undefined ? { denyRules: args.denyRules } : {}),
86
+ ...(args.askRules !== undefined ? { askRules: args.askRules } : {}),
87
+ ...(args.fileOutputPath !== undefined ? { ownedWriteExemption: args.fileOutputPath } : {}),
88
+ ...(args.naxAllowWrite !== undefined ? { naxAllowWrite: args.naxAllowWrite } : {}),
89
+ }),
90
+ declaredCommands: new Set(declaredCommands.keys()),
91
+ interceptor: args.interceptor,
92
+ protectedPaths: args.protectedPaths,
93
+ ...(args.abortSignal !== undefined ? { signal: args.abortSignal } : {}),
94
+ ...(args.askResolver !== undefined ? { askResolver: args.askResolver } : {}),
95
+ ...(args.commandShadow !== undefined ? { commandShadow: args.commandShadow } : {}),
96
+ // US-004: the sandbox's temp posture reaches the command guard's temp-only
97
+ // exemption. `isTempConfined` keeps the state read out of this function,
98
+ // which sits at its complexity baseline.
99
+ tempConfined: isTempConfined(args.launcher),
100
+ ...(args.pipelineStage !== undefined ? { pipelineStage: args.pipelineStage } : {}),
101
+ ...(args.storyId !== undefined ? { storyId: args.storyId } : {}),
102
+ ...(args.callId !== undefined ? { callId: args.callId } : {}),
103
+ ...(args.scopeId !== undefined ? { scopeId: args.scopeId } : {}),
104
+ ...(args.denyPaths !== undefined ? { denyPaths: args.denyPaths } : {}),
105
+ sink,
106
+ extraTools: [
107
+ ...(args.extraTools ?? []),
108
+ ...buildDeclaredCommandTools({
109
+ declaredCommands,
110
+ runDeclaredCommand: args.runDeclaredCommand,
111
+ allowExec,
112
+ execGrant,
113
+ allowBash,
114
+ bashDescriptionPatterns,
115
+ bashApproval,
116
+ humanApproval: args.askResolver?.humanReachable === true,
117
+ root: args.root,
118
+ ...(args.repoRoot !== undefined ? { repoRoot: args.repoRoot } : {}),
119
+ ...(args.packageWorkdir !== undefined ? { packageWorkdir: args.packageWorkdir } : {}),
120
+ ...(args.commandCwd !== undefined ? { commandCwd: args.commandCwd } : {}),
121
+ ...(args.allowScripts !== undefined ? { allowScripts: args.allowScripts } : {}),
122
+ ...(args.packageName !== undefined ? { packageName: args.packageName } : {}),
123
+ ...(args.stripEnvVars !== undefined ? { stripEnvVars: args.stripEnvVars } : {}),
124
+ ...(args.shell !== undefined ? { shell: args.shell } : {}),
125
+ ...(args.launcher !== undefined ? { launcher: args.launcher } : {}),
126
+ }),
127
+ ],
128
+ ...(args.providerIdByTool !== undefined ? { providerIdByTool: args.providerIdByTool } : {}),
129
+ });
130
+ const tools = runtime.advertised(advertised);
131
+ // The fixed per-hop cost of advertising provider tools: their schemas enter
132
+ // the prompt whether or not any is called. #2031 shipped the meter and left
133
+ // it unread; this is its consumer, and the same instrument nax#1991's
134
+ // context-burn report needs.
135
+ const providerTools = tools.filter((tool) => args.providerIdByTool?.has(tool.name) === true);
136
+ if (providerTools.length > 0) {
137
+ getSafeLogger()?.debug("tools", "[provider] advertised", {
138
+ storyId: args.storyId,
139
+ count: providerTools.length,
140
+ schemaBytes: advertisedSchemaBytes(providerTools),
141
+ });
142
+ }
143
+ if (tools.length === 0)
144
+ return undefined;
145
+ return { runtime, tools, auditSink: sink };
146
+ }
147
+ /**
148
+ * Ledger session name.
149
+ *
150
+ * Story-only names collide across the three TDD roles, which all write to one
151
+ * directory -- so a ledger could not answer which session made a call, and that
152
+ * is the evidence ADR-029 parity claims are read from.
153
+ */
154
+ export function buildLedgerSessionName(opts) {
155
+ const base = opts.storyId ?? opts.featureName;
156
+ if (base === undefined)
157
+ return "unattached";
158
+ return opts.sessionRole === undefined ? base : `${base}-${opts.sessionRole}`;
159
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Tools appended to the declaration of every op that receives coding tools.
3
+ *
4
+ * Kept separate from DEFAULT_CODING_TOOLS (which is also the fallback
5
+ * `resolveDeclaredTools` returns for an op that omits `tools`) so the append
6
+ * can filter out the copies such a declaration already carries: advertising a
7
+ * name twice puts two entries into `runtime.advertised()`'s output, and from
8
+ * there a duplicate ToolDefinition in the provider request.
9
+ */
10
+ import type { CodingToolName } from "#src/tools/index";
11
+ export declare const UNIVERSAL_CODING_TOOLS: readonly CodingToolName[];
@@ -0,0 +1,5 @@
1
+ export const UNIVERSAL_CODING_TOOLS = [
2
+ "ScratchpadWrite",
3
+ "ScratchpadRead",
4
+ "ScratchpadList",
5
+ ];
@@ -0,0 +1,55 @@
1
+ import type { ShellInterceptRequest, ShellInterceptResult } from "./shell.ts";
2
+ /** Two members: the Git argv site and the Bash shell site (R10). */
3
+ export type Site = "git" | "bash";
4
+ export interface InterceptRequest {
5
+ readonly kind: "argv";
6
+ readonly argv: readonly string[];
7
+ readonly cwd: string;
8
+ readonly site: Site;
9
+ }
10
+ export type InterceptResult = {
11
+ readonly kind: "unchanged";
12
+ } | {
13
+ readonly kind: "rewritten";
14
+ readonly argv: readonly string[];
15
+ readonly provider: string;
16
+ } | {
17
+ readonly kind: "declined";
18
+ readonly reason: string;
19
+ };
20
+ export interface CommandInterceptor {
21
+ readonly provider: string;
22
+ intercept(req: InterceptRequest): Promise<InterceptResult>;
23
+ /** The Bash site's entry point. Absent on an argv-only interceptor. */
24
+ interceptShell?(req: ShellInterceptRequest): Promise<ShellInterceptResult>;
25
+ /** Consulted ONLY for output of a command this interceptor actually rewrote. */
26
+ postProcess?(output: string, req: InterceptRequest | ShellInterceptRequest): {
27
+ output: string;
28
+ notes?: Record<string, string>;
29
+ };
30
+ }
31
+ export interface InterceptOutcome {
32
+ readonly argv: readonly string[];
33
+ readonly executed?: readonly string[];
34
+ readonly provider?: string;
35
+ readonly rewritten: boolean;
36
+ }
37
+ /**
38
+ * A rewrite may do exactly one thing: prefix the original argv with the
39
+ * provider's own binary name. Anything else is refused.
40
+ *
41
+ * The argv analogue of R9's shell narrowing, and far tighter because it can be:
42
+ * the mapping is static and built by nax, never a string parsed back out of a
43
+ * subprocess.
44
+ */
45
+ export declare function validateRewrite(req: InterceptRequest, result: InterceptResult): InterceptResult;
46
+ /**
47
+ * The whole seam, so call sites stay one line (see the file-size gate).
48
+ *
49
+ * Fails open at REWRITE time only (R3): if the interceptor is sick we run the
50
+ * original. A rewritten command that actually RAN and failed keeps its exit
51
+ * code — nax never re-runs raw to disambiguate one, because that makes
52
+ * execution non-idempotent and can cost a full test-suite run.
53
+ */
54
+ export declare function interceptArgv(argv: readonly string[], cwd: string, interceptor: CommandInterceptor | undefined): Promise<InterceptOutcome>;
55
+ export * from "./shell.ts";
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Generic command-interception seam. rtk is a consumer, not a concept here (R1).
3
+ *
4
+ * Two request shapes, one per site: `argv` for the Git tool, validated by
5
+ * `validateRewrite` below, and `shell` for the model-authored Bash string,
6
+ * validated by `validateShellRewrite` in `./shell`. R10 keeps config-supplied
7
+ * command strings out of both.
8
+ */
9
+ import { GIT_ESCAPE_FLAGS } from "#src/tools/git-flags/index";
10
+ function decline(reason) {
11
+ return { kind: "declined", reason };
12
+ }
13
+ /**
14
+ * A rewrite may do exactly one thing: prefix the original argv with the
15
+ * provider's own binary name. Anything else is refused.
16
+ *
17
+ * The argv analogue of R9's shell narrowing, and far tighter because it can be:
18
+ * the mapping is static and built by nax, never a string parsed back out of a
19
+ * subprocess.
20
+ */
21
+ export function validateRewrite(req, result) {
22
+ if (result.kind !== "rewritten")
23
+ return result;
24
+ const { argv, provider } = result;
25
+ if (argv.length !== req.argv.length + 1)
26
+ return decline("rewrite must add exactly one leading token");
27
+ if (argv[0] !== provider)
28
+ return decline(`rewrite must lead with the provider binary, got ${argv[0]}`);
29
+ for (const [i, token] of req.argv.entries()) {
30
+ if (argv[i + 1] !== token)
31
+ return decline(`rewrite altered token ${i}`);
32
+ }
33
+ if (argv.some((token) => GIT_ESCAPE_FLAGS.includes(token)))
34
+ return decline("argv carries a git escape flag");
35
+ return result;
36
+ }
37
+ /**
38
+ * The whole seam, so call sites stay one line (see the file-size gate).
39
+ *
40
+ * Fails open at REWRITE time only (R3): if the interceptor is sick we run the
41
+ * original. A rewritten command that actually RAN and failed keeps its exit
42
+ * code — nax never re-runs raw to disambiguate one, because that makes
43
+ * execution non-idempotent and can cost a full test-suite run.
44
+ */
45
+ export async function interceptArgv(argv, cwd, interceptor) {
46
+ if (interceptor === undefined)
47
+ return { argv, rewritten: false };
48
+ const req = { kind: "argv", argv, cwd, site: "git" };
49
+ let outcome;
50
+ try {
51
+ outcome = validateRewrite(req, await interceptor.intercept(req));
52
+ }
53
+ catch {
54
+ outcome = decline("interceptor threw");
55
+ }
56
+ if (outcome.kind !== "rewritten")
57
+ return { argv, rewritten: false };
58
+ return { argv: outcome.argv, executed: outcome.argv, provider: outcome.provider, rewritten: true };
59
+ }
60
+ export * from "./shell.js";
@@ -0,0 +1,36 @@
1
+ import type { CommandInterceptor } from "./index.ts";
2
+ export interface ShellInterceptRequest {
3
+ readonly kind: "shell";
4
+ readonly command: string;
5
+ readonly cwd: string;
6
+ readonly site: "bash";
7
+ }
8
+ export type ShellInterceptResult = {
9
+ readonly kind: "unchanged";
10
+ } | {
11
+ readonly kind: "rewritten";
12
+ readonly command: string;
13
+ readonly provider: string;
14
+ } | {
15
+ readonly kind: "declined";
16
+ readonly reason: string;
17
+ };
18
+ export interface ShellInterceptOutcome {
19
+ /** What to execute: the original, or the validated rewrite. */
20
+ readonly command: string;
21
+ readonly provider?: string;
22
+ readonly rewritten: boolean;
23
+ }
24
+ /**
25
+ * Accepts a rewrite only if it is the original plus `rtk ` insertions at command
26
+ * words. The provider's own answer is never trusted: every rule below is checked
27
+ * against the byte-exact candidate, and the first failure names why.
28
+ */
29
+ export declare function validateShellRewrite(req: ShellInterceptRequest, result: ShellInterceptResult): ShellInterceptResult;
30
+ /**
31
+ * The seam, one line at the call site.
32
+ *
33
+ * Fails open at REWRITE time only (R3): no interceptor, no shell method, a
34
+ * throw or a failed validation all run the original command unchanged.
35
+ */
36
+ export declare function interceptShell(command: string, cwd: string, interceptor: CommandInterceptor | undefined): Promise<ShellInterceptOutcome>;
@@ -0,0 +1,137 @@
1
+ import { lexBashCommand } from "#src/permissions/index";
2
+ /** The four bytes an accepted rewrite may add, once or many times. */
3
+ const INSERTION = "rtk ";
4
+ /** A leading `NAME=value` assignment, which precedes (and is not) the command word. */
5
+ const ASSIGNMENT = /^[A-Za-z_][A-Za-z0-9_]*=/;
6
+ function decline(reason) {
7
+ return { kind: "declined", reason };
8
+ }
9
+ /**
10
+ * Rule 1: walk the original and the candidate together. The only legal
11
+ * divergence is the candidate holding `rtk ` while the original still has a
12
+ * character to offer; any other difference, and any insertion trailing the
13
+ * original's last character, is refused. Returns the insertion count, or -1.
14
+ */
15
+ function countInsertions(original, candidate) {
16
+ let i = 0;
17
+ let j = 0;
18
+ let insertions = 0;
19
+ while (j < candidate.length) {
20
+ if (i < original.length && original[i] === candidate[j]) {
21
+ i += 1;
22
+ j += 1;
23
+ continue;
24
+ }
25
+ if (i < original.length && candidate.startsWith(INSERTION, j)) {
26
+ insertions += 1;
27
+ j += INSERTION.length;
28
+ continue;
29
+ }
30
+ return -1;
31
+ }
32
+ return i === original.length ? insertions : -1;
33
+ }
34
+ /** Rule 4's index: the command word follows the leading assignments, if any. */
35
+ function leadingAssignments(tokens) {
36
+ let k = 0;
37
+ while (k < tokens.length && ASSIGNMENT.test(tokens[k].text))
38
+ k += 1;
39
+ return k;
40
+ }
41
+ /**
42
+ * Rules 4 and 5 for one segment: its tokens either match the original's, or
43
+ * match them with a single `rtk` inserted at the command word — and a segment
44
+ * already prefixed with `rtk` is left alone.
45
+ */
46
+ function classifySegment(original, candidate) {
47
+ const before = original.tokens;
48
+ const after = candidate.tokens;
49
+ if (before.length === after.length) {
50
+ for (let i = 0; i < before.length; i += 1) {
51
+ if (before[i].text !== after[i].text)
52
+ return "invalid";
53
+ }
54
+ return "equal";
55
+ }
56
+ if (after.length !== before.length + 1)
57
+ return "invalid";
58
+ const k = leadingAssignments(before);
59
+ const inserted = after[k];
60
+ if (inserted.text !== "rtk" || inserted.opaque)
61
+ return "invalid";
62
+ if (before[k]?.text === "rtk")
63
+ return "invalid";
64
+ for (let i = 0; i < k; i += 1) {
65
+ if (before[i].text !== after[i].text)
66
+ return "invalid";
67
+ }
68
+ for (let i = k; i < before.length; i += 1) {
69
+ if (before[i].text !== after[i + 1].text)
70
+ return "invalid";
71
+ }
72
+ return "inserted";
73
+ }
74
+ /**
75
+ * Rule 6: a segment whose own separator is `|` writes into a pipe, and one
76
+ * whose predecessor's separator is `|` reads from it. Compaction would change
77
+ * what the neighbouring program sees, so neither is rewritten.
78
+ */
79
+ function inPipeline(segments, index) {
80
+ return segments[index].separator === "|" || segments[index - 1]?.separator === "|";
81
+ }
82
+ /**
83
+ * Accepts a rewrite only if it is the original plus `rtk ` insertions at command
84
+ * words. The provider's own answer is never trusted: every rule below is checked
85
+ * against the byte-exact candidate, and the first failure names why.
86
+ */
87
+ export function validateShellRewrite(req, result) {
88
+ if (result.kind !== "rewritten")
89
+ return result;
90
+ const insertions = countInsertions(req.command, result.command);
91
+ if (insertions < 0)
92
+ return decline("candidate is not the original plus `rtk ` insertions");
93
+ if (insertions === 0)
94
+ return { kind: "unchanged" };
95
+ const before = lexBashCommand(req.command);
96
+ const after = lexBashCommand(result.command);
97
+ if (before.kind !== "ok" || after.kind !== "ok")
98
+ return decline("original or candidate does not lex");
99
+ if (before.segments.length !== after.segments.length)
100
+ return decline("candidate changed the segment count");
101
+ let inserted = 0;
102
+ for (let i = 0; i < before.segments.length; i += 1) {
103
+ const rewrite = classifySegment(before.segments[i], after.segments[i]);
104
+ if (rewrite === "invalid")
105
+ return decline(`segment ${i} is not the original plus an rtk command word`);
106
+ if (rewrite === "inserted") {
107
+ if (inPipeline(before.segments, i))
108
+ return decline("a rewritten segment would change what a pipe reads");
109
+ inserted += 1;
110
+ }
111
+ }
112
+ if (inserted !== insertions)
113
+ return decline("insertions do not line up with commands");
114
+ return { kind: "rewritten", command: result.command, provider: result.provider };
115
+ }
116
+ /**
117
+ * The seam, one line at the call site.
118
+ *
119
+ * Fails open at REWRITE time only (R3): no interceptor, no shell method, a
120
+ * throw or a failed validation all run the original command unchanged.
121
+ */
122
+ export async function interceptShell(command, cwd, interceptor) {
123
+ if (interceptor === undefined || interceptor.interceptShell === undefined) {
124
+ return { command, rewritten: false };
125
+ }
126
+ const req = { kind: "shell", command, cwd, site: "bash" };
127
+ let outcome;
128
+ try {
129
+ outcome = validateShellRewrite(req, await interceptor.interceptShell(req));
130
+ }
131
+ catch {
132
+ outcome = decline("interceptor threw");
133
+ }
134
+ if (outcome.kind !== "rewritten")
135
+ return { command, rewritten: false };
136
+ return { command: outcome.command, provider: outcome.provider, rewritten: true };
137
+ }
@@ -0,0 +1,19 @@
1
+ import type { CommandShadow } from "./types.ts";
2
+ export declare const COMMAND_SAFETY_DIR = "command-safety";
3
+ export interface BuildCommandShadowOptions {
4
+ readonly config: {
5
+ readonly shadow?: {
6
+ readonly url: string;
7
+ readonly timeoutMs: number;
8
+ readonly authEnv: string;
9
+ };
10
+ readonly guard?: {
11
+ readonly threshold: number;
12
+ };
13
+ } | undefined;
14
+ readonly outputDir: string;
15
+ readonly runId: string;
16
+ readonly storyId?: string;
17
+ readonly env: Readonly<Record<string, string | undefined>>;
18
+ }
19
+ export declare function buildCommandShadow(opts: BuildCommandShadowOptions): CommandShadow | undefined;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Builds the per-story shadow from config (spec 4.5, 5).
3
+ *
4
+ * The config parameter is typed structurally so this module imports nothing
5
+ * from src/config (D8). Returns undefined when no URL is configured, so no
6
+ * shadow code runs on the call path by default.
7
+ *
8
+ * US-003: when the config also carries a `guard`, the shadow exposes a
9
+ * `CommandGuard` whose `assess` reuses this shadow's classifier cache and
10
+ * rule baseline. Absent guard = `shadow.guard === undefined`, the safe
11
+ * default that no shipped profile opts into.
12
+ */
13
+ import { join } from "node:path";
14
+ import { errorMessage } from "#src/infra/errors";
15
+ import { getSafeLogger } from "#src/infra/index";
16
+ import { appendCommandSafetyRow } from "./row.js";
17
+ import { createCommandShadow } from "./shadow.js";
18
+ import { createSystemOneClient } from "./systemone-client.js";
19
+ export const COMMAND_SAFETY_DIR = "command-safety";
20
+ export function buildCommandShadow(opts) {
21
+ const shadow = opts.config?.shadow;
22
+ if (shadow === undefined)
23
+ return undefined;
24
+ const token = opts.env[shadow.authEnv];
25
+ const dir = join(opts.outputDir, COMMAND_SAFETY_DIR);
26
+ let warned = false;
27
+ return createCommandShadow({
28
+ classify: createSystemOneClient({
29
+ url: shadow.url,
30
+ timeoutMs: shadow.timeoutMs,
31
+ ...(token !== undefined && token.length > 0 ? { token } : {}),
32
+ }),
33
+ write: (row) => appendCommandSafetyRow(dir, opts.runId, row),
34
+ runId: opts.runId,
35
+ timeoutMs: shadow.timeoutMs,
36
+ ...(opts.config?.guard !== undefined ? { guard: opts.config.guard } : {}),
37
+ onWriteError: (err) => {
38
+ if (warned)
39
+ return;
40
+ warned = true;
41
+ getSafeLogger()?.warn("command-safety", "Shadow row append failed; later failures this story are not logged", {
42
+ storyId: opts.storyId,
43
+ error: errorMessage(err),
44
+ });
45
+ },
46
+ });
47
+ }