@kuralle-agents/core 0.3.16 → 0.3.18
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/dist/audit/index.d.ts +1 -0
- package/dist/audit/index.js +1 -0
- package/dist/audit/record.d.ts +15 -0
- package/dist/audit/record.js +23 -0
- package/dist/capabilities/ValidationCapability.d.ts +8 -0
- package/dist/flow/collectDigression.d.ts +31 -0
- package/dist/flow/collectDigression.js +99 -0
- package/dist/flow/collectUntilComplete.d.ts +6 -1
- package/dist/flow/collectUntilComplete.js +28 -5
- package/dist/flow/normalizeTransition.d.ts +6 -1
- package/dist/flow/runFlow.d.ts +2 -1
- package/dist/flow/runFlow.js +45 -7
- package/dist/runtime/channels/TextDriver.js +7 -5
- package/dist/runtime/channels/VoiceDriver.js +7 -4
- package/dist/runtime/grounding/gather.d.ts +2 -0
- package/dist/runtime/grounding/gather.js +22 -4
- package/dist/runtime/grounding/knowledge.js +7 -1
- package/dist/runtime/hostLoop.js +4 -4
- package/dist/runtime/policies/agentTurn.d.ts +5 -1
- package/dist/runtime/policies/agentTurn.js +93 -7
- package/dist/runtime/policies/resolvePolicies.js +2 -2
- package/dist/runtime/select.d.ts +2 -0
- package/dist/runtime/select.js +5 -1
- package/dist/types/agentConfig.d.ts +6 -0
- package/dist/types/flow.d.ts +5 -0
- package/dist/types/run-context.d.ts +8 -2
- package/package.json +2 -2
package/dist/audit/index.d.ts
CHANGED
package/dist/audit/index.js
CHANGED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ConversationAuditEntry, AuditEntryBase } from './types.js';
|
|
2
|
+
import type { Session } from '../types/session.js';
|
|
3
|
+
export interface AuditRecordContext {
|
|
4
|
+
sessionId: string;
|
|
5
|
+
conversationId?: string;
|
|
6
|
+
userId?: string;
|
|
7
|
+
agentId?: string;
|
|
8
|
+
turnIndex?: number;
|
|
9
|
+
}
|
|
10
|
+
export type NewAuditEntry = {
|
|
11
|
+
[K in ConversationAuditEntry['type']]: Omit<Extract<ConversationAuditEntry, {
|
|
12
|
+
type: K;
|
|
13
|
+
}>, keyof AuditEntryBase>;
|
|
14
|
+
}[ConversationAuditEntry['type']];
|
|
15
|
+
export declare function appendConversationAudit(session: Session, ctx: AuditRecordContext, entry: NewAuditEntry): void;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function appendConversationAudit(session, ctx, entry) {
|
|
2
|
+
const at = new Date().toISOString();
|
|
3
|
+
const full = {
|
|
4
|
+
at,
|
|
5
|
+
sessionId: ctx.sessionId,
|
|
6
|
+
conversationId: ctx.conversationId,
|
|
7
|
+
userId: ctx.userId,
|
|
8
|
+
agentId: ctx.agentId,
|
|
9
|
+
turnIndex: ctx.turnIndex,
|
|
10
|
+
...entry,
|
|
11
|
+
};
|
|
12
|
+
if (!session.metadata) {
|
|
13
|
+
session.metadata = {
|
|
14
|
+
createdAt: session.createdAt,
|
|
15
|
+
lastActiveAt: session.updatedAt,
|
|
16
|
+
totalTokens: 0,
|
|
17
|
+
totalSteps: 0,
|
|
18
|
+
handoffHistory: [],
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
session.metadata.audit ??= [];
|
|
22
|
+
session.metadata.audit.push(full);
|
|
23
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { EscalationReason } from '../escalation/types.js';
|
|
1
2
|
import type { Session, SourceRef, ToolCallRecord } from '../types/index.js';
|
|
2
3
|
export interface ValidationCapability {
|
|
3
4
|
readonly name: string;
|
|
@@ -21,6 +22,13 @@ export type ValidateDecision = {
|
|
|
21
22
|
confidence: number;
|
|
22
23
|
rewrittenOutput: string;
|
|
23
24
|
rationale: string;
|
|
25
|
+
} | {
|
|
26
|
+
decision: 'escalate';
|
|
27
|
+
confidence: number;
|
|
28
|
+
rationale: string;
|
|
29
|
+
escalateTo?: string;
|
|
30
|
+
escalationReason?: EscalationReason;
|
|
31
|
+
userFacingMessage?: string;
|
|
24
32
|
} | {
|
|
25
33
|
decision: 'block';
|
|
26
34
|
confidence: number;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { AgentConfig } from '../types/agentConfig.js';
|
|
2
|
+
import type { ChannelDriver } from '../types/channel.js';
|
|
3
|
+
import type { CollectNode } from '../types/flow.js';
|
|
4
|
+
import type { RunContext } from '../types/run-context.js';
|
|
5
|
+
import type { RunState } from '../runtime/durable/types.js';
|
|
6
|
+
import type { NormalizedTransition } from './normalizeTransition.js';
|
|
7
|
+
import { selectHostTarget } from '../runtime/select.js';
|
|
8
|
+
export interface FlowPark {
|
|
9
|
+
flow: string;
|
|
10
|
+
node: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function getFlowPark(state: Record<string, unknown>): FlowPark | undefined;
|
|
13
|
+
export declare function looksLikeOffScriptQuestion(input: string): boolean;
|
|
14
|
+
export interface CollectDigressionOptions {
|
|
15
|
+
agent: AgentConfig;
|
|
16
|
+
node: CollectNode;
|
|
17
|
+
activeFlowName: string;
|
|
18
|
+
run: RunState;
|
|
19
|
+
driver: ChannelDriver;
|
|
20
|
+
ctx: RunContext;
|
|
21
|
+
select?: typeof selectHostTarget;
|
|
22
|
+
}
|
|
23
|
+
export type CollectDigressionResult = {
|
|
24
|
+
kind: 'transition';
|
|
25
|
+
transition: NormalizedTransition;
|
|
26
|
+
} | {
|
|
27
|
+
kind: 'answeredThenResume';
|
|
28
|
+
} | {
|
|
29
|
+
kind: 'none';
|
|
30
|
+
};
|
|
31
|
+
export declare function runCollectDigression(options: CollectDigressionOptions): Promise<CollectDigressionResult>;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { resolveReplyNode } from './nodeBuilders.js';
|
|
2
|
+
import { selectHostTarget } from '../runtime/select.js';
|
|
3
|
+
const FLOW_PARK_KEY = '__flowPark';
|
|
4
|
+
export function getFlowPark(state) {
|
|
5
|
+
const raw = state[FLOW_PARK_KEY];
|
|
6
|
+
if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
const flow = raw.flow;
|
|
10
|
+
const node = raw.node;
|
|
11
|
+
if (typeof flow === 'string' && typeof node === 'string') {
|
|
12
|
+
return { flow, node };
|
|
13
|
+
}
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
function setFlowPark(state, park) {
|
|
17
|
+
state[FLOW_PARK_KEY] = park;
|
|
18
|
+
}
|
|
19
|
+
function appendAssistantMessage(run, text) {
|
|
20
|
+
if (!text.trim()) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const message = { role: 'assistant', content: text };
|
|
24
|
+
run.messages = [...run.messages, message];
|
|
25
|
+
}
|
|
26
|
+
export function looksLikeOffScriptQuestion(input) {
|
|
27
|
+
const trimmed = input.trim();
|
|
28
|
+
if (!trimmed) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
if (trimmed.includes('?')) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
return /^(what|how|why|when|where|who|can|could|do|does|is|are|will|would)\b/i.test(trimmed);
|
|
35
|
+
}
|
|
36
|
+
export async function runCollectDigression(options) {
|
|
37
|
+
const { agent, node, activeFlowName, run, driver, ctx } = options;
|
|
38
|
+
const select = options.select ?? selectHostTarget;
|
|
39
|
+
const offScriptInput = peekLatestUserMessage(run);
|
|
40
|
+
if (!offScriptInput) {
|
|
41
|
+
return { kind: 'none' };
|
|
42
|
+
}
|
|
43
|
+
const selection = await select({
|
|
44
|
+
agent,
|
|
45
|
+
run,
|
|
46
|
+
model: agent.routing?.model ?? ctx.controlModel,
|
|
47
|
+
alwaysRoute: agent.routing?.always === true,
|
|
48
|
+
excludeFlowNames: [activeFlowName],
|
|
49
|
+
});
|
|
50
|
+
if (selection.kind === 'route') {
|
|
51
|
+
return {
|
|
52
|
+
kind: 'transition',
|
|
53
|
+
transition: { kind: 'handoff', to: selection.agentId, reason: selection.reason },
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
if (selection.kind === 'enterFlow') {
|
|
57
|
+
setFlowPark(run.state, { flow: activeFlowName, node: node.id });
|
|
58
|
+
return {
|
|
59
|
+
kind: 'transition',
|
|
60
|
+
transition: {
|
|
61
|
+
kind: 'switchFlow',
|
|
62
|
+
flow: selection.flow,
|
|
63
|
+
park: { flow: activeFlowName, node: node.id },
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
if (!looksLikeOffScriptQuestion(offScriptInput)) {
|
|
68
|
+
return { kind: 'none' };
|
|
69
|
+
}
|
|
70
|
+
const replyNode = {
|
|
71
|
+
kind: 'reply',
|
|
72
|
+
id: `${node.id}__digression`,
|
|
73
|
+
instructions: ctx.baseInstructions ??
|
|
74
|
+
'Answer the user helpfully and concisely. Do not mention internal routing or flows.',
|
|
75
|
+
tools: ctx.globalTools,
|
|
76
|
+
};
|
|
77
|
+
const turn = await driver.runAgentTurn(resolveReplyNode(replyNode, run.state, { freeConversation: true }), ctx);
|
|
78
|
+
if (turn.text.trim()) {
|
|
79
|
+
ctx.emit({ type: 'text-delta', text: turn.text });
|
|
80
|
+
ctx.emit({ type: 'turn-end' });
|
|
81
|
+
appendAssistantMessage(run, turn.text);
|
|
82
|
+
}
|
|
83
|
+
if (turn.control?.type === 'handoff') {
|
|
84
|
+
return {
|
|
85
|
+
kind: 'transition',
|
|
86
|
+
transition: { kind: 'handoff', to: turn.control.target, reason: turn.control.reason },
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
return { kind: 'answeredThenResume' };
|
|
90
|
+
}
|
|
91
|
+
function peekLatestUserMessage(run) {
|
|
92
|
+
for (let i = run.messages.length - 1; i >= 0; i -= 1) {
|
|
93
|
+
const message = run.messages[i];
|
|
94
|
+
if (message?.role === 'user' && typeof message.content === 'string') {
|
|
95
|
+
return message.content;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
import type { AgentConfig } from '../types/agentConfig.js';
|
|
1
2
|
import type { ChannelDriver } from '../types/channel.js';
|
|
2
3
|
import type { CollectNode } from '../types/flow.js';
|
|
3
4
|
import type { RunContext } from '../types/run-context.js';
|
|
4
5
|
import type { RunState } from '../runtime/durable/types.js';
|
|
5
6
|
import type { NormalizedTransition } from './normalizeTransition.js';
|
|
6
|
-
export declare function collectUntilComplete(node: CollectNode, run: RunState, driver: ChannelDriver, ctx: RunContext
|
|
7
|
+
export declare function collectUntilComplete(node: CollectNode, run: RunState, driver: ChannelDriver, ctx: RunContext, options?: {
|
|
8
|
+
agent?: AgentConfig;
|
|
9
|
+
activeFlowName?: string;
|
|
10
|
+
}): Promise<NormalizedTransition>;
|
|
11
|
+
export declare function emitCollectAsk(node: CollectNode, run: RunState, ctx: RunContext): void;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { runCollectDigression } from './collectDigression.js';
|
|
1
2
|
import { hasPendingUserInput } from '../runtime/channels/inputBuffer.js';
|
|
2
3
|
import { resolveCollectExtractionNode } from './nodeBuilders.js';
|
|
3
4
|
import { computeMissingFields, createExtractionSubmitTool, getCollectData, incrementCollectTurns, emitExtractionTelemetry, mergeTurnExtraction, projectCollectData, schemaSatisfied, } from './extraction.js';
|
|
@@ -13,7 +14,7 @@ function appendUserMessage(run, input) {
|
|
|
13
14
|
const message = { role: 'user', content: input };
|
|
14
15
|
run.messages = [...run.messages, message];
|
|
15
16
|
}
|
|
16
|
-
export async function collectUntilComplete(node, run, driver, ctx) {
|
|
17
|
+
export async function collectUntilComplete(node, run, driver, ctx, options) {
|
|
17
18
|
for (;;) {
|
|
18
19
|
if (schemaSatisfied(node, run.state)) {
|
|
19
20
|
const data = projectCollectData(node, run.state);
|
|
@@ -44,11 +45,11 @@ export async function collectUntilComplete(node, run, driver, ctx) {
|
|
|
44
45
|
const data = projectCollectData(node, run.state);
|
|
45
46
|
return normalizeTransition(await node.onComplete(data, run.state));
|
|
46
47
|
}
|
|
47
|
-
const
|
|
48
|
-
const submitTool = createExtractionSubmitTool(node,
|
|
48
|
+
const missingBefore = computeMissingFields(node, getCollectData(run.state, node.id));
|
|
49
|
+
const submitTool = createExtractionSubmitTool(node, missingBefore, {
|
|
49
50
|
userMessage: peekLatestUserMessage(run),
|
|
50
51
|
});
|
|
51
|
-
const resolved = resolveCollectExtractionNode(node,
|
|
52
|
+
const resolved = resolveCollectExtractionNode(node, missingBefore, run.state, submitTool);
|
|
52
53
|
// Non-speaking extraction: the model's prose is DISCARDED (never emitted or
|
|
53
54
|
// appended), so a collect turn cannot author narration that contradicts flow
|
|
54
55
|
// state. Falls back to runAgentTurn for drivers without runExtraction; its
|
|
@@ -58,6 +59,28 @@ export async function collectUntilComplete(node, run, driver, ctx) {
|
|
|
58
59
|
? driver.runExtraction(resolved, ctx)
|
|
59
60
|
: driver.runAgentTurn(resolved, ctx));
|
|
60
61
|
mergeExtractionFromTurn(node, run, turn, ctx);
|
|
62
|
+
const missingAfter = computeMissingFields(node, getCollectData(run.state, node.id));
|
|
63
|
+
const advanced = missingAfter.length < missingBefore.length || schemaSatisfied(node, run.state);
|
|
64
|
+
if (!advanced &&
|
|
65
|
+
ctx.outOfBandControl &&
|
|
66
|
+
options?.agent &&
|
|
67
|
+
options.activeFlowName) {
|
|
68
|
+
const digression = await runCollectDigression({
|
|
69
|
+
agent: options.agent,
|
|
70
|
+
node,
|
|
71
|
+
activeFlowName: options.activeFlowName,
|
|
72
|
+
run,
|
|
73
|
+
driver,
|
|
74
|
+
ctx,
|
|
75
|
+
});
|
|
76
|
+
if (digression.kind === 'transition') {
|
|
77
|
+
return digression.transition;
|
|
78
|
+
}
|
|
79
|
+
if (digression.kind === 'answeredThenResume') {
|
|
80
|
+
emitCollectAsk(node, run, ctx);
|
|
81
|
+
return { kind: 'stay' };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
61
84
|
}
|
|
62
85
|
}
|
|
63
86
|
function humanizeField(field) {
|
|
@@ -82,7 +105,7 @@ function renderCollectAsk(node, missing, state) {
|
|
|
82
105
|
}
|
|
83
106
|
return 'Could you tell me a little more?';
|
|
84
107
|
}
|
|
85
|
-
function emitCollectAsk(node, run, ctx) {
|
|
108
|
+
export function emitCollectAsk(node, run, ctx) {
|
|
86
109
|
const missing = computeMissingFields(node, getCollectData(run.state, node.id));
|
|
87
110
|
const text = renderCollectAsk(node, missing, run.state);
|
|
88
111
|
if (!text.trim()) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { FlowNode, Transition } from '../types/flow.js';
|
|
1
|
+
import type { Flow, FlowNode, Transition } from '../types/flow.js';
|
|
2
|
+
import type { FlowPark } from './collectDigression.js';
|
|
2
3
|
export type NormalizedTransition = {
|
|
3
4
|
kind: 'goto';
|
|
4
5
|
node: FlowNode;
|
|
@@ -15,6 +16,10 @@ export type NormalizedTransition = {
|
|
|
15
16
|
reason: string;
|
|
16
17
|
} | {
|
|
17
18
|
kind: 'stay';
|
|
19
|
+
} | {
|
|
20
|
+
kind: 'switchFlow';
|
|
21
|
+
flow: Flow;
|
|
22
|
+
park: FlowPark;
|
|
18
23
|
};
|
|
19
24
|
export declare function resolveNodeRef(ref: FlowNode | (() => FlowNode)): FlowNode;
|
|
20
25
|
export declare function normalizeTransition(transition: Transition): NormalizedTransition;
|
package/dist/flow/runFlow.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AgentConfig } from '../types/agentConfig.js';
|
|
1
2
|
import type { ChannelDriver } from '../types/channel.js';
|
|
2
3
|
import type { Flow } from '../types/flow.js';
|
|
3
4
|
import type { RunContext } from '../types/run-context.js';
|
|
@@ -15,4 +16,4 @@ export type FlowResult = {
|
|
|
15
16
|
export declare class FlowOscillationError extends Error {
|
|
16
17
|
constructor(from: string, to: string);
|
|
17
18
|
}
|
|
18
|
-
export declare function runFlow(flow: Flow, run: RunState, driver: ChannelDriver, ctx: RunContext): Promise<FlowResult>;
|
|
19
|
+
export declare function runFlow(flow: Flow, run: RunState, driver: ChannelDriver, ctx: RunContext, agent?: AgentConfig): Promise<FlowResult>;
|
package/dist/flow/runFlow.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getFlowPark } from './collectDigression.js';
|
|
1
2
|
import { parseConfirmation } from './confirmParse.js';
|
|
2
3
|
import { hasPendingUserInput } from '../runtime/channels/inputBuffer.js';
|
|
3
4
|
import { collectUntilComplete } from './collectUntilComplete.js';
|
|
@@ -11,6 +12,7 @@ import { loadRecordedSteps } from '../runtime/durable/replay.js';
|
|
|
11
12
|
import { SuspendError } from '../runtime/durable/RunStore.js';
|
|
12
13
|
import { ToolApprovalDeniedError } from '../tools/effect/errors.js';
|
|
13
14
|
import { emitInteractiveOnNodeEnter } from './emitInteractive.js';
|
|
15
|
+
import { appendConversationAudit } from '../audit/record.js';
|
|
14
16
|
import { appendSafeAssistantMessage, degradeFlowError, findEscalateNode, } from './degrade.js';
|
|
15
17
|
export class FlowOscillationError extends Error {
|
|
16
18
|
constructor(from, to) {
|
|
@@ -87,12 +89,15 @@ function appendAssistantMessage(run, text) {
|
|
|
87
89
|
const message = { role: 'assistant', content: text };
|
|
88
90
|
run.messages = [...run.messages, message];
|
|
89
91
|
}
|
|
90
|
-
async function dispatchNode(node, run, driver, ctx) {
|
|
92
|
+
async function dispatchNode(node, run, driver, ctx, agent, flow) {
|
|
91
93
|
if (isActionNode(node)) {
|
|
92
94
|
return normalizeTransition(await node.run(run.state, toActionContext(ctx)));
|
|
93
95
|
}
|
|
94
96
|
if (isCollectNode(node)) {
|
|
95
|
-
return collectUntilComplete(node, run, driver, ctx
|
|
97
|
+
return collectUntilComplete(node, run, driver, ctx, {
|
|
98
|
+
agent,
|
|
99
|
+
activeFlowName: flow.name,
|
|
100
|
+
});
|
|
96
101
|
}
|
|
97
102
|
if (isDecideNode(node)) {
|
|
98
103
|
if (node.confirmGate) {
|
|
@@ -139,7 +144,7 @@ async function dispatchNode(node, run, driver, ctx) {
|
|
|
139
144
|
if (decision.kind === 'redispatch') {
|
|
140
145
|
const signal = await driver.awaitUser(ctx);
|
|
141
146
|
appendUserMessage(run, signal.input);
|
|
142
|
-
return dispatchNode(node, run, driver, ctx);
|
|
147
|
+
return dispatchNode(node, run, driver, ctx, agent, flow);
|
|
143
148
|
}
|
|
144
149
|
appendAssistantMessage(run, turn.text);
|
|
145
150
|
if (decision.kind === 'transition') {
|
|
@@ -151,7 +156,7 @@ async function dispatchNode(node, run, driver, ctx) {
|
|
|
151
156
|
if (turn.interrupted) {
|
|
152
157
|
const signal = await driver.awaitUser(ctx);
|
|
153
158
|
appendUserMessage(run, signal.input);
|
|
154
|
-
return dispatchNode(node, run, driver, ctx);
|
|
159
|
+
return dispatchNode(node, run, driver, ctx, agent, flow);
|
|
155
160
|
}
|
|
156
161
|
if (turn.control?.type === 'handoff') {
|
|
157
162
|
return { kind: 'handoff', to: turn.control.target, reason: turn.control.reason };
|
|
@@ -165,6 +170,21 @@ async function dispatchNode(node, run, driver, ctx) {
|
|
|
165
170
|
if (turn.control?.type === 'recover') {
|
|
166
171
|
return { kind: 'end', reason: turn.control.reason ?? 'error_degraded' };
|
|
167
172
|
}
|
|
173
|
+
if (node.confidenceGate &&
|
|
174
|
+
turn.confidence != null &&
|
|
175
|
+
turn.confidence < node.confidenceGate.min) {
|
|
176
|
+
appendConversationAudit(ctx.session, {
|
|
177
|
+
sessionId: ctx.session.id,
|
|
178
|
+
conversationId: ctx.session.conversationId,
|
|
179
|
+
userId: ctx.session.userId,
|
|
180
|
+
agentId: ctx.runState.activeAgentId,
|
|
181
|
+
}, {
|
|
182
|
+
type: 'escalation',
|
|
183
|
+
reason: 'low-confidence',
|
|
184
|
+
confidence: turn.confidence,
|
|
185
|
+
});
|
|
186
|
+
return normalizeTransition(node.confidenceGate.onLow);
|
|
187
|
+
}
|
|
168
188
|
if (node.next) {
|
|
169
189
|
return normalizeTransition(await node.next(turn, run.state));
|
|
170
190
|
}
|
|
@@ -172,7 +192,7 @@ async function dispatchNode(node, run, driver, ctx) {
|
|
|
172
192
|
}
|
|
173
193
|
throw new Error(`Unknown node kind: ${node.kind}`);
|
|
174
194
|
}
|
|
175
|
-
export async function runFlow(flow, run, driver, ctx) {
|
|
195
|
+
export async function runFlow(flow, run, driver, ctx, agent) {
|
|
176
196
|
const registry = buildNodeRegistry(flow);
|
|
177
197
|
const startNode = resolveStartNode(flow);
|
|
178
198
|
const initialNodeId = run.activeNode ?? startNode.id;
|
|
@@ -192,7 +212,7 @@ export async function runFlow(flow, run, driver, ctx) {
|
|
|
192
212
|
for (;;) {
|
|
193
213
|
let transition;
|
|
194
214
|
try {
|
|
195
|
-
transition = await dispatchNode(node, run, driver, ctx);
|
|
215
|
+
transition = await dispatchNode(node, run, driver, ctx, agent, flow);
|
|
196
216
|
}
|
|
197
217
|
catch (error) {
|
|
198
218
|
if (error instanceof SuspendError || error instanceof ToolApprovalDeniedError) {
|
|
@@ -200,9 +220,27 @@ export async function runFlow(flow, run, driver, ctx) {
|
|
|
200
220
|
}
|
|
201
221
|
const message = error instanceof Error ? error.message : String(error);
|
|
202
222
|
ctx.emit({ type: 'error', error: message });
|
|
203
|
-
return degradeFlowError(flow, registry, run, driver, ctx, dispatchNode);
|
|
223
|
+
return degradeFlowError(flow, registry, run, driver, ctx, (n, r, d, c) => dispatchNode(n, r, d, c, agent, flow));
|
|
224
|
+
}
|
|
225
|
+
if (transition.kind === 'switchFlow') {
|
|
226
|
+
run.activeFlow = transition.flow.name;
|
|
227
|
+
run.activeNode = undefined;
|
|
228
|
+
await ctx.runStore.putRunState(run);
|
|
229
|
+
return runFlow(transition.flow, run, driver, ctx, agent);
|
|
204
230
|
}
|
|
205
231
|
if (transition.kind === 'end') {
|
|
232
|
+
const park = getFlowPark(run.state);
|
|
233
|
+
if (park && agent) {
|
|
234
|
+
delete run.state.__flowPark;
|
|
235
|
+
const parkedFlow = agent.flows?.find((candidate) => candidate.name === park.flow);
|
|
236
|
+
if (parkedFlow) {
|
|
237
|
+
run.activeFlow = park.flow;
|
|
238
|
+
run.activeNode = park.node;
|
|
239
|
+
await ctx.runStore.putRunState(run);
|
|
240
|
+
ctx.emit({ type: 'flow-end', flow: flow.name, reason: transition.reason });
|
|
241
|
+
return runFlow(parkedFlow, run, driver, ctx, agent);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
206
244
|
ctx.emit({ type: 'flow-end', flow: flow.name, reason: transition.reason });
|
|
207
245
|
return { kind: 'ended', reason: transition.reason };
|
|
208
246
|
}
|
|
@@ -98,11 +98,13 @@ export class TextDriver {
|
|
|
98
98
|
messages.push(toolResultMessage({ toolName: call.toolName, input: call.input, toolCallId: call.toolCallId }, toolResult));
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
|
-
const postTurn = await applyPostTurnPolicies(ctx, draftText, toolCallsMade);
|
|
102
|
-
|
|
103
|
-
out.
|
|
104
|
-
|
|
105
|
-
|
|
101
|
+
const postTurn = await applyPostTurnPolicies(ctx, draftText, toolCallsMade, gather.citations ?? []);
|
|
102
|
+
out.confidence = postTurn.confidence;
|
|
103
|
+
out.control = postTurn.control;
|
|
104
|
+
const emitText = postTurn.control ? (postTurn.blockedMessage ?? postTurn.text) : postTurn.text;
|
|
105
|
+
out.text = emitText;
|
|
106
|
+
if (emitText) {
|
|
107
|
+
ctx.emit({ type: 'text-delta', text: emitText });
|
|
106
108
|
}
|
|
107
109
|
ctx.emit({ type: 'turn-end' });
|
|
108
110
|
return out;
|
|
@@ -56,10 +56,13 @@ export class VoiceDriver {
|
|
|
56
56
|
break;
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
|
-
const postTurn = await applyPostTurnPolicies(ctx, draftText, toolCallsMade);
|
|
60
|
-
out.
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
const postTurn = await applyPostTurnPolicies(ctx, draftText, toolCallsMade, gather.citations ?? []);
|
|
60
|
+
out.confidence = postTurn.confidence;
|
|
61
|
+
out.control = postTurn.control;
|
|
62
|
+
const emitText = postTurn.control ? (postTurn.blockedMessage ?? postTurn.text) : postTurn.text;
|
|
63
|
+
out.text = emitText;
|
|
64
|
+
if (emitText) {
|
|
65
|
+
ctx.emit({ type: 'text-delta', text: emitText });
|
|
63
66
|
}
|
|
64
67
|
ctx.emit({ type: 'turn-end' });
|
|
65
68
|
return out;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import type { SourceRef } from '../../types/voice.js';
|
|
1
2
|
import type { GatherScope, RunContext } from '../../types/run-context.js';
|
|
2
3
|
export type { GatherScope } from '../../types/run-context.js';
|
|
3
4
|
export interface GatherResult {
|
|
4
5
|
retrievalBlock?: string;
|
|
5
6
|
memoryBlock?: string;
|
|
7
|
+
citations?: SourceRef[];
|
|
6
8
|
}
|
|
7
9
|
export declare function runGatherPhase(ctx: RunContext, scope?: GatherScope): Promise<GatherResult>;
|
|
@@ -1,9 +1,27 @@
|
|
|
1
|
+
function normalizeRetrieveResult(raw) {
|
|
2
|
+
if (raw == null) {
|
|
3
|
+
return {};
|
|
4
|
+
}
|
|
5
|
+
if (typeof raw === 'string') {
|
|
6
|
+
return { retrievalBlock: raw };
|
|
7
|
+
}
|
|
8
|
+
return { retrievalBlock: raw.block, citations: raw.citations };
|
|
9
|
+
}
|
|
1
10
|
export async function runGatherPhase(ctx, scope) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
11
|
+
let retrievalBlock;
|
|
12
|
+
let citations;
|
|
13
|
+
if (ctx.autoRetrieve && scope?.knowledge?.autoRetrieve !== false) {
|
|
14
|
+
const raw = await ctx.autoRetrieve.retrieve(ctx, scope);
|
|
15
|
+
const normalized = normalizeRetrieveResult(raw);
|
|
16
|
+
retrievalBlock = normalized.retrievalBlock;
|
|
17
|
+
citations = normalized.citations;
|
|
18
|
+
ctx.lastRetrievalCitations = citations;
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
ctx.lastRetrievalCitations = undefined;
|
|
22
|
+
}
|
|
5
23
|
const memoryBlock = ctx.memoryService?.preload && scope?.memory?.preload !== false
|
|
6
24
|
? await ctx.memoryService.preload(ctx, scope)
|
|
7
25
|
: undefined;
|
|
8
|
-
return { retrievalBlock, memoryBlock };
|
|
26
|
+
return { retrievalBlock, memoryBlock, citations };
|
|
9
27
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { normalizeCitations } from '../citations/index.js';
|
|
1
2
|
import { KnowledgeProvider } from '../KnowledgeProvider.js';
|
|
2
3
|
function latestUserMessage(ctx) {
|
|
3
4
|
for (let index = ctx.runState.messages.length - 1; index >= 0; index -= 1) {
|
|
@@ -51,7 +52,12 @@ export function buildAutoRetrieveProvider(provider, agent) {
|
|
|
51
52
|
...retrievalResults.map((result) => ({ text: result.text })),
|
|
52
53
|
];
|
|
53
54
|
const maxChars = provider.resolveConfig(merged).maxOutputTokens * 4;
|
|
54
|
-
|
|
55
|
+
const block = formatRetrievalBlock(combined, maxChars);
|
|
56
|
+
const citations = normalizeCitations(retrievalResults);
|
|
57
|
+
if (!block && citations.length === 0) {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
return { block, citations: citations.length > 0 ? citations : undefined };
|
|
55
61
|
},
|
|
56
62
|
};
|
|
57
63
|
}
|
package/dist/runtime/hostLoop.js
CHANGED
|
@@ -14,7 +14,7 @@ export async function hostLoop(options) {
|
|
|
14
14
|
if (!flow) {
|
|
15
15
|
throw new Error(`Active flow "${run.activeFlow}" not found on agent "${agent.id}"`);
|
|
16
16
|
}
|
|
17
|
-
return await runActiveFlow(flow, run, driver, ctx);
|
|
17
|
+
return await runActiveFlow(flow, run, driver, ctx, agent);
|
|
18
18
|
}
|
|
19
19
|
const alwaysRoute = agent.routing?.always === true;
|
|
20
20
|
if (shouldRunHostSelector(agent, run.activeFlow, alwaysRoute)) {
|
|
@@ -25,7 +25,7 @@ export async function hostLoop(options) {
|
|
|
25
25
|
alwaysRoute,
|
|
26
26
|
});
|
|
27
27
|
if (selection.kind === 'enterFlow') {
|
|
28
|
-
return await runActiveFlow(selection.flow, run, driver, ctx);
|
|
28
|
+
return await runActiveFlow(selection.flow, run, driver, ctx, agent);
|
|
29
29
|
}
|
|
30
30
|
if (selection.kind === 'route') {
|
|
31
31
|
ctx.emit({ type: 'handoff', targetAgent: selection.agentId, reason: selection.reason });
|
|
@@ -45,10 +45,10 @@ export async function hostLoop(options) {
|
|
|
45
45
|
throw error;
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
|
-
async function runActiveFlow(flow, run, driver, ctx) {
|
|
48
|
+
async function runActiveFlow(flow, run, driver, ctx, agent) {
|
|
49
49
|
incrementTurnCount(run);
|
|
50
50
|
assertWithinTurnLimit(run, ctx.limits);
|
|
51
|
-
const result = await runFlow(flow, run, driver, ctx);
|
|
51
|
+
const result = await runFlow(flow, run, driver, ctx, agent);
|
|
52
52
|
if (result.kind === 'handoff') {
|
|
53
53
|
return { kind: 'handoff', to: result.to, reason: result.reason };
|
|
54
54
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import type { TurnControl } from '../../types/channel.js';
|
|
1
2
|
import type { ToolCallRecord } from '../../types/session.js';
|
|
3
|
+
import type { SourceRef } from '../../types/voice.js';
|
|
2
4
|
import type { RunContext } from '../../types/run-context.js';
|
|
3
5
|
export interface PreTurnResult {
|
|
4
6
|
proceed: boolean;
|
|
@@ -9,6 +11,8 @@ export interface PostTurnResult {
|
|
|
9
11
|
proceed: boolean;
|
|
10
12
|
text: string;
|
|
11
13
|
blockedMessage?: string;
|
|
14
|
+
control?: TurnControl;
|
|
15
|
+
confidence?: number;
|
|
12
16
|
}
|
|
13
17
|
export declare function applyPreTurnPolicies(ctx: RunContext): Promise<PreTurnResult>;
|
|
14
|
-
export declare function applyPostTurnPolicies(ctx: RunContext, assistantOutput: string, toolCallsMade?: ToolCallRecord[]): Promise<PostTurnResult>;
|
|
18
|
+
export declare function applyPostTurnPolicies(ctx: RunContext, assistantOutput: string, toolCallsMade?: ToolCallRecord[], knowledgeCitations?: SourceRef[]): Promise<PostTurnResult>;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { appendConversationAudit } from '../../audit/record.js';
|
|
2
|
+
import { SAFE_DEGRADED_MESSAGE } from '../../flow/degrade.js';
|
|
1
3
|
import { runInputProcessors, runOutputProcessors } from '../../processors/ProcessorRunner.js';
|
|
2
4
|
function latestUserMessage(messages) {
|
|
3
5
|
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
@@ -8,6 +10,67 @@ function latestUserMessage(messages) {
|
|
|
8
10
|
}
|
|
9
11
|
return '';
|
|
10
12
|
}
|
|
13
|
+
function auditContext(ctx) {
|
|
14
|
+
return {
|
|
15
|
+
sessionId: ctx.session.id,
|
|
16
|
+
conversationId: ctx.session.conversationId,
|
|
17
|
+
userId: ctx.session.userId,
|
|
18
|
+
agentId: ctx.runState.activeAgentId,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function recordKnowledgeCitations(ctx, citations) {
|
|
22
|
+
for (const citation of citations) {
|
|
23
|
+
appendConversationAudit(ctx.session, auditContext(ctx), {
|
|
24
|
+
type: 'knowledge-citation',
|
|
25
|
+
sourceId: citation.id,
|
|
26
|
+
title: citation.title,
|
|
27
|
+
score: citation.score,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function recordValidationDecision(ctx, policyName, decision) {
|
|
32
|
+
if (decision.decision === 'block') {
|
|
33
|
+
appendConversationAudit(ctx.session, auditContext(ctx), {
|
|
34
|
+
type: 'safety-block',
|
|
35
|
+
moderator: policyName,
|
|
36
|
+
rationale: decision.rationale,
|
|
37
|
+
userFacingMessage: decision.userFacingMessage ?? decision.rationale,
|
|
38
|
+
});
|
|
39
|
+
appendConversationAudit(ctx.session, auditContext(ctx), {
|
|
40
|
+
type: 'validation',
|
|
41
|
+
aggregate: 'block',
|
|
42
|
+
confidence: decision.confidence,
|
|
43
|
+
rationale: decision.rationale,
|
|
44
|
+
moderator: policyName,
|
|
45
|
+
});
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (decision.decision === 'escalate') {
|
|
49
|
+
appendConversationAudit(ctx.session, auditContext(ctx), {
|
|
50
|
+
type: 'escalation',
|
|
51
|
+
reason: decision.escalationReason ?? 'low-confidence',
|
|
52
|
+
confidence: decision.confidence,
|
|
53
|
+
});
|
|
54
|
+
appendConversationAudit(ctx.session, auditContext(ctx), {
|
|
55
|
+
type: 'validation',
|
|
56
|
+
aggregate: 'block',
|
|
57
|
+
confidence: decision.confidence,
|
|
58
|
+
rationale: decision.rationale,
|
|
59
|
+
moderator: policyName,
|
|
60
|
+
});
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
appendConversationAudit(ctx.session, auditContext(ctx), {
|
|
64
|
+
type: 'validation',
|
|
65
|
+
aggregate: decision.decision === 'rewrite' ? 'rewrite' : 'continue',
|
|
66
|
+
confidence: decision.confidence,
|
|
67
|
+
rationale: decision.rationale ?? '',
|
|
68
|
+
moderator: policyName,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
function safeBlockedText(decision) {
|
|
72
|
+
return decision.userFacingMessage?.trim() || decision.rationale?.trim() || SAFE_DEGRADED_MESSAGE;
|
|
73
|
+
}
|
|
11
74
|
async function runRefinementPolicies(ctx, userMessage) {
|
|
12
75
|
const policies = ctx.refinementPolicies ?? [];
|
|
13
76
|
if (policies.length === 0) {
|
|
@@ -36,34 +99,56 @@ async function runRefinementPolicies(ctx, userMessage) {
|
|
|
36
99
|
}
|
|
37
100
|
return { proceed: true, userMessage: current };
|
|
38
101
|
}
|
|
39
|
-
async function runValidationPolicies(ctx, userMessage, assistantOutput, toolCallsMade) {
|
|
102
|
+
async function runValidationPolicies(ctx, userMessage, assistantOutput, toolCallsMade, knowledgeCitations) {
|
|
40
103
|
const policies = ctx.validationPolicies ?? [];
|
|
41
104
|
if (policies.length === 0) {
|
|
42
105
|
return { proceed: true, text: assistantOutput };
|
|
43
106
|
}
|
|
107
|
+
if (knowledgeCitations.length > 0) {
|
|
108
|
+
recordKnowledgeCitations(ctx, knowledgeCitations);
|
|
109
|
+
}
|
|
44
110
|
const sorted = [...policies].sort((a, b) => a.name.localeCompare(b.name));
|
|
45
111
|
let current = assistantOutput;
|
|
112
|
+
let lastConfidence;
|
|
46
113
|
for (const policy of sorted) {
|
|
47
114
|
const decision = await policy.validate({
|
|
48
115
|
session: ctx.session,
|
|
49
116
|
userMessage,
|
|
50
117
|
assistantOutput: current,
|
|
51
118
|
toolCallsMade,
|
|
52
|
-
knowledgeCitations
|
|
119
|
+
knowledgeCitations,
|
|
53
120
|
abortSignal: ctx.abortSignal,
|
|
54
121
|
});
|
|
122
|
+
recordValidationDecision(ctx, policy.name, decision);
|
|
123
|
+
lastConfidence = decision.confidence;
|
|
55
124
|
if (decision.decision === 'block') {
|
|
125
|
+
const safe = safeBlockedText(decision);
|
|
56
126
|
return {
|
|
57
127
|
proceed: false,
|
|
58
|
-
text:
|
|
59
|
-
blockedMessage:
|
|
128
|
+
text: safe,
|
|
129
|
+
blockedMessage: safe,
|
|
130
|
+
control: { type: 'recover', reason: decision.rationale },
|
|
131
|
+
confidence: decision.confidence,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
if (decision.decision === 'escalate') {
|
|
135
|
+
const safe = safeBlockedText(decision);
|
|
136
|
+
return {
|
|
137
|
+
proceed: false,
|
|
138
|
+
text: safe,
|
|
139
|
+
blockedMessage: safe,
|
|
140
|
+
control: { type: 'escalate', reason: decision.rationale },
|
|
141
|
+
confidence: decision.confidence,
|
|
60
142
|
};
|
|
61
143
|
}
|
|
62
144
|
if (decision.decision === 'rewrite') {
|
|
63
145
|
current = decision.rewrittenOutput;
|
|
64
146
|
}
|
|
65
147
|
}
|
|
66
|
-
|
|
148
|
+
if (ctx.session.metadata) {
|
|
149
|
+
ctx.session.metadata.lastValidationConfidence = lastConfidence;
|
|
150
|
+
}
|
|
151
|
+
return { proceed: true, text: current, confidence: lastConfidence };
|
|
67
152
|
}
|
|
68
153
|
export async function applyPreTurnPolicies(ctx) {
|
|
69
154
|
const userMessage = latestUserMessage(ctx.runState.messages);
|
|
@@ -92,7 +177,7 @@ export async function applyPreTurnPolicies(ctx) {
|
|
|
92
177
|
}
|
|
93
178
|
return runRefinementPolicies(ctx, latestUserMessage(ctx.runState.messages));
|
|
94
179
|
}
|
|
95
|
-
export async function applyPostTurnPolicies(ctx, assistantOutput, toolCallsMade = []) {
|
|
180
|
+
export async function applyPostTurnPolicies(ctx, assistantOutput, toolCallsMade = [], knowledgeCitations = []) {
|
|
96
181
|
const userMessage = latestUserMessage(ctx.runState.messages);
|
|
97
182
|
const processors = ctx.outputProcessors ?? [];
|
|
98
183
|
let current = assistantOutput;
|
|
@@ -117,7 +202,8 @@ export async function applyPostTurnPolicies(ctx, assistantOutput, toolCallsMade
|
|
|
117
202
|
}
|
|
118
203
|
current = outcome.text;
|
|
119
204
|
}
|
|
120
|
-
|
|
205
|
+
const citations = knowledgeCitations.length > 0 ? knowledgeCitations : (ctx.lastRetrievalCitations ?? []);
|
|
206
|
+
return runValidationPolicies(ctx, userMessage, current, toolCallsMade, citations);
|
|
121
207
|
}
|
|
122
208
|
function patchLatestUserMessage(messages, next) {
|
|
123
209
|
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
@@ -5,8 +5,8 @@ export function resolveAgentPolicies(agent) {
|
|
|
5
5
|
return {
|
|
6
6
|
inputProcessors: guardrails?.input ?? [],
|
|
7
7
|
outputProcessors: guardrails?.output ?? [],
|
|
8
|
-
refinementPolicies: [],
|
|
9
|
-
validationPolicies: [],
|
|
8
|
+
refinementPolicies: agent.refine ?? [],
|
|
9
|
+
validationPolicies: agent.validate ?? [],
|
|
10
10
|
limits: agent.limits,
|
|
11
11
|
enforcer: enforcementRules.length > 0 ? createToolEnforcer(enforcementRules) : undefined,
|
|
12
12
|
};
|
package/dist/runtime/select.d.ts
CHANGED
|
@@ -17,5 +17,7 @@ export interface SelectHostOptions {
|
|
|
17
17
|
run: RunState;
|
|
18
18
|
model: LanguageModel;
|
|
19
19
|
alwaysRoute?: boolean;
|
|
20
|
+
/** Flow names to exclude from enterFlow (e.g. the active flow during in-flow digression). */
|
|
21
|
+
excludeFlowNames?: string[];
|
|
20
22
|
}
|
|
21
23
|
export declare function selectHostTarget(options: SelectHostOptions): Promise<HostSelection>;
|
package/dist/runtime/select.js
CHANGED
|
@@ -19,8 +19,12 @@ export async function selectHostTarget(options) {
|
|
|
19
19
|
}
|
|
20
20
|
const completed = run.state.__completedFlows;
|
|
21
21
|
const completedFlows = Array.isArray(completed) ? completed : [];
|
|
22
|
-
const
|
|
22
|
+
const excluded = new Set(options.excludeFlowNames ?? []);
|
|
23
|
+
const availableFlows = flows.filter((flow) => !completedFlows.includes(flow.name) && !excluded.has(flow.name));
|
|
23
24
|
const agentRoutes = routes.filter((route) => route.agent);
|
|
25
|
+
if (availableFlows.length === 0 && agentRoutes.length === 0) {
|
|
26
|
+
return { kind: 'keep' };
|
|
27
|
+
}
|
|
24
28
|
if (availableFlows.length === 1 && agentRoutes.length === 0) {
|
|
25
29
|
return { kind: 'enterFlow', flow: availableFlows[0] };
|
|
26
30
|
}
|
|
@@ -4,6 +4,8 @@ import type { Flow } from './flow.js';
|
|
|
4
4
|
import type { Route, RoutingPolicy } from './route.js';
|
|
5
5
|
import type { Guardrails, Limits } from './guardrails.js';
|
|
6
6
|
import type { AgentKnowledge, AgentMemory } from './grounding.js';
|
|
7
|
+
import type { RefinementCapability } from '../capabilities/RefinementCapability.js';
|
|
8
|
+
import type { ValidationCapability } from '../capabilities/ValidationCapability.js';
|
|
7
9
|
import type { AnyTool } from './effectTool.js';
|
|
8
10
|
export type Instructions = string | AgentPrompt | ((ctx: {
|
|
9
11
|
state: Record<string, unknown>;
|
|
@@ -35,6 +37,10 @@ export interface AgentConfig {
|
|
|
35
37
|
memory?: AgentMemory;
|
|
36
38
|
guardrails?: Guardrails;
|
|
37
39
|
limits?: Limits;
|
|
40
|
+
/** Post-turn validation policies (grounding/confidence gate). Default: none. */
|
|
41
|
+
validate?: ValidationCapability[];
|
|
42
|
+
/** Pre-turn refinement policies. Default: none. */
|
|
43
|
+
refine?: RefinementCapability[];
|
|
38
44
|
experimental?: {
|
|
39
45
|
/** Flow reply nodes: silo flow-transition control tools + deterministic evaluator (ADR 0003 H1). Default OFF. */
|
|
40
46
|
outOfBandControl?: boolean;
|
package/dist/types/flow.d.ts
CHANGED
|
@@ -56,6 +56,11 @@ export interface ReplyNode {
|
|
|
56
56
|
model?: LanguageModel;
|
|
57
57
|
context?: ContextStrategy;
|
|
58
58
|
grounding?: NodeGrounding;
|
|
59
|
+
/** When set, routes to `onLow` when post-turn validation confidence is below `min`. */
|
|
60
|
+
confidenceGate?: {
|
|
61
|
+
min: number;
|
|
62
|
+
onLow: Transition;
|
|
63
|
+
};
|
|
59
64
|
next?: (turn: TurnResult, state: FlowState) => Transition | Promise<Transition>;
|
|
60
65
|
}
|
|
61
66
|
export interface CollectNode {
|
|
@@ -9,7 +9,7 @@ import type { ValidationCapability } from '../capabilities/ValidationCapability.
|
|
|
9
9
|
import type { Limits } from './guardrails.js';
|
|
10
10
|
import type { AnyTool } from './effectTool.js';
|
|
11
11
|
import type { Instructions } from './agentConfig.js';
|
|
12
|
-
import type { AgentKnowledgeOverrides } from './voice.js';
|
|
12
|
+
import type { AgentKnowledgeOverrides, SourceRef } from './voice.js';
|
|
13
13
|
export interface GatherScope {
|
|
14
14
|
query?: string;
|
|
15
15
|
knowledge?: AgentKnowledgeOverrides & {
|
|
@@ -33,8 +33,12 @@ export interface EffectToolExecutor {
|
|
|
33
33
|
/** Resolve a registered tool definition by name (used to read flags like `needsApproval`). */
|
|
34
34
|
getTool?(name: string): AnyTool | undefined;
|
|
35
35
|
}
|
|
36
|
+
export interface AutoRetrieveResult {
|
|
37
|
+
block?: string;
|
|
38
|
+
citations?: SourceRef[];
|
|
39
|
+
}
|
|
36
40
|
export interface AutoRetrieveProvider {
|
|
37
|
-
retrieve(ctx: RunContext, scope?: GatherScope): Promise<string | undefined>;
|
|
41
|
+
retrieve(ctx: RunContext, scope?: GatherScope): Promise<AutoRetrieveResult | string | undefined>;
|
|
38
42
|
}
|
|
39
43
|
export interface MemoryService {
|
|
40
44
|
preload?(ctx: RunContext, scope?: GatherScope): Promise<string | undefined>;
|
|
@@ -73,6 +77,8 @@ export interface RunContext {
|
|
|
73
77
|
* context. Reset to false on every `createRunContext` (i.e. every turn).
|
|
74
78
|
*/
|
|
75
79
|
turnInputConsumed?: boolean;
|
|
80
|
+
/** Citations from the latest gather-phase retrieval on this turn. */
|
|
81
|
+
lastRetrievalCitations?: SourceRef[];
|
|
76
82
|
/** Agent base layer (ADR 0001), set when entering a flow. `baseInstructions`
|
|
77
83
|
* is composed as a prefix into every node turn's system prompt (persona /
|
|
78
84
|
* safety / grounding floor); `globalTools` are safe tools made model-visible
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"url": "git+https://github.com/kuralle/kuralle-agents.git",
|
|
7
7
|
"directory": "packages/kuralle-core"
|
|
8
8
|
},
|
|
9
|
-
"version": "0.3.
|
|
9
|
+
"version": "0.3.18",
|
|
10
10
|
"description": "A framework for structured conversational AI agents",
|
|
11
11
|
"publishConfig": {
|
|
12
12
|
"access": "public"
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
"dotenv": "^16.4.0",
|
|
98
98
|
"typescript": "^5.3.0",
|
|
99
99
|
"zod": "^3.23.0",
|
|
100
|
-
"@kuralle-agents/realtime-audio": "0.3.
|
|
100
|
+
"@kuralle-agents/realtime-audio": "0.3.18"
|
|
101
101
|
},
|
|
102
102
|
"dependencies": {
|
|
103
103
|
"chrono-node": "^2.6.0",
|