@namzu/sdk 3.2.0 → 3.3.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 (76) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/dist/agents/SupervisorAgent.d.ts.map +1 -1
  3. package/dist/agents/SupervisorAgent.js +6 -0
  4. package/dist/agents/SupervisorAgent.js.map +1 -1
  5. package/dist/agents/__tests__/supervisor-hands-down-hitl.test.d.ts +2 -0
  6. package/dist/agents/__tests__/supervisor-hands-down-hitl.test.d.ts.map +1 -0
  7. package/dist/agents/__tests__/supervisor-hands-down-hitl.test.js +112 -0
  8. package/dist/agents/__tests__/supervisor-hands-down-hitl.test.js.map +1 -0
  9. package/dist/connector/index.d.ts +1 -0
  10. package/dist/connector/index.d.ts.map +1 -1
  11. package/dist/connector/index.js +1 -0
  12. package/dist/connector/index.js.map +1 -1
  13. package/dist/connector/mcp/__tests__/prompt-as-tool.test.d.ts +2 -0
  14. package/dist/connector/mcp/__tests__/prompt-as-tool.test.d.ts.map +1 -0
  15. package/dist/connector/mcp/__tests__/prompt-as-tool.test.js +122 -0
  16. package/dist/connector/mcp/__tests__/prompt-as-tool.test.js.map +1 -0
  17. package/dist/connector/mcp/discovery.d.ts +15 -1
  18. package/dist/connector/mcp/discovery.d.ts.map +1 -1
  19. package/dist/connector/mcp/discovery.js +39 -1
  20. package/dist/connector/mcp/discovery.js.map +1 -1
  21. package/dist/connector/mcp/index.d.ts +2 -1
  22. package/dist/connector/mcp/index.d.ts.map +1 -1
  23. package/dist/connector/mcp/index.js +2 -1
  24. package/dist/connector/mcp/index.js.map +1 -1
  25. package/dist/connector/mcp/policy.d.ts +17 -0
  26. package/dist/connector/mcp/policy.d.ts.map +1 -1
  27. package/dist/connector/mcp/policy.js +18 -7
  28. package/dist/connector/mcp/policy.js.map +1 -1
  29. package/dist/connector/mcp/prompt-adapter.d.ts +39 -0
  30. package/dist/connector/mcp/prompt-adapter.d.ts.map +1 -0
  31. package/dist/connector/mcp/prompt-adapter.js +113 -0
  32. package/dist/connector/mcp/prompt-adapter.js.map +1 -0
  33. package/dist/manager/agent/__tests__/hitl-across-spawn.test.d.ts +2 -0
  34. package/dist/manager/agent/__tests__/hitl-across-spawn.test.d.ts.map +1 -0
  35. package/dist/manager/agent/__tests__/hitl-across-spawn.test.js +226 -0
  36. package/dist/manager/agent/__tests__/hitl-across-spawn.test.js.map +1 -0
  37. package/dist/manager/agent/lifecycle.d.ts.map +1 -1
  38. package/dist/manager/agent/lifecycle.js +13 -0
  39. package/dist/manager/agent/lifecycle.js.map +1 -1
  40. package/dist/plugin/__tests__/mcp-admission.test.js +37 -2
  41. package/dist/plugin/__tests__/mcp-admission.test.js.map +1 -1
  42. package/dist/plugin/lifecycle.d.ts.map +1 -1
  43. package/dist/plugin/lifecycle.js +10 -0
  44. package/dist/plugin/lifecycle.js.map +1 -1
  45. package/dist/public-runtime.d.ts +1 -1
  46. package/dist/public-runtime.d.ts.map +1 -1
  47. package/dist/public-runtime.js +1 -1
  48. package/dist/public-runtime.js.map +1 -1
  49. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  50. package/dist/runtime/query/iteration/index.js +1 -46
  51. package/dist/runtime/query/iteration/index.js.map +1 -1
  52. package/dist/telemetry/__tests__/model-call-span.test.js +13 -8
  53. package/dist/telemetry/__tests__/model-call-span.test.js.map +1 -1
  54. package/dist/types/agent/base.d.ts +26 -0
  55. package/dist/types/agent/base.d.ts.map +1 -1
  56. package/dist/types/agent/task.d.ts +17 -0
  57. package/dist/types/agent/task.d.ts.map +1 -1
  58. package/dist/types/agent/task.js.map +1 -1
  59. package/package.json +1 -1
  60. package/src/agents/SupervisorAgent.ts +6 -0
  61. package/src/agents/__tests__/supervisor-hands-down-hitl.test.ts +131 -0
  62. package/src/connector/index.ts +1 -0
  63. package/src/connector/mcp/__tests__/prompt-as-tool.test.ts +157 -0
  64. package/src/connector/mcp/discovery.ts +48 -1
  65. package/src/connector/mcp/index.ts +2 -1
  66. package/src/connector/mcp/policy.ts +23 -8
  67. package/src/connector/mcp/prompt-adapter.ts +139 -0
  68. package/src/manager/agent/__tests__/hitl-across-spawn.test.ts +273 -0
  69. package/src/manager/agent/lifecycle.ts +12 -0
  70. package/src/plugin/__tests__/mcp-admission.test.ts +46 -2
  71. package/src/plugin/lifecycle.ts +11 -0
  72. package/src/public-runtime.ts +2 -0
  73. package/src/runtime/query/iteration/index.ts +0 -48
  74. package/src/telemetry/__tests__/model-call-span.test.ts +13 -7
  75. package/src/types/agent/base.ts +27 -0
  76. package/src/types/agent/task.ts +18 -0
@@ -11,7 +11,6 @@ import {
11
11
  GENAI,
12
12
  NAMZU,
13
13
  agentIterationSpanName,
14
- chatSpanName,
15
14
  parentContext,
16
15
  } from '../../../telemetry/attributes.js'
17
16
  import { getTracer } from '../../../telemetry/runtime-accessors.js'
@@ -154,9 +153,6 @@ export class IterationOrchestrator {
154
153
  {},
155
154
  parentContext(this.ctx.rootSpan),
156
155
  )
157
- // Declared out here so the iteration's own finally can close it on
158
- // any path that does not reach its success branch.
159
- let chatSpan: Span | undefined
160
156
  try {
161
157
  // Tool spans for this turn belong under this iteration. Inside
162
158
  // the try rather than before it: a throw from any of these left
@@ -282,27 +278,6 @@ export class IterationOrchestrator {
282
278
  // aggregated `ChatCompletionResponse` for the legacy
283
279
  // downstream paths (assistantMsg construction, working
284
280
  // state extraction, telemetry attribute stamping).
285
- // The model call gets its own span. There was none at all —
286
- // `chatSpanName` shipped with zero call sites — so a run's traces
287
- // carried no LLM latency whatsoever, and the one thing anybody
288
- // opens a trace to find (which turn was slow, and why) was the
289
- // one thing not in it.
290
- chatSpan = tracer.startSpan(chatSpanName(stepModel), {}, parentContext(iterSpan))
291
- chatSpan.setAttributes({
292
- [GENAI.OPERATION_NAME]: 'chat',
293
- [GENAI.SYSTEM]: this.ctx.provider.id,
294
- [GENAI.REQUEST_MODEL]: stepModel,
295
- ...((step.temperature ?? runConfig.temperature) !== undefined
296
- ? { [GENAI.REQUEST_TEMPERATURE]: (step.temperature ?? runConfig.temperature) as number }
297
- : {}),
298
- ...((step.maxResponseTokens ?? runConfig.maxResponseTokens) !== undefined
299
- ? {
300
- [GENAI.REQUEST_MAX_TOKENS]: (step.maxResponseTokens ??
301
- runConfig.maxResponseTokens) as number,
302
- }
303
- : {}),
304
- })
305
-
306
281
  const { response, messageId } = yield* streamProviderTurn(
307
282
  this.ctx.provider,
308
283
  {
@@ -339,27 +314,6 @@ export class IterationOrchestrator {
339
314
  iterSpan,
340
315
  )
341
316
 
342
- // Stamped on the call that produced them. The token counts also
343
- // stay on the iteration span below, where they have always been:
344
- // moving them would silently break whatever reads them today,
345
- // and one turn per iteration makes the two agree.
346
- chatSpan.setAttributes({
347
- [GENAI.RESPONSE_MODEL]: response.model || stepModel,
348
- [GENAI.RESPONSE_ID]: response.id,
349
- [GENAI.USAGE_INPUT_TOKENS]: response.usage.promptTokens,
350
- [GENAI.USAGE_OUTPUT_TOKENS]: response.usage.completionTokens,
351
- // An array, per the semantic convention: one call can finish
352
- // several ways when a provider returns more than one choice.
353
- [GENAI.RESPONSE_FINISH_REASONS]: [response.finishReason ?? 'stop'],
354
- [NAMZU.CACHE_READ_TOKENS]: response.usage.cachedTokens ?? 0,
355
- [NAMZU.CACHE_WRITE_TOKENS]: response.usage.cacheWriteTokens ?? 0,
356
- })
357
- chatSpan.setStatus({ code: SpanStatusCode.OK })
358
- chatSpan.end()
359
- // Closed here for an accurate duration, and cleared so the
360
- // iteration finally does not close it a second time.
361
- chatSpan = undefined
362
-
363
317
  // Main-loop turn: also records the prompt size compaction reads.
364
318
  runMgr.recordTurnUsage(response.usage)
365
319
 
@@ -794,8 +748,6 @@ export class IterationOrchestrator {
794
748
  iterSpan.recordException(err instanceof Error ? err : new Error(String(err)))
795
749
  throw err
796
750
  } finally {
797
- // A model call that threw never reached its own close above.
798
- chatSpan?.end()
799
751
  // The only place the iteration span ends. It used to be ended at each of
800
752
  // seventeen exits, which is a rule every future edit has to
801
753
  // remember; a generator abandoned by its consumer never reached
@@ -109,10 +109,14 @@ describe('the model call has a span of its own', () => {
109
109
  it('opens one named for the model', async () => {
110
110
  await runOnce([{ text: 'done' }])
111
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')
112
+ // EXACTLY one. This was written as `toHaveLength(1)`, failed with 2,
113
+ // and was relaxed to `>= 1` under a plausible-sounding explanation
114
+ // about forced-final turns. The 2 was real: a second span with the
115
+ // same name and the same parent had been added beside the one
116
+ // `stream-turn.ts` already opened, so a naive sum double-counted both
117
+ // latency and tokens. Relaxing the assertion is what let that ship.
118
+ expect(chatSpans()).toHaveLength(1)
119
+ expect(chatSpans()[0]?.name).toBe('chat mock-model')
116
120
  })
117
121
 
118
122
  it('closes it', async () => {
@@ -168,11 +172,13 @@ describe('the model call has a span of its own', () => {
168
172
  expect(attrs).toHaveProperty('namzu.cache.write_tokens')
169
173
  })
170
174
 
171
- it('opens one per turn, not one per run', async () => {
175
+ it('opens exactly one per model call, however many turns a run takes', async () => {
172
176
  await runOnce([{ text: 'first' }, { text: 'second' }])
173
177
 
174
- // A run with several turns is exactly the case a trace is opened for.
175
- expect(chatSpans().length).toBeGreaterThanOrEqual(1)
178
+ // The count has to track model calls, not runs and not iterations
179
+ // which is the only way a duplicate is visible at all.
180
+ const calls = spans.filter((s) => s.name.includes('iteration')).length
181
+ expect(chatSpans().length).toBeLessThanOrEqual(calls)
176
182
  })
177
183
 
178
184
  it('nests under the iteration span rather than emitting as a root', async () => {
@@ -1,4 +1,5 @@
1
1
  import type { AgentStatus, CostInfo, TokenUsage } from '../common/index.js'
2
+ import type { ResumeHandler } from '../hitl/index.js'
2
3
  import type { RunId, SessionId, TenantId } from '../ids/index.js'
3
4
  import type { InvocationState } from '../invocation/index.js'
4
5
  import type { Message } from '../message/index.js'
@@ -78,6 +79,32 @@ export interface BaseAgentConfig {
78
79
 
79
80
  /** Span a delegated run hangs off. Absent for a top-level run. */
80
81
  parentSpan?: import('@opentelemetry/api').Span
82
+
83
+ /**
84
+ * Where this agent takes a decision it cannot make alone — a tool that
85
+ * needs approval, a question for a human, a plan to sign off.
86
+ *
87
+ * Declared HERE, on the base config, rather than only on the agent
88
+ * shapes that happened to want it. `AgentManager` builds a child as a
89
+ * `BaseAgentConfig` and `SendMessageOptions.configOverrides` is a
90
+ * `Partial` of it, so a field further down the hierarchy is one a
91
+ * spawn cannot express AT THE TYPE LEVEL — and that is what happened:
92
+ * every delegated child fell through to the SDK's `autoApproveHandler`
93
+ * however carefully its parent had been wired.
94
+ *
95
+ * What that cost is narrower than "no gate in children" and worth
96
+ * stating exactly. A `VerificationGate` DENY still bites inside a
97
+ * child, because denials are threaded into the executor and no later
98
+ * approval releases them. What was lost is the REVIEW tier: every call
99
+ * the gate left undecided went to the resume handler, and for a child
100
+ * that handler auto-approved. So a host running "ask before acting"
101
+ * had a human review `write` at the top level and never see the same
102
+ * `write` issued one hop down.
103
+ *
104
+ * Absent still means auto-approve, so a host that never wired one is
105
+ * unaffected.
106
+ */
107
+ resumeHandler?: ResumeHandler
81
108
  }
82
109
 
83
110
  export type RuntimeToolOverrides = Record<string, ToolAvailability | 'disabled'>
@@ -1,6 +1,7 @@
1
1
  import type { ActorRef } from '../../types/session/actor.js'
2
2
  import type { WorkspaceBackendKind } from '../../types/workspace/ref.js'
3
3
  import type { TokenUsage } from '../common/index.js'
4
+ import type { ResumeHandler } from '../hitl/index.js'
4
5
  import type { RunId, SessionId, TaskId, TenantId } from '../ids/index.js'
5
6
  import type { Message } from '../message/index.js'
6
7
  import type { RunEventListener } from '../run/events.js'
@@ -41,6 +42,23 @@ export interface AgentTaskContext {
41
42
 
42
43
  factoryOptions?: AgentFactoryOptions
43
44
 
45
+ /**
46
+ * The parent's channel to whoever can answer a decision it cannot make
47
+ * alone, handed down so a child asks the same person.
48
+ *
49
+ * Passed as the function itself, which works because delegation is
50
+ * in-process: `LocalTaskGateway` is the only `TaskGateway` in the tree.
51
+ * A gateway that dispatched across a process boundary could not carry a
52
+ * closure and would have to proxy the request onto the parent's event
53
+ * stream and route the answer back by request id — the upward half of
54
+ * which already exists, since `wrapChildListener` stamps lineage on
55
+ * every child event the parent sees.
56
+ *
57
+ * Absent means the child auto-approves, exactly as every child did
58
+ * before this existed.
59
+ */
60
+ resumeHandler?: ResumeHandler
61
+
44
62
  /** Isolation boundary. Required per session-hierarchy.md §12.1. */
45
63
  tenantId: TenantId
46
64