@namzu/sdk 6.2.0 → 8.0.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 (251) hide show
  1. package/CHANGELOG.md +677 -0
  2. package/dist/agents/ReactiveAgent.d.ts.map +1 -1
  3. package/dist/agents/ReactiveAgent.js +5 -0
  4. package/dist/agents/ReactiveAgent.js.map +1 -1
  5. package/dist/agents/SupervisorAgent.d.ts.map +1 -1
  6. package/dist/agents/SupervisorAgent.js +172 -158
  7. package/dist/agents/SupervisorAgent.js.map +1 -1
  8. package/dist/agents/__tests__/supervisor-inbox-scope.test.d.ts +2 -0
  9. package/dist/agents/__tests__/supervisor-inbox-scope.test.d.ts.map +1 -0
  10. package/dist/agents/__tests__/supervisor-inbox-scope.test.js +125 -0
  11. package/dist/agents/__tests__/supervisor-inbox-scope.test.js.map +1 -0
  12. package/dist/agents/runAgent.d.ts +19 -1
  13. package/dist/agents/runAgent.d.ts.map +1 -1
  14. package/dist/agents/runAgent.js +2 -0
  15. package/dist/agents/runAgent.js.map +1 -1
  16. package/dist/bridge/a2a/mapper.d.ts.map +1 -1
  17. package/dist/bridge/a2a/mapper.js +4 -0
  18. package/dist/bridge/a2a/mapper.js.map +1 -1
  19. package/dist/bridge/sse/mapper.d.ts.map +1 -1
  20. package/dist/bridge/sse/mapper.js +24 -0
  21. package/dist/bridge/sse/mapper.js.map +1 -1
  22. package/dist/connector/mcp/__tests__/positional-arrays.test.d.ts +2 -0
  23. package/dist/connector/mcp/__tests__/positional-arrays.test.d.ts.map +1 -0
  24. package/dist/connector/mcp/__tests__/positional-arrays.test.js +142 -0
  25. package/dist/connector/mcp/__tests__/positional-arrays.test.js.map +1 -0
  26. package/dist/connector/mcp/adapter.d.ts.map +1 -1
  27. package/dist/connector/mcp/adapter.js +123 -8
  28. package/dist/connector/mcp/adapter.js.map +1 -1
  29. package/dist/constants/agent/index.d.ts +5 -0
  30. package/dist/constants/agent/index.d.ts.map +1 -1
  31. package/dist/constants/agent/index.js +5 -0
  32. package/dist/constants/agent/index.js.map +1 -1
  33. package/dist/constants/plugin/index.d.ts +15 -0
  34. package/dist/constants/plugin/index.d.ts.map +1 -1
  35. package/dist/constants/plugin/index.js +15 -0
  36. package/dist/constants/plugin/index.js.map +1 -1
  37. package/dist/contracts/api.d.ts +1 -1
  38. package/dist/contracts/api.d.ts.map +1 -1
  39. package/dist/gateway/__tests__/completion-inbox.test.js +292 -2
  40. package/dist/gateway/__tests__/completion-inbox.test.js.map +1 -1
  41. package/dist/gateway/completion-inbox.d.ts +94 -6
  42. package/dist/gateway/completion-inbox.d.ts.map +1 -1
  43. package/dist/gateway/completion-inbox.js +235 -15
  44. package/dist/gateway/completion-inbox.js.map +1 -1
  45. package/dist/gateway/local.d.ts +11 -0
  46. package/dist/gateway/local.d.ts.map +1 -1
  47. package/dist/gateway/local.js +27 -1
  48. package/dist/gateway/local.js.map +1 -1
  49. package/dist/manager/agent/lifecycle.d.ts.map +1 -1
  50. package/dist/manager/agent/lifecycle.js +6 -0
  51. package/dist/manager/agent/lifecycle.js.map +1 -1
  52. package/dist/manager/run/persistence.d.ts +8 -0
  53. package/dist/manager/run/persistence.d.ts.map +1 -1
  54. package/dist/manager/run/persistence.js +12 -0
  55. package/dist/manager/run/persistence.js.map +1 -1
  56. package/dist/provider/thinking-support.d.ts +2 -1
  57. package/dist/provider/thinking-support.d.ts.map +1 -1
  58. package/dist/provider/thinking-support.js +14 -0
  59. package/dist/provider/thinking-support.js.map +1 -1
  60. package/dist/public-runtime.d.ts +1 -1
  61. package/dist/public-runtime.d.ts.map +1 -1
  62. package/dist/public-runtime.js +9 -1
  63. package/dist/public-runtime.js.map +1 -1
  64. package/dist/run/reporter.d.ts.map +1 -1
  65. package/dist/run/reporter.js +11 -0
  66. package/dist/run/reporter.js.map +1 -1
  67. package/dist/runtime/query/__tests__/completion-does-not-erase-the-answer.test.d.ts +2 -0
  68. package/dist/runtime/query/__tests__/completion-does-not-erase-the-answer.test.d.ts.map +1 -0
  69. package/dist/runtime/query/__tests__/completion-does-not-erase-the-answer.test.js +142 -0
  70. package/dist/runtime/query/__tests__/completion-does-not-erase-the-answer.test.js.map +1 -0
  71. package/dist/runtime/query/__tests__/completion-notification.test.js +414 -32
  72. package/dist/runtime/query/__tests__/completion-notification.test.js.map +1 -1
  73. package/dist/runtime/query/__tests__/context-size-on-the-wire.test.d.ts +2 -0
  74. package/dist/runtime/query/__tests__/context-size-on-the-wire.test.d.ts.map +1 -0
  75. package/dist/runtime/query/__tests__/context-size-on-the-wire.test.js +100 -0
  76. package/dist/runtime/query/__tests__/context-size-on-the-wire.test.js.map +1 -0
  77. package/dist/runtime/query/__tests__/context.test.js +18 -0
  78. package/dist/runtime/query/__tests__/context.test.js.map +1 -1
  79. package/dist/runtime/query/__tests__/effort-reaches-the-wire.test.d.ts +2 -0
  80. package/dist/runtime/query/__tests__/effort-reaches-the-wire.test.d.ts.map +1 -0
  81. package/dist/runtime/query/__tests__/effort-reaches-the-wire.test.js +118 -0
  82. package/dist/runtime/query/__tests__/effort-reaches-the-wire.test.js.map +1 -0
  83. package/dist/runtime/query/__tests__/tool-timeout.test.js +34 -0
  84. package/dist/runtime/query/__tests__/tool-timeout.test.js.map +1 -1
  85. package/dist/runtime/query/context.d.ts.map +1 -1
  86. package/dist/runtime/query/context.js +16 -1
  87. package/dist/runtime/query/context.js.map +1 -1
  88. package/dist/runtime/query/executor.d.ts.map +1 -1
  89. package/dist/runtime/query/executor.js +11 -1
  90. package/dist/runtime/query/executor.js.map +1 -1
  91. package/dist/runtime/query/guard.d.ts +28 -0
  92. package/dist/runtime/query/guard.d.ts.map +1 -1
  93. package/dist/runtime/query/guard.js +31 -0
  94. package/dist/runtime/query/guard.js.map +1 -1
  95. package/dist/runtime/query/iteration/__tests__/settle-grace.test.d.ts +2 -0
  96. package/dist/runtime/query/iteration/__tests__/settle-grace.test.d.ts.map +1 -0
  97. package/dist/runtime/query/iteration/__tests__/settle-grace.test.js +226 -0
  98. package/dist/runtime/query/iteration/__tests__/settle-grace.test.js.map +1 -0
  99. package/dist/runtime/query/iteration/index.d.ts +92 -0
  100. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  101. package/dist/runtime/query/iteration/index.js +818 -565
  102. package/dist/runtime/query/iteration/index.js.map +1 -1
  103. package/dist/runtime/query/iteration/phases/__tests__/compaction-declined.test.d.ts +2 -0
  104. package/dist/runtime/query/iteration/phases/__tests__/compaction-declined.test.d.ts.map +1 -0
  105. package/dist/runtime/query/iteration/phases/__tests__/compaction-declined.test.js +95 -0
  106. package/dist/runtime/query/iteration/phases/__tests__/compaction-declined.test.js.map +1 -0
  107. package/dist/runtime/query/iteration/phases/compaction.d.ts +34 -0
  108. package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
  109. package/dist/runtime/query/iteration/phases/compaction.js +61 -4
  110. package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
  111. package/dist/telemetry/__tests__/model-call-span.test.js +22 -4
  112. package/dist/telemetry/__tests__/model-call-span.test.js.map +1 -1
  113. package/dist/telemetry/__tests__/span-closure.test.js +12 -5
  114. package/dist/telemetry/__tests__/span-closure.test.js.map +1 -1
  115. package/dist/tools/__tests__/untrusted-envelope.test.js +16 -0
  116. package/dist/tools/__tests__/untrusted-envelope.test.js.map +1 -1
  117. package/dist/tools/coordinator/__tests__/completion-delivery.test.js +117 -0
  118. package/dist/tools/coordinator/__tests__/completion-delivery.test.js.map +1 -1
  119. package/dist/tools/coordinator/__tests__/task-list.test.js +57 -0
  120. package/dist/tools/coordinator/__tests__/task-list.test.js.map +1 -1
  121. package/dist/tools/coordinator/__tests__/wait-with-idle-bound.test.d.ts +2 -0
  122. package/dist/tools/coordinator/__tests__/wait-with-idle-bound.test.d.ts.map +1 -0
  123. package/dist/tools/coordinator/__tests__/wait-with-idle-bound.test.js +193 -0
  124. package/dist/tools/coordinator/__tests__/wait-with-idle-bound.test.js.map +1 -0
  125. package/dist/tools/coordinator/index.d.ts +19 -0
  126. package/dist/tools/coordinator/index.d.ts.map +1 -1
  127. package/dist/tools/coordinator/index.js +191 -71
  128. package/dist/tools/coordinator/index.js.map +1 -1
  129. package/dist/tools/coordinator/wait-with-idle-bound.d.ts +66 -0
  130. package/dist/tools/coordinator/wait-with-idle-bound.d.ts.map +1 -0
  131. package/dist/tools/coordinator/wait-with-idle-bound.js +78 -0
  132. package/dist/tools/coordinator/wait-with-idle-bound.js.map +1 -0
  133. package/dist/tools/untrusted-envelope.d.ts.map +1 -1
  134. package/dist/tools/untrusted-envelope.js +9 -1
  135. package/dist/tools/untrusted-envelope.js.map +1 -1
  136. package/dist/types/agent/base.d.ts +16 -0
  137. package/dist/types/agent/base.d.ts.map +1 -1
  138. package/dist/types/agent/gateway.d.ts +41 -0
  139. package/dist/types/agent/gateway.d.ts.map +1 -1
  140. package/dist/types/agent/lifecycle-event.d.ts +9 -1
  141. package/dist/types/agent/lifecycle-event.d.ts.map +1 -1
  142. package/dist/types/agent/task.d.ts +5 -0
  143. package/dist/types/agent/task.d.ts.map +1 -1
  144. package/dist/types/hitl/index.d.ts +10 -0
  145. package/dist/types/hitl/index.d.ts.map +1 -1
  146. package/dist/types/hitl/index.js.map +1 -1
  147. package/dist/types/probe/registry.d.ts +6 -0
  148. package/dist/types/probe/registry.d.ts.map +1 -1
  149. package/dist/types/provider/interface.d.ts +35 -0
  150. package/dist/types/provider/interface.d.ts.map +1 -1
  151. package/dist/types/run/config.d.ts +25 -0
  152. package/dist/types/run/config.d.ts.map +1 -1
  153. package/dist/types/run/entity.d.ts +16 -0
  154. package/dist/types/run/entity.d.ts.map +1 -1
  155. package/dist/types/run/events.d.ts +75 -0
  156. package/dist/types/run/events.d.ts.map +1 -1
  157. package/dist/types/run/events.js.map +1 -1
  158. package/dist/types/run/prepare-step.d.ts +17 -2
  159. package/dist/types/run/prepare-step.d.ts.map +1 -1
  160. package/dist/types/verification/index.d.ts +98 -0
  161. package/dist/types/verification/index.d.ts.map +1 -1
  162. package/dist/types/verification/index.js +10 -0
  163. package/dist/types/verification/index.js.map +1 -1
  164. package/dist/utils/__tests__/abort-reason.test.d.ts +2 -0
  165. package/dist/utils/__tests__/abort-reason.test.d.ts.map +1 -0
  166. package/dist/utils/__tests__/abort-reason.test.js +48 -0
  167. package/dist/utils/__tests__/abort-reason.test.js.map +1 -0
  168. package/dist/utils/abort.d.ts +26 -0
  169. package/dist/utils/abort.d.ts.map +1 -1
  170. package/dist/utils/abort.js +34 -0
  171. package/dist/utils/abort.js.map +1 -1
  172. package/dist/verification/__tests__/argument-pattern.test.d.ts +2 -0
  173. package/dist/verification/__tests__/argument-pattern.test.d.ts.map +1 -0
  174. package/dist/verification/__tests__/argument-pattern.test.js +122 -0
  175. package/dist/verification/__tests__/argument-pattern.test.js.map +1 -0
  176. package/dist/verification/__tests__/rule-order-and-reason.test.d.ts +2 -0
  177. package/dist/verification/__tests__/rule-order-and-reason.test.d.ts.map +1 -0
  178. package/dist/verification/__tests__/rule-order-and-reason.test.js +126 -0
  179. package/dist/verification/__tests__/rule-order-and-reason.test.js.map +1 -0
  180. package/dist/verification/gate.d.ts +17 -1
  181. package/dist/verification/gate.d.ts.map +1 -1
  182. package/dist/verification/gate.js +102 -2
  183. package/dist/verification/gate.js.map +1 -1
  184. package/dist/verification/index.d.ts +1 -1
  185. package/dist/verification/index.d.ts.map +1 -1
  186. package/dist/verification/index.js +1 -1
  187. package/dist/verification/index.js.map +1 -1
  188. package/dist/verification/rules.d.ts.map +1 -1
  189. package/dist/verification/rules.js +27 -0
  190. package/dist/verification/rules.js.map +1 -1
  191. package/package.json +1 -1
  192. package/src/agents/ReactiveAgent.ts +5 -0
  193. package/src/agents/SupervisorAgent.ts +175 -162
  194. package/src/agents/__tests__/supervisor-inbox-scope.test.ts +149 -0
  195. package/src/agents/runAgent.ts +22 -1
  196. package/src/bridge/a2a/mapper.ts +4 -0
  197. package/src/bridge/sse/mapper.ts +25 -0
  198. package/src/connector/mcp/__tests__/positional-arrays.test.ts +183 -0
  199. package/src/connector/mcp/adapter.ts +131 -7
  200. package/src/constants/agent/index.ts +5 -0
  201. package/src/constants/plugin/index.ts +15 -0
  202. package/src/contracts/api.ts +1 -0
  203. package/src/gateway/__tests__/completion-inbox.test.ts +348 -2
  204. package/src/gateway/completion-inbox.ts +248 -16
  205. package/src/gateway/local.ts +26 -1
  206. package/src/manager/agent/lifecycle.ts +6 -0
  207. package/src/manager/run/persistence.ts +12 -0
  208. package/src/provider/thinking-support.ts +19 -2
  209. package/src/public-runtime.ts +9 -0
  210. package/src/run/reporter.ts +12 -0
  211. package/src/runtime/query/__tests__/completion-does-not-erase-the-answer.test.ts +163 -0
  212. package/src/runtime/query/__tests__/completion-notification.test.ts +486 -34
  213. package/src/runtime/query/__tests__/context-size-on-the-wire.test.ts +122 -0
  214. package/src/runtime/query/__tests__/context.test.ts +24 -0
  215. package/src/runtime/query/__tests__/effort-reaches-the-wire.test.ts +135 -0
  216. package/src/runtime/query/__tests__/tool-timeout.test.ts +38 -0
  217. package/src/runtime/query/context.ts +16 -1
  218. package/src/runtime/query/executor.ts +11 -1
  219. package/src/runtime/query/guard.ts +32 -0
  220. package/src/runtime/query/iteration/__tests__/settle-grace.test.ts +265 -0
  221. package/src/runtime/query/iteration/index.ts +906 -635
  222. package/src/runtime/query/iteration/phases/__tests__/compaction-declined.test.ts +124 -0
  223. package/src/runtime/query/iteration/phases/compaction.ts +83 -10
  224. package/src/telemetry/__tests__/model-call-span.test.ts +22 -5
  225. package/src/telemetry/__tests__/span-closure.test.ts +12 -5
  226. package/src/tools/__tests__/untrusted-envelope.test.ts +23 -0
  227. package/src/tools/coordinator/__tests__/completion-delivery.test.ts +147 -0
  228. package/src/tools/coordinator/__tests__/task-list.test.ts +72 -0
  229. package/src/tools/coordinator/__tests__/wait-with-idle-bound.test.ts +247 -0
  230. package/src/tools/coordinator/index.ts +205 -78
  231. package/src/tools/coordinator/wait-with-idle-bound.ts +142 -0
  232. package/src/tools/untrusted-envelope.ts +9 -1
  233. package/src/types/agent/base.ts +17 -0
  234. package/src/types/agent/gateway.ts +42 -0
  235. package/src/types/agent/lifecycle-event.ts +7 -0
  236. package/src/types/agent/task.ts +5 -0
  237. package/src/types/hitl/index.ts +10 -0
  238. package/src/types/probe/registry.ts +6 -0
  239. package/src/types/provider/interface.ts +39 -0
  240. package/src/types/run/config.ts +26 -0
  241. package/src/types/run/entity.ts +17 -0
  242. package/src/types/run/events.ts +75 -0
  243. package/src/types/run/prepare-step.ts +17 -2
  244. package/src/types/verification/index.ts +61 -0
  245. package/src/utils/__tests__/abort-reason.test.ts +56 -0
  246. package/src/utils/abort.ts +34 -0
  247. package/src/verification/__tests__/argument-pattern.test.ts +158 -0
  248. package/src/verification/__tests__/rule-order-and-reason.test.ts +149 -0
  249. package/src/verification/gate.ts +106 -3
  250. package/src/verification/index.ts +1 -1
  251. package/src/verification/rules.ts +28 -0
@@ -185,177 +185,190 @@ export class SupervisorAgent extends AbstractAgent<SupervisorAgentConfig, Superv
185
185
  const completionInbox = new CompletionInbox()
186
186
  completionInbox.attach(gateway)
187
187
 
188
- const coordinatorToolDefs = buildCoordinatorTools({
189
- gateway,
190
- completionInbox,
191
- workingDirectory: input.workingDirectory,
192
- runtimeContext: input.runtimeContext,
193
- allowedAgentIds: config.agentIds,
194
- // The only hop between the config and the decision. Omit it and
195
- // everything still compiles: the field is settable, documented, and
196
- // read by nothing — which is the shape of a declaration this repo
197
- // has had to go and delete before.
198
- allowDelegation: config.allowDelegation,
199
- taskStore: input.taskStore,
200
- runId,
201
- getPlanManager: () => planManagerRef,
202
- onTaskLaunched: (agentTaskId, meta) => {
203
- launchedTasks.set(agentTaskId, meta)
204
- },
205
- // With a resume handler present the coordinator surface gains
206
- // ask_user_question — the model can park the run on a question
207
- // routed through the same HITL channel as plan approvals.
208
- ...(config.resumeHandler ? { resumeHandler: config.resumeHandler } : {}),
209
- questionParks,
210
- pendingAnswers,
211
- })
188
+ // From here to the return in a try/finally, so the listener is released
189
+ // on every way out. The registration loop below can throw
190
+ // ToolNameCollisionError, and a host that hits that fixes its config and
191
+ // runs again — which is how a leak of one listener per run becomes a leak
192
+ // of one per ATTEMPT.
193
+ try {
194
+ const coordinatorToolDefs = buildCoordinatorTools({
195
+ gateway,
196
+ completionInbox,
197
+ workingDirectory: input.workingDirectory,
198
+ runtimeContext: input.runtimeContext,
199
+ allowedAgentIds: config.agentIds,
200
+ // The only hop between the config and the decision. Omit it and
201
+ // everything still compiles: the field is settable, documented, and
202
+ // read by nothing — which is the shape of a declaration this repo
203
+ // has had to go and delete before.
204
+ allowDelegation: config.allowDelegation,
205
+ taskStore: input.taskStore,
206
+ runId,
207
+ getPlanManager: () => planManagerRef,
208
+ onTaskLaunched: (agentTaskId, meta) => {
209
+ launchedTasks.set(agentTaskId, meta)
210
+ },
211
+ // With a resume handler present the coordinator surface gains
212
+ // ask_user_question — the model can park the run on a question
213
+ // routed through the same HITL channel as plan approvals.
214
+ ...(config.resumeHandler ? { resumeHandler: config.resumeHandler } : {}),
215
+ questionParks,
216
+ pendingAnswers,
217
+ })
212
218
 
213
- const tools = new ToolRegistry()
214
- if (config.tools) {
215
- for (const tool of config.tools.getAll()) {
216
- tools.register(tool, config.tools.getAvailability(tool.name))
219
+ const tools = new ToolRegistry()
220
+ if (config.tools) {
221
+ for (const tool of config.tools.getAll()) {
222
+ tools.register(tool, config.tools.getAvailability(tool.name))
223
+ }
217
224
  }
218
- }
219
- // Registered the way every other kernel-mounted tool in this SDK is
220
- // registered: honouring `runtimeToolOverrides`, and refusing to take a
221
- // name the host already used.
222
- //
223
- // Both halves were missing here and nowhere else. `runtimeToolOverrides`
224
- // is declared on `AgentInput`, is forwarded into this very `drainQuery`
225
- // call below, and is consulted for the task tools and for the advisory
226
- // tools but the coordinator tools were registered before that and
227
- // unconditionally, so `{ create_task: 'disabled' }` was honoured
228
- // everywhere except the one surface a host would most want to decline.
229
- // A run that must not delegate had prompt text and a gateway refusal as
230
- // its only defences.
231
- //
232
- // Collision REFUSES rather than overwrites, and the principle is
233
- // complete mediation rather than fail-safe defaults: "proposals to gain
234
- // performance by remembering the result of an authority check [must] be
235
- // examined skeptically. If a change in authority occurs, such remembered
236
- // results must be systematically updated" (Saltzer & Schroeder 1975,
237
- // §I.A.3(c)). A registry entry is a remembered binding of a name to an
238
- // authority, and a later write that rebinds the name leaves every
239
- // decision made about the old binding stale.
240
- //
241
- // The counter-argument is that today the host's tool merely loses
242
- // quietly and the run still works, so six reserved names is a real cost
243
- // on a name a consumer may have chosen long ago. It does not hold,
244
- // because "loses quietly" is not what happens. `registerOne` ends with
245
- // `availability.set(id, state)` and this call passes no state, so a tool
246
- // the host registered `deferred` or `suspended` is silently PROMOTED to
247
- // active under someone else's implementation; and because the store is a
248
- // Map, the replacement inherits the host's insertion position in the
249
- // prompt-cache prefix. That is a different authorization surface, not a
250
- // lost registration. CWE-390 is the shape `ManagedRegistry` has here
251
- // detection of an error condition without action and CWE-694's own
252
- // mitigation is nearly this fix: do not operate any resource with a
253
- // non-unique identifier, and report the error.
254
- //
255
- // Refusing is also what the peer set does. One runtime's registry
256
- // primitive throws on both duplicate and reserved names; another refuses
257
- // its injected delegation name in a pre-flight that tells the author to
258
- // rename. Closer to home, `ProviderRegistry.register` already throws
259
- // unless the caller passes `{ replace: true }` declared intent is what
260
- // separates a legitimate replacement from an accidental one, and no such
261
- // intent is expressible here.
262
- const overrides = input.runtimeToolOverrides
263
- for (const tool of coordinatorToolDefs) {
264
- const override = overrides?.[tool.name]
265
- if (override === 'disabled') continue
266
- if (config.tools?.has(tool.name)) {
267
- throw new ToolNameCollisionError(tool.name, 'the supervisor coordinator surface')
225
+ // Registered the way every other kernel-mounted tool in this SDK is
226
+ // registered: honouring `runtimeToolOverrides`, and refusing to take a
227
+ // name the host already used.
228
+ //
229
+ // Both halves were missing here and nowhere else. `runtimeToolOverrides`
230
+ // is declared on `AgentInput`, is forwarded into this very `drainQuery`
231
+ // call below, and is consulted for the task tools and for the advisory
232
+ // tools but the coordinator tools were registered before that and
233
+ // unconditionally, so `{ create_task: 'disabled' }` was honoured
234
+ // everywhere except the one surface a host would most want to decline.
235
+ // A run that must not delegate had prompt text and a gateway refusal as
236
+ // its only defences.
237
+ //
238
+ // Collision REFUSES rather than overwrites, and the principle is
239
+ // complete mediation rather than fail-safe defaults: "proposals to gain
240
+ // performance by remembering the result of an authority check [must] be
241
+ // examined skeptically. If a change in authority occurs, such remembered
242
+ // results must be systematically updated" (Saltzer & Schroeder 1975,
243
+ // §I.A.3(c)). A registry entry is a remembered binding of a name to an
244
+ // authority, and a later write that rebinds the name leaves every
245
+ // decision made about the old binding stale.
246
+ //
247
+ // The counter-argument is that today the host's tool merely loses
248
+ // quietly and the run still works, so six reserved names is a real cost
249
+ // on a name a consumer may have chosen long ago. It does not hold,
250
+ // because "loses quietly" is not what happens. `registerOne` ends with
251
+ // `availability.set(id, state)` and this call passes no state, so a tool
252
+ // the host registered `deferred` or `suspended` is silently PROMOTED to
253
+ // active under someone else's implementation; and because the store is a
254
+ // Map, the replacement inherits the host's insertion position in the
255
+ // prompt-cache prefix. That is a different authorization surface, not a
256
+ // lost registration. CWE-390 is the shape `ManagedRegistry` has here
257
+ // detection of an error condition without action and CWE-694's own
258
+ // mitigation is nearly this fix: do not operate any resource with a
259
+ // non-unique identifier, and report the error.
260
+ //
261
+ // Refusing is also what the peer set does. One runtime's registry
262
+ // primitive throws on both duplicate and reserved names; another refuses
263
+ // its injected delegation name in a pre-flight that tells the author to
264
+ // rename. Closer to home, `ProviderRegistry.register` already throws
265
+ // unless the caller passes `{ replace: true }` declared intent is what
266
+ // separates a legitimate replacement from an accidental one, and no such
267
+ // intent is expressible here.
268
+ const overrides = input.runtimeToolOverrides
269
+ for (const tool of coordinatorToolDefs) {
270
+ const override = overrides?.[tool.name]
271
+ if (override === 'disabled') continue
272
+ if (config.tools?.has(tool.name)) {
273
+ throw new ToolNameCollisionError(tool.name, 'the supervisor coordinator surface')
274
+ }
275
+ tools.register(tool, override ?? 'active')
268
276
  }
269
- tools.register(tool, override ?? 'active')
270
- }
271
277
 
272
- const childInvocationState = deriveChildState(
273
- config.invocationState ?? { tenantId },
274
- this.metadata.id,
275
- )
278
+ const childInvocationState = deriveChildState(
279
+ config.invocationState ?? { tenantId },
280
+ this.metadata.id,
281
+ )
276
282
 
277
- const run = await drainQuery(
278
- {
279
- systemPrompt: config.systemPrompt,
280
- skills: config.skills,
281
- provider: config.provider,
282
- tools,
283
- runConfig: {
284
- model: config.model,
285
- tokenBudget: config.tokenBudget,
286
- timeoutMs: config.timeoutMs,
287
- maxIterations: config.maxIterations,
288
- temperature: config.temperature,
289
- env: config.env,
290
- },
291
- questionParks,
292
- pendingAnswers,
293
- agentId: this.metadata.id,
294
- agentName: this.metadata.name,
295
- workingDirectory: input.workingDirectory,
296
- messages: input.messages,
297
- signal: input.signal,
298
- sessionId,
299
- threadId,
300
- projectId,
301
- tenantId,
302
- runId,
303
- parentRunId: config.parentRunId,
304
- depth: config.depth,
305
- contextLevel: 'full',
306
- onContextCreated: ({ planManager }) => {
307
- planManagerRef = planManager
283
+ const run = await drainQuery(
284
+ {
285
+ systemPrompt: config.systemPrompt,
286
+ skills: config.skills,
287
+ provider: config.provider,
288
+ tools,
289
+ runConfig: {
290
+ model: config.model,
291
+ tokenBudget: config.tokenBudget,
292
+ timeoutMs: config.timeoutMs,
293
+ maxIterations: config.maxIterations,
294
+ temperature: config.temperature,
295
+ env: config.env,
296
+ // See ReactiveAgent: a hand-listed literal drops what nobody
297
+ // remembered to add, and reports nothing when it does.
298
+ ...(config.thinking ? { thinking: config.thinking } : {}),
299
+ ...(config.effort ? { effort: config.effort } : {}),
300
+ },
301
+ questionParks,
302
+ pendingAnswers,
303
+ agentId: this.metadata.id,
304
+ agentName: this.metadata.name,
305
+ workingDirectory: input.workingDirectory,
306
+ messages: input.messages,
307
+ signal: input.signal,
308
+ sessionId,
309
+ threadId,
310
+ projectId,
311
+ tenantId,
312
+ runId,
313
+ parentRunId: config.parentRunId,
314
+ depth: config.depth,
315
+ contextLevel: 'full',
316
+ onContextCreated: ({ planManager }) => {
317
+ planManagerRef = planManager
318
+ },
319
+ taskStore: input.taskStore,
320
+ runtimeToolOverrides: input.runtimeToolOverrides,
321
+ runtimeContext: input.runtimeContext,
322
+ taskGateway: gateway,
323
+ completionInbox,
324
+ launchedTasks,
325
+ advisory: config.advisory,
326
+ invocationState: childInvocationState,
327
+ // HITL surface: forward optional review-time hooks so hosts can
328
+ // run "Ask before acting" supervisors instead of the default
329
+ // auto-approve. drainQuery falls back to autoApproveHandler
330
+ // when resumeHandler is omitted (= same behaviour as before).
331
+ ...(config.resumeHandler ? { resumeHandler: config.resumeHandler } : {}),
332
+ // Forwarded for the same reason the handler is. A capability the
333
+ // kernel honours in `drainQuery` but that never reaches the
334
+ // surface a host actually constructs is a capability nobody can
335
+ // use — which is the shape of defect this file has already been
336
+ // corrected for twice.
337
+ ...(config.steering ? { steering: config.steering } : {}),
338
+ ...(config.verificationGate ? { verificationGate: config.verificationGate } : {}),
339
+ ...(config.sandboxProvider ? { sandboxProvider: config.sandboxProvider } : {}),
340
+ // Working-memory / compaction seam (optional; absent => unchanged
341
+ // run path, byte-identical for every existing consumer).
342
+ ...(config.compactionConfig ? { compactionConfig: config.compactionConfig } : {}),
343
+ ...(config.workingMemoryProvider
344
+ ? { workingMemoryProvider: config.workingMemoryProvider }
345
+ : {}),
308
346
  },
309
- taskStore: input.taskStore,
310
- runtimeToolOverrides: input.runtimeToolOverrides,
311
- runtimeContext: input.runtimeContext,
312
- taskGateway: gateway,
313
- completionInbox,
314
- launchedTasks,
315
- advisory: config.advisory,
316
- invocationState: childInvocationState,
317
- // HITL surface: forward optional review-time hooks so hosts can
318
- // run "Ask before acting" supervisors instead of the default
319
- // auto-approve. drainQuery falls back to autoApproveHandler
320
- // when resumeHandler is omitted (= same behaviour as before).
321
- ...(config.resumeHandler ? { resumeHandler: config.resumeHandler } : {}),
322
- // Forwarded for the same reason the handler is. A capability the
323
- // kernel honours in `drainQuery` but that never reaches the
324
- // surface a host actually constructs is a capability nobody can
325
- // use — which is the shape of defect this file has already been
326
- // corrected for twice.
327
- ...(config.steering ? { steering: config.steering } : {}),
328
- ...(config.verificationGate ? { verificationGate: config.verificationGate } : {}),
329
- ...(config.sandboxProvider ? { sandboxProvider: config.sandboxProvider } : {}),
330
- // Working-memory / compaction seam (optional; absent => unchanged
331
- // run path, byte-identical for every existing consumer).
332
- ...(config.compactionConfig ? { compactionConfig: config.compactionConfig } : {}),
333
- ...(config.workingMemoryProvider
334
- ? { workingMemoryProvider: config.workingMemoryProvider }
335
- : {}),
336
- },
337
- listener,
338
- )
347
+ listener,
348
+ )
339
349
 
340
- const taskHandles = gateway.listTasks()
341
- const taskResults = synthesizeTaskResults(taskHandles, runId)
350
+ const taskHandles = gateway.listTasks()
351
+ const taskResults = synthesizeTaskResults(taskHandles, runId)
342
352
 
343
- const completedTasks = countCompletedTasks(taskResults)
353
+ const completedTasks = countCompletedTasks(taskResults)
344
354
 
345
- return {
346
- runId: run.id,
347
- status: run.status === 'completed' ? 'completed' : 'failed',
348
- stopReason: run.stopReason,
349
- usage: run.tokenUsage,
350
- cost: run.costInfo,
351
- iterations: run.currentIteration,
352
- durationMs: Date.now() - startTime,
353
- messages: run.messages,
354
- result: run.result,
355
- lastError: run.lastError,
356
- taskResults,
357
- completedTasks,
358
- totalTasks: taskResults.length,
355
+ return {
356
+ runId: run.id,
357
+ status: run.status === 'completed' ? 'completed' : 'failed',
358
+ stopReason: run.stopReason,
359
+ usage: run.tokenUsage,
360
+ cost: run.costInfo,
361
+ iterations: run.currentIteration,
362
+ durationMs: Date.now() - startTime,
363
+ messages: run.messages,
364
+ result: run.result,
365
+ lastError: run.lastError,
366
+ taskResults,
367
+ completedTasks,
368
+ totalTasks: taskResults.length,
369
+ }
370
+ } finally {
371
+ completionInbox.close()
359
372
  }
360
373
  }
361
374
  }
@@ -0,0 +1,149 @@
1
+ import { mkdtemp } from 'node:fs/promises'
2
+ import { tmpdir } from 'node:os'
3
+ import { join } from 'node:path'
4
+ import { describe, expect, it } from 'vitest'
5
+ import { z } from 'zod'
6
+
7
+ import { MockLLMProvider } from '../../provider/mock.js'
8
+ import { ToolNameCollisionError, ToolRegistry } from '../../registry/tool/execute.js'
9
+ import { defineTool } from '../../tools/defineTool.js'
10
+ import type { TaskGateway, TaskHandle } from '../../types/agent/gateway.js'
11
+ import type { TaskId } from '../../types/ids/index.js'
12
+ import { SupervisorAgent } from '../SupervisorAgent.js'
13
+
14
+ /**
15
+ * A gateway the HOST owns, which is the case that goes wrong.
16
+ *
17
+ * `SupervisorAgentConfig.gateway` is a first-class option, and a host that
18
+ * built a gateway reuses it — across sequential runs, and across concurrent
19
+ * ones. The supervisor attached a fresh `CompletionInbox` to it on every run
20
+ * and never detached, so the subscription set only grew: three runs, three
21
+ * live listeners, each still holding its own run's handles and each still
22
+ * being handed every other run's completions.
23
+ */
24
+ class HostGateway implements TaskGateway {
25
+ readonly listeners = new Set<(h: TaskHandle) => void>()
26
+
27
+ async createTask(): Promise<TaskHandle> {
28
+ throw new Error('this test never launches')
29
+ }
30
+ async waitForTask(): Promise<TaskHandle> {
31
+ throw new Error('this test never waits')
32
+ }
33
+ async continueTask(): Promise<void> {}
34
+ cancelTask(): void {}
35
+ getTask(): TaskHandle | undefined {
36
+ return undefined
37
+ }
38
+ listTasks(): TaskHandle[] {
39
+ return []
40
+ }
41
+ onTaskCompleted(cb: (h: TaskHandle) => void): () => void {
42
+ this.listeners.add(cb)
43
+ return () => this.listeners.delete(cb)
44
+ }
45
+ }
46
+
47
+ /** A host tool whose name the coordinator surface also wants. */
48
+ const collidingTool = defineTool({
49
+ name: 'create_task',
50
+ description: 'a tool this host registered under a coordinator name',
51
+ inputSchema: z.object({}),
52
+ category: 'custom',
53
+ permissions: [],
54
+ readOnly: true,
55
+ destructive: false,
56
+ concurrencySafe: true,
57
+ async execute() {
58
+ return { success: true as const, output: 'never runs' }
59
+ },
60
+ })
61
+
62
+ async function runOnce(
63
+ gateway: TaskGateway,
64
+ id: string,
65
+ options: { collide?: boolean } = {},
66
+ ): Promise<void> {
67
+ const agent = new SupervisorAgent({
68
+ id,
69
+ name: 'Supervisor',
70
+ version: '1',
71
+ category: 'test',
72
+ description: 'coordinates workers',
73
+ })
74
+
75
+ const tools = new ToolRegistry()
76
+ if (options.collide) tools.register(collidingTool)
77
+
78
+ await agent.run(
79
+ {
80
+ messages: [{ role: 'user', content: 'go', timestamp: 1 }],
81
+ workingDirectory: await mkdtemp(join(tmpdir(), 'namzu-inbox-scope-')),
82
+ } as never,
83
+ {
84
+ provider: new MockLLMProvider({ turns: [{ text: 'nothing to delegate' }] }),
85
+ agentIds: ['worker'],
86
+ gateway,
87
+ tools,
88
+ systemPrompt: 'You coordinate.',
89
+ model: 'mock-model',
90
+ tokenBudget: 100_000,
91
+ timeoutMs: 30_000,
92
+ maxIterations: 2,
93
+ sessionId: 'ses_scope',
94
+ threadId: 'thd_scope',
95
+ projectId: 'prj_scope',
96
+ tenantId: 'tnt_scope',
97
+ } as never,
98
+ )
99
+ }
100
+
101
+ describe('a supervisor releases the gateway it borrowed', () => {
102
+ it('leaves no listener behind, however many runs the host makes', async () => {
103
+ const gateway = new HostGateway()
104
+
105
+ for (let i = 0; i < 3; i++) {
106
+ await runOnce(gateway, `sup_${i}`)
107
+ expect(gateway.listeners.size, `run ${i + 1} left its completion listener attached`).toBe(0)
108
+ }
109
+ }, 60_000)
110
+
111
+ it('releases it when setup throws before the run ever starts', async () => {
112
+ // The reason it is a `finally` covering the whole body and not a line
113
+ // after `drainQuery`. A host whose tool shares a coordinator name gets
114
+ // `ToolNameCollisionError` from the registration loop — after the inbox
115
+ // attached — then fixes its config and runs again. A leak of one
116
+ // listener per run becomes one per ATTEMPT, and the attempts are what
117
+ // there are most of.
118
+ const gateway = new HostGateway()
119
+
120
+ await expect(runOnce(gateway, 'sup_collide', { collide: true })).rejects.toThrow(
121
+ ToolNameCollisionError,
122
+ )
123
+
124
+ expect(gateway.listeners.size, 'a run that threw left its listener attached').toBe(0)
125
+ }, 60_000)
126
+
127
+ it('never hands a completion to a run that did not launch it', async () => {
128
+ // Two supervisors, one gateway. The second run's inbox is gone by the
129
+ // time this fires, but the assertion that matters is the one above it:
130
+ // nothing is listening that should not be.
131
+ const gateway = new HostGateway()
132
+ await runOnce(gateway, 'sup_a')
133
+ await runOnce(gateway, 'sup_b')
134
+
135
+ let delivered = 0
136
+ for (const cb of gateway.listeners) {
137
+ delivered += 1
138
+ cb({
139
+ taskId: 'tsk_foreign' as TaskId,
140
+ agentId: 'worker',
141
+ state: 'completed',
142
+ createdAt: 0,
143
+ completedAt: 1,
144
+ } as TaskHandle)
145
+ }
146
+
147
+ expect(delivered).toBe(0)
148
+ }, 60_000)
149
+ })
@@ -2,7 +2,7 @@ import { ToolRegistry } from '../registry/tool/execute.js'
2
2
  import { drainQuery } from '../runtime/query/index.js'
3
3
  import type { ProjectId, SessionId, TenantId, ThreadId } from '../types/ids/index.js'
4
4
  import type { Message } from '../types/message/index.js'
5
- import type { LLMProvider } from '../types/provider/index.js'
5
+ import type { LLMProvider, ReasoningEffort, ThinkingConfig } from '../types/provider/index.js'
6
6
  import type { Run, RunEventListener } from '../types/run/index.js'
7
7
  import type { Skill } from '../types/skills/index.js'
8
8
  import type { ToolRegistryContract } from '../types/tool/index.js'
@@ -83,6 +83,25 @@ export interface RunAgentOptions extends AgentIdentity {
83
83
  timeoutMs?: number
84
84
  temperature?: number
85
85
 
86
+ /**
87
+ * Extended-thinking request and response-effort level, forwarded on every
88
+ * model call.
89
+ *
90
+ * These are here because the run config below is assembled by HAND, and a
91
+ * hand-listed literal silently drops whatever nobody remembered to add —
92
+ * which is precisely what happened. `thinking` shipped on `AgentRunConfig`
93
+ * and was reachable only from the raw kernel entry point, because this
94
+ * function, `ReactiveAgent` and `SupervisorAgent` each rebuilt the object
95
+ * from a fixed list. So the capability existed and the front door could not
96
+ * open it.
97
+ *
98
+ * A live run is what found it: the unit tests passed because they drove the
99
+ * kernel directly, and a real agent run through this function put no effort
100
+ * on the wire at all.
101
+ */
102
+ thinking?: ThinkingConfig
103
+ effort?: ReasoningEffort
104
+
86
105
  /** Names the agent in traces and events. Defaults to `Agent`. */
87
106
  name?: string
88
107
 
@@ -184,6 +203,8 @@ export async function runAgent(options: RunAgentOptions): Promise<RunAgentResult
184
203
  tokenBudget: options.tokenBudget ?? DEFAULT_TOKEN_BUDGET,
185
204
  timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
186
205
  ...(options.temperature !== undefined ? { temperature: options.temperature } : {}),
206
+ ...(options.thinking ? { thinking: options.thinking } : {}),
207
+ ...(options.effort ? { effort: options.effort } : {}),
187
208
  },
188
209
  // One option covers both. `drainQuery` separates the id from the
189
210
  // display name because a fleet needs a stable key and a readable
@@ -188,6 +188,10 @@ const MAPPING: {
188
188
  // Context management is kernel-internal bookkeeping; A2A peers model a
189
189
  // task lifecycle, not the host runtime's memory strategy.
190
190
  compaction_completed: null,
191
+ // Compaction, succeeded or declined, is a property of how this runtime
192
+ // manages its own context. A peer models a task lifecycle and cannot act on
193
+ // either outcome.
194
+ compaction_failed: null,
191
195
  // A refusal is the run's own policy decision; the peer sees it in the
192
196
  // terminal task state, not as a separate signal.
193
197
  guardrail_triggered: null,
@@ -92,6 +92,21 @@ const MAPPING: {
92
92
  }),
93
93
  },
94
94
 
95
+ // Carried for the same reason its sibling is: a host that can show a user
96
+ // context was dropped must also be able to show them it was not, because a
97
+ // run continuing at full context is the state that ends in an opaque
98
+ // provider rejection later.
99
+ compaction_failed: {
100
+ wire: 'compaction.failed',
101
+ transform: (e, runId) => ({
102
+ run_id: runId,
103
+ iteration: e.iteration,
104
+ cause: e.cause,
105
+ messages: e.messages,
106
+ ...(e.error !== undefined ? { error: e.error } : {}),
107
+ }),
108
+ },
109
+
95
110
  tool_executing: {
96
111
  wire: 'tool.executing',
97
112
  transform: (e, runId) => ({
@@ -212,6 +227,16 @@ const MAPPING: {
212
227
  run_id: runId,
213
228
  usage: e.usage,
214
229
  cost: e.cost,
230
+ // Carried, and named apart from `usage` on the wire as well as in
231
+ // the type. A remote surface has exactly the same opportunity to
232
+ // divide cumulative spend by a context window as a local one, and
233
+ // no more information with which to notice.
234
+ ...(e.contextTokens !== undefined ? { context_tokens: e.contextTokens } : {}),
235
+ ...(e.contextMeasuredBy !== undefined ? { context_measured_by: e.contextMeasuredBy } : {}),
236
+ ...(e.contextWindowTokens !== undefined
237
+ ? { context_window_tokens: e.contextWindowTokens }
238
+ : {}),
239
+ ...(e.windowSource !== undefined ? { window_source: e.windowSource } : {}),
215
240
  }),
216
241
  },
217
242