neoagent 2.4.2-beta.5 → 2.4.2-beta.7
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 +48 -18
- package/flutter_app/lib/main_controller.dart +0 -1
- package/flutter_app/lib/main_models.dart +28 -0
- package/flutter_app/lib/main_operations.dart +1 -1
- package/flutter_app/lib/main_settings.dart +9 -14
- package/flutter_app/lib/main_shared.dart +0 -9
- package/flutter_app/lib/main_unified.dart +0 -2
- package/flutter_app/lib/src/backend_client.dart +8 -0
- package/package.json +1 -1
- package/server/db/database.js +112 -0
- package/server/http/routes.js +1 -0
- package/server/public/.last_build_id +1 -1
- package/server/public/flutter_bootstrap.js +2 -2
- package/server/public/main.dart.js +10083 -10063
- package/server/routes/mcp.js +9 -0
- package/server/routes/memory.js +35 -2
- package/server/routes/runtime.js +7 -1
- package/server/routes/settings.js +34 -1
- package/server/routes/skills.js +42 -0
- package/server/routes/task_webhooks.js +65 -0
- package/server/services/ai/engine.js +525 -24
- package/server/services/ai/learning.js +56 -8
- package/server/services/ai/providers/anthropic.js +40 -8
- package/server/services/ai/providers/google.js +10 -0
- package/server/services/ai/providers/openai.js +3 -15
- package/server/services/ai/providers/openaiCompatible.js +11 -0
- package/server/services/ai/repetitionGuard.js +60 -0
- package/server/services/ai/systemPrompt.js +52 -16
- package/server/services/ai/taskAnalysis.js +15 -6
- package/server/services/ai/toolEvidence.js +3 -1
- package/server/services/ai/toolRunner.js +43 -1
- package/server/services/ai/toolSelector.js +92 -46
- package/server/services/ai/tools.js +89 -9
- package/server/services/ai/usage.js +114 -0
- package/server/services/manager.js +34 -0
- package/server/services/memory/manager.js +170 -4
- package/server/services/security/capability_audit.js +107 -0
- package/server/services/tasks/adapters/index.js +1 -0
- package/server/services/tasks/adapters/webhook.js +14 -0
- package/server/services/tasks/runtime.js +1 -1
- package/server/services/tasks/webhooks.js +146 -0
- package/server/services/workspace/code_navigation.js +194 -0
- package/server/services/workspace/structured_data.js +93 -0
- package/server/utils/cloud-security.js +24 -2
|
@@ -10,7 +10,12 @@ const {
|
|
|
10
10
|
sanitizeConversationMessages
|
|
11
11
|
} = require('./history');
|
|
12
12
|
const { ensureDefaultAiSettings, getAiSettings } = require('./settings');
|
|
13
|
-
const {
|
|
13
|
+
const {
|
|
14
|
+
activateTools,
|
|
15
|
+
buildToolCatalog,
|
|
16
|
+
selectInitialTools,
|
|
17
|
+
selectToolsForTask,
|
|
18
|
+
} = require('./toolSelector');
|
|
14
19
|
const { compactToolResult } = require('./toolResult');
|
|
15
20
|
const { salvageTextToolCalls } = require('./toolCallSalvage');
|
|
16
21
|
const { sanitizeModelOutput } = require('./outputSanitizer');
|
|
@@ -50,6 +55,8 @@ const { buildLoopPolicy, resolveToolResultLimits } = require('./loopPolicy');
|
|
|
50
55
|
const { globalHooks } = require('./hooks');
|
|
51
56
|
const { withProviderRetry, isTransientError } = require('./providerRetry');
|
|
52
57
|
const { normalizeCompletionConfidence, shouldAcceptTaskComplete } = require('./completion');
|
|
58
|
+
const { normalizeUsage, recordModelUsage } = require('./usage');
|
|
59
|
+
const { ToolRepetitionGuard } = require('./repetitionGuard');
|
|
53
60
|
const { shortenRunId, summarizeForLog, parseMaybeJson } = require('./logFormat');
|
|
54
61
|
const {
|
|
55
62
|
normalizeOutgoingMessage,
|
|
@@ -209,18 +216,34 @@ async function getProviderForUser(userId, task = '', isSubagent = false, modelOv
|
|
|
209
216
|
: {};
|
|
210
217
|
const preferredPurpose = String(selectionHint.purpose || '').trim().toLowerCase();
|
|
211
218
|
const highAutonomy = selectionHint.autonomyLevel === 'high' || selectionHint.complexity === 'complex';
|
|
219
|
+
const requiredConfidence = String(selectionHint.requiredConfidence || '').trim().toLowerCase();
|
|
220
|
+
const costMode = String(selectionHint.costMode || aiSettings.cost_mode || 'balanced_auto').trim().toLowerCase();
|
|
212
221
|
const requestedPurpose = ['planning', 'coding', 'general', 'fast'].includes(preferredPurpose)
|
|
213
222
|
? preferredPurpose
|
|
214
223
|
: '';
|
|
224
|
+
const priceRank = { free: 0, cheap: 1, medium: 2, expensive: 3 };
|
|
225
|
+
const chooseForPurpose = (purpose) => {
|
|
226
|
+
const candidates = availableModels.filter((model) => model.purpose === purpose);
|
|
227
|
+
if (candidates.length === 0) return null;
|
|
228
|
+
if (['economy', 'cost_saver', 'lowest_cost'].includes(costMode)) {
|
|
229
|
+
return [...candidates].sort((left, right) => (
|
|
230
|
+
(priceRank[left.priceTier] ?? 99) - (priceRank[right.priceTier] ?? 99)
|
|
231
|
+
))[0];
|
|
232
|
+
}
|
|
233
|
+
if (['quality', 'highest_quality'].includes(costMode) || requiredConfidence === 'high') {
|
|
234
|
+
return candidates.find((model) => model.priceTier !== 'free' && model.priceTier !== 'cheap') || candidates[0];
|
|
235
|
+
}
|
|
236
|
+
return candidates[0];
|
|
237
|
+
};
|
|
215
238
|
|
|
216
239
|
if (smarterSelection && requestedPurpose) {
|
|
217
|
-
selectedModelDef =
|
|
240
|
+
selectedModelDef = chooseForPurpose(requestedPurpose) || fallbackModel;
|
|
218
241
|
} else if (smarterSelection && highAutonomy) {
|
|
219
|
-
selectedModelDef =
|
|
242
|
+
selectedModelDef = chooseForPurpose('planning') || chooseForPurpose('general') || fallbackModel;
|
|
220
243
|
} else if (isSubagent) {
|
|
221
|
-
selectedModelDef =
|
|
244
|
+
selectedModelDef = chooseForPurpose('fast') || fallbackModel;
|
|
222
245
|
} else {
|
|
223
|
-
selectedModelDef =
|
|
246
|
+
selectedModelDef = chooseForPurpose('general') || fallbackModel;
|
|
224
247
|
}
|
|
225
248
|
}
|
|
226
249
|
|
|
@@ -302,17 +325,20 @@ class AgentEngine {
|
|
|
302
325
|
}
|
|
303
326
|
|
|
304
327
|
async buildSystemPrompt(userId, context = {}) {
|
|
305
|
-
const {
|
|
328
|
+
const { buildSystemPromptSections } = require('./systemPrompt');
|
|
306
329
|
const { MemoryManager } = require('../memory/manager');
|
|
307
330
|
const memoryManager = this.memoryManager || new MemoryManager();
|
|
308
|
-
const
|
|
331
|
+
const promptSections = await buildSystemPromptSections(userId, context, memoryManager);
|
|
309
332
|
const skillRunner = context.skillRunner || this.skillRunner || null;
|
|
310
333
|
const skillsPrompt = skillRunner?.getSkillsForPrompt?.({
|
|
311
334
|
maxTotalChars: 9000,
|
|
312
335
|
maxDescriptionChars: 180,
|
|
313
336
|
maxTriggerChars: 100,
|
|
314
337
|
}) || '';
|
|
315
|
-
return
|
|
338
|
+
return {
|
|
339
|
+
stable: [promptSections.stable, skillsPrompt].filter(Boolean).join('\n\n'),
|
|
340
|
+
dynamic: promptSections.dynamic,
|
|
341
|
+
};
|
|
316
342
|
}
|
|
317
343
|
|
|
318
344
|
persistRunMetadata(runId, patch = {}) {
|
|
@@ -516,7 +542,10 @@ class AgentEngine {
|
|
|
516
542
|
normalize,
|
|
517
543
|
fallback = {},
|
|
518
544
|
reasoningEffort,
|
|
545
|
+
telemetry = null,
|
|
546
|
+
phase = 'structured',
|
|
519
547
|
}) {
|
|
548
|
+
const startedAt = Date.now();
|
|
520
549
|
const response = await withProviderRetry(
|
|
521
550
|
() => provider.chat(
|
|
522
551
|
sanitizeConversationMessages([
|
|
@@ -532,12 +561,26 @@ class AgentEngine {
|
|
|
532
561
|
),
|
|
533
562
|
{ label: `Engine ${model} (structured)` }
|
|
534
563
|
);
|
|
564
|
+
if (telemetry?.runId && telemetry?.userId) {
|
|
565
|
+
recordModelUsage({
|
|
566
|
+
runId: telemetry.runId,
|
|
567
|
+
stepId: telemetry.stepId || null,
|
|
568
|
+
userId: telemetry.userId,
|
|
569
|
+
agentId: telemetry.agentId || null,
|
|
570
|
+
provider: providerName,
|
|
571
|
+
model,
|
|
572
|
+
phase,
|
|
573
|
+
usage: response.usage,
|
|
574
|
+
latencyMs: Date.now() - startedAt,
|
|
575
|
+
});
|
|
576
|
+
}
|
|
535
577
|
|
|
536
578
|
const parsed = parseJsonObject(response.content || '');
|
|
579
|
+
const normalizedUsage = normalizeUsage(response.usage);
|
|
537
580
|
return {
|
|
538
581
|
value: normalize(parsed || {}, fallback),
|
|
539
582
|
raw: response.content || '',
|
|
540
|
-
usage:
|
|
583
|
+
usage: normalizedUsage?.totalTokens || 0,
|
|
541
584
|
};
|
|
542
585
|
}
|
|
543
586
|
|
|
@@ -551,6 +594,7 @@ class AgentEngine {
|
|
|
551
594
|
runId,
|
|
552
595
|
iteration,
|
|
553
596
|
}) {
|
|
597
|
+
const startedAt = Date.now();
|
|
554
598
|
const requestMessages = sanitizeConversationMessages(messages);
|
|
555
599
|
const callOptions = {
|
|
556
600
|
model,
|
|
@@ -627,6 +671,20 @@ class AgentEngine {
|
|
|
627
671
|
error.code = 'MODEL_EMPTY_RESPONSE';
|
|
628
672
|
throw error;
|
|
629
673
|
}
|
|
674
|
+
if (options.runId && options.userId) {
|
|
675
|
+
recordModelUsage({
|
|
676
|
+
runId: options.runId,
|
|
677
|
+
stepId: options.stepId || null,
|
|
678
|
+
userId: options.userId,
|
|
679
|
+
agentId: options.agentId || null,
|
|
680
|
+
provider: providerName,
|
|
681
|
+
model,
|
|
682
|
+
phase: options.phase || 'model_turn',
|
|
683
|
+
usage: resolvedResponse.usage,
|
|
684
|
+
latencyMs: Date.now() - startedAt,
|
|
685
|
+
metadata: { iteration },
|
|
686
|
+
});
|
|
687
|
+
}
|
|
630
688
|
|
|
631
689
|
return {
|
|
632
690
|
response: resolvedResponse,
|
|
@@ -661,6 +719,8 @@ class AgentEngine {
|
|
|
661
719
|
normalize: normalizeTaskAnalysis,
|
|
662
720
|
fallback: buildAnalyzeTaskFallback(forceMode, userMessage),
|
|
663
721
|
reasoningEffort: this.getReasoningEffort(providerName, options),
|
|
722
|
+
telemetry: options,
|
|
723
|
+
phase: 'task_analysis',
|
|
664
724
|
});
|
|
665
725
|
|
|
666
726
|
return {
|
|
@@ -692,6 +752,8 @@ class AgentEngine {
|
|
|
692
752
|
success_criteria: analysis.success_criteria,
|
|
693
753
|
},
|
|
694
754
|
reasoningEffort: this.getReasoningEffort(providerName, options),
|
|
755
|
+
telemetry: options,
|
|
756
|
+
phase: 'execution_plan',
|
|
695
757
|
});
|
|
696
758
|
|
|
697
759
|
return {
|
|
@@ -767,6 +829,8 @@ class AgentEngine {
|
|
|
767
829
|
},
|
|
768
830
|
fallback: { status: fallbackStatus },
|
|
769
831
|
reasoningEffort: this.getReasoningEffort(providerName, options),
|
|
832
|
+
telemetry: options,
|
|
833
|
+
phase: 'loop_decision',
|
|
770
834
|
});
|
|
771
835
|
|
|
772
836
|
return {
|
|
@@ -810,6 +874,8 @@ class AgentEngine {
|
|
|
810
874
|
final_reply: finalReply,
|
|
811
875
|
},
|
|
812
876
|
reasoningEffort: this.getReasoningEffort(providerName, options),
|
|
877
|
+
telemetry: options,
|
|
878
|
+
phase: 'verification',
|
|
813
879
|
});
|
|
814
880
|
|
|
815
881
|
return {
|
|
@@ -953,6 +1019,212 @@ class AgentEngine {
|
|
|
953
1019
|
return executeTool(toolName, args, context, this);
|
|
954
1020
|
}
|
|
955
1021
|
|
|
1022
|
+
isReadOnlyToolCall(toolCall) {
|
|
1023
|
+
const name = String(toolCall?.function?.name || '');
|
|
1024
|
+
const readOnly = new Set([
|
|
1025
|
+
'read_file',
|
|
1026
|
+
'list_directory',
|
|
1027
|
+
'search_files',
|
|
1028
|
+
'code_navigate',
|
|
1029
|
+
'query_structured_data',
|
|
1030
|
+
'memory_recall',
|
|
1031
|
+
'memory_read',
|
|
1032
|
+
'session_search',
|
|
1033
|
+
'web_search',
|
|
1034
|
+
'list_tasks',
|
|
1035
|
+
'list_skills',
|
|
1036
|
+
'list_subagents',
|
|
1037
|
+
'recordings_list',
|
|
1038
|
+
'recordings_get',
|
|
1039
|
+
'recordings_search',
|
|
1040
|
+
'read_health_data',
|
|
1041
|
+
]);
|
|
1042
|
+
if (name === 'http_request') {
|
|
1043
|
+
try {
|
|
1044
|
+
const args = JSON.parse(toolCall.function.arguments || '{}');
|
|
1045
|
+
return String(args.method || 'GET').toUpperCase() === 'GET';
|
|
1046
|
+
} catch {
|
|
1047
|
+
return false;
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
return readOnly.has(name);
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
async executeReadOnlyBatch(toolCalls, context = {}) {
|
|
1054
|
+
const {
|
|
1055
|
+
userId,
|
|
1056
|
+
runId,
|
|
1057
|
+
agentId,
|
|
1058
|
+
app,
|
|
1059
|
+
triggerType,
|
|
1060
|
+
triggerSource,
|
|
1061
|
+
conversationId,
|
|
1062
|
+
startingStepIndex,
|
|
1063
|
+
options = {},
|
|
1064
|
+
} = context;
|
|
1065
|
+
const prepared = [];
|
|
1066
|
+
let nextStepIndex = startingStepIndex;
|
|
1067
|
+
for (const toolCall of toolCalls) {
|
|
1068
|
+
nextStepIndex += 1;
|
|
1069
|
+
let toolArgs = {};
|
|
1070
|
+
try { toolArgs = JSON.parse(toolCall.function.arguments || '{}'); } catch {}
|
|
1071
|
+
const toolName = toolCall.function.name;
|
|
1072
|
+
const repetitionGuard = this.getRunMeta(runId)?.repetitionGuard;
|
|
1073
|
+
if (repetitionGuard?.shouldBlock(toolName, toolArgs)) {
|
|
1074
|
+
const result = {
|
|
1075
|
+
status: 'blocked',
|
|
1076
|
+
reason: 'The same read-only call already returned an unchanged result twice.',
|
|
1077
|
+
};
|
|
1078
|
+
prepared.push({
|
|
1079
|
+
toolCall,
|
|
1080
|
+
toolName,
|
|
1081
|
+
toolArgs,
|
|
1082
|
+
stepIndex: nextStepIndex,
|
|
1083
|
+
blocked: true,
|
|
1084
|
+
result,
|
|
1085
|
+
error: result.reason,
|
|
1086
|
+
});
|
|
1087
|
+
this.recordRunEvent(userId, runId, 'repetition_blocked', {
|
|
1088
|
+
toolName,
|
|
1089
|
+
toolArgs,
|
|
1090
|
+
parallel: true,
|
|
1091
|
+
}, { agentId });
|
|
1092
|
+
continue;
|
|
1093
|
+
}
|
|
1094
|
+
if (globalHooks.has('before_tool_call')) {
|
|
1095
|
+
const hookResult = await globalHooks.run('before_tool_call', {
|
|
1096
|
+
toolName,
|
|
1097
|
+
toolArgs,
|
|
1098
|
+
runId,
|
|
1099
|
+
userId,
|
|
1100
|
+
agentId,
|
|
1101
|
+
iteration: context.iteration,
|
|
1102
|
+
});
|
|
1103
|
+
if (hookResult.block) {
|
|
1104
|
+
prepared.push({
|
|
1105
|
+
toolCall,
|
|
1106
|
+
toolName,
|
|
1107
|
+
toolArgs,
|
|
1108
|
+
stepIndex: nextStepIndex,
|
|
1109
|
+
blocked: true,
|
|
1110
|
+
result: { status: 'skipped', reason: 'Blocked by policy.' },
|
|
1111
|
+
});
|
|
1112
|
+
continue;
|
|
1113
|
+
}
|
|
1114
|
+
if (hookResult.toolArgs) toolArgs = hookResult.toolArgs;
|
|
1115
|
+
}
|
|
1116
|
+
const stepId = uuidv4();
|
|
1117
|
+
db.prepare(
|
|
1118
|
+
`INSERT INTO agent_steps (
|
|
1119
|
+
id, run_id, step_index, type, description, status, tool_name, tool_input, started_at
|
|
1120
|
+
) VALUES (?, ?, ?, ?, ?, 'running', ?, ?, datetime('now'))`
|
|
1121
|
+
).run(
|
|
1122
|
+
stepId,
|
|
1123
|
+
runId,
|
|
1124
|
+
nextStepIndex,
|
|
1125
|
+
this.getStepType(toolName),
|
|
1126
|
+
`${toolName}: ${JSON.stringify(toolArgs).slice(0, 200)}`,
|
|
1127
|
+
toolName,
|
|
1128
|
+
JSON.stringify(toolArgs),
|
|
1129
|
+
);
|
|
1130
|
+
this.emit(userId, 'run:tool_start', {
|
|
1131
|
+
runId,
|
|
1132
|
+
stepId,
|
|
1133
|
+
stepIndex: nextStepIndex,
|
|
1134
|
+
toolName,
|
|
1135
|
+
toolArgs,
|
|
1136
|
+
type: this.getStepType(toolName),
|
|
1137
|
+
});
|
|
1138
|
+
this.recordRunEvent(userId, runId, 'tool_started', {
|
|
1139
|
+
stepIndex: nextStepIndex,
|
|
1140
|
+
toolName,
|
|
1141
|
+
toolArgs,
|
|
1142
|
+
type: this.getStepType(toolName),
|
|
1143
|
+
parallel: true,
|
|
1144
|
+
}, { agentId, stepId });
|
|
1145
|
+
prepared.push({ toolCall, toolName, toolArgs, stepId, stepIndex: nextStepIndex });
|
|
1146
|
+
}
|
|
1147
|
+
this.recordRunEvent(userId, runId, 'parallel_batch_started', {
|
|
1148
|
+
toolNames: prepared.map((item) => item.toolName),
|
|
1149
|
+
count: prepared.length,
|
|
1150
|
+
}, { agentId });
|
|
1151
|
+
const results = await Promise.all(prepared.map(async (item) => {
|
|
1152
|
+
if (item.blocked) return item;
|
|
1153
|
+
const startedAt = Date.now();
|
|
1154
|
+
try {
|
|
1155
|
+
const result = await this.executeTool(item.toolName, item.toolArgs, {
|
|
1156
|
+
userId,
|
|
1157
|
+
runId,
|
|
1158
|
+
agentId,
|
|
1159
|
+
app,
|
|
1160
|
+
triggerType,
|
|
1161
|
+
triggerSource,
|
|
1162
|
+
conversationId,
|
|
1163
|
+
source: options.source || null,
|
|
1164
|
+
chatId: options.chatId || null,
|
|
1165
|
+
taskId: options.taskId || null,
|
|
1166
|
+
widgetId: options.widgetId || null,
|
|
1167
|
+
deliveryState: options.deliveryState || null,
|
|
1168
|
+
allowMultipleProactiveMessages: options.allowMultipleProactiveMessages === true,
|
|
1169
|
+
allowExternalSideEffects: false,
|
|
1170
|
+
});
|
|
1171
|
+
const error = inferToolFailureMessage(item.toolName, result);
|
|
1172
|
+
const status = error ? 'failed' : 'completed';
|
|
1173
|
+
db.prepare(
|
|
1174
|
+
`UPDATE agent_steps
|
|
1175
|
+
SET status = ?, result = ?, error = ?, screenshot_path = ?, completed_at = datetime('now')
|
|
1176
|
+
WHERE id = ?`
|
|
1177
|
+
).run(
|
|
1178
|
+
status,
|
|
1179
|
+
JSON.stringify(result).slice(0, 20000),
|
|
1180
|
+
error || null,
|
|
1181
|
+
result?.screenshotPath || null,
|
|
1182
|
+
item.stepId,
|
|
1183
|
+
);
|
|
1184
|
+
this.emit(userId, 'run:tool_end', {
|
|
1185
|
+
runId,
|
|
1186
|
+
stepId: item.stepId,
|
|
1187
|
+
toolName: item.toolName,
|
|
1188
|
+
result,
|
|
1189
|
+
error: error || undefined,
|
|
1190
|
+
status,
|
|
1191
|
+
});
|
|
1192
|
+
this.recordRunEvent(userId, runId, error ? 'tool_failed' : 'tool_completed', {
|
|
1193
|
+
toolName: item.toolName,
|
|
1194
|
+
status,
|
|
1195
|
+
durationMs: Date.now() - startedAt,
|
|
1196
|
+
resultPreview: summarizeForLog(result),
|
|
1197
|
+
parallel: true,
|
|
1198
|
+
}, { agentId, stepId: item.stepId });
|
|
1199
|
+
return { ...item, result, error };
|
|
1200
|
+
} catch (err) {
|
|
1201
|
+
db.prepare(
|
|
1202
|
+
`UPDATE agent_steps SET status = 'failed', error = ?, completed_at = datetime('now') WHERE id = ?`
|
|
1203
|
+
).run(err.message, item.stepId);
|
|
1204
|
+
this.emit(userId, 'run:tool_end', {
|
|
1205
|
+
runId,
|
|
1206
|
+
stepId: item.stepId,
|
|
1207
|
+
toolName: item.toolName,
|
|
1208
|
+
error: err.message,
|
|
1209
|
+
status: 'failed',
|
|
1210
|
+
});
|
|
1211
|
+
this.recordRunEvent(userId, runId, 'tool_failed', {
|
|
1212
|
+
toolName: item.toolName,
|
|
1213
|
+
status: 'failed',
|
|
1214
|
+
error: err.message,
|
|
1215
|
+
durationMs: Date.now() - startedAt,
|
|
1216
|
+
parallel: true,
|
|
1217
|
+
}, { agentId, stepId: item.stepId });
|
|
1218
|
+
return { ...item, result: { error: err.message }, error: err.message };
|
|
1219
|
+
}
|
|
1220
|
+
}));
|
|
1221
|
+
this.recordRunEvent(userId, runId, 'parallel_batch_completed', {
|
|
1222
|
+
toolNames: results.map((item) => item.toolName),
|
|
1223
|
+
failedCount: results.filter((item) => item.error).length,
|
|
1224
|
+
}, { agentId });
|
|
1225
|
+
return { results, endingStepIndex: nextStepIndex };
|
|
1226
|
+
}
|
|
1227
|
+
|
|
956
1228
|
async persistRunContext(userId, {
|
|
957
1229
|
triggerSource,
|
|
958
1230
|
runTitle,
|
|
@@ -979,6 +1251,47 @@ class AgentEngine {
|
|
|
979
1251
|
return this.activeRuns.get(runId) || null;
|
|
980
1252
|
}
|
|
981
1253
|
|
|
1254
|
+
initializeToolRuntime(runId, allTools, initialTools, options = {}) {
|
|
1255
|
+
const runMeta = this.getRunMeta(runId);
|
|
1256
|
+
if (!runMeta) return;
|
|
1257
|
+
runMeta.toolCatalog = Array.isArray(allTools) ? allTools : [];
|
|
1258
|
+
runMeta.activeTools = Array.isArray(initialTools) ? initialTools : [];
|
|
1259
|
+
runMeta.toolSelectionOptions = {
|
|
1260
|
+
widgetId: options.widgetId || null,
|
|
1261
|
+
};
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
getActiveTools(runId) {
|
|
1265
|
+
return this.getRunMeta(runId)?.activeTools || [];
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
activateToolsForRun(runId, names = []) {
|
|
1269
|
+
const runMeta = this.getRunMeta(runId);
|
|
1270
|
+
if (!runMeta) throw new Error('Run is not active.');
|
|
1271
|
+
const result = activateTools(
|
|
1272
|
+
runMeta.activeTools,
|
|
1273
|
+
runMeta.toolCatalog,
|
|
1274
|
+
names,
|
|
1275
|
+
runMeta.toolSelectionOptions,
|
|
1276
|
+
);
|
|
1277
|
+
runMeta.activeTools = result.tools;
|
|
1278
|
+
this.recordRunEvent(runMeta.userId, runId, 'tools_activated', {
|
|
1279
|
+
activated: result.activated,
|
|
1280
|
+
evicted: result.evicted,
|
|
1281
|
+
unknown: result.unknown,
|
|
1282
|
+
notActivated: result.notActivated,
|
|
1283
|
+
activeToolNames: result.tools.map((tool) => tool.name),
|
|
1284
|
+
}, { agentId: runMeta.agentId });
|
|
1285
|
+
return {
|
|
1286
|
+
success: result.unknown.length === 0 && result.notActivated.length === 0,
|
|
1287
|
+
activated: result.activated,
|
|
1288
|
+
evicted: result.evicted,
|
|
1289
|
+
unknown: result.unknown,
|
|
1290
|
+
not_activated: result.notActivated,
|
|
1291
|
+
active_tools: result.tools.map((tool) => tool.name),
|
|
1292
|
+
};
|
|
1293
|
+
}
|
|
1294
|
+
|
|
982
1295
|
findActiveRunForUser(userId, predicate = null) {
|
|
983
1296
|
let candidate = null;
|
|
984
1297
|
for (const [runId, runMeta] of this.activeRuns.entries()) {
|
|
@@ -1112,7 +1425,20 @@ class AgentEngine {
|
|
|
1112
1425
|
}
|
|
1113
1426
|
|
|
1114
1427
|
buildContextMessages(systemPrompt, summaryMessage, historyMessages, recallMsg) {
|
|
1115
|
-
const messages = [
|
|
1428
|
+
const messages = [];
|
|
1429
|
+
if (systemPrompt && typeof systemPrompt === 'object') {
|
|
1430
|
+
if (systemPrompt.stable) {
|
|
1431
|
+
messages.push({
|
|
1432
|
+
role: 'system',
|
|
1433
|
+
content: systemPrompt.stable,
|
|
1434
|
+
});
|
|
1435
|
+
}
|
|
1436
|
+
if (systemPrompt.dynamic) {
|
|
1437
|
+
messages.push({ role: 'system', content: systemPrompt.dynamic });
|
|
1438
|
+
}
|
|
1439
|
+
} else {
|
|
1440
|
+
messages.push({ role: 'system', content: systemPrompt });
|
|
1441
|
+
}
|
|
1116
1442
|
if (summaryMessage) messages.push(summaryMessage);
|
|
1117
1443
|
if (Array.isArray(historyMessages)) messages.push(...historyMessages);
|
|
1118
1444
|
if (recallMsg) messages.push({ role: 'system', content: recallMsg });
|
|
@@ -1340,6 +1666,7 @@ class AgentEngine {
|
|
|
1340
1666
|
voiceSessionId: options.voiceSessionId || null,
|
|
1341
1667
|
steeringQueue: [],
|
|
1342
1668
|
toolPids: new Set(),
|
|
1669
|
+
repetitionGuard: new ToolRepetitionGuard(),
|
|
1343
1670
|
});
|
|
1344
1671
|
this.emit(userId, 'run:start', { runId, agentId, title: runTitle, model, triggerType, triggerSource });
|
|
1345
1672
|
this.recordRunEvent(userId, runId, 'run_started', {
|
|
@@ -1371,8 +1698,9 @@ class AgentEngine {
|
|
|
1371
1698
|
const mcpManager = app?.locals?.mcpManager || app?.locals?.mcpClient || this.mcpManager;
|
|
1372
1699
|
const integrationManager = app?.locals?.integrationManager || null;
|
|
1373
1700
|
const mcpTools = mcpManager ? mcpManager.getAllTools(userId, { agentId }) : [];
|
|
1374
|
-
const
|
|
1375
|
-
|
|
1701
|
+
const allTools = selectToolsForTask(userMessage, builtInTools, mcpTools, options);
|
|
1702
|
+
let tools = allTools;
|
|
1703
|
+
const toolNames = allTools.map((tool) => tool.name).filter(Boolean);
|
|
1376
1704
|
const coreToolStatus = {
|
|
1377
1705
|
send_message: toolNames.includes('send_message'),
|
|
1378
1706
|
create_task: toolNames.includes('create_task'),
|
|
@@ -1470,7 +1798,7 @@ class AgentEngine {
|
|
|
1470
1798
|
capabilityHealth,
|
|
1471
1799
|
forceMode: options.forceMode || null,
|
|
1472
1800
|
userMessage,
|
|
1473
|
-
options: { ...options, triggerSource },
|
|
1801
|
+
options: { ...options, triggerSource, runId, userId, agentId },
|
|
1474
1802
|
}));
|
|
1475
1803
|
analysisUsage = analysisResult.usage || 0;
|
|
1476
1804
|
totalTokens += analysisUsage;
|
|
@@ -1511,6 +1839,25 @@ class AgentEngine {
|
|
|
1511
1839
|
|
|
1512
1840
|
}
|
|
1513
1841
|
|
|
1842
|
+
tools = selectInitialTools(allTools, analysis.suggested_tools, {
|
|
1843
|
+
widgetId: options.widgetId || null,
|
|
1844
|
+
});
|
|
1845
|
+
this.initializeToolRuntime(runId, allTools, tools, options);
|
|
1846
|
+
messages.push({
|
|
1847
|
+
role: 'system',
|
|
1848
|
+
content: [
|
|
1849
|
+
'[Available tool catalog]',
|
|
1850
|
+
buildToolCatalog(allTools),
|
|
1851
|
+
'',
|
|
1852
|
+
`Active tools: ${tools.map((tool) => tool.name).join(', ')}`,
|
|
1853
|
+
'Use activate_tools with exact catalog names when another schema is required.',
|
|
1854
|
+
].join('\n'),
|
|
1855
|
+
});
|
|
1856
|
+
this.recordRunEvent(userId, runId, 'tool_selection_applied', {
|
|
1857
|
+
activeToolNames: tools.map((tool) => tool.name),
|
|
1858
|
+
catalogSize: allTools.length,
|
|
1859
|
+
}, { agentId });
|
|
1860
|
+
|
|
1514
1861
|
const activeDefaultModelSetting = triggerType === 'subagent'
|
|
1515
1862
|
? aiSettings.default_subagent_model
|
|
1516
1863
|
: aiSettings.default_chat_model;
|
|
@@ -1532,6 +1879,8 @@ class AgentEngine {
|
|
|
1532
1879
|
purpose: requestedPurpose,
|
|
1533
1880
|
complexity: analysis?.complexity,
|
|
1534
1881
|
autonomyLevel: analysis?.autonomy_level,
|
|
1882
|
+
requiredConfidence: analysis?.completion_confidence_required,
|
|
1883
|
+
costMode: aiSettings.cost_mode,
|
|
1535
1884
|
},
|
|
1536
1885
|
}
|
|
1537
1886
|
);
|
|
@@ -1568,7 +1917,7 @@ class AgentEngine {
|
|
|
1568
1917
|
model,
|
|
1569
1918
|
messages,
|
|
1570
1919
|
tools,
|
|
1571
|
-
options,
|
|
1920
|
+
options: { ...options, runId, userId, agentId },
|
|
1572
1921
|
});
|
|
1573
1922
|
totalTokens += deliverableSelectionResult.usage || 0;
|
|
1574
1923
|
const selectedWorkflow = getDeliverableWorkflow(deliverableSelectionResult.selection.type);
|
|
@@ -1589,6 +1938,7 @@ class AgentEngine {
|
|
|
1589
1938
|
await selectedWorkflow.run(deliverablePlan, {
|
|
1590
1939
|
engine: this,
|
|
1591
1940
|
userId,
|
|
1941
|
+
agentId,
|
|
1592
1942
|
runId,
|
|
1593
1943
|
agentId,
|
|
1594
1944
|
app,
|
|
@@ -1616,7 +1966,7 @@ class AgentEngine {
|
|
|
1616
1966
|
messages,
|
|
1617
1967
|
analysis,
|
|
1618
1968
|
capabilitySummary,
|
|
1619
|
-
options,
|
|
1969
|
+
options: { ...options, runId, userId, agentId },
|
|
1620
1970
|
}));
|
|
1621
1971
|
totalTokens += planResult.usage || 0;
|
|
1622
1972
|
plan = planResult.plan;
|
|
@@ -1723,7 +2073,7 @@ class AgentEngine {
|
|
|
1723
2073
|
model,
|
|
1724
2074
|
messages,
|
|
1725
2075
|
tools,
|
|
1726
|
-
options: { ...options, userId },
|
|
2076
|
+
options: { ...options, userId, agentId, runId, phase: 'model_turn' },
|
|
1727
2077
|
runId,
|
|
1728
2078
|
iteration,
|
|
1729
2079
|
});
|
|
@@ -1899,7 +2249,7 @@ class AgentEngine {
|
|
|
1899
2249
|
messagingSent,
|
|
1900
2250
|
iteration,
|
|
1901
2251
|
maxIterations,
|
|
1902
|
-
options,
|
|
2252
|
+
options: { ...options, runId, userId, agentId },
|
|
1903
2253
|
fallbackStatus,
|
|
1904
2254
|
}));
|
|
1905
2255
|
totalTokens += loopState.usage || 0;
|
|
@@ -1920,6 +2270,65 @@ class AgentEngine {
|
|
|
1920
2270
|
break;
|
|
1921
2271
|
}
|
|
1922
2272
|
|
|
2273
|
+
const canRunParallelBatch = (
|
|
2274
|
+
response.toolCalls.length > 1
|
|
2275
|
+
&& response.toolCalls.every((toolCall) => this.isReadOnlyToolCall(toolCall))
|
|
2276
|
+
);
|
|
2277
|
+
if (canRunParallelBatch) {
|
|
2278
|
+
const batch = await this.executeReadOnlyBatch(response.toolCalls, {
|
|
2279
|
+
userId,
|
|
2280
|
+
runId,
|
|
2281
|
+
agentId,
|
|
2282
|
+
app,
|
|
2283
|
+
triggerType,
|
|
2284
|
+
triggerSource,
|
|
2285
|
+
conversationId,
|
|
2286
|
+
startingStepIndex: stepIndex,
|
|
2287
|
+
iteration,
|
|
2288
|
+
options,
|
|
2289
|
+
});
|
|
2290
|
+
stepIndex = batch.endingStepIndex;
|
|
2291
|
+
for (const item of batch.results) {
|
|
2292
|
+
const execution = classifyToolExecution(
|
|
2293
|
+
item.toolName,
|
|
2294
|
+
item.toolArgs,
|
|
2295
|
+
item.result,
|
|
2296
|
+
item.error || '',
|
|
2297
|
+
);
|
|
2298
|
+
execution.input = item.toolArgs;
|
|
2299
|
+
execution.artifacts = await extractArtifactsFromResult(item.toolName, item.result);
|
|
2300
|
+
toolExecutions.push(execution);
|
|
2301
|
+
this.getRunMeta(runId)?.repetitionGuard?.observe(item.toolName, item.toolArgs, item.result);
|
|
2302
|
+
if (item.error) failedStepCount += 1;
|
|
2303
|
+
const modelPayload = compactPayloadForModel(item.toolName, item.result);
|
|
2304
|
+
const toolResultLimits = resolveToolResultLimits(item.toolName, loopPolicy);
|
|
2305
|
+
const toolMessage = {
|
|
2306
|
+
role: 'tool',
|
|
2307
|
+
name: item.toolName,
|
|
2308
|
+
tool_call_id: item.toolCall.id,
|
|
2309
|
+
content: compactToolResult(item.toolName, item.toolArgs, modelPayload.result, {
|
|
2310
|
+
softLimit: toolResultLimits.softLimit,
|
|
2311
|
+
hardLimit: toolResultLimits.hardLimit,
|
|
2312
|
+
}),
|
|
2313
|
+
};
|
|
2314
|
+
messages.push(toolMessage);
|
|
2315
|
+
if (conversationId) {
|
|
2316
|
+
db.prepare(
|
|
2317
|
+
`INSERT INTO conversation_messages (
|
|
2318
|
+
conversation_id, role, content, tool_call_id, name
|
|
2319
|
+
) VALUES (?, 'tool', ?, ?, ?)`
|
|
2320
|
+
).run(conversationId, toolMessage.content, item.toolCall.id, item.toolName);
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
this.persistRunMetadata(runId, {
|
|
2324
|
+
evidenceSources: [...new Set(toolExecutions.map((item) => item.evidenceSource).filter(Boolean))],
|
|
2325
|
+
subagentState: this.listSubagents(runId),
|
|
2326
|
+
deliverableArtifacts,
|
|
2327
|
+
compactionMetrics: compactionMetrics.slice(-20),
|
|
2328
|
+
});
|
|
2329
|
+
continue;
|
|
2330
|
+
}
|
|
2331
|
+
|
|
1923
2332
|
for (const toolCall of response.toolCalls) {
|
|
1924
2333
|
if (this.isRunStopped(runId)) break;
|
|
1925
2334
|
stepIndex++;
|
|
@@ -1983,6 +2392,36 @@ class AgentEngine {
|
|
|
1983
2392
|
break; // exit the for-loop; the while condition will also exit
|
|
1984
2393
|
}
|
|
1985
2394
|
|
|
2395
|
+
const repetitionGuard = this.getRunMeta(runId)?.repetitionGuard;
|
|
2396
|
+
if (repetitionGuard?.shouldBlock(toolName, toolArgs)) {
|
|
2397
|
+
const blockedResult = {
|
|
2398
|
+
tool: toolName,
|
|
2399
|
+
status: 'blocked',
|
|
2400
|
+
reason: 'The same tool call already returned an unchanged result twice. Change the approach or complete with the available evidence.',
|
|
2401
|
+
};
|
|
2402
|
+
messages.push({
|
|
2403
|
+
role: 'tool',
|
|
2404
|
+
name: toolName,
|
|
2405
|
+
tool_call_id: toolCall.id,
|
|
2406
|
+
content: JSON.stringify(blockedResult),
|
|
2407
|
+
});
|
|
2408
|
+
this.recordRunEvent(userId, runId, 'repetition_blocked', {
|
|
2409
|
+
toolName,
|
|
2410
|
+
toolArgs,
|
|
2411
|
+
}, { agentId });
|
|
2412
|
+
this.emit(userId, 'run:tool_end', {
|
|
2413
|
+
runId,
|
|
2414
|
+
toolName,
|
|
2415
|
+
status: 'blocked',
|
|
2416
|
+
result: blockedResult,
|
|
2417
|
+
});
|
|
2418
|
+
messages.push({
|
|
2419
|
+
role: 'system',
|
|
2420
|
+
content: 'The repeated call was blocked because it made no progress. Use a different tool, change the arguments, or finish with a truthful result.',
|
|
2421
|
+
});
|
|
2422
|
+
continue;
|
|
2423
|
+
}
|
|
2424
|
+
|
|
1986
2425
|
// ── before_tool_call hook ──
|
|
1987
2426
|
// Plugins can block a tool call (e.g. security policy) or mutate args.
|
|
1988
2427
|
if (globalHooks.has('before_tool_call')) {
|
|
@@ -2091,6 +2530,8 @@ class AgentEngine {
|
|
|
2091
2530
|
}
|
|
2092
2531
|
|
|
2093
2532
|
const execution = classifyToolExecution(toolName, toolArgs, toolResult, toolErrorMessage);
|
|
2533
|
+
execution.input = toolArgs;
|
|
2534
|
+
repetitionGuard?.observe(toolName, toolArgs, toolResult);
|
|
2094
2535
|
execution.artifacts = await extractArtifactsFromResult(toolName, toolResult);
|
|
2095
2536
|
toolExecutions.push(execution);
|
|
2096
2537
|
if (deliverableWorkflow && Array.isArray(execution.artifacts) && execution.artifacts.length > 0) {
|
|
@@ -2114,7 +2555,6 @@ class AgentEngine {
|
|
|
2114
2555
|
deliverableArtifacts,
|
|
2115
2556
|
compactionMetrics: compactionMetrics.slice(-20),
|
|
2116
2557
|
});
|
|
2117
|
-
|
|
2118
2558
|
const modelPayload = compactPayloadForModel(toolName, toolResult);
|
|
2119
2559
|
if (modelPayload.metrics?.applied) {
|
|
2120
2560
|
const metric = {
|
|
@@ -2144,6 +2584,9 @@ class AgentEngine {
|
|
|
2144
2584
|
})
|
|
2145
2585
|
};
|
|
2146
2586
|
messages.push(toolMessage);
|
|
2587
|
+
if (toolName === 'activate_tools' && !toolErrorMessage) {
|
|
2588
|
+
tools = this.getActiveTools(runId);
|
|
2589
|
+
}
|
|
2147
2590
|
|
|
2148
2591
|
if (toolErrorMessage) {
|
|
2149
2592
|
consecutiveToolFailures += 1;
|
|
@@ -2259,7 +2702,7 @@ class AgentEngine {
|
|
|
2259
2702
|
provider,
|
|
2260
2703
|
model,
|
|
2261
2704
|
providerName,
|
|
2262
|
-
options,
|
|
2705
|
+
options: { ...options, runId, userId, agentId },
|
|
2263
2706
|
stepIndex,
|
|
2264
2707
|
failedStepCount,
|
|
2265
2708
|
toolExecutions,
|
|
@@ -2330,7 +2773,7 @@ class AgentEngine {
|
|
|
2330
2773
|
tools,
|
|
2331
2774
|
toolExecutions,
|
|
2332
2775
|
finalReply: finalResponseText,
|
|
2333
|
-
options,
|
|
2776
|
+
options: { ...options, runId, userId, agentId },
|
|
2334
2777
|
}));
|
|
2335
2778
|
totalTokens += verificationResult.usage || 0;
|
|
2336
2779
|
verification = verificationResult.verification;
|
|
@@ -2502,7 +2945,6 @@ class AgentEngine {
|
|
|
2502
2945
|
executionMode: analysis?.mode || 'execute',
|
|
2503
2946
|
verificationStatus: verification?.status || 'skipped',
|
|
2504
2947
|
}, { agentId });
|
|
2505
|
-
|
|
2506
2948
|
// ── on_loop_end hook ──
|
|
2507
2949
|
// Fire-and-forget: plugins can use this for self-improvement, memory
|
|
2508
2950
|
// consolidation, analytics, or other post-run housekeeping.
|
|
@@ -2514,6 +2956,27 @@ class AgentEngine {
|
|
|
2514
2956
|
finalContent: finalResponseText,
|
|
2515
2957
|
}).catch(() => {});
|
|
2516
2958
|
}
|
|
2959
|
+
if (this.learningManager) {
|
|
2960
|
+
try {
|
|
2961
|
+
const learningSteps = db.prepare(
|
|
2962
|
+
`SELECT tool_name, tool_input, result, status
|
|
2963
|
+
FROM agent_steps WHERE run_id = ? ORDER BY step_index ASC`
|
|
2964
|
+
).all(runId);
|
|
2965
|
+
this.learningManager.maybeCaptureDraft({
|
|
2966
|
+
userId,
|
|
2967
|
+
agentId,
|
|
2968
|
+
runId,
|
|
2969
|
+
triggerSource,
|
|
2970
|
+
triggerType,
|
|
2971
|
+
task: userMessage,
|
|
2972
|
+
title: runTitle,
|
|
2973
|
+
finalContent: finalResponseText || lastContent,
|
|
2974
|
+
steps: learningSteps,
|
|
2975
|
+
});
|
|
2976
|
+
} catch (learningError) {
|
|
2977
|
+
console.warn('[Engine] Skill reflection failed:', learningError.message);
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2517
2980
|
|
|
2518
2981
|
return { runId, content: lastContent, totalTokens, iterations: iteration, status: 'completed' };
|
|
2519
2982
|
} catch (err) {
|
|
@@ -2700,6 +3163,15 @@ class AgentEngine {
|
|
|
2700
3163
|
async spawnSubagent(userId, parentRunId, task, options = {}) {
|
|
2701
3164
|
const handle = uuidv4();
|
|
2702
3165
|
const childRunId = uuidv4();
|
|
3166
|
+
let relevantMemories = [];
|
|
3167
|
+
try {
|
|
3168
|
+
relevantMemories = this.memoryManager
|
|
3169
|
+
? await this.memoryManager.recallMemory(userId, task, {
|
|
3170
|
+
agentId: options.agentId || null,
|
|
3171
|
+
limit: 4,
|
|
3172
|
+
})
|
|
3173
|
+
: [];
|
|
3174
|
+
} catch {}
|
|
2703
3175
|
const subEngine = new AgentEngine(this.io, {
|
|
2704
3176
|
app: options.app || this.app,
|
|
2705
3177
|
browserController: this.browserController,
|
|
@@ -2713,13 +3185,31 @@ class AgentEngine {
|
|
|
2713
3185
|
memoryManager: this.memoryManager,
|
|
2714
3186
|
});
|
|
2715
3187
|
|
|
3188
|
+
const subagentContract = [
|
|
3189
|
+
`Goal: ${String(task || '').trim()}`,
|
|
3190
|
+
options.context ? `Constraints and relevant context:\n${String(options.context).trim()}` : '',
|
|
3191
|
+
relevantMemories.length > 0
|
|
3192
|
+
? `Top relevant memories: ${JSON.stringify(relevantMemories.map((memory) => ({
|
|
3193
|
+
content: memory.content,
|
|
3194
|
+
confidence: memory.confidence,
|
|
3195
|
+
})))}`
|
|
3196
|
+
: '',
|
|
3197
|
+
Array.isArray(options.requiredArtifacts) && options.requiredArtifacts.length > 0
|
|
3198
|
+
? `Required artifacts: ${JSON.stringify(options.requiredArtifacts)}`
|
|
3199
|
+
: '',
|
|
3200
|
+
Array.isArray(options.selectedTools) && options.selectedTools.length > 0
|
|
3201
|
+
? `Selected tools: ${JSON.stringify(options.selectedTools.slice(0, 20))}`
|
|
3202
|
+
: '',
|
|
3203
|
+
'Return a single JSON object with exactly these top-level fields: status, findings, evidence, artifacts, confidence, remaining_blockers.',
|
|
3204
|
+
'status must be completed, partial, or blocked. findings, evidence, artifacts, and remaining_blockers must be arrays. confidence must be low, medium, or high.',
|
|
3205
|
+
].filter(Boolean).join('\n\n');
|
|
2716
3206
|
const record = {
|
|
2717
3207
|
handle,
|
|
2718
3208
|
parentRunId,
|
|
2719
3209
|
childRunId,
|
|
2720
3210
|
userId,
|
|
2721
3211
|
agentId: options.agentId || null,
|
|
2722
|
-
task,
|
|
3212
|
+
task: subagentContract,
|
|
2723
3213
|
model: options.model || null,
|
|
2724
3214
|
status: 'running',
|
|
2725
3215
|
createdAt: new Date().toISOString(),
|
|
@@ -2741,7 +3231,7 @@ class AgentEngine {
|
|
|
2741
3231
|
try {
|
|
2742
3232
|
const result = await subEngine.runWithModel(
|
|
2743
3233
|
userId,
|
|
2744
|
-
|
|
3234
|
+
subagentContract,
|
|
2745
3235
|
{
|
|
2746
3236
|
app: options.app || this.app,
|
|
2747
3237
|
triggerType: 'subagent',
|
|
@@ -2752,9 +3242,20 @@ class AgentEngine {
|
|
|
2752
3242
|
options.model || null
|
|
2753
3243
|
);
|
|
2754
3244
|
record.status = result.status || 'completed';
|
|
3245
|
+
let structured = null;
|
|
3246
|
+
try {
|
|
3247
|
+
const raw = String(result.content || '').trim().replace(/^```(?:json)?\s*/i, '').replace(/\s*```$/, '');
|
|
3248
|
+
const parsed = JSON.parse(raw);
|
|
3249
|
+
if (parsed && typeof parsed === 'object') structured = parsed;
|
|
3250
|
+
} catch {}
|
|
2755
3251
|
record.result = {
|
|
2756
3252
|
runId: result.runId,
|
|
2757
|
-
|
|
3253
|
+
status: structured?.status || result.status || 'completed',
|
|
3254
|
+
findings: Array.isArray(structured?.findings) ? structured.findings : [String(result.content || '').trim()].filter(Boolean),
|
|
3255
|
+
evidence: Array.isArray(structured?.evidence) ? structured.evidence : [],
|
|
3256
|
+
artifacts: Array.isArray(structured?.artifacts) ? structured.artifacts : [],
|
|
3257
|
+
confidence: ['low', 'medium', 'high'].includes(structured?.confidence) ? structured.confidence : 'medium',
|
|
3258
|
+
remainingBlockers: Array.isArray(structured?.remaining_blockers) ? structured.remaining_blockers : [],
|
|
2758
3259
|
totalTokens: result.totalTokens,
|
|
2759
3260
|
iterations: result.iterations,
|
|
2760
3261
|
};
|