@memberjunction/ai-agents 6.2.0-edge.1 → 6.2.0-edge.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.
- package/README.md +18 -0
- package/dist/AgentDecisionService.d.ts +90 -0
- package/dist/AgentDecisionService.d.ts.map +1 -0
- package/dist/AgentDecisionService.js +159 -0
- package/dist/AgentDecisionService.js.map +1 -0
- package/dist/AgentRunner.d.ts +9 -0
- package/dist/AgentRunner.d.ts.map +1 -1
- package/dist/AgentRunner.js +23 -7
- package/dist/AgentRunner.js.map +1 -1
- package/dist/ConversationCompactionManager.d.ts +7 -1
- package/dist/ConversationCompactionManager.d.ts.map +1 -1
- package/dist/ConversationCompactionManager.js +4 -1
- package/dist/ConversationCompactionManager.js.map +1 -1
- package/dist/KnowledgeAgent.d.ts.map +1 -1
- package/dist/KnowledgeAgent.js +6 -6
- package/dist/KnowledgeAgent.js.map +1 -1
- package/dist/PayloadFeedbackManager.d.ts +116 -14
- package/dist/PayloadFeedbackManager.d.ts.map +1 -1
- package/dist/PayloadFeedbackManager.js +229 -88
- package/dist/PayloadFeedbackManager.js.map +1 -1
- package/dist/agent-context-injector.d.ts +47 -3
- package/dist/agent-context-injector.d.ts.map +1 -1
- package/dist/agent-context-injector.js +77 -10
- package/dist/agent-context-injector.js.map +1 -1
- package/dist/agent-memory-context-builder.d.ts +13 -0
- package/dist/agent-memory-context-builder.d.ts.map +1 -1
- package/dist/agent-memory-context-builder.js +24 -5
- package/dist/agent-memory-context-builder.js.map +1 -1
- package/dist/agent-types/flow-agent-type.d.ts +97 -0
- package/dist/agent-types/flow-agent-type.d.ts.map +1 -1
- package/dist/agent-types/flow-agent-type.js +281 -24
- package/dist/agent-types/flow-agent-type.js.map +1 -1
- package/dist/agent-types/flow-graph-executor.d.ts +12 -1
- package/dist/agent-types/flow-graph-executor.d.ts.map +1 -1
- package/dist/agent-types/flow-graph-executor.js +25 -1
- package/dist/agent-types/flow-graph-executor.js.map +1 -1
- package/dist/agent-types/loop-agent-prompt-params.d.ts +239 -66
- package/dist/agent-types/loop-agent-prompt-params.d.ts.map +1 -1
- package/dist/agent-types/loop-agent-prompt-params.js +47 -2
- package/dist/agent-types/loop-agent-prompt-params.js.map +1 -1
- package/dist/agent-types/loop-agent-response-type.d.ts +12 -1
- package/dist/agent-types/loop-agent-response-type.d.ts.map +1 -1
- package/dist/agent-types/loop-agent-response-type.js.map +1 -1
- package/dist/agent-types/loop-agent-type.d.ts +26 -0
- package/dist/agent-types/loop-agent-type.d.ts.map +1 -1
- package/dist/agent-types/loop-agent-type.js +115 -5
- package/dist/agent-types/loop-agent-type.js.map +1 -1
- package/dist/base-agent.d.ts +527 -10
- package/dist/base-agent.d.ts.map +1 -1
- package/dist/base-agent.js +1779 -110
- package/dist/base-agent.js.map +1 -1
- package/dist/catalog-narrowing.d.ts +136 -0
- package/dist/catalog-narrowing.d.ts.map +1 -0
- package/dist/catalog-narrowing.js +212 -0
- package/dist/catalog-narrowing.js.map +1 -0
- package/dist/decision-discovery.d.ts +401 -0
- package/dist/decision-discovery.d.ts.map +1 -0
- package/dist/decision-discovery.js +511 -0
- package/dist/decision-discovery.js.map +1 -0
- package/dist/finish-if-mode-warnings.d.ts +39 -0
- package/dist/finish-if-mode-warnings.d.ts.map +1 -0
- package/dist/finish-if-mode-warnings.js +78 -0
- package/dist/finish-if-mode-warnings.js.map +1 -0
- package/dist/finish-if-state.d.ts +72 -0
- package/dist/finish-if-state.d.ts.map +1 -0
- package/dist/finish-if-state.js +109 -0
- package/dist/finish-if-state.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -1
- package/dist/memory-manager-agent.d.ts +108 -2
- package/dist/memory-manager-agent.d.ts.map +1 -1
- package/dist/memory-manager-agent.js +172 -9
- package/dist/memory-manager-agent.js.map +1 -1
- package/dist/memory-note-gate.d.ts +208 -0
- package/dist/memory-note-gate.d.ts.map +1 -0
- package/dist/memory-note-gate.js +294 -0
- package/dist/memory-note-gate.js.map +1 -0
- package/dist/native-tools/action-tool-builder.d.ts +14 -0
- package/dist/native-tools/action-tool-builder.d.ts.map +1 -1
- package/dist/native-tools/action-tool-builder.js +62 -7
- package/dist/native-tools/action-tool-builder.js.map +1 -1
- package/dist/native-tools/control-tools.d.ts +17 -1
- package/dist/native-tools/control-tools.d.ts.map +1 -1
- package/dist/native-tools/control-tools.js +40 -3
- package/dist/native-tools/control-tools.js.map +1 -1
- package/dist/realtime/bridge-host-params.d.ts +22 -0
- package/dist/realtime/bridge-host-params.d.ts.map +1 -0
- package/dist/realtime/bridge-host-params.js +53 -0
- package/dist/realtime/bridge-host-params.js.map +1 -0
- package/dist/realtime/bridge-realtime-session-factory.d.ts +23 -2
- package/dist/realtime/bridge-realtime-session-factory.d.ts.map +1 -1
- package/dist/realtime/bridge-realtime-session-factory.js +22 -6
- package/dist/realtime/bridge-realtime-session-factory.js.map +1 -1
- package/dist/realtime/bridge-room-transcript-sink.d.ts.map +1 -1
- package/dist/realtime/bridge-room-transcript-sink.js +15 -9
- package/dist/realtime/bridge-room-transcript-sink.js.map +1 -1
- package/dist/realtime/bridge-session-transcript-sink.d.ts +37 -0
- package/dist/realtime/bridge-session-transcript-sink.d.ts.map +1 -0
- package/dist/realtime/bridge-session-transcript-sink.js +58 -0
- package/dist/realtime/bridge-session-transcript-sink.js.map +1 -0
- package/dist/realtime/realtime-client-session-service.d.ts +117 -12
- package/dist/realtime/realtime-client-session-service.d.ts.map +1 -1
- package/dist/realtime/realtime-client-session-service.js +162 -39
- package/dist/realtime/realtime-client-session-service.js.map +1 -1
- package/dist/realtime/realtime-coagent-resolution.d.ts +90 -0
- package/dist/realtime/realtime-coagent-resolution.d.ts.map +1 -0
- package/dist/realtime/realtime-coagent-resolution.js +186 -0
- package/dist/realtime/realtime-coagent-resolution.js.map +1 -0
- package/dist/realtime/realtime-delegation-narrator.d.ts +97 -0
- package/dist/realtime/realtime-delegation-narrator.d.ts.map +1 -0
- package/dist/realtime/realtime-delegation-narrator.js +162 -0
- package/dist/realtime/realtime-delegation-narrator.js.map +1 -0
- package/dist/realtime/realtime-session-runner.d.ts +2 -82
- package/dist/realtime/realtime-session-runner.d.ts.map +1 -1
- package/dist/realtime/realtime-session-runner.js +12 -187
- package/dist/realtime/realtime-session-runner.js.map +1 -1
- package/dist/realtime/realtime-vendor-resolution.d.ts +8 -3
- package/dist/realtime/realtime-vendor-resolution.d.ts.map +1 -1
- package/dist/realtime/realtime-vendor-resolution.js +3 -3
- package/dist/realtime/realtime-vendor-resolution.js.map +1 -1
- package/dist/rubric-evaluation-agent-runner.d.ts +2 -0
- package/dist/rubric-evaluation-agent-runner.d.ts.map +1 -0
- package/dist/rubric-evaluation-agent-runner.js +47 -0
- package/dist/rubric-evaluation-agent-runner.js.map +1 -0
- package/dist/self-check.d.ts +101 -0
- package/dist/self-check.d.ts.map +1 -0
- package/dist/self-check.js +74 -0
- package/dist/self-check.js.map +1 -0
- package/package.json +19 -18
|
@@ -17,9 +17,9 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
17
17
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
18
18
|
};
|
|
19
19
|
import { RegisterClass, SafeExpressionEvaluator, UUIDsEqual } from '@memberjunction/global';
|
|
20
|
-
import { SelectOutgoingEdges } from '@memberjunction/ai-core-plus';
|
|
20
|
+
import { CollectDecisionStepKeys, DecisionHoldReason, DecisionReferencesIn, NO_DECISIONS, ReadFlowDecisionStepConfiguration, ResolveDecisionState, ResolveDecisionStepAnswers, SelectOutgoingEdges, ValidateTaskGraphSpec, } from '@memberjunction/ai-core-plus';
|
|
21
21
|
import { AIEngineGraphRepository, SafeConditionEvaluator } from './flow-graph-adapters.js';
|
|
22
|
-
import { CompileFlowAgentToTaskGraph, FormatFlowCompileErrors } from './flow-graph-executor.js';
|
|
22
|
+
import { CompileFlowAgentToTaskGraph, FormatFlowCompileErrors, FormatFlowValidationErrors } from './flow-graph-executor.js';
|
|
23
23
|
import { BaseAgentType } from './base-agent-type.js';
|
|
24
24
|
import { LogError, LogStatus, LogStatusEx, IsVerboseLoggingEnabled } from '@memberjunction/core';
|
|
25
25
|
import { AIEngine } from '@memberjunction/aiengine';
|
|
@@ -46,9 +46,38 @@ export class FlowExecutionState {
|
|
|
46
46
|
* later hook, so a run never switches engines part-way through.
|
|
47
47
|
*/
|
|
48
48
|
this.executionMode = 'dispatch'; // case-violation-ok-legacy-back-compat: object literals are assigned to this class, so an accessor stub changes what they must supply
|
|
49
|
+
/**
|
|
50
|
+
* What this run's Decision steps have answered, keyed by each step's `key` and then by question.
|
|
51
|
+
*
|
|
52
|
+
* `Answers` holds the answers a path condition may act on, and is what the `decisions` root
|
|
53
|
+
* reads. `Unresolved` says why each of the rest is not usable (the call failed, the answer fell
|
|
54
|
+
* below its question's `minConfidence`, or the step was skipped), and a condition that reads one
|
|
55
|
+
* of them holds. Never written into the payload.
|
|
56
|
+
*/
|
|
57
|
+
this.Decisions = NO_DECISIONS;
|
|
58
|
+
/**
|
|
59
|
+
* The keys of the Decision steps whose call failed in this run.
|
|
60
|
+
*
|
|
61
|
+
* A walker cannot retry, so such a decision never answers in this run, and a path reading it can
|
|
62
|
+
* never be taken: it is passed over, and a recovery path ranked below it can be. An answer below
|
|
63
|
+
* its `minConfidence` is different — the path might have been the one to take — and holds.
|
|
64
|
+
*/
|
|
65
|
+
this.FailedDecisionKeys = new Set();
|
|
49
66
|
this.agentId = agentId;
|
|
50
67
|
}
|
|
51
68
|
}
|
|
69
|
+
/** A run's decisions with one Decision step's answers replacing whatever that step gave before. */
|
|
70
|
+
function withDecisionStep(decisions, config, resolved) {
|
|
71
|
+
const answers = { ...decisions.Answers };
|
|
72
|
+
const unresolved = { ...decisions.Unresolved };
|
|
73
|
+
delete answers[config.key];
|
|
74
|
+
delete unresolved[config.key];
|
|
75
|
+
if (Object.keys(resolved.Answers).length > 0)
|
|
76
|
+
answers[config.key] = resolved.Answers;
|
|
77
|
+
if (Object.keys(resolved.Unresolved).length > 0)
|
|
78
|
+
unresolved[config.key] = resolved.Unresolved;
|
|
79
|
+
return { Answers: answers, Unresolved: unresolved };
|
|
80
|
+
}
|
|
52
81
|
/**
|
|
53
82
|
* Implementation of the Flow Agent Type pattern.
|
|
54
83
|
*
|
|
@@ -199,7 +228,11 @@ let FlowAgentType = class FlowAgentType extends BaseAgentType {
|
|
|
199
228
|
}
|
|
200
229
|
// Find valid paths from current step
|
|
201
230
|
// Pass params to enable data/context access in path conditions (Phase 1)
|
|
202
|
-
const
|
|
231
|
+
const selection = await this.getValidPaths(flowState.currentStepId, payload, flowState, params);
|
|
232
|
+
if (selection.HoldReason) {
|
|
233
|
+
return this.stopOnHeldPath(flowState.currentStepId, selection.HoldReason, payload);
|
|
234
|
+
}
|
|
235
|
+
const paths = selection.Paths;
|
|
203
236
|
if (paths.length === 0) {
|
|
204
237
|
// No valid paths - flow is complete
|
|
205
238
|
return this.createSuccessStep({
|
|
@@ -300,6 +333,7 @@ let FlowAgentType = class FlowAgentType extends BaseAgentType {
|
|
|
300
333
|
* - flowContext: Flow execution metadata (current step, completed steps, path)
|
|
301
334
|
* - data: Transient template data from ExecuteAgentParams (NEW in Phase 1)
|
|
302
335
|
* - context: Runtime context from ExecuteAgentParams (NEW in Phase 1)
|
|
336
|
+
* - decisions: The usable answers of the run's Decision steps, by key (`decisions.<key>.<question>`)
|
|
303
337
|
*
|
|
304
338
|
* This allows path conditions to access runtime parameters like:
|
|
305
339
|
* - `data.userApproval === true`
|
|
@@ -324,21 +358,69 @@ let FlowAgentType = class FlowAgentType extends BaseAgentType {
|
|
|
324
358
|
*/
|
|
325
359
|
async getValidPaths(stepId, payload, flowState, params) {
|
|
326
360
|
const repo = new AIEngineGraphRepository(flowState.agentId);
|
|
327
|
-
|
|
361
|
+
// The Decision hold runs BEFORE any condition is evaluated, on every step's paths: a later
|
|
362
|
+
// step's condition can read an earlier Decision step's answer. A condition reading an answer
|
|
363
|
+
// that is not usable is never evaluated, because `undefined === 'billing'` is a confident,
|
|
364
|
+
// wrong `false`.
|
|
365
|
+
const selection = SelectOutgoingEdges(stepId, repo, this._conditionEvaluator, this.buildConditionContext(payload, flowState, params), (condition) => this.decisionHold(condition, flowState));
|
|
328
366
|
for (const rejection of selection.Rejected) {
|
|
329
367
|
// ConditionFalse is the normal case and stays quiet. The other three are all "this graph
|
|
330
368
|
// is not shaped the way its author thinks", and each previously vanished: a broken
|
|
331
369
|
// expression was logged as if it were a false one, and a dangling edge failed the run
|
|
332
370
|
// with a message that named the missing ID but not the edge that pointed at it.
|
|
333
|
-
|
|
371
|
+
// ConditionHeld is quiet too: one that outranks the choice stops the run with its reason,
|
|
372
|
+
// and one ranked below the choice could not have won.
|
|
373
|
+
if (rejection.Reason === 'ConditionFalse' || rejection.Reason === 'ConditionHeld')
|
|
334
374
|
continue;
|
|
335
375
|
LogError(`Flow path ${rejection.EdgeId} from step ${stepId} not followed (${rejection.Reason})` +
|
|
336
376
|
`${rejection.Detail ? `: ${rejection.Detail}` : ''}`);
|
|
337
377
|
}
|
|
338
378
|
const byId = new Map(AIEngine.Instance.GetPathsFromStep(stepId).map(p => [p.ID, p]));
|
|
339
|
-
return
|
|
340
|
-
|
|
341
|
-
|
|
379
|
+
return {
|
|
380
|
+
Paths: selection.Edges
|
|
381
|
+
.map(e => byId.get(e.id))
|
|
382
|
+
.filter((p) => !!p),
|
|
383
|
+
HoldReason: selection.Held?.Detail ?? null
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Why a path condition must not be evaluated, because of a decision it reads, or `null`.
|
|
388
|
+
*
|
|
389
|
+
* A condition reading a decision whose call failed is UNANSWERABLE in this run: a walker has no
|
|
390
|
+
* retry, so the path can never be taken, and a recovery path ranked below it is taken instead.
|
|
391
|
+
* Any other unusable answer — below its `minConfidence`, or not given — holds, because the path
|
|
392
|
+
* might have been the one to take.
|
|
393
|
+
*/
|
|
394
|
+
decisionHold(condition, flowState) {
|
|
395
|
+
const reason = DecisionHoldReason(condition, flowState.Decisions);
|
|
396
|
+
if (!reason)
|
|
397
|
+
return null;
|
|
398
|
+
const failed = DecisionReferencesIn(condition).References.find(r => flowState.FailedDecisionKeys.has(r.NodeId));
|
|
399
|
+
if (!failed)
|
|
400
|
+
return { Detail: reason };
|
|
401
|
+
const unresolved = flowState.Decisions.Unresolved[failed.NodeId];
|
|
402
|
+
return { Detail: unresolved?.[failed.QuestionKey] ?? reason, Unanswerable: true };
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Stops the run because a step cannot choose its next step without guessing.
|
|
406
|
+
*
|
|
407
|
+
* A path ranked at or above every followable one reads a decision that is not usable, so it
|
|
408
|
+
* might have been the one to take. The dispatcher holds such a fork; a walker cannot wait, so it
|
|
409
|
+
* fails the run with the reason instead, and never takes a lower-ranked path in its place.
|
|
410
|
+
*/
|
|
411
|
+
stopOnHeldPath(stepId, holdReason, payload) {
|
|
412
|
+
const stepName = AIEngine.Instance.GetAgentStepByID(stepId)?.Name ?? stepId;
|
|
413
|
+
return this.stopFlow(`Step "${stepName}" cannot choose its next step: a path from it that ranks at or above the ` +
|
|
414
|
+
`others reads a decision that is not usable, because ${holdReason}. The flow stops rather than guess.`, payload);
|
|
415
|
+
}
|
|
416
|
+
/** A failed step that ends the run: no recovery path is taken after it. */
|
|
417
|
+
stopFlow(errorMessage, payload) {
|
|
418
|
+
return this.createNextStep('Failed', {
|
|
419
|
+
errorMessage,
|
|
420
|
+
terminate: true,
|
|
421
|
+
newPayload: payload,
|
|
422
|
+
previousPayload: payload
|
|
423
|
+
});
|
|
342
424
|
}
|
|
343
425
|
/**
|
|
344
426
|
* The variables a path condition can reference.
|
|
@@ -361,7 +443,9 @@ let FlowAgentType = class FlowAgentType extends BaseAgentType {
|
|
|
361
443
|
stepCount: flowState.completedStepIds.size
|
|
362
444
|
},
|
|
363
445
|
data: params.data || {},
|
|
364
|
-
context: params.context || {}
|
|
446
|
+
context: params.context || {},
|
|
447
|
+
// Only the usable answers. A condition reading any other one holds before it is evaluated.
|
|
448
|
+
decisions: flowState.Decisions.Answers
|
|
365
449
|
};
|
|
366
450
|
}
|
|
367
451
|
/**
|
|
@@ -518,8 +602,16 @@ let FlowAgentType = class FlowAgentType extends BaseAgentType {
|
|
|
518
602
|
flowState.executionPath.push(node.ID);
|
|
519
603
|
// Store a skip marker as the step result
|
|
520
604
|
flowState.stepResults.set(node.ID, { skipped: true, stepName: node.Name });
|
|
605
|
+
// A skipped Decision step answered nothing, so every condition that reads it holds.
|
|
606
|
+
if (node.StepType === 'Decision') {
|
|
607
|
+
this.recordDecisionOutcome(flowState, node, { Status: 'Skipped' });
|
|
608
|
+
}
|
|
521
609
|
// Find the next step by evaluating paths from this skipped step
|
|
522
|
-
const
|
|
610
|
+
const selection = await this.getValidPaths(node.ID, payload, flowState, params);
|
|
611
|
+
if (selection.HoldReason) {
|
|
612
|
+
return this.stopOnHeldPath(node.ID, selection.HoldReason, payload);
|
|
613
|
+
}
|
|
614
|
+
const paths = selection.Paths;
|
|
523
615
|
if (paths.length === 0) {
|
|
524
616
|
// No more paths - flow is complete
|
|
525
617
|
return this.createSuccessStep({
|
|
@@ -634,6 +726,8 @@ let FlowAgentType = class FlowAgentType extends BaseAgentType {
|
|
|
634
726
|
return await this.convertForEachStepToOperation(node, payload, flowState, params);
|
|
635
727
|
case 'While':
|
|
636
728
|
return await this.convertWhileStepToOperation(node, payload, flowState, params);
|
|
729
|
+
case 'Decision':
|
|
730
|
+
return this.createDecisionStep(node, payload);
|
|
637
731
|
default:
|
|
638
732
|
return this.createNextStep('Failed', {
|
|
639
733
|
errorMessage: `Unknown step type: ${node.StepType}`,
|
|
@@ -660,6 +754,120 @@ let FlowAgentType = class FlowAgentType extends BaseAgentType {
|
|
|
660
754
|
const agent = AIEngine.Instance.Agents.find(a => UUIDsEqual(a.ID, agentId));
|
|
661
755
|
return agent?.Name || null;
|
|
662
756
|
}
|
|
757
|
+
/**
|
|
758
|
+
* Turns a Decision step into the `'Decision'` next step BaseAgent runs.
|
|
759
|
+
*
|
|
760
|
+
* The same hand-off a Prompt step makes with its marker: the walker does not call a model
|
|
761
|
+
* itself. BaseAgent runs the request through its own decision execution, so the call gets the
|
|
762
|
+
* prompt default, state resolution and error handling every agent decision gets, and is logged
|
|
763
|
+
* as a `Decision` run step. The answers come back to {@link completeDecisionStep}.
|
|
764
|
+
*
|
|
765
|
+
* A configuration that cannot be read, or a prompt that no longer exists, ends the run: the
|
|
766
|
+
* dispatched path refuses the same flow before it starts.
|
|
767
|
+
*
|
|
768
|
+
* A state the dispatched path refuses — missing, or empty, `{}` and whitespace-only text
|
|
769
|
+
* included — is refused here by the same rule (`ResolveDecisionState`), before any model is
|
|
770
|
+
* asked. The step then fails as a failed call does, so its recovery path runs.
|
|
771
|
+
*/
|
|
772
|
+
createDecisionStep(node, payload) {
|
|
773
|
+
const read = ReadFlowDecisionStepConfiguration(node.Configuration);
|
|
774
|
+
if ('Error' in read) {
|
|
775
|
+
return this.stopFlow(`Decision step "${node.Name}" cannot run: ${read.Error}.`, payload);
|
|
776
|
+
}
|
|
777
|
+
const promptName = this.decisionPromptName(node);
|
|
778
|
+
if (promptName === null) {
|
|
779
|
+
return this.stopFlow(`Decision step "${node.Name}" refers to a prompt that no longer exists (${node.PromptID}). ` +
|
|
780
|
+
`Point it at an existing one, or clear it to use the Default Decision prompt.`, payload);
|
|
781
|
+
}
|
|
782
|
+
const { key, state, questions } = read.Config;
|
|
783
|
+
const resolved = ResolveDecisionState(state, payload);
|
|
784
|
+
if ('ErrorMessage' in resolved) {
|
|
785
|
+
return this.decisionNotAsked(key, resolved.ErrorMessage, payload);
|
|
786
|
+
}
|
|
787
|
+
return this.createNextStep('Decision', {
|
|
788
|
+
decisions: [{ id: key, state: state ?? 'payload', questions }],
|
|
789
|
+
...(promptName ? { decisionPromptName: promptName } : {}),
|
|
790
|
+
newPayload: payload,
|
|
791
|
+
previousPayload: payload
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* A Decision step that is not asked, handed back the way BaseAgent hands back a failed call: a
|
|
796
|
+
* `'Retry'` carrying one failed result. {@link completeDecisionStep} then fails the step, so a
|
|
797
|
+
* condition reading its answers holds or is passed over, and its recovery path is taken, as
|
|
798
|
+
* after any failed call. No model is asked and no Decision run step is logged; the dispatched
|
|
799
|
+
* path logs none for a step it does not ask either.
|
|
800
|
+
*/
|
|
801
|
+
decisionNotAsked(key, reason, payload) {
|
|
802
|
+
return this.createNextStep('Retry', {
|
|
803
|
+
decisionResults: [{ id: key, success: false, error: `it was not asked: ${reason}` }],
|
|
804
|
+
newPayload: payload,
|
|
805
|
+
previousPayload: payload
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* A Decision step's prompt by name: `undefined` when it has none (BaseAgent then uses
|
|
810
|
+
* `Default Decision`), `null` when the prompt it names is gone.
|
|
811
|
+
*/
|
|
812
|
+
decisionPromptName(node) {
|
|
813
|
+
if (!node.PromptID)
|
|
814
|
+
return undefined;
|
|
815
|
+
return AIEngine.Instance.Prompts.find(p => UUIDsEqual(p.ID, node.PromptID))?.Name ?? null;
|
|
816
|
+
}
|
|
817
|
+
/**
|
|
818
|
+
* Records what the current Decision step answered, and turns BaseAgent's `'Retry'` into the
|
|
819
|
+
* step's outcome.
|
|
820
|
+
*
|
|
821
|
+
* A call that succeeded makes the step `'Success'`; one that failed makes it `'Failed'`, so a
|
|
822
|
+
* failed decision takes the same recovery handling as any failed step: the paths that read its
|
|
823
|
+
* answers are passed over, and a recovery path is taken whatever its rank.
|
|
824
|
+
*
|
|
825
|
+
* @param payload the payload the step hands on, which is its `stepResult.result`
|
|
826
|
+
*/
|
|
827
|
+
completeDecisionStep(step, flowState, payload) {
|
|
828
|
+
const node = flowState.currentStepId ? AIEngine.Instance.GetAgentStepByID(flowState.currentStepId) : null;
|
|
829
|
+
const result = step.decisionResults?.[0];
|
|
830
|
+
const answers = result?.success && result.answers && !Array.isArray(result.answers) ? result.answers : null;
|
|
831
|
+
const outcome = answers
|
|
832
|
+
? { Status: 'Complete', Answers: answers }
|
|
833
|
+
: { Status: 'Failed', ErrorMessage: result?.error || 'the decision returned no answers' };
|
|
834
|
+
if (node)
|
|
835
|
+
this.recordDecisionOutcome(flowState, node, outcome);
|
|
836
|
+
if (outcome.Status === 'Complete') {
|
|
837
|
+
return {
|
|
838
|
+
Next: { ...step, step: 'Success' },
|
|
839
|
+
StepResult: { Success: true, step: 'Success', result: payload }
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
const errorMessage = `Decision step "${node?.Name ?? flowState.currentStepId}" failed: ${outcome.ErrorMessage}`;
|
|
843
|
+
return {
|
|
844
|
+
Next: { ...step, step: 'Failed', errorMessage },
|
|
845
|
+
StepResult: { Success: false, step: 'Failed', result: payload }
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* Puts a Decision step's answers into the flow state under its key, replacing any it gave on an
|
|
850
|
+
* earlier visit. The shared answer rules decide which are usable and say why the rest are not.
|
|
851
|
+
*/
|
|
852
|
+
recordDecisionOutcome(flowState, node, outcome) {
|
|
853
|
+
const read = ReadFlowDecisionStepConfiguration(node.Configuration);
|
|
854
|
+
// A step whose settings cannot be read has no key, so nothing can name it: a condition that
|
|
855
|
+
// tries still holds, on "no Decision step has answered".
|
|
856
|
+
if ('Error' in read)
|
|
857
|
+
return;
|
|
858
|
+
const run = {
|
|
859
|
+
Name: node.Name,
|
|
860
|
+
Status: outcome.Status,
|
|
861
|
+
ErrorMessage: outcome.Status === 'Failed' ? outcome.ErrorMessage : null
|
|
862
|
+
};
|
|
863
|
+
const given = outcome.Status === 'Complete' ? outcome.Answers : {};
|
|
864
|
+
const resolved = ResolveDecisionStepAnswers(run, read.Config.questions, given);
|
|
865
|
+
flowState.Decisions = withDecisionStep(flowState.Decisions, read.Config, resolved);
|
|
866
|
+
if (outcome.Status === 'Failed')
|
|
867
|
+
flowState.FailedDecisionKeys.add(read.Config.key);
|
|
868
|
+
else
|
|
869
|
+
flowState.FailedDecisionKeys.delete(read.Config.key);
|
|
870
|
+
}
|
|
663
871
|
/**
|
|
664
872
|
* Processes action results and applies output mapping if configured
|
|
665
873
|
* This should be called by BaseAgent after action execution
|
|
@@ -1036,24 +1244,44 @@ let FlowAgentType = class FlowAgentType extends BaseAgentType {
|
|
|
1036
1244
|
`workflow from its starting step, or split it so the portion you want is its own workflow.`
|
|
1037
1245
|
});
|
|
1038
1246
|
}
|
|
1039
|
-
const
|
|
1040
|
-
if (
|
|
1041
|
-
return this.createNextStep('Failed', {
|
|
1042
|
-
errorMessage: `Workflow '${params.agent.Name}' could not be prepared to run:\n${FormatFlowCompileErrors(compiled)}`
|
|
1043
|
-
});
|
|
1247
|
+
const prepared = this.compileAndValidateFlow(params);
|
|
1248
|
+
if ('ErrorMessage' in prepared) {
|
|
1249
|
+
return this.createNextStep('Failed', { errorMessage: prepared.ErrorMessage });
|
|
1044
1250
|
}
|
|
1045
|
-
this.logCompiledGraph(params.agent.Name,
|
|
1251
|
+
this.logCompiledGraph(params.agent.Name, prepared.Spec.tasks.length, prepared.Excluded);
|
|
1046
1252
|
// 'Tasks' is not in the BaseAgentNextStep step union — it is non-terminal in the same sense
|
|
1047
1253
|
// as 'ClientTools'/'Plan' (see that type's doc comment), and BaseAgent routes it to
|
|
1048
1254
|
// executeTasksStep, whose terminal step is 'Success'.
|
|
1049
1255
|
return {
|
|
1050
1256
|
step: 'Tasks',
|
|
1051
1257
|
terminate: false,
|
|
1052
|
-
taskGraph: { spec:
|
|
1258
|
+
taskGraph: { spec: prepared.Spec, folded: false },
|
|
1053
1259
|
previousPayload: payload,
|
|
1054
1260
|
newPayload: payload
|
|
1055
1261
|
};
|
|
1056
1262
|
}
|
|
1263
|
+
/**
|
|
1264
|
+
* Compiles the flow and holds it to the checks `Submit` makes, or says why it cannot run.
|
|
1265
|
+
*
|
|
1266
|
+
* The dispatched path runs this before it submits, and the in-run walker before its first step
|
|
1267
|
+
* when the flow has a Decision step, so a flow one mode accepts the other cannot refuse — an
|
|
1268
|
+
* incomplete Choice fork, a decision read before it can answer, or one read through the payload
|
|
1269
|
+
* is refused in both. The refusal names the author's steps: a compiled graph's tempIds are step
|
|
1270
|
+
* IDs, and an incomplete fork, for one, is reported by its group, which is its origin's ID.
|
|
1271
|
+
*/
|
|
1272
|
+
compileAndValidateFlow(params) {
|
|
1273
|
+
const compiled = CompileFlowAgentToTaskGraph(params.agent.ID, params.agent.Name, params.agent.Description ?? undefined);
|
|
1274
|
+
if (!compiled.Success || !compiled.Spec) {
|
|
1275
|
+
return { ErrorMessage: `Workflow '${params.agent.Name}' could not be prepared to run:\n${FormatFlowCompileErrors(compiled)}` };
|
|
1276
|
+
}
|
|
1277
|
+
const validation = ValidateTaskGraphSpec(compiled.Spec);
|
|
1278
|
+
if (!validation.Valid) {
|
|
1279
|
+
return {
|
|
1280
|
+
ErrorMessage: `Workflow '${params.agent.Name}' could not be prepared to run:\n${FormatFlowValidationErrors(validation.Errors, compiled.Spec)}`
|
|
1281
|
+
};
|
|
1282
|
+
}
|
|
1283
|
+
return { Spec: compiled.Spec, Excluded: compiled.Excluded };
|
|
1284
|
+
}
|
|
1057
1285
|
/**
|
|
1058
1286
|
* Logs what the compile produced.
|
|
1059
1287
|
*
|
|
@@ -1084,6 +1312,25 @@ let FlowAgentType = class FlowAgentType extends BaseAgentType {
|
|
|
1084
1312
|
async determineInitialStepInRun(params, payload, agentTypeState) {
|
|
1085
1313
|
const flowState = agentTypeState;
|
|
1086
1314
|
const payloadToUse = payload || {};
|
|
1315
|
+
// The compiler's own key check, over every active step: the walker can start anywhere
|
|
1316
|
+
// (startAtStep), so it cannot limit itself to what the entry reaches. A shared key would
|
|
1317
|
+
// leave a condition's `decisions.<key>` naming two steps, and the dispatched path refuses
|
|
1318
|
+
// the same flow before it starts.
|
|
1319
|
+
const activeSteps = AIEngine.Instance.GetAgentSteps(flowState.agentId, 'Active') ?? [];
|
|
1320
|
+
const duplicateKeys = CollectDecisionStepKeys(activeSteps).Errors;
|
|
1321
|
+
if (duplicateKeys.length > 0) {
|
|
1322
|
+
return this.stopFlow(`Workflow '${params.agent.Name}' cannot run:\n${FormatFlowCompileErrors({ Success: false, Errors: duplicateKeys, Excluded: [] })}`, payloadToUse);
|
|
1323
|
+
}
|
|
1324
|
+
// A flow with a Decision step gets the dispatched path's whole check, once, before its first
|
|
1325
|
+
// step: an incomplete Choice fork ends the flow "successfully" on the option nobody drew a
|
|
1326
|
+
// path for, and in-run is the default mode for every sub-agent flow. So such a flow must be
|
|
1327
|
+
// one the dispatcher would accept — which also rules out a loop drawn with a path back. A
|
|
1328
|
+
// flow without a Decision step is walked as it always was.
|
|
1329
|
+
if (activeSteps.some(s => s.StepType === 'Decision')) {
|
|
1330
|
+
const prepared = this.compileAndValidateFlow(params);
|
|
1331
|
+
if ('ErrorMessage' in prepared)
|
|
1332
|
+
return this.stopFlow(prepared.ErrorMessage, payloadToUse);
|
|
1333
|
+
}
|
|
1087
1334
|
// Check for startAtStep in agentTypeParams (FlowAgentExecuteParams)
|
|
1088
1335
|
const flowParams = params.agentTypeParams;
|
|
1089
1336
|
if (flowParams?.startAtStep) {
|
|
@@ -1171,9 +1418,14 @@ let FlowAgentType = class FlowAgentType extends BaseAgentType {
|
|
|
1171
1418
|
previousPayload: currentPayload
|
|
1172
1419
|
});
|
|
1173
1420
|
}
|
|
1421
|
+
// A Decision step's answers come back on the 'Retry' BaseAgent returns after running it. They
|
|
1422
|
+
// are recorded first, so this step's paths, and every later step's, can read them. From here
|
|
1423
|
+
// on the step is its outcome: Success, or Failed when the call failed.
|
|
1424
|
+
const decision = step.decisionResults ? this.completeDecisionStep(step, flowState, currentPayload) : null;
|
|
1425
|
+
const finished = decision?.Next ?? step;
|
|
1174
1426
|
// Store the step result so path conditions can access it via stepResult
|
|
1175
1427
|
// The step parameter contains the result from the just-completed step (Sub-Agent, Action, or Prompt)
|
|
1176
|
-
flowState.stepResults.set(flowState.currentStepId, step);
|
|
1428
|
+
flowState.stepResults.set(flowState.currentStepId, decision ? decision.StepResult : step);
|
|
1177
1429
|
// Add to execution path if not already present
|
|
1178
1430
|
if (!flowState.executionPath.includes(flowState.currentStepId)) {
|
|
1179
1431
|
flowState.executionPath.push(flowState.currentStepId);
|
|
@@ -1182,17 +1434,21 @@ let FlowAgentType = class FlowAgentType extends BaseAgentType {
|
|
|
1182
1434
|
flowState.completedStepIds.add(flowState.currentStepId);
|
|
1183
1435
|
// Find valid paths from current step using updated payload
|
|
1184
1436
|
// Pass params to enable data/context access in path conditions (Phase 1)
|
|
1185
|
-
const
|
|
1437
|
+
const selection = await this.getValidPaths(flowState.currentStepId, currentPayload, flowState, params);
|
|
1438
|
+
if (selection.HoldReason) {
|
|
1439
|
+
return this.stopOnHeldPath(flowState.currentStepId, selection.HoldReason, currentPayload);
|
|
1440
|
+
}
|
|
1441
|
+
const paths = selection.Paths;
|
|
1186
1442
|
if (paths.length === 0) {
|
|
1187
1443
|
// No valid paths found
|
|
1188
1444
|
// If the previous step failed, propagate the failure with its error message
|
|
1189
|
-
if (
|
|
1445
|
+
if (finished.step === 'Failed') {
|
|
1190
1446
|
LogStatusEx({
|
|
1191
|
-
message: `⚠️ Flow Agent: Step failed with no recovery path. Error: ${
|
|
1447
|
+
message: `⚠️ Flow Agent: Step failed with no recovery path. Error: ${finished.errorMessage || 'No error message'}`,
|
|
1192
1448
|
verboseOnly: false
|
|
1193
1449
|
});
|
|
1194
1450
|
return this.createNextStep('Failed', {
|
|
1195
|
-
errorMessage:
|
|
1451
|
+
errorMessage: finished.errorMessage || 'Flow step failed with no recovery path',
|
|
1196
1452
|
newPayload: currentPayload,
|
|
1197
1453
|
previousPayload: currentPayload,
|
|
1198
1454
|
terminate: true
|
|
@@ -1221,7 +1477,7 @@ let FlowAgentType = class FlowAgentType extends BaseAgentType {
|
|
|
1221
1477
|
});
|
|
1222
1478
|
}
|
|
1223
1479
|
// Log when we're navigating to a recovery path after a failure
|
|
1224
|
-
if (
|
|
1480
|
+
if (finished.step === 'Failed') {
|
|
1225
1481
|
LogStatusEx({
|
|
1226
1482
|
message: `🔄 Flow Agent: Failure detected, navigating to recovery path: '${nextStep.Name}'`,
|
|
1227
1483
|
verboseOnly: false
|
|
@@ -1233,7 +1489,7 @@ let FlowAgentType = class FlowAgentType extends BaseAgentType {
|
|
|
1233
1489
|
for (let i = 1; i < paths.length; i++) {
|
|
1234
1490
|
const alternateStep = await this.getStepById(paths[i].DestinationStepID);
|
|
1235
1491
|
if (alternateStep && alternateStep.Status === 'Active') {
|
|
1236
|
-
if (
|
|
1492
|
+
if (finished.step === 'Failed') {
|
|
1237
1493
|
LogStatusEx({
|
|
1238
1494
|
message: `🔄 Flow Agent: Using alternate recovery path: '${alternateStep.Name}'`,
|
|
1239
1495
|
verboseOnly: false
|
|
@@ -1468,6 +1724,7 @@ let FlowAgentType = class FlowAgentType extends BaseAgentType {
|
|
|
1468
1724
|
*/
|
|
1469
1725
|
GetPromptConfigurationGuidance() {
|
|
1470
1726
|
return ` - Flow agents use step-level prompts (StepType="Prompt" with PromptID)\n` +
|
|
1727
|
+
` - Decision steps (StepType="Decision") use PromptID for their decision prompt; empty means "Default Decision"\n` +
|
|
1471
1728
|
` - Prompts should be configured in agent steps, not AI Agent Prompts relationship\n` +
|
|
1472
1729
|
` - Verify that prompts exist in AI Prompts table and are active`;
|
|
1473
1730
|
}
|