@memberjunction/ai-agents 6.1.0-edge.0 → 6.1.0-edge.2
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 +24 -2
- package/dist/MJAIAgentRequestEntityServer.d.ts.map +1 -1
- package/dist/MJAIAgentRequestEntityServer.js +10 -1
- package/dist/MJAIAgentRequestEntityServer.js.map +1 -1
- package/dist/agent-types/base-agent-type.d.ts +23 -0
- package/dist/agent-types/base-agent-type.d.ts.map +1 -1
- package/dist/agent-types/base-agent-type.js +47 -1
- package/dist/agent-types/base-agent-type.js.map +1 -1
- package/dist/agent-types/flow-agent-prompt-params.d.ts +57 -0
- package/dist/agent-types/flow-agent-prompt-params.d.ts.map +1 -0
- package/dist/agent-types/flow-agent-prompt-params.js +8 -0
- package/dist/agent-types/flow-agent-prompt-params.js.map +1 -0
- package/dist/agent-types/flow-agent-type.d.ts +77 -6
- package/dist/agent-types/flow-agent-type.d.ts.map +1 -1
- package/dist/agent-types/flow-agent-type.js +182 -53
- package/dist/agent-types/flow-agent-type.js.map +1 -1
- package/dist/agent-types/flow-graph-adapters.d.ts +42 -0
- package/dist/agent-types/flow-graph-adapters.d.ts.map +1 -0
- package/dist/agent-types/flow-graph-adapters.js +99 -0
- package/dist/agent-types/flow-graph-adapters.js.map +1 -0
- package/dist/agent-types/flow-graph-executor.d.ts +26 -0
- package/dist/agent-types/flow-graph-executor.d.ts.map +1 -0
- package/dist/agent-types/flow-graph-executor.js +105 -0
- package/dist/agent-types/flow-graph-executor.js.map +1 -0
- package/dist/agent-types/index.d.ts +2 -0
- package/dist/agent-types/index.d.ts.map +1 -1
- package/dist/agent-types/index.js +2 -0
- package/dist/agent-types/index.js.map +1 -1
- package/dist/agent-types/loop-agent-prompt-params.d.ts +27 -0
- package/dist/agent-types/loop-agent-prompt-params.d.ts.map +1 -1
- package/dist/agent-types/loop-agent-prompt-params.js +5 -1
- package/dist/agent-types/loop-agent-prompt-params.js.map +1 -1
- package/dist/agent-types/loop-agent-response-type.d.ts +14 -3
- 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 +25 -0
- package/dist/agent-types/loop-agent-type.d.ts.map +1 -1
- package/dist/agent-types/loop-agent-type.js +109 -1
- package/dist/agent-types/loop-agent-type.js.map +1 -1
- package/dist/base-agent.d.ts +89 -10
- package/dist/base-agent.d.ts.map +1 -1
- package/dist/base-agent.js +306 -91
- package/dist/base-agent.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/realtime/bridge-realtime-session-factory.d.ts.map +1 -1
- package/dist/realtime/bridge-realtime-session-factory.js +2 -13
- package/dist/realtime/bridge-realtime-session-factory.js.map +1 -1
- package/dist/realtime/realtime-client-session-service.d.ts +67 -16
- package/dist/realtime/realtime-client-session-service.d.ts.map +1 -1
- package/dist/realtime/realtime-client-session-service.js +79 -29
- package/dist/realtime/realtime-client-session-service.js.map +1 -1
- package/dist/realtime/realtime-coagent-config.d.ts +143 -14
- package/dist/realtime/realtime-coagent-config.d.ts.map +1 -1
- package/dist/realtime/realtime-coagent-config.js +187 -18
- package/dist/realtime/realtime-coagent-config.js.map +1 -1
- package/dist/realtime/realtime-vendor-resolution.d.ts +49 -0
- package/dist/realtime/realtime-vendor-resolution.d.ts.map +1 -0
- package/dist/realtime/realtime-vendor-resolution.js +43 -0
- package/dist/realtime/realtime-vendor-resolution.js.map +1 -0
- package/package.json +18 -18
package/dist/base-agent.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* @author MemberJunction.com
|
|
11
11
|
* @since 2.49.0
|
|
12
12
|
*/
|
|
13
|
-
import { FileStorageEngineBase } from '@memberjunction/core-entities';
|
|
13
|
+
import { FileStorageEngineBase, MJEnvironmentEntityExtended } from '@memberjunction/core-entities';
|
|
14
14
|
import { Metadata, RunView, LogStatus, LogStatusEx, LogError, LogErrorEx, IsVerboseLoggingEnabled, DatabaseProviderBase } from '@memberjunction/core';
|
|
15
15
|
import { AgentRunWatchdog } from './agent-run-watchdog.js';
|
|
16
16
|
import { AIPromptRunner } from '@memberjunction/ai-prompts';
|
|
@@ -23,8 +23,9 @@ import { CrushJSON, DescribeCrush, PartitionStablePrefix } from '@memberjunction
|
|
|
23
23
|
import { CrushCode } from '@memberjunction/context-crush/code';
|
|
24
24
|
import { RealtimeSessionRunner } from './realtime/realtime-session-runner.js';
|
|
25
25
|
import { ResolveNarrationInstructionsTemplate } from './realtime/realtime-narration.js';
|
|
26
|
-
import { BuildRealtimeOverridesJson, BuildVoiceMannerSection, GetNarrationPaceMs, GetProviderVoiceSettings, GetSessionTuningSettings, DeepMergeConfigs, ResolveEffectiveRealtimeConfig } from './realtime/realtime-coagent-config.js';
|
|
27
|
-
import {
|
|
26
|
+
import { BuildRealtimeOverridesJson, BuildVoiceMannerSection, GetNarrationPaceMs, GetProviderVoiceSettings, GetSessionTuningSettings, GetModelCatalogSessionSettings, DeepMergeConfigs, ResolveEffectiveRealtimeConfig } from './realtime/realtime-coagent-config.js';
|
|
27
|
+
import { SelectRealtimeVendorForModel } from './realtime/realtime-vendor-resolution.js';
|
|
28
|
+
import { RealtimeClientSessionService, WarnOnUnmatchedProviderVoice } from './realtime/realtime-client-session-service.js';
|
|
28
29
|
import { BuildRealtimeAgentFraming } from './realtime/realtime-tool-broker.js';
|
|
29
30
|
import { RealtimeRecordingController } from './realtime/realtime-recording-capture.js';
|
|
30
31
|
import { resolveRecordingStorageAccountID, storeRealtimeRecording } from './realtime/realtime-recording-store.js';
|
|
@@ -38,13 +39,16 @@ import { FormatToolResultSection, FormatToolErrorSection, RenderToolResultData,
|
|
|
38
39
|
import { PriorTurnToolResultCache } from './prior-turn-tool-result-cache.js';
|
|
39
40
|
import { PromptComponentResolver, InjectScopedPromptParts } from './prompt-component-resolver.js';
|
|
40
41
|
import { ScopedPromptConfigResolver, ApplyScopedPromptConfig } from './scoped-prompt-config-resolver.js';
|
|
41
|
-
import { AIPromptParams, ChildPromptParam, ConversationUtility, ParseFileOutputRef, parseAssignmentStrategy, ResolveClientTools, initAgentRunStep, finalizeAgentRunStep, AgentRunStepSaveQueue, ExtractPromptResultText } from '@memberjunction/ai-core-plus';
|
|
42
|
+
import { AIPromptParams, ChildPromptParam, ConversationUtility, ParseFileOutputRef, parseAssignmentStrategy, ResolveClientTools, initAgentRunStep, finalizeAgentRunStep, AgentRunStepSaveQueue, ExtractPromptResultText, GetTaskGraphSubmitter } from '@memberjunction/ai-core-plus';
|
|
42
43
|
import { AgentRunner } from './AgentRunner.js';
|
|
43
44
|
import { PayloadManager } from './PayloadManager.js';
|
|
44
45
|
import { ScratchpadManager } from './ScratchpadManager.js';
|
|
45
46
|
import { ArtifactToolManager } from './ArtifactToolManager.js';
|
|
46
47
|
import { MemoryWriteManager } from './MemoryWriteManager.js';
|
|
47
48
|
import { PipelineExecutor, PipelineToolRegistry, ActionInvocable, ArtifactToolInvocable, BuildPipelineToolDocs, formatFinalOutput, summarizePipelineStages, } from './pipeline/index.js';
|
|
49
|
+
// The ONE payload-mapping dialect. Loop agents and the task-graph dispatcher read the same authored
|
|
50
|
+
// mapping strings, so these must be the same functions rather than two copies that agree today.
|
|
51
|
+
import { GetValueFromPath, SetMappedValue } from '@memberjunction/ai-core-plus';
|
|
48
52
|
import { AgentDataPreloader } from './AgentDataPreloader.js';
|
|
49
53
|
import { ClientToolRequestManager } from './ClientToolRequestManager.js';
|
|
50
54
|
import { ConversationMessageResolver } from './utils/ConversationMessageResolver.js';
|
|
@@ -144,6 +148,29 @@ export class BaseAgent {
|
|
|
144
148
|
* @private
|
|
145
149
|
*/
|
|
146
150
|
this._agentRun = null;
|
|
151
|
+
/**
|
|
152
|
+
* The task graph this run submitted and is now waiting on, or null.
|
|
153
|
+
*
|
|
154
|
+
* **Why a run parks instead of completing.** Submitting a graph is submit-and-detach: the run
|
|
155
|
+
* returns as soon as the graph is durable, and the dispatcher executes it afterwards — possibly
|
|
156
|
+
* minutes later, possibly on another instance, and for a graph containing a human approval,
|
|
157
|
+
* possibly days later. That is deliberate and must not change: awaiting would hold a run (and the
|
|
158
|
+
* conversation turn behind it) open for the length of an approval, and a graph that settles on a
|
|
159
|
+
* different host could never complete an awaiting run at all.
|
|
160
|
+
*
|
|
161
|
+
* But finishing as `Completed` said something untrue. A run that reported success, a duration of
|
|
162
|
+
* 441ms and a green COMPLETED badge, above a workflow where nothing had happened yet, is not a
|
|
163
|
+
* display problem — the row itself claimed to be done. So the run ends in `Paused`, which the
|
|
164
|
+
* conversation UI already reads as in-progress, and the DISPATCHER completes it when the graph
|
|
165
|
+
* settles (`TaskGraphDispatcher.settleSubmittingRun`). Nothing blocks, and no row claims to be
|
|
166
|
+
* finished while work it caused is still in flight.
|
|
167
|
+
*
|
|
168
|
+
* Set for whichever run submitted — root or sub-agent. A sub-agent that dispatches a graph parks
|
|
169
|
+
* its own run exactly the same way; its parent is unaffected and completes normally, which is
|
|
170
|
+
* correct, because the parent's own work IS done. What a viewer of the PARENT sees is covered by
|
|
171
|
+
* the run tree, which walks into the sub-agent's graph and reports it still running.
|
|
172
|
+
*/
|
|
173
|
+
this._awaitingWorkflowTaskID = null;
|
|
147
174
|
/**
|
|
148
175
|
* Stores the ID of an AIAgentRequest created when a Chat step fires.
|
|
149
176
|
* Populated by executeChatStep(), returned in ExecuteAgentResult.feedbackRequestId.
|
|
@@ -602,7 +629,7 @@ export class BaseAgent {
|
|
|
602
629
|
// Check if this param is marked as MediaOutput in action metadata
|
|
603
630
|
// Note: 'MediaOutput' ValueType is added in v3.1.x migration.
|
|
604
631
|
// Before CodeGen runs, this property may not exist on the entity type.
|
|
605
|
-
const paramMetadata = actionEntity?.Params?.find(p => p.Name === param.Name);
|
|
632
|
+
const paramMetadata = actionEntity?.Params?.Items.find(p => p.Name === param.Name);
|
|
606
633
|
const valueType = paramMetadata?.ValueType;
|
|
607
634
|
const isMediaOutputParam = valueType === 'MediaOutput';
|
|
608
635
|
// Handle MediaOutput params (e.g., Generate Image action)
|
|
@@ -1355,6 +1382,7 @@ export class BaseAgent {
|
|
|
1355
1382
|
// passed in, not our chained signal.
|
|
1356
1383
|
params.cancellationToken = upstreamToken;
|
|
1357
1384
|
this.releasePerRunDataCache();
|
|
1385
|
+
await this.finalizeRun(this.deriveRunOutcome());
|
|
1358
1386
|
}
|
|
1359
1387
|
}
|
|
1360
1388
|
/**
|
|
@@ -1369,6 +1397,32 @@ export class BaseAgent {
|
|
|
1369
1397
|
AgentDataPreloader.Instance.clearRunCache(this._agentRun.ID);
|
|
1370
1398
|
}
|
|
1371
1399
|
}
|
|
1400
|
+
/**
|
|
1401
|
+
* Subclass extension point for per-run cleanup of resources this instance owns outside of
|
|
1402
|
+
* MJ's own tracked state (e.g. an external sandbox or session). No-op by default. Called
|
|
1403
|
+
* unconditionally from `Execute()`'s top-level `finally` block, once per `Execute()` call,
|
|
1404
|
+
* so it runs on every exit path (success, failure, or cancellation) exactly like
|
|
1405
|
+
* {@link releasePerRunDataCache}.
|
|
1406
|
+
*/
|
|
1407
|
+
async finalizeRun(outcome) {
|
|
1408
|
+
// Intentionally empty — subclasses override as needed.
|
|
1409
|
+
}
|
|
1410
|
+
/**
|
|
1411
|
+
* Maps the just-completed run's final `AgentRun.Status` to the 3-value outcome that
|
|
1412
|
+
* {@link finalizeRun} hooks care about. `'AwaitingFeedback'` is a normal settled end-of-turn
|
|
1413
|
+
* (see {@link settledRunStatuses}) — the conversational turn is genuinely over, not paused
|
|
1414
|
+
* mid-`Execute()` — so it maps to `'success'`, same as `'Completed'`.
|
|
1415
|
+
*/
|
|
1416
|
+
deriveRunOutcome() {
|
|
1417
|
+
switch (this._agentRun?.Status) {
|
|
1418
|
+
case 'Cancelled':
|
|
1419
|
+
return 'cancelled';
|
|
1420
|
+
case 'Failed':
|
|
1421
|
+
return 'failure';
|
|
1422
|
+
default:
|
|
1423
|
+
return 'success';
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1372
1426
|
// =====================================================================================
|
|
1373
1427
|
// REALTIME (SESSION-DRIVEN) AGENT SUPPORT
|
|
1374
1428
|
//
|
|
@@ -1544,19 +1598,26 @@ export class BaseAgent {
|
|
|
1544
1598
|
// a usable model exists. This mirrors the same fix in RealtimeClientSessionService.
|
|
1545
1599
|
const candidates = this.selectRealtimeModelCandidates(params.agent, overrideModelID);
|
|
1546
1600
|
for (const model of candidates) {
|
|
1547
|
-
const vendor =
|
|
1601
|
+
const vendor = SelectRealtimeVendorForModel(model.ID);
|
|
1548
1602
|
if (!vendor) {
|
|
1549
1603
|
continue;
|
|
1550
1604
|
}
|
|
1551
|
-
const apiKey = GetAIAPIKey(vendor.
|
|
1605
|
+
const apiKey = GetAIAPIKey(vendor.DriverClass);
|
|
1552
1606
|
if (!apiKey) {
|
|
1553
1607
|
continue;
|
|
1554
1608
|
}
|
|
1555
|
-
const instance = MJGlobal.Instance.ClassFactory.CreateInstance(BaseRealtimeModel, vendor.
|
|
1609
|
+
const instance = MJGlobal.Instance.ClassFactory.CreateInstance(BaseRealtimeModel, vendor.DriverClass, apiKey);
|
|
1556
1610
|
if (!instance) {
|
|
1557
1611
|
continue;
|
|
1558
1612
|
}
|
|
1559
|
-
return {
|
|
1613
|
+
return {
|
|
1614
|
+
model: instance,
|
|
1615
|
+
modelID: model.ID,
|
|
1616
|
+
vendorID: vendor.VendorID,
|
|
1617
|
+
modelVendorID: vendor.ModelVendorID,
|
|
1618
|
+
apiName: vendor.APIName,
|
|
1619
|
+
driverClass: vendor.DriverClass
|
|
1620
|
+
};
|
|
1560
1621
|
}
|
|
1561
1622
|
return null;
|
|
1562
1623
|
}
|
|
@@ -1622,24 +1683,6 @@ export class BaseAgent {
|
|
|
1622
1683
|
}
|
|
1623
1684
|
return ResolveEffectiveRealtimeConfig(typeDefault, agent.TypeConfiguration ?? null, null);
|
|
1624
1685
|
}
|
|
1625
|
-
/**
|
|
1626
|
-
* Selects the highest-priority active vendor for a model whose `DriverClass` has a resolvable
|
|
1627
|
-
* API key. Mirrors the vendor-selection pattern used by prompt execution.
|
|
1628
|
-
*
|
|
1629
|
-
* @param modelID The chosen model's ID.
|
|
1630
|
-
* @returns The vendor driver/api identifiers, or `null` when none has a usable key.
|
|
1631
|
-
*/
|
|
1632
|
-
selectRealtimeVendor(modelID) {
|
|
1633
|
-
const vendors = AIEngine.Instance.ModelVendors
|
|
1634
|
-
.filter(mv => UUIDsEqual(mv.ModelID, modelID) && mv.Status === 'Active' && mv.DriverClass != null)
|
|
1635
|
-
.sort((a, b) => (b.Priority ?? 0) - (a.Priority ?? 0));
|
|
1636
|
-
for (const v of vendors) {
|
|
1637
|
-
if (GetAIAPIKey(v.DriverClass)) {
|
|
1638
|
-
return { vendorID: v.VendorID ?? '', driverClass: v.DriverClass, apiName: v.APIName ?? '' };
|
|
1639
|
-
}
|
|
1640
|
-
}
|
|
1641
|
-
return null;
|
|
1642
|
-
}
|
|
1643
1686
|
/**
|
|
1644
1687
|
* Creates the single long-lived `AIPromptRun` that realtime usage is checkpointed onto.
|
|
1645
1688
|
*
|
|
@@ -1699,7 +1742,7 @@ export class BaseAgent {
|
|
|
1699
1742
|
*/
|
|
1700
1743
|
async buildRealtimeSessionDeps(params, config, modelResolution, promptRun) {
|
|
1701
1744
|
const effectiveConfig = this.resolveRealtimeEffectiveConfig(params.agent);
|
|
1702
|
-
const sessionParams = await this.buildRealtimeSessionParams(params, config, modelResolution.apiName, effectiveConfig, modelResolution.driverClass);
|
|
1745
|
+
const sessionParams = await this.buildRealtimeSessionParams(params, config, modelResolution.apiName, effectiveConfig, modelResolution.driverClass, modelResolution.modelID, modelResolution.modelVendorID);
|
|
1703
1746
|
return {
|
|
1704
1747
|
Model: modelResolution.model,
|
|
1705
1748
|
SessionParams: sessionParams,
|
|
@@ -1737,7 +1780,7 @@ export class BaseAgent {
|
|
|
1737
1780
|
* @param modelApiName The vendor API name of the resolved realtime model.
|
|
1738
1781
|
* @returns The session parameters.
|
|
1739
1782
|
*/
|
|
1740
|
-
async buildRealtimeSessionParams(params, config, modelApiName, effectiveConfig, driverClass) {
|
|
1783
|
+
async buildRealtimeSessionParams(params, config, modelApiName, effectiveConfig, driverClass, modelID, modelVendorID) {
|
|
1741
1784
|
// Identity framing comes from the ONE shared producer so the agent speaks first-person AS the
|
|
1742
1785
|
// TARGET (Sage / Marketing Agent / …), identical to every other realtime host — not as the co-agent.
|
|
1743
1786
|
// See BuildRealtimeAgentFraming + plans/realtime/realtime-core-host-convergence.md.
|
|
@@ -1750,13 +1793,22 @@ export class BaseAgent {
|
|
|
1750
1793
|
const systemPrompt = [framing, basePrompt, voiceManner, memoryContext]
|
|
1751
1794
|
.filter(part => part && part.trim().length > 0)
|
|
1752
1795
|
.join('\n\n');
|
|
1753
|
-
//
|
|
1754
|
-
//
|
|
1755
|
-
//
|
|
1796
|
+
// Model-catalog defaults (the AIModelType < AIModel < AIModelVendor ModelConfiguration
|
|
1797
|
+
// cascade) merge as the BASE layer, then the voice settings — the persona's agnostic
|
|
1798
|
+
// wire-level slots (see RealtimeVoicePersona) plus any matching
|
|
1799
|
+
// `realtime.voice.providers.<provider>` bag —
|
|
1800
|
+
// AND session-tuning knobs (realtime.session) flow into the driver's open Config bag: the same
|
|
1801
|
+
// pact every other config entry rides, mirroring the client-direct builder's cascade exactly.
|
|
1802
|
+
// Same unmatched-provider diagnosis too, so this surface cannot drift back into dropping
|
|
1803
|
+
// authored settings silently (#3530).
|
|
1804
|
+
const catalogSettings = modelID
|
|
1805
|
+
? GetModelCatalogSessionSettings(AIEngine.Instance.GetEffectiveModelConfiguration(modelID, modelVendorID))
|
|
1806
|
+
: null;
|
|
1756
1807
|
const providerVoice = GetProviderVoiceSettings(effectiveConfig, driverClass ?? null);
|
|
1808
|
+
WarnOnUnmatchedProviderVoice(effectiveConfig, driverClass, 'BaseAgent.buildRealtimeSessionParams');
|
|
1757
1809
|
const sessionTuning = GetSessionTuningSettings(effectiveConfig);
|
|
1758
|
-
const configBag = (sessionTuning || providerVoice)
|
|
1759
|
-
? DeepMergeConfigs(sessionTuning, providerVoice)
|
|
1810
|
+
const configBag = (catalogSettings || sessionTuning || providerVoice)
|
|
1811
|
+
? DeepMergeConfigs(catalogSettings, sessionTuning, providerVoice)
|
|
1760
1812
|
: undefined;
|
|
1761
1813
|
return {
|
|
1762
1814
|
Model: modelApiName,
|
|
@@ -3124,6 +3176,12 @@ export class BaseAgent {
|
|
|
3124
3176
|
case 'ClientTools':
|
|
3125
3177
|
// Client tools are valid - execution handled by executeClientToolsStep
|
|
3126
3178
|
return nextStep;
|
|
3179
|
+
// Type assertion required because 'Tasks' is not part of the BaseAgentNextStep step
|
|
3180
|
+
// union (submit-and-detach — the terminal step it produces is 'Success'). The graph was
|
|
3181
|
+
// already validated against TaskGraphSpec by the agent type, so there is nothing left
|
|
3182
|
+
// to check here; submission is handled by executeTasksStep.
|
|
3183
|
+
case 'Tasks':
|
|
3184
|
+
return nextStep;
|
|
3127
3185
|
default:
|
|
3128
3186
|
// if we get here, the next step is not recognized, we can return a retry step
|
|
3129
3187
|
this.logError(`Invalid next step '${nextStep.step}' for agent '${params.agent.Name}'`, {
|
|
@@ -3782,6 +3840,29 @@ export class BaseAgent {
|
|
|
3782
3840
|
*/
|
|
3783
3841
|
async hasExceededAgentRunGuardrails(params, agentRun) {
|
|
3784
3842
|
const agent = params.agent;
|
|
3843
|
+
// Refresh the run's accumulated cost/token actuals before comparing them to the agent's
|
|
3844
|
+
// static limits.
|
|
3845
|
+
//
|
|
3846
|
+
// The LIMITS are static — MaxCostPerRun and MaxTokensPerRun live on the agent and never
|
|
3847
|
+
// change during a run. What was missing is the other side of the comparison: TotalCost and
|
|
3848
|
+
// TotalTokensUsed are DERIVED from the run's steps by calculateTokenStats(), and the only
|
|
3849
|
+
// writer (applyTokenStatsToRun) previously ran on terminal paths alone —
|
|
3850
|
+
// createFailureResult / createCancelledResult / finalizeAgentRun — plus the post-compaction
|
|
3851
|
+
// top-up, which only fires if compaction happens to trigger.
|
|
3852
|
+
//
|
|
3853
|
+
// So mid-run both fields sat at 0, and because the checks below are guarded on
|
|
3854
|
+
// `agent.MaxCostPerRun && agentRun.TotalCost`, a falsy 0 short-circuited them entirely. The
|
|
3855
|
+
// cost and token ceilings were evaluated only at the moment a run ENDED, which is too late
|
|
3856
|
+
// to stop anything: they became reporting, not guardrails. Only the iteration and time
|
|
3857
|
+
// limits actually interrupted a run, because TotalPromptIterations is incremented in the
|
|
3858
|
+
// loop.
|
|
3859
|
+
//
|
|
3860
|
+
// Recomputing here rather than at the call site means every caller — including subclasses
|
|
3861
|
+
// that override the loop — gets a truthful comparison, and the recompute is cheap: it walks
|
|
3862
|
+
// the in-memory step array, with no database round trip.
|
|
3863
|
+
if (this._agentRun === agentRun) {
|
|
3864
|
+
this.applyTokenStatsToRun(agentRun, this.calculateTokenStats());
|
|
3865
|
+
}
|
|
3785
3866
|
// Check absolute maximum iterations (safety net to prevent infinite loops)
|
|
3786
3867
|
const DEFAULT_ABSOLUTE_MAX_ITERATIONS = 5000;
|
|
3787
3868
|
const absoluteMaxIterations = params.absoluteMaxIterations ?? DEFAULT_ABSOLUTE_MAX_ITERATIONS;
|
|
@@ -5610,6 +5691,20 @@ The context is now within limits. Please retry your request with the recovered c
|
|
|
5610
5691
|
responseType[responseTypeKey] = true;
|
|
5611
5692
|
}
|
|
5612
5693
|
}
|
|
5694
|
+
// Capability gates align the OTHER way: default OFF, and the section appears only when the
|
|
5695
|
+
// gate is explicitly on. Running these through the loop above would invert their meaning —
|
|
5696
|
+
// that loop's `else` branch defaults an unset key to true, which is exactly what a gated
|
|
5697
|
+
// capability must never do.
|
|
5698
|
+
const gateMappings = [
|
|
5699
|
+
{ gateFlag: 'enableTaskGraphs', responseTypeKey: 'tasks' }
|
|
5700
|
+
];
|
|
5701
|
+
for (const { gateFlag, responseTypeKey } of gateMappings) {
|
|
5702
|
+
const wasExplicitlySet = explicitResponseType &&
|
|
5703
|
+
Object.prototype.hasOwnProperty.call(explicitResponseType, responseTypeKey);
|
|
5704
|
+
if (!wasExplicitlySet) {
|
|
5705
|
+
responseType[responseTypeKey] = params[gateFlag] === true;
|
|
5706
|
+
}
|
|
5707
|
+
}
|
|
5613
5708
|
}
|
|
5614
5709
|
/**
|
|
5615
5710
|
* Extracts default values from a JSON Schema definition.
|
|
@@ -5967,12 +6062,12 @@ The context is now within limits. Please retry your request with the recovered c
|
|
|
5967
6062
|
const lines = [];
|
|
5968
6063
|
lines.push(`### ${action.Name}`);
|
|
5969
6064
|
lines.push(action.Description);
|
|
5970
|
-
const inputParams = action.Params
|
|
6065
|
+
const inputParams = action.Params.Items
|
|
5971
6066
|
.filter(p => {
|
|
5972
6067
|
const t = p.Type.trim().toLowerCase();
|
|
5973
6068
|
return t === 'input' || t === 'both';
|
|
5974
6069
|
});
|
|
5975
|
-
const outputParams = action.Params
|
|
6070
|
+
const outputParams = action.Params.Items
|
|
5976
6071
|
.filter(p => {
|
|
5977
6072
|
const t = p.Type.trim().toLowerCase();
|
|
5978
6073
|
return t === 'output' || t === 'both';
|
|
@@ -5983,8 +6078,8 @@ The context is now within limits. Please retry your request with the recovered c
|
|
|
5983
6078
|
if (outputParams.length > 0) {
|
|
5984
6079
|
lines.push(`**Output:** ${outputParams.map(p => this.formatActionParameter(p)).join(', ')}`);
|
|
5985
6080
|
}
|
|
5986
|
-
if (action.ResultCodes.length > 0) {
|
|
5987
|
-
const rcParts = action.ResultCodes.map(rc => {
|
|
6081
|
+
if (action.ResultCodes.Items.length > 0) {
|
|
6082
|
+
const rcParts = action.ResultCodes.Items.map(rc => {
|
|
5988
6083
|
const marker = rc.IsSuccess ? '✓' : '✗';
|
|
5989
6084
|
const desc = rc.Description && rc.Description.toLowerCase() !== rc.ResultCode.toLowerCase()
|
|
5990
6085
|
? ` ${rc.Description}`
|
|
@@ -6590,6 +6685,10 @@ The context is now within limits. Please retry your request with the recovered c
|
|
|
6590
6685
|
// If neither is set, EffortLevel remains null (no effort level override was used)
|
|
6591
6686
|
// Set parent run ID if we're in a sub-agent context
|
|
6592
6687
|
this._agentRun.ParentRunID = params.parentRun?.ID;
|
|
6688
|
+
// Only a continuation deliverer sets this; every ordinary run stays at the column default of
|
|
6689
|
+
// 0. It has to be recorded on the run itself because the run is the only thing that outlives
|
|
6690
|
+
// the dispatcher that started it, and the next graph this run submits reads it back.
|
|
6691
|
+
this._agentRun.ContinuationDepth = params.continuationDepth ?? 0;
|
|
6593
6692
|
// Set LastRunID for run chaining (different from ParentRunID)
|
|
6594
6693
|
if (params.lastRunId) {
|
|
6595
6694
|
// Check for circular references in the run chain
|
|
@@ -7166,6 +7265,10 @@ The context is now within limits. Please retry your request with the recovered c
|
|
|
7166
7265
|
}
|
|
7167
7266
|
return await this.executePromptStep(params, config, previousDecision, stepCount);
|
|
7168
7267
|
case 'Sub-Agent':
|
|
7268
|
+
// A Sub-Agent step carrying a task graph is a FOLDED graph (D9): the agent type
|
|
7269
|
+
// rewrote a one-node graph into an ordinary in-run call. Record it before running,
|
|
7270
|
+
// so the run shows what was emitted even if the sub-agent then fails.
|
|
7271
|
+
await this.recordFoldedTaskGraph(params, previousDecision);
|
|
7169
7272
|
return await this.processSubAgentStep(params, previousDecision, undefined, undefined, stepCount);
|
|
7170
7273
|
case 'Actions':
|
|
7171
7274
|
return await this.executeActionsStep(params, previousDecision, undefined, true, stepCount);
|
|
@@ -7179,6 +7282,11 @@ The context is now within limits. Please retry your request with the recovered c
|
|
|
7179
7282
|
// (Plan Mode). executePlanStep's terminal return is 'Chat'-shaped (see its doc comment).
|
|
7180
7283
|
case 'Plan':
|
|
7181
7284
|
return await this.executePlanStep(params, previousDecision);
|
|
7285
|
+
// Type assertion required because 'Tasks' is not part of the BaseAgentNextStep step
|
|
7286
|
+
// union — LoopAgentType.DetermineNextStep() emits it when an opted-in agent submits a
|
|
7287
|
+
// durable task graph. executeTasksStep detaches (see its doc comment).
|
|
7288
|
+
case 'Tasks':
|
|
7289
|
+
return await this.executeTasksStep(params, previousDecision);
|
|
7182
7290
|
// Type assertion required because 'ClientTools' is not part of the BaseAgentNextStep
|
|
7183
7291
|
// step union — LoopAgentType.DetermineNextStep() emits it when the LLM chooses client tools.
|
|
7184
7292
|
case 'ClientTools':
|
|
@@ -8793,24 +8901,14 @@ The context is now within limits. Please retry your request with the recovered c
|
|
|
8793
8901
|
* @param value - Value to set or append
|
|
8794
8902
|
* @private
|
|
8795
8903
|
*/
|
|
8904
|
+
/**
|
|
8905
|
+
* Sets a value on a target object, honouring the `[]` array-append suffix.
|
|
8906
|
+
*
|
|
8907
|
+
* Delegates for the same reason as {@link getValueFromPath} — `results[]` must mean append in
|
|
8908
|
+
* both engines, or a workflow accumulates in one and overwrites in the other.
|
|
8909
|
+
*/
|
|
8796
8910
|
setMappedValue(target, key, value) {
|
|
8797
|
-
|
|
8798
|
-
const actualKey = isArrayAppend ? key.slice(0, -2) : key;
|
|
8799
|
-
if (isArrayAppend) {
|
|
8800
|
-
// Array append operation
|
|
8801
|
-
if (!(actualKey in target)) {
|
|
8802
|
-
target[actualKey] = [];
|
|
8803
|
-
}
|
|
8804
|
-
if (!Array.isArray(target[actualKey])) {
|
|
8805
|
-
throw new Error(`Cannot append to '${actualKey}': target is not an array. ` +
|
|
8806
|
-
`Use '${actualKey}' without [] suffix for property update.`);
|
|
8807
|
-
}
|
|
8808
|
-
target[actualKey].push(value);
|
|
8809
|
-
}
|
|
8810
|
-
else {
|
|
8811
|
-
// Normal property assignment
|
|
8812
|
-
target[actualKey] = value;
|
|
8813
|
-
}
|
|
8911
|
+
SetMappedValue(target, key, value);
|
|
8814
8912
|
}
|
|
8815
8913
|
/**
|
|
8816
8914
|
* Applies sub-agent input mapping to prepare initial payload for related sub-agent.
|
|
@@ -9731,6 +9829,131 @@ The context is now within limits. Please retry your request with the recovered c
|
|
|
9731
9829
|
*
|
|
9732
9830
|
* @protected
|
|
9733
9831
|
*/
|
|
9832
|
+
/**
|
|
9833
|
+
* Submits a durable task graph and ends the turn (submit-and-detach).
|
|
9834
|
+
*
|
|
9835
|
+
* **Why detaching is the whole point.** The alternative — holding the run open until the graph
|
|
9836
|
+
* finishes — is exactly what the old client-driven path did, and it is what made a page reload
|
|
9837
|
+
* lose the work, a server restart orphan it, and every non-Explorer channel unable to reach it
|
|
9838
|
+
* at all. Once submission returns, the graph lives in Task rows and a server-side dispatcher
|
|
9839
|
+
* owns it; the run has no reason to stay alive, so it doesn't.
|
|
9840
|
+
*
|
|
9841
|
+
* **The run step is written whether or not submission succeeds.** A rejected graph is the case
|
|
9842
|
+
* where forensics matter most — the operator needs to see what the model emitted and why it was
|
|
9843
|
+
* refused, and a step written only on the happy path would erase precisely that.
|
|
9844
|
+
*
|
|
9845
|
+
* **A missing submitter is reported, never swallowed.** In a host with no durable-execution
|
|
9846
|
+
* package loaded there is nobody to run the graph. Returning a failure the model can read is
|
|
9847
|
+
* the honest outcome; silently continuing would leave the agent believing it had scheduled work
|
|
9848
|
+
* that does not exist.
|
|
9849
|
+
*
|
|
9850
|
+
* @protected
|
|
9851
|
+
*/
|
|
9852
|
+
async executeTasksStep(params, previousDecision) {
|
|
9853
|
+
const graph = previousDecision.taskGraph;
|
|
9854
|
+
const spec = graph?.spec;
|
|
9855
|
+
if (!spec) {
|
|
9856
|
+
return {
|
|
9857
|
+
step: 'Failed',
|
|
9858
|
+
terminate: true,
|
|
9859
|
+
errorMessage: 'A Tasks step reached execution with no task graph attached.',
|
|
9860
|
+
previousPayload: previousDecision.previousPayload,
|
|
9861
|
+
newPayload: previousDecision.newPayload || previousDecision.previousPayload
|
|
9862
|
+
};
|
|
9863
|
+
}
|
|
9864
|
+
const stepEntity = await this.createStepEntity({
|
|
9865
|
+
stepType: 'TaskGraph',
|
|
9866
|
+
stepName: `Task Graph: ${spec.workflowName}`,
|
|
9867
|
+
contextUser: params.contextUser,
|
|
9868
|
+
inputData: { spec, folded: false }
|
|
9869
|
+
});
|
|
9870
|
+
const submitter = GetTaskGraphSubmitter();
|
|
9871
|
+
if (!submitter) {
|
|
9872
|
+
const errorMessage = 'No task-graph submitter is registered on this host, so the graph could not be made durable. ' +
|
|
9873
|
+
'Task graphs require the durable-execution package to be loaded.';
|
|
9874
|
+
await this.finalizeStepEntity(stepEntity, false, errorMessage, { spec, submitted: false });
|
|
9875
|
+
return {
|
|
9876
|
+
step: 'Failed',
|
|
9877
|
+
terminate: true,
|
|
9878
|
+
errorMessage,
|
|
9879
|
+
previousPayload: previousDecision.previousPayload,
|
|
9880
|
+
newPayload: previousDecision.newPayload || previousDecision.previousPayload
|
|
9881
|
+
};
|
|
9882
|
+
}
|
|
9883
|
+
const outcome = await submitter.Submit({
|
|
9884
|
+
Spec: spec,
|
|
9885
|
+
EnvironmentID: MJEnvironmentEntityExtended.DefaultEnvironmentID,
|
|
9886
|
+
ConversationDetailID: params.conversationDetailId ?? null,
|
|
9887
|
+
AgentRunID: this._agentRun?.ID ?? null,
|
|
9888
|
+
// If THIS run was itself started by a finished graph, the graph it emits inherits that
|
|
9889
|
+
// depth + 1. Without this the chain restarts at zero on every hop and
|
|
9890
|
+
// MAX_REINVOKE_DEPTH never fires — a graph reinvoking an agent that emits a graph would
|
|
9891
|
+
// run forever.
|
|
9892
|
+
ReinvokeDepth: this._agentRun?.ContinuationDepth ?? 0,
|
|
9893
|
+
ContextUser: params.contextUser,
|
|
9894
|
+
Provider: this.ProviderToUse
|
|
9895
|
+
});
|
|
9896
|
+
await this.finalizeStepEntity(stepEntity, outcome.Success, outcome.ErrorMessage, { spec, submitted: outcome.Success, parentTaskID: outcome.ParentTaskID });
|
|
9897
|
+
if (!outcome.Success) {
|
|
9898
|
+
return {
|
|
9899
|
+
step: 'Failed',
|
|
9900
|
+
terminate: true,
|
|
9901
|
+
errorMessage: outcome.ErrorMessage || 'Task graph submission failed.',
|
|
9902
|
+
previousPayload: previousDecision.previousPayload,
|
|
9903
|
+
newPayload: previousDecision.newPayload || previousDecision.previousPayload
|
|
9904
|
+
};
|
|
9905
|
+
}
|
|
9906
|
+
// Success means "this graph is durable and will run", NOT "this graph has run". Saying
|
|
9907
|
+
// otherwise here is the exact lie the old await-everything path told when it returned early.
|
|
9908
|
+
//
|
|
9909
|
+
// The run PARKS on it rather than completing — see `_awaitingWorkflowTaskID`. Recorded only
|
|
9910
|
+
// on a successful submission with a real parent task: a failed submission has nothing to
|
|
9911
|
+
// wait for, and a graph nobody can point at could never be settled by the dispatcher, which
|
|
9912
|
+
// would leave the run parked forever.
|
|
9913
|
+
if (outcome.ParentTaskID) {
|
|
9914
|
+
this._awaitingWorkflowTaskID = outcome.ParentTaskID;
|
|
9915
|
+
}
|
|
9916
|
+
return {
|
|
9917
|
+
step: 'Success',
|
|
9918
|
+
terminate: true,
|
|
9919
|
+
message: previousDecision.message ||
|
|
9920
|
+
`Started **${spec.workflowName}** — ${spec.tasks.length} task(s) running. ` +
|
|
9921
|
+
`I'll follow up when it finishes.`,
|
|
9922
|
+
reasoning: previousDecision.reasoning,
|
|
9923
|
+
confidence: previousDecision.confidence,
|
|
9924
|
+
previousPayload: previousDecision.previousPayload,
|
|
9925
|
+
newPayload: previousDecision.newPayload || previousDecision.previousPayload
|
|
9926
|
+
};
|
|
9927
|
+
}
|
|
9928
|
+
/**
|
|
9929
|
+
* Records a constant-folded task graph (D9) without submitting it.
|
|
9930
|
+
*
|
|
9931
|
+
* Folding rewrites a one-node graph into an ordinary in-run sub-agent call, which means no Task
|
|
9932
|
+
* row and no dispatcher hop. Writing the step anyway is what keeps that decision legible: run
|
|
9933
|
+
* forensics show why a graph did not reach the dispatcher, a user who edits a two-node graph
|
|
9934
|
+
* down to one can read the durability change off the run record instead of inferring it, and
|
|
9935
|
+
* Save as Workflow (D17) can attach to the recorded spec — so the single-node case, the shape
|
|
9936
|
+
* most likely worth promoting, is promotable like any other.
|
|
9937
|
+
*
|
|
9938
|
+
* @protected
|
|
9939
|
+
*/
|
|
9940
|
+
async recordFoldedTaskGraph(params, nextStep) {
|
|
9941
|
+
const graph = nextStep.taskGraph;
|
|
9942
|
+
if (!graph?.folded)
|
|
9943
|
+
return;
|
|
9944
|
+
const stepEntity = await this.createStepEntity({
|
|
9945
|
+
stepType: 'TaskGraph',
|
|
9946
|
+
stepName: `Task Graph (folded): ${graph.spec.workflowName}`,
|
|
9947
|
+
contextUser: params.contextUser,
|
|
9948
|
+
inputData: { spec: graph.spec, folded: true, foldReason: graph.foldReason }
|
|
9949
|
+
});
|
|
9950
|
+
await this.finalizeStepEntity(stepEntity, true, undefined, {
|
|
9951
|
+
spec: graph.spec,
|
|
9952
|
+
folded: true,
|
|
9953
|
+
foldReason: graph.foldReason,
|
|
9954
|
+
submitted: false
|
|
9955
|
+
});
|
|
9956
|
+
}
|
|
9734
9957
|
async executePlanStep(params, previousDecision) {
|
|
9735
9958
|
const planText = previousDecision.planDetails?.plan ?? '';
|
|
9736
9959
|
const stepEntity = await this.createStepEntity({
|
|
@@ -10812,7 +11035,15 @@ The context is now within limits. Please retry your request with the recovered c
|
|
|
10812
11035
|
? this.resolveMediaPlaceholdersInPayload(finalStep.actionableCommands)
|
|
10813
11036
|
: finalStep.actionableCommands;
|
|
10814
11037
|
if (this._agentRun) {
|
|
10815
|
-
|
|
11038
|
+
// A run waiting on a workflow has NOT completed, so it gets no completion time. The
|
|
11039
|
+
// dispatcher stamps this when the graph settles; until then the absence is the honest
|
|
11040
|
+
// record, and a duration computed from it would be the 441ms it took to hand the work
|
|
11041
|
+
// off rather than the time the work took.
|
|
11042
|
+
const parkedOnWorkflow = !!this._awaitingWorkflowTaskID
|
|
11043
|
+
&& (finalStep.step === 'Success' || finalStep.step === 'Chat');
|
|
11044
|
+
if (!parkedOnWorkflow) {
|
|
11045
|
+
this._agentRun.CompletedAt = new Date();
|
|
11046
|
+
}
|
|
10816
11047
|
this._agentRun.Success = finalStep.step === 'Success' || finalStep.step === 'Chat';
|
|
10817
11048
|
if (!this._agentRun.Success) {
|
|
10818
11049
|
// Capture error message from either errorMessage or message field
|
|
@@ -10826,6 +11057,17 @@ The context is now within limits. Please retry your request with the recovered c
|
|
|
10826
11057
|
// set status to Failed
|
|
10827
11058
|
this._agentRun.Status = 'Failed';
|
|
10828
11059
|
}
|
|
11060
|
+
else if (parkedOnWorkflow) {
|
|
11061
|
+
// Waiting on a task graph the dispatcher is still executing. `Paused` rather than a
|
|
11062
|
+
// new status because it already exists on the entity AND the conversation's process
|
|
11063
|
+
// panel already reads it as in-progress (`Status === 'Running' || === 'Paused'`), so
|
|
11064
|
+
// a parked run presents correctly with no UI change.
|
|
11065
|
+
//
|
|
11066
|
+
// `Success` stays true: the turn genuinely succeeded at what it was asked to do —
|
|
11067
|
+
// start the workflow. Whether the WORKFLOW succeeded is a different question, and
|
|
11068
|
+
// the dispatcher answers it here when the graph settles.
|
|
11069
|
+
this._agentRun.Status = 'Paused';
|
|
11070
|
+
}
|
|
10829
11071
|
else if (finalStep.step === 'Chat') {
|
|
10830
11072
|
// Chat steps mean the agent is waiting for human input
|
|
10831
11073
|
this._agentRun.Status = 'AwaitingFeedback';
|
|
@@ -11797,43 +12039,16 @@ The context is now within limits. Please retry your request with the recovered c
|
|
|
11797
12039
|
});
|
|
11798
12040
|
}
|
|
11799
12041
|
/**
|
|
11800
|
-
*
|
|
12042
|
+
* Reads a value out of a nested object by dotted path, with `name[0]` array indexing.
|
|
12043
|
+
*
|
|
12044
|
+
* **Delegates to the one implementation.** This used to be a private copy of the same walk the
|
|
12045
|
+
* task-graph dispatcher performs, and the two had to agree exactly: a Loop agent and the
|
|
12046
|
+
* compiled graph of the same workflow read the SAME authored mapping strings, so any divergence
|
|
12047
|
+
* would make a workflow behave differently depending on which engine ran it — silently, and only
|
|
12048
|
+
* for the paths where they differed.
|
|
11801
12049
|
*/
|
|
11802
12050
|
getValueFromPath(obj, path) {
|
|
11803
|
-
|
|
11804
|
-
let current = obj;
|
|
11805
|
-
for (const part of parts) {
|
|
11806
|
-
if (!part)
|
|
11807
|
-
continue;
|
|
11808
|
-
// Check for array indexing
|
|
11809
|
-
const arrayMatch = part.match(/^([^[]+)\[(\d+)\]$/);
|
|
11810
|
-
if (arrayMatch) {
|
|
11811
|
-
const arrayName = arrayMatch[1];
|
|
11812
|
-
const index = parseInt(arrayMatch[2], 10);
|
|
11813
|
-
if (current && typeof current === 'object' && arrayName in current) {
|
|
11814
|
-
current = current[arrayName];
|
|
11815
|
-
if (Array.isArray(current) && index >= 0 && index < current.length) {
|
|
11816
|
-
current = current[index];
|
|
11817
|
-
}
|
|
11818
|
-
else {
|
|
11819
|
-
return undefined;
|
|
11820
|
-
}
|
|
11821
|
-
}
|
|
11822
|
-
else {
|
|
11823
|
-
return undefined;
|
|
11824
|
-
}
|
|
11825
|
-
}
|
|
11826
|
-
else {
|
|
11827
|
-
// Regular property access
|
|
11828
|
-
if (current && typeof current === 'object' && part in current) {
|
|
11829
|
-
current = current[part];
|
|
11830
|
-
}
|
|
11831
|
-
else {
|
|
11832
|
-
return undefined;
|
|
11833
|
-
}
|
|
11834
|
-
}
|
|
11835
|
-
}
|
|
11836
|
-
return current;
|
|
12051
|
+
return GetValueFromPath(obj, path);
|
|
11837
12052
|
}
|
|
11838
12053
|
/**
|
|
11839
12054
|
* Parse the agent's SecondaryScopeConfig from the ScopeConfig field.
|