@namzu/sdk 3.0.0 → 3.2.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 (307) hide show
  1. package/CHANGELOG.md +223 -0
  2. package/dist/advisory/__tests__/consultation-context.test.d.ts +2 -0
  3. package/dist/advisory/__tests__/consultation-context.test.d.ts.map +1 -0
  4. package/dist/advisory/__tests__/consultation-context.test.js +124 -0
  5. package/dist/advisory/__tests__/consultation-context.test.js.map +1 -0
  6. package/dist/advisory/context.d.ts +25 -0
  7. package/dist/advisory/context.d.ts.map +1 -1
  8. package/dist/advisory/context.js +18 -0
  9. package/dist/advisory/context.js.map +1 -1
  10. package/dist/advisory/executor.d.ts.map +1 -1
  11. package/dist/advisory/executor.js +25 -3
  12. package/dist/advisory/executor.js.map +1 -1
  13. package/dist/compaction/__tests__/context-reducer.test.d.ts +2 -0
  14. package/dist/compaction/__tests__/context-reducer.test.d.ts.map +1 -0
  15. package/dist/compaction/__tests__/context-reducer.test.js +197 -0
  16. package/dist/compaction/__tests__/context-reducer.test.js.map +1 -0
  17. package/dist/compaction/factory.d.ts +7 -0
  18. package/dist/compaction/factory.d.ts.map +1 -1
  19. package/dist/compaction/factory.js +7 -0
  20. package/dist/compaction/factory.js.map +1 -1
  21. package/dist/compaction/index.d.ts +2 -0
  22. package/dist/compaction/index.d.ts.map +1 -1
  23. package/dist/compaction/index.js +1 -0
  24. package/dist/compaction/index.js.map +1 -1
  25. package/dist/compaction/interface.d.ts +13 -0
  26. package/dist/compaction/interface.d.ts.map +1 -1
  27. package/dist/compaction/managers/null.d.ts +3 -0
  28. package/dist/compaction/managers/null.d.ts.map +1 -1
  29. package/dist/compaction/managers/null.js +3 -0
  30. package/dist/compaction/managers/null.js.map +1 -1
  31. package/dist/compaction/managers/slidingWindow.d.ts +6 -0
  32. package/dist/compaction/managers/slidingWindow.d.ts.map +1 -1
  33. package/dist/compaction/managers/slidingWindow.js +6 -0
  34. package/dist/compaction/managers/slidingWindow.js.map +1 -1
  35. package/dist/compaction/managers/structured.d.ts +10 -0
  36. package/dist/compaction/managers/structured.d.ts.map +1 -1
  37. package/dist/compaction/managers/structured.js +10 -0
  38. package/dist/compaction/managers/structured.js.map +1 -1
  39. package/dist/compaction/reducer.d.ts +86 -0
  40. package/dist/compaction/reducer.d.ts.map +1 -0
  41. package/dist/compaction/reducer.js +77 -0
  42. package/dist/compaction/reducer.js.map +1 -0
  43. package/dist/connector/builtins/__tests__/oauth2-auth.test.d.ts +2 -0
  44. package/dist/connector/builtins/__tests__/oauth2-auth.test.d.ts.map +1 -0
  45. package/dist/connector/builtins/__tests__/oauth2-auth.test.js +54 -0
  46. package/dist/connector/builtins/__tests__/oauth2-auth.test.js.map +1 -0
  47. package/dist/connector/builtins/http.d.ts.map +1 -1
  48. package/dist/connector/builtins/http.js +24 -2
  49. package/dist/connector/builtins/http.js.map +1 -1
  50. package/dist/connector/builtins/http.test.js +18 -2
  51. package/dist/connector/builtins/http.test.js.map +1 -1
  52. package/dist/connector/index.d.ts +2 -2
  53. package/dist/connector/index.d.ts.map +1 -1
  54. package/dist/connector/index.js +1 -1
  55. package/dist/connector/index.js.map +1 -1
  56. package/dist/connector/mcp/__tests__/prompts-and-lifecycle.test.d.ts +2 -0
  57. package/dist/connector/mcp/__tests__/prompts-and-lifecycle.test.d.ts.map +1 -0
  58. package/dist/connector/mcp/__tests__/prompts-and-lifecycle.test.js +214 -0
  59. package/dist/connector/mcp/__tests__/prompts-and-lifecycle.test.js.map +1 -0
  60. package/dist/connector/mcp/client.d.ts +52 -1
  61. package/dist/connector/mcp/client.d.ts.map +1 -1
  62. package/dist/connector/mcp/client.js +86 -0
  63. package/dist/connector/mcp/client.js.map +1 -1
  64. package/dist/connector/mcp/discovery.d.ts +12 -1
  65. package/dist/connector/mcp/discovery.d.ts.map +1 -1
  66. package/dist/connector/mcp/discovery.js +19 -4
  67. package/dist/connector/mcp/discovery.js.map +1 -1
  68. package/dist/connector/mcp/index.d.ts +2 -2
  69. package/dist/connector/mcp/index.d.ts.map +1 -1
  70. package/dist/connector/mcp/index.js +1 -1
  71. package/dist/connector/mcp/index.js.map +1 -1
  72. package/dist/connector/mcp/server.d.ts +42 -1
  73. package/dist/connector/mcp/server.d.ts.map +1 -1
  74. package/dist/connector/mcp/server.js +77 -4
  75. package/dist/connector/mcp/server.js.map +1 -1
  76. package/dist/manager/agent/__tests__/depth-limit-authority.test.d.ts +2 -0
  77. package/dist/manager/agent/__tests__/depth-limit-authority.test.d.ts.map +1 -0
  78. package/dist/manager/agent/__tests__/depth-limit-authority.test.js +58 -0
  79. package/dist/manager/agent/__tests__/depth-limit-authority.test.js.map +1 -0
  80. package/dist/plugin/__tests__/discovery-scopes.test.d.ts +2 -0
  81. package/dist/plugin/__tests__/discovery-scopes.test.d.ts.map +1 -0
  82. package/dist/plugin/__tests__/discovery-scopes.test.js +97 -0
  83. package/dist/plugin/__tests__/discovery-scopes.test.js.map +1 -0
  84. package/dist/plugin/__tests__/enable-contributions.test.js +5 -1
  85. package/dist/plugin/__tests__/enable-contributions.test.js.map +1 -1
  86. package/dist/plugin/__tests__/mcp-admission.test.d.ts +2 -0
  87. package/dist/plugin/__tests__/mcp-admission.test.d.ts.map +1 -0
  88. package/dist/plugin/__tests__/mcp-admission.test.js +192 -0
  89. package/dist/plugin/__tests__/mcp-admission.test.js.map +1 -0
  90. package/dist/plugin/lifecycle.d.ts +41 -0
  91. package/dist/plugin/lifecycle.d.ts.map +1 -1
  92. package/dist/plugin/lifecycle.js +29 -1
  93. package/dist/plugin/lifecycle.js.map +1 -1
  94. package/dist/plugin/loader.d.ts +39 -3
  95. package/dist/plugin/loader.d.ts.map +1 -1
  96. package/dist/plugin/loader.js +37 -4
  97. package/dist/plugin/loader.js.map +1 -1
  98. package/dist/public-runtime.d.ts +5 -3
  99. package/dist/public-runtime.d.ts.map +1 -1
  100. package/dist/public-runtime.js +11 -3
  101. package/dist/public-runtime.js.map +1 -1
  102. package/dist/public-types.d.ts +2 -2
  103. package/dist/public-types.d.ts.map +1 -1
  104. package/dist/rag/__tests__/namespace-isolation.test.d.ts +2 -0
  105. package/dist/rag/__tests__/namespace-isolation.test.d.ts.map +1 -0
  106. package/dist/rag/__tests__/namespace-isolation.test.js +80 -0
  107. package/dist/rag/__tests__/namespace-isolation.test.js.map +1 -0
  108. package/dist/rag/ingestion.d.ts.map +1 -1
  109. package/dist/rag/ingestion.js +1 -0
  110. package/dist/rag/ingestion.js.map +1 -1
  111. package/dist/rag/retriever.d.ts.map +1 -1
  112. package/dist/rag/retriever.js +2 -0
  113. package/dist/rag/retriever.js.map +1 -1
  114. package/dist/rag/vector-store.d.ts.map +1 -1
  115. package/dist/rag/vector-store.js +6 -0
  116. package/dist/rag/vector-store.js.map +1 -1
  117. package/dist/registry/tool/execute.d.ts.map +1 -1
  118. package/dist/registry/tool/execute.js +113 -109
  119. package/dist/registry/tool/execute.js.map +1 -1
  120. package/dist/runtime/query/__tests__/capability-negotiation.test.js +59 -0
  121. package/dist/runtime/query/__tests__/capability-negotiation.test.js.map +1 -1
  122. package/dist/runtime/query/__tests__/per-step-skills.test.d.ts +10 -0
  123. package/dist/runtime/query/__tests__/per-step-skills.test.d.ts.map +1 -0
  124. package/dist/runtime/query/__tests__/per-step-skills.test.js +122 -0
  125. package/dist/runtime/query/__tests__/per-step-skills.test.js.map +1 -0
  126. package/dist/runtime/query/__tests__/per-step-tool-choice.test.d.ts +2 -0
  127. package/dist/runtime/query/__tests__/per-step-tool-choice.test.d.ts.map +1 -0
  128. package/dist/runtime/query/__tests__/per-step-tool-choice.test.js +153 -0
  129. package/dist/runtime/query/__tests__/per-step-tool-choice.test.js.map +1 -0
  130. package/dist/runtime/query/__tests__/resume-run.test.d.ts +2 -0
  131. package/dist/runtime/query/__tests__/resume-run.test.d.ts.map +1 -0
  132. package/dist/runtime/query/__tests__/resume-run.test.js +211 -0
  133. package/dist/runtime/query/__tests__/resume-run.test.js.map +1 -0
  134. package/dist/runtime/query/executor.d.ts +1 -0
  135. package/dist/runtime/query/executor.d.ts.map +1 -1
  136. package/dist/runtime/query/executor.js +10 -1
  137. package/dist/runtime/query/executor.js.map +1 -1
  138. package/dist/runtime/query/index.d.ts +10 -0
  139. package/dist/runtime/query/index.d.ts.map +1 -1
  140. package/dist/runtime/query/index.js +26 -0
  141. package/dist/runtime/query/index.js.map +1 -1
  142. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  143. package/dist/runtime/query/iteration/index.js +118 -34
  144. package/dist/runtime/query/iteration/index.js.map +1 -1
  145. package/dist/runtime/query/iteration/phases/compaction-model-routing.test.d.ts +2 -0
  146. package/dist/runtime/query/iteration/phases/compaction-model-routing.test.d.ts.map +1 -0
  147. package/dist/runtime/query/iteration/phases/compaction-model-routing.test.js +96 -0
  148. package/dist/runtime/query/iteration/phases/compaction-model-routing.test.js.map +1 -0
  149. package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
  150. package/dist/runtime/query/iteration/phases/compaction.js +95 -5
  151. package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
  152. package/dist/runtime/query/iteration/phases/context-reducer-dispatch.test.d.ts +2 -0
  153. package/dist/runtime/query/iteration/phases/context-reducer-dispatch.test.d.ts.map +1 -0
  154. package/dist/runtime/query/iteration/phases/context-reducer-dispatch.test.js +180 -0
  155. package/dist/runtime/query/iteration/phases/context-reducer-dispatch.test.js.map +1 -0
  156. package/dist/runtime/query/iteration/phases/context.d.ts +9 -0
  157. package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
  158. package/dist/runtime/query/iteration/phases/context.js.map +1 -1
  159. package/dist/runtime/query/resume-run.d.ts +70 -0
  160. package/dist/runtime/query/resume-run.d.ts.map +1 -0
  161. package/dist/runtime/query/resume-run.js +46 -0
  162. package/dist/runtime/query/resume-run.js.map +1 -0
  163. package/dist/telemetry/__tests__/model-call-span.test.d.ts +2 -0
  164. package/dist/telemetry/__tests__/model-call-span.test.d.ts.map +1 -0
  165. package/dist/telemetry/__tests__/model-call-span.test.js +147 -0
  166. package/dist/telemetry/__tests__/model-call-span.test.js.map +1 -0
  167. package/dist/telemetry/__tests__/span-closure.test.d.ts +2 -0
  168. package/dist/telemetry/__tests__/span-closure.test.d.ts.map +1 -0
  169. package/dist/telemetry/__tests__/span-closure.test.js +124 -0
  170. package/dist/telemetry/__tests__/span-closure.test.js.map +1 -0
  171. package/dist/tools/advisory/index.js +1 -1
  172. package/dist/tools/advisory/index.js.map +1 -1
  173. package/dist/tools/builtins/__tests__/edit-commits-atomically.test.d.ts +2 -0
  174. package/dist/tools/builtins/__tests__/edit-commits-atomically.test.d.ts.map +1 -0
  175. package/dist/tools/builtins/__tests__/edit-commits-atomically.test.js +78 -0
  176. package/dist/tools/builtins/__tests__/edit-commits-atomically.test.js.map +1 -0
  177. package/dist/tools/builtins/__tests__/edit.test.js +242 -1
  178. package/dist/tools/builtins/__tests__/edit.test.js.map +1 -1
  179. package/dist/tools/builtins/__tests__/stale-file-refusal.test.d.ts +2 -0
  180. package/dist/tools/builtins/__tests__/stale-file-refusal.test.d.ts.map +1 -0
  181. package/dist/tools/builtins/__tests__/stale-file-refusal.test.js +187 -0
  182. package/dist/tools/builtins/__tests__/stale-file-refusal.test.js.map +1 -0
  183. package/dist/tools/builtins/atomic-write-file.d.ts +1 -1
  184. package/dist/tools/builtins/atomic-write-file.d.ts.map +1 -1
  185. package/dist/tools/builtins/atomic-write-file.js +31 -2
  186. package/dist/tools/builtins/atomic-write-file.js.map +1 -1
  187. package/dist/tools/builtins/content-fingerprint.d.ts +23 -0
  188. package/dist/tools/builtins/content-fingerprint.d.ts.map +1 -0
  189. package/dist/tools/builtins/content-fingerprint.js +30 -0
  190. package/dist/tools/builtins/content-fingerprint.js.map +1 -0
  191. package/dist/tools/builtins/edit.d.ts.map +1 -1
  192. package/dist/tools/builtins/edit.js +172 -31
  193. package/dist/tools/builtins/edit.js.map +1 -1
  194. package/dist/tools/builtins/read-file.d.ts.map +1 -1
  195. package/dist/tools/builtins/read-file.js +5 -2
  196. package/dist/tools/builtins/read-file.js.map +1 -1
  197. package/dist/tools/builtins/write-file.d.ts.map +1 -1
  198. package/dist/tools/builtins/write-file.js +75 -27
  199. package/dist/tools/builtins/write-file.js.map +1 -1
  200. package/dist/tools/coordinator/__tests__/ask-user-question.test.js +55 -0
  201. package/dist/tools/coordinator/__tests__/ask-user-question.test.js.map +1 -1
  202. package/dist/tools/coordinator/__tests__/plan-dependencies.test.d.ts +2 -0
  203. package/dist/tools/coordinator/__tests__/plan-dependencies.test.d.ts.map +1 -0
  204. package/dist/tools/coordinator/__tests__/plan-dependencies.test.js +126 -0
  205. package/dist/tools/coordinator/__tests__/plan-dependencies.test.js.map +1 -0
  206. package/dist/tools/coordinator/index.d.ts.map +1 -1
  207. package/dist/tools/coordinator/index.js +75 -5
  208. package/dist/tools/coordinator/index.js.map +1 -1
  209. package/dist/tools/coordinator/plan-dependencies.d.ts +43 -0
  210. package/dist/tools/coordinator/plan-dependencies.d.ts.map +1 -0
  211. package/dist/tools/coordinator/plan-dependencies.js +148 -0
  212. package/dist/tools/coordinator/plan-dependencies.js.map +1 -0
  213. package/dist/types/agent/supervisor.d.ts +15 -0
  214. package/dist/types/agent/supervisor.d.ts.map +1 -1
  215. package/dist/types/connector/core.d.ts +34 -0
  216. package/dist/types/connector/core.d.ts.map +1 -1
  217. package/dist/types/connector/definition.d.ts +10 -0
  218. package/dist/types/connector/definition.d.ts.map +1 -1
  219. package/dist/types/connector/mcp.d.ts +13 -0
  220. package/dist/types/connector/mcp.d.ts.map +1 -1
  221. package/dist/types/rag/retrieval.d.ts +16 -0
  222. package/dist/types/rag/retrieval.d.ts.map +1 -1
  223. package/dist/types/rag/storage.d.ts +9 -0
  224. package/dist/types/rag/storage.d.ts.map +1 -1
  225. package/dist/types/rag/vector.d.ts +11 -0
  226. package/dist/types/rag/vector.d.ts.map +1 -1
  227. package/dist/types/router/task-router.d.ts +19 -0
  228. package/dist/types/router/task-router.d.ts.map +1 -1
  229. package/dist/types/run/prepare-step.d.ts +56 -3
  230. package/dist/types/run/prepare-step.d.ts.map +1 -1
  231. package/dist/types/tool/index.d.ts +17 -1
  232. package/dist/types/tool/index.d.ts.map +1 -1
  233. package/dist/types/tool/index.js.map +1 -1
  234. package/dist/types/toolset/index.d.ts +22 -0
  235. package/dist/types/toolset/index.d.ts.map +1 -1
  236. package/package.json +1 -1
  237. package/src/advisory/__tests__/consultation-context.test.ts +191 -0
  238. package/src/advisory/context.ts +32 -0
  239. package/src/advisory/executor.ts +30 -3
  240. package/src/compaction/__tests__/context-reducer.test.ts +239 -0
  241. package/src/compaction/factory.ts +7 -0
  242. package/src/compaction/index.ts +8 -0
  243. package/src/compaction/interface.ts +13 -0
  244. package/src/compaction/managers/null.ts +3 -0
  245. package/src/compaction/managers/slidingWindow.ts +6 -0
  246. package/src/compaction/managers/structured.ts +10 -0
  247. package/src/compaction/reducer.ts +154 -0
  248. package/src/connector/builtins/__tests__/oauth2-auth.test.ts +73 -0
  249. package/src/connector/builtins/http.test.ts +28 -2
  250. package/src/connector/builtins/http.ts +26 -2
  251. package/src/connector/index.ts +6 -2
  252. package/src/connector/mcp/__tests__/prompts-and-lifecycle.test.ts +286 -0
  253. package/src/connector/mcp/client.ts +95 -0
  254. package/src/connector/mcp/discovery.ts +19 -4
  255. package/src/connector/mcp/index.ts +6 -2
  256. package/src/connector/mcp/server.ts +101 -3
  257. package/src/manager/agent/__tests__/depth-limit-authority.test.ts +74 -0
  258. package/src/plugin/__tests__/discovery-scopes.test.ts +133 -0
  259. package/src/plugin/__tests__/enable-contributions.test.ts +5 -1
  260. package/src/plugin/__tests__/mcp-admission.test.ts +242 -0
  261. package/src/plugin/lifecycle.ts +57 -1
  262. package/src/plugin/loader.ts +57 -3
  263. package/src/public-runtime.ts +16 -1
  264. package/src/public-types.ts +5 -0
  265. package/src/rag/__tests__/namespace-isolation.test.ts +109 -0
  266. package/src/rag/ingestion.ts +1 -0
  267. package/src/rag/retriever.ts +2 -0
  268. package/src/rag/vector-store.ts +5 -0
  269. package/src/registry/tool/execute.ts +123 -119
  270. package/src/runtime/query/__tests__/capability-negotiation.test.ts +69 -0
  271. package/src/runtime/query/__tests__/per-step-skills.test.ts +154 -0
  272. package/src/runtime/query/__tests__/per-step-tool-choice.test.ts +180 -0
  273. package/src/runtime/query/__tests__/resume-run.test.ts +262 -0
  274. package/src/runtime/query/executor.ts +9 -1
  275. package/src/runtime/query/index.ts +39 -0
  276. package/src/runtime/query/iteration/index.ts +130 -34
  277. package/src/runtime/query/iteration/phases/compaction-model-routing.test.ts +125 -0
  278. package/src/runtime/query/iteration/phases/compaction.ts +106 -5
  279. package/src/runtime/query/iteration/phases/context-reducer-dispatch.test.ts +238 -0
  280. package/src/runtime/query/iteration/phases/context.ts +11 -0
  281. package/src/runtime/query/resume-run.ts +93 -0
  282. package/src/telemetry/__tests__/model-call-span.test.ts +189 -0
  283. package/src/telemetry/__tests__/span-closure.test.ts +153 -0
  284. package/src/tools/advisory/index.ts +1 -1
  285. package/src/tools/builtins/__tests__/edit-commits-atomically.test.ts +103 -0
  286. package/src/tools/builtins/__tests__/edit.test.ts +324 -1
  287. package/src/tools/builtins/__tests__/stale-file-refusal.test.ts +258 -0
  288. package/src/tools/builtins/atomic-write-file.ts +30 -2
  289. package/src/tools/builtins/content-fingerprint.ts +33 -0
  290. package/src/tools/builtins/edit.ts +188 -36
  291. package/src/tools/builtins/read-file.ts +5 -2
  292. package/src/tools/builtins/write-file.ts +79 -28
  293. package/src/tools/coordinator/__tests__/ask-user-question.test.ts +68 -0
  294. package/src/tools/coordinator/__tests__/plan-dependencies.test.ts +186 -0
  295. package/src/tools/coordinator/index.ts +116 -42
  296. package/src/tools/coordinator/plan-dependencies.ts +175 -0
  297. package/src/types/agent/supervisor.ts +15 -0
  298. package/src/types/connector/core.ts +34 -0
  299. package/src/types/connector/definition.ts +10 -0
  300. package/src/types/connector/mcp.ts +14 -0
  301. package/src/types/rag/retrieval.ts +16 -0
  302. package/src/types/rag/storage.ts +9 -0
  303. package/src/types/rag/vector.ts +11 -0
  304. package/src/types/router/task-router.ts +19 -0
  305. package/src/types/run/prepare-step.ts +58 -3
  306. package/src/types/tool/index.ts +17 -1
  307. package/src/types/toolset/index.ts +22 -0
@@ -0,0 +1,238 @@
1
+ import { describe, expect, it, vi } from 'vitest'
2
+
3
+ import { WorkingStateManager } from '../../../../compaction/manager.js'
4
+ import type { ContextReducer } from '../../../../compaction/reducer.js'
5
+ import { CompactionConfigSchema } from '../../../../config/runtime.js'
6
+ import type { CompactionConfig } from '../../../../config/runtime.js'
7
+ import type { Message } from '../../../../types/message/index.js'
8
+ import { runCompactionCheck } from './compaction.js'
9
+ import type { IterationContext } from './context.js'
10
+
11
+ /**
12
+ * `strategy` had three values and two behaviours.
13
+ *
14
+ * The runtime asked only `strategy !== 'disabled'`, so `'sliding-window'` —
15
+ * the name a host picks precisely to AVOID paying for summarization — ran the
16
+ * full structured pass, model call included. These tests are about the
17
+ * dispatch rather than the algorithms: that choosing a strategy selects one,
18
+ * that a host-supplied reducer outranks the enum, and that the structured
19
+ * pass does not also run when something else owns reduction.
20
+ */
21
+
22
+ const sys = (content: string): Message => ({ role: 'system', content, timestamp: 1 })
23
+ const user = (content: string): Message => ({ role: 'user', content, timestamp: 1 })
24
+
25
+ function longHistory(): Message[] {
26
+ // Long enough to be over any threshold once the window is small.
27
+ return [sys('prompt'), ...Array.from({ length: 40 }, (_, i) => user(`turn ${i} `.repeat(200)))]
28
+ }
29
+
30
+ interface Harness {
31
+ readonly ctx: IterationContext
32
+ readonly messages: Message[]
33
+ readonly providerCalls: () => number
34
+ readonly warnings: string[]
35
+ }
36
+
37
+ function harness(
38
+ over: Partial<IterationContext> & { compaction?: Partial<CompactionConfig> },
39
+ ): Harness {
40
+ const messages = longHistory()
41
+ const config: CompactionConfig = {
42
+ ...CompactionConfigSchema.parse({}),
43
+ // A tiny window puts the run over the trigger immediately.
44
+ contextWindowTokens: 1_000,
45
+ keepRecentMessages: 4,
46
+ ...over.compaction,
47
+ }
48
+ const warnings: string[] = []
49
+ let providerCalls = 0
50
+
51
+ const ctx = {
52
+ compactionConfig: config,
53
+ workingStateManager: new WorkingStateManager(config),
54
+ runConfig: { model: 'mock-model' },
55
+ runMgr: {
56
+ id: 'run_dispatch',
57
+ messages,
58
+ accumulateUsage: vi.fn(),
59
+ clearLastPromptTokens: vi.fn(),
60
+ },
61
+ provider: {
62
+ // The structured path's only observable side effect that a reducer
63
+ // path must not have: it pays for a summary.
64
+ chat: async () => {
65
+ providerCalls++
66
+ return { content: 'summary', usage: { promptTokens: 1, completionTokens: 1 } }
67
+ },
68
+ },
69
+ log: {
70
+ debug: vi.fn(),
71
+ info: vi.fn(),
72
+ warn: (message: string) => warnings.push(message),
73
+ error: vi.fn(),
74
+ },
75
+ ...over,
76
+ } as unknown as IterationContext
77
+
78
+ return { ctx, messages, providerCalls: () => providerCalls, warnings }
79
+ }
80
+
81
+ describe('choosing a strategy selects one', () => {
82
+ it("'sliding-window' trims and summarizes nothing", async () => {
83
+ const h = harness({ compaction: { strategy: 'sliding-window', llmVerification: true } })
84
+
85
+ await runCompactionCheck(h.ctx)
86
+
87
+ expect(h.messages.length).toBeLessThan(41)
88
+ // The whole reason a host names this strategy.
89
+ expect(h.providerCalls()).toBe(0)
90
+ // Every survivor is verbatim — no `[COMPACTED CONTEXT]` block appears.
91
+ expect(h.messages.some((m) => String(m.content).includes('COMPACTED CONTEXT'))).toBe(false)
92
+ })
93
+
94
+ it("'structured' still summarizes", async () => {
95
+ const h = harness({ compaction: { strategy: 'structured', llmVerification: false } })
96
+
97
+ await runCompactionCheck(h.ctx)
98
+
99
+ expect(h.messages.some((m) => String(m.content).includes('COMPACTED CONTEXT'))).toBe(true)
100
+ })
101
+
102
+ it("'disabled' leaves the history alone", async () => {
103
+ const h = harness({ compaction: { strategy: 'disabled' } })
104
+
105
+ await runCompactionCheck(h.ctx)
106
+
107
+ expect(h.messages.length).toBe(41)
108
+ })
109
+ })
110
+
111
+ describe('a host reducer outranks the enum', () => {
112
+ it('runs instead of the structured pass', async () => {
113
+ // Deliberately NOT a tiny slice: leave more than the structured pass
114
+ // needs to act, so "the reducer ran" and "only the reducer ran" are
115
+ // different observations.
116
+ const reduce: ContextReducer = ({ messages }) => messages.slice(0, 20)
117
+ const h = harness({ contextReducer: reduce, compaction: { strategy: 'structured' } })
118
+
119
+ await runCompactionCheck(h.ctx)
120
+
121
+ expect(h.messages.length).toBe(20)
122
+ expect(h.messages.some((m) => String(m.content).includes('COMPACTED CONTEXT'))).toBe(false)
123
+ })
124
+
125
+ it('is told whether the provider already rejected the prompt', async () => {
126
+ const seen: string[] = []
127
+ const reduce: ContextReducer = ({ messages, reason }) => {
128
+ seen.push(reason)
129
+ return messages.slice(0, 5)
130
+ }
131
+ const h = harness({ contextReducer: reduce })
132
+
133
+ await runCompactionCheck(h.ctx)
134
+ await runCompactionCheck(h.ctx, { force: true })
135
+
136
+ expect(seen).toEqual(['threshold', 'overflow'])
137
+ })
138
+
139
+ it('may be async, so a reducer can call a model of its own', async () => {
140
+ const reduce: ContextReducer = async ({ messages }) => {
141
+ await Promise.resolve()
142
+ return messages.slice(0, 3)
143
+ }
144
+ const h = harness({ contextReducer: reduce })
145
+
146
+ await runCompactionCheck(h.ctx)
147
+
148
+ expect(h.messages.length).toBe(3)
149
+ })
150
+
151
+ it('gets the window it has to fit, not the run token budget', async () => {
152
+ let saw = 0
153
+ const reduce: ContextReducer = ({ contextWindowTokens, messages }) => {
154
+ saw = contextWindowTokens
155
+ return messages.slice(0, 5)
156
+ }
157
+ const h = harness({ contextReducer: reduce, compaction: { contextWindowTokens: 12_345 } })
158
+
159
+ await runCompactionCheck(h.ctx)
160
+
161
+ expect(saw).toBe(12_345)
162
+ })
163
+ })
164
+
165
+ describe('a reducer that cannot be trusted is not obeyed', () => {
166
+ it('keeps the full history when the reducer throws', async () => {
167
+ const reduce: ContextReducer = () => {
168
+ throw new Error('boom')
169
+ }
170
+ const h = harness({ contextReducer: reduce })
171
+
172
+ await runCompactionCheck(h.ctx)
173
+
174
+ // Fail open: a broken reduction hook should not take down a healthy run,
175
+ // the same way a broken `prepareStep` does not.
176
+ expect(h.messages.length).toBe(41)
177
+ expect(h.warnings.some((w) => w.includes('threw'))).toBe(true)
178
+ })
179
+
180
+ it('refuses a result that orphans a tool result', async () => {
181
+ const h = harness({
182
+ contextReducer: ({ messages }) => [
183
+ messages[0] as Message,
184
+ { role: 'tool', content: 'ok', timestamp: 1, toolCallId: 'nobody' } as Message,
185
+ ],
186
+ })
187
+
188
+ await runCompactionCheck(h.ctx)
189
+
190
+ // Installing this would trade a nameable "your reducer split a tool
191
+ // pair" for an opaque provider rejection a call later.
192
+ expect(h.messages.length).toBe(41)
193
+ expect(h.warnings.some((w) => w.includes('split a tool pair'))).toBe(true)
194
+ })
195
+
196
+ it('ignores a reducer that returns the history unchanged', async () => {
197
+ const h = harness({ contextReducer: ({ messages }) => messages })
198
+
199
+ await runCompactionCheck(h.ctx)
200
+
201
+ expect(h.messages.length).toBe(41)
202
+ })
203
+
204
+ it('leaves the history alone when the reducer declines', async () => {
205
+ const h = harness({ contextReducer: () => undefined })
206
+
207
+ await runCompactionCheck(h.ctx)
208
+
209
+ expect(h.messages.length).toBe(41)
210
+ })
211
+ })
212
+
213
+ describe('the threshold still governs a reducer', () => {
214
+ it('does not run one below the trigger', async () => {
215
+ const reduce = vi.fn(() => undefined)
216
+ const h = harness({
217
+ contextReducer: reduce,
218
+ // A window far larger than the history.
219
+ compaction: { contextWindowTokens: 10_000_000 },
220
+ })
221
+
222
+ await runCompactionCheck(h.ctx)
223
+
224
+ expect(reduce).not.toHaveBeenCalled()
225
+ })
226
+
227
+ it('runs one below the trigger when the provider forced the issue', async () => {
228
+ const reduce = vi.fn(() => undefined)
229
+ const h = harness({
230
+ contextReducer: reduce,
231
+ compaction: { contextWindowTokens: 10_000_000 },
232
+ })
233
+
234
+ await runCompactionCheck(h.ctx, { force: true })
235
+
236
+ expect(reduce).toHaveBeenCalledOnce()
237
+ })
238
+ })
@@ -1,6 +1,7 @@
1
1
  import type { AdvisoryContext } from '../../../../advisory/context.js'
2
2
  import type { AgentBus } from '../../../../bus/index.js'
3
3
  import type { WorkingStateManager } from '../../../../compaction/manager.js'
4
+ import type { ContextReducer } from '../../../../compaction/reducer.js'
4
5
  import type { CompactionConfig } from '../../../../config/runtime.js'
5
6
  import type { PlanManager } from '../../../../manager/plan/lifecycle.js'
6
7
  import type { RunPersistence } from '../../../../manager/run/persistence.js'
@@ -14,6 +15,7 @@ import type {
14
15
  } from '../../../../types/hitl/index.js'
15
16
  import type { TaskId } from '../../../../types/ids/index.js'
16
17
  import type { LLMProvider } from '../../../../types/provider/index.js'
18
+ import type { TaskRouterConfig } from '../../../../types/router/index.js'
17
19
  import type { ReviewAnswer } from '../../../../types/run/answer-review.js'
18
20
  import type {
19
21
  AgentRunConfig,
@@ -104,10 +106,19 @@ export interface IterationContext {
104
106
  */
105
107
  readonly toolGrants?: ToolGrantSet
106
108
 
109
+ /** Per-task model overrides. Consulted for the compaction summary call. */
110
+ readonly taskRouter?: TaskRouterConfig
111
+
107
112
  readonly compactionConfig?: CompactionConfig
108
113
 
109
114
  readonly workingStateManager?: WorkingStateManager
110
115
 
116
+ /**
117
+ * Host-supplied context reduction. Outranks `compactionConfig.strategy`
118
+ * and replaces the structured pass for this run.
119
+ */
120
+ readonly contextReducer?: ContextReducer
121
+
111
122
  readonly workingMemoryProvider?: WorkingMemoryProvider
112
123
 
113
124
  readonly advisoryCtx?: AdvisoryContext
@@ -0,0 +1,93 @@
1
+ import type { PendingDecision } from '../../types/hitl/index.js'
2
+ import type { CheckpointStore } from '../../types/run/checkpoint-store.js'
3
+ import type { Run } from '../../types/run/entity.js'
4
+ import type { RunState } from '../../types/run/state.js'
5
+ import { type QueryParams, drainQuery } from './index.js'
6
+ import { type RunStateScope, loadRunState } from './run-state.js'
7
+
8
+ /**
9
+ * What a resume attempt found.
10
+ *
11
+ * Three outcomes rather than a nullable `Run`, because the two failures
12
+ * mean opposite things to a caller. "No checkpoint" is a dead end — there
13
+ * is nothing to continue and starting fresh would be a different run
14
+ * wearing the same id. "Awaiting decision" is the run working as designed:
15
+ * it parked on a question, and continuing means answering it first.
16
+ */
17
+ export type ResumeOutcome =
18
+ | { readonly resumed: false; readonly reason: 'no-checkpoint' }
19
+ | {
20
+ readonly resumed: false
21
+ readonly reason: 'awaiting-decision'
22
+ /** What to put in front of a human. */
23
+ readonly pending: PendingDecision
24
+ readonly state: RunState
25
+ }
26
+ | { readonly resumed: true; readonly run: Run; readonly state: RunState }
27
+
28
+ /**
29
+ * The half of a run that cannot be serialized, plus where to look.
30
+ *
31
+ * `messages` is deliberately absent. On resume the history comes from the
32
+ * checkpoint and any passed messages would be discarded, so accepting them
33
+ * would invite a caller to hand over a transcript that is silently ignored.
34
+ */
35
+ export interface ResumeRunParams
36
+ extends Omit<QueryParams, 'messages' | 'runId' | 'resumeFromCheckpoint'> {
37
+ /** Identifies the run inside the checkpoint store. */
38
+ readonly scope: RunStateScope
39
+ /** Required to find the checkpoint; also threaded into the resumed run. */
40
+ readonly checkpointStore: CheckpointStore
41
+ /**
42
+ * Resume a specific checkpoint instead of the one the store would pick.
43
+ * Absent means the parked checkpoint if there is one, else the newest.
44
+ */
45
+ readonly checkpointId?: RunState['checkpointId']
46
+ }
47
+
48
+ /**
49
+ * Continue a run that a different process started.
50
+ *
51
+ * Everything this needs already existed — `CheckpointManager` writes the
52
+ * history, budgets, working state, trace context and any park;
53
+ * `loadRunState` reads them back; `query` accepts `runId` +
54
+ * `resumeFromCheckpoint` and restores all of it. What was missing was the
55
+ * few lines that join them, so every host wrote their own — and in this
56
+ * repo nothing outside the SDK ever did, which means the whole path shipped
57
+ * untravelled.
58
+ *
59
+ * The division of labour is the one the mechanism already implies: the
60
+ * CALLER brings what cannot be serialized — the provider client, the tool
61
+ * registry, the sandbox, the working directory — and the STORE brings the
62
+ * state. A snapshot deliberately holds no socket, no client and no open
63
+ * file, so it could never have carried the first half.
64
+ *
65
+ * Refusing beats guessing at both failure points. A missing checkpoint does
66
+ * not silently become a fresh run under a recycled id, and a park is not
67
+ * resumed past without the answer it is waiting for.
68
+ */
69
+ export async function resumeRun(params: ResumeRunParams): Promise<ResumeOutcome> {
70
+ const { scope, checkpointStore, checkpointId, pendingDecision, ...rest } = params
71
+
72
+ const state = await loadRunState(checkpointStore, scope, checkpointId)
73
+ if (!state?.checkpointId) return { resumed: false, reason: 'no-checkpoint' }
74
+
75
+ // A park is outstanding until it is answered. `resolvedAt` is what marks
76
+ // an answered one, so a checkpoint that carries a resolved decision is an
77
+ // ordinary resume, not a question waiting on anybody.
78
+ const outstanding = state.pending && !state.pending.resolvedAt ? state.pending : undefined
79
+ if (outstanding && !pendingDecision) {
80
+ return { resumed: false, reason: 'awaiting-decision', pending: outstanding, state }
81
+ }
82
+
83
+ const run = await drainQuery({
84
+ ...rest,
85
+ messages: [],
86
+ runId: state.runId,
87
+ resumeFromCheckpoint: state.checkpointId,
88
+ checkpointStore,
89
+ ...(pendingDecision ? { pendingDecision } : {}),
90
+ } as QueryParams)
91
+
92
+ return { resumed: true, run, state }
93
+ }
@@ -0,0 +1,189 @@
1
+ import { mkdtemp, rm } from 'node:fs/promises'
2
+ import { tmpdir } from 'node:os'
3
+ import { join } from 'node:path'
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
5
+
6
+ /**
7
+ * There was no span around the model call at all.
8
+ *
9
+ * `chatSpanName` shipped in the telemetry attributes with zero call sites,
10
+ * so a run's traces carried no LLM latency whatsoever — and the one thing
11
+ * anybody opens a trace to find, which turn was slow and why, was the one
12
+ * thing missing from it. The token counts landed on the iteration span
13
+ * instead of the operation that produced them.
14
+ */
15
+
16
+ interface Recorded {
17
+ name: string
18
+ attributes: Record<string, unknown>
19
+ ended: boolean
20
+ status?: number
21
+ }
22
+
23
+ const spans: Recorded[] = []
24
+
25
+ vi.mock('../runtime-accessors.js', () => ({
26
+ getTracer: () => ({
27
+ startSpan: (name: string) => {
28
+ const rec: Recorded = { name, attributes: {}, ended: false }
29
+ spans.push(rec)
30
+ return {
31
+ setAttributes: (a: Record<string, unknown>) => Object.assign(rec.attributes, a),
32
+ setAttribute: (k: string, v: unknown) => {
33
+ rec.attributes[k] = v
34
+ },
35
+ setStatus: (s: { code: number }) => {
36
+ rec.status = s.code
37
+ },
38
+ recordException: () => undefined,
39
+ addEvent: () => undefined,
40
+ end: () => {
41
+ rec.ended = true
42
+ },
43
+ spanContext: () => ({ traceId: 't', spanId: 's', traceFlags: 1 }),
44
+ isRecording: () => true,
45
+ updateName: () => undefined,
46
+ }
47
+ },
48
+ startActiveSpan: (_n: string, _o: unknown, _c: unknown, fn: (s: unknown) => unknown) =>
49
+ fn({
50
+ setAttributes: () => undefined,
51
+ setStatus: () => undefined,
52
+ recordException: () => undefined,
53
+ end: () => undefined,
54
+ spanContext: () => ({ traceId: 't', spanId: 's', traceFlags: 1 }),
55
+ isRecording: () => true,
56
+ }),
57
+ }),
58
+ getMeter: () => ({
59
+ createCounter: () => ({ add: () => undefined }),
60
+ createHistogram: () => ({ record: () => undefined }),
61
+ }),
62
+ }))
63
+
64
+ let workdirs: string[] = []
65
+
66
+ beforeEach(() => {
67
+ spans.length = 0
68
+ })
69
+
70
+ afterEach(async () => {
71
+ await Promise.all(workdirs.map((d) => rm(d, { recursive: true, force: true })))
72
+ workdirs = []
73
+ })
74
+
75
+ async function runOnce(turns: { text?: string }[]): Promise<void> {
76
+ const { MockLLMProvider } = await import('../../provider/mock.js')
77
+ const { ToolRegistry } = await import('../../registry/tool/execute.js')
78
+ const { drainQuery } = await import('../../runtime/query/index.js')
79
+ const { createUserMessage } = await import('../../types/message/index.js')
80
+
81
+ const dir = await mkdtemp(join(tmpdir(), 'namzu-chatspan-'))
82
+ workdirs.push(dir)
83
+
84
+ await drainQuery({
85
+ provider: new MockLLMProvider({ turns: turns as never }),
86
+ tools: new ToolRegistry(),
87
+ runConfig: {
88
+ model: 'mock-model',
89
+ timeoutMs: 30_000,
90
+ tokenBudget: 100_000,
91
+ maxIterations: 3,
92
+ maxResponseTokens: 256,
93
+ temperature: 0.3,
94
+ },
95
+ agentId: 'agent_cs',
96
+ agentName: 'Chat Span Agent',
97
+ workingDirectory: dir,
98
+ sessionId: 'ses_cs',
99
+ threadId: 'thd_cs',
100
+ projectId: 'prj_cs',
101
+ tenantId: 'tnt_cs',
102
+ messages: [createUserMessage('go')],
103
+ } as never)
104
+ }
105
+
106
+ const chatSpans = () => spans.filter((s) => s.name.startsWith('chat '))
107
+
108
+ describe('the model call has a span of its own', () => {
109
+ it('opens one named for the model', async () => {
110
+ await runOnce([{ text: 'done' }])
111
+
112
+ // One per model call. A run makes at least one, and a forced-final
113
+ // turn makes another — both are model calls and both deserve a span.
114
+ expect(chatSpans().length).toBeGreaterThanOrEqual(1)
115
+ for (const s of chatSpans()) expect(s.name).toBe('chat mock-model')
116
+ })
117
+
118
+ it('closes it', async () => {
119
+ await runOnce([{ text: 'done' }])
120
+
121
+ for (const s of chatSpans()) expect(s.ended).toBe(true)
122
+ })
123
+
124
+ it('carries the request parameters that were actually used', async () => {
125
+ await runOnce([{ text: 'done' }])
126
+
127
+ const attrs = chatSpans()[0]?.attributes ?? {}
128
+ expect(attrs['gen_ai.operation.name']).toBe('chat')
129
+ expect(attrs['gen_ai.request.model']).toBe('mock-model')
130
+ expect(attrs['gen_ai.request.temperature']).toBe(0.3)
131
+ expect(attrs['gen_ai.request.max_tokens']).toBe(256)
132
+ })
133
+
134
+ it('carries the usage on the call that produced it', async () => {
135
+ await runOnce([{ text: 'done' }])
136
+
137
+ const attrs = chatSpans()[0]?.attributes ?? {}
138
+ // These previously landed only on the iteration span — one level up
139
+ // from the operation that spent the tokens.
140
+ expect(attrs).toHaveProperty('gen_ai.usage.input_tokens')
141
+ expect(attrs).toHaveProperty('gen_ai.usage.output_tokens')
142
+ })
143
+
144
+ it('records what the response said it was, not only what was asked for', async () => {
145
+ await runOnce([{ text: 'done' }])
146
+
147
+ const attrs = chatSpans()[0]?.attributes ?? {}
148
+ // A provider may answer on a different model than the alias asked
149
+ // for, and the response id is how a trace is matched to a provider's
150
+ // own logs. Both constants existed and neither was ever set.
151
+ expect(attrs['gen_ai.response.model']).toBe('mock-model')
152
+ expect(attrs['gen_ai.response.id']).toBeDefined()
153
+ })
154
+
155
+ it('records the finish reason as an array, per the convention', async () => {
156
+ await runOnce([{ text: 'done' }])
157
+
158
+ // One call can finish several ways when a provider returns more than
159
+ // one choice, which is why the convention makes it a list.
160
+ expect(Array.isArray(chatSpans()[0]?.attributes['gen_ai.response.finish_reasons'])).toBe(true)
161
+ })
162
+
163
+ it('reports cache tokens, which had constants and no producer', async () => {
164
+ await runOnce([{ text: 'done' }])
165
+
166
+ const attrs = chatSpans()[0]?.attributes ?? {}
167
+ expect(attrs).toHaveProperty('namzu.cache.read_tokens')
168
+ expect(attrs).toHaveProperty('namzu.cache.write_tokens')
169
+ })
170
+
171
+ it('opens one per turn, not one per run', async () => {
172
+ await runOnce([{ text: 'first' }, { text: 'second' }])
173
+
174
+ // A run with several turns is exactly the case a trace is opened for.
175
+ expect(chatSpans().length).toBeGreaterThanOrEqual(1)
176
+ })
177
+
178
+ it('nests under the iteration span rather than emitting as a root', async () => {
179
+ await runOnce([{ text: 'done' }])
180
+
181
+ // The iteration span is created first; the chat span is parented to it
182
+ // explicitly because this body is an async generator and the ambient
183
+ // context at resume time belongs to the consumer.
184
+ const iterationIndex = spans.findIndex((s) => s.name.includes('iteration'))
185
+ const chatIndex = spans.findIndex((s) => s.name.startsWith('chat '))
186
+ expect(iterationIndex).toBeGreaterThanOrEqual(0)
187
+ expect(chatIndex).toBeGreaterThan(iterationIndex)
188
+ })
189
+ })