@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,128 @@
1
+ /**
2
+ * The two compaction steps of the native turn loop.
3
+ *
4
+ * `runProactiveCompaction` runs before a round trip when the estimate crosses
5
+ * the threshold; `runOverflowCompaction` is the backstop when a round trip
6
+ * throws a context overflow anyway. They are deliberately TWO functions, not one
7
+ * with a `reactive` flag, because they differ in exactly three ways a boolean
8
+ * would hide:
9
+ *
10
+ * 1. the backstop halves the keep budget (`keepBudget(..., true)`);
11
+ * 2. a summarizer throw is swallowed by the proactive step (the request may
12
+ * still fit) and propagates out of the backstop;
13
+ * 3. only the proactive step logs its progress (Finding 2, whole-branch
14
+ * review 2026-09-04);
15
+ * 4. a `before_compaction` decline is HONOURED proactively — the branch runs
16
+ * before any request, so sending uncompacted is a legitimate outcome — and
17
+ * IGNORED + logged at overflow, where the request has already failed with
18
+ * a context overflow and declining would leave no recovery (spec 6.2).
19
+ *
20
+ * They share only the summarize-and-apply core below. The failure semantics
21
+ * stay with the callers on purpose — see each function's catch.
22
+ */
23
+ import { getSafeLogger } from "#src/infra/index";
24
+ import { applyCompaction, estimateContextTokens, keepBudget, prepareCompaction, } from "./compaction.js";
25
+ import { usageBeat } from "./turn-accumulator.js";
26
+ /**
27
+ * The half both branches share: summarize the dropped span, then apply the
28
+ * summary to the transcript. A throw here is left to propagate — catching it is
29
+ * the proactive caller's decision, not this function's.
30
+ */
31
+ async function summarizeAndApply(messages, plan, deps, source = {}) {
32
+ const summary = source.replacementSummary !== undefined
33
+ ? { text: source.replacementSummary, usage: { inputTokens: 0, outputTokens: 0 }, costUsd: 0 }
34
+ : await deps.summarize(plan.toSummarize, plan.previousSummary);
35
+ // Rebound, not spliced in place: `messages` is a local accumulator and
36
+ // rebinding it keeps the compacted array a fresh value.
37
+ const compacted = applyCompaction(messages, plan, summary.text);
38
+ return { messages: compacted, summary };
39
+ }
40
+ export async function runProactiveCompaction(args) {
41
+ const { messages, usage, sessionName, lastUsage, anchorIndex, deps, signal, loopEvents } = args;
42
+ const preCompactionTokens = estimateContextTokens(messages, lastUsage, anchorIndex);
43
+ const plan = prepareCompaction(messages, keepBudget(deps.contextWindow, deps.compaction));
44
+ if (plan === undefined) {
45
+ return { messages, compacted: false, summarizeFailed: false };
46
+ }
47
+ try {
48
+ const patch = await loopEvents.dispatch("before_compaction", {
49
+ reason: "proactive",
50
+ toSummarize: plan.toSummarize,
51
+ ...(plan.previousSummary !== undefined ? { previousSummary: plan.previousSummary } : {}),
52
+ estimatedTokens: preCompactionTokens,
53
+ });
54
+ // Nothing has been requested yet, so sending uncompacted is a legitimate
55
+ // outcome — the decline is HONOURED here (spec 6.2). `summarizeFailed`
56
+ // stays false: the overflow backstop must remain armed for the uncompacted
57
+ // try. A decline wins over a replacement summary — not compacting at all
58
+ // subsumes replacing what the summarizer would say.
59
+ if (patch.decline === true) {
60
+ return { messages, compacted: false, summarizeFailed: false };
61
+ }
62
+ const applied = await summarizeAndApply(messages, plan, deps, { replacementSummary: patch.summary });
63
+ // Finding 2 (whole-branch review, 2026-09-04): a previous-summary merge
64
+ // can produce a same-size (or larger) array — a paid model call that
65
+ // shrank nothing. Not fatal (the reactive backstop is the real safety
66
+ // net if this repeats into an overflow) but worth surfacing, since it
67
+ // would otherwise burn a model call every round trip with no signal.
68
+ const postCompactionTokens = estimateContextTokens(applied.messages, undefined, undefined);
69
+ if (postCompactionTokens >= preCompactionTokens) {
70
+ getSafeLogger()?.warn("native-adapter", "compaction made no size progress", {
71
+ sessionName,
72
+ preCompactionTokens,
73
+ postCompactionTokens,
74
+ });
75
+ }
76
+ getSafeLogger()?.info("native-adapter", "compaction completed", {
77
+ sessionName,
78
+ // Keep token counts under the plural `tokens` metric key so the
79
+ // logger's credential redactor does not mistake them for secrets.
80
+ tokens: { before: preCompactionTokens, after: postCompactionTokens },
81
+ messagesDropped: plan.toSummarize.length,
82
+ summaryLength: applied.summary.text.length,
83
+ });
84
+ usage.add(applied.summary.usage, applied.summary.costUsd, applied.summary.rates);
85
+ // Resets the watchdog's lastActivityAt between the summary and the
86
+ // round trip, so the two silent spans do not add up against one budget.
87
+ deps.onActivity?.(usageBeat(applied.summary.usage, applied.summary.costUsd));
88
+ return { messages: applied.messages, compacted: true, summarizeFailed: false };
89
+ }
90
+ catch (err) {
91
+ if (deps.deadline?.expired() === true || signal?.aborted === true)
92
+ throw err;
93
+ // Not fatal: the request may still fit, and if it does not it fails
94
+ // through the path #1837 and #1839 made correct. Killing a story
95
+ // because a summarizer hiccuped would be worse than the problem.
96
+ getSafeLogger()?.warn("native-adapter", "compaction summary failed; sending uncompacted", {
97
+ sessionName,
98
+ error: err instanceof Error ? err.message : String(err),
99
+ });
100
+ return { messages, compacted: false, summarizeFailed: true };
101
+ }
102
+ }
103
+ export async function runOverflowCompaction(args) {
104
+ const { messages, usage, sessionName, lastUsage, anchorIndex, deps, error, loopEvents } = args;
105
+ // Same code path, half the keep budget. Not a second algorithm.
106
+ const plan = prepareCompaction(messages, keepBudget(deps.contextWindow, deps.compaction, true));
107
+ if (plan === undefined)
108
+ throw error;
109
+ const patch = await loopEvents.dispatch("before_compaction", {
110
+ reason: "overflow",
111
+ toSummarize: plan.toSummarize,
112
+ ...(plan.previousSummary !== undefined ? { previousSummary: plan.previousSummary } : {}),
113
+ estimatedTokens: estimateContextTokens(messages, lastUsage, anchorIndex),
114
+ });
115
+ // The request has ALREADY failed with a context overflow: there is no
116
+ // uncompacted path left, so a decline has nothing to decline into and
117
+ // honouring it would kill the story. The signal stops, the compaction does
118
+ // not (spec 6.2), and the ignore is on the record.
119
+ if (patch.decline === true) {
120
+ getSafeLogger()?.warn("native-loop-events", "before_compaction decline ignored at overflow", {
121
+ sessionName,
122
+ });
123
+ }
124
+ const applied = await summarizeAndApply(messages, plan, deps, { replacementSummary: patch.summary });
125
+ usage.add(applied.summary.usage, applied.summary.costUsd, applied.summary.rates);
126
+ deps.onActivity?.(usageBeat(applied.summary.usage, applied.summary.costUsd));
127
+ return { messages: applied.messages, compacted: true, summarizeFailed: false };
128
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * The complete-with-recovery step of the native turn loop.
3
+ *
4
+ * `deps.complete` plus its two recoveries — the bounded transport-fault retry
5
+ * (nax#1870) and the context-overflow compaction backstop — live here as one
6
+ * site: one round trip is issued, and a throw gets exactly one of the two
7
+ * recoveries. They were two branches of the loop's single try/catch; folding
8
+ * them into a step is what will let one seam observe every round trip. P3
9
+ * attaches `transform_context` and `before_request` here: both fire per request
10
+ * attempt from the private `request()` wrapper below (spec 6.5, 6.6) —
11
+ * before_request shapes the per-call options, transform_context shapes the WIRE
12
+ * COPY only, and the array the caller holds (which saveTranscript persists) is
13
+ * returned untouched. An honoured rewrite is reported as `honoured`, with the
14
+ * boundary fact that produced it as `boundary`, so the caller can clear the
15
+ * cache anchor exactly when spec 3.6 says to — a prefix-stable honour leaves
16
+ * the anchor valid (spec 6.6).
17
+ *
18
+ * The private `isContextOverflow` guard is this module's alone: it decides
19
+ * which recovery a thrown round trip gets.
20
+ */
21
+ import type { TranscriptMessage as NativeTranscriptMessage } from "./compaction.ts";
22
+ import type { LoopEventRegistry } from "./loop-events/index.ts";
23
+ import type { TurnAccumulator } from "./turn-accumulator.ts";
24
+ import type { NativeTurnResponse, TurnDeps } from "./turn-types.ts";
25
+ export interface CompleteStepResult {
26
+ readonly res: NativeTurnResponse;
27
+ /** The transcript after recovery; rebound only by the overflow branch. */
28
+ readonly messages: readonly NativeTranscriptMessage[];
29
+ /** True when the overflow branch ran: it always rebinds messages (and the caller clears lastUsage/anchorIndex). */
30
+ readonly compacted: boolean;
31
+ /**
32
+ * True when the provider was sent an honoured `transform_context` rewrite
33
+ * (spec 6.6) — the last attempt to reach the wire is the one that counts.
34
+ * The caller clears lastUsage/anchorIndex on it ONLY together with
35
+ * `boundary`: a prefix-stable honour leaves the anchor valid (spec 6.6 —
36
+ * wire and persisted prefix are reference-identical, nothing to
37
+ * invalidate), while a boundary-exempt honour changed the prefix the
38
+ * provider saw even though the saved array did not (spec 3.6).
39
+ */
40
+ readonly honoured: boolean;
41
+ /**
42
+ * True when the honoured rewrite (if any) rode the boundary exemption —
43
+ * the post-compaction retry is the only boundary this step can produce
44
+ * (`compacted`; spec 3.4: a handler may never assert one). Paired with
45
+ * `honoured` rather than folded into it: the step-level contract keeps both
46
+ * facts.
47
+ */
48
+ readonly boundary: boolean;
49
+ }
50
+ export interface CompleteStepArgs {
51
+ readonly messages: readonly NativeTranscriptMessage[];
52
+ readonly tools: Parameters<TurnDeps["complete"]>[1];
53
+ readonly usage: TurnAccumulator;
54
+ /**
55
+ * Set by the proactive step for this round trip: the summarizer already threw,
56
+ * so the overflow retry must not try again.
57
+ */
58
+ readonly summarizeFailed: boolean;
59
+ readonly sessionName: string;
60
+ readonly lastUsage: {
61
+ readonly promptTokens: number;
62
+ } | undefined;
63
+ readonly anchorIndex: number | undefined;
64
+ /**
65
+ * The registry actually in use — the loop's local (`deps.loopEvents ??
66
+ * createLoopEventRegistry()`), not `deps.loopEvents` itself, which is
67
+ * usually absent and would dispatch to nothing.
68
+ */
69
+ readonly loopEvents: LoopEventRegistry;
70
+ /** The round trip this request belongs to, as counted by the loop. */
71
+ readonly roundTrip: number;
72
+ /** The session's resolved model; undefined when driven without a modelDef. */
73
+ readonly model?: string;
74
+ readonly deps: TurnDeps;
75
+ readonly signal?: AbortSignal;
76
+ }
77
+ export declare function completeWithRecovery(args: CompleteStepArgs): Promise<CompleteStepResult>;
@@ -0,0 +1,159 @@
1
+ /**
2
+ * The complete-with-recovery step of the native turn loop.
3
+ *
4
+ * `deps.complete` plus its two recoveries — the bounded transport-fault retry
5
+ * (nax#1870) and the context-overflow compaction backstop — live here as one
6
+ * site: one round trip is issued, and a throw gets exactly one of the two
7
+ * recoveries. They were two branches of the loop's single try/catch; folding
8
+ * them into a step is what will let one seam observe every round trip. P3
9
+ * attaches `transform_context` and `before_request` here: both fire per request
10
+ * attempt from the private `request()` wrapper below (spec 6.5, 6.6) —
11
+ * before_request shapes the per-call options, transform_context shapes the WIRE
12
+ * COPY only, and the array the caller holds (which saveTranscript persists) is
13
+ * returned untouched. An honoured rewrite is reported as `honoured`, with the
14
+ * boundary fact that produced it as `boundary`, so the caller can clear the
15
+ * cache anchor exactly when spec 3.6 says to — a prefix-stable honour leaves
16
+ * the anchor valid (spec 6.6).
17
+ *
18
+ * The private `isContextOverflow` guard is this module's alone: it decides
19
+ * which recovery a thrown round trip gets.
20
+ */
21
+ import { getSafeLogger } from "#src/infra/index";
22
+ import { applyHistoryPatch } from "./loop-events/cache-boundary.js";
23
+ import { runOverflowCompaction } from "./turn-compaction-step.js";
24
+ import { realSleep, retryTransportFault } from "./turn-retry.js";
25
+ /**
26
+ * Structural, matching adapter-deps.ts's guard: nax-ai's error class is not
27
+ * importable here and the kind is what matters.
28
+ */
29
+ function isContextOverflow(err) {
30
+ if (typeof err !== "object" || err === null || !("protocolError" in err))
31
+ return false;
32
+ const { protocolError } = err;
33
+ return protocolError?.kind === "context-overflow";
34
+ }
35
+ export async function completeWithRecovery(args) {
36
+ const { tools, usage, summarizeFailed, sessionName, lastUsage, anchorIndex, loopEvents, roundTrip, model, deps, signal, } = args;
37
+ let messages = args.messages;
38
+ let compacted = false;
39
+ let honoured = false;
40
+ let res;
41
+ // Model, thinking level and timeout are bound in the adapter's closure above
42
+ // the loop (spec 6.3), so the bag this dispatches starts empty; a handler's
43
+ // patch is the only thing that ever fills it.
44
+ const baseOptions = {};
45
+ // One wrapper, three call sites (spec 6.5). The wrapper owns `attempt`, so
46
+ // the retry machinery reports 2..n without knowing an event exists.
47
+ let attempt = 0;
48
+ const request = async (msgs) => {
49
+ attempt += 1;
50
+ const patch = await loopEvents.dispatch("before_request", {
51
+ ...(model !== undefined ? { model } : {}),
52
+ roundTrip,
53
+ attempt,
54
+ options: baseOptions,
55
+ });
56
+ const options = patch.options === undefined ? baseOptions : { ...baseOptions, ...patch.options };
57
+ // transform_context fires here too, per attempt (spec 6.6). The patch
58
+ // shapes ONLY the wire copy handed to deps.complete below: `msgs` — the
59
+ // caller's array, the one saveTranscript persists — is returned untouched,
60
+ // so the transcript stays the true record. `boundary` is this step's
61
+ // overflow fact (`compacted`): true only on the post-compaction retry,
62
+ // where a prefix rewrite is free. A model change never reaches this step:
63
+ // the transcript store refuses another model's history (spec 8.3).
64
+ const transformed = await loopEvents.dispatch("transform_context", {
65
+ messages: msgs,
66
+ tools,
67
+ anchorIndex,
68
+ boundary: compacted,
69
+ ...(model !== undefined ? { model } : {}),
70
+ });
71
+ const wire = applyHistoryPatch({
72
+ before: msgs,
73
+ patched: transformed.messages,
74
+ anchorIndex,
75
+ boundary: compacted,
76
+ event: "transform_context",
77
+ });
78
+ // Last write wins: a failed attempt is discarded wholesale, so the
79
+ // successful attempt is the last one to reach this line — `honoured`
80
+ // describes the wire the provider actually answered (spec 3.6).
81
+ honoured = wire.honoured;
82
+ return deps.complete(wire.messages, tools, options);
83
+ };
84
+ try {
85
+ res = await request(messages);
86
+ }
87
+ catch (err) {
88
+ // Written as one guarded `if` (not a separate `canRetry` boolean) so
89
+ // TypeScript's narrowing carries deps.summarize/contextWindow/compaction
90
+ // as defined below — a boolean flag loses that narrowing.
91
+ if (!isContextOverflow(err) ||
92
+ summarizeFailed ||
93
+ deps.summarize === undefined ||
94
+ deps.contextWindow === undefined ||
95
+ deps.compaction === undefined ||
96
+ !deps.compaction.enabled) {
97
+ // nax#1870: not an overflow this branch can handle. One more
98
+ // guarded branch beside the overflow backstop above, not a second
99
+ // loop or a second try/catch here — the retry's own looping and
100
+ // backoff live in retryTransportFault (./turn-retry), called once.
101
+ if (deps.transportRetry === undefined)
102
+ throw err;
103
+ res = await retryTransportFault(err, {
104
+ attempt: () => request(messages),
105
+ config: deps.transportRetry,
106
+ deadline: deps.deadline,
107
+ signal,
108
+ sleep: deps.sleep ?? realSleep,
109
+ onRetry: (retryNumber, delayMs, fault) => {
110
+ getSafeLogger()?.warn("native-adapter", `retrying after a ${fault.protocolError.kind} fault`, {
111
+ sessionName,
112
+ retryNumber,
113
+ delayMs,
114
+ kind: fault.protocolError.kind,
115
+ message: fault.protocolError.message,
116
+ ...(fault.protocolError.status !== undefined ? { status: fault.protocolError.status } : {}),
117
+ ...(fault.protocolError.retryAfter !== undefined ? { retryAfter: fault.protocolError.retryAfter } : {}),
118
+ });
119
+ // Resets the watchdog's lastActivityAt so a call being retried
120
+ // is not mistaken for an idle one — same mechanism the
121
+ // compaction summary above uses. All-zero is honest, not
122
+ // fabricated: a pre-first-event transport throw bills nothing
123
+ // (see nax-ai's retry.ts), so this beat truly carries zero
124
+ // tokens, not a guessed non-zero number.
125
+ deps.onActivity?.({ kind: "usage", inputTokens: 0, outputTokens: 0, costUsd: 0 });
126
+ },
127
+ });
128
+ // Falls through to the shared round-trip bookkeeping and tool
129
+ // execution below, exactly like the overflow-retry branch's own
130
+ // `res = await request(messages)` two lines down — one success
131
+ // path, reached from either recovery, not a second copy of it.
132
+ }
133
+ else {
134
+ const step = await runOverflowCompaction({
135
+ messages,
136
+ usage,
137
+ sessionName,
138
+ lastUsage,
139
+ anchorIndex,
140
+ loopEvents,
141
+ deps: {
142
+ summarize: deps.summarize,
143
+ contextWindow: deps.contextWindow,
144
+ compaction: deps.compaction,
145
+ onActivity: deps.onActivity,
146
+ deadline: deps.deadline,
147
+ },
148
+ error: err,
149
+ ...(signal !== undefined ? { signal } : {}),
150
+ });
151
+ messages = [...step.messages];
152
+ compacted = true;
153
+ // Retried once. A second overflow propagates: compacting further would be
154
+ // guessing, and the failure now carries a correct diagnosis.
155
+ res = await request(messages);
156
+ }
157
+ }
158
+ return { res, messages, compacted, honoured, boundary: compacted };
159
+ }
@@ -0,0 +1,3 @@
1
+ import type { LoopEventRegistry } from "./loop-events/index.ts";
2
+ import type { BeforeTurnEndPayload } from "./loop-events/types.ts";
3
+ export declare function dispatchTurnEndOnError(loopEvents: LoopEventRegistry, payload: Omit<BeforeTurnEndPayload, "ended">, signal?: AbortSignal): Promise<void>;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Review #20: `before_turn_end` also fires when a turn throws, so a handler
3
+ * sees every ending. The result is ignored on this path -- there is no turn
4
+ * left to continue. The dispatch is awaited without a timeout of its own: a
5
+ * plugin handler is timed by its wrapper (`wrapExternalHandler`, US-002),
6
+ * and a built-in handler is not timed at all.
7
+ */
8
+ import { getSafeLogger } from "#src/infra/index";
9
+ export async function dispatchTurnEndOnError(loopEvents, payload, signal) {
10
+ const patch = await loopEvents.dispatch("before_turn_end", {
11
+ ...payload,
12
+ ended: signal?.aborted === true ? "aborted" : "errored",
13
+ });
14
+ if (patch.followUp !== undefined) {
15
+ getSafeLogger()?.warn("native-loop-events", "before_turn_end followUp ignored on error path", {});
16
+ }
17
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Native round-trip activity -> AgentStreamEvent.
3
+ *
4
+ * The idle watchdog subscribes to the runtime stream bus and tracks calls from
5
+ * these events; nothing under src/agents/native/ emitted any, so
6
+ * `agent.idleWatchdog` was inert on every native session in both modes.
7
+ *
8
+ * Native has no token streaming — one `complete()` is a single call — so these
9
+ * are emitted at round-trip boundaries rather than continuously. That is
10
+ * sufficient: a HUNG call is already bounded by the per-call abort, and the
11
+ * watchdog's unique job is the productive-looking loop that keeps calling tools
12
+ * forever. That loop emits `tool` AND `usage` on every iteration, so the usage
13
+ * event carries `perRoundTrip` to mark it as a round-trip boundary rather than
14
+ * semantic progress — otherwise it reset `lastNonToolCallActivityAt` every
15
+ * iteration and `toolCallOnlyIdleTimeout` could never fire (nax#2013).
16
+ */
17
+ import type { AgentStreamEvent } from "#src/session/agent-stream-events";
18
+ export type NativeTurnActivity = {
19
+ kind: "message";
20
+ bytes: number;
21
+ } | {
22
+ kind: "thinking";
23
+ bytes: number;
24
+ } | {
25
+ kind: "usage";
26
+ inputTokens: number;
27
+ outputTokens: number;
28
+ costUsd: number;
29
+ /** Absent when the round trip reported no cache data — never coerced to 0. */
30
+ cacheRead?: number;
31
+ /** Absent when the round trip reported no cache data — never coerced to 0. */
32
+ cacheWrite?: number;
33
+ /** 1-based ordinal of the round trip this beat covers. Absent on a
34
+ * compaction-summary or retry beat, which are not round-trip boundaries. */
35
+ roundTrip?: number;
36
+ } | {
37
+ kind: "tool";
38
+ toolName: string;
39
+ } | {
40
+ /**
41
+ * US-004: the turn is waiting on a human approval prompt. Carried so the
42
+ * runtime stream bus can turn it into `agent.awaiting_human` for the idle
43
+ * watchdog, which must not read legitimate human waiting as idleness.
44
+ */
45
+ kind: "awaiting_human";
46
+ };
47
+ export interface NativeStreamEventBase {
48
+ readonly callId: string;
49
+ readonly runId: string;
50
+ readonly agentName: string;
51
+ readonly sessionName: string;
52
+ readonly storyId?: string;
53
+ /** Pipeline stage label; the contract does not know nax's stage union (S1 spec port 4). */
54
+ readonly stage?: string;
55
+ /**
56
+ * The transcript/ledger join key (`transcript.owner` = `ledger.scopeId`),
57
+ * forwarded from the native session's owner map. Absent when the session was
58
+ * opened without a `transcriptOwner` — absent reads as "unknown". Never the
59
+ * stream-local `callId`.
60
+ */
61
+ readonly scopeId?: string;
62
+ }
63
+ export declare function buildNativeStreamEvent(base: NativeStreamEventBase, activity: NativeTurnActivity, timestamp: number): AgentStreamEvent;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Native round-trip activity -> AgentStreamEvent.
3
+ *
4
+ * The idle watchdog subscribes to the runtime stream bus and tracks calls from
5
+ * these events; nothing under src/agents/native/ emitted any, so
6
+ * `agent.idleWatchdog` was inert on every native session in both modes.
7
+ *
8
+ * Native has no token streaming — one `complete()` is a single call — so these
9
+ * are emitted at round-trip boundaries rather than continuously. That is
10
+ * sufficient: a HUNG call is already bounded by the per-call abort, and the
11
+ * watchdog's unique job is the productive-looking loop that keeps calling tools
12
+ * forever. That loop emits `tool` AND `usage` on every iteration, so the usage
13
+ * event carries `perRoundTrip` to mark it as a round-trip boundary rather than
14
+ * semantic progress — otherwise it reset `lastNonToolCallActivityAt` every
15
+ * iteration and `toolCallOnlyIdleTimeout` could never fire (nax#2013).
16
+ */
17
+ export function buildNativeStreamEvent(base, activity, timestamp) {
18
+ const common = { ...base, timestamp };
19
+ switch (activity.kind) {
20
+ case "message":
21
+ return { ...common, kind: "agent.message_update", deltaBytes: activity.bytes };
22
+ case "thinking":
23
+ return { ...common, kind: "agent.thinking_update", deltaBytes: activity.bytes };
24
+ case "usage":
25
+ return {
26
+ ...common,
27
+ kind: "agent.usage_update",
28
+ inputTokens: activity.inputTokens,
29
+ outputTokens: activity.outputTokens,
30
+ costUsd: activity.costUsd,
31
+ ...(activity.cacheRead !== undefined ? { cacheRead: activity.cacheRead } : {}),
32
+ ...(activity.cacheWrite !== undefined ? { cacheWrite: activity.cacheWrite } : {}),
33
+ // Only a real round-trip boundary may claim `perRoundTrip` and an
34
+ // ordinal: the compaction-summary and transport-retry beats are usage
35
+ // events too, but they are not round trips (nax#2013, nax#2045). The
36
+ // cadence is still nax's own loop even on those beats.
37
+ ...(activity.roundTrip !== undefined ? { roundTrip: activity.roundTrip, perRoundTrip: true } : {}),
38
+ cadence: "round-trip",
39
+ };
40
+ case "tool":
41
+ return { ...common, kind: "agent.tool_call_update", toolName: activity.toolName };
42
+ case "awaiting_human":
43
+ // US-004: the turn is waiting on a human approval prompt. The watchdog
44
+ // treats this as semantic progress on both activity clocks and clears an
45
+ // open grace window, so a pending prompt never reads as idle.
46
+ return { ...common, kind: "agent.awaiting_human" };
47
+ }
48
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * The round-trip loop and turn-end phase of the native turn loop.
3
+ *
4
+ * Split out of `./turn-loop.ts` during the complexity drain
5
+ * (docs/plans/STATUS-complexity-drain.md A2): `runNativeTurn` scored 97 as one
6
+ * function covering setup, the round-trip loop (deliberately unbounded —
7
+ * bounded by the deadline, the idle watchdog, and the spin breaker, not a
8
+ * call count), and the `before_turn_end` continuation decision. Both loop
9
+ * bodies move here unchanged; only the plumbing that threads state across
10
+ * calls changed shape. A pure extraction: no loop rule changed.
11
+ *
12
+ * `runRoundTripLoop` mutates its `state` argument in place rather than
13
+ * returning a new one, and does so on every field the moment each value is
14
+ * known — not only at a clean exit. The original was one function where
15
+ * `messages` (and friends) were plain `let`s: a mid-loop throw (the
16
+ * `batch.cancelled` path below) still left the catch block reading the
17
+ * latest values, because it was the same variable. Returning a fresh object
18
+ * only at the end of the function would lose every update since the last
19
+ * return the moment a throw skips it — exactly the synthetic tool-result
20
+ * messages `runToolBatch` just appended before signalling cancellation. The
21
+ * caller passes one `TurnLoopState` and keeps reading through that same
22
+ * reference after an `await` that might throw.
23
+ *
24
+ * `spinStopped` / `spinWarned` were two `let`s in `runNativeTurn`, one of them
25
+ * (`spinWarned`) mutated by an `onSpinStop` closure registered ONCE in setup,
26
+ * before either extracted function is ever called, and read/written across
27
+ * every outer-loop iteration after that. Splitting the loop body out cannot
28
+ * split that closure's target too, so both flags now live in one mutable
29
+ * `SpinFlags` object created in setup and threaded by reference into every
30
+ * call here — the same shared-mutable-state semantics as the original two
31
+ * `let`s, just addressed through an object instead of two closed-over
32
+ * bindings.
33
+ */
34
+ import type { ToolDefinition } from "@nathapp/nax-ai";
35
+ import type { SpinBreaker } from "#src/infra/spin-breaker/index";
36
+ import type { InteractionExchange, SendTurnOpts, SessionHandle } from "#src/session/session-types";
37
+ import { type TranscriptMessage as NativeTranscriptMessage } from "./compaction.ts";
38
+ import type { InvalidCallBudget } from "./handle-invalid-tool-call.ts";
39
+ import type { LoopEventRegistry } from "./loop-events/index.ts";
40
+ import type { createTurnAccumulator } from "./turn-accumulator.ts";
41
+ import type { TurnDeps } from "./turn-types.ts";
42
+ /**
43
+ * The per-turn bound on `before_turn_end`'s followUp channel (spec 6.4): the
44
+ * only event that can spend money on its own, so injections are capped per
45
+ * turn regardless of what handlers return. At the cap the channel stops and
46
+ * the turn ends.
47
+ */
48
+ export declare const MAX_FOLLOW_UPS_PER_TURN = 3;
49
+ /** Shared across every call in one turn — see the file header. */
50
+ export interface SpinFlags {
51
+ /** Set ONLY when the breaker ended the turn, so the wiring layer can classify it as `fail-spin` rather than a generic incomplete turn. */
52
+ stopped: boolean;
53
+ /** nax#2120: the first stop verdict spends a terminal round trip rather than tearing the turn down, so a false positive does not cost the transcript. */
54
+ warned: boolean;
55
+ }
56
+ /** Values that change across outer-loop (followUp) iterations. */
57
+ export interface TurnLoopState {
58
+ messages: NativeTranscriptMessage[];
59
+ lastUsage: {
60
+ promptTokens: number;
61
+ } | undefined;
62
+ anchorIndex: number | undefined;
63
+ roundTrips: number;
64
+ output: string;
65
+ completedNormally: boolean;
66
+ timedOut: boolean;
67
+ followUpsSoFar: number;
68
+ }
69
+ /** Turn-scoped values that never change across outer-loop iterations. */
70
+ export interface TurnRoundParams {
71
+ handle: SessionHandle;
72
+ opts: SendTurnOpts;
73
+ deps: TurnDeps;
74
+ /** `transcriptIdentity.model` — carried into every `nativeSessionLastUsage.set` this turn writes. */
75
+ transcriptModel: string | undefined;
76
+ tools: ToolDefinition[];
77
+ codingToolNames: ReadonlySet<string>;
78
+ loopEvents: LoopEventRegistry;
79
+ invalidCallBudget: InvalidCallBudget;
80
+ spinBreaker: SpinBreaker | undefined;
81
+ spinFlags: SpinFlags;
82
+ maxInteractions: number;
83
+ /** Mutated in place — the caller owns the turn-lifetime array. */
84
+ interactions: InteractionExchange[];
85
+ /** Mutated in place — the caller owns the turn-lifetime array. */
86
+ codingToolsCalled: string[];
87
+ /** Mutated in place via `.add()` — the caller owns the turn-lifetime accumulator. */
88
+ usage: ReturnType<typeof createTurnAccumulator>;
89
+ }
90
+ /**
91
+ * Deliberately unbounded by COUNT of varied calls. A coding agent working a
92
+ * story is bounded by wall clock (deps.deadline), by the idle watchdog, and
93
+ * — since nax#2013 — by the spin breaker, which ends a turn that keeps
94
+ * REPEATING a call it already made. `agent.maxInteractionTurns` is NOT this
95
+ * budget — it bounds human Q&A exchanges, which are counted separately.
96
+ */
97
+ export declare function runRoundTripLoop(state: TurnLoopState, params: TurnRoundParams): Promise<void>;
98
+ export type TurnEndOutcome = {
99
+ action: "break";
100
+ } | {
101
+ action: "continue";
102
+ state: TurnLoopState;
103
+ };
104
+ /**
105
+ * P3 `before_turn_end` (spec 6.4): fires at every turn ENDING, before the
106
+ * final saveTranscript. `stopped` is dispatcher-computed: the three endings
107
+ * that are stops, not completions — the spin breaker (nax#2120), the
108
+ * invalid-call budget (nax#2047), and the deadline — close the followUp
109
+ * channel entirely, because resurrecting a turn a breaker just killed would
110
+ * re-open the loops those breakers exist to close. The payload's `stopped`
111
+ * flag is all a handler sees: WHICH breaker fired is not leaked, and any
112
+ * followUp returned against a stop is ignored (with a warn — a handler
113
+ * trying to resurrect is worth a trace). The channel is also capped per turn
114
+ * at MAX_FOLLOW_UPS_PER_TURN.
115
+ */
116
+ export declare function runTurnEndPhase(state: TurnLoopState, params: TurnRoundParams): Promise<TurnEndOutcome>;