@kuralle-agents/core 0.13.0 → 0.14.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 (158) hide show
  1. package/README.md +4 -4
  2. package/dist/ai-sdk/uiMessageStream.d.ts +2 -2
  3. package/dist/ai-sdk/uiMessageStream.js +42 -29
  4. package/dist/capabilities/LivePromptAssembler.d.ts +1 -1
  5. package/dist/capabilities/LivePromptAssembler.js +1 -1
  6. package/dist/capabilities/index.d.ts +1 -1
  7. package/dist/eval/EvalRunner.js +4 -4
  8. package/dist/eval/simulation.js +4 -3
  9. package/dist/events/TurnHandle.d.ts +3 -3
  10. package/dist/flow/collectUntilComplete.js +4 -4
  11. package/dist/flow/degrade.js +8 -4
  12. package/dist/flow/emitInteractive.d.ts +2 -2
  13. package/dist/flow/emitInteractive.js +6 -3
  14. package/dist/flow/extraction.d.ts +2 -2
  15. package/dist/flow/extraction.js +10 -4
  16. package/dist/flow/reduceTransition.d.ts +2 -2
  17. package/dist/flow/reduceTransition.js +7 -3
  18. package/dist/flow/runFlow.js +34 -12
  19. package/dist/foundation/AgentStateController.d.ts +1 -1
  20. package/dist/foundation/ToolExecutor.d.ts +2 -2
  21. package/dist/foundation/index.d.ts +0 -11
  22. package/dist/foundation/index.js +0 -5
  23. package/dist/index.d.ts +9 -14
  24. package/dist/index.js +5 -6
  25. package/dist/outcomes/streamPart.d.ts +2 -2
  26. package/dist/outcomes/streamPart.js +2 -3
  27. package/dist/prompts/PromptBuilder.d.ts +3 -3
  28. package/dist/prompts/PromptBuilder.js +6 -6
  29. package/dist/prompts/index.d.ts +1 -1
  30. package/dist/prompts/types.d.ts +7 -2
  31. package/dist/runtime/InMemoryRetrievalCache.d.ts +1 -1
  32. package/dist/runtime/KnowledgeProvider.d.ts +3 -2
  33. package/dist/runtime/KnowledgeProvider.js +54 -30
  34. package/dist/runtime/Runtime.d.ts +3 -1
  35. package/dist/runtime/Runtime.js +84 -30
  36. package/dist/runtime/TraceRecorder.d.ts +22 -4
  37. package/dist/runtime/TraceRecorder.js +65 -29
  38. package/dist/runtime/buildAgentToolSurface.js +1 -1
  39. package/dist/runtime/channels/TextDriver.js +12 -9
  40. package/dist/runtime/channels/executeModelTool.d.ts +6 -0
  41. package/dist/runtime/channels/executeModelTool.js +70 -10
  42. package/dist/runtime/channels/extractionTurn.js +1 -1
  43. package/dist/runtime/channels/index.d.ts +0 -3
  44. package/dist/runtime/channels/index.js +0 -6
  45. package/dist/runtime/channels/streaming/hostControlSpeak.js +5 -1
  46. package/dist/runtime/channels/streaming/speakGated.js +24 -12
  47. package/dist/runtime/citations/index.d.ts +3 -3
  48. package/dist/runtime/closeRun.js +3 -2
  49. package/dist/runtime/controlFlowSignal.d.ts +24 -0
  50. package/dist/runtime/controlFlowSignal.js +29 -0
  51. package/dist/runtime/ctx.d.ts +3 -4
  52. package/dist/runtime/ctx.js +39 -5
  53. package/dist/runtime/durable/RunStore.js +1 -1
  54. package/dist/runtime/grounding/gather.d.ts +1 -1
  55. package/dist/runtime/grounding/inMemoryKnowledge.d.ts +1 -1
  56. package/dist/runtime/grounding/knowledge.d.ts +1 -1
  57. package/dist/runtime/hostLoop.js +26 -6
  58. package/dist/runtime/index.d.ts +1 -2
  59. package/dist/runtime/index.js +1 -1
  60. package/dist/runtime/outcomeMarking.d.ts +2 -2
  61. package/dist/runtime/outcomeMarking.js +2 -1
  62. package/dist/runtime/policies/agentTurn.d.ts +1 -1
  63. package/dist/runtime/runHookSafely.d.ts +3 -0
  64. package/dist/runtime/runHookSafely.js +19 -0
  65. package/dist/scheduler/index.d.ts +2 -2
  66. package/dist/scheduler/index.js +1 -1
  67. package/dist/services/TracingService.js +2 -3
  68. package/dist/skills/collectSkills.d.ts +11 -3
  69. package/dist/skills/collectSkills.js +51 -8
  70. package/dist/skills/compositeSkillStore.d.ts +26 -0
  71. package/dist/skills/compositeSkillStore.js +54 -0
  72. package/dist/skills/defineSkill.d.ts +21 -0
  73. package/dist/skills/defineSkill.js +26 -0
  74. package/dist/skills/fsSkillStore.d.ts +3 -0
  75. package/dist/skills/fsSkillStore.js +79 -0
  76. package/dist/skills/index.d.ts +4 -0
  77. package/dist/skills/index.js +4 -0
  78. package/dist/skills/parseSkillFrontmatter.d.ts +14 -0
  79. package/dist/skills/parseSkillFrontmatter.js +183 -0
  80. package/dist/skills/wireAgentSkills.d.ts +2 -1
  81. package/dist/skills/wireAgentSkills.js +2 -2
  82. package/dist/testing/mocks.d.ts +3 -3
  83. package/dist/testing/mocks.js +7 -3
  84. package/dist/tools/controlResults.d.ts +14 -0
  85. package/dist/tools/controlResults.js +15 -0
  86. package/dist/tools/effect/ToolExecutor.d.ts +6 -0
  87. package/dist/tools/effect/ToolExecutor.js +71 -35
  88. package/dist/tools/effect/defineTool.d.ts +9 -0
  89. package/dist/tools/effect/defineTool.js +1 -0
  90. package/dist/tools/effect/errors.d.ts +2 -5
  91. package/dist/tools/effect/errors.js +2 -5
  92. package/dist/tools/index.d.ts +0 -1
  93. package/dist/tools/index.js +0 -1
  94. package/dist/types/effectTool.d.ts +2 -0
  95. package/dist/types/flow.d.ts +1 -1
  96. package/dist/types/guardrails.d.ts +5 -0
  97. package/dist/types/hooks.d.ts +2 -2
  98. package/dist/types/index.d.ts +25 -16
  99. package/dist/types/index.js +8 -13
  100. package/dist/types/knowledge.d.ts +213 -0
  101. package/dist/types/processors.d.ts +0 -58
  102. package/dist/types/run-context.d.ts +3 -7
  103. package/dist/types/runtime.d.ts +3 -74
  104. package/dist/types/session.d.ts +1 -1
  105. package/dist/types/skills.d.ts +15 -1
  106. package/dist/types/stream.d.ts +133 -71
  107. package/dist/types/stream.js +34 -1
  108. package/dist/types/trace.d.ts +2 -0
  109. package/guides/AGENTS.md +3 -3
  110. package/guides/FLOWS.md +1 -1
  111. package/guides/GETTING_STARTED.md +1 -1
  112. package/guides/RUNTIME.md +20 -15
  113. package/guides/TOOLS.md +1 -1
  114. package/package.json +3 -13
  115. package/dist/foundation/AgentDefinition.d.ts +0 -17
  116. package/dist/foundation/ConversationEventLog.d.ts +0 -71
  117. package/dist/foundation/ConversationEventLog.js +0 -1
  118. package/dist/foundation/ConversationState.d.ts +0 -33
  119. package/dist/foundation/ConversationState.js +0 -1
  120. package/dist/foundation/DefaultConversationEventLog.d.ts +0 -27
  121. package/dist/foundation/DefaultConversationEventLog.js +0 -205
  122. package/dist/foundation/DefaultConversationState.d.ts +0 -36
  123. package/dist/foundation/DefaultConversationState.js +0 -103
  124. package/dist/foundation/DefaultToolExecutor.d.ts +0 -57
  125. package/dist/foundation/DefaultToolExecutor.js +0 -132
  126. package/dist/foundation/createFoundation.d.ts +0 -32
  127. package/dist/foundation/createFoundation.js +0 -33
  128. package/dist/hooks/HookRunner.d.ts +0 -37
  129. package/dist/hooks/HookRunner.js +0 -111
  130. package/dist/hooks/builtin/logging.d.ts +0 -5
  131. package/dist/hooks/builtin/logging.js +0 -98
  132. package/dist/hooks/builtin/metrics.d.ts +0 -7
  133. package/dist/hooks/builtin/metrics.js +0 -60
  134. package/dist/hooks/builtin/observability.d.ts +0 -20
  135. package/dist/hooks/builtin/observability.js +0 -572
  136. package/dist/hooks/helpers.d.ts +0 -22
  137. package/dist/hooks/helpers.js +0 -183
  138. package/dist/hooks/index.d.ts +0 -8
  139. package/dist/hooks/index.js +0 -6
  140. package/dist/realtime/RealtimeAudioClient.d.ts +0 -166
  141. package/dist/realtime/RealtimeAudioClient.js +0 -14
  142. package/dist/realtime/RealtimeSessionHandle.d.ts +0 -6
  143. package/dist/realtime/RealtimeSessionHandle.js +0 -1
  144. package/dist/realtime/index.d.ts +0 -2
  145. package/dist/realtime/index.js +0 -1
  146. package/dist/runtime/channels/VoiceDriver.d.ts +0 -31
  147. package/dist/runtime/channels/VoiceDriver.js +0 -321
  148. package/dist/runtime/channels/voiceTools.d.ts +0 -9
  149. package/dist/runtime/channels/voiceTools.js +0 -51
  150. package/dist/tools/errorHandling.d.ts +0 -35
  151. package/dist/tools/errorHandling.js +0 -145
  152. package/dist/types/voice.d.ts +0 -517
  153. package/dist/types/voice.js +0 -14
  154. package/dist/utils/isRecord.d.ts +0 -1
  155. package/dist/utils/isRecord.js +0 -3
  156. package/dist/utils/messageNormalization.d.ts +0 -2
  157. package/dist/utils/messageNormalization.js +0 -137
  158. /package/dist/{foundation/AgentDefinition.js → types/knowledge.js} +0 -0
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @kuralle-agents/core
2
2
 
3
- The runtime and primitives for building conversational AI agents — text and voice — with structured flows, routing, and durable tool execution.
3
+ The runtime and primitives for building conversational AI agents with structured flows, routing, and durable tool execution.
4
4
 
5
5
  ## Install
6
6
 
@@ -54,8 +54,8 @@ const runtime = createRuntime({ agents: [agent], defaultAgentId: 'support' });
54
54
 
55
55
  const handle = runtime.run({ input: 'Hello', sessionId: 'demo' });
56
56
  for await (const part of handle.events) { // events is a property, not a method
57
- if (part.type === 'text-delta') process.stdout.write(part.delta);
58
- if (part.type === 'done') console.log('\nSession:', part.sessionId);
57
+ if (part.type === 'text-delta') process.stdout.write(part.payload.delta);
58
+ if (part.type === 'done') console.log('\nSession:', part.payload.sessionId);
59
59
  }
60
60
  await handle; // resolves to TurnResult once the stream is consumed
61
61
  ```
@@ -230,7 +230,7 @@ return handle.toUIMessageStreamResponse({ sessionId: 'demo' });
230
230
 
231
231
  Kuralle orchestration events (flow telemetry, safety blocks, interactive choices) arrive as typed `data-kuralle-*` parts. Import `KuralleUIMessage` and `KuralleDataParts` for compile-time-safe `message.parts` and `useChat({ onData })` handlers.
232
232
 
233
- For non-UI consumers (curl, custom transports), use `handle.toResponseStream('sse')` to emit raw `HarnessStreamPart` JSON-SSE. Or use `@kuralle-agents/hono-server` — `POST /api/chat/sse` defaults to native `UIMessageStream`; append `?format=raw` for the legacy wire.
233
+ For non-UI consumers (curl, custom transports), use `handle.toResponseStream('sse')` to emit raw `StreamPart` JSON-SSE. Or use `@kuralle-agents/hono-server` — `POST /api/chat/sse` defaults to native `UIMessageStream`; append `?format=raw` for the legacy wire.
234
234
 
235
235
  ## Related
236
236
 
@@ -1,7 +1,7 @@
1
1
  import { type UIMessage } from 'ai';
2
2
  import type { ConversationOutcome } from '../outcomes/types.js';
3
3
  import type { ChoiceOption } from '../types/selection.js';
4
- import type { HarnessStreamPart } from '../types/stream.js';
4
+ import type { StreamPart } from '../types/stream.js';
5
5
  export type KuralleMetadata = {
6
6
  sessionId?: string;
7
7
  };
@@ -46,6 +46,6 @@ export type KuralleDataParts = {
46
46
  };
47
47
  };
48
48
  export type KuralleUIMessage = UIMessage<KuralleMetadata, KuralleDataParts>;
49
- export declare function harnessToUIMessageStream(source: AsyncIterable<HarnessStreamPart>, opts?: {
49
+ export declare function harnessToUIMessageStream(source: AsyncIterable<StreamPart>, opts?: {
50
50
  sessionId?: string;
51
51
  }): ReadableStream;
@@ -2,64 +2,64 @@ import { createUIMessageStream, generateId, } from 'ai';
2
2
  function writeHarnessPart(part, writer) {
3
3
  switch (part.type) {
4
4
  case 'text-start':
5
- writer.write({ type: 'text-start', id: part.id });
5
+ writer.write({ type: 'text-start', id: part.payload.id });
6
6
  break;
7
7
  case 'text-delta':
8
- writer.write({ type: 'text-delta', id: part.id, delta: part.delta });
8
+ writer.write({ type: 'text-delta', id: part.payload.id, delta: part.payload.delta });
9
9
  break;
10
10
  case 'text-end':
11
- writer.write({ type: 'text-end', id: part.id });
11
+ writer.write({ type: 'text-end', id: part.payload.id });
12
12
  break;
13
13
  case 'text-cancel':
14
- writer.write({ type: 'text-end', id: part.id });
14
+ writer.write({ type: 'text-end', id: part.payload.id });
15
15
  break;
16
16
  case 'tool-call':
17
17
  writer.write({
18
18
  type: 'tool-input-available',
19
- toolCallId: part.toolCallId ?? generateId(),
20
- toolName: part.toolName,
21
- input: part.args,
19
+ toolCallId: part.payload.toolCallId ?? generateId(),
20
+ toolName: part.payload.toolName,
21
+ input: part.payload.args,
22
22
  });
23
23
  break;
24
24
  case 'tool-result':
25
25
  writer.write({
26
26
  type: 'tool-output-available',
27
- toolCallId: part.toolCallId ?? 'unknown',
28
- output: part.result,
27
+ toolCallId: part.payload.toolCallId ?? 'unknown',
28
+ output: part.payload.result,
29
29
  });
30
30
  break;
31
31
  case 'node-enter':
32
32
  writer.write({
33
33
  type: 'data-kuralle-node',
34
- data: { event: 'enter', node: part.nodeName },
34
+ data: { event: 'enter', node: part.payload.nodeName },
35
35
  transient: true,
36
36
  });
37
37
  break;
38
38
  case 'node-exit':
39
39
  writer.write({
40
40
  type: 'data-kuralle-node',
41
- data: { event: 'exit', node: part.nodeName },
41
+ data: { event: 'exit', node: part.payload.nodeName },
42
42
  transient: true,
43
43
  });
44
44
  break;
45
45
  case 'flow-enter':
46
46
  writer.write({
47
47
  type: 'data-kuralle-flow',
48
- data: { event: 'enter', flow: part.flow },
48
+ data: { event: 'enter', flow: part.payload.flow },
49
49
  transient: true,
50
50
  });
51
51
  break;
52
52
  case 'flow-end':
53
53
  writer.write({
54
54
  type: 'data-kuralle-flow',
55
- data: { event: 'end', flow: part.flow, reason: part.reason },
55
+ data: { event: 'end', flow: part.payload.flow, reason: part.payload.reason },
56
56
  transient: true,
57
57
  });
58
58
  break;
59
59
  case 'flow-transition':
60
60
  writer.write({
61
61
  type: 'data-kuralle-flow',
62
- data: { event: 'transition', from: part.from, to: part.to },
62
+ data: { event: 'transition', from: part.payload.from, to: part.payload.to },
63
63
  transient: true,
64
64
  });
65
65
  break;
@@ -67,14 +67,18 @@ function writeHarnessPart(part, writer) {
67
67
  writer.write({
68
68
  type: 'data-kuralle-handoff',
69
69
  id: generateId(),
70
- data: { targetAgent: part.targetAgent, reason: part.reason },
70
+ data: { targetAgent: part.payload.targetAgent, reason: part.payload.reason },
71
71
  });
72
72
  break;
73
73
  case 'interactive':
74
74
  writer.write({
75
75
  type: 'data-kuralle-interactive',
76
- id: part.nodeId,
77
- data: { nodeId: part.nodeId, prompt: part.prompt, options: part.options },
76
+ id: part.payload.nodeId,
77
+ data: {
78
+ nodeId: part.payload.nodeId,
79
+ prompt: part.payload.prompt,
80
+ options: part.payload.options,
81
+ },
78
82
  });
79
83
  break;
80
84
  case 'safety-blocked':
@@ -83,9 +87,9 @@ function writeHarnessPart(part, writer) {
83
87
  id: generateId(),
84
88
  data: {
85
89
  kind: 'safety-blocked',
86
- moderator: part.moderator,
87
- rationale: part.rationale,
88
- userFacingMessage: part.userFacingMessage,
90
+ moderator: part.payload.moderator,
91
+ rationale: part.payload.rationale,
92
+ userFacingMessage: part.payload.userFacingMessage,
89
93
  },
90
94
  });
91
95
  break;
@@ -95,8 +99,8 @@ function writeHarnessPart(part, writer) {
95
99
  id: generateId(),
96
100
  data: {
97
101
  kind: 'pipeline-validation-block',
98
- rationale: part.rationale,
99
- userFacingMessage: part.userFacingMessage,
102
+ rationale: part.payload.rationale,
103
+ userFacingMessage: part.payload.userFacingMessage,
100
104
  },
101
105
  });
102
106
  break;
@@ -104,35 +108,44 @@ function writeHarnessPart(part, writer) {
104
108
  writer.write({
105
109
  type: 'data-kuralle-outcome',
106
110
  id: generateId(),
107
- data: { outcome: part.outcome },
111
+ data: { outcome: part.payload.outcome },
108
112
  });
109
113
  break;
110
114
  case 'interrupted':
111
115
  writer.write({
112
116
  type: 'data-kuralle-control',
113
- data: { event: 'interrupted', reason: part.reason },
117
+ data: { event: 'interrupted', reason: part.payload.reason },
114
118
  transient: true,
115
119
  });
116
120
  break;
117
121
  case 'paused':
118
122
  writer.write({
119
123
  type: 'data-kuralle-control',
120
- data: { event: 'paused', waitingFor: part.waitingFor },
124
+ data: { event: 'paused', waitingFor: part.payload.waitingFor },
121
125
  transient: true,
122
126
  });
123
127
  break;
124
128
  case 'custom':
125
129
  writer.write({
126
130
  type: 'data-kuralle-custom',
127
- data: { name: part.name, data: part.data },
131
+ data: { name: part.payload.name, data: part.payload.data },
128
132
  transient: true,
129
133
  });
130
134
  break;
131
135
  case 'error':
132
- throw new Error(part.error);
136
+ throw new Error(part.payload.error);
133
137
  case 'done':
134
138
  case 'turn-end':
135
139
  break;
140
+ default: {
141
+ // Client-channel exhaustiveness guard. Internal-channel parts are
142
+ // deliberately unmapped here — they never reach a UI client. But a new
143
+ // `client` variant added without a case above would be silently dropped
144
+ // on the ADR-0005 default output path, so pin the residual to internal:
145
+ // adding a client variant without a case fails this assignment.
146
+ const _internalOnly = part;
147
+ void _internalOnly;
148
+ }
136
149
  }
137
150
  }
138
151
  export function harnessToUIMessageStream(source, opts) {
@@ -146,8 +159,8 @@ export function harnessToUIMessageStream(source, opts) {
146
159
  });
147
160
  }
148
161
  for await (const part of source) {
149
- if (part.type === 'done' && part.sessionId) {
150
- doneSessionId = doneSessionId ?? part.sessionId;
162
+ if (part.type === 'done' && part.payload.sessionId) {
163
+ doneSessionId = doneSessionId ?? part.payload.sessionId;
151
164
  }
152
165
  else {
153
166
  writeHarnessPart(part, writer);
@@ -7,7 +7,7 @@
7
7
  * CapabilityHost with Runtime context (working memory, long-term memory,
8
8
  * policy injections) and structures the output per audio-model best practices.
9
9
  *
10
- * Adapters (Gemini, OpenAI Realtime, LiveKit) can extend or replace
10
+ * Adapters can extend or replace
11
11
  * the default with backend-specific voice rules and guardrails.
12
12
  *
13
13
  * Prompt structure follows Google's Live API best practices and
@@ -7,7 +7,7 @@
7
7
  * CapabilityHost with Runtime context (working memory, long-term memory,
8
8
  * policy injections) and structures the output per audio-model best practices.
9
9
  *
10
- * Adapters (Gemini, OpenAI Realtime, LiveKit) can extend or replace
10
+ * Adapters can extend or replace
11
11
  * the default with backend-specific voice rules and guardrails.
12
12
  *
13
13
  * Prompt structure follows Google's Live API best practices and
@@ -1,7 +1,7 @@
1
1
  import type { ZodTypeAny } from 'zod';
2
2
  /**
3
3
  * Backend-agnostic tool declaration. Produced by capabilities,
4
- * consumed by adapters (Gemini, AI SDK, LiveKit).
4
+ * consumed by adapters (AI SDK and others).
5
5
  */
6
6
  export interface ToolDeclaration<TInput = unknown, TOutput = unknown> {
7
7
  name: string;
@@ -25,16 +25,16 @@ export class EvalRunner {
25
25
  const handle = runtime.run({ input: turn.input, sessionId });
26
26
  for await (const part of handle.events) {
27
27
  if (part.type === 'text-delta') {
28
- response += part.delta;
28
+ response += part.payload.delta;
29
29
  }
30
30
  if (part.type === 'tool-call') {
31
- toolsCalled.push(part.toolName);
31
+ toolsCalled.push(part.payload.toolName);
32
32
  }
33
33
  if (part.type === 'flow-transition') {
34
- transitions.push({ from: part.from, to: part.to });
34
+ transitions.push({ from: part.payload.from, to: part.payload.to });
35
35
  }
36
36
  if (part.type === 'done') {
37
- sessionId = part.sessionId;
37
+ sessionId = part.payload.sessionId;
38
38
  }
39
39
  }
40
40
  await handle;
@@ -66,10 +66,11 @@ export async function simulateConversation(options) {
66
66
  let reply = '';
67
67
  for await (const part of handle.events) {
68
68
  if (part.type === 'text-delta')
69
- reply += part.delta;
69
+ reply += part.payload.delta;
70
70
  if (part.type === 'tool-call')
71
- toolsCalled.push(part.toolName);
72
- if (part.type === 'escalation' || (part.type === 'handoff' && part.targetAgent === 'human')) {
71
+ toolsCalled.push(part.payload.toolName);
72
+ if (part.type === 'escalation' ||
73
+ (part.type === 'handoff' && part.payload.targetAgent === 'human')) {
73
74
  escalated = true;
74
75
  }
75
76
  }
@@ -1,7 +1,7 @@
1
- import type { HarnessStreamPart, TurnHandle } from '../types/stream.js';
1
+ import type { StreamPart, TurnHandle } from '../types/stream.js';
2
2
  export interface EventBus {
3
- emit(part: HarnessStreamPart): void;
4
- events(): AsyncIterable<HarnessStreamPart>;
3
+ emit(part: StreamPart): void;
4
+ events(): AsyncIterable<StreamPart>;
5
5
  close(): void;
6
6
  }
7
7
  export declare function createEventBus(): EventBus;
@@ -121,10 +121,10 @@ export function emitCollectAsk(node, run, ctx) {
121
121
  return;
122
122
  }
123
123
  const id = crypto.randomUUID();
124
- ctx.emit({ type: 'text-start', id });
125
- ctx.emit({ type: 'text-delta', id, delta: text });
126
- ctx.emit({ type: 'text-end', id });
127
- ctx.emit({ type: 'turn-end' });
124
+ ctx.emit({ channel: 'client', type: 'text-start', payload: { id } });
125
+ ctx.emit({ channel: 'client', type: 'text-delta', payload: { id, delta: text } });
126
+ ctx.emit({ channel: 'client', type: 'text-end', payload: { id } });
127
+ ctx.emit({ channel: 'internal', type: 'turn-end', payload: {} });
128
128
  appendAssistantMessage(run, text);
129
129
  }
130
130
  function mergeExtractionFromTurn(node, run, turn, ctx) {
@@ -7,9 +7,9 @@ export function appendSafeAssistantMessage(run, ctx, text = SAFE_DEGRADED_MESSAG
7
7
  const message = { role: 'assistant', content: text };
8
8
  run.messages = [...run.messages, message];
9
9
  const id = crypto.randomUUID();
10
- ctx.emit({ type: 'text-start', id });
11
- ctx.emit({ type: 'text-delta', id, delta: text });
12
- ctx.emit({ type: 'text-end', id });
10
+ ctx.emit({ channel: 'client', type: 'text-start', payload: { id } });
11
+ ctx.emit({ channel: 'client', type: 'text-delta', payload: { id, delta: text } });
12
+ ctx.emit({ channel: 'client', type: 'text-end', payload: { id } });
13
13
  }
14
14
  export async function degradeFlowError(flow, registry, run, driver, ctx, dispatchNode) {
15
15
  appendSafeAssistantMessage(run, ctx);
@@ -29,6 +29,10 @@ export async function degradeFlowError(flow, registry, run, driver, ctx, dispatc
29
29
  // Fall through to graceful end if the escalate node also fails.
30
30
  }
31
31
  }
32
- ctx.emit({ type: 'flow-end', flow: flow.name, reason: 'error_degraded' });
32
+ ctx.emit({
33
+ channel: 'internal',
34
+ type: 'flow-end',
35
+ payload: { flow: flow.name, reason: 'error_degraded' },
36
+ });
33
37
  return { kind: 'ended', reason: 'error_degraded' };
34
38
  }
@@ -1,3 +1,3 @@
1
1
  import type { FlowNode, FlowState } from '../types/flow.js';
2
- import type { HarnessStreamPart } from '../types/stream.js';
3
- export declare function emitInteractiveOnNodeEnter(node: FlowNode, state: FlowState, emit: (part: HarnessStreamPart) => void): void;
2
+ import type { StreamPart } from '../types/stream.js';
3
+ export declare function emitInteractiveOnNodeEnter(node: FlowNode, state: FlowState, emit: (part: StreamPart) => void): void;
@@ -35,9 +35,12 @@ export function emitInteractiveOnNodeEnter(node, state, emit) {
35
35
  return;
36
36
  }
37
37
  emit({
38
+ channel: 'internal',
38
39
  type: 'interactive',
39
- nodeId: node.id,
40
- options: node.choices,
41
- prompt: interactivePrompt(node, state),
40
+ payload: {
41
+ nodeId: node.id,
42
+ options: node.choices,
43
+ prompt: interactivePrompt(node, state),
44
+ },
42
45
  });
43
46
  }
@@ -1,6 +1,6 @@
1
1
  import type { CollectNode, FlowState } from '../types/flow.js';
2
2
  import type { Tool } from '../types/effectTool.js';
3
- import type { HarnessStreamPart } from '../types/stream.js';
3
+ import type { StreamPart } from '../types/stream.js';
4
4
  export declare function resetCollect(state: FlowState, nodeId: string): void;
5
5
  export declare function getCollectData(state: FlowState, nodeId: string): Record<string, unknown>;
6
6
  export declare function incrementCollectTurns(state: FlowState, nodeId: string): number;
@@ -20,4 +20,4 @@ export declare function mergeTurnExtraction(node: CollectNode, state: FlowState,
20
20
  name: string;
21
21
  result: unknown;
22
22
  }>): MergeTurnExtractionResult;
23
- export declare function emitExtractionTelemetry(node: CollectNode, state: FlowState, incoming: Record<string, unknown>, emit: (part: HarnessStreamPart) => void): void;
23
+ export declare function emitExtractionTelemetry(node: CollectNode, state: FlowState, incoming: Record<string, unknown>, emit: (part: StreamPart) => void): void;
@@ -129,16 +129,22 @@ export function emitExtractionTelemetry(node, state, incoming, emit) {
129
129
  }
130
130
  }
131
131
  emit({
132
+ channel: 'internal',
132
133
  type: 'custom',
133
- name: 'flow.extraction.submission',
134
- data: { node: node.id, fieldsAccepted, fieldsRejected },
134
+ payload: {
135
+ name: 'flow.extraction.submission',
136
+ data: { node: node.id, fieldsAccepted, fieldsRejected },
137
+ },
135
138
  });
136
139
  const collected = getCollectData(state, node.id);
137
140
  const missing = computeMissingFields(node, collected);
138
141
  emit({
142
+ channel: 'internal',
139
143
  type: 'custom',
140
- name: 'flow.extraction.update',
141
- data: { nodeId: node.id, collected, missing },
144
+ payload: {
145
+ name: 'flow.extraction.update',
146
+ data: { nodeId: node.id, collected, missing },
147
+ },
142
148
  });
143
149
  }
144
150
  function fieldPopulated(value) {
@@ -1,7 +1,7 @@
1
1
  import type { LanguageModel } from 'ai';
2
2
  import type { Flow, FlowNode } from '../types/flow.js';
3
3
  import type { RunState } from '../runtime/durable/types.js';
4
- import type { HarnessStreamPart } from '../types/stream.js';
4
+ import type { StreamPart } from '../types/stream.js';
5
5
  export interface ReduceTransitionInput {
6
6
  fromNodeId: string;
7
7
  toNode: FlowNode;
@@ -9,7 +9,7 @@ export interface ReduceTransitionInput {
9
9
  flow: Flow;
10
10
  model: LanguageModel;
11
11
  data?: Record<string, unknown>;
12
- emit: (part: HarnessStreamPart) => void;
12
+ emit: (part: StreamPart) => void;
13
13
  abortSignal?: AbortSignal;
14
14
  }
15
15
  export declare function reduceTransition(input: ReduceTransitionInput): Promise<void>;
@@ -8,9 +8,13 @@ function resolveNodeContext(toNode, flow) {
8
8
  }
9
9
  export async function reduceTransition(input) {
10
10
  const { fromNodeId, toNode, run, flow, model, data, emit, abortSignal } = input;
11
- emit({ type: 'node-exit', nodeName: fromNodeId });
12
- emit({ type: 'flow-transition', from: fromNodeId, to: toNode.id });
13
- emit({ type: 'node-enter', nodeName: toNode.id });
11
+ emit({ channel: 'internal', type: 'node-exit', payload: { nodeName: fromNodeId } });
12
+ emit({
13
+ channel: 'internal',
14
+ type: 'flow-transition',
15
+ payload: { from: fromNodeId, to: toNode.id },
16
+ });
17
+ emit({ channel: 'internal', type: 'node-enter', payload: { nodeName: toNode.id } });
14
18
  emitInteractiveOnNodeEnter(toNode, run.state, emit);
15
19
  await applyContextStrategy({
16
20
  strategy: resolveNodeContext(toNode, flow),
@@ -11,8 +11,7 @@ import { evaluateReplyControl } from './controlEvaluator.js';
11
11
  import { runNodeVerify, VerifyBlockedError } from './verify.js';
12
12
  import { loadRecordedSteps } from '../runtime/durable/replay.js';
13
13
  import { persistTurnUsageFromTurn } from '../runtime/turnTokenUsage.js';
14
- import { SuspendError } from '../runtime/durable/RunStore.js';
15
- import { ToolApprovalDeniedError } from '../tools/effect/errors.js';
14
+ import { isApprovalDenial, isControlFlowSignal } from '../runtime/controlFlowSignal.js';
16
15
  import { emitInteractiveOnNodeEnter } from './emitInteractive.js';
17
16
  import { appendConversationAudit } from '../audit/record.js';
18
17
  import { appendSafeAssistantMessage, degradeFlowError, findEscalateNode, } from './degrade.js';
@@ -265,8 +264,8 @@ export async function runFlow(flow, run, driver, ctx, agent) {
265
264
  if (!run.activeNode) {
266
265
  run.activeNode = node.id;
267
266
  run.activeFlow = flow.name;
268
- ctx.emit({ type: 'flow-enter', flow: flow.name });
269
- ctx.emit({ type: 'node-enter', nodeName: node.id });
267
+ ctx.emit({ channel: 'internal', type: 'flow-enter', payload: { flow: flow.name } });
268
+ ctx.emit({ channel: 'internal', type: 'node-enter', payload: { nodeName: node.id } });
270
269
  emitInteractiveOnNodeEnter(node, run.state, ctx.emit);
271
270
  }
272
271
  const edgeCounts = new Map();
@@ -277,11 +276,14 @@ export async function runFlow(flow, run, driver, ctx, agent) {
277
276
  transition = await dispatchNode(node, run, driver, ctx, agent, flow);
278
277
  }
279
278
  catch (error) {
280
- if (error instanceof SuspendError || error instanceof ToolApprovalDeniedError) {
279
+ // Neither is a malfunction, so neither may reach degradeFlowError and be reported to
280
+ // the user as "something went wrong on my side". A suspend resumes later; a denial is
281
+ // the action node author's to handle, since they chose to call the tool.
282
+ if (isControlFlowSignal(error) || isApprovalDenial(error)) {
281
283
  throw error;
282
284
  }
283
285
  const message = error instanceof Error ? error.message : String(error);
284
- ctx.emit({ type: 'error', error: message });
286
+ ctx.emit({ channel: 'client', type: 'error', payload: { error: message } });
285
287
  return degradeFlowError(flow, registry, run, driver, ctx, (n, r, d, c) => dispatchNode(n, r, d, c, agent, flow));
286
288
  }
287
289
  if (transition.kind === 'switchFlow') {
@@ -298,15 +300,27 @@ export async function runFlow(flow, run, driver, ctx, agent) {
298
300
  run.activeFlow = park.flow;
299
301
  run.activeNode = park.node;
300
302
  await ctx.runStore.putRunState(run);
301
- ctx.emit({ type: 'flow-end', flow: flow.name, reason: transition.reason });
303
+ ctx.emit({
304
+ channel: 'internal',
305
+ type: 'flow-end',
306
+ payload: { flow: flow.name, reason: transition.reason },
307
+ });
302
308
  return runFlow(parkedFlow, run, driver, ctx, agent);
303
309
  }
304
310
  }
305
- ctx.emit({ type: 'flow-end', flow: flow.name, reason: transition.reason });
311
+ ctx.emit({
312
+ channel: 'internal',
313
+ type: 'flow-end',
314
+ payload: { flow: flow.name, reason: transition.reason },
315
+ });
306
316
  return { kind: 'ended', reason: transition.reason };
307
317
  }
308
318
  if (transition.kind === 'handoff') {
309
- ctx.emit({ type: 'handoff', targetAgent: transition.to, reason: transition.reason });
319
+ ctx.emit({
320
+ channel: 'internal',
321
+ type: 'handoff',
322
+ payload: { targetAgent: transition.to, reason: transition.reason },
323
+ });
310
324
  return { kind: 'handoff', to: transition.to, reason: transition.reason };
311
325
  }
312
326
  if (transition.kind === 'escalate') {
@@ -337,14 +351,18 @@ export async function runFlow(flow, run, driver, ctx, agent) {
337
351
  }
338
352
  catch (error) {
339
353
  if (error instanceof VerifyBlockedError) {
340
- ctx.emit({ type: 'error', error: error.message });
354
+ ctx.emit({ channel: 'client', type: 'error', payload: { error: error.message } });
341
355
  return { kind: 'awaitingUser' };
342
356
  }
343
357
  throw error;
344
358
  }
345
359
  const oscillation = bumpOscillation(edgeCounts, node.id, target.id);
346
360
  if (oscillation > maxOscillations) {
347
- ctx.emit({ type: 'error', error: `Flow oscillation blocked: ${node.id} -> ${target.id}` });
361
+ ctx.emit({
362
+ channel: 'client',
363
+ type: 'error',
364
+ payload: { error: `Flow oscillation blocked: ${node.id} -> ${target.id}` },
365
+ });
348
366
  const escalateNode = findEscalateNode(registry);
349
367
  if (escalateNode) {
350
368
  appendSafeAssistantMessage(run, ctx);
@@ -363,7 +381,11 @@ export async function runFlow(flow, run, driver, ctx, agent) {
363
381
  continue;
364
382
  }
365
383
  appendSafeAssistantMessage(run, ctx);
366
- ctx.emit({ type: 'flow-end', flow: flow.name, reason: 'error_degraded' });
384
+ ctx.emit({
385
+ channel: 'internal',
386
+ type: 'flow-end',
387
+ payload: { flow: flow.name, reason: 'error_degraded' },
388
+ });
367
389
  return { kind: 'ended', reason: 'error_degraded' };
368
390
  }
369
391
  await reduceTransition({
@@ -1,7 +1,7 @@
1
1
  import type { Session } from '../types/index.js';
2
2
  /**
3
3
  * Manages active agent resolution, handoff recording, and agent state.
4
- * Shared by Runtime and VoiceEngine.
4
+ * Used by Runtime.
5
5
  */
6
6
  export interface AgentStateController {
7
7
  /** Resolve the active agent ID, falling back to the provided default. */
@@ -1,7 +1,7 @@
1
1
  import type { Session, ToolCallRecord } from '../types/index.js';
2
2
  /**
3
3
  * Minimal tool shape required by the executor.
4
- * Compatible with AI SDK `Tool`, VoiceToolDef, and plain `{ execute }` objects.
4
+ * Compatible with AI SDK `Tool` and plain `{ execute }` objects.
5
5
  */
6
6
  export interface ExecutableTool {
7
7
  execute: (args: unknown, options?: unknown) => Promise<unknown>;
@@ -9,7 +9,7 @@ export interface ExecutableTool {
9
9
  }
10
10
  /**
11
11
  * Encapsulates tool execution with enforcement, idempotency, and context enrichment.
12
- * Shared by Runtime (text path) and VoiceEngine (audio path).
12
+ * Used by Runtime.
13
13
  */
14
14
  export interface ToolExecutor {
15
15
  /**
@@ -1,14 +1,3 @@
1
- export type { AgentDefinition } from './AgentDefinition.js';
2
1
  export type { ToolExecutor, ExecutableTool } from './ToolExecutor.js';
3
- export type { ConversationState } from './ConversationState.js';
4
- export type { ConversationEventLog, ConversationEvent } from './ConversationEventLog.js';
5
2
  export type { AgentStateController } from './AgentStateController.js';
6
- export { DefaultToolExecutor, ToolTimeoutError } from './DefaultToolExecutor.js';
7
- export type { DefaultToolExecutorConfig } from './DefaultToolExecutor.js';
8
- export { DefaultConversationState } from './DefaultConversationState.js';
9
- export type { DefaultConversationStateConfig } from './DefaultConversationState.js';
10
- export { DefaultConversationEventLog } from './DefaultConversationEventLog.js';
11
- export type { DefaultConversationEventLogConfig } from './DefaultConversationEventLog.js';
12
3
  export { DefaultAgentStateController } from './DefaultAgentStateController.js';
13
- export { createFoundation } from './createFoundation.js';
14
- export type { Foundation, FoundationConfig } from './createFoundation.js';
@@ -1,7 +1,2 @@
1
1
  // Default implementations
2
- export { DefaultToolExecutor, ToolTimeoutError } from './DefaultToolExecutor.js';
3
- export { DefaultConversationState } from './DefaultConversationState.js';
4
- export { DefaultConversationEventLog } from './DefaultConversationEventLog.js';
5
2
  export { DefaultAgentStateController } from './DefaultAgentStateController.js';
6
- // Factory
7
- export { createFoundation } from './createFoundation.js';