neoagent 3.2.1-beta.8 → 3.3.0
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/flutter_app/lib/main_app_shell.dart +178 -34
- package/flutter_app/lib/main_chat.dart +542 -80
- package/flutter_app/lib/main_controller.dart +59 -4
- package/flutter_app/lib/main_models.dart +171 -22
- package/flutter_app/lib/main_operations.dart +0 -49
- package/flutter_app/lib/main_settings.dart +338 -0
- package/flutter_app/lib/src/backend_client.dart +19 -0
- package/package.json +1 -1
- package/server/db/database.js +2 -2
- package/server/http/routes.js +1 -0
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +85079 -83904
- package/server/routes/behavior.js +80 -0
- package/server/routes/settings.js +4 -0
- package/server/services/agents/manager.js +1 -1
- package/server/services/ai/history.js +1 -1
- package/server/services/ai/loop/agent_engine_core.js +131 -18
- package/server/services/ai/loop/blank_recovery.js +5 -4
- package/server/services/ai/loop/completion_judge.js +280 -18
- package/server/services/ai/loop/conversation_loop.js +92 -34
- package/server/services/ai/loop/messaging_delivery.js +47 -0
- package/server/services/ai/loop/progress_classification.js +2 -0
- package/server/services/ai/loopPolicy.js +24 -2
- package/server/services/ai/messagingFallback.js +17 -17
- package/server/services/ai/model_failure_cache.js +7 -0
- package/server/services/ai/systemPrompt.js +31 -122
- package/server/services/ai/taskAnalysis.js +101 -12
- package/server/services/ai/toolEvidence.js +198 -0
- package/server/services/ai/tools.js +60 -19
- package/server/services/behavior/config.js +251 -0
- package/server/services/behavior/defaults.js +68 -0
- package/server/services/behavior/delivery.js +176 -0
- package/server/services/behavior/index.js +43 -0
- package/server/services/behavior/model_client.js +35 -0
- package/server/services/behavior/modules/agent_identity.js +37 -0
- package/server/services/behavior/modules/channel_style.js +28 -0
- package/server/services/behavior/modules/index.js +29 -0
- package/server/services/behavior/modules/norms.js +101 -0
- package/server/services/behavior/modules/persona.js +172 -0
- package/server/services/behavior/modules/persona_prompt.js +238 -0
- package/server/services/behavior/modules/social_memory.js +86 -0
- package/server/services/behavior/modules/social_observability.js +94 -0
- package/server/services/behavior/modules/social_signals.js +41 -0
- package/server/services/behavior/modules/theory_of_mind.js +118 -0
- package/server/services/behavior/modules/turn_taking.js +238 -0
- package/server/services/behavior/pipeline.js +341 -0
- package/server/services/behavior/registry.js +78 -0
- package/server/services/behavior/signals.js +107 -0
- package/server/services/behavior/state.js +99 -0
- package/server/services/behavior/system_prompt.js +75 -0
- package/server/services/memory/manager.js +14 -33
- package/server/services/messaging/access_policy.js +269 -74
- package/server/services/messaging/automation.js +158 -27
- package/server/services/messaging/discord.js +11 -1
- package/server/services/messaging/formatting_guides.js +5 -4
- package/server/services/messaging/http_platforms.js +73 -28
- package/server/services/messaging/inbound_queue.js +74 -13
- package/server/services/messaging/inbound_store.js +33 -0
- package/server/services/messaging/manager.js +57 -5
- package/server/services/messaging/telegram.js +10 -1
- package/server/services/messaging/whatsapp.js +11 -0
- package/server/services/social_reach/channels/social_video.js +1 -1
- package/server/services/social_video/captions.js +2 -2
- package/server/services/social_video/service.js +194 -29
- package/server/services/voice/message.js +1 -1
- package/server/services/voice/runtime.js +2 -2
- package/server/utils/logger.js +19 -0
- package/server/services/ai/terminal_reply.js +0 -57
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const express = require('express');
|
|
4
|
+
const { requireAuth } = require('../middleware/auth');
|
|
5
|
+
const {
|
|
6
|
+
MODULE_IDS,
|
|
7
|
+
cloneDefaults,
|
|
8
|
+
getBehaviorConfig,
|
|
9
|
+
setBehaviorConfig,
|
|
10
|
+
resolveBehaviorConfig,
|
|
11
|
+
} = require('../services/behavior');
|
|
12
|
+
const {
|
|
13
|
+
getAgentIdFromRequest,
|
|
14
|
+
resolveAgentId,
|
|
15
|
+
} = require('../services/agents/manager');
|
|
16
|
+
const { invalidateSystemPromptCache } = require('../services/ai/systemPrompt');
|
|
17
|
+
|
|
18
|
+
const router = express.Router();
|
|
19
|
+
router.use(requireAuth);
|
|
20
|
+
|
|
21
|
+
function requestAgentId(req) {
|
|
22
|
+
return resolveAgentId(req.session.userId, getAgentIdFromRequest(req));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
router.get('/', (req, res) => {
|
|
26
|
+
const userId = req.session.userId;
|
|
27
|
+
const agentId = requestAgentId(req);
|
|
28
|
+
res.json({
|
|
29
|
+
agentId,
|
|
30
|
+
modules: MODULE_IDS,
|
|
31
|
+
defaults: cloneDefaults(),
|
|
32
|
+
config: getBehaviorConfig(userId, agentId),
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
router.put('/', (req, res) => {
|
|
37
|
+
if (!req.body?.config || typeof req.body.config !== 'object' || Array.isArray(req.body.config)) {
|
|
38
|
+
return res.status(400).json({ error: 'config must be an object' });
|
|
39
|
+
}
|
|
40
|
+
const userId = req.session.userId;
|
|
41
|
+
const agentId = requestAgentId(req);
|
|
42
|
+
const config = setBehaviorConfig(userId, agentId, req.body.config);
|
|
43
|
+
invalidateSystemPromptCache(userId, agentId);
|
|
44
|
+
return res.json({ agentId, config });
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
router.get('/effective', (req, res) => {
|
|
48
|
+
const userId = req.session.userId;
|
|
49
|
+
const agentId = requestAgentId(req);
|
|
50
|
+
const platform = String(req.query.platform || '').trim();
|
|
51
|
+
const chatId = String(req.query.chatId || '').trim();
|
|
52
|
+
if (!platform || !chatId) {
|
|
53
|
+
return res.status(400).json({ error: 'platform and chatId are required' });
|
|
54
|
+
}
|
|
55
|
+
return res.json({
|
|
56
|
+
agentId,
|
|
57
|
+
config: resolveBehaviorConfig(userId, agentId, {
|
|
58
|
+
platform,
|
|
59
|
+
chatId,
|
|
60
|
+
isGroup: req.query.isGroup !== 'false',
|
|
61
|
+
}),
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
router.get('/diagnostics', (req, res) => {
|
|
66
|
+
const userId = req.session.userId;
|
|
67
|
+
const agentId = requestAgentId(req);
|
|
68
|
+
const platform = String(req.query.platform || '').trim();
|
|
69
|
+
const chatId = String(req.query.chatId || '').trim();
|
|
70
|
+
if (!platform || !chatId) {
|
|
71
|
+
return res.status(400).json({ error: 'platform and chatId are required' });
|
|
72
|
+
}
|
|
73
|
+
const pipeline = req.app?.locals?.behaviorPipeline;
|
|
74
|
+
if (!pipeline?.getDiagnostics) {
|
|
75
|
+
return res.status(503).json({ error: 'Behavior runtime is not initialized' });
|
|
76
|
+
}
|
|
77
|
+
return res.json(pipeline.getDiagnostics(userId, agentId, platform, chatId));
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
module.exports = router;
|
|
@@ -346,6 +346,10 @@ router.put('/', async (req, res) => {
|
|
|
346
346
|
});
|
|
347
347
|
|
|
348
348
|
tx(Object.entries(normalizedBody));
|
|
349
|
+
if (Object.prototype.hasOwnProperty.call(normalizedBody, 'assistant_behavior_notes')) {
|
|
350
|
+
const { invalidateSystemPromptCache } = require('../services/ai/systemPrompt');
|
|
351
|
+
invalidateSystemPromptCache(userId, agentId);
|
|
352
|
+
}
|
|
349
353
|
|
|
350
354
|
if (Object.keys(normalizedBody).some((key) => VOICE_SETTING_KEYS.has(key))) {
|
|
351
355
|
const manager = req.app?.locals?.messagingManager;
|
|
@@ -77,7 +77,7 @@ function ensureMainAgent(userId) {
|
|
|
77
77
|
userId,
|
|
78
78
|
MAIN_AGENT_SLUG,
|
|
79
79
|
'Main',
|
|
80
|
-
'Default personal
|
|
80
|
+
'Default personal AI contact and fallback agent.',
|
|
81
81
|
'Handle general requests. Delegate to specialist agents only when their responsibilities clearly match.',
|
|
82
82
|
'',
|
|
83
83
|
);
|
|
@@ -126,7 +126,7 @@ async function summarizeMessages(
|
|
|
126
126
|
const prompt = [
|
|
127
127
|
{
|
|
128
128
|
role: 'system',
|
|
129
|
-
content: 'Compress conversation context. Preserve user goals, constraints, preferences, decisions, promised follow-ups, recurring schedules, important facts, tool outcomes, and
|
|
129
|
+
content: 'Compress conversation context. Preserve user goals, constraints, preferences, decisions, promised follow-ups, recurring schedules, important facts, tool outcomes, unresolved issues, and any user-stated communication preferences. Keep concrete details (names, dates, times, statuses) and avoid vague wording. Output plain text only.'
|
|
130
130
|
},
|
|
131
131
|
{
|
|
132
132
|
role: 'user',
|
|
@@ -20,6 +20,10 @@ const { summarizeCapabilityHealth } = require('../capabilityHealth');
|
|
|
20
20
|
const { shouldAcceptTaskComplete } = require('../completion');
|
|
21
21
|
const { shortenRunId, summarizeForLog } = require('../logFormat');
|
|
22
22
|
const { getProviderForUser } = require('../provider_selector');
|
|
23
|
+
const {
|
|
24
|
+
recordModelFailure,
|
|
25
|
+
recordModelSuccess,
|
|
26
|
+
} = require('../model_failure_cache');
|
|
23
27
|
const { runConversation } = require('./conversation_loop');
|
|
24
28
|
const {
|
|
25
29
|
TERMINAL_STATUSES,
|
|
@@ -33,6 +37,7 @@ const {
|
|
|
33
37
|
buildChurnAssessmentPrompt,
|
|
34
38
|
buildCompletionDecisionPrompt,
|
|
35
39
|
enforceTerminalReplyDecision,
|
|
40
|
+
enforceChurnAssessment,
|
|
36
41
|
normalizeChurnAssessment,
|
|
37
42
|
normalizeCompletionDecision,
|
|
38
43
|
resolveRunGoalContext,
|
|
@@ -85,8 +90,8 @@ const {
|
|
|
85
90
|
normalizeOutgoingMessage,
|
|
86
91
|
clampRunContext,
|
|
87
92
|
} = require('../messagingFallback');
|
|
88
|
-
const { isDeferredWorkReply } = require('../terminal_reply');
|
|
89
93
|
const {
|
|
94
|
+
assessResearchAdequacy,
|
|
90
95
|
summarizeToolExecutions,
|
|
91
96
|
} = require('../toolEvidence');
|
|
92
97
|
const {
|
|
@@ -637,6 +642,82 @@ class AgentEngine {
|
|
|
637
642
|
});
|
|
638
643
|
}
|
|
639
644
|
|
|
645
|
+
async inferStructured({
|
|
646
|
+
userId,
|
|
647
|
+
agentId = null,
|
|
648
|
+
modelId = null,
|
|
649
|
+
purpose = 'fast',
|
|
650
|
+
system,
|
|
651
|
+
prompt,
|
|
652
|
+
maxTokens = 400,
|
|
653
|
+
fallback = {},
|
|
654
|
+
signal = null,
|
|
655
|
+
}) {
|
|
656
|
+
const { getFailureFallbackModelId } = require('./conversation_loop');
|
|
657
|
+
const configuredFallbackId = getAiSettings(userId, agentId).fallback_model_id;
|
|
658
|
+
const failedModelIds = new Set();
|
|
659
|
+
let requestedModelId = modelId;
|
|
660
|
+
let selected = null;
|
|
661
|
+
let result = null;
|
|
662
|
+
|
|
663
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
664
|
+
selected = await getProviderForUser(
|
|
665
|
+
userId,
|
|
666
|
+
prompt,
|
|
667
|
+
false,
|
|
668
|
+
requestedModelId,
|
|
669
|
+
{
|
|
670
|
+
agentId,
|
|
671
|
+
signal,
|
|
672
|
+
selectionHint: {
|
|
673
|
+
purpose: purpose === 'general' ? 'general' : 'fast',
|
|
674
|
+
costMode: purpose === 'fast' ? 'economy' : 'balanced_auto',
|
|
675
|
+
},
|
|
676
|
+
},
|
|
677
|
+
);
|
|
678
|
+
try {
|
|
679
|
+
result = await this.requestStructuredJson({
|
|
680
|
+
provider: selected.provider,
|
|
681
|
+
providerName: selected.providerName,
|
|
682
|
+
model: selected.model,
|
|
683
|
+
messages: system ? [{ role: 'system', content: String(system) }] : [],
|
|
684
|
+
prompt: String(prompt || ''),
|
|
685
|
+
maxTokens,
|
|
686
|
+
normalize: (value) => value,
|
|
687
|
+
fallback,
|
|
688
|
+
telemetry: { userId, agentId, signal },
|
|
689
|
+
phase: 'behavior_inference',
|
|
690
|
+
});
|
|
691
|
+
recordModelSuccess(userId, agentId, selected.modelSelectionId);
|
|
692
|
+
break;
|
|
693
|
+
} catch (error) {
|
|
694
|
+
if (isAbortError(error, signal) || signal?.aborted) throw error;
|
|
695
|
+
recordModelFailure(userId, agentId, selected.modelSelectionId, error);
|
|
696
|
+
failedModelIds.add(selected.modelSelectionId);
|
|
697
|
+
if (attempt >= 2) throw error;
|
|
698
|
+
requestedModelId = await getFailureFallbackModelId(
|
|
699
|
+
userId,
|
|
700
|
+
agentId,
|
|
701
|
+
selected.modelSelectionId,
|
|
702
|
+
configuredFallbackId,
|
|
703
|
+
error,
|
|
704
|
+
signal,
|
|
705
|
+
failedModelIds,
|
|
706
|
+
);
|
|
707
|
+
if (!requestedModelId) throw error;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
return {
|
|
712
|
+
parsed: result.value,
|
|
713
|
+
raw: result.raw,
|
|
714
|
+
usage: result.usage,
|
|
715
|
+
model: selected.model,
|
|
716
|
+
modelSelectionId: selected.modelSelectionId,
|
|
717
|
+
providerName: selected.providerName,
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
|
|
640
721
|
async requestModelResponse({
|
|
641
722
|
provider,
|
|
642
723
|
providerName,
|
|
@@ -793,6 +874,11 @@ class AgentEngine {
|
|
|
793
874
|
}) {
|
|
794
875
|
const runMeta = options?.runId ? this.getRunMeta(options.runId) : null;
|
|
795
876
|
const goalContext = resolveRunGoalContext(runMeta, analysis, plan);
|
|
877
|
+
const researchAdequacy = assessResearchAdequacy({
|
|
878
|
+
analysis,
|
|
879
|
+
goalContext,
|
|
880
|
+
toolExecutions,
|
|
881
|
+
});
|
|
796
882
|
const response = await this.requestStructuredJson({
|
|
797
883
|
provider,
|
|
798
884
|
providerName,
|
|
@@ -808,6 +894,8 @@ class AgentEngine {
|
|
|
808
894
|
lastReply,
|
|
809
895
|
iteration,
|
|
810
896
|
maxIterations,
|
|
897
|
+
analysis,
|
|
898
|
+
researchAdequacy,
|
|
811
899
|
}),
|
|
812
900
|
maxTokens: 500,
|
|
813
901
|
normalize: (raw) => normalizeCompletionDecision(raw, 'continue'),
|
|
@@ -817,9 +905,15 @@ class AgentEngine {
|
|
|
817
905
|
phase: 'completion_decision',
|
|
818
906
|
});
|
|
819
907
|
return {
|
|
820
|
-
decision: enforceTerminalReplyDecision(response.value, lastReply
|
|
908
|
+
decision: enforceTerminalReplyDecision(response.value, lastReply, {
|
|
909
|
+
analysis,
|
|
910
|
+
goalContext,
|
|
911
|
+
toolExecutions,
|
|
912
|
+
researchAdequacy,
|
|
913
|
+
}),
|
|
821
914
|
usage: response.usage,
|
|
822
915
|
raw: response.raw,
|
|
916
|
+
researchAdequacy,
|
|
823
917
|
};
|
|
824
918
|
}
|
|
825
919
|
|
|
@@ -905,6 +999,12 @@ class AgentEngine {
|
|
|
905
999
|
goalContext,
|
|
906
1000
|
toolExecutions,
|
|
907
1001
|
iteration,
|
|
1002
|
+
analysis,
|
|
1003
|
+
researchAdequacy: assessResearchAdequacy({
|
|
1004
|
+
analysis,
|
|
1005
|
+
goalContext,
|
|
1006
|
+
toolExecutions,
|
|
1007
|
+
}),
|
|
908
1008
|
}),
|
|
909
1009
|
maxTokens: 200,
|
|
910
1010
|
normalize: normalizeChurnAssessment,
|
|
@@ -913,9 +1013,20 @@ class AgentEngine {
|
|
|
913
1013
|
telemetry: options,
|
|
914
1014
|
phase: 'churn_assessment',
|
|
915
1015
|
});
|
|
1016
|
+
const researchAdequacy = assessResearchAdequacy({
|
|
1017
|
+
analysis,
|
|
1018
|
+
goalContext,
|
|
1019
|
+
toolExecutions,
|
|
1020
|
+
});
|
|
916
1021
|
return {
|
|
917
|
-
assessment: response.value,
|
|
1022
|
+
assessment: enforceChurnAssessment(response.value, {
|
|
1023
|
+
analysis,
|
|
1024
|
+
goalContext,
|
|
1025
|
+
toolExecutions,
|
|
1026
|
+
researchAdequacy,
|
|
1027
|
+
}),
|
|
918
1028
|
usage: response.usage,
|
|
1029
|
+
researchAdequacy,
|
|
919
1030
|
};
|
|
920
1031
|
}
|
|
921
1032
|
|
|
@@ -942,6 +1053,15 @@ class AgentEngine {
|
|
|
942
1053
|
content: [
|
|
943
1054
|
'Return JSON only. Distill the current thread working state. Keep it concise and concrete.',
|
|
944
1055
|
'Track summary, open_commitments, unresolved_questions, referenced_entities, and last_verified_facts. Do not invent facts.',
|
|
1056
|
+
options.memoryScope?.scopeType === 'channel'
|
|
1057
|
+
? [
|
|
1058
|
+
'This is a shared room. Extract only durable room or participant facts that are safe and useful in this room.',
|
|
1059
|
+
'Do not infer or store facts about the authenticated owner from another participant’s message.',
|
|
1060
|
+
options.context?.socialIntelligence?.message?.sender
|
|
1061
|
+
? `For facts about the current speaker, use the canonical subject "${options.source}:${options.context.socialIntelligence.message.sender}".`
|
|
1062
|
+
: '',
|
|
1063
|
+
].filter(Boolean).join(' ')
|
|
1064
|
+
: '',
|
|
945
1065
|
buildMemoryConsolidationInstructions(new Date().toISOString()),
|
|
946
1066
|
'Schema:',
|
|
947
1067
|
JSON.stringify({
|
|
@@ -1018,6 +1138,14 @@ class AgentEngine {
|
|
|
1018
1138
|
agentId: options.agentId || null,
|
|
1019
1139
|
conversationId,
|
|
1020
1140
|
runId,
|
|
1141
|
+
scope: options.memoryScope || undefined,
|
|
1142
|
+
metadata: options.memoryScope
|
|
1143
|
+
? {
|
|
1144
|
+
trustLevel: 'shared_room_conversation',
|
|
1145
|
+
platform: options.source || null,
|
|
1146
|
+
chatId: options.chatId || null,
|
|
1147
|
+
}
|
|
1148
|
+
: undefined,
|
|
1021
1149
|
signal: options.signal,
|
|
1022
1150
|
},
|
|
1023
1151
|
);
|
|
@@ -1214,21 +1342,6 @@ class AgentEngine {
|
|
|
1214
1342
|
return options.reasoningEffort || process.env.REASONING_EFFORT || 'low';
|
|
1215
1343
|
}
|
|
1216
1344
|
|
|
1217
|
-
shouldFastCompleteVoiceReply({
|
|
1218
|
-
options = {},
|
|
1219
|
-
toolExecutions = [],
|
|
1220
|
-
failedStepCount = 0,
|
|
1221
|
-
messagingSent = false,
|
|
1222
|
-
lastReply = '',
|
|
1223
|
-
}) {
|
|
1224
|
-
return options.latencyProfile === 'voice'
|
|
1225
|
-
&& toolExecutions.length === 0
|
|
1226
|
-
&& failedStepCount === 0
|
|
1227
|
-
&& !messagingSent
|
|
1228
|
-
&& Boolean(String(lastReply || '').trim())
|
|
1229
|
-
&& !isDeferredWorkReply(lastReply);
|
|
1230
|
-
}
|
|
1231
|
-
|
|
1232
1345
|
getMessagingRetryLimit(maxIterations) {
|
|
1233
1346
|
// Cap at 3: more than 3 autonomous messaging retries indicates a structural
|
|
1234
1347
|
// problem (model unavailable, bad config) that more retries won't solve.
|
|
@@ -33,9 +33,9 @@ function shouldContinueAfterRecoverableToolFailure({
|
|
|
33
33
|
if (Number(remainingIterations || 0) <= 0) return false;
|
|
34
34
|
const failedExecution = latestFailedToolExecution(toolExecutions);
|
|
35
35
|
if (!isRecoverableInternalToolFailure(failedExecution)) return false;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
return
|
|
36
|
+
// Structural only: recoverable internal tool failure + remaining budget.
|
|
37
|
+
// Do not phrase-match the draft reply.
|
|
38
|
+
return Boolean(normalizeOutgoingMessage(lastContent || '') || failedExecution);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
function buildBlankAfterToolFailureGuidance(toolExecutions = []) {
|
|
@@ -46,7 +46,8 @@ function buildBlankAfterToolFailureGuidance(toolExecutions = []) {
|
|
|
46
46
|
return [
|
|
47
47
|
`The previous tool "${toolName}" failed with: ${summarizeForLog(failure, 240)}.`,
|
|
48
48
|
'The latest assistant turn returned no user-facing answer and no tool call, so the task is not terminal.',
|
|
49
|
-
'Continue with the next safe recovery action: retry with corrected arguments, use another available tool, verify from existing evidence, or report a real blocker only if no autonomous path remains.',
|
|
49
|
+
'Continue with the next safe recovery action now in this same turn: retry with corrected arguments, use another available tool, verify from existing evidence, or report a real blocker only if no autonomous path remains.',
|
|
50
|
+
'Do not invent a finished result. Prefer a concrete recovery step or a truthful partial answer over silence.',
|
|
50
51
|
].join(' ');
|
|
51
52
|
}
|
|
52
53
|
|