@namzu/sdk 0.4.4 → 0.5.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 (180) hide show
  1. package/CHANGELOG.md +241 -0
  2. package/dist/advisory/executor.d.ts.map +1 -1
  3. package/dist/advisory/executor.js +3 -2
  4. package/dist/advisory/executor.js.map +1 -1
  5. package/dist/advisory/executor.test.js +36 -14
  6. package/dist/advisory/executor.test.js.map +1 -1
  7. package/dist/agents/ReactiveAgent.d.ts.map +1 -1
  8. package/dist/agents/ReactiveAgent.js +1 -0
  9. package/dist/agents/ReactiveAgent.js.map +1 -1
  10. package/dist/agents/RouterAgent.d.ts.map +1 -1
  11. package/dist/agents/RouterAgent.js +3 -2
  12. package/dist/agents/RouterAgent.js.map +1 -1
  13. package/dist/agents/SupervisorAgent.d.ts.map +1 -1
  14. package/dist/agents/SupervisorAgent.js +2 -0
  15. package/dist/agents/SupervisorAgent.js.map +1 -1
  16. package/dist/bridge/a2a/mapper.d.ts.map +1 -1
  17. package/dist/bridge/a2a/mapper.js +23 -9
  18. package/dist/bridge/a2a/mapper.js.map +1 -1
  19. package/dist/bridge/a2a/mapper.test.js +35 -9
  20. package/dist/bridge/a2a/mapper.test.js.map +1 -1
  21. package/dist/bridge/sse/mapper.d.ts.map +1 -1
  22. package/dist/bridge/sse/mapper.js +60 -8
  23. package/dist/bridge/sse/mapper.js.map +1 -1
  24. package/dist/bridge/sse/mapper.test.js +123 -16
  25. package/dist/bridge/sse/mapper.test.js.map +1 -1
  26. package/dist/compaction/verifier.d.ts.map +1 -1
  27. package/dist/compaction/verifier.js +3 -2
  28. package/dist/compaction/verifier.js.map +1 -1
  29. package/dist/config/runtime.d.ts +14 -14
  30. package/dist/config/runtime.js +1 -1
  31. package/dist/config/runtime.js.map +1 -1
  32. package/dist/contracts/api.d.ts +1 -1
  33. package/dist/contracts/api.d.ts.map +1 -1
  34. package/dist/contracts/schemas.js +1 -1
  35. package/dist/contracts/schemas.js.map +1 -1
  36. package/dist/gateway/local.d.ts +1 -1
  37. package/dist/gateway/local.d.ts.map +1 -1
  38. package/dist/gateway/local.js +1 -0
  39. package/dist/gateway/local.js.map +1 -1
  40. package/dist/manager/agent/__tests__/lifecycle.test.js +2 -2
  41. package/dist/provider/collect.d.ts +25 -0
  42. package/dist/provider/collect.d.ts.map +1 -0
  43. package/dist/provider/collect.js +82 -0
  44. package/dist/provider/collect.js.map +1 -0
  45. package/dist/provider/collect.test.d.ts +22 -0
  46. package/dist/provider/collect.test.d.ts.map +1 -0
  47. package/dist/provider/collect.test.js +123 -0
  48. package/dist/provider/collect.test.js.map +1 -0
  49. package/dist/provider/instrumentation.d.ts.map +1 -1
  50. package/dist/provider/instrumentation.js +10 -43
  51. package/dist/provider/instrumentation.js.map +1 -1
  52. package/dist/provider/instrumentation.test.d.ts +15 -0
  53. package/dist/provider/instrumentation.test.d.ts.map +1 -1
  54. package/dist/provider/instrumentation.test.js +73 -87
  55. package/dist/provider/instrumentation.test.js.map +1 -1
  56. package/dist/provider/mock.d.ts +1 -2
  57. package/dist/provider/mock.d.ts.map +1 -1
  58. package/dist/provider/mock.js +2 -5
  59. package/dist/provider/mock.js.map +1 -1
  60. package/dist/public-runtime.d.ts +1 -0
  61. package/dist/public-runtime.d.ts.map +1 -1
  62. package/dist/public-runtime.js +5 -0
  63. package/dist/public-runtime.js.map +1 -1
  64. package/dist/run/LimitChecker.test.d.ts +2 -0
  65. package/dist/run/LimitChecker.test.d.ts.map +1 -0
  66. package/dist/run/LimitChecker.test.js +26 -0
  67. package/dist/run/LimitChecker.test.js.map +1 -0
  68. package/dist/run/reporter.d.ts.map +1 -1
  69. package/dist/run/reporter.js +10 -6
  70. package/dist/run/reporter.js.map +1 -1
  71. package/dist/runtime/query/__tests__/prompt.test.d.ts +2 -0
  72. package/dist/runtime/query/__tests__/prompt.test.d.ts.map +1 -0
  73. package/dist/runtime/query/__tests__/prompt.test.js +35 -0
  74. package/dist/runtime/query/__tests__/prompt.test.js.map +1 -0
  75. package/dist/runtime/query/context-cache.d.ts +2 -0
  76. package/dist/runtime/query/context-cache.d.ts.map +1 -1
  77. package/dist/runtime/query/context-cache.js +3 -0
  78. package/dist/runtime/query/context-cache.js.map +1 -1
  79. package/dist/runtime/query/events.d.ts +2 -0
  80. package/dist/runtime/query/events.d.ts.map +1 -1
  81. package/dist/runtime/query/events.js +48 -1
  82. package/dist/runtime/query/events.js.map +1 -1
  83. package/dist/runtime/query/executor.d.ts.map +1 -1
  84. package/dist/runtime/query/executor.js +55 -5
  85. package/dist/runtime/query/executor.js.map +1 -1
  86. package/dist/runtime/query/index.d.ts +2 -1
  87. package/dist/runtime/query/index.d.ts.map +1 -1
  88. package/dist/runtime/query/index.js +2 -0
  89. package/dist/runtime/query/index.js.map +1 -1
  90. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  91. package/dist/runtime/query/iteration/index.js +245 -13
  92. package/dist/runtime/query/iteration/index.js.map +1 -1
  93. package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
  94. package/dist/runtime/query/iteration/phases/compaction.js +2 -0
  95. package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
  96. package/dist/runtime/query/prompt.d.ts +2 -0
  97. package/dist/runtime/query/prompt.d.ts.map +1 -1
  98. package/dist/runtime/query/prompt.js +35 -13
  99. package/dist/runtime/query/prompt.js.map +1 -1
  100. package/dist/session/__tests__/integration/e2e-spawn.test.js +2 -2
  101. package/dist/session/__tests__/integration/event-stream-ordering.test.d.ts +1 -1
  102. package/dist/session/__tests__/integration/event-stream-ordering.test.js +7 -7
  103. package/dist/streaming/coalesce.d.ts +28 -0
  104. package/dist/streaming/coalesce.d.ts.map +1 -0
  105. package/dist/streaming/coalesce.js +75 -0
  106. package/dist/streaming/coalesce.js.map +1 -0
  107. package/dist/streaming/coalesce.test.d.ts +19 -0
  108. package/dist/streaming/coalesce.test.d.ts.map +1 -0
  109. package/dist/streaming/coalesce.test.js +120 -0
  110. package/dist/streaming/coalesce.test.js.map +1 -0
  111. package/dist/tools/coordinator/index.d.ts +2 -0
  112. package/dist/tools/coordinator/index.d.ts.map +1 -1
  113. package/dist/tools/coordinator/index.js +1 -0
  114. package/dist/tools/coordinator/index.js.map +1 -1
  115. package/dist/types/agent/base.d.ts +7 -0
  116. package/dist/types/agent/base.d.ts.map +1 -1
  117. package/dist/types/agent/gateway.d.ts +2 -1
  118. package/dist/types/agent/gateway.d.ts.map +1 -1
  119. package/dist/types/ids/index.d.ts +10 -0
  120. package/dist/types/ids/index.d.ts.map +1 -1
  121. package/dist/types/ids/index.js.map +1 -1
  122. package/dist/types/provider/interface.d.ts +26 -2
  123. package/dist/types/provider/interface.d.ts.map +1 -1
  124. package/dist/types/provider/stream.d.ts +18 -0
  125. package/dist/types/provider/stream.d.ts.map +1 -1
  126. package/dist/types/run/events.d.ts +58 -8
  127. package/dist/types/run/events.d.ts.map +1 -1
  128. package/dist/types/run/events.js +23 -1
  129. package/dist/types/run/events.js.map +1 -1
  130. package/dist/types/run/schema-version.d.ts +7 -1
  131. package/dist/types/run/schema-version.d.ts.map +1 -1
  132. package/dist/types/run/schema-version.js +7 -1
  133. package/dist/types/run/schema-version.js.map +1 -1
  134. package/dist/types/run/stop-reason.d.ts +9 -0
  135. package/dist/types/run/stop-reason.d.ts.map +1 -1
  136. package/package.json +1 -1
  137. package/src/advisory/executor.test.ts +37 -15
  138. package/src/advisory/executor.ts +10 -7
  139. package/src/agents/ReactiveAgent.ts +1 -0
  140. package/src/agents/RouterAgent.ts +9 -6
  141. package/src/agents/SupervisorAgent.ts +2 -0
  142. package/src/bridge/a2a/mapper.test.ts +35 -9
  143. package/src/bridge/a2a/mapper.ts +23 -9
  144. package/src/bridge/sse/mapper.test.ts +152 -24
  145. package/src/bridge/sse/mapper.ts +66 -9
  146. package/src/compaction/verifier.ts +9 -6
  147. package/src/config/runtime.ts +1 -1
  148. package/src/contracts/api.ts +7 -0
  149. package/src/contracts/schemas.ts +1 -1
  150. package/src/gateway/local.ts +3 -2
  151. package/src/manager/agent/__tests__/lifecycle.test.ts +2 -2
  152. package/src/provider/collect.test.ts +142 -0
  153. package/src/provider/collect.ts +85 -0
  154. package/src/provider/instrumentation.test.ts +81 -100
  155. package/src/provider/instrumentation.ts +11 -53
  156. package/src/provider/mock.ts +2 -6
  157. package/src/public-runtime.ts +6 -0
  158. package/src/run/LimitChecker.test.ts +32 -0
  159. package/src/run/reporter.ts +10 -7
  160. package/src/runtime/query/__tests__/prompt.test.ts +38 -0
  161. package/src/runtime/query/context-cache.ts +5 -0
  162. package/src/runtime/query/events.ts +52 -1
  163. package/src/runtime/query/executor.ts +54 -5
  164. package/src/runtime/query/index.ts +5 -1
  165. package/src/runtime/query/iteration/index.ts +301 -26
  166. package/src/runtime/query/iteration/phases/compaction.ts +2 -0
  167. package/src/runtime/query/prompt.ts +45 -17
  168. package/src/session/__tests__/integration/e2e-spawn.test.ts +2 -2
  169. package/src/session/__tests__/integration/event-stream-ordering.test.ts +7 -7
  170. package/src/streaming/coalesce.test.ts +132 -0
  171. package/src/streaming/coalesce.ts +89 -0
  172. package/src/tools/coordinator/index.ts +3 -0
  173. package/src/types/agent/base.ts +9 -0
  174. package/src/types/agent/gateway.ts +3 -1
  175. package/src/types/ids/index.ts +10 -0
  176. package/src/types/provider/interface.ts +28 -3
  177. package/src/types/provider/stream.ts +18 -0
  178. package/src/types/run/events.ts +105 -9
  179. package/src/types/run/schema-version.ts +7 -1
  180. package/src/types/run/stop-reason.ts +17 -0
@@ -2,7 +2,7 @@
2
2
  * Integration — event stream ordering + lineage + schemaVersion envelope.
3
3
  *
4
4
  * Covers roadmap §5 invariants:
5
- * - §10.1 schemaVersion: 2 on every sub-session RunEvent
5
+ * - §10.1 schemaVersion: 3 on every sub-session RunEvent
6
6
  * - §10.3 tree-scoped monotonic ordering by (rootSessionId, eventId)
7
7
  * - §10.3 depth filter ('self' vs 'tree') at subscribe time
8
8
  * - §10.4 lineage stamped on every sub-session event with parent + root + depth
@@ -32,7 +32,7 @@ import {
32
32
  } from './_fixtures.js'
33
33
 
34
34
  describe('Integration — event stream ordering + lineage + schemaVersion', () => {
35
- it('every sub-session RunEvent carries schemaVersion: 2', async () => {
35
+ it('every sub-session RunEvent carries schemaVersion: 3', async () => {
36
36
  const harness = buildHarness()
37
37
  const { project, thread, session, actor } = await seedActiveParent(harness)
38
38
  harness.registry.register(buildDefinition(buildAgent('worker')))
@@ -59,7 +59,7 @@ describe('Integration — event stream ordering + lineage + schemaVersion', () =
59
59
  )
60
60
  await harness.manager.waitForCompletion(task.taskId)
61
61
 
62
- // Every sub-session lifecycle event is stamped with schemaVersion: 2.
62
+ // Every sub-session lifecycle event is stamped with schemaVersion: 3.
63
63
  const subSessionEvents = captured.filter(
64
64
  (e) =>
65
65
  e.type === 'subsession_spawned' ||
@@ -68,7 +68,7 @@ describe('Integration — event stream ordering + lineage + schemaVersion', () =
68
68
  )
69
69
  expect(subSessionEvents.length).toBeGreaterThan(0)
70
70
  for (const ev of subSessionEvents) {
71
- expect(ev.schemaVersion).toBe(2)
71
+ expect(ev.schemaVersion).toBe(3)
72
72
  }
73
73
  })
74
74
 
@@ -348,9 +348,9 @@ describe('Integration — event stream ordering + lineage + schemaVersion', () =
348
348
  expect(outerDepths.every((d) => d === 1)).toBe(true)
349
349
  })
350
350
 
351
- it('run_started and other core RunEvents also carry schemaVersion: 2 when stamped by the child listener wrapper', async () => {
351
+ it('run_started and other core RunEvents also carry schemaVersion: 3 when stamped by the child listener wrapper', async () => {
352
352
  // The listener wrapper in `manager/agent/lifecycle.ts#wrapChildListener`
353
- // stamps `schemaVersion: 2` + `lineage` on EVERY event emitted inside
353
+ // stamps `schemaVersion: 3` + `lineage` on EVERY event emitted inside
354
354
  // the child's run. Core events that pass through the wrapped listener
355
355
  // therefore inherit the envelope even though they have no lineage in
356
356
  // their own type definition.
@@ -404,7 +404,7 @@ describe('Integration — event stream ordering + lineage + schemaVersion', () =
404
404
  const runStarted = captured.find((e) => e.type === 'run_started')
405
405
  expect(runStarted).toBeDefined()
406
406
  if (runStarted && 'schemaVersion' in runStarted) {
407
- expect(runStarted.schemaVersion).toBe(2)
407
+ expect(runStarted.schemaVersion).toBe(3)
408
408
  }
409
409
  })
410
410
  })
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Behavioural contract for `coalesce()` (ses_001-tool-stream-events phase 1A):
3
+ *
4
+ * - `text_delta` events for the same `messageId` within the configured
5
+ * `windowMs` are merged into a single event whose `text` is the
6
+ * concatenation in arrival order.
7
+ * - `tool_input_delta` events for the same `toolUseId` within the window
8
+ * are merged the same way on `partialJson`.
9
+ * - Any other event flushes pending buffers first, preserving overall
10
+ * stream ordering.
11
+ * - End-of-stream flushes any remaining buffers.
12
+ * - Different `messageId`s and `toolUseId`s never merge with each other.
13
+ *
14
+ * The coalescer is opt-in for slow downstream consumers (SSE adapters);
15
+ * the orchestrator emits raw deltas. A 16ms default roughly aligns with
16
+ * one 60fps animation frame.
17
+ */
18
+
19
+ import { describe, expect, it } from 'vitest'
20
+
21
+ import type { MessageId, RunId, ToolUseId } from '../types/ids/index.js'
22
+ import type { RunEvent } from '../types/run/events.js'
23
+
24
+ import { coalesce } from './coalesce.js'
25
+
26
+ const RID = 'run_1' as RunId
27
+ const MID = 'msg_1' as MessageId
28
+ const MID2 = 'msg_2' as MessageId
29
+ const TUID: ToolUseId = 'toolu_a'
30
+ const TUID2: ToolUseId = 'toolu_b'
31
+
32
+ async function* fromArray(events: RunEvent[]): AsyncIterable<RunEvent> {
33
+ for (const e of events) yield e
34
+ }
35
+
36
+ async function drain(stream: AsyncIterable<RunEvent>): Promise<RunEvent[]> {
37
+ const out: RunEvent[] = []
38
+ for await (const e of stream) out.push(e)
39
+ return out
40
+ }
41
+
42
+ describe('coalesce()', () => {
43
+ it('merges consecutive text_delta events with same messageId within window', async () => {
44
+ const events: RunEvent[] = [
45
+ { type: 'text_delta', runId: RID, iteration: 0, messageId: MID, text: 'hel' },
46
+ { type: 'text_delta', runId: RID, iteration: 0, messageId: MID, text: 'lo' },
47
+ { type: 'text_delta', runId: RID, iteration: 0, messageId: MID, text: ' world' },
48
+ ]
49
+ const result = await drain(coalesce(fromArray(events), { windowMs: 1000 }))
50
+ expect(result).toHaveLength(1)
51
+ expect(result[0]).toMatchObject({
52
+ type: 'text_delta',
53
+ text: 'hello world',
54
+ messageId: MID,
55
+ })
56
+ })
57
+
58
+ it('merges consecutive tool_input_delta events with same toolUseId', async () => {
59
+ const events: RunEvent[] = [
60
+ { type: 'tool_input_delta', runId: RID, toolUseId: TUID, partialJson: '{"file":' },
61
+ { type: 'tool_input_delta', runId: RID, toolUseId: TUID, partialJson: '"/a"' },
62
+ { type: 'tool_input_delta', runId: RID, toolUseId: TUID, partialJson: '}' },
63
+ ]
64
+ const result = await drain(coalesce(fromArray(events), { windowMs: 1000 }))
65
+ expect(result).toHaveLength(1)
66
+ expect(result[0]).toMatchObject({
67
+ type: 'tool_input_delta',
68
+ toolUseId: TUID,
69
+ partialJson: '{"file":"/a"}',
70
+ })
71
+ })
72
+
73
+ it('does not merge across different messageIds', async () => {
74
+ const events: RunEvent[] = [
75
+ { type: 'text_delta', runId: RID, iteration: 0, messageId: MID, text: 'a' },
76
+ { type: 'text_delta', runId: RID, iteration: 0, messageId: MID2, text: 'b' },
77
+ ]
78
+ const result = await drain(coalesce(fromArray(events), { windowMs: 1000 }))
79
+ expect(result).toHaveLength(2)
80
+ })
81
+
82
+ it('does not merge across different toolUseIds', async () => {
83
+ const events: RunEvent[] = [
84
+ { type: 'tool_input_delta', runId: RID, toolUseId: TUID, partialJson: 'x' },
85
+ { type: 'tool_input_delta', runId: RID, toolUseId: TUID2, partialJson: 'y' },
86
+ ]
87
+ const result = await drain(coalesce(fromArray(events), { windowMs: 1000 }))
88
+ expect(result).toHaveLength(2)
89
+ })
90
+
91
+ it('flushes pending buffers when a non-coalescable event arrives', async () => {
92
+ const events: RunEvent[] = [
93
+ { type: 'text_delta', runId: RID, iteration: 0, messageId: MID, text: 'a' },
94
+ { type: 'text_delta', runId: RID, iteration: 0, messageId: MID, text: 'b' },
95
+ {
96
+ type: 'tool_input_started',
97
+ runId: RID,
98
+ iteration: 0,
99
+ messageId: MID,
100
+ toolUseId: TUID,
101
+ toolName: 'Read',
102
+ },
103
+ { type: 'text_delta', runId: RID, iteration: 0, messageId: MID, text: 'c' },
104
+ ]
105
+ const result = await drain(coalesce(fromArray(events), { windowMs: 1000 }))
106
+ expect(result.map((e) => e.type)).toEqual(['text_delta', 'tool_input_started', 'text_delta'])
107
+ expect((result[0] as { text: string }).text).toBe('ab')
108
+ expect((result[2] as { text: string }).text).toBe('c')
109
+ })
110
+
111
+ it('flushes residual buffers at end of stream', async () => {
112
+ const events: RunEvent[] = [
113
+ { type: 'text_delta', runId: RID, iteration: 0, messageId: MID, text: 'tail' },
114
+ ]
115
+ const result = await drain(coalesce(fromArray(events), { windowMs: 1000 }))
116
+ expect(result).toHaveLength(1)
117
+ })
118
+
119
+ it('emits new event after window expires', async () => {
120
+ const events: RunEvent[] = [
121
+ { type: 'text_delta', runId: RID, iteration: 0, messageId: MID, text: 'a' },
122
+ { type: 'text_delta', runId: RID, iteration: 0, messageId: MID, text: 'b' },
123
+ ]
124
+ const stream: AsyncIterable<RunEvent> = (async function* () {
125
+ yield events[0]!
126
+ await new Promise((r) => setTimeout(r, 30))
127
+ yield events[1]!
128
+ })()
129
+ const result = await drain(coalesce(stream, { windowMs: 16 }))
130
+ expect(result).toHaveLength(2)
131
+ })
132
+ })
@@ -0,0 +1,89 @@
1
+ import type { ToolUseId } from '../types/ids/index.js'
2
+ import type { RunEvent } from '../types/run/events.js'
3
+
4
+ interface CoalesceOptions {
5
+ windowMs: number
6
+ }
7
+
8
+ /**
9
+ * Coalesces high-frequency `text_delta` and `tool_input_delta` events into
10
+ * fewer, larger events to relieve downstream backpressure (typically a
11
+ * Server-Sent Events adapter writing to a slow client).
12
+ *
13
+ * Within a sliding `windowMs` window, consecutive `text_delta` events for
14
+ * the same `messageId` are merged by string concatenation; consecutive
15
+ * `tool_input_delta` events for the same `toolUseId` are likewise merged.
16
+ * All other event types pass through immediately and flush any buffered
17
+ * deltas first to preserve ordering.
18
+ *
19
+ * The orchestrator does NOT use this — it emits raw deltas. SSE adapters
20
+ * and other slow consumers opt in. A 16ms window roughly aligns with one
21
+ * UI animation frame at 60fps, which is the empirically derived default
22
+ * for cowork's stream route.
23
+ *
24
+ * Backpressure semantics: this helper does not drop events. If the
25
+ * upstream produces faster than the consumer drains, the helper still
26
+ * yields every coalesced batch; the consumer must apply its own bound or
27
+ * accept queue growth.
28
+ */
29
+ export async function* coalesce(
30
+ stream: AsyncIterable<RunEvent>,
31
+ options: CoalesceOptions = { windowMs: 16 },
32
+ ): AsyncGenerator<RunEvent, void, unknown> {
33
+ const { windowMs } = options
34
+ let textBuf: { event: Extract<RunEvent, { type: 'text_delta' }>; deadline: number } | null = null
35
+ const toolBufs = new Map<
36
+ ToolUseId,
37
+ { event: Extract<RunEvent, { type: 'tool_input_delta' }>; deadline: number }
38
+ >()
39
+
40
+ function* flushAll(): Generator<RunEvent> {
41
+ if (textBuf) {
42
+ yield textBuf.event
43
+ textBuf = null
44
+ }
45
+ for (const buf of toolBufs.values()) {
46
+ yield buf.event
47
+ }
48
+ toolBufs.clear()
49
+ }
50
+
51
+ const now = () => Date.now()
52
+
53
+ for await (const event of stream) {
54
+ if (event.type === 'text_delta') {
55
+ if (textBuf && textBuf.event.messageId === event.messageId && textBuf.deadline > now()) {
56
+ textBuf.event = {
57
+ ...textBuf.event,
58
+ text: textBuf.event.text + event.text,
59
+ }
60
+ } else {
61
+ if (textBuf) yield textBuf.event
62
+ textBuf = { event, deadline: now() + windowMs }
63
+ }
64
+ continue
65
+ }
66
+
67
+ if (event.type === 'tool_input_delta') {
68
+ const existing = toolBufs.get(event.toolUseId)
69
+ if (existing && existing.deadline > now()) {
70
+ existing.event = {
71
+ ...existing.event,
72
+ partialJson: existing.event.partialJson + event.partialJson,
73
+ }
74
+ } else {
75
+ if (existing) yield existing.event
76
+ toolBufs.set(event.toolUseId, {
77
+ event,
78
+ deadline: now() + windowMs,
79
+ })
80
+ }
81
+ continue
82
+ }
83
+
84
+ yield* flushAll()
85
+ yield event
86
+ }
87
+
88
+ yield* flushAll()
89
+ }
@@ -1,5 +1,6 @@
1
1
  import { z } from 'zod'
2
2
  import type { PlanManager } from '../../manager/plan/lifecycle.js'
3
+ import type { AgentRuntimeContext } from '../../types/agent/base.js'
3
4
  import type { TaskGateway } from '../../types/agent/gateway.js'
4
5
  import type { RunId, TaskId } from '../../types/ids/index.js'
5
6
  import type { TaskStore } from '../../types/task/index.js'
@@ -18,6 +19,7 @@ export type TaskLaunchedCallback = (
18
19
  export interface CoordinatorToolsOptions {
19
20
  gateway: TaskGateway
20
21
  workingDirectory: string
22
+ runtimeContext?: AgentRuntimeContext
21
23
  allowedAgentIds: string[]
22
24
 
23
25
  taskStore?: TaskStore
@@ -88,6 +90,7 @@ export function buildCoordinatorTools(opts: CoordinatorToolsOptions): ToolDefini
88
90
  agentId: agent_id,
89
91
  prompt,
90
92
  workingDirectory: cwd,
93
+ runtimeContext: opts.runtimeContext,
91
94
  })
92
95
 
93
96
  if (onTaskLaunched) {
@@ -62,6 +62,13 @@ export interface BaseAgentConfig {
62
62
 
63
63
  export type RuntimeToolOverrides = Record<string, ToolAvailability | 'disabled'>
64
64
 
65
+ export interface AgentRuntimeContext {
66
+ label?: string
67
+ outputDirectory?: string
68
+ outputFileMarker?: string
69
+ notes?: readonly string[]
70
+ }
71
+
65
72
  export interface AgentInput {
66
73
  messages: Message[]
67
74
  workingDirectory: string
@@ -70,6 +77,8 @@ export interface AgentInput {
70
77
  taskStore?: TaskStore
71
78
 
72
79
  runtimeToolOverrides?: RuntimeToolOverrides
80
+
81
+ runtimeContext?: AgentRuntimeContext
73
82
  }
74
83
 
75
84
  export interface BaseAgentResult {
@@ -1,5 +1,5 @@
1
1
  import type { TaskId } from '../ids/index.js'
2
- import type { BaseAgentResult } from './base.js'
2
+ import type { AgentRuntimeContext, BaseAgentResult } from './base.js'
3
3
  import type { AgentTaskState } from './task.js'
4
4
 
5
5
  export interface TaskHandle {
@@ -18,6 +18,8 @@ export interface CreateTaskOptions {
18
18
 
19
19
  workingDirectory: string
20
20
 
21
+ runtimeContext?: AgentRuntimeContext
22
+
21
23
  configOverrides?: Record<string, unknown>
22
24
  }
23
25
 
@@ -2,6 +2,16 @@ export type RunId = `run_${string}`
2
2
  export type MessageId = `msg_${string}`
3
3
  export type SessionId = `ses_${string}`
4
4
  export type ToolCallId = `call_${string}`
5
+ /**
6
+ * Provider-issued tool-use identifier surfaced on the streaming event bus.
7
+ * Providers emit different prefixes (Anthropic: `toolu_*`, OpenAI: `call_*`,
8
+ * others vary), so this type intentionally stays unbranded — we accept the
9
+ * provider's verbatim string and use it solely as a correlation key across
10
+ * `tool_input_*`, `tool_executing`, and `tool_completed` events. Distinct
11
+ * from {@link ToolCallId} which is the OpenAI-format identifier carried in
12
+ * persisted assistant messages and replay records.
13
+ */
14
+ export type ToolUseId = string
5
15
  export type ActivityId = `act_${string}`
6
16
  export type TaskId = `task_${string}`
7
17
  export type PlanId = `plan_${string}`
@@ -1,4 +1,6 @@
1
- import type { ChatCompletionParams, ChatCompletionResponse } from './chat.js'
1
+ import type { DoctorCheckResult } from '../doctor/index.js'
2
+
3
+ import type { ChatCompletionParams } from './chat.js'
2
4
  import type { ModelInfo } from './model.js'
3
5
  import type { StreamChunk } from './stream.js'
4
6
 
@@ -6,11 +8,34 @@ export interface LLMProvider {
6
8
  readonly id: string
7
9
  readonly name: string
8
10
 
9
- chat(params: ChatCompletionParams): Promise<ChatCompletionResponse>
10
-
11
+ /**
12
+ * The single LLM entry point. Returns an async iterable of
13
+ * {@link StreamChunk} carrying text deltas, tool-call argument
14
+ * fragments, and per-tool-block boundary signals (`toolCallEnd`).
15
+ *
16
+ * Consumers that need an aggregated response (legacy
17
+ * `ChatCompletionResponse` shape) call
18
+ * `collect(provider.chatStream(params))` from
19
+ * `@namzu/sdk/provider/collect`. The kernel's iteration
20
+ * orchestrator consumes the stream directly so it can emit
21
+ * per-delta `RunEvent`s.
22
+ *
23
+ * Phase 2 of ses_001-tool-stream-events removed the previous
24
+ * non-streaming `chat()` method from this interface.
25
+ */
11
26
  chatStream(params: ChatCompletionParams): AsyncIterable<StreamChunk>
12
27
 
13
28
  listModels?(): Promise<ModelInfo[]>
14
29
 
15
30
  healthCheck?(): Promise<boolean>
31
+
32
+ /**
33
+ * Optional structured health probe used by `runDoctor()`.
34
+ *
35
+ * Returns a `DoctorCheckResult` with provider-specific detail
36
+ * (latency, model availability, auth status, …). Providers that
37
+ * cannot be cheaply probed should return `{ status: 'inconclusive' }`
38
+ * so the doctor doesn't mark them as failing — see ses_007 Q6.4.
39
+ */
40
+ doctorCheck?(): Promise<DoctorCheckResult>
16
41
  }
@@ -13,6 +13,24 @@ export interface StreamChunk {
13
13
  arguments?: string
14
14
  }
15
15
  }>
16
+ /**
17
+ * Provider signal that a tool-use content block has finished
18
+ * streaming arguments. Translates from Anthropic's
19
+ * `content_block_stop` (for tool_use blocks) and from the
20
+ * equivalent end-of-tool-arguments boundary on other providers.
21
+ *
22
+ * The orchestrator uses this to emit `tool_input_completed` per
23
+ * tool as soon as its block closes, rather than waiting for
24
+ * `message_stop`. Providers that cannot emit a per-tool boundary
25
+ * leave this undefined; the orchestrator infers from
26
+ * end-of-stream instead.
27
+ *
28
+ * Added 2026-05-01 (ses_001-tool-stream-events A9).
29
+ */
30
+ toolCallEnd?: {
31
+ index: number
32
+ id: string
33
+ }
16
34
  }
17
35
  finishReason?: 'stop' | 'tool_calls' | 'length' | 'content_filter'
18
36
  usage?: TokenUsage
@@ -2,18 +2,28 @@ import type { ActivityStatus, ActivityType } from '../activity/index.js'
2
2
  import type { BaseAgentResult } from '../agent/base.js'
3
3
  import type { CostInfo, TokenUsage } from '../common/index.js'
4
4
  import type { CheckpointId, ToolCallSummary } from '../hitl/index.js'
5
- import type { ActivityId, PlanId, PluginId, RunId, SandboxId, TaskId } from '../ids/index.js'
5
+ import type {
6
+ ActivityId,
7
+ MessageId,
8
+ PlanId,
9
+ PluginId,
10
+ RunId,
11
+ SandboxId,
12
+ TaskId,
13
+ ToolUseId,
14
+ } from '../ids/index.js'
6
15
  import type { PlanStep } from '../plan/index.js'
7
16
  import type { PluginHookEvent, PluginHookResult } from '../plugin/index.js'
8
17
  import type { TaskStatus } from '../task/index.js'
9
18
  import type { Lineage } from './lineage.js'
19
+ import type { MessageStopReason } from './stop-reason.js'
10
20
  import type {
11
21
  SubsessionIdledEvent,
12
22
  SubsessionMessagedEvent,
13
23
  SubsessionSpawnedEvent,
14
24
  } from './subsession-events.js'
15
25
 
16
- export type { StopReason } from './stop-reason.js'
26
+ export type { MessageStopReason, StopReason } from './stop-reason.js'
17
27
 
18
28
  /**
19
29
  * Additive envelope fields present on every {@link RunEvent} variant.
@@ -25,7 +35,13 @@ export type { StopReason } from './stop-reason.js'
25
35
  * absent on root-session events.
26
36
  */
27
37
  interface RunEventEnvelope {
28
- schemaVersion?: 2
38
+ /**
39
+ * v3 envelope (ses_001-tool-stream-events, 2026-05-01). Removes
40
+ * `llm_response`; adds message + tool-input lifecycle variants;
41
+ * tightens `tool_executing` / `tool_completed` payloads. Emitters
42
+ * stamp this from {@link RUN_EVENT_SCHEMA_VERSION}.
43
+ */
44
+ schemaVersion?: 3
29
45
  lineage?: Lineage
30
46
  }
31
47
 
@@ -38,23 +54,20 @@ type CoreRunEvent =
38
54
  iteration: number
39
55
  hasToolCalls: boolean
40
56
  }
41
- | {
42
- type: 'llm_response'
43
- runId: RunId
44
- content: string | null
45
- hasToolCalls: boolean
46
- }
47
57
  | {
48
58
  type: 'tool_executing'
49
59
  runId: RunId
60
+ toolUseId: ToolUseId
50
61
  toolName: string
51
62
  input: unknown
52
63
  }
53
64
  | {
54
65
  type: 'tool_completed'
55
66
  runId: RunId
67
+ toolUseId: ToolUseId
56
68
  toolName: string
57
69
  result: string
70
+ isError: boolean
58
71
  }
59
72
  | {
60
73
  type: 'tool_review_requested'
@@ -193,6 +206,64 @@ type CoreRunEvent =
193
206
  durationMs: number
194
207
  }
195
208
  | { type: 'sandbox_destroyed'; runId: RunId; sandboxId: SandboxId }
209
+ // ─────────────────────────────────────────────────────────────────────
210
+ // v3 message + tool-input lifecycle (additive 2026-05; see
211
+ // ses_001-tool-stream-events). These are not yet emitted by the
212
+ // iteration orchestrator — phase 4 of the migration switches the
213
+ // orchestrator to streaming consumption and removes `llm_response`.
214
+ // Until then these variants exist so consumers can be wired ahead of
215
+ // the producer-side cutover.
216
+ // ─────────────────────────────────────────────────────────────────────
217
+ | {
218
+ type: 'message_started'
219
+ runId: RunId
220
+ iteration: number
221
+ messageId: MessageId
222
+ }
223
+ | {
224
+ type: 'text_delta'
225
+ runId: RunId
226
+ iteration: number
227
+ messageId: MessageId
228
+ text: string
229
+ }
230
+ | {
231
+ type: 'message_completed'
232
+ runId: RunId
233
+ iteration: number
234
+ messageId: MessageId
235
+ stopReason: MessageStopReason
236
+ usage?: TokenUsage
237
+ /**
238
+ * Aggregated assistant text accumulated from `text_delta`
239
+ * events for this message. Optional so consumers that
240
+ * already concatenate deltas themselves don't have to pay
241
+ * the duplication; consumers that only care about the
242
+ * completed message (telemetry, A2A bridge, postmortem
243
+ * tooling) can read this field directly.
244
+ */
245
+ content?: string
246
+ }
247
+ | {
248
+ type: 'tool_input_started'
249
+ runId: RunId
250
+ iteration: number
251
+ messageId: MessageId
252
+ toolUseId: ToolUseId
253
+ toolName: string
254
+ }
255
+ | {
256
+ type: 'tool_input_delta'
257
+ runId: RunId
258
+ toolUseId: ToolUseId
259
+ partialJson: string
260
+ }
261
+ | {
262
+ type: 'tool_input_completed'
263
+ runId: RunId
264
+ toolUseId: ToolUseId
265
+ input: unknown
266
+ }
196
267
 
197
268
  /**
198
269
  * Discriminated union of all run-scoped events emitted by the kernel.
@@ -210,3 +281,28 @@ export type RunEvent =
210
281
  | SubsessionIdledEvent
211
282
 
212
283
  export type RunEventListener = (event: RunEvent) => void | Promise<void>
284
+
285
+ /**
286
+ * Event types whose volume makes durable persistence wasteful.
287
+ *
288
+ * `text_delta` and `tool_input_delta` arrive at provider cadence (often
289
+ * 50–100 events per second), carry no information not derivable from the
290
+ * surrounding message/tool lifecycle events, and are not consulted by
291
+ * replay (`runtime/query/replay/prepare.ts` reads checkpoints, not the
292
+ * transcript). The kernel still dispatches them on the in-memory bus so
293
+ * SSE consumers can render live progress, but the disk store
294
+ * (`store/run/disk.ts:appendEvent`) skips them via this predicate.
295
+ *
296
+ * Keeping the predicate centralised — rather than threading an
297
+ * `ephemeral: true` field through every emit site — means new ephemeral
298
+ * variants are added by editing one Set and consumers don't have to
299
+ * inspect event shape to decide what to persist.
300
+ */
301
+ const EPHEMERAL_EVENT_TYPES: ReadonlySet<RunEvent['type']> = new Set<RunEvent['type']>([
302
+ 'text_delta',
303
+ 'tool_input_delta',
304
+ ])
305
+
306
+ export function isEphemeralEvent(event: RunEvent): boolean {
307
+ return EPHEMERAL_EVENT_TYPES.has(event.type)
308
+ }
@@ -4,10 +4,16 @@
4
4
  * - v1: pre-0.2.0 (implicit; untagged events are treated as v1 by consumers).
5
5
  * - v2: 0.2.0+ — adds `schemaVersion`, `lineage`, and sub-session lifecycle
6
6
  * events.
7
+ * - v3: 2026-05-01 — removes `llm_response`; adds message + tool-input
8
+ * lifecycle variants (`message_started`, `text_delta`,
9
+ * `message_completed`, `tool_input_started`, `tool_input_delta`,
10
+ * `tool_input_completed`); `tool_executing`/`tool_completed` carry
11
+ * required `toolUseId`; `tool_completed` carries required `isError`.
12
+ * See ses_001-tool-stream-events.
7
13
  *
8
14
  * See session-hierarchy.md §10.1 (Event-schema evolution contract) and
9
15
  * §13.3.2 (`schemaVersion` back-compat).
10
16
  */
11
- export const RUN_EVENT_SCHEMA_VERSION = 2 as const
17
+ export const RUN_EVENT_SCHEMA_VERSION = 3 as const
12
18
 
13
19
  export type RunEventSchemaVersion = typeof RUN_EVENT_SCHEMA_VERSION
@@ -8,3 +8,20 @@ export type StopReason =
8
8
  | 'plan_rejected'
9
9
  | 'paused'
10
10
  | 'error'
11
+
12
+ /**
13
+ * Per-LLM-message stop reason — distinct from the run-level {@link StopReason}.
14
+ *
15
+ * Mirrors the union of Anthropic and OpenAI finish reasons normalised into a
16
+ * provider-agnostic vocabulary. `forced_finalize` is a Namzu-specific value
17
+ * emitted by the orchestrator when iteration limits force a final response
18
+ * without a model-issued stop reason.
19
+ */
20
+ export type MessageStopReason =
21
+ | 'end_turn'
22
+ | 'tool_use'
23
+ | 'max_tokens'
24
+ | 'stop_sequence'
25
+ | 'pause_turn'
26
+ | 'refusal'
27
+ | 'forced_finalize'