@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,32 @@
1
+ /**
2
+ * Compose links into a resolver. First non-abstain wins.
3
+ *
4
+ * A throwing link abstains rather than propagating: `runtime.callTool` turns an
5
+ * exception out of the resolver into a TOOL ERROR surfaced to the model, not a
6
+ * denial, which would lose the `denied:ask` ledger row and hand the agent
7
+ * something it may retry around.
8
+ *
9
+ * US-003: forwards the same `control` object to every link so a turn-cancel
10
+ * signal reaches whichever link would have answered, not just the first one
11
+ * that asks.
12
+ */
13
+ export function chainAskLinks(links) {
14
+ return {
15
+ async resolve(req, control) {
16
+ const started = Date.now();
17
+ for (const link of links) {
18
+ let outcome;
19
+ try {
20
+ outcome = await link.resolve(req, control);
21
+ }
22
+ catch {
23
+ continue;
24
+ }
25
+ if (outcome.decision === "abstain")
26
+ continue;
27
+ return { decision: outcome.decision, decidedBy: outcome.decidedBy, latencyMs: Date.now() - started };
28
+ }
29
+ return { decision: "deny", decidedBy: "unavailable", latencyMs: Date.now() - started };
30
+ },
31
+ };
32
+ }
@@ -0,0 +1,23 @@
1
+ import { type AskResolver } from "./ask-chain.ts";
2
+ /**
3
+ * Why an ask-matched call was refused. One string per CASE: the previous single
4
+ * message asserted "this run is headless" for situations that are not, and the
5
+ * three are materially different facts for anyone reading the ledger.
6
+ */
7
+ export declare const ASK_NO_CHANNEL_REASON = "matched an ask rule requiring human approval; no approval channel is configured for this run, so the call is refused";
8
+ export declare const ASK_TIMEOUT_REASON = "matched an ask rule requiring human approval; no answer arrived before the approval timeout, so the call is refused";
9
+ export declare const ASK_DENIED_REASON = "matched an ask rule requiring human approval; the operator denied it";
10
+ /**
11
+ * US-003: the orchestrating turn was cancelled before anyone answered (or
12
+ * between the resolver returning allow and the tool actually running). The
13
+ * tool never executed; the message stays short so it reads in a tool error.
14
+ */
15
+ export declare const ASK_CANCELLED_REASON = "Not run: the turn was cancelled before anyone answered.";
16
+ export declare const ASK_UNSHOWABLE_REASON = "Not run: the command contains a secret that cannot be shown to the approver safely; pass it through an environment variable instead.";
17
+ /** Kept for compatibility with existing callers and tests. */
18
+ export declare const ASK_UNAVAILABLE_REASON = "matched an ask rule requiring human approval; no approval channel is configured for this run, so the call is refused";
19
+ /**
20
+ * The resolver a run gets when no channel is available: a chain with zero
21
+ * links, whose terminal deny supplies the answer. TOTAL -- never abstains.
22
+ */
23
+ export declare function headlessAskResolver(): AskResolver;
@@ -0,0 +1,25 @@
1
+ import { chainAskLinks } from "./ask-chain.js";
2
+ /**
3
+ * Why an ask-matched call was refused. One string per CASE: the previous single
4
+ * message asserted "this run is headless" for situations that are not, and the
5
+ * three are materially different facts for anyone reading the ledger.
6
+ */
7
+ export const ASK_NO_CHANNEL_REASON = "matched an ask rule requiring human approval; no approval channel is configured for this run, so the call is refused";
8
+ export const ASK_TIMEOUT_REASON = "matched an ask rule requiring human approval; no answer arrived before the approval timeout, so the call is refused";
9
+ export const ASK_DENIED_REASON = "matched an ask rule requiring human approval; the operator denied it";
10
+ /**
11
+ * US-003: the orchestrating turn was cancelled before anyone answered (or
12
+ * between the resolver returning allow and the tool actually running). The
13
+ * tool never executed; the message stays short so it reads in a tool error.
14
+ */
15
+ export const ASK_CANCELLED_REASON = "Not run: the turn was cancelled before anyone answered.";
16
+ export const ASK_UNSHOWABLE_REASON = "Not run: the command contains a secret that cannot be shown to the approver safely; pass it through an environment variable instead.";
17
+ /** Kept for compatibility with existing callers and tests. */
18
+ export const ASK_UNAVAILABLE_REASON = ASK_NO_CHANNEL_REASON;
19
+ /**
20
+ * The resolver a run gets when no channel is available: a chain with zero
21
+ * links, whose terminal deny supplies the answer. TOTAL -- never abstains.
22
+ */
23
+ export function headlessAskResolver() {
24
+ return chainAskLinks([]);
25
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Lexer and segmenter for a model-authored Bash command (spec §4 US-005.1-2).
3
+ *
4
+ * Safe-by-refusal, not safe-by-sandbox: any construct this lexer does not
5
+ * MODEL is refused by name, because a payload the gate cannot read is a
6
+ * payload no shell gets (spec R11). That is a deliberately small language —
7
+ * words, quotes, operators, simple redirections — and growing it is a
8
+ * permission decision, not a parser improvement.
9
+ *
10
+ * "Refused by name" is the whole guarantee, so it must hold for EVERY
11
+ * unmodelled construct, not only the ones that look dangerous. A construct
12
+ * that folds into a word instead is worse than one that is modelled: the
13
+ * segment still reaches the policy, and its first token is no longer the
14
+ * command a deny rule was written against.
15
+ *
16
+ * ZERO imports, on purpose. `src/permissions` must not value-import
17
+ * `@/tools` (the edge runs the other way: src/tools/runtime.ts imports this
18
+ * package), and a lexer that needs nothing is also a lexer that can be tested
19
+ * without a filesystem, a policy or a spawn.
20
+ */
21
+ /** One word of a segment. `opaque` means it contained `$`-expansion, so its
22
+ * RUNTIME value is unknown here and policy-bash.ts refuses it before execution. */
23
+ export interface BashToken {
24
+ readonly text: string;
25
+ readonly opaque: boolean;
26
+ }
27
+ /** A simple redirection. The target is containment-checked by the caller. */
28
+ export interface BashRedirect {
29
+ readonly operator: string;
30
+ readonly target: string;
31
+ readonly opaque: boolean;
32
+ }
33
+ /** The control operator that follows a command segment, when any. */
34
+ export type BashSegmentSeparator = ";" | "&&" | "||" | "|" | "&";
35
+ /** One command between control operators. */
36
+ export interface BashSegment {
37
+ readonly tokens: readonly BashToken[];
38
+ readonly redirects: readonly BashRedirect[];
39
+ readonly separator?: BashSegmentSeparator;
40
+ }
41
+ export type BashLexResult = {
42
+ readonly kind: "ok";
43
+ readonly segments: readonly BashSegment[];
44
+ } | {
45
+ readonly kind: "refused";
46
+ readonly construct: string;
47
+ /**
48
+ * The lexable prefix at the point of refusal: the completed segments,
49
+ * followed by the interrupted segment's completed tokens and redirects
50
+ * only. The word being built when the refusal struck is dropped, as is a
51
+ * redirect operator still waiting for its target; a trailing segment with
52
+ * neither tokens nor redirects is omitted. `[]` when the refusal precedes
53
+ * any completed word. The gate runs its deny and payload checks over this
54
+ * prefix, so an out-of-bounds command never reaches the human just because
55
+ * a later construct was unreadable.
56
+ */
57
+ readonly prefix: readonly BashSegment[];
58
+ };
59
+ export declare function lexBashCommand(command: string): BashLexResult;
@@ -0,0 +1,279 @@
1
+ /**
2
+ * Lexer and segmenter for a model-authored Bash command (spec §4 US-005.1-2).
3
+ *
4
+ * Safe-by-refusal, not safe-by-sandbox: any construct this lexer does not
5
+ * MODEL is refused by name, because a payload the gate cannot read is a
6
+ * payload no shell gets (spec R11). That is a deliberately small language —
7
+ * words, quotes, operators, simple redirections — and growing it is a
8
+ * permission decision, not a parser improvement.
9
+ *
10
+ * "Refused by name" is the whole guarantee, so it must hold for EVERY
11
+ * unmodelled construct, not only the ones that look dangerous. A construct
12
+ * that folds into a word instead is worse than one that is modelled: the
13
+ * segment still reaches the policy, and its first token is no longer the
14
+ * command a deny rule was written against.
15
+ *
16
+ * ZERO imports, on purpose. `src/permissions` must not value-import
17
+ * `@/tools` (the edge runs the other way: src/tools/runtime.ts imports this
18
+ * package), and a lexer that needs nothing is also a lexer that can be tested
19
+ * without a filesystem, a policy or a spawn.
20
+ */
21
+ /** End index of a double-quoted run starting at `from`, honouring backslash
22
+ * escapes, or -1 when the quote is never closed. */
23
+ function doubleQuoteEnd(command, from) {
24
+ for (let i = from; i < command.length; i += 1) {
25
+ const char = command[i];
26
+ if (char === "\\") {
27
+ i += 1;
28
+ continue;
29
+ }
30
+ if (char === '"')
31
+ return i;
32
+ }
33
+ return -1;
34
+ }
35
+ export function lexBashCommand(command) {
36
+ // Checked up front so the reason names the real problem. Leaving it to the
37
+ // final flushSegment() would report "an empty command segment", which is
38
+ // true and useless.
39
+ if (command.trim() === "")
40
+ return { kind: "refused", construct: "an empty command", prefix: [] };
41
+ const state = {
42
+ command,
43
+ segments: [],
44
+ tokens: [],
45
+ redirects: [],
46
+ word: "",
47
+ opaque: false,
48
+ started: false,
49
+ pendingRedirect: undefined,
50
+ };
51
+ let i = 0;
52
+ while (i < command.length) {
53
+ const step = stepAt(state, i);
54
+ if (step === undefined) {
55
+ // Plain word character: accumulates into the token being built.
56
+ state.word += command[i];
57
+ state.started = true;
58
+ i += 1;
59
+ continue;
60
+ }
61
+ if (step.kind === "refuse")
62
+ return refusedHere(state, step.construct);
63
+ i += step.by;
64
+ }
65
+ const error = flushSegment(state);
66
+ if (error !== undefined)
67
+ return refusedHere(state, error);
68
+ return { kind: "ok", segments: state.segments };
69
+ }
70
+ /** Snapshots the lexable prefix at a refusal site (see `BashLexResult`). The
71
+ * in-progress `word` is deliberately excluded, and `pendingRedirect` -- an
72
+ * operator awaiting its target -- lives outside `redirects`, so it is dropped
73
+ * with it. */
74
+ function refusedHere(state, construct) {
75
+ const prefix = state.tokens.length === 0 && state.redirects.length === 0
76
+ ? [...state.segments]
77
+ : [...state.segments, { tokens: state.tokens, redirects: state.redirects }];
78
+ return { kind: "refused", construct, prefix };
79
+ }
80
+ function flushWord(state) {
81
+ if (!state.started)
82
+ return;
83
+ if (state.pendingRedirect !== undefined) {
84
+ state.redirects.push({ operator: state.pendingRedirect, target: state.word, opaque: state.opaque });
85
+ state.pendingRedirect = undefined;
86
+ }
87
+ else {
88
+ state.tokens.push({ text: state.word, opaque: state.opaque });
89
+ }
90
+ state.word = "";
91
+ state.opaque = false;
92
+ state.started = false;
93
+ }
94
+ /** Closes a segment, or names why it cannot be closed. A dangling operator
95
+ * leaves an empty segment, which is refused rather than silently dropped:
96
+ * `bun test &&` is a truncated command, and guessing at intent here would
97
+ * approve something nobody wrote. */
98
+ function flushSegment(state, separator) {
99
+ flushWord(state);
100
+ if (state.pendingRedirect !== undefined)
101
+ return "a redirection with no target";
102
+ if (state.tokens.length === 0 && state.redirects.length === 0)
103
+ return "an empty command segment";
104
+ state.segments.push({
105
+ tokens: state.tokens,
106
+ redirects: state.redirects,
107
+ ...(separator === undefined ? {} : { separator }),
108
+ });
109
+ state.tokens = [];
110
+ state.redirects = [];
111
+ return undefined;
112
+ }
113
+ function lexSingleQuote(state, i) {
114
+ if (state.command[i] !== "'")
115
+ return undefined;
116
+ const end = state.command.indexOf("'", i + 1);
117
+ if (end === -1)
118
+ return { kind: "refuse", construct: "an unbalanced single quote" };
119
+ // Single quotes suppress every expansion, so the content stays literal
120
+ // and the token stays analysable.
121
+ state.word += state.command.slice(i + 1, end);
122
+ state.started = true;
123
+ return { kind: "advance", by: end + 1 - i };
124
+ }
125
+ function lexDoubleQuote(state, i) {
126
+ if (state.command[i] !== '"')
127
+ return undefined;
128
+ const end = doubleQuoteEnd(state.command, i + 1);
129
+ if (end === -1)
130
+ return { kind: "refuse", construct: "an unbalanced double quote" };
131
+ const inner = state.command.slice(i + 1, end);
132
+ if (inner.includes("$("))
133
+ return { kind: "refuse", construct: "a command substitution `$(...)`" };
134
+ if (inner.includes("`"))
135
+ return { kind: "refuse", construct: "a backtick command substitution" };
136
+ if (inner.includes("$"))
137
+ state.opaque = true;
138
+ state.word += inner;
139
+ state.started = true;
140
+ return { kind: "advance", by: end + 1 - i };
141
+ }
142
+ function lexEscape(state, i) {
143
+ if (state.command[i] !== "\\")
144
+ return undefined;
145
+ const next = state.command[i + 1];
146
+ if (next === undefined)
147
+ return { kind: "refuse", construct: "a trailing backslash" };
148
+ state.word += next;
149
+ state.started = true;
150
+ return { kind: "advance", by: 2 };
151
+ }
152
+ /** Grouping, negation, comments and command substitutions are shell SYNTAX,
153
+ * not word characters: left unmodelled they fold into the token text, so
154
+ * `(rm -rf x)` and `! rm -rf x` present a first token of `(rm` / `!` that no
155
+ * `Bash(rm*)` deny rule can match, while /bin/sh runs the `rm` regardless.
156
+ * Refused by name, like every other construct this lexer cannot read. `!` and
157
+ * `#` are only special at the START of a word -- `a!b` and `a#b` are ordinary
158
+ * literals in sh, and refusing those would deny commands a grant plainly
159
+ * covers. */
160
+ function refuseGroupingOrExpansion(state, i) {
161
+ const command = state.command;
162
+ const char = command[i];
163
+ const next = command[i + 1];
164
+ if (char === "(" || char === ")")
165
+ return { kind: "refuse", construct: "a subshell `( ... )`" };
166
+ if (char === "!" && !state.started)
167
+ return { kind: "refuse", construct: "a `!` negation" };
168
+ if (char === "#" && !state.started)
169
+ return { kind: "refuse", construct: "a `#` comment" };
170
+ if (char === "$" && next === "(")
171
+ return { kind: "refuse", construct: "a command substitution `$(...)`" };
172
+ if (char === "`")
173
+ return { kind: "refuse", construct: "a backtick command substitution" };
174
+ return undefined;
175
+ }
176
+ /** Redirection FORMS this lexer does not model: a process substitution, a
177
+ * here-document, file-descriptor duplication and bash's `&>` shorthand are
178
+ * each refused by name before the plain `<`/`>` handler can claim them. */
179
+ function refuseRedirectForm(state, i) {
180
+ const command = state.command;
181
+ const char = command[i];
182
+ const next = command[i + 1];
183
+ if ((char === "<" || char === ">") && next === "(") {
184
+ return { kind: "refuse", construct: "a process substitution `<(...)` / `>(...)`" };
185
+ }
186
+ if (char === "<" && next === "<")
187
+ return { kind: "refuse", construct: "a here-document `<<`" };
188
+ if ((char === "<" || char === ">") && next === "&") {
189
+ return { kind: "refuse", construct: "file-descriptor duplication (`2>&1`)" };
190
+ }
191
+ if (char === "&" && next === ">")
192
+ return { kind: "refuse", construct: "the `&>` redirection form" };
193
+ return undefined;
194
+ }
195
+ function lexDollar(state, i) {
196
+ if (state.command[i] !== "$")
197
+ return undefined;
198
+ state.opaque = true;
199
+ state.word += "$";
200
+ state.started = true;
201
+ return { kind: "advance", by: 1 };
202
+ }
203
+ function skipWhitespace(state, i) {
204
+ const char = state.command[i];
205
+ if (char !== " " && char !== "\t" && char !== "\r")
206
+ return undefined;
207
+ flushWord(state);
208
+ return { kind: "advance", by: 1 };
209
+ }
210
+ function lexLineSeparator(state, i) {
211
+ const char = state.command[i];
212
+ if (char !== "\n" && char !== ";")
213
+ return undefined;
214
+ const error = flushSegment(state, ";");
215
+ if (error !== undefined)
216
+ return { kind: "refuse", construct: error };
217
+ return { kind: "advance", by: 1 };
218
+ }
219
+ function lexControlOperator(state, i) {
220
+ const command = state.command;
221
+ const char = command[i];
222
+ const next = command[i + 1];
223
+ if ((char === "&" && next === "&") || (char === "|" && next === "|")) {
224
+ const error = flushSegment(state, char === "&" ? "&&" : "||");
225
+ if (error !== undefined)
226
+ return { kind: "refuse", construct: error };
227
+ return { kind: "advance", by: 2 };
228
+ }
229
+ if (char === "|" || char === "&") {
230
+ const error = flushSegment(state, char);
231
+ if (error !== undefined)
232
+ return { kind: "refuse", construct: error };
233
+ return { kind: "advance", by: 1 };
234
+ }
235
+ return undefined;
236
+ }
237
+ function lexRedirect(state, i) {
238
+ const command = state.command;
239
+ const char = command[i];
240
+ if (char !== ">" && char !== "<")
241
+ return undefined;
242
+ // A bare fd digit belongs to the operator (`2>err.txt`), not to argv:
243
+ // left in the token list it would have to satisfy an allow rule, and
244
+ // `Bash(bun test *)` would refuse a command it plainly covers.
245
+ if (/^\d$/.test(state.word)) {
246
+ state.word = "";
247
+ state.started = false;
248
+ }
249
+ flushWord(state);
250
+ const appends = char === ">" && command[i + 1] === ">";
251
+ state.pendingRedirect = appends ? ">>" : char;
252
+ return { kind: "advance", by: appends ? 2 : 1 };
253
+ }
254
+ /** The dispatch table. ORDER IS BEHAVIOUR: the first handler that claims the
255
+ * character wins, so the table must stay in the original guard chain's order —
256
+ * quote/escape handling, structural refusals, redirect-form refusals, `$`,
257
+ * whitespace, separators, plain redirection. */
258
+ const HANDLERS = [
259
+ lexSingleQuote,
260
+ lexDoubleQuote,
261
+ lexEscape,
262
+ refuseGroupingOrExpansion,
263
+ refuseRedirectForm,
264
+ lexDollar,
265
+ skipWhitespace,
266
+ lexLineSeparator,
267
+ lexControlOperator,
268
+ lexRedirect,
269
+ ];
270
+ /** Runs the first handler that claims the character at `i`, or `undefined`
271
+ * when it is a plain word character (accumulated by `lexBashCommand`). */
272
+ function stepAt(state, i) {
273
+ for (const lex of HANDLERS) {
274
+ const step = lex(state, i);
275
+ if (step !== undefined)
276
+ return step;
277
+ }
278
+ return undefined;
279
+ }
@@ -0,0 +1,12 @@
1
+ import type { ToolGrant } from "./types.ts";
2
+ /**
3
+ * Parse one #374 tool expression.
4
+ *
5
+ * `Read` -> unconditional
6
+ * `Write(src/**,test/**)` -> those two globs
7
+ * `Git(diff,log)` -> those two subcommands
8
+ */
9
+ export declare function parseToolExpression(expression: string): ToolGrant;
10
+ /** Parse a list of rule expressions into grants. Effect is carried by which
11
+ * config list (allow/deny/ask) the expressions came from, not by the grant. */
12
+ export declare function parseRuleList(expressions: readonly string[]): ToolGrant[];
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Parse one #374 tool expression.
3
+ *
4
+ * `Read` -> unconditional
5
+ * `Write(src/**,test/**)` -> those two globs
6
+ * `Git(diff,log)` -> those two subcommands
7
+ */
8
+ export function parseToolExpression(expression) {
9
+ const open = expression.indexOf("(");
10
+ if (open === -1)
11
+ return { tool: expression.trim(), patterns: ["*"] };
12
+ const tool = expression.slice(0, open).trim();
13
+ const inner = expression.slice(open + 1, expression.lastIndexOf(")"));
14
+ const patterns = inner
15
+ .split(",")
16
+ .map((p) => p.trim())
17
+ .filter((p) => p.length > 0);
18
+ return { tool, patterns: patterns.length > 0 ? patterns : ["*"] };
19
+ }
20
+ /** Parse a list of rule expressions into grants. Effect is carried by which
21
+ * config list (allow/deny/ask) the expressions came from, not by the grant. */
22
+ export function parseRuleList(expressions) {
23
+ return expressions.map(parseToolExpression);
24
+ }
@@ -0,0 +1,11 @@
1
+ export * from "./approval-audit.ts";
2
+ export * from "./approvals-link.ts";
3
+ export * from "./approvals-store.ts";
4
+ export * from "./approvals-taint.ts";
5
+ export { ASK_CANCELLED_REASON, ASK_DENIED_REASON, ASK_NO_CHANNEL_REASON, ASK_TIMEOUT_REASON, ASK_UNAVAILABLE_REASON, ASK_UNSHOWABLE_REASON, headlessAskResolver, } from "./ask.ts";
6
+ export * from "./ask-chain.ts";
7
+ export type { BashLexResult, BashRedirect, BashSegment, BashSegmentSeparator, BashToken } from "./bash-lex.ts";
8
+ export { lexBashCommand } from "./bash-lex.ts";
9
+ export { parseRuleList, parseToolExpression } from "./grammar.ts";
10
+ export * from "./secret-spans.ts";
11
+ export type { AskRequest, ResolvedPermissions } from "./types.ts";
@@ -0,0 +1,9 @@
1
+ export * from "./approval-audit.js";
2
+ export * from "./approvals-link.js";
3
+ export * from "./approvals-store.js";
4
+ export * from "./approvals-taint.js";
5
+ export { ASK_CANCELLED_REASON, ASK_DENIED_REASON, ASK_NO_CHANNEL_REASON, ASK_TIMEOUT_REASON, ASK_UNAVAILABLE_REASON, ASK_UNSHOWABLE_REASON, headlessAskResolver, } from "./ask.js";
6
+ export * from "./ask-chain.js";
7
+ export { lexBashCommand } from "./bash-lex.js";
8
+ export { parseRuleList, parseToolExpression } from "./grammar.js";
9
+ export * from "./secret-spans.js";
@@ -0,0 +1,19 @@
1
+ export interface SecretSpan {
2
+ readonly start: number;
3
+ readonly end: number;
4
+ readonly kind: string;
5
+ }
6
+ export type MaskResult = {
7
+ readonly ok: true;
8
+ readonly masked: string;
9
+ readonly count: number;
10
+ } | {
11
+ readonly ok: false;
12
+ readonly reason: string;
13
+ };
14
+ /** Non-overlapping spans, sorted; overlapping or touching matches merge (earliest kind wins). */
15
+ export declare function findSecretSpans(text: string): readonly SecretSpan[];
16
+ export declare function maskForPrompt(text: string): MaskResult;
17
+ export declare function redactForRow(text: string): string;
18
+ /** Apply `redactForRow` to every string leaf. Keys and non-string values are kept as-is. */
19
+ export declare function redactRowStrings<T>(value: T): T;
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Secret spans in agent-authored command text (review #9, spec section 1).
3
+ *
4
+ * Two consumers with different stakes:
5
+ * - `maskForPrompt` feeds an approval prompt. A masked span must never hide
6
+ * code from the human approving it, so any span containing shell syntax
7
+ * makes the whole text unshowable and the ask is denied instead (D18).
8
+ * - `redactForRow` feeds local audit rows. Nothing is approved from them but
9
+ * forensic content matters, so each span is masked only up to its first
10
+ * shell-active character: `Cookie: a=b'; rm -rf ~` keeps `'; rm -rf ~`.
11
+ * Accepted residual: secret characters after a shell-active character stay
12
+ * visible in the row.
13
+ */
14
+ import { SECRET_VALUE_PATTERNS } from "#src/internal/redact";
15
+ const SHELL_ACTIVE = /[$`;|&<>()'"\n]/;
16
+ /** A PEM body is newline-separated by construction; only real shell syntax cuts it. */
17
+ const SHELL_ACTIVE_IN_PEM = /[$`;|&<>()'"]/;
18
+ /** Kinds whose match is `NAME=value` / `name: value`; a `$` value is a reference, not a secret. */
19
+ const VALUE_KINDS = new Set(["assignment", "api-key-header"]);
20
+ function referencesVariable(kind, match) {
21
+ if (!VALUE_KINDS.has(kind))
22
+ return false;
23
+ const sep = match.search(/[=:]/);
24
+ return (sep >= 0 &&
25
+ match
26
+ .slice(sep + 1)
27
+ .trimStart()
28
+ .startsWith("$"));
29
+ }
30
+ function rawSpans(text) {
31
+ return SECRET_VALUE_PATTERNS.flatMap(({ kind, re }) => {
32
+ // A private copy: the shared /g regex carries lastIndex between callers.
33
+ const scan = new RegExp(re.source, re.flags);
34
+ const found = [];
35
+ for (let m = scan.exec(text); m !== null; m = scan.exec(text)) {
36
+ if (!referencesVariable(kind, m[0]))
37
+ found.push({ start: m.index, end: m.index + m[0].length, kind });
38
+ }
39
+ return found;
40
+ });
41
+ }
42
+ /** Non-overlapping spans, sorted; overlapping or touching matches merge (earliest kind wins). */
43
+ export function findSecretSpans(text) {
44
+ const sorted = [...rawSpans(text)].sort((a, b) => a.start - b.start || b.end - a.end);
45
+ const merged = [];
46
+ for (const span of sorted) {
47
+ const last = merged.at(-1);
48
+ if (last !== undefined && span.start <= last.end) {
49
+ merged[merged.length - 1] = { ...last, end: Math.max(last.end, span.end) };
50
+ }
51
+ else {
52
+ merged.push(span);
53
+ }
54
+ }
55
+ return merged;
56
+ }
57
+ function replaceSpans(text, spans, render) {
58
+ const parts = spans.map((span, i) => {
59
+ const gapStart = i === 0 ? 0 : (spans[i - 1]?.end ?? 0);
60
+ return text.slice(gapStart, span.start) + render(span, text.slice(span.start, span.end));
61
+ });
62
+ return parts.join("") + text.slice(spans.at(-1)?.end ?? 0);
63
+ }
64
+ export function maskForPrompt(text) {
65
+ const spans = findSecretSpans(text);
66
+ const unsafe = spans.find((span) => SHELL_ACTIVE.test(text.slice(span.start, span.end)));
67
+ if (unsafe !== undefined) {
68
+ return { ok: false, reason: `a ${unsafe.kind} secret spans shell syntax` };
69
+ }
70
+ return { ok: true, masked: replaceSpans(text, spans, (span) => `[REDACTED:${span.kind}]`), count: spans.length };
71
+ }
72
+ export function redactForRow(text) {
73
+ return replaceSpans(text, findSecretSpans(text), (span, body) => {
74
+ const cut = body.search(span.kind === "pem" ? SHELL_ACTIVE_IN_PEM : SHELL_ACTIVE);
75
+ return cut === -1 ? `[REDACTED:${span.kind}]` : `[REDACTED:${span.kind}]${body.slice(cut)}`;
76
+ });
77
+ }
78
+ /** Apply `redactForRow` to every string leaf. Keys and non-string values are kept as-is. */
79
+ export function redactRowStrings(value) {
80
+ if (typeof value === "string")
81
+ return redactForRow(value);
82
+ if (Array.isArray(value))
83
+ return value.map((item) => redactRowStrings(item));
84
+ if (value !== null && typeof value === "object") {
85
+ return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, redactRowStrings(v)]));
86
+ }
87
+ return value;
88
+ }