@kuralle-agents/core 0.3.7 → 0.3.9
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/authoring/index.d.ts +1 -1
- package/dist/authoring/index.js +1 -1
- package/dist/authoring/nodes.d.ts +1 -1
- package/dist/authoring/nodes.js +1 -1
- package/dist/flow/classifyControl.js +7 -0
- package/dist/flow/confirmParse.d.ts +2 -0
- package/dist/flow/confirmParse.js +170 -0
- package/dist/flow/degradableErrors.d.ts +1 -0
- package/dist/flow/degradableErrors.js +5 -0
- package/dist/flow/degrade.d.ts +17 -0
- package/dist/flow/degrade.js +31 -0
- package/dist/flow/runFlow.js +80 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +2 -1
- package/dist/runtime/Runtime.js +17 -1
- package/dist/runtime/channels/TextDriver.js +5 -29
- package/dist/runtime/channels/VoiceDriver.js +15 -34
- package/dist/runtime/channels/executeModelTool.d.ts +29 -0
- package/dist/runtime/channels/executeModelTool.js +40 -0
- package/dist/runtime/channels/extractionTurn.js +3 -26
- package/dist/runtime/hostLoop.js +7 -0
- package/dist/tools/controlResults.d.ts +15 -0
- package/dist/tools/controlResults.js +18 -0
- package/dist/types/channel.d.ts +6 -0
- package/dist/types/flow.d.ts +17 -3
- package/dist/types/flow.js +13 -0
- package/package.json +2 -2
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { reply, collect, action, decide, defineFlow } from './nodes.js';
|
|
1
|
+
export { reply, collect, action, decide, confirmGate, defineFlow } from './nodes.js';
|
|
2
2
|
export { defineAgent, type AgentConfig, type Instructions } from './defineAgent.js';
|
package/dist/authoring/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { reply, collect, action, decide, defineFlow } from './nodes.js';
|
|
1
|
+
export { reply, collect, action, decide, confirmGate, defineFlow } from './nodes.js';
|
|
2
2
|
export { defineAgent } from './defineAgent.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { reply, collect, action, decide, defineFlow } from '../types/flow.js';
|
|
1
|
+
export { reply, collect, action, decide, confirmGate, defineFlow } from '../types/flow.js';
|
package/dist/authoring/nodes.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { reply, collect, action, decide, defineFlow } from '../types/flow.js';
|
|
1
|
+
export { reply, collect, action, decide, confirmGate, defineFlow } from '../types/flow.js';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isHandoffResult } from '../tools/handoff.js';
|
|
2
2
|
import { isFinalResult } from '../tools/final.js';
|
|
3
|
+
import { isEscalateResult, isRecoverResult } from '../tools/controlResults.js';
|
|
3
4
|
export function classifyControl(result) {
|
|
4
5
|
if (isHandoffResult(result)) {
|
|
5
6
|
return {
|
|
@@ -11,5 +12,11 @@ export function classifyControl(result) {
|
|
|
11
12
|
if (isFinalResult(result)) {
|
|
12
13
|
return { type: 'end', reason: result.text };
|
|
13
14
|
}
|
|
15
|
+
if (isEscalateResult(result)) {
|
|
16
|
+
return { type: 'escalate', reason: result.reason };
|
|
17
|
+
}
|
|
18
|
+
if (isRecoverResult(result)) {
|
|
19
|
+
return { type: 'recover', reason: result.reason };
|
|
20
|
+
}
|
|
14
21
|
return undefined;
|
|
15
22
|
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
const AFFIRM_TOKENS = new Set([
|
|
2
|
+
'yes',
|
|
3
|
+
'yeah',
|
|
4
|
+
'yep',
|
|
5
|
+
'yup',
|
|
6
|
+
'ya',
|
|
7
|
+
'sure',
|
|
8
|
+
'ok',
|
|
9
|
+
'okay',
|
|
10
|
+
'okey',
|
|
11
|
+
'k',
|
|
12
|
+
'confirm',
|
|
13
|
+
'confirmed',
|
|
14
|
+
'correct',
|
|
15
|
+
'right',
|
|
16
|
+
'proceed',
|
|
17
|
+
'do',
|
|
18
|
+
'done',
|
|
19
|
+
'yessir',
|
|
20
|
+
'affirmative',
|
|
21
|
+
'ow',
|
|
22
|
+
'ova',
|
|
23
|
+
'hari',
|
|
24
|
+
'hariyata',
|
|
25
|
+
'ehenam',
|
|
26
|
+
'aam',
|
|
27
|
+
'aama',
|
|
28
|
+
'sari',
|
|
29
|
+
'seri',
|
|
30
|
+
]);
|
|
31
|
+
const AFFIRM_PHRASES = [
|
|
32
|
+
'go ahead',
|
|
33
|
+
'do it',
|
|
34
|
+
'book it',
|
|
35
|
+
'place it',
|
|
36
|
+
'place the order',
|
|
37
|
+
'place my order',
|
|
38
|
+
'sounds good',
|
|
39
|
+
"that's right",
|
|
40
|
+
'thats right',
|
|
41
|
+
'that is correct',
|
|
42
|
+
'please do',
|
|
43
|
+
"let's do it",
|
|
44
|
+
'lets do it',
|
|
45
|
+
'go for it',
|
|
46
|
+
'confirm the order',
|
|
47
|
+
'yes please',
|
|
48
|
+
];
|
|
49
|
+
const AFFIRM_SCRIPT = ['ඔව්', 'හරි', 'හා', 'හරියට', 'ஆம்', 'சரி', 'ஆமா'];
|
|
50
|
+
const DECLINE_TOKENS = new Set([
|
|
51
|
+
'no',
|
|
52
|
+
'nope',
|
|
53
|
+
'nah',
|
|
54
|
+
'na',
|
|
55
|
+
'not',
|
|
56
|
+
'dont',
|
|
57
|
+
"don't",
|
|
58
|
+
'stop',
|
|
59
|
+
'cancel',
|
|
60
|
+
'wait',
|
|
61
|
+
'hold',
|
|
62
|
+
'change',
|
|
63
|
+
'edit',
|
|
64
|
+
'incorrect',
|
|
65
|
+
'wrong',
|
|
66
|
+
'nevermind',
|
|
67
|
+
'never',
|
|
68
|
+
'naha',
|
|
69
|
+
'nae',
|
|
70
|
+
'epa',
|
|
71
|
+
'epaa',
|
|
72
|
+
'wenas',
|
|
73
|
+
'illa',
|
|
74
|
+
'illai',
|
|
75
|
+
'vendam',
|
|
76
|
+
'vendaam',
|
|
77
|
+
'vena',
|
|
78
|
+
]);
|
|
79
|
+
const DECLINE_PHRASES = [
|
|
80
|
+
'not yet',
|
|
81
|
+
'hold on',
|
|
82
|
+
'no thanks',
|
|
83
|
+
'no thank you',
|
|
84
|
+
"don't",
|
|
85
|
+
'do not',
|
|
86
|
+
'change it',
|
|
87
|
+
'change the',
|
|
88
|
+
'let me change',
|
|
89
|
+
'something else',
|
|
90
|
+
'go back',
|
|
91
|
+
];
|
|
92
|
+
const DECLINE_SCRIPT = ['නැහැ', 'නෑ', 'එපා', 'වෙනස්', 'இல்லை', 'வேண்டாம்', 'வேற'];
|
|
93
|
+
const INTERROGATIVE_WORDS = [
|
|
94
|
+
'what',
|
|
95
|
+
'how',
|
|
96
|
+
'when',
|
|
97
|
+
'where',
|
|
98
|
+
'why',
|
|
99
|
+
'which',
|
|
100
|
+
'who',
|
|
101
|
+
'do you',
|
|
102
|
+
'can you',
|
|
103
|
+
'could you',
|
|
104
|
+
'is there',
|
|
105
|
+
'tell me',
|
|
106
|
+
'show me',
|
|
107
|
+
];
|
|
108
|
+
function normalize(raw) {
|
|
109
|
+
return raw.trim().toLowerCase().replace(/\s+/g, ' ');
|
|
110
|
+
}
|
|
111
|
+
function stripTokenPunctuation(token) {
|
|
112
|
+
return token.replace(/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu, '');
|
|
113
|
+
}
|
|
114
|
+
function tokenize(normalized) {
|
|
115
|
+
return normalized
|
|
116
|
+
.split(/\s+/)
|
|
117
|
+
.map(stripTokenPunctuation)
|
|
118
|
+
.filter((token) => token.length > 0);
|
|
119
|
+
}
|
|
120
|
+
function hasDeclineToken(tokens) {
|
|
121
|
+
return tokens.some((token) => DECLINE_TOKENS.has(token));
|
|
122
|
+
}
|
|
123
|
+
function hasDeclinePhrase(normalized) {
|
|
124
|
+
return DECLINE_PHRASES.some((phrase) => normalized.includes(phrase));
|
|
125
|
+
}
|
|
126
|
+
function hasDeclineScript(normalized, raw) {
|
|
127
|
+
return DECLINE_SCRIPT.some((fragment) => normalized.includes(fragment) || raw.includes(fragment));
|
|
128
|
+
}
|
|
129
|
+
function hasDecline(normalized, tokens, raw) {
|
|
130
|
+
return hasDeclineToken(tokens) || hasDeclinePhrase(normalized) || hasDeclineScript(normalized, raw);
|
|
131
|
+
}
|
|
132
|
+
function hasInterrogative(normalized) {
|
|
133
|
+
if (normalized.includes('?')) {
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
return INTERROGATIVE_WORDS.some((word) => {
|
|
137
|
+
const pattern = new RegExp(`(?:^|\\s)${word.replace(/\s+/g, '\\s+')}(?:\\s|$|[?.!,])`, 'i');
|
|
138
|
+
return pattern.test(normalized);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
function hasAffirmScript(normalized, raw) {
|
|
142
|
+
return AFFIRM_SCRIPT.some((fragment) => normalized.includes(fragment) || raw.includes(fragment));
|
|
143
|
+
}
|
|
144
|
+
function hasAffirmPhrase(normalized) {
|
|
145
|
+
return AFFIRM_PHRASES.some((phrase) => normalized.startsWith(phrase));
|
|
146
|
+
}
|
|
147
|
+
function hasAffirmToken(tokens) {
|
|
148
|
+
const head = tokens.slice(0, 3);
|
|
149
|
+
if (head.some((token) => AFFIRM_TOKENS.has(token))) {
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
return tokens.length === 1 && tokens[0] === 'y';
|
|
153
|
+
}
|
|
154
|
+
function hasAffirm(normalized, tokens, raw) {
|
|
155
|
+
return hasAffirmScript(normalized, raw) || hasAffirmPhrase(normalized) || hasAffirmToken(tokens);
|
|
156
|
+
}
|
|
157
|
+
export function parseConfirmation(raw) {
|
|
158
|
+
const normalized = normalize(raw);
|
|
159
|
+
const tokens = tokenize(normalized);
|
|
160
|
+
if (hasDecline(normalized, tokens, raw)) {
|
|
161
|
+
return 'decline';
|
|
162
|
+
}
|
|
163
|
+
if (hasInterrogative(normalized)) {
|
|
164
|
+
return 'ambiguous';
|
|
165
|
+
}
|
|
166
|
+
if (hasAffirm(normalized, tokens, raw)) {
|
|
167
|
+
return 'affirm';
|
|
168
|
+
}
|
|
169
|
+
return 'ambiguous';
|
|
170
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function isDegradableRuntimeError(error: unknown): boolean;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ChannelDriver } from '../types/channel.js';
|
|
2
|
+
import type { Flow, FlowNode } from '../types/flow.js';
|
|
3
|
+
import type { RunContext } from '../types/run-context.js';
|
|
4
|
+
import type { RunState } from '../runtime/durable/types.js';
|
|
5
|
+
type DegradedFlowResult = {
|
|
6
|
+
kind: 'ended';
|
|
7
|
+
reason: string;
|
|
8
|
+
} | {
|
|
9
|
+
kind: 'handoff';
|
|
10
|
+
to: string;
|
|
11
|
+
reason?: string;
|
|
12
|
+
};
|
|
13
|
+
export declare const SAFE_DEGRADED_MESSAGE = "I'm sorry \u2014 something went wrong on my side. Let me try to help you another way.";
|
|
14
|
+
export declare function findEscalateNode(registry: Map<string, FlowNode>): FlowNode | undefined;
|
|
15
|
+
export declare function appendSafeAssistantMessage(run: RunState, ctx: RunContext, text?: string): void;
|
|
16
|
+
export declare function degradeFlowError(flow: Flow, registry: Map<string, FlowNode>, run: RunState, driver: ChannelDriver, ctx: RunContext, dispatchNode: (node: FlowNode, run: RunState, driver: ChannelDriver, ctx: RunContext) => Promise<import('./normalizeTransition.js').NormalizedTransition>): Promise<DegradedFlowResult>;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { SuspendError } from '../runtime/durable/RunStore.js';
|
|
2
|
+
export const SAFE_DEGRADED_MESSAGE = "I'm sorry — something went wrong on my side. Let me try to help you another way.";
|
|
3
|
+
export function findEscalateNode(registry) {
|
|
4
|
+
return registry.get('escalate');
|
|
5
|
+
}
|
|
6
|
+
export function appendSafeAssistantMessage(run, ctx, text = SAFE_DEGRADED_MESSAGE) {
|
|
7
|
+
const message = { role: 'assistant', content: text };
|
|
8
|
+
run.messages = [...run.messages, message];
|
|
9
|
+
ctx.emit({ type: 'text-delta', text });
|
|
10
|
+
}
|
|
11
|
+
export async function degradeFlowError(flow, registry, run, driver, ctx, dispatchNode) {
|
|
12
|
+
appendSafeAssistantMessage(run, ctx);
|
|
13
|
+
const escalateNode = findEscalateNode(registry);
|
|
14
|
+
if (escalateNode) {
|
|
15
|
+
try {
|
|
16
|
+
const transition = await dispatchNode(escalateNode, run, driver, ctx);
|
|
17
|
+
if (transition.kind === 'escalate') {
|
|
18
|
+
await ctx.signal('__escalate', { meta: { reason: transition.reason } });
|
|
19
|
+
return { kind: 'handoff', to: 'human', reason: transition.reason };
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
if (error instanceof SuspendError) {
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
// Fall through to graceful end if the escalate node also fails.
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
ctx.emit({ type: 'flow-end', flow: flow.name, reason: 'error_degraded' });
|
|
30
|
+
return { kind: 'ended', reason: 'error_degraded' };
|
|
31
|
+
}
|
package/dist/flow/runFlow.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { parseConfirmation } from './confirmParse.js';
|
|
1
2
|
import { hasPendingUserInput } from '../runtime/channels/inputBuffer.js';
|
|
2
3
|
import { collectUntilComplete } from './collectUntilComplete.js';
|
|
3
4
|
import { isActionNode, isCollectNode, isDecideNode, isReplyNode, } from './nodeKinds.js';
|
|
@@ -6,7 +7,10 @@ import { reduceTransition } from './reduceTransition.js';
|
|
|
6
7
|
import { resolveReplyNode } from './nodeBuilders.js';
|
|
7
8
|
import { runNodeVerify, VerifyBlockedError } from './verify.js';
|
|
8
9
|
import { loadRecordedSteps } from '../runtime/durable/replay.js';
|
|
10
|
+
import { SuspendError } from '../runtime/durable/RunStore.js';
|
|
11
|
+
import { ToolApprovalDeniedError } from '../tools/effect/errors.js';
|
|
9
12
|
import { emitInteractiveOnNodeEnter } from './emitInteractive.js';
|
|
13
|
+
import { appendSafeAssistantMessage, degradeFlowError, findEscalateNode, } from './degrade.js';
|
|
10
14
|
export class FlowOscillationError extends Error {
|
|
11
15
|
constructor(from, to) {
|
|
12
16
|
super(`Flow oscillation blocked: ${from} -> ${to}`);
|
|
@@ -43,6 +47,38 @@ function appendUserMessage(run, input) {
|
|
|
43
47
|
const message = { role: 'user', content: input };
|
|
44
48
|
run.messages = [...run.messages, message];
|
|
45
49
|
}
|
|
50
|
+
function latestUserText(run) {
|
|
51
|
+
for (let i = run.messages.length - 1; i >= 0; i -= 1) {
|
|
52
|
+
const message = run.messages[i];
|
|
53
|
+
if (message?.role === 'user' && typeof message.content === 'string') {
|
|
54
|
+
return message.content;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return '';
|
|
58
|
+
}
|
|
59
|
+
async function dispatchConfirmGate(node, run, driver, ctx) {
|
|
60
|
+
const gate = node.confirmGate;
|
|
61
|
+
if (!hasPendingUserInput(ctx.session) && ctx.turnInputConsumed) {
|
|
62
|
+
return { kind: 'stay' };
|
|
63
|
+
}
|
|
64
|
+
let input = '';
|
|
65
|
+
if (hasPendingUserInput(ctx.session)) {
|
|
66
|
+
const signal = await driver.awaitUser(ctx);
|
|
67
|
+
input = signal.input;
|
|
68
|
+
appendUserMessage(run, signal.input);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
input = latestUserText(run);
|
|
72
|
+
}
|
|
73
|
+
ctx.turnInputConsumed = true;
|
|
74
|
+
const verdict = parseConfirmation(input);
|
|
75
|
+
const branch = verdict === 'affirm'
|
|
76
|
+
? gate.onConfirm
|
|
77
|
+
: verdict === 'decline'
|
|
78
|
+
? gate.onDecline
|
|
79
|
+
: (gate.onAmbiguous ?? 'stay');
|
|
80
|
+
return normalizeTransition(branch);
|
|
81
|
+
}
|
|
46
82
|
function appendAssistantMessage(run, text) {
|
|
47
83
|
if (!text.trim()) {
|
|
48
84
|
return;
|
|
@@ -58,9 +94,15 @@ async function dispatchNode(node, run, driver, ctx) {
|
|
|
58
94
|
return collectUntilComplete(node, run, driver, ctx);
|
|
59
95
|
}
|
|
60
96
|
if (isDecideNode(node)) {
|
|
97
|
+
if (node.confirmGate) {
|
|
98
|
+
return dispatchConfirmGate(node, run, driver, ctx);
|
|
99
|
+
}
|
|
61
100
|
if (!driver.runStructured) {
|
|
62
101
|
throw new Error('ChannelDriver.runStructured is required for decide nodes');
|
|
63
102
|
}
|
|
103
|
+
if (!node.schema || !node.decide) {
|
|
104
|
+
throw new Error(`decide node "${node.id}" requires schema and decide`);
|
|
105
|
+
}
|
|
64
106
|
// An interactive choice node (withChoices) reached when the turn's input was
|
|
65
107
|
// already consumed by a prior node: its choices were presented on node-enter,
|
|
66
108
|
// so wait for the user to actually pick rather than auto-deciding on stale
|
|
@@ -98,6 +140,12 @@ async function dispatchNode(node, run, driver, ctx) {
|
|
|
98
140
|
if (turn.control?.type === 'end') {
|
|
99
141
|
return { kind: 'end', reason: turn.control.reason };
|
|
100
142
|
}
|
|
143
|
+
if (turn.control?.type === 'escalate') {
|
|
144
|
+
return { kind: 'escalate', reason: turn.control.reason };
|
|
145
|
+
}
|
|
146
|
+
if (turn.control?.type === 'recover') {
|
|
147
|
+
return { kind: 'end', reason: turn.control.reason ?? 'error_degraded' };
|
|
148
|
+
}
|
|
101
149
|
if (node.next) {
|
|
102
150
|
return normalizeTransition(await node.next(turn, run.state));
|
|
103
151
|
}
|
|
@@ -123,7 +171,18 @@ export async function runFlow(flow, run, driver, ctx) {
|
|
|
123
171
|
const edgeCounts = new Map();
|
|
124
172
|
const maxOscillations = flow.maxOscillations ?? 2;
|
|
125
173
|
for (;;) {
|
|
126
|
-
|
|
174
|
+
let transition;
|
|
175
|
+
try {
|
|
176
|
+
transition = await dispatchNode(node, run, driver, ctx);
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
if (error instanceof SuspendError || error instanceof ToolApprovalDeniedError) {
|
|
180
|
+
throw error;
|
|
181
|
+
}
|
|
182
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
183
|
+
ctx.emit({ type: 'error', error: message });
|
|
184
|
+
return degradeFlowError(flow, registry, run, driver, ctx, dispatchNode);
|
|
185
|
+
}
|
|
127
186
|
if (transition.kind === 'end') {
|
|
128
187
|
ctx.emit({ type: 'flow-end', flow: flow.name, reason: transition.reason });
|
|
129
188
|
return { kind: 'ended', reason: transition.reason };
|
|
@@ -168,7 +227,26 @@ export async function runFlow(flow, run, driver, ctx) {
|
|
|
168
227
|
const oscillation = bumpOscillation(edgeCounts, node.id, target.id);
|
|
169
228
|
if (oscillation > maxOscillations) {
|
|
170
229
|
ctx.emit({ type: 'error', error: `Flow oscillation blocked: ${node.id} -> ${target.id}` });
|
|
171
|
-
|
|
230
|
+
const escalateNode = findEscalateNode(registry);
|
|
231
|
+
if (escalateNode) {
|
|
232
|
+
appendSafeAssistantMessage(run, ctx);
|
|
233
|
+
await reduceTransition({
|
|
234
|
+
fromNodeId: node.id,
|
|
235
|
+
toNode: escalateNode,
|
|
236
|
+
run,
|
|
237
|
+
flow,
|
|
238
|
+
model: ctx.model,
|
|
239
|
+
data: transition.data,
|
|
240
|
+
emit: ctx.emit,
|
|
241
|
+
abortSignal: ctx.abortSignal,
|
|
242
|
+
});
|
|
243
|
+
await ctx.runStore.putRunState(run);
|
|
244
|
+
node = escalateNode;
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
appendSafeAssistantMessage(run, ctx);
|
|
248
|
+
ctx.emit({ type: 'flow-end', flow: flow.name, reason: 'error_degraded' });
|
|
249
|
+
return { kind: 'ended', reason: 'error_degraded' };
|
|
172
250
|
}
|
|
173
251
|
await reduceTransition({
|
|
174
252
|
fromNodeId: node.id,
|
package/dist/index.d.ts
CHANGED
|
@@ -65,13 +65,15 @@ export type { AuditConfig, AuditEntryBase, AuditEntryType, AuditListOptions, Aud
|
|
|
65
65
|
export type { RealtimeAudioClient, RealtimeSessionConfig, RealtimeAudioConfig, RealtimeToolResponse, RealtimeEventMap, RealtimeSessionHandle, } from './realtime/index.js';
|
|
66
66
|
export type { Hooks } from './types/hooks.js';
|
|
67
67
|
export type { HarnessHooks } from './types/runtime.js';
|
|
68
|
-
export { defineAgent, defineFlow, reply, collect, action, decide, } from './authoring/index.js';
|
|
68
|
+
export { defineAgent, defineFlow, reply, collect, action, decide, confirmGate, } from './authoring/index.js';
|
|
69
69
|
export { defineTool } from './types/effectTool.js';
|
|
70
70
|
export { buildToolSet, toolToAiSdk, ToolApprovalDeniedError } from './tools/effect/index.js';
|
|
71
71
|
export type { Tool as EffectTool } from './types/effectTool.js';
|
|
72
72
|
export type { AgentRoute } from './types/processors.js';
|
|
73
73
|
export type { AgentConfig, Instructions } from './types/agentConfig.js';
|
|
74
|
-
export type { Flow, FlowNode, Transition, CollectNode, DecideNode } from './types/flow.js';
|
|
74
|
+
export type { Flow, FlowNode, Transition, CollectNode, DecideNode, ConfirmGate } from './types/flow.js';
|
|
75
|
+
export { parseConfirmation } from './flow/confirmParse.js';
|
|
76
|
+
export type { ConfirmVerdict } from './flow/confirmParse.js';
|
|
75
77
|
export type { Route } from './types/route.js';
|
|
76
78
|
export type { TurnHandle } from './types/stream.js';
|
|
77
79
|
export type { HarnessStreamPart } from './types/stream.js';
|
package/dist/index.js
CHANGED
|
@@ -36,7 +36,8 @@ export { handoffFilters, removeToolHistory, keepRecentMessages, removeKeys, comp
|
|
|
36
36
|
export { DefaultToolExecutor, DefaultConversationState, DefaultConversationEventLog, DefaultAgentStateController, createFoundation, } from './foundation/index.js';
|
|
37
37
|
export { CapabilityHost, TriageCapability, ExtractionCapability, HandoffCapability, GuardrailCapability, AutoRetrieveCapability, PassThroughRefinement, PassThroughValidation, toGeminiDeclarations, toAISDKTools, } from './capabilities/index.js';
|
|
38
38
|
export { filterAuditEntries } from './audit/index.js';
|
|
39
|
-
export { defineAgent, defineFlow, reply, collect, action, decide, } from './authoring/index.js';
|
|
39
|
+
export { defineAgent, defineFlow, reply, collect, action, decide, confirmGate, } from './authoring/index.js';
|
|
40
40
|
export { defineTool } from './types/effectTool.js';
|
|
41
41
|
export { buildToolSet, toolToAiSdk, ToolApprovalDeniedError } from './tools/effect/index.js';
|
|
42
|
+
export { parseConfirmation } from './flow/confirmParse.js';
|
|
42
43
|
export { createRuntime, Runtime, } from './runtime/Runtime.js';
|
package/dist/runtime/Runtime.js
CHANGED
|
@@ -6,6 +6,8 @@ import { createRunContext } from './ctx.js';
|
|
|
6
6
|
import { createEventBus, createTurnHandle } from '../events/TurnHandle.js';
|
|
7
7
|
import { CoreToolExecutor } from '../tools/effect/index.js';
|
|
8
8
|
import { hostLoop } from './hostLoop.js';
|
|
9
|
+
import { isDegradableRuntimeError } from '../flow/degradableErrors.js';
|
|
10
|
+
import { SAFE_DEGRADED_MESSAGE } from '../flow/degrade.js';
|
|
9
11
|
import { openRun } from './openRun.js';
|
|
10
12
|
import { closeRun } from './closeRun.js';
|
|
11
13
|
import { SessionRunStore } from './durable/SessionRunStore.js';
|
|
@@ -163,7 +165,21 @@ export class Runtime {
|
|
|
163
165
|
}
|
|
164
166
|
catch (error) {
|
|
165
167
|
await this.hooks?.onError?.(runCtx, error);
|
|
166
|
-
|
|
168
|
+
if (isDegradableRuntimeError(error)) {
|
|
169
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
170
|
+
emit({ type: 'error', error: message });
|
|
171
|
+
emit({ type: 'text-delta', text: SAFE_DEGRADED_MESSAGE });
|
|
172
|
+
runCtx.runState.messages = [
|
|
173
|
+
...runCtx.runState.messages,
|
|
174
|
+
{ role: 'assistant', content: SAFE_DEGRADED_MESSAGE },
|
|
175
|
+
];
|
|
176
|
+
await runCtx.runStore.putRunState(runCtx.runState);
|
|
177
|
+
terminalOutcome = 'unresolved';
|
|
178
|
+
loopResult = { kind: 'ended', reason: 'error_degraded' };
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
throw error;
|
|
182
|
+
}
|
|
167
183
|
}
|
|
168
184
|
finally {
|
|
169
185
|
this.activeTurnAborts.delete(sessionId);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { streamText, generateObject } from 'ai';
|
|
2
2
|
import { buildNodePrompt, resolveInstructions, composeSystem } from '../../flow/nodeBuilders.js';
|
|
3
3
|
import { buildToolSet } from '../../tools/effect/index.js';
|
|
4
|
-
import {
|
|
4
|
+
import { executeModelToolCall, toolResultMessage } from './executeModelTool.js';
|
|
5
5
|
import { consumePendingUserInput } from './inputBuffer.js';
|
|
6
6
|
import { runSilentExtraction } from './extractionTurn.js';
|
|
7
7
|
import { applyPreTurnPolicies, applyPostTurnPolicies } from '../policies/agentTurn.js';
|
|
@@ -70,21 +70,7 @@ export class TextDriver {
|
|
|
70
70
|
args: call.input,
|
|
71
71
|
toolCallId: call.toolCallId,
|
|
72
72
|
});
|
|
73
|
-
const
|
|
74
|
-
const toolResult = await ctx.tool(call.toolName, call.input, {
|
|
75
|
-
toolCallId: call.toolCallId,
|
|
76
|
-
...(localTool && {
|
|
77
|
-
def: localTool,
|
|
78
|
-
toolCtx: {
|
|
79
|
-
session: ctx.session,
|
|
80
|
-
runState: ctx.runState,
|
|
81
|
-
tool: ctx.tool.bind(ctx),
|
|
82
|
-
now: ctx.now.bind(ctx),
|
|
83
|
-
uuid: ctx.uuid.bind(ctx),
|
|
84
|
-
emit: ctx.emit.bind(ctx),
|
|
85
|
-
},
|
|
86
|
-
}),
|
|
87
|
-
});
|
|
73
|
+
const { result: toolResult, control, failed } = await executeModelToolCall(ctx, { toolName: call.toolName, input: call.input, toolCallId: call.toolCallId }, node.localTools);
|
|
88
74
|
out.toolResults.push({
|
|
89
75
|
name: call.toolName,
|
|
90
76
|
args: call.input,
|
|
@@ -96,27 +82,17 @@ export class TextDriver {
|
|
|
96
82
|
toolName: call.toolName,
|
|
97
83
|
args: call.input,
|
|
98
84
|
result: toolResult,
|
|
99
|
-
success:
|
|
85
|
+
success: !failed,
|
|
100
86
|
timestamp: Date.now(),
|
|
101
87
|
});
|
|
102
|
-
out.control ??=
|
|
88
|
+
out.control ??= control;
|
|
103
89
|
ctx.emit({
|
|
104
90
|
type: 'tool-result',
|
|
105
91
|
toolName: call.toolName,
|
|
106
92
|
result: toolResult,
|
|
107
93
|
toolCallId: call.toolCallId,
|
|
108
94
|
});
|
|
109
|
-
messages.push({
|
|
110
|
-
role: 'tool',
|
|
111
|
-
content: [
|
|
112
|
-
{
|
|
113
|
-
type: 'tool-result',
|
|
114
|
-
toolCallId: call.toolCallId,
|
|
115
|
-
toolName: call.toolName,
|
|
116
|
-
output: { type: 'json', value: toolResult },
|
|
117
|
-
},
|
|
118
|
-
],
|
|
119
|
-
});
|
|
95
|
+
messages.push(toolResultMessage({ toolName: call.toolName, input: call.input, toolCallId: call.toolCallId }, toolResult));
|
|
120
96
|
}
|
|
121
97
|
}
|
|
122
98
|
const postTurn = await applyPostTurnPolicies(ctx, draftText, toolCallsMade);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { generateObject } from 'ai';
|
|
2
2
|
import { runSilentExtraction } from './extractionTurn.js';
|
|
3
3
|
import { buildNodePrompt, resolveInstructions, composeSystem } from '../../flow/nodeBuilders.js';
|
|
4
|
-
import {
|
|
4
|
+
import { executeModelToolCall } from './executeModelTool.js';
|
|
5
5
|
import { applyPreTurnPolicies, applyPostTurnPolicies } from '../policies/agentTurn.js';
|
|
6
6
|
import { resolveMaxSteps } from '../policies/limits.js';
|
|
7
7
|
import { appendGatherBlocks, runGatherPhase } from '../grounding/index.js';
|
|
@@ -128,39 +128,20 @@ export class VoiceDriver {
|
|
|
128
128
|
};
|
|
129
129
|
const onToolCall = (id, name, args) => {
|
|
130
130
|
void (async () => {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
},
|
|
146
|
-
}),
|
|
147
|
-
});
|
|
148
|
-
out.toolResults.push({ name, args, result: toolResult, toolCallId: id });
|
|
149
|
-
toolCallsMade.push({
|
|
150
|
-
toolCallId: id,
|
|
151
|
-
toolName: name,
|
|
152
|
-
args,
|
|
153
|
-
result: toolResult,
|
|
154
|
-
success: true,
|
|
155
|
-
timestamp: Date.now(),
|
|
156
|
-
});
|
|
157
|
-
out.control ??= classifyControl(toolResult);
|
|
158
|
-
ctx.emit({ type: 'tool-result', toolName: name, result: toolResult, toolCallId: id });
|
|
159
|
-
this.client.sendToolResponse([{ id, name, output: toolResult }]);
|
|
160
|
-
}
|
|
161
|
-
catch (error) {
|
|
162
|
-
fail(error);
|
|
163
|
-
}
|
|
131
|
+
ctx.emit({ type: 'tool-call', toolName: name, args, toolCallId: id });
|
|
132
|
+
const { result: toolResult, control, failed } = await executeModelToolCall(ctx, { toolName: name, input: args, toolCallId: id }, localTools);
|
|
133
|
+
out.toolResults.push({ name, args, result: toolResult, toolCallId: id });
|
|
134
|
+
toolCallsMade.push({
|
|
135
|
+
toolCallId: id,
|
|
136
|
+
toolName: name,
|
|
137
|
+
args,
|
|
138
|
+
result: toolResult,
|
|
139
|
+
success: !failed,
|
|
140
|
+
timestamp: Date.now(),
|
|
141
|
+
});
|
|
142
|
+
out.control ??= control;
|
|
143
|
+
ctx.emit({ type: 'tool-result', toolName: name, result: toolResult, toolCallId: id });
|
|
144
|
+
this.client.sendToolResponse([{ id, name, output: toolResult }]);
|
|
164
145
|
})();
|
|
165
146
|
};
|
|
166
147
|
const onTurnComplete = () => {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { JSONValue } from 'ai';
|
|
2
|
+
import type { TurnControl } from '../../types/channel.js';
|
|
3
|
+
import type { RunContext } from '../../types/run-context.js';
|
|
4
|
+
import type { AnyTool } from '../../types/effectTool.js';
|
|
5
|
+
export interface ModelToolCall {
|
|
6
|
+
toolName: string;
|
|
7
|
+
input: unknown;
|
|
8
|
+
toolCallId: string;
|
|
9
|
+
}
|
|
10
|
+
export interface ModelToolCallOutcome {
|
|
11
|
+
result: unknown;
|
|
12
|
+
control?: TurnControl;
|
|
13
|
+
failed: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function executeModelToolCall(ctx: RunContext, call: ModelToolCall, localTools?: Record<string, AnyTool>): Promise<ModelToolCallOutcome>;
|
|
16
|
+
export declare function toolResultMessage(call: ModelToolCall, result: unknown): {
|
|
17
|
+
role: 'tool';
|
|
18
|
+
content: [
|
|
19
|
+
{
|
|
20
|
+
type: 'tool-result';
|
|
21
|
+
toolCallId: string;
|
|
22
|
+
toolName: string;
|
|
23
|
+
output: {
|
|
24
|
+
type: 'json';
|
|
25
|
+
value: JSONValue;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
];
|
|
29
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { classifyControl } from '../../flow/classifyControl.js';
|
|
2
|
+
import { toolErrorResult } from '../../tools/controlResults.js';
|
|
3
|
+
export async function executeModelToolCall(ctx, call, localTools) {
|
|
4
|
+
try {
|
|
5
|
+
const localTool = localTools?.[call.toolName];
|
|
6
|
+
const toolResult = await ctx.tool(call.toolName, call.input, {
|
|
7
|
+
toolCallId: call.toolCallId,
|
|
8
|
+
...(localTool && {
|
|
9
|
+
def: localTool,
|
|
10
|
+
toolCtx: {
|
|
11
|
+
session: ctx.session,
|
|
12
|
+
runState: ctx.runState,
|
|
13
|
+
tool: ctx.tool.bind(ctx),
|
|
14
|
+
now: ctx.now.bind(ctx),
|
|
15
|
+
uuid: ctx.uuid.bind(ctx),
|
|
16
|
+
emit: ctx.emit.bind(ctx),
|
|
17
|
+
},
|
|
18
|
+
}),
|
|
19
|
+
});
|
|
20
|
+
return { result: toolResult, control: classifyControl(toolResult), failed: false };
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
24
|
+
ctx.emit({ type: 'error', error: message });
|
|
25
|
+
return { result: toolErrorResult(error), failed: true };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function toolResultMessage(call, result) {
|
|
29
|
+
return {
|
|
30
|
+
role: 'tool',
|
|
31
|
+
content: [
|
|
32
|
+
{
|
|
33
|
+
type: 'tool-result',
|
|
34
|
+
toolCallId: call.toolCallId,
|
|
35
|
+
toolName: call.toolName,
|
|
36
|
+
output: { type: 'json', value: result },
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { streamText } from 'ai';
|
|
2
|
+
import { executeModelToolCall, toolResultMessage } from './executeModelTool.js';
|
|
2
3
|
import { buildToolSet } from '../../tools/effect/index.js';
|
|
3
4
|
import { buildNodePrompt, composeSystem } from '../../flow/nodeBuilders.js';
|
|
4
5
|
/**
|
|
@@ -37,38 +38,14 @@ export async function runSilentExtraction(node, ctx, model, maxSteps) {
|
|
|
37
38
|
}
|
|
38
39
|
const toolCalls = await result.toolCalls;
|
|
39
40
|
for (const call of toolCalls) {
|
|
40
|
-
const
|
|
41
|
-
const toolResult = await ctx.tool(call.toolName, call.input, {
|
|
42
|
-
toolCallId: call.toolCallId,
|
|
43
|
-
...(localTool && {
|
|
44
|
-
def: localTool,
|
|
45
|
-
toolCtx: {
|
|
46
|
-
session: ctx.session,
|
|
47
|
-
runState: ctx.runState,
|
|
48
|
-
tool: ctx.tool.bind(ctx),
|
|
49
|
-
now: ctx.now.bind(ctx),
|
|
50
|
-
uuid: ctx.uuid.bind(ctx),
|
|
51
|
-
emit: ctx.emit.bind(ctx),
|
|
52
|
-
},
|
|
53
|
-
}),
|
|
54
|
-
});
|
|
41
|
+
const { result: toolResult } = await executeModelToolCall(ctx, { toolName: call.toolName, input: call.input, toolCallId: call.toolCallId }, node.localTools);
|
|
55
42
|
out.toolResults.push({
|
|
56
43
|
name: call.toolName,
|
|
57
44
|
args: call.input,
|
|
58
45
|
result: toolResult,
|
|
59
46
|
toolCallId: call.toolCallId,
|
|
60
47
|
});
|
|
61
|
-
messages.push({
|
|
62
|
-
role: 'tool',
|
|
63
|
-
content: [
|
|
64
|
-
{
|
|
65
|
-
type: 'tool-result',
|
|
66
|
-
toolCallId: call.toolCallId,
|
|
67
|
-
toolName: call.toolName,
|
|
68
|
-
output: { type: 'json', value: toolResult },
|
|
69
|
-
},
|
|
70
|
-
],
|
|
71
|
-
});
|
|
48
|
+
messages.push(toolResultMessage({ toolName: call.toolName, input: call.input, toolCallId: call.toolCallId }, toolResult));
|
|
72
49
|
}
|
|
73
50
|
}
|
|
74
51
|
return out;
|
package/dist/runtime/hostLoop.js
CHANGED
|
@@ -87,6 +87,13 @@ async function runFreeConversation(agent, run, driver, ctx) {
|
|
|
87
87
|
if (turn.control?.type === 'end') {
|
|
88
88
|
return { kind: 'ended', reason: turn.control.reason };
|
|
89
89
|
}
|
|
90
|
+
if (turn.control?.type === 'escalate') {
|
|
91
|
+
ctx.emit({ type: 'handoff', targetAgent: 'human', reason: turn.control.reason });
|
|
92
|
+
return { kind: 'handoff', to: 'human', reason: turn.control.reason };
|
|
93
|
+
}
|
|
94
|
+
if (turn.control?.type === 'recover') {
|
|
95
|
+
return { kind: 'ended', reason: turn.control.reason ?? 'error_degraded' };
|
|
96
|
+
}
|
|
90
97
|
return { kind: 'turnComplete' };
|
|
91
98
|
}
|
|
92
99
|
function findFlowByName(agent, flowName) {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface EscalateResult {
|
|
2
|
+
__escalate: true;
|
|
3
|
+
reason: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function isEscalateResult(result: unknown): result is EscalateResult;
|
|
6
|
+
export interface RecoverResult {
|
|
7
|
+
__recover: true;
|
|
8
|
+
reason?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function isRecoverResult(result: unknown): result is RecoverResult;
|
|
11
|
+
/** Standard tool-result shape when `ctx.tool` fails in a model-initiated call. */
|
|
12
|
+
export declare function toolErrorResult(error: unknown): {
|
|
13
|
+
error: true;
|
|
14
|
+
message: string;
|
|
15
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function isEscalateResult(result) {
|
|
2
|
+
return (typeof result === 'object' &&
|
|
3
|
+
result !== null &&
|
|
4
|
+
'__escalate' in result &&
|
|
5
|
+
result.__escalate === true &&
|
|
6
|
+
typeof result.reason === 'string');
|
|
7
|
+
}
|
|
8
|
+
export function isRecoverResult(result) {
|
|
9
|
+
return (typeof result === 'object' &&
|
|
10
|
+
result !== null &&
|
|
11
|
+
'__recover' in result &&
|
|
12
|
+
result.__recover === true);
|
|
13
|
+
}
|
|
14
|
+
/** Standard tool-result shape when `ctx.tool` fails in a model-initiated call. */
|
|
15
|
+
export function toolErrorResult(error) {
|
|
16
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
17
|
+
return { error: true, message };
|
|
18
|
+
}
|
package/dist/types/channel.d.ts
CHANGED
package/dist/types/flow.d.ts
CHANGED
|
@@ -61,16 +61,30 @@ export interface ActionNode {
|
|
|
61
61
|
outputSchema?: StandardSchemaV1;
|
|
62
62
|
run: (state: FlowState, ctx: ActionContext) => Transition | Promise<Transition>;
|
|
63
63
|
}
|
|
64
|
+
export interface ConfirmGate {
|
|
65
|
+
onConfirm: Transition;
|
|
66
|
+
onDecline: Transition;
|
|
67
|
+
onAmbiguous?: Transition;
|
|
68
|
+
}
|
|
64
69
|
export interface DecideNode {
|
|
65
70
|
kind: 'decide';
|
|
66
71
|
id: string;
|
|
67
72
|
instructions: Instructions;
|
|
68
|
-
schema
|
|
73
|
+
schema?: StandardSchemaV1;
|
|
69
74
|
choices?: ChoiceOption[];
|
|
70
|
-
|
|
75
|
+
confirmGate?: ConfirmGate;
|
|
76
|
+
decide?: (data: unknown, state: FlowState) => Transition | Promise<Transition>;
|
|
71
77
|
}
|
|
72
78
|
export declare function reply(node: Omit<ReplyNode, 'kind'>): ReplyNode;
|
|
73
79
|
export declare function collect(node: Omit<CollectNode, 'kind'>): CollectNode;
|
|
74
80
|
export declare function action(node: Omit<ActionNode, 'kind'>): ActionNode;
|
|
75
|
-
export declare function decide(node: Omit<DecideNode, 'kind'>): DecideNode;
|
|
81
|
+
export declare function decide(node: Omit<DecideNode, 'kind' | 'confirmGate'> & Required<Pick<DecideNode, 'schema' | 'decide'>>): DecideNode;
|
|
82
|
+
export declare function confirmGate(node: {
|
|
83
|
+
id: string;
|
|
84
|
+
instructions: Instructions;
|
|
85
|
+
onConfirm: Transition;
|
|
86
|
+
onDecline: Transition;
|
|
87
|
+
onAmbiguous?: Transition;
|
|
88
|
+
choices?: ChoiceOption[];
|
|
89
|
+
}): DecideNode;
|
|
76
90
|
export declare function defineFlow(flow: Flow): Flow;
|
package/dist/types/flow.js
CHANGED
|
@@ -10,6 +10,19 @@ export function action(node) {
|
|
|
10
10
|
export function decide(node) {
|
|
11
11
|
return { kind: 'decide', ...node };
|
|
12
12
|
}
|
|
13
|
+
export function confirmGate(node) {
|
|
14
|
+
return {
|
|
15
|
+
kind: 'decide',
|
|
16
|
+
id: node.id,
|
|
17
|
+
instructions: node.instructions,
|
|
18
|
+
choices: node.choices,
|
|
19
|
+
confirmGate: {
|
|
20
|
+
onConfirm: node.onConfirm,
|
|
21
|
+
onDecline: node.onDecline,
|
|
22
|
+
onAmbiguous: node.onAmbiguous,
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
13
26
|
export function defineFlow(flow) {
|
|
14
27
|
return flow;
|
|
15
28
|
}
|
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.9",
|
|
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.9"
|
|
101
101
|
},
|
|
102
102
|
"dependencies": {
|
|
103
103
|
"chrono-node": "^2.6.0",
|