@kuralle-agents/core 0.3.1 → 0.3.3
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.
|
@@ -19,6 +19,22 @@ export async function collectUntilComplete(node, run, driver, ctx) {
|
|
|
19
19
|
const data = projectCollectData(node, run.state);
|
|
20
20
|
return normalizeTransition(await node.onComplete(data, run.state));
|
|
21
21
|
}
|
|
22
|
+
// Acquire THIS turn's fresh input before extracting. If input is pending,
|
|
23
|
+
// consume it so extraction reads the user's actual reply. If nothing is
|
|
24
|
+
// pending AND the turn's input was already consumed by a prior node, pause:
|
|
25
|
+
// the prompt was presented on node-enter, so await the next turn rather than
|
|
26
|
+
// running extraction over stale history (which makes the model fabricate
|
|
27
|
+
// required fields). On the run's first input-node the turn's input is in
|
|
28
|
+
// `messages` with nothing pending and `turnInputConsumed` false, so we fall
|
|
29
|
+
// through and extract it.
|
|
30
|
+
if (hasPendingUserInput(ctx.session)) {
|
|
31
|
+
const signal = await driver.awaitUser(ctx);
|
|
32
|
+
appendUserMessage(run, signal.input);
|
|
33
|
+
}
|
|
34
|
+
else if (ctx.turnInputConsumed) {
|
|
35
|
+
return { kind: 'stay' };
|
|
36
|
+
}
|
|
37
|
+
ctx.turnInputConsumed = true;
|
|
22
38
|
const turns = incrementCollectTurns(run.state, node.id);
|
|
23
39
|
const maxTurns = node.maxTurns ?? 10;
|
|
24
40
|
if (turns > maxTurns) {
|
|
@@ -33,14 +49,6 @@ export async function collectUntilComplete(node, run, driver, ctx) {
|
|
|
33
49
|
const turn = await driver.runAgentTurn(resolved, ctx);
|
|
34
50
|
mergeExtractionFromTurn(node, run, turn);
|
|
35
51
|
appendAssistantMessage(run, turn.text);
|
|
36
|
-
if (schemaSatisfied(node, run.state)) {
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
if (!hasPendingUserInput(ctx.session)) {
|
|
40
|
-
return { kind: 'stay' };
|
|
41
|
-
}
|
|
42
|
-
const signal = await driver.awaitUser(ctx);
|
|
43
|
-
appendUserMessage(run, signal.input);
|
|
44
52
|
}
|
|
45
53
|
}
|
|
46
54
|
function mergeExtractionFromTurn(node, run, turn) {
|
package/dist/flow/runFlow.js
CHANGED
|
@@ -61,6 +61,15 @@ async function dispatchNode(node, run, driver, ctx) {
|
|
|
61
61
|
if (!driver.runStructured) {
|
|
62
62
|
throw new Error('ChannelDriver.runStructured is required for decide nodes');
|
|
63
63
|
}
|
|
64
|
+
// An interactive choice node (withChoices) reached when the turn's input was
|
|
65
|
+
// already consumed by a prior node: its choices were presented on node-enter,
|
|
66
|
+
// so wait for the user to actually pick rather than auto-deciding on stale
|
|
67
|
+
// context. Returning `stay` lets the loop park as `awaitingUser`. (A plain
|
|
68
|
+
// decide with no choices is a pure branch and still runs; and an interactive
|
|
69
|
+
// decide that IS the turn's first input-node still decides on that input.)
|
|
70
|
+
if (node.choices?.length && !hasPendingUserInput(ctx.session) && ctx.turnInputConsumed) {
|
|
71
|
+
return { kind: 'stay' };
|
|
72
|
+
}
|
|
64
73
|
// On resume, the new turn's input is buffered as pending and is not yet in
|
|
65
74
|
// the message history the decision reads. Consume it first (mirrors the
|
|
66
75
|
// collect path) so the decision sees the user's actual reply instead of
|
|
@@ -70,6 +79,8 @@ async function dispatchNode(node, run, driver, ctx) {
|
|
|
70
79
|
const signal = await driver.awaitUser(ctx);
|
|
71
80
|
appendUserMessage(run, signal.input);
|
|
72
81
|
}
|
|
82
|
+
// This decide consumes the turn's input for its decision.
|
|
83
|
+
ctx.turnInputConsumed = true;
|
|
73
84
|
const structured = await driver.runStructured(node, ctx);
|
|
74
85
|
return normalizeTransition(await node.decide(structured, run.state));
|
|
75
86
|
}
|
package/dist/runtime/ctx.js
CHANGED
|
@@ -108,6 +108,7 @@ function makeCtx(deps) {
|
|
|
108
108
|
memoryService: deps.memoryService,
|
|
109
109
|
bargeIn: deps.bargeIn,
|
|
110
110
|
abortSignal: deps.abortSignal,
|
|
111
|
+
turnInputConsumed: false,
|
|
111
112
|
tool: async (name, args, options) => {
|
|
112
113
|
// needsApproval gate: a tool flagged `needsApproval` must be approved by a human
|
|
113
114
|
// before it runs. Approval is a durable pause (the `__approval` signal); on resume
|
|
@@ -49,6 +49,14 @@ export interface RunContext {
|
|
|
49
49
|
bargeIn?: AbortSignal;
|
|
50
50
|
abortSignal?: AbortSignal;
|
|
51
51
|
telemetry?: TelemetrySettings;
|
|
52
|
+
/**
|
|
53
|
+
* Ephemeral, per-run-invocation flag: has the current turn's user input been
|
|
54
|
+
* consumed yet by an input-node (collect/decide)? Input-nodes extract/decide
|
|
55
|
+
* from the turn's fresh input; once it is consumed, later nodes in the same
|
|
56
|
+
* turn pause (present prompt, await next turn) instead of acting on stale
|
|
57
|
+
* context. Reset to false on every `createRunContext` (i.e. every turn).
|
|
58
|
+
*/
|
|
59
|
+
turnInputConsumed?: boolean;
|
|
52
60
|
tool(name: string, args: unknown, options?: {
|
|
53
61
|
toolCallId?: string;
|
|
54
62
|
def?: AnyTool;
|
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.3",
|
|
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.3"
|
|
101
101
|
},
|
|
102
102
|
"dependencies": {
|
|
103
103
|
"chrono-node": "^2.6.0",
|