@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,92 @@
1
+ /**
2
+ * One POST to a SystemOne endpoint (spec 6.2).
3
+ *
4
+ * Total by construction: every failure maps to a ModelResult, so the promise
5
+ * never rejects. It never retries (a retry adds load to a local model for data
6
+ * that decides nothing) and never truncates (an oversize command is recorded
7
+ * as `oversize`). The timeout is a config value, applied as an AbortSignal.
8
+ */
9
+ import { buildRequest } from "./questions.js";
10
+ import { HARM_OPTIONS, QUESTION_IDS } from "./types.js";
11
+ /** Injectable seams: tests drive the timeout by hand instead of waiting on it. */
12
+ export const _systemOneClientDeps = {
13
+ fetch: (input, init) => fetch(input, init),
14
+ timeoutSignal: (ms) => AbortSignal.timeout(ms),
15
+ now: () => performance.now(),
16
+ };
17
+ const isTimeout = (err) => typeof err === "object" && err !== null && "name" in err && err.name === "TimeoutError";
18
+ export function createSystemOneClient(opts) {
19
+ return async (command) => {
20
+ const started = _systemOneClientDeps.now();
21
+ const elapsed = () => Math.round(_systemOneClientDeps.now() - started);
22
+ const unavailable = (error) => ({ status: "unavailable", error, latencyMs: elapsed() });
23
+ let res;
24
+ try {
25
+ res = await _systemOneClientDeps.fetch(opts.url, {
26
+ method: "POST",
27
+ headers: {
28
+ "content-type": "application/json",
29
+ ...(opts.token !== undefined && opts.token.length > 0 ? { authorization: `Bearer ${opts.token}` } : {}),
30
+ },
31
+ body: JSON.stringify(buildRequest(command)),
32
+ signal: _systemOneClientDeps.timeoutSignal(opts.timeoutMs),
33
+ });
34
+ }
35
+ catch (err) {
36
+ return unavailable(isTimeout(err) ? "timeout" : "network");
37
+ }
38
+ if (res.status === 413)
39
+ return { status: "oversize", latencyMs: elapsed() };
40
+ if (res.status === 401)
41
+ return unavailable("unauthorized");
42
+ if (res.status !== 200)
43
+ return unavailable(`http_${res.status}`);
44
+ let body;
45
+ try {
46
+ body = await res.json();
47
+ }
48
+ catch (err) {
49
+ return unavailable(isTimeout(err) ? "timeout" : "malformed");
50
+ }
51
+ return parseAnswer(body, elapsed());
52
+ };
53
+ }
54
+ const isProbability = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 && v <= 1;
55
+ const record = (v) => typeof v === "object" && v !== null && !Array.isArray(v) ? v : undefined;
56
+ /** Map a 200 body to a ModelResult. Anything short of all seven valid answers is `malformed`. */
57
+ export function parseAnswer(body, latencyMs) {
58
+ const malformed = { status: "unavailable", error: "malformed", latencyMs };
59
+ const top = record(body);
60
+ if (top === undefined)
61
+ return malformed;
62
+ const decision = record(top.x_proxy)?.decision_id;
63
+ const decisionId = typeof decision === "string" ? decision : undefined;
64
+ if (record(top.error)?.kind === "provider_blocked") {
65
+ return { status: "blocked", ...(decisionId !== undefined ? { decisionId } : {}), latencyMs };
66
+ }
67
+ const answers = record(top.answers);
68
+ const probabilities = record(record(answers?.harm)?.probabilities);
69
+ if (answers === undefined || probabilities === undefined)
70
+ return malformed;
71
+ const harm = {};
72
+ for (const option of HARM_OPTIONS) {
73
+ const p = probabilities[option];
74
+ if (!isProbability(p))
75
+ return malformed;
76
+ harm[option] = p;
77
+ }
78
+ const noul = {};
79
+ for (const id of QUESTION_IDS) {
80
+ const p = record(answers[id])?.noul;
81
+ if (!isProbability(p))
82
+ return malformed;
83
+ noul[id] = p;
84
+ }
85
+ return {
86
+ status: "answered",
87
+ answers: { harm: harm, noul: noul },
88
+ ...(typeof top.model === "string" ? { model: top.model } : {}),
89
+ ...(decisionId !== undefined ? { decisionId } : {}),
90
+ latencyMs,
91
+ };
92
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * The only code runtime.callTool calls (spec 4.2).
3
+ *
4
+ * Takes plain values rather than tool types, so src/command-safety imports
5
+ * nothing from src/tools. Only the `Bash` and `Exec` identities are observed;
6
+ * a RunCommand verb call runs a user-declared command and never is (D14).
7
+ */
8
+ import { type CallIdentifiers } from "./identifiers.ts";
9
+ import type { CommandShadow, ExecRun, LedgerOutcome, MechanicalVerdict } from "./types.ts";
10
+ export interface ShadowTap {
11
+ /**
12
+ * `run` is what the Exec tool actually ran (argv after normalization, and
13
+ * its cwd). It is forwarded to the shadow only for an `Exec` observation; a
14
+ * `Bash` row drops it, since its executed argv is `[shell, "-c", command]`
15
+ * and its cwd (when the caller supplied `root`) is already on the observation.
16
+ */
17
+ settle(ledger: LedgerOutcome, decidedBy?: string, run?: ExecRun): void;
18
+ }
19
+ export interface ShadowCall extends CallIdentifiers {
20
+ readonly key: string;
21
+ readonly identity: string;
22
+ readonly command: unknown;
23
+ readonly argv: unknown;
24
+ /** The policy root. A Bash command starts there, so it becomes the observation's cwd. */
25
+ readonly root?: string;
26
+ readonly verdict: {
27
+ readonly allowed: boolean;
28
+ readonly outcome?: string;
29
+ readonly breach?: boolean;
30
+ readonly rule?: string;
31
+ };
32
+ readonly stage: string;
33
+ readonly storyId?: string;
34
+ }
35
+ export declare function toMechanical(verdict: ShadowCall["verdict"]): MechanicalVerdict;
36
+ /** Observe now; settle later, exactly once. Never throws. */
37
+ export declare function openShadowTap(shadow: CommandShadow | undefined, call: ShadowCall): ShadowTap;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * The only code runtime.callTool calls (spec 4.2).
3
+ *
4
+ * Takes plain values rather than tool types, so src/command-safety imports
5
+ * nothing from src/tools. Only the `Bash` and `Exec` identities are observed;
6
+ * a RunCommand verb call runs a user-declared command and never is (D14).
7
+ */
8
+ import { callIdentifiers } from "./identifiers.js";
9
+ const NO_TAP = { settle: () => undefined };
10
+ export function toMechanical(verdict) {
11
+ if (verdict.allowed)
12
+ return { verdict: "allow", breach: false };
13
+ return {
14
+ verdict: verdict.outcome === "ask" ? "ask" : "deny",
15
+ breach: verdict.breach === true,
16
+ ...(verdict.rule !== undefined ? { rule: verdict.rule } : {}),
17
+ };
18
+ }
19
+ function toObservation(call) {
20
+ const base = {
21
+ stage: call.stage,
22
+ ...(call.storyId !== undefined ? { storyId: call.storyId } : {}),
23
+ mechanical: toMechanical(call.verdict),
24
+ ...callIdentifiers(call),
25
+ };
26
+ if (call.identity === "Bash" && typeof call.command === "string") {
27
+ return {
28
+ command: call.command,
29
+ identity: "Bash",
30
+ ...base,
31
+ ...(call.root !== undefined ? { cwd: call.root } : {}),
32
+ };
33
+ }
34
+ if (call.identity === "Exec" && Array.isArray(call.argv) && call.argv.every((a) => typeof a === "string")) {
35
+ const argv = call.argv.map(String);
36
+ return { command: argv.join(" "), identity: "Exec", argv, ...base };
37
+ }
38
+ return undefined;
39
+ }
40
+ /** Observe now; settle later, exactly once. Never throws. */
41
+ export function openShadowTap(shadow, call) {
42
+ if (shadow === undefined)
43
+ return NO_TAP;
44
+ try {
45
+ const obs = toObservation(call);
46
+ if (obs === undefined)
47
+ return NO_TAP;
48
+ shadow.observe(call.key, obs);
49
+ let settled = false;
50
+ return {
51
+ settle(ledger, decidedBy, run) {
52
+ if (settled)
53
+ return;
54
+ settled = true;
55
+ try {
56
+ const outcome = { ledger, ...(decidedBy !== undefined ? { decidedBy } : {}) };
57
+ // Forward `run` for an Exec observation only: a Bash row's executed
58
+ // argv is `[shell, "-c", command]`, which the row's own `command`
59
+ // already states, so passing it would add a redundant key.
60
+ if (obs.identity === "Exec" && run !== undefined) {
61
+ shadow.settle(call.key, outcome, run);
62
+ }
63
+ else {
64
+ shadow.settle(call.key, outcome);
65
+ }
66
+ }
67
+ catch {
68
+ // A shadow failure stops the row, never the call (spec 4.3).
69
+ }
70
+ },
71
+ };
72
+ }
73
+ catch {
74
+ // Same contract as settle: the tap is total.
75
+ return NO_TAP;
76
+ }
77
+ }
@@ -0,0 +1,6 @@
1
+ /** True when the command touches only temp paths, `$TMPDIR`, paths under
2
+ * `cwd`, and the three allowlisted /dev entries — and at least one of those
3
+ * paths is a literal temp root. A refused lex, an undefined `cwd`, or any
4
+ * unjudgeable token (URL, attached short option, opaque non-`$TMPDIR`, or a
5
+ * relative path after an unresolvable cd) returns false. */
6
+ export declare function isTempOnly(command: string, cwd: string | undefined): boolean;
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Conservative temp-only predicate (US-002).
3
+ *
4
+ * Returns true when every path-like token and redirect target in `command`
5
+ * names a temp path, a `$TMPDIR` expansion that resolves to one, a path
6
+ * under `cwd`, or `/dev/null` / `/dev/stdout` / `/dev/stderr` — AND at
7
+ * least one of them is a temp path. Two unjudgeable shapes force `false`:
8
+ * an attached short-option value containing `/` (e.g. `-C/etc`) and a URL
9
+ * (any token containing `://`).
10
+ *
11
+ * The frame (working-directory proxy) advances through `cd` segments the
12
+ * same way `detectTmpWrite` does, with one difference: when a `cd` target is
13
+ * an opaque word (`$D`) or a `~` reference — anything the lexer did not
14
+ * literalize — the frame becomes unknown, and any later relative path-like
15
+ * token fails the predicate. A refused lex or an undefined `cwd` also
16
+ * fails it.
17
+ *
18
+ * This is a no-ask filter; a `false` return is information only, never a
19
+ * decision.
20
+ */
21
+ import { posix } from "node:path";
22
+ import { lexBashCommand } from "#src/permissions/index";
23
+ /** The literal temp roots a temp path must land in. */
24
+ const TEMP_ROOTS = ["/tmp", "/private/tmp"];
25
+ /** The two opaque variable names accepted as temp path expansions. */
26
+ const TMPDIR_BARE = "$TMPDIR";
27
+ const TMPDIR_BRACE = "$" + "{TMPDIR}";
28
+ /** The three /dev entries allowed as safe redirect targets. */
29
+ const DEV_ALLOWLIST = new Set(["/dev/null", "/dev/stdout", "/dev/stderr"]);
30
+ /** True when `token` looks like a path the predicate must judge:
31
+ * contains a `/`, equals `.` or `..`, or starts with `~`. */
32
+ function isPathLike(token) {
33
+ return token.includes("/") || token === "." || token === ".." || token.startsWith("~");
34
+ }
35
+ /** True when `token` is a shell option flag the predicate must skip past to
36
+ * find a real target: starts with `-` but is not itself path-like and has no
37
+ * `--flag=value` to extract. `cd -P /tmp` has `-P` as the option and `/tmp`
38
+ * as the target. */
39
+ function isOptionFlag(token) {
40
+ if (token === "-")
41
+ return false;
42
+ if (!token.startsWith("-"))
43
+ return false;
44
+ if (isPathLike(token))
45
+ return false;
46
+ if (flagValue(token) !== undefined)
47
+ return false;
48
+ return true;
49
+ }
50
+ /** The judged value of a `--flag=value` token, or undefined when `token` is
51
+ * not of that form. Anything else is judged as-is. */
52
+ function flagValue(token) {
53
+ if (!token.startsWith("--"))
54
+ return undefined;
55
+ const eq = token.indexOf("=");
56
+ return eq === -1 ? undefined : token.slice(eq + 1);
57
+ }
58
+ /** True when `path` is or lies under one of the literal temp roots. */
59
+ function isTempPath(path) {
60
+ return TEMP_ROOTS.some((root) => path === root || path.startsWith(`${root}/`));
61
+ }
62
+ /** True when `path` equals `cwd` or lies under it. */
63
+ function isUnderCwd(path, cwd) {
64
+ return path === cwd || path.startsWith(`${cwd}/`);
65
+ }
66
+ /** True when `text` contains a `..` path segment anywhere — used to refuse
67
+ * `$TMPDIR/../x` shapes that would climb out of the temp root at runtime. */
68
+ function hasParentSegment(text) {
69
+ return text.split("/").includes("..");
70
+ }
71
+ /** True when `token` is exactly `$TMPDIR` / `${TMPDIR}` or one of them
72
+ * followed by `/` and a remainder with no `..` segment. False when it is a
73
+ * different opaque word, undefined when the token is not opaque. */
74
+ function isTmpDirExpansion(token) {
75
+ if (!token.opaque)
76
+ return undefined;
77
+ if (token.text === TMPDIR_BARE || token.text === TMPDIR_BRACE)
78
+ return true;
79
+ if (token.text.startsWith(`${TMPDIR_BARE}/`)) {
80
+ return !hasParentSegment(token.text.slice(TMPDIR_BARE.length + 1));
81
+ }
82
+ if (token.text.startsWith(`${TMPDIR_BRACE}/`)) {
83
+ return !hasParentSegment(token.text.slice(TMPDIR_BRACE.length + 1));
84
+ }
85
+ return false;
86
+ }
87
+ /** True when `token` is an attached short option value the predicate cannot
88
+ * split (`-C/etc`), or a `--flag=value` form. The value, when present, is
89
+ * classified recursively. */
90
+ function shortOptionVerdict(token, frame, cwd) {
91
+ if (!token.text.startsWith("-") || !token.text.includes("/"))
92
+ return undefined;
93
+ if (token.text.startsWith("--")) {
94
+ const value = flagValue(token.text);
95
+ if (value !== undefined)
96
+ return classify({ text: value, opaque: token.opaque }, frame, cwd);
97
+ }
98
+ return "refuse";
99
+ }
100
+ /** True when `token` is an unjudgeable shape (URL, `~` path, opaque word
101
+ * other than `$TMPDIR`). Callers must check `isTmpDirExpansion` first. */
102
+ function unjudgeable(token) {
103
+ if (token.text.includes("://"))
104
+ return true;
105
+ if (token.text.startsWith("~"))
106
+ return true;
107
+ return token.opaque;
108
+ }
109
+ /** Classifies a literal (non-opaque, non-short-option, non-flag) absolute or
110
+ * relative path against the temp-root, /dev allowlist, cwd, and frame. */
111
+ function classifyLiteral(token, frame, cwd) {
112
+ if (token.text.startsWith("/")) {
113
+ const normalized = posix.normalize(token.text);
114
+ if (isTempPath(normalized))
115
+ return "temp";
116
+ if (DEV_ALLOWLIST.has(normalized))
117
+ return "allowed";
118
+ return isUnderCwd(normalized, cwd) ? "allowed" : "refuse";
119
+ }
120
+ if (frame === undefined)
121
+ return "refuse";
122
+ const resolved = posix.normalize(posix.join(frame, token.text));
123
+ if (isTempPath(resolved))
124
+ return "temp";
125
+ return isUnderCwd(resolved, cwd) ? "allowed" : "refuse";
126
+ }
127
+ /** Classifies one path-like `token` (or redirect target) against the rules.
128
+ * Returns `temp` when the path is a temp path (the credit), `allowed` when
129
+ * it is some other safe value (cwd, /dev, `$TMPDIR` expansion), and `refuse`
130
+ * when it cannot be judged or falls outside the safe set. */
131
+ function classify(token, frame, cwd) {
132
+ const short = shortOptionVerdict(token, frame, cwd);
133
+ if (short !== undefined)
134
+ return short;
135
+ const tmpdir = isTmpDirExpansion(token);
136
+ if (tmpdir !== undefined)
137
+ return tmpdir ? "temp" : "refuse";
138
+ if (unjudgeable(token))
139
+ return "refuse";
140
+ return classifyLiteral(token, frame, cwd);
141
+ }
142
+ /** The frame a `cd <target>` segment leaves behind. When the cd target is
143
+ * not a literal, cwd-rooted, or $TMPDIR path the frame is lost. Any option
144
+ * flags the user wrote before the target (`-P`, `-L`, `--`) are skipped, so
145
+ * `cd -P $D` classifies `$D` (opaque, unresolvable) and clears the frame
146
+ * instead of joining a synthetic one from the option word. */
147
+ function frameAfter(segment, frame, cwd) {
148
+ const [command, ...rest] = segment.tokens;
149
+ if (command?.text !== "cd")
150
+ return frame;
151
+ const target = rest.find((token) => !isOptionFlag(token.text));
152
+ if (target === undefined)
153
+ return frame;
154
+ if (target.text === "-")
155
+ return undefined;
156
+ if (classify({ text: target.text, opaque: target.opaque }, frame, cwd) === "refuse")
157
+ return undefined;
158
+ if (target.text.startsWith("/"))
159
+ return posix.normalize(target.text);
160
+ return frame === undefined ? undefined : posix.normalize(posix.join(frame, target.text));
161
+ }
162
+ /** Classifies every path-like token and redirect target in one segment.
163
+ * Returns `true` when any verdict was `temp` (so far). Returns `false` when
164
+ * any verdict was `refuse`; the caller then returns false from the whole
165
+ * predicate. A `--flag=value` token is judged by its VALUE — the flag name
166
+ * hides any `/`, `.`, `..`, or `~` the value contains, so the whole token
167
+ * would not look path-like on its own. */
168
+ function classifySegment(segment, frame, cwd) {
169
+ let hasTempPath = false;
170
+ for (const redirect of segment.redirects) {
171
+ const verdict = classify({ text: redirect.target, opaque: redirect.opaque }, frame, cwd);
172
+ if (verdict === "refuse")
173
+ return { hasTempPath, refused: true };
174
+ if (verdict === "temp")
175
+ hasTempPath = true;
176
+ }
177
+ for (const token of segment.tokens) {
178
+ const value = flagValue(token.text);
179
+ const judgedText = value ?? token.text;
180
+ if (!isPathLike(judgedText))
181
+ continue;
182
+ const verdict = classify({ text: judgedText, opaque: token.opaque }, frame, cwd);
183
+ if (verdict === "refuse")
184
+ return { hasTempPath, refused: true };
185
+ if (verdict === "temp")
186
+ hasTempPath = true;
187
+ }
188
+ return { hasTempPath, refused: false };
189
+ }
190
+ /** True when the command touches only temp paths, `$TMPDIR`, paths under
191
+ * `cwd`, and the three allowlisted /dev entries — and at least one of those
192
+ * paths is a literal temp root. A refused lex, an undefined `cwd`, or any
193
+ * unjudgeable token (URL, attached short option, opaque non-`$TMPDIR`, or a
194
+ * relative path after an unresolvable cd) returns false. */
195
+ export function isTempOnly(command, cwd) {
196
+ if (cwd === undefined || cwd === "")
197
+ return false;
198
+ const lexed = lexBashCommand(command);
199
+ if (lexed.kind === "refused")
200
+ return false;
201
+ const normalizedCwd = posix.normalize(cwd);
202
+ if (normalizedCwd === "" || normalizedCwd === ".")
203
+ return false;
204
+ let frame = normalizedCwd;
205
+ let hasTempPath = false;
206
+ for (const segment of lexed.segments) {
207
+ const outcome = classifySegment(segment, frame, normalizedCwd);
208
+ if (outcome.refused)
209
+ return false;
210
+ if (outcome.hasTempPath)
211
+ hasTempPath = true;
212
+ frame = frameAfter(segment, frame, normalizedCwd);
213
+ }
214
+ return hasTempPath;
215
+ }
@@ -0,0 +1,2 @@
1
+ /** True when the command writes to a literal /tmp or /private/tmp path outside nax's own dirs. */
2
+ export declare function detectTmpWrite(command: string, cwd?: string): boolean;
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Observable literal-temp-write signal (US-005).
3
+ *
4
+ * Records, never decides. True when a command writes to a literal `/tmp` or
5
+ * `/private/tmp` path outside nax's own temp trees — the shared `/tmp/nax/`
6
+ * parent of every run root and the per-user `/tmp/nax-<uid>` fallback — so the
7
+ * frequency of that scratch-write habit is measurable. The caller only stores
8
+ * the boolean on the shadow row: it is not a `QuestionId`, not in
9
+ * `rules.hits`, and changes no verdict.
10
+ *
11
+ * Total by construction: `lexBashCommand` is safe-by-refusal and does no I/O,
12
+ * so a refused lex scans its lexable prefix instead of throwing.
13
+ */
14
+ import { posix } from "node:path";
15
+ import { lexBashCommand } from "#src/permissions/index";
16
+ /** The literal temp roots a flagged write must land in. */
17
+ const TEMP_ROOTS = ["/tmp", "/private/tmp"];
18
+ /** nax's shared temp parent, a directory directly under a temp root. */
19
+ const NAX_PARENT_SEGMENT = "nax";
20
+ /** nax's per-user fallback parent, whose `<uid>` keeps it outside the shared parent's subtree. */
21
+ const NAX_FALLBACK_SEGMENT = "nax-";
22
+ /** First tokens whose later non-flag words are all write targets. */
23
+ const ARG_TARGET_COMMANDS = new Set(["tee", "touch", "mkdir"]);
24
+ /** First tokens whose LAST non-flag word is the write target. */
25
+ const LAST_TARGET_COMMANDS = new Set(["cp", "mv"]);
26
+ /** Every write target of one segment, in the order the lexer saw them. */
27
+ function writeTargets(segment) {
28
+ const targets = [];
29
+ for (const redirect of segment.redirects) {
30
+ // Input redirects (`<`, and the refused `<<` / `<<<`) read; they do not write.
31
+ if (redirect.operator.startsWith("<"))
32
+ continue;
33
+ targets.push({ text: redirect.target, opaque: redirect.opaque });
34
+ }
35
+ const first = segment.tokens[0]?.text;
36
+ if (first !== undefined && ARG_TARGET_COMMANDS.has(first)) {
37
+ for (const token of segment.tokens.slice(1)) {
38
+ if (!token.text.startsWith("-"))
39
+ targets.push(token);
40
+ }
41
+ }
42
+ else if (first !== undefined && LAST_TARGET_COMMANDS.has(first)) {
43
+ const words = segment.tokens.slice(1).filter((token) => !token.text.startsWith("-"));
44
+ const last = words[words.length - 1];
45
+ if (last !== undefined)
46
+ targets.push(last);
47
+ }
48
+ return targets;
49
+ }
50
+ /**
51
+ * The absolute path `target` names under `frame`, or undefined when it cannot
52
+ * be read literally: an opaque (expansion-bearing) word, a `~` home reference,
53
+ * or a relative word with no known frame.
54
+ */
55
+ function resolveTarget(target, frame) {
56
+ if (target.opaque || target.text.startsWith("~"))
57
+ return undefined;
58
+ if (target.text.startsWith("/"))
59
+ return posix.normalize(target.text);
60
+ return frame === undefined ? undefined : posix.normalize(posix.join(frame, target.text));
61
+ }
62
+ /** True when `path` lies in nax's own temp tree under `root`, never counted.
63
+ * Two trees are exempt: the shared parent `<root>/nax/` (parent of every run
64
+ * root) and the per-user fallback `<root>/nax-<digits>` whose `<digits>` is
65
+ * the WHOLE path segment after `nax-` (the literal UID in `/tmp/nax-<uid>`).
66
+ * Anything else — `/tmp/nax-red-check`, `/tmp/nax-r1/...`, `/tmp/nax-501x`
67
+ * — is a real write, because the confined sandbox denies writes to those
68
+ * directories and they are user- or run-named, not nax-owned. */
69
+ function isNaxTempTree(path, root) {
70
+ if (path.startsWith(`${root}/${NAX_PARENT_SEGMENT}/`))
71
+ return true;
72
+ const fallbackPrefix = `${root}/${NAX_FALLBACK_SEGMENT}`;
73
+ if (!path.startsWith(fallbackPrefix))
74
+ return false;
75
+ const afterDash = path.slice(fallbackPrefix.length);
76
+ const firstSlash = afterDash.indexOf("/");
77
+ const segment = firstSlash === -1 ? afterDash : afterDash.slice(0, firstSlash);
78
+ return /^\d+$/.test(segment);
79
+ }
80
+ /** True when `path` is a temp root or lies under one, but not under nax's own subtree. */
81
+ function isTmpPath(path) {
82
+ for (const root of TEMP_ROOTS) {
83
+ if (path !== root && !path.startsWith(`${root}/`))
84
+ continue;
85
+ return !isNaxTempTree(path, root);
86
+ }
87
+ return false;
88
+ }
89
+ /** The frame a `cd <target>` segment leaves behind; otherwise the frame unchanged. */
90
+ function frameAfter(segment, frame) {
91
+ const [command, target] = segment.tokens;
92
+ if (command?.text !== "cd" || target === undefined)
93
+ return frame;
94
+ return resolveTarget(target, frame) ?? frame;
95
+ }
96
+ /** True when the command writes to a literal /tmp or /private/tmp path outside nax's own dirs. */
97
+ export function detectTmpWrite(command, cwd) {
98
+ const lexed = lexBashCommand(command);
99
+ // A refused lex still exposes the part it read: the gate refuses on the
100
+ // unreadable construct, not on the prefix before it.
101
+ const segments = lexed.kind === "ok" ? lexed.segments : lexed.prefix;
102
+ let frame = cwd === undefined ? undefined : posix.normalize(cwd);
103
+ for (const segment of segments) {
104
+ for (const target of writeTargets(segment)) {
105
+ const resolved = resolveTarget(target, frame);
106
+ if (resolved !== undefined && isTmpPath(resolved))
107
+ return true;
108
+ }
109
+ frame = frameAfter(segment, frame);
110
+ }
111
+ return false;
112
+ }