@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,369 @@
1
+ /**
2
+ * The native session adapter: openSession / sendTurn / closeSession over
3
+ * nax-ai, no subprocess. openSession/closeSession are transcript-file
4
+ * bookkeeping, and sendTurn maps the native turn loop (session/turn-loop.ts)
5
+ * over complete() — nax owns the conversation because nax-ai's client is
6
+ * stateless (ADR-027 section 10, ADR-028).
7
+ */
8
+ import { randomUUID } from "node:crypto";
9
+ import { priceCall } from "#src/cost/core/index";
10
+ import { getSafeLogger } from "#src/infra/index";
11
+ import { SessionTurnError, } from "#src/session/session-types";
12
+ import { createTurnDeadline } from "#src/session/turn-deadline";
13
+ import { _adapterDeps, authFields, isProtocolStreamError } from "./adapter-deps.js";
14
+ import { getNativeClient } from "./client.js";
15
+ import { toAdapterFailure } from "./errors.js";
16
+ import { buildRateCard, NATIVE_AGENT, parseNativeModel, resolveContextWindow, toThinkingLevel } from "./models.js";
17
+ import { closeNativeSession, markNativeTurnOutcome, nativeSessionCompaction, nativeSessionSpinBreaker, nativeSessionStreamHooks, nativeSessionTimeouts, nativeSessionTranscriptOwners, nativeSessionTransportRetry, openNativeSession, } from "./session/session.js";
18
+ import { buildNativeStreamEvent } from "./session/turn-events.js";
19
+ import { runNativeTurn } from "./session/turn-loop.js";
20
+ import { readNativeTurnFailureUsage } from "./session/turn-types.js";
21
+ import { nativeSessionId } from "./session-affinity.js";
22
+ /**
23
+ * Fallback whole-turn budget when a session's timeout entry is missing.
24
+ *
25
+ * Matches `execution.sessionTimeoutSeconds`' own default. The turn MUST stay
26
+ * bounded: an absent entry previously degraded to an unbounded deadline with
27
+ * no per-call timer either, silently removing the guard this module exists to
28
+ * apply. Bounded-and-logged beats unbounded-and-quiet.
29
+ */
30
+ const FALLBACK_TURN_TIMEOUT_SECONDS = 3600;
31
+ function summaryPrompt(previousSummary) {
32
+ const base = "Summarize the conversation above so it can be dropped from context. " +
33
+ "Record what was attempted, what was rejected and why, any decisions that still bind, " +
34
+ "and list the files read and the files modified. Be specific: this summary is the only " +
35
+ "memory of this work that survives.";
36
+ if (previousSummary === undefined)
37
+ return base;
38
+ return (`${base}\n\nAn earlier summary of still-older history follows. Merge it into your summary ` +
39
+ `rather than repeating or discarding it:\n\n${previousSummary}`);
40
+ }
41
+ /**
42
+ * US-003: the plugin-contributed loop handlers a turn carries, in the shape
43
+ * `runNativeTurn` takes them in. Built here rather than inline in `sendTurn`
44
+ * because those two conditional spreads are what pushed that method past its
45
+ * recorded cognitive complexity; each field is omitted entirely when the
46
+ * caller supplied none, so a session that loaded no `loop-handlers` plugin
47
+ * carries neither key.
48
+ */
49
+ function loopHandlerDeps(opts) {
50
+ return {
51
+ ...(opts.loopHandlers !== undefined ? { loopHandlers: opts.loopHandlers } : {}),
52
+ ...(opts.loopHandlerContext !== undefined ? { loopHandlerContext: opts.loopHandlerContext } : {}),
53
+ };
54
+ }
55
+ export class NativeSessionAdapter {
56
+ catalogOverrides;
57
+ constructor(catalogOverrides = []) {
58
+ this.catalogOverrides = catalogOverrides;
59
+ }
60
+ /**
61
+ * Can this agent authenticate to at least one provider?
62
+ *
63
+ * Deliberately not "is the provider this run needs satisfied": this method
64
+ * takes no provider, and it cannot get one. The registry receives the
65
+ * manager's config slice, and agentManagerConfigSelector excludes
66
+ * config.models by design (ADR-019). Probing every provider for a specific
67
+ * answer is not an alternative either — pi's resolve() may execute commands.
68
+ *
69
+ * So this prunes exactly one case: nothing stored anywhere and nothing
70
+ * ambient. That is the real failure — a user who has never run
71
+ * `nax auth login` and has no provider environment variables — and every
72
+ * native call is going to fail anyway. A wrong-provider credential still
73
+ * surfaces per request, through the typed mapping from ProtocolError.kind
74
+ * "auth" to availability / fail-auth.
75
+ *
76
+ * US-004: with `auth.source: "exec"` it answers true without asking anything.
77
+ * A helper's providers cannot be listed, so an empty credential file says
78
+ * nothing about whether this run can authenticate — and spawning the helper
79
+ * here would put a credential read before the run has any reason for one.
80
+ *
81
+ * Errors resolve to true. Pruning an agent that would have worked kills a
82
+ * run; the opposite costs one request-time error that is already handled.
83
+ */
84
+ async hasCredentials() {
85
+ try {
86
+ if (await _adapterDeps.authSourceIsExec())
87
+ return true;
88
+ if ((await _adapterDeps.listStoredProviders()).length > 0)
89
+ return true;
90
+ return await _adapterDeps.anyAmbientCredential();
91
+ }
92
+ catch {
93
+ return true;
94
+ }
95
+ }
96
+ openSession(name, opts) {
97
+ return openNativeSession(name, opts);
98
+ }
99
+ async sendTurn(handle, prompt, opts) {
100
+ const { provider, model, effort } = parseNativeModel(handle.modelDef?.model ?? "");
101
+ const thinking = toThinkingLevel(effort);
102
+ const client = await getNativeClient(this.catalogOverrides);
103
+ const resolved = await client.model(provider, model);
104
+ const catalog = client.pricing(resolved);
105
+ const { rates, source: pricingSource } = buildRateCard(catalog, handle.modelDef?.pricing);
106
+ const storedTimeoutSeconds = nativeSessionTimeouts.get(handle.id);
107
+ if (storedTimeoutSeconds === undefined) {
108
+ getSafeLogger()?.warn("native-adapter", "session has no recorded timeout; falling back to the default budget", {
109
+ sessionName: handle.id,
110
+ fallbackTimeoutSeconds: FALLBACK_TURN_TIMEOUT_SECONDS,
111
+ });
112
+ }
113
+ const timeoutSeconds = storedTimeoutSeconds ?? FALLBACK_TURN_TIMEOUT_SECONDS;
114
+ // Keyed on the session, so every turn of one conversation carries the same
115
+ // id and the provider can keep its cache warm across them.
116
+ const sessionId = nativeSessionId(handle.id);
117
+ // One budget for the whole turn, not one per round-trip. Created here
118
+ // because this is where `timeoutSeconds` is known; consulted by the loop.
119
+ const deadline = createTurnDeadline(timeoutSeconds);
120
+ const hooks = nativeSessionStreamHooks.get(handle.id);
121
+ // One callId per turn, mirroring SpawnAcpSession.prompt(). `runId` is
122
+ // backfilled by the runtime's forwarding closure, which is the only place
123
+ // that knows it — see runtime/index.ts.
124
+ const callId = randomUUID();
125
+ // The transcript/ledger join key, not a stream id. `nativeSessionTranscriptOwners`
126
+ // is keyed on this same `handle.id` and already holds the session's
127
+ // `transcriptOwner` (`scopeId ?? callId` from session-run-hop.ts). Omitted
128
+ // entirely when the session declared no owner, so an absent key reads as
129
+ // "unknown" rather than as a wrong one.
130
+ const owner = nativeSessionTranscriptOwners.get(handle.id);
131
+ const eventBase = {
132
+ callId,
133
+ runId: "",
134
+ agentName: handle.agentName,
135
+ sessionName: handle.id,
136
+ ...(owner !== undefined ? { scopeId: owner } : {}),
137
+ };
138
+ const turnController = new AbortController();
139
+ // The watchdog's cancel handle IS the turn controller, so an idle cancel
140
+ // and the whole-turn deadline end the same in-flight call. Registering
141
+ // through the hook (rather than a private registry) is what lets
142
+ // sendPrompt tell a watchdog cancel from an unrelated process kill.
143
+ hooks?.onActiveCall?.(callId, async () => turnController.abort());
144
+ hooks?.onStreamActivity?.({
145
+ ...eventBase,
146
+ kind: "agent.call_started",
147
+ model: handle.modelDef?.model ?? "",
148
+ timeoutSeconds,
149
+ timestamp: Date.now(),
150
+ });
151
+ // US-002: one per-turn signal, fanned in from the caller's
152
+ // `opts.signal`, the watchdog's `turnController.signal`, and a
153
+ // whole-turn deadline timer armed with `deadline.remainingMs()`. The
154
+ // timer is cleared at turn settlement (finally, below) so the AbortSignal
155
+ // any(...) does not keep a timer alive past the turn's end. Complete and
156
+ // summarize retain their per-call timers but combine them with this same
157
+ // turnSignal so a turn-cancel reaches an in-flight call mid-roundtrip.
158
+ const deadlineController = new AbortController();
159
+ const deadlineMs = deadline.remainingMs();
160
+ const deadlineTimer = deadlineMs !== undefined ? _adapterDeps.setTimeout(() => deadlineController.abort(), deadlineMs) : undefined;
161
+ const turnSignals = [turnController.signal, deadlineController.signal];
162
+ if (opts.signal !== undefined)
163
+ turnSignals.unshift(opts.signal);
164
+ const turnSignal = AbortSignal.any(turnSignals);
165
+ let result;
166
+ try {
167
+ // US-002: clear the deadline timer at turn settlement, no matter how
168
+ // the turn ends (clean exit, throw, or any other path). The
169
+ // `AbortSignal.any` above holds a reference to `deadlineController.signal`,
170
+ // so a settled turn must drop its timer to avoid keeping it armed past
171
+ // the turn boundary.
172
+ result = await runNativeTurn(handle, prompt, opts, {
173
+ deadline,
174
+ contextWindow: resolveContextWindow(handle.modelDef?.contextWindow, resolved.contextWindow),
175
+ ...(nativeSessionCompaction.get(handle.id) !== undefined
176
+ ? { compaction: nativeSessionCompaction.get(handle.id) }
177
+ : {}),
178
+ ...(nativeSessionTransportRetry.get(handle.id) !== undefined
179
+ ? { transportRetry: nativeSessionTransportRetry.get(handle.id) }
180
+ : {}),
181
+ ...(nativeSessionSpinBreaker.get(handle.id) !== undefined
182
+ ? { spinBreaker: nativeSessionSpinBreaker.get(handle.id) }
183
+ : {}),
184
+ ...(opts.loopEvents !== undefined ? { loopEvents: opts.loopEvents } : {}),
185
+ // US-003: the run's plugin-contributed loop handlers and the facts
186
+ // their handlers read travel the same way as `loopEvents` — both are
187
+ // per-turn inputs the loop installs onto its registry rather than
188
+ // state the adapter owns.
189
+ ...loopHandlerDeps(opts),
190
+ // US-002: the one per-turn signal threaded into the batch and the
191
+ // in-flight coding-tool runtime. When `opts.signal` and the watchdog
192
+ // and the deadline are all absent, `turnSignal` is a non-aborted
193
+ // composite that behaves as the no-signal regression guard requires.
194
+ signal: turnSignal,
195
+ // US-002 AC14: forward an onWaiting callback the batch can hand to
196
+ // every coding-tool request. Wired in US-004 to emit the keepalive
197
+ // activity; today it is a no-op so the field plumbing is real in the
198
+ // production native path even though the keepalive is not yet
199
+ // active. The handler copies it onto the tool's `ToolCallContext`
200
+ // and a tool that blocks on a human approval calls it before the wait.
201
+ onWaiting: () => { },
202
+ pricingSource,
203
+ onActivity: (activity) => {
204
+ hooks?.onStreamActivity?.(buildNativeStreamEvent(eventBase, activity, Date.now()));
205
+ },
206
+ summarize: async (span, previousSummary) => {
207
+ // Same model, same clock, no tools. The prompt asks for what a coding
208
+ // agent needs back: what was tried, what was rejected and why, and the
209
+ // files touched -- without them the agent re-reads what it already read.
210
+ const remainingMs = deadline.remainingMs();
211
+ const controller = new AbortController();
212
+ const timer = remainingMs !== undefined ? _adapterDeps.setTimeout(() => controller.abort(), remainingMs) : undefined;
213
+ // US-002: the per-call signal still combines with the per-turn signal
214
+ // (watchdog + deadline + caller), so a turn cancel ends the summary
215
+ // even mid-call. The per-call timer is the additional budget on top.
216
+ const signal = AbortSignal.any(opts.signal !== undefined
217
+ ? [opts.signal, controller.signal, turnController.signal, deadlineController.signal]
218
+ : [controller.signal, turnController.signal, deadlineController.signal]);
219
+ try {
220
+ const res = await client.complete(resolved, {
221
+ messages: [...span, { role: "user", content: summaryPrompt(previousSummary) }],
222
+ sessionId,
223
+ signal,
224
+ });
225
+ const summaryUsage = res.usage;
226
+ const { costUsd, resolvedRates } = priceCall(summaryUsage, rates);
227
+ return { text: res.text, usage: summaryUsage, costUsd, rates: resolvedRates };
228
+ }
229
+ finally {
230
+ if (timer !== undefined)
231
+ _adapterDeps.clearTimeout(timer);
232
+ }
233
+ },
234
+ complete: async (messages, tools, requestOptions) => {
235
+ // The controller is armed with what is LEFT of the turn, so N
236
+ // round-trips can no longer add up to N x timeoutSeconds. Still
237
+ // combined with any caller-supplied opts.signal via AbortSignal.any so
238
+ // either can end the call. US-002: also combined with the per-turn
239
+ // turnController + deadlineController signals so an in-flight call
240
+ // observes the same cancellation the batch sees between calls.
241
+ const remainingMs = deadline.remainingMs();
242
+ const controller = new AbortController();
243
+ const timer = remainingMs !== undefined ? _adapterDeps.setTimeout(() => controller.abort(), remainingMs) : undefined;
244
+ const signal = AbortSignal.any(opts.signal !== undefined
245
+ ? [opts.signal, controller.signal, turnController.signal, deadlineController.signal]
246
+ : [controller.signal, turnController.signal, deadlineController.signal]);
247
+ // The loop-event bag speaks in booleans: `false` explicitly drops
248
+ // the session's inherited thinking level for this request, while
249
+ // `true` leaves that resolved level in place. nax-ai accepts a
250
+ // thinking LEVEL rather than a boolean, so normalize at this boundary.
251
+ const requestThinking = requestOptions?.thinking === false ? undefined : thinking;
252
+ try {
253
+ const res = await client.complete(resolved, {
254
+ messages,
255
+ ...(tools.length > 0 ? { tools } : {}),
256
+ sessionId,
257
+ signal,
258
+ ...(requestThinking !== undefined ? { thinking: requestThinking } : {}),
259
+ ...(requestOptions?.temperature !== undefined ? { temperature: requestOptions.temperature } : {}),
260
+ // nax#1835: "short" is fixed, not config-driven (this repo's
261
+ // precedent -- the compaction design -- rejects knobs added
262
+ // before evidence). The turn loop's round trips are seconds
263
+ // apart, so "short" already hits; "long" would only pay off for
264
+ // a later turn and bills more at write time for a window this
265
+ // turn does not need. Only this round-trip closure sets it: the
266
+ // one-shot complete() and the summarize closure below have no
267
+ // successor turn (or, for summarize, a shape unlikely to repeat)
268
+ // to reuse the entry, so a cache write there costs more than it
269
+ // saves.
270
+ cacheRetention: "short",
271
+ });
272
+ const usage = res.usage;
273
+ // Single `priceCall` invocation: `costUsd` and `resolvedRates`
274
+ // come from the same call so they cannot diverge — the
275
+ // verifiability property the story names ("recorded rates
276
+ // reproduce recorded cost") would silently break if tier
277
+ // selection ever grew a side channel.
278
+ const { costUsd, resolvedRates } = priceCall(usage, rates);
279
+ return {
280
+ text: res.text,
281
+ ...(res.toolCalls !== undefined ? { toolCalls: res.toolCalls } : {}),
282
+ ...(res.thinking !== undefined ? { thinking: res.thinking } : {}),
283
+ usage,
284
+ costUsd,
285
+ // US-002: thread the per-call resolved rates back to
286
+ // `runNativeTurn` so it can stamp them on the `TurnResult`.
287
+ // The last round-trip's rates win — each round-trip re-prices
288
+ // on its own usage, and the most recent decision is what
289
+ // affected the user's last-mile spend.
290
+ rates: resolvedRates,
291
+ };
292
+ }
293
+ finally {
294
+ if (timer !== undefined)
295
+ _adapterDeps.clearTimeout(timer);
296
+ }
297
+ },
298
+ });
299
+ }
300
+ catch (err) {
301
+ hooks?.onStreamActivity?.({
302
+ ...eventBase,
303
+ kind: "agent.call_ended",
304
+ status: turnController.signal.aborted ? "cancelled" : "error",
305
+ timestamp: Date.now(),
306
+ });
307
+ markNativeTurnOutcome(handle.id, true);
308
+ // The same treatment complete() gives a protocol fault, on the path that
309
+ // was missing it (nax#1838). Rethrowing untouched left build-hop-callback
310
+ // to synthesise a generic fail-adapter-error, which cost a rate limit its
311
+ // backoff and an auth failure its unavailable mark.
312
+ //
313
+ // nax#1840: classification and cost are read off two different error
314
+ // classes, and a throw can only be one. SessionTurnError is the carrier
315
+ // both hop callbacks already read cost off, so it now also carries the
316
+ // classification (its optional adapterFailure field) — one throw, both
317
+ // facts. runNativeTurn attaches whatever was already spent on earlier
318
+ // round trips to this same err before it reaches here; read it back
319
+ // rather than dropping it as the pre-#1840 SessionFailureError did.
320
+ //
321
+ // Only a protocol fault is wrapped. A TypeError from our own code is not a
322
+ // vendor failure, and dressing it as one would hide the bug.
323
+ if (isProtocolStreamError(err)) {
324
+ const adapterFailure = toAdapterFailure(err.protocolError);
325
+ const usage = readNativeTurnFailureUsage(err);
326
+ throw new SessionTurnError(err.protocolError.message, false, adapterFailure.retriable, usage?.tokenUsage, usage?.costUsd, undefined,
327
+ // pricingSource — the native protocol-fault path has no rate card
328
+ // to name here; left undefined (SessionTurnError.pricingSource).
329
+ undefined, adapterFailure);
330
+ }
331
+ throw err;
332
+ }
333
+ finally {
334
+ if (deadlineTimer !== undefined)
335
+ _adapterDeps.clearTimeout(deadlineTimer);
336
+ }
337
+ hooks?.onStreamActivity?.({
338
+ ...eventBase,
339
+ kind: "agent.call_ended",
340
+ status: result.timedOut === true ? "timeout" : turnController.signal.aborted ? "cancelled" : "success",
341
+ timestamp: Date.now(),
342
+ });
343
+ markNativeTurnOutcome(handle.id, false);
344
+ // US-006: the store only observes `provider` while a request is in flight,
345
+ // so the stamp is read here, after the loop.
346
+ return { ...result, ...authFields(provider) };
347
+ }
348
+ closeSession(handle) {
349
+ // The adapter interface has no failure signal, so the verdict comes from the
350
+ // session's last turn (markNativeTurnOutcome) rather than from this call.
351
+ // Passing a literal false here is what deleted the transcript of a failed
352
+ // session -- the one the retry reloads and a human reads (nax#1838).
353
+ return closeNativeSession(handle);
354
+ }
355
+ /**
356
+ * Run teardown addresses a session the process no longer has a handle for,
357
+ * by id string (execution/session-manager-runtime.ts). Without this the
358
+ * native maps were unreachable at teardown: every `keepOpen` session was
359
+ * removed from SessionManager._sessions by closeStory while its nine entries
360
+ * stayed behind for the process lifetime.
361
+ *
362
+ * The ACP contract takes a handle string and closeNativeSession takes a
363
+ * SessionHandle, but the maps are keyed by the session-name string both
364
+ * carry, so clear by name rather than synthesising a handle.
365
+ */
366
+ async closePhysicalSession(handle, _workdir, _options) {
367
+ return closeNativeSession({ id: handle, agentName: NATIVE_AGENT });
368
+ }
369
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Session identity for native requests.
3
+ *
4
+ * nax owns what a session *is*; nax-ai owns how one reaches a provider. So this
5
+ * file derives an id and stops there — which header carries it, and for which
6
+ * vendor, is nax-ai's business (`vendorSessionHeaders`, plus everything pi-ai
7
+ * already derives from a per-model affinity format and the prompt-cache key).
8
+ * An earlier revision kept that mapping here, which meant nax had learned a
9
+ * vendor vocabulary it has no reason to know.
10
+ *
11
+ * The id is a hash, never the session name. nax builds session ids from role,
12
+ * story and feature ("implementer-US-001-auth-system"), so sending one raw
13
+ * would hand a third party the shape of a private repository's work for no
14
+ * gain — a provider needs an opaque token it can match, and nothing else.
15
+ * Hashing also makes the value safe to put on the wire by construction,
16
+ * whatever the id happened to contain.
17
+ */
18
+ /**
19
+ * The session id, independent of who will serve the request.
20
+ *
21
+ * Deliberately not memoised: it is a hash of its input, so it is already stable
22
+ * for a given session, and a cache keyed on session ids would outlive the runs
23
+ * that created them.
24
+ */
25
+ export declare function nativeSessionId(sessionKey: string): string;
26
+ /**
27
+ * A fresh key for a caller that has no session id of its own.
28
+ *
29
+ * `complete()` is one call with no successor, so there is nothing to name. A
30
+ * fresh key per *call* would satisfy the letter of it and defeat the purpose,
31
+ * since affinity only pays when two requests share an id. The adapter holds one
32
+ * of these for its own lifetime, which the agent registry's adapter cache
33
+ * scopes to a run, so the one-shots of a run share a backend while unrelated
34
+ * runs do not.
35
+ */
36
+ export declare function newSessionKey(): string;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Session identity for native requests.
3
+ *
4
+ * nax owns what a session *is*; nax-ai owns how one reaches a provider. So this
5
+ * file derives an id and stops there — which header carries it, and for which
6
+ * vendor, is nax-ai's business (`vendorSessionHeaders`, plus everything pi-ai
7
+ * already derives from a per-model affinity format and the prompt-cache key).
8
+ * An earlier revision kept that mapping here, which meant nax had learned a
9
+ * vendor vocabulary it has no reason to know.
10
+ *
11
+ * The id is a hash, never the session name. nax builds session ids from role,
12
+ * story and feature ("implementer-US-001-auth-system"), so sending one raw
13
+ * would hand a third party the shape of a private repository's work for no
14
+ * gain — a provider needs an opaque token it can match, and nothing else.
15
+ * Hashing also makes the value safe to put on the wire by construction,
16
+ * whatever the id happened to contain.
17
+ */
18
+ import { createHash, randomUUID } from "node:crypto";
19
+ /** 128 bits of a SHA-256, far past collision concerns for routing. */
20
+ const ID_LENGTH = 32;
21
+ /**
22
+ * The session id, independent of who will serve the request.
23
+ *
24
+ * Deliberately not memoised: it is a hash of its input, so it is already stable
25
+ * for a given session, and a cache keyed on session ids would outlive the runs
26
+ * that created them.
27
+ */
28
+ export function nativeSessionId(sessionKey) {
29
+ return createHash("sha256").update(sessionKey).digest("hex").slice(0, ID_LENGTH);
30
+ }
31
+ /**
32
+ * A fresh key for a caller that has no session id of its own.
33
+ *
34
+ * `complete()` is one call with no successor, so there is nothing to name. A
35
+ * fresh key per *call* would satisfy the letter of it and defeat the purpose,
36
+ * since affinity only pays when two requests share an id. The adapter holds one
37
+ * of these for its own lifetime, which the agent registry's adapter cache
38
+ * scopes to a run, so the one-shots of a run share a backend while unrelated
39
+ * runs do not.
40
+ */
41
+ export function newSessionKey() {
42
+ return randomUUID();
43
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The two config fields this reads, declared here so the move set needs no nax
3
+ * config type (S1 spec section 4.2, port 4). nax's `PrecheckConfig` satisfies it.
4
+ */
5
+ export interface NativeTierConfig {
6
+ readonly agent?: {
7
+ readonly native?: {
8
+ readonly catalogOverrides?: readonly {
9
+ readonly provider: string;
10
+ }[];
11
+ };
12
+ };
13
+ readonly models?: Readonly<Record<string, Readonly<Record<string, string | {
14
+ readonly model: string;
15
+ } | undefined>> | undefined>>;
16
+ }
17
+ /** provider -> tiers, for the `models.native` map, skipping catalog-override providers. */
18
+ export declare function nativeTierProviders(config: NativeTierConfig): Map<string, string[]>;
@@ -0,0 +1,20 @@
1
+ import { NATIVE_AGENT } from "./models.js";
2
+ /** The provider prefix of a native id, or undefined when it has none (never guessed). */
3
+ function providerOf(id) {
4
+ const slash = id.indexOf("/");
5
+ return slash > 0 ? id.slice(0, slash) : undefined;
6
+ }
7
+ /** provider -> tiers, for the `models.native` map, skipping catalog-override providers. */
8
+ export function nativeTierProviders(config) {
9
+ const overridden = new Set((config.agent?.native?.catalogOverrides ?? []).map((override) => override.provider));
10
+ const byProvider = new Map();
11
+ for (const [tier, entry] of Object.entries(config.models?.[NATIVE_AGENT] ?? {})) {
12
+ if (entry === undefined)
13
+ continue;
14
+ const provider = providerOf(typeof entry === "string" ? entry : entry.model);
15
+ if (provider === undefined || overridden.has(provider))
16
+ continue;
17
+ byProvider.set(provider, [...(byProvider.get(provider) ?? []), tier]);
18
+ }
19
+ return byProvider;
20
+ }
@@ -0,0 +1,10 @@
1
+ import type { AskDecidedBy } from "./ask-chain.ts";
2
+ import type { AskRequest } from "./types.ts";
3
+ export interface ApprovalAuditRow {
4
+ readonly request: AskRequest;
5
+ readonly decision: "allow" | "deny";
6
+ readonly decidedBy: AskDecidedBy;
7
+ readonly latencyMs: number;
8
+ readonly at: string;
9
+ }
10
+ export declare function appendApprovalAudit(dir: string, runId: string, row: ApprovalAuditRow): Promise<void>;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Corpus of resolved permission asks (P2 design 7.2).
3
+ *
4
+ * One row is appended for EVERY resolved ask -- allow or deny, whichever link
5
+ * answered. `decidedBy` distinguishes a human decision from the rest (cache,
6
+ * model, timeout, unavailable, unshowable), so P5's classifier can select the
7
+ * human-labelled ground-truth rows -- from the actual decision-maker, on the
8
+ * real distribution -- out of the same file. P5's design records that such a
9
+ * corpus does not exist; writing it now means P5 starts with labels rather
10
+ * than bootstrapping them.
11
+ *
12
+ * One JSON object per line, mirroring finish-audit (src/finish/audit.ts:42).
13
+ * String leaves pass through redactRowStrings so a secret in the request never
14
+ * reaches the local corpus.
15
+ */
16
+ import { appendFile, mkdir } from "node:fs/promises";
17
+ import { join } from "node:path";
18
+ import { redactRowStrings } from "./secret-spans.js";
19
+ export async function appendApprovalAudit(dir, runId, row) {
20
+ await mkdir(dir, { recursive: true });
21
+ await appendFile(join(dir, `${runId}.jsonl`), `${JSON.stringify(redactRowStrings(row))}\n`, "utf8");
22
+ }
@@ -0,0 +1,20 @@
1
+ import type { AskLink } from "./ask-chain.ts";
2
+ export declare function createApprovalsLink(opts: {
3
+ readonly approvalsFile: string;
4
+ readonly repoRoot: string;
5
+ /**
6
+ * The project root (`ctx.projectDir`). An entry is honoured only when its
7
+ * `root` is this root or lies inside it, as worktrees and package dirs do.
8
+ */
9
+ readonly projectRoot: string;
10
+ /** Every stage's resolved bashApproval mode in this run. */
11
+ readonly stageModes: readonly string[];
12
+ /**
13
+ * `execution.sandbox.enabled` (P4). With the sandbox enabled a raw stage is
14
+ * either wrapped -- and the approvals file is ALWAYS in its denyWrite, even
15
+ * when outputDir puts it inside a write root -- or refused outright, so it
16
+ * cannot forge this file DURING THIS RUN. What earlier runs wrote is
17
+ * precondition 3's job. Config-only by design: no dependency on the probe.
18
+ */
19
+ readonly sandboxEnabled: boolean;
20
+ }): AskLink;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * The approvals cache as the FIRST link of the ask chain (P2 design 4.3, 6.6).
3
+ *
4
+ * TRUST BOUNDARY. Living outside repoRoot protects this file from the TYPED
5
+ * path-bearing tools, but NOT from Bash: nax-owned-writes.ts:52-58 excludes
6
+ * Bash by design, and under `raw` mode screenRawBashCommand's protectedHit
7
+ * skips every path outside the root (policy-bash-raw.ts:84), so the file is
8
+ * never screened. A raw shell can forge entries.
9
+ *
10
+ * That is not a new vulnerability -- a raw shell needs no forged permission to
11
+ * run a command -- but it IS a cross-stage escalation in a MIXED-mode run,
12
+ * where a raw stage poisons the cache an escalate stage later trusts. Hence
13
+ * precondition 1. P4's sandbox closes the underlying hole when enabled: see
14
+ * `sandboxEnabled`.
15
+ *
16
+ * The file is project-scoped and OUTLIVES the run, so precondition 1 covers
17
+ * only THIS run: an EARLIER forge-capable run could have written it (#2199).
18
+ * Precondition 3 covers that history: a tainted store is never trusted (see
19
+ * approvals-taint.ts, which also states the residual risk). Entry `root` is
20
+ * compared with the project root too, but only as hygiene -- a forger
21
+ * controls `root`.
22
+ *
23
+ * Every precondition fails by ABSTAINING, which escalates to the human, so a
24
+ * failure costs prompts rather than safety.
25
+ */
26
+ import { relative, resolve } from "node:path";
27
+ import { getSafeLogger } from "#src/infra/index";
28
+ import { findApproval, readApprovalsFile } from "./approvals-store.js";
29
+ import { isForgeCapable } from "./approvals-taint.js";
30
+ const ABSTAIN = { decision: "abstain", decidedBy: "cache" };
31
+ function insideRepo(repoRoot, file) {
32
+ const rel = relative(resolve(repoRoot), resolve(file));
33
+ return rel !== "" && !rel.startsWith("..");
34
+ }
35
+ export function createApprovalsLink(opts) {
36
+ const rawStage = isForgeCapable(opts.stageModes, opts.sandboxEnabled);
37
+ const inRepo = insideRepo(opts.repoRoot, opts.approvalsFile);
38
+ const disabled = rawStage || inRepo;
39
+ if (disabled) {
40
+ getSafeLogger()?.warn("permissions", "[approvals] cache disabled; every ask reaches the human", {
41
+ reason: rawStage
42
+ ? "a stage resolves to bashApproval:raw without the sandbox, which can forge this file"
43
+ : "approvals file is inside repoRoot",
44
+ approvalsFile: opts.approvalsFile,
45
+ });
46
+ }
47
+ return {
48
+ name: "approvals-cache",
49
+ async resolve(req) {
50
+ if (disabled)
51
+ return ABSTAIN;
52
+ if (req.command === undefined)
53
+ return ABSTAIN;
54
+ // Read per ask: a concurrent forge-capable run can taint the store mid-run.
55
+ const store = await readApprovalsFile(opts.approvalsFile);
56
+ if (store.taint !== undefined)
57
+ return ABSTAIN;
58
+ const hit = findApproval(store.entries, req.stage, req.command, opts.projectRoot);
59
+ return hit === undefined ? ABSTAIN : { decision: "allow", decidedBy: "cache" };
60
+ },
61
+ };
62
+ }