@kuralle-agents/core 0.13.0 → 0.15.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.
- package/README.md +4 -4
- package/dist/ai-sdk/uiMessageStream.d.ts +2 -2
- package/dist/ai-sdk/uiMessageStream.js +42 -29
- package/dist/capabilities/LivePromptAssembler.d.ts +1 -1
- package/dist/capabilities/LivePromptAssembler.js +1 -1
- package/dist/capabilities/index.d.ts +1 -1
- package/dist/eval/EvalRunner.js +4 -4
- package/dist/eval/simulation.js +4 -3
- package/dist/events/TurnHandle.d.ts +3 -3
- package/dist/flow/collectUntilComplete.js +30 -7
- package/dist/flow/degrade.js +8 -4
- package/dist/flow/emitInteractive.d.ts +2 -2
- package/dist/flow/emitInteractive.js +6 -3
- package/dist/flow/extraction.d.ts +21 -2
- package/dist/flow/extraction.js +62 -7
- package/dist/flow/reduceTransition.d.ts +2 -2
- package/dist/flow/reduceTransition.js +7 -3
- package/dist/flow/runFlow.d.ts +11 -0
- package/dist/flow/runFlow.js +68 -12
- package/dist/foundation/AgentStateController.d.ts +1 -1
- package/dist/foundation/ToolExecutor.d.ts +2 -2
- package/dist/foundation/index.d.ts +0 -11
- package/dist/foundation/index.js +0 -5
- package/dist/index.d.ts +9 -14
- package/dist/index.js +5 -6
- package/dist/outcomes/streamPart.d.ts +2 -2
- package/dist/outcomes/streamPart.js +2 -3
- package/dist/prompts/PromptBuilder.d.ts +3 -3
- package/dist/prompts/PromptBuilder.js +6 -6
- package/dist/prompts/index.d.ts +1 -1
- package/dist/prompts/types.d.ts +7 -2
- package/dist/runtime/InMemoryRetrievalCache.d.ts +1 -1
- package/dist/runtime/KnowledgeProvider.d.ts +3 -2
- package/dist/runtime/KnowledgeProvider.js +54 -30
- package/dist/runtime/Runtime.d.ts +9 -1
- package/dist/runtime/Runtime.js +123 -30
- package/dist/runtime/TraceRecorder.d.ts +22 -4
- package/dist/runtime/TraceRecorder.js +65 -29
- package/dist/runtime/buildAgentToolSurface.js +1 -1
- package/dist/runtime/channels/TextDriver.js +12 -9
- package/dist/runtime/channels/executeModelTool.d.ts +6 -0
- package/dist/runtime/channels/executeModelTool.js +70 -10
- package/dist/runtime/channels/extractionTurn.js +1 -1
- package/dist/runtime/channels/index.d.ts +0 -3
- package/dist/runtime/channels/index.js +0 -6
- package/dist/runtime/channels/streaming/hostControlSpeak.js +5 -1
- package/dist/runtime/channels/streaming/speakGated.js +33 -12
- package/dist/runtime/citations/index.d.ts +3 -3
- package/dist/runtime/closeRun.js +3 -2
- package/dist/runtime/controlFlowSignal.d.ts +24 -0
- package/dist/runtime/controlFlowSignal.js +29 -0
- package/dist/runtime/ctx.d.ts +3 -4
- package/dist/runtime/ctx.js +39 -5
- package/dist/runtime/durable/RunStore.js +1 -1
- package/dist/runtime/grounding/gather.d.ts +1 -1
- package/dist/runtime/grounding/inMemoryKnowledge.d.ts +1 -1
- package/dist/runtime/grounding/knowledge.d.ts +1 -1
- package/dist/runtime/hostLoop.js +39 -10
- package/dist/runtime/index.d.ts +1 -2
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/outcomeMarking.d.ts +2 -2
- package/dist/runtime/outcomeMarking.js +2 -1
- package/dist/runtime/policies/agentTurn.d.ts +1 -1
- package/dist/runtime/runHookSafely.d.ts +3 -0
- package/dist/runtime/runHookSafely.js +19 -0
- package/dist/scheduler/index.d.ts +2 -2
- package/dist/scheduler/index.js +1 -1
- package/dist/services/TracingService.js +2 -3
- package/dist/skills/collectSkills.d.ts +11 -3
- package/dist/skills/collectSkills.js +51 -8
- package/dist/skills/compositeSkillStore.d.ts +26 -0
- package/dist/skills/compositeSkillStore.js +54 -0
- package/dist/skills/defineSkill.d.ts +21 -0
- package/dist/skills/defineSkill.js +26 -0
- package/dist/skills/fsSkillStore.d.ts +3 -0
- package/dist/skills/fsSkillStore.js +79 -0
- package/dist/skills/index.d.ts +4 -0
- package/dist/skills/index.js +4 -0
- package/dist/skills/parseSkillFrontmatter.d.ts +14 -0
- package/dist/skills/parseSkillFrontmatter.js +183 -0
- package/dist/skills/wireAgentSkills.d.ts +2 -1
- package/dist/skills/wireAgentSkills.js +2 -2
- package/dist/testing/mocks.d.ts +3 -3
- package/dist/testing/mocks.js +7 -3
- package/dist/tools/controlResults.d.ts +14 -0
- package/dist/tools/controlResults.js +15 -0
- package/dist/tools/effect/ToolExecutor.d.ts +6 -0
- package/dist/tools/effect/ToolExecutor.js +71 -35
- package/dist/tools/effect/defineTool.d.ts +9 -0
- package/dist/tools/effect/defineTool.js +1 -0
- package/dist/tools/effect/errors.d.ts +2 -5
- package/dist/tools/effect/errors.js +2 -5
- package/dist/tools/fs/createFsTool.js +2 -1
- package/dist/tools/index.d.ts +0 -1
- package/dist/tools/index.js +0 -1
- package/dist/types/effectTool.d.ts +2 -0
- package/dist/types/flow.d.ts +1 -1
- package/dist/types/guardrails.d.ts +5 -0
- package/dist/types/hooks.d.ts +2 -2
- package/dist/types/index.d.ts +25 -16
- package/dist/types/index.js +8 -13
- package/dist/types/knowledge.d.ts +213 -0
- package/dist/types/processors.d.ts +0 -58
- package/dist/types/run-context.d.ts +3 -7
- package/dist/types/runtime.d.ts +3 -74
- package/dist/types/session.d.ts +1 -1
- package/dist/types/skills.d.ts +15 -1
- package/dist/types/stream.d.ts +133 -71
- package/dist/types/stream.js +34 -1
- package/dist/types/trace.d.ts +2 -0
- package/guides/AGENTS.md +3 -3
- package/guides/FLOWS.md +1 -1
- package/guides/GETTING_STARTED.md +1 -1
- package/guides/RUNTIME.md +20 -15
- package/guides/TOOLS.md +1 -1
- package/package.json +3 -13
- package/dist/foundation/AgentDefinition.d.ts +0 -17
- package/dist/foundation/ConversationEventLog.d.ts +0 -71
- package/dist/foundation/ConversationEventLog.js +0 -1
- package/dist/foundation/ConversationState.d.ts +0 -33
- package/dist/foundation/ConversationState.js +0 -1
- package/dist/foundation/DefaultConversationEventLog.d.ts +0 -27
- package/dist/foundation/DefaultConversationEventLog.js +0 -205
- package/dist/foundation/DefaultConversationState.d.ts +0 -36
- package/dist/foundation/DefaultConversationState.js +0 -103
- package/dist/foundation/DefaultToolExecutor.d.ts +0 -57
- package/dist/foundation/DefaultToolExecutor.js +0 -132
- package/dist/foundation/createFoundation.d.ts +0 -32
- package/dist/foundation/createFoundation.js +0 -33
- package/dist/hooks/HookRunner.d.ts +0 -37
- package/dist/hooks/HookRunner.js +0 -111
- package/dist/hooks/builtin/logging.d.ts +0 -5
- package/dist/hooks/builtin/logging.js +0 -98
- package/dist/hooks/builtin/metrics.d.ts +0 -7
- package/dist/hooks/builtin/metrics.js +0 -60
- package/dist/hooks/builtin/observability.d.ts +0 -20
- package/dist/hooks/builtin/observability.js +0 -572
- package/dist/hooks/helpers.d.ts +0 -22
- package/dist/hooks/helpers.js +0 -183
- package/dist/hooks/index.d.ts +0 -8
- package/dist/hooks/index.js +0 -6
- package/dist/realtime/RealtimeAudioClient.d.ts +0 -166
- package/dist/realtime/RealtimeAudioClient.js +0 -14
- package/dist/realtime/RealtimeSessionHandle.d.ts +0 -6
- package/dist/realtime/RealtimeSessionHandle.js +0 -1
- package/dist/realtime/index.d.ts +0 -2
- package/dist/realtime/index.js +0 -1
- package/dist/runtime/channels/VoiceDriver.d.ts +0 -31
- package/dist/runtime/channels/VoiceDriver.js +0 -321
- package/dist/runtime/channels/voiceTools.d.ts +0 -9
- package/dist/runtime/channels/voiceTools.js +0 -51
- package/dist/tools/errorHandling.d.ts +0 -35
- package/dist/tools/errorHandling.js +0 -145
- package/dist/types/voice.d.ts +0 -517
- package/dist/types/voice.js +0 -14
- package/dist/utils/isRecord.d.ts +0 -1
- package/dist/utils/isRecord.js +0 -3
- package/dist/utils/messageNormalization.d.ts +0 -2
- package/dist/utils/messageNormalization.js +0 -137
- /package/dist/{foundation/AgentDefinition.js → types/knowledge.js} +0 -0
package/dist/runtime/Runtime.js
CHANGED
|
@@ -34,6 +34,13 @@ import { TraceRecorder } from './TraceRecorder.js';
|
|
|
34
34
|
import { MemoryTraceStore } from '../tracing/MemoryTraceStore.js';
|
|
35
35
|
import { mutateSessionWithRetry } from '../session/utils.js';
|
|
36
36
|
import { isTraceStore } from '../tracing/TraceStore.js';
|
|
37
|
+
import { runHookSafely } from './runHookSafely.js';
|
|
38
|
+
/**
|
|
39
|
+
* What the user is told when the run hands off to a human and the app has not configured an
|
|
40
|
+
* escalation handler to say something better. Silence is the wrong default: an escalation
|
|
41
|
+
* that emits no text is indistinguishable from the agent having crashed.
|
|
42
|
+
*/
|
|
43
|
+
export const HANDOFF_TO_HUMAN_MESSAGE = "I'm bringing a colleague into this — they'll pick it up from here.";
|
|
37
44
|
export class Runtime {
|
|
38
45
|
config;
|
|
39
46
|
agentsById;
|
|
@@ -72,6 +79,7 @@ export class Runtime {
|
|
|
72
79
|
const recorder = this.shouldTrace(sessionId, opts.input)
|
|
73
80
|
? new TraceRecorder({
|
|
74
81
|
sessionId,
|
|
82
|
+
agentId: opts.agentId ?? this.config.defaultAgentId,
|
|
75
83
|
input: opts.input,
|
|
76
84
|
onSpan: (span) => this.writeSpan(span),
|
|
77
85
|
})
|
|
@@ -84,12 +92,19 @@ export class Runtime {
|
|
|
84
92
|
}
|
|
85
93
|
const execute = async () => {
|
|
86
94
|
let runCtx;
|
|
95
|
+
// Whether the user has been told anything at all this turn. A terminal handoff with
|
|
96
|
+
// no escalation handler configured used to emit zero text — seven consecutive turns
|
|
97
|
+
// were observed producing empty output while silently firing transfer_to_agent, which
|
|
98
|
+
// is indistinguishable from an outage.
|
|
99
|
+
let sawUserText = false;
|
|
87
100
|
const emit = (part) => {
|
|
101
|
+
if (part.type === 'text-delta' && part.payload.delta)
|
|
102
|
+
sawUserText = true;
|
|
88
103
|
recorder?.record(part);
|
|
89
104
|
if (part.type === 'done')
|
|
90
105
|
this.flushTraceSinks();
|
|
91
106
|
bus.emit(part);
|
|
92
|
-
void this.hooks?.onStreamPart?.(runCtx, part);
|
|
107
|
+
void runHookSafely('onStreamPart', () => this.hooks?.onStreamPart?.(runCtx, part));
|
|
93
108
|
};
|
|
94
109
|
const opened = await openRun(this.agentsById, {
|
|
95
110
|
sessionId,
|
|
@@ -106,6 +121,7 @@ export class Runtime {
|
|
|
106
121
|
defaultAgentId: this.config.defaultAgentId,
|
|
107
122
|
sessionStore: this.sessionStore,
|
|
108
123
|
});
|
|
124
|
+
recorder?.setInitiatingAgent(opened.agent.id);
|
|
109
125
|
const policies = resolveAgentPolicies(opened.agent);
|
|
110
126
|
const knowledgeProvider = this.config.knowledge
|
|
111
127
|
? buildKnowledgeProvider(this.config.knowledge)
|
|
@@ -121,9 +137,16 @@ export class Runtime {
|
|
|
121
137
|
agentId: opened.agent.id,
|
|
122
138
|
onInterim: (message) => {
|
|
123
139
|
const id = crypto.randomUUID();
|
|
124
|
-
emit({ type: 'text-start', id });
|
|
125
|
-
emit({ type: 'text-delta', id, delta: message });
|
|
126
|
-
emit({ type: 'text-end', id });
|
|
140
|
+
emit({ channel: 'client', type: 'text-start', payload: { id } });
|
|
141
|
+
emit({ channel: 'client', type: 'text-delta', payload: { id, delta: message } });
|
|
142
|
+
emit({ channel: 'client', type: 'text-end', payload: { id } });
|
|
143
|
+
},
|
|
144
|
+
onChunk: (chunk, toolName, toolCallId) => {
|
|
145
|
+
emit({
|
|
146
|
+
channel: 'internal',
|
|
147
|
+
type: 'tool-result',
|
|
148
|
+
payload: { toolName, result: chunk, toolCallId, preliminary: true },
|
|
149
|
+
});
|
|
127
150
|
},
|
|
128
151
|
});
|
|
129
152
|
const steps = await loadRecordedSteps(opened.runStore, opened.runState.runId);
|
|
@@ -170,9 +193,9 @@ export class Runtime {
|
|
|
170
193
|
runCtx.skillPrompt = openingSurface.skillPrompt;
|
|
171
194
|
runCtx.workingMemoryPrompt = appendGoalsPrompt(openingSurface.workingMemoryPrompt, opened.session.workingMemory);
|
|
172
195
|
runCtx.workingMemoryTools = openingSurface.workingMemoryTools;
|
|
173
|
-
await this.hooks?.onStart?.(runCtx);
|
|
196
|
+
await runHookSafely('onStart', () => this.hooks?.onStart?.(runCtx));
|
|
174
197
|
if (opts.wake) {
|
|
175
|
-
emit({ type: 'wake', reason: opts.wake.reason });
|
|
198
|
+
emit({ channel: 'internal', type: 'wake', payload: { reason: opts.wake.reason } });
|
|
176
199
|
}
|
|
177
200
|
const driver = opts.driver ?? new TextDriver();
|
|
178
201
|
let activeAgent = opened.agent;
|
|
@@ -202,8 +225,38 @@ export class Runtime {
|
|
|
202
225
|
}
|
|
203
226
|
if (loopResult.kind === 'handoff') {
|
|
204
227
|
if (this.terminalHandoffTargets.has(loopResult.to)) {
|
|
205
|
-
emit
|
|
228
|
+
// This emit is load-bearing for TERMINAL targets specifically. A terminal
|
|
229
|
+
// handoff breaks out of the loop here, before hostLoop's own emit and before
|
|
230
|
+
// handoffHistory is appended — so this is the only handoff part a client ever
|
|
231
|
+
// sees for an escalation. Removing it as "redundant" silently produced zero
|
|
232
|
+
// handoff parts for every escalation; escalation.test.ts catches it.
|
|
233
|
+
//
|
|
234
|
+
// The self-edge seen live (handoff human->human, two spans on one turn) is a
|
|
235
|
+
// real but SEPARATE defect on the non-terminal path — fix it there, not by
|
|
236
|
+
// deleting this.
|
|
237
|
+
emit({
|
|
238
|
+
channel: 'internal',
|
|
239
|
+
type: 'handoff',
|
|
240
|
+
payload: { targetAgent: loopResult.to, reason: loopResult.reason },
|
|
241
|
+
});
|
|
206
242
|
runCtx.runState.status = 'paused';
|
|
243
|
+
// Never hand off in silence. dispatchEscalation returns immediately when no
|
|
244
|
+
// escalation handler is configured, so without this the turn ends with no
|
|
245
|
+
// assistant text at all and the user cannot tell an escalation from a crash.
|
|
246
|
+
if (!sawUserText) {
|
|
247
|
+
const handoffId = crypto.randomUUID();
|
|
248
|
+
emit({ channel: 'client', type: 'text-start', payload: { id: handoffId } });
|
|
249
|
+
emit({
|
|
250
|
+
channel: 'client',
|
|
251
|
+
type: 'text-delta',
|
|
252
|
+
payload: { id: handoffId, delta: HANDOFF_TO_HUMAN_MESSAGE },
|
|
253
|
+
});
|
|
254
|
+
emit({ channel: 'client', type: 'text-end', payload: { id: handoffId } });
|
|
255
|
+
runCtx.runState.messages = [
|
|
256
|
+
...runCtx.runState.messages,
|
|
257
|
+
{ role: 'assistant', content: HANDOFF_TO_HUMAN_MESSAGE },
|
|
258
|
+
];
|
|
259
|
+
}
|
|
207
260
|
await runCtx.runStore.putRunState(runCtx.runState);
|
|
208
261
|
await this.dispatchEscalation(runCtx, activeAgent, { reason: loopResult.reason ?? 'handoff_to_human', category: loopResult.category }, emit, { setLatch: false });
|
|
209
262
|
break;
|
|
@@ -285,9 +338,20 @@ export class Runtime {
|
|
|
285
338
|
agentId: target.id,
|
|
286
339
|
onInterim: (message) => {
|
|
287
340
|
const id = crypto.randomUUID();
|
|
288
|
-
emit({ type: 'text-start', id });
|
|
289
|
-
emit({
|
|
290
|
-
|
|
341
|
+
emit({ channel: 'client', type: 'text-start', payload: { id } });
|
|
342
|
+
emit({
|
|
343
|
+
channel: 'client',
|
|
344
|
+
type: 'text-delta',
|
|
345
|
+
payload: { id, delta: message },
|
|
346
|
+
});
|
|
347
|
+
emit({ channel: 'client', type: 'text-end', payload: { id } });
|
|
348
|
+
},
|
|
349
|
+
onChunk: (chunk, toolName, toolCallId) => {
|
|
350
|
+
emit({
|
|
351
|
+
channel: 'internal',
|
|
352
|
+
type: 'tool-result',
|
|
353
|
+
payload: { toolName, result: chunk, toolCallId, preliminary: true },
|
|
354
|
+
});
|
|
291
355
|
},
|
|
292
356
|
});
|
|
293
357
|
await runCtx.runStore.putRunState(runCtx.runState);
|
|
@@ -314,14 +378,18 @@ export class Runtime {
|
|
|
314
378
|
await this.applyCompaction(runCtx, activeAgent, emit, false);
|
|
315
379
|
}
|
|
316
380
|
catch (error) {
|
|
317
|
-
await this.hooks?.onError?.(runCtx, error);
|
|
381
|
+
await runHookSafely('onError', () => this.hooks?.onError?.(runCtx, error));
|
|
318
382
|
if (isDegradableRuntimeError(error)) {
|
|
319
383
|
const message = error instanceof Error ? error.message : String(error);
|
|
320
|
-
emit({ type: 'error', error: message });
|
|
384
|
+
emit({ channel: 'client', type: 'error', payload: { error: message } });
|
|
321
385
|
const degradedId = crypto.randomUUID();
|
|
322
|
-
emit({ type: 'text-start', id: degradedId });
|
|
323
|
-
emit({
|
|
324
|
-
|
|
386
|
+
emit({ channel: 'client', type: 'text-start', payload: { id: degradedId } });
|
|
387
|
+
emit({
|
|
388
|
+
channel: 'client',
|
|
389
|
+
type: 'text-delta',
|
|
390
|
+
payload: { id: degradedId, delta: SAFE_DEGRADED_MESSAGE },
|
|
391
|
+
});
|
|
392
|
+
emit({ channel: 'client', type: 'text-end', payload: { id: degradedId } });
|
|
325
393
|
runCtx.runState.messages = [
|
|
326
394
|
...runCtx.runState.messages,
|
|
327
395
|
{ role: 'assistant', content: SAFE_DEGRADED_MESSAGE },
|
|
@@ -354,11 +422,14 @@ export class Runtime {
|
|
|
354
422
|
}
|
|
355
423
|
},
|
|
356
424
|
});
|
|
357
|
-
await this.hooks?.onEnd?.(runCtx);
|
|
425
|
+
await runHookSafely('onEnd', () => this.hooks?.onEnd?.(runCtx));
|
|
358
426
|
emit({
|
|
427
|
+
channel: 'client',
|
|
359
428
|
type: 'done',
|
|
360
|
-
|
|
361
|
-
|
|
429
|
+
payload: {
|
|
430
|
+
sessionId: opened.session.id,
|
|
431
|
+
usage: computeTurnTraceUsage(usageBaseline, runCtx.runState.state),
|
|
432
|
+
},
|
|
362
433
|
});
|
|
363
434
|
}
|
|
364
435
|
return { text: collectAssistantText(runCtx.runState.messages), toolResults: [] };
|
|
@@ -378,8 +449,13 @@ export class Runtime {
|
|
|
378
449
|
run: gated,
|
|
379
450
|
});
|
|
380
451
|
}
|
|
381
|
-
runOnce(opts) {
|
|
382
|
-
|
|
452
|
+
async runOnce(opts) {
|
|
453
|
+
const existing = opts.sessionId ? await this.sessionStore.get(opts.sessionId) : null;
|
|
454
|
+
// Caller's explicit agentId wins, matching run() (`opts.agentId ?? defaultAgentId`).
|
|
455
|
+
// Persisted state is the fallback, not an override — otherwise the two public
|
|
456
|
+
// entry points disagree about which agent handles the same turn.
|
|
457
|
+
const agentId = opts.agentId ?? existing?.activeAgentId ?? existing?.currentAgent ?? this.config.defaultAgentId;
|
|
458
|
+
return recordRunOnce(this, { ...opts, agentId });
|
|
383
459
|
}
|
|
384
460
|
stream(opts) {
|
|
385
461
|
return this.run(opts);
|
|
@@ -398,6 +474,10 @@ export class Runtime {
|
|
|
398
474
|
getTraceStore() {
|
|
399
475
|
return this.traceStore;
|
|
400
476
|
}
|
|
477
|
+
/** The agent used when neither the caller nor persisted state names one. */
|
|
478
|
+
getDefaultAgentId() {
|
|
479
|
+
return this.config.defaultAgentId;
|
|
480
|
+
}
|
|
401
481
|
getSessionStore() {
|
|
402
482
|
return this.sessionStore;
|
|
403
483
|
}
|
|
@@ -506,7 +586,11 @@ export class Runtime {
|
|
|
506
586
|
});
|
|
507
587
|
if (!result.compacted) {
|
|
508
588
|
if (force) {
|
|
509
|
-
emit({
|
|
589
|
+
emit({
|
|
590
|
+
channel: 'internal',
|
|
591
|
+
type: 'compaction-skipped',
|
|
592
|
+
payload: { reason: result.reason },
|
|
593
|
+
});
|
|
510
594
|
}
|
|
511
595
|
return false;
|
|
512
596
|
}
|
|
@@ -518,10 +602,13 @@ export class Runtime {
|
|
|
518
602
|
});
|
|
519
603
|
runCtx.session.messages = [...result.messages];
|
|
520
604
|
emit({
|
|
605
|
+
channel: 'internal',
|
|
521
606
|
type: 'context-compacted',
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
607
|
+
payload: {
|
|
608
|
+
beforeTokens: result.beforeTokens,
|
|
609
|
+
afterTokens: result.afterTokens,
|
|
610
|
+
summarizedCount: result.summarizedCount,
|
|
611
|
+
},
|
|
525
612
|
});
|
|
526
613
|
return true;
|
|
527
614
|
}
|
|
@@ -536,9 +623,12 @@ export class Runtime {
|
|
|
536
623
|
runCtx.runState.messages = runCtx.session.messages;
|
|
537
624
|
const compacted = await this.applyCompaction(runCtx, agent, emit, true);
|
|
538
625
|
emit({
|
|
626
|
+
channel: 'internal',
|
|
539
627
|
type: 'context-overflow-recovered',
|
|
540
|
-
|
|
541
|
-
|
|
628
|
+
payload: {
|
|
629
|
+
strippedCount: recovery.strippedCount,
|
|
630
|
+
compacted,
|
|
631
|
+
},
|
|
542
632
|
});
|
|
543
633
|
}
|
|
544
634
|
async getConversationLength(sessionId) {
|
|
@@ -592,11 +682,14 @@ export class Runtime {
|
|
|
592
682
|
});
|
|
593
683
|
runCtx.session.metadata = latestSession.metadata;
|
|
594
684
|
emit({
|
|
685
|
+
channel: 'internal',
|
|
595
686
|
type: 'escalation',
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
687
|
+
payload: {
|
|
688
|
+
reason: info.reason,
|
|
689
|
+
category: info.category,
|
|
690
|
+
outcome: outcome.status,
|
|
691
|
+
summary: request.summary,
|
|
692
|
+
},
|
|
600
693
|
});
|
|
601
694
|
}
|
|
602
695
|
/**
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { TurnResult } from '../types/channel.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { StreamPart, TurnHandle } from '../types/stream.js';
|
|
3
3
|
import type { AgentSpan, AgentTrace } from '../types/trace.js';
|
|
4
4
|
import type { RunOptions } from './Runtime.js';
|
|
5
5
|
export interface TraceRecorderOptions {
|
|
6
6
|
sessionId?: string;
|
|
7
|
+
agentId?: string;
|
|
7
8
|
input?: unknown;
|
|
8
9
|
onSpan?: (span: AgentSpan) => void;
|
|
9
10
|
}
|
|
@@ -16,8 +17,10 @@ export declare class TraceRecorder {
|
|
|
16
17
|
private readonly toolCallIds;
|
|
17
18
|
private readonly emitted;
|
|
18
19
|
private readonly onSpan?;
|
|
20
|
+
private currentAgentId?;
|
|
19
21
|
constructor(options?: TraceRecorderOptions);
|
|
20
|
-
|
|
22
|
+
setInitiatingAgent(agentId: string): void;
|
|
23
|
+
record(part: StreamPart): void;
|
|
21
24
|
finish(result: TurnResult): AgentTrace;
|
|
22
25
|
private openSpan;
|
|
23
26
|
private takeToolSpan;
|
|
@@ -28,6 +31,21 @@ export declare class TraceRecorder {
|
|
|
28
31
|
private close;
|
|
29
32
|
private emitSpan;
|
|
30
33
|
}
|
|
31
|
-
|
|
34
|
+
/**
|
|
35
|
+
* What the standalone {@link runOnce} needs from a runtime. `run` is required;
|
|
36
|
+
* the optional accessors let it resolve the same agent `Runtime.runOnce` would.
|
|
37
|
+
*
|
|
38
|
+
* It must NOT call `runtime.runOnce` — that method delegates here, so delegating
|
|
39
|
+
* back is infinite recursion.
|
|
40
|
+
*/
|
|
41
|
+
export interface RunOnceRuntime {
|
|
32
42
|
run(opts: RunOptions): TurnHandle;
|
|
33
|
-
|
|
43
|
+
getSessionStore?(): {
|
|
44
|
+
get(id: string): Promise<{
|
|
45
|
+
activeAgentId?: string;
|
|
46
|
+
currentAgent?: string;
|
|
47
|
+
} | null>;
|
|
48
|
+
};
|
|
49
|
+
getDefaultAgentId?(): string;
|
|
50
|
+
}
|
|
51
|
+
export declare function runOnce(runtime: RunOnceRuntime, opts: RunOptions): Promise<AgentTrace>;
|
|
@@ -7,11 +7,13 @@ export class TraceRecorder {
|
|
|
7
7
|
toolCallIds = new Map();
|
|
8
8
|
emitted = new Set();
|
|
9
9
|
onSpan;
|
|
10
|
+
currentAgentId;
|
|
10
11
|
constructor(options = {}) {
|
|
11
12
|
const startedAt = Date.now();
|
|
12
13
|
const sessionId = options.sessionId ?? '';
|
|
13
14
|
const traceId = crypto.randomUUID().replaceAll('-', '');
|
|
14
15
|
this.onSpan = options.onSpan;
|
|
16
|
+
this.currentAgentId = options.agentId;
|
|
15
17
|
this.root = {
|
|
16
18
|
traceId,
|
|
17
19
|
spanId: newSpanId(),
|
|
@@ -21,6 +23,7 @@ export class TraceRecorder {
|
|
|
21
23
|
status: 'ok',
|
|
22
24
|
attributes: {
|
|
23
25
|
sessionId,
|
|
26
|
+
...(options.agentId ? { agentId: options.agentId } : {}),
|
|
24
27
|
...(options.input !== undefined ? { input: toJsonValue(options.input) } : {}),
|
|
25
28
|
},
|
|
26
29
|
};
|
|
@@ -35,22 +38,26 @@ export class TraceRecorder {
|
|
|
35
38
|
startedAt,
|
|
36
39
|
};
|
|
37
40
|
}
|
|
41
|
+
setInitiatingAgent(agentId) {
|
|
42
|
+
this.currentAgentId = agentId;
|
|
43
|
+
this.root.attributes.agentId = agentId;
|
|
44
|
+
}
|
|
38
45
|
record(part) {
|
|
39
46
|
try {
|
|
40
47
|
const at = Date.now();
|
|
41
48
|
switch (part.type) {
|
|
42
49
|
case 'text-delta':
|
|
43
|
-
this.trace.answer += part.delta;
|
|
50
|
+
this.trace.answer += part.payload.delta;
|
|
44
51
|
break;
|
|
45
52
|
case 'flow-enter':
|
|
46
53
|
this.closeNode(at);
|
|
47
54
|
this.closeFlow(at);
|
|
48
55
|
this.currentFlow = this.openSpan({
|
|
49
|
-
name: `flow:${part.flow}`,
|
|
56
|
+
name: `flow:${part.payload.flow}`,
|
|
50
57
|
kind: 'flow',
|
|
51
58
|
parentSpanId: this.root.spanId,
|
|
52
59
|
at,
|
|
53
|
-
attributes: { activeFlow: part.flow },
|
|
60
|
+
attributes: { activeFlow: part.payload.flow },
|
|
54
61
|
});
|
|
55
62
|
break;
|
|
56
63
|
case 'flow-end':
|
|
@@ -60,13 +67,13 @@ export class TraceRecorder {
|
|
|
60
67
|
case 'node-enter':
|
|
61
68
|
this.closeNode(at);
|
|
62
69
|
this.currentNode = this.openSpan({
|
|
63
|
-
name: `node:${part.nodeName}`,
|
|
70
|
+
name: `node:${part.payload.nodeName}`,
|
|
64
71
|
kind: 'node',
|
|
65
72
|
parentSpanId: this.currentFlow?.spanId ?? this.root.spanId,
|
|
66
73
|
at,
|
|
67
74
|
attributes: {
|
|
68
75
|
...(this.currentFlow ? { activeFlow: activeFlowName(this.currentFlow) } : {}),
|
|
69
|
-
nodeId: part.nodeName,
|
|
76
|
+
nodeId: part.payload.nodeName,
|
|
70
77
|
},
|
|
71
78
|
});
|
|
72
79
|
break;
|
|
@@ -75,37 +82,40 @@ export class TraceRecorder {
|
|
|
75
82
|
break;
|
|
76
83
|
case 'tool-call': {
|
|
77
84
|
const span = this.openSpan({
|
|
78
|
-
name: `tool:${part.toolName}`,
|
|
85
|
+
name: `tool:${part.payload.toolName}`,
|
|
79
86
|
kind: 'tool',
|
|
80
87
|
parentSpanId: this.currentNode?.spanId ?? this.root.spanId,
|
|
81
88
|
at,
|
|
82
89
|
attributes: {
|
|
83
90
|
...(this.currentFlow ? { activeFlow: activeFlowName(this.currentFlow) } : {}),
|
|
84
91
|
...(this.currentNode ? { nodeId: nodeName(this.currentNode) } : {}),
|
|
85
|
-
toolName: part.toolName,
|
|
86
|
-
input: toJsonValue(part.args),
|
|
92
|
+
toolName: part.payload.toolName,
|
|
93
|
+
input: toJsonValue(part.payload.args),
|
|
87
94
|
},
|
|
88
95
|
});
|
|
89
|
-
this.toolCallIds.set(span, part.toolCallId);
|
|
96
|
+
this.toolCallIds.set(span, part.payload.toolCallId);
|
|
90
97
|
this.openTools.push(span);
|
|
91
98
|
this.trace.usedTool = true;
|
|
92
|
-
this.trace.toolCalls.push({
|
|
99
|
+
this.trace.toolCalls.push({
|
|
100
|
+
name: part.payload.toolName,
|
|
101
|
+
args: toJsonValue(part.payload.args),
|
|
102
|
+
});
|
|
93
103
|
break;
|
|
94
104
|
}
|
|
95
105
|
case 'tool-result': {
|
|
96
106
|
this.trace.usedTool = true;
|
|
97
|
-
const result = toJsonValue(part.result);
|
|
98
|
-
this.trace.toolResults.push({ name: part.toolName, result });
|
|
99
|
-
const span = this.takeToolSpan(part.toolName, part.toolCallId) ??
|
|
107
|
+
const result = toJsonValue(part.payload.result);
|
|
108
|
+
this.trace.toolResults.push({ name: part.payload.toolName, result });
|
|
109
|
+
const span = this.takeToolSpan(part.payload.toolName, part.payload.toolCallId) ??
|
|
100
110
|
this.openSpan({
|
|
101
|
-
name: `tool:${part.toolName}`,
|
|
111
|
+
name: `tool:${part.payload.toolName}`,
|
|
102
112
|
kind: 'tool',
|
|
103
113
|
parentSpanId: this.currentNode?.spanId ?? this.root.spanId,
|
|
104
114
|
at,
|
|
105
115
|
attributes: {
|
|
106
116
|
...(this.currentFlow ? { activeFlow: activeFlowName(this.currentFlow) } : {}),
|
|
107
117
|
...(this.currentNode ? { nodeId: nodeName(this.currentNode) } : {}),
|
|
108
|
-
toolName: part.toolName,
|
|
118
|
+
toolName: part.payload.toolName,
|
|
109
119
|
},
|
|
110
120
|
});
|
|
111
121
|
span.attributes.output = result;
|
|
@@ -114,35 +124,43 @@ export class TraceRecorder {
|
|
|
114
124
|
break;
|
|
115
125
|
}
|
|
116
126
|
case 'handoff': {
|
|
127
|
+
const handoffFrom = this.currentAgentId;
|
|
117
128
|
const span = this.openSpan({
|
|
118
|
-
name: `handoff:${part.targetAgent}`,
|
|
129
|
+
name: `handoff:${part.payload.targetAgent}`,
|
|
119
130
|
kind: 'handoff',
|
|
120
131
|
parentSpanId: this.root.spanId,
|
|
121
132
|
at,
|
|
122
|
-
attributes: {
|
|
133
|
+
attributes: {
|
|
134
|
+
...(handoffFrom ? { handoffFrom } : {}),
|
|
135
|
+
handoffTo: part.payload.targetAgent,
|
|
136
|
+
},
|
|
123
137
|
});
|
|
124
138
|
span.endTime = at;
|
|
125
139
|
this.emitSpan(span);
|
|
140
|
+
this.currentAgentId = part.payload.targetAgent;
|
|
126
141
|
break;
|
|
127
142
|
}
|
|
128
143
|
case 'error': {
|
|
129
144
|
const span = this.openTools.at(-1) ?? this.currentNode ?? this.currentFlow ?? this.root;
|
|
130
145
|
span.status = 'error';
|
|
131
|
-
span.attributes.error = part.error;
|
|
146
|
+
span.attributes.error = part.payload.error;
|
|
132
147
|
this.root.status = 'error';
|
|
133
|
-
this.root.attributes.error = part.error;
|
|
148
|
+
this.root.attributes.error = part.payload.error;
|
|
134
149
|
break;
|
|
135
150
|
}
|
|
136
151
|
case 'done':
|
|
137
|
-
this.setSessionId(part.sessionId);
|
|
152
|
+
this.setSessionId(part.payload.sessionId);
|
|
138
153
|
this.root.attributes.output = this.trace.answer;
|
|
139
|
-
if (part.usage) {
|
|
140
|
-
if (typeof part.usage.inputTokens === 'number')
|
|
141
|
-
this.root.attributes.tokensIn = part.usage.inputTokens;
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
154
|
+
if (part.payload.usage) {
|
|
155
|
+
if (typeof part.payload.usage.inputTokens === 'number') {
|
|
156
|
+
this.root.attributes.tokensIn = part.payload.usage.inputTokens;
|
|
157
|
+
}
|
|
158
|
+
if (typeof part.payload.usage.outputTokens === 'number') {
|
|
159
|
+
this.root.attributes.tokensOut = part.payload.usage.outputTokens;
|
|
160
|
+
}
|
|
161
|
+
if (typeof part.payload.usage.contextTokens === 'number') {
|
|
162
|
+
this.root.attributes.contextTokens = part.payload.usage.contextTokens;
|
|
163
|
+
}
|
|
146
164
|
}
|
|
147
165
|
this.close(at);
|
|
148
166
|
break;
|
|
@@ -174,7 +192,11 @@ export class TraceRecorder {
|
|
|
174
192
|
kind: args.kind,
|
|
175
193
|
startTime: args.at,
|
|
176
194
|
status: 'ok',
|
|
177
|
-
attributes: {
|
|
195
|
+
attributes: {
|
|
196
|
+
sessionId: this.trace.sessionId,
|
|
197
|
+
...(this.currentAgentId ? { agentId: this.currentAgentId } : {}),
|
|
198
|
+
...args.attributes,
|
|
199
|
+
},
|
|
178
200
|
};
|
|
179
201
|
this.trace.spans.push(span);
|
|
180
202
|
return span;
|
|
@@ -239,8 +261,22 @@ export class TraceRecorder {
|
|
|
239
261
|
}
|
|
240
262
|
}
|
|
241
263
|
export async function runOnce(runtime, opts) {
|
|
264
|
+
// Resolve attribution the same way Runtime.runOnce does: caller's agentId
|
|
265
|
+
// first, then persisted state, then the runtime default. Without this the
|
|
266
|
+
// standalone helper produced spans with no `agentId` and handoff spans with
|
|
267
|
+
// no `handoffFrom` — the exact feature it is documented to provide.
|
|
268
|
+
let agentId = opts.agentId;
|
|
269
|
+
if (!agentId && opts.sessionId && runtime.getSessionStore) {
|
|
270
|
+
const existing = await runtime.getSessionStore().get(opts.sessionId);
|
|
271
|
+
agentId = existing?.activeAgentId ?? existing?.currentAgent;
|
|
272
|
+
}
|
|
273
|
+
agentId ??= runtime.getDefaultAgentId?.();
|
|
242
274
|
const handle = runtime.run(opts);
|
|
243
|
-
const recorder = new TraceRecorder({
|
|
275
|
+
const recorder = new TraceRecorder({
|
|
276
|
+
sessionId: opts.sessionId,
|
|
277
|
+
agentId,
|
|
278
|
+
input: opts.input,
|
|
279
|
+
});
|
|
244
280
|
for await (const part of handle.events) {
|
|
245
281
|
recorder.record(part);
|
|
246
282
|
}
|
|
@@ -28,7 +28,7 @@ export async function buildAgentToolSurface(agent, session, deps) {
|
|
|
28
28
|
let skillPrompt;
|
|
29
29
|
let skillTools = {};
|
|
30
30
|
if (agent.skills) {
|
|
31
|
-
const wired = await wireAgentSkills(agent);
|
|
31
|
+
const wired = await wireAgentSkills(agent, resolvedWorkspace?.fs);
|
|
32
32
|
if (wired) {
|
|
33
33
|
skillTools = wired.tools;
|
|
34
34
|
Object.assign(executorTools, wired.tools);
|
|
@@ -29,16 +29,19 @@ export class TextDriver {
|
|
|
29
29
|
if (!preTurn.proceed) {
|
|
30
30
|
const blocked = preTurn.blockedMessage ?? 'Input blocked by guardrails';
|
|
31
31
|
ctx.emit({
|
|
32
|
+
channel: 'internal',
|
|
32
33
|
type: 'safety-blocked',
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
payload: {
|
|
35
|
+
moderator: preTurn.blockedBy ?? 'input-guardrails',
|
|
36
|
+
rationale: preTurn.blockedReason ?? 'input blocked',
|
|
37
|
+
userFacingMessage: blocked,
|
|
38
|
+
},
|
|
36
39
|
});
|
|
37
40
|
const id = crypto.randomUUID();
|
|
38
|
-
ctx.emit({ type: 'text-start', id });
|
|
39
|
-
ctx.emit({ type: 'text-delta', id, delta: blocked });
|
|
40
|
-
ctx.emit({ type: 'text-end', id });
|
|
41
|
-
ctx.emit({ type: 'turn-end' });
|
|
41
|
+
ctx.emit({ channel: 'client', type: 'text-start', payload: { id } });
|
|
42
|
+
ctx.emit({ channel: 'client', type: 'text-delta', payload: { id, delta: blocked } });
|
|
43
|
+
ctx.emit({ channel: 'client', type: 'text-end', payload: { id } });
|
|
44
|
+
ctx.emit({ channel: 'internal', type: 'turn-end', payload: {} });
|
|
42
45
|
return { text: blocked, toolResults: [] };
|
|
43
46
|
}
|
|
44
47
|
const scope = resolveNodeGatherScope(replyNode, ctx.runState.state, ctx.runState.messages);
|
|
@@ -75,7 +78,7 @@ export class TextDriver {
|
|
|
75
78
|
if (part.type === 'error') {
|
|
76
79
|
const err = part.error;
|
|
77
80
|
const message = err instanceof Error ? err.message : String(err);
|
|
78
|
-
ctx.emit({ type: 'error', error: message });
|
|
81
|
+
ctx.emit({ channel: 'client', type: 'error', payload: { error: message } });
|
|
79
82
|
throw err instanceof Error ? err : new Error(message);
|
|
80
83
|
}
|
|
81
84
|
}
|
|
@@ -160,7 +163,7 @@ export class TextDriver {
|
|
|
160
163
|
if (turnUsage && turnUsage.totalTokens > 0) {
|
|
161
164
|
out.usage = turnUsage;
|
|
162
165
|
}
|
|
163
|
-
ctx.emit({ type: 'turn-end' });
|
|
166
|
+
ctx.emit({ channel: 'internal', type: 'turn-end', payload: {} });
|
|
164
167
|
return out;
|
|
165
168
|
}
|
|
166
169
|
// Non-speaking field extraction for collect nodes (shared helper so text and
|
|
@@ -11,6 +11,12 @@ export interface ModelToolCallOutcome {
|
|
|
11
11
|
result: unknown;
|
|
12
12
|
control?: TurnControl;
|
|
13
13
|
failed: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Set when the call unwound with a control-flow signal rather than a failure. Held as a
|
|
16
|
+
* value so a parallel batch can still finalize its siblings; the dispatcher rethrows it
|
|
17
|
+
* once the batch has settled. Never surfaced to the model.
|
|
18
|
+
*/
|
|
19
|
+
signal?: unknown;
|
|
14
20
|
}
|
|
15
21
|
export declare function executeModelToolCall(ctx: RunContext, call: ModelToolCall, localTools?: Record<string, AnyTool>, durableOpts?: {
|
|
16
22
|
callsite?: string;
|