neoagent 3.2.1-beta.10 → 3.2.1-beta.11

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.
Files changed (61) hide show
  1. package/flutter_app/lib/main_controller.dart +46 -4
  2. package/flutter_app/lib/main_models.dart +4 -2
  3. package/flutter_app/lib/main_operations.dart +0 -49
  4. package/flutter_app/lib/main_settings.dart +338 -0
  5. package/flutter_app/lib/src/backend_client.dart +19 -0
  6. package/package.json +1 -1
  7. package/server/http/routes.js +1 -0
  8. package/server/public/.last_build_id +1 -1
  9. package/server/public/flutter_bootstrap.js +1 -1
  10. package/server/public/main.dart.js +65635 -65088
  11. package/server/routes/behavior.js +80 -0
  12. package/server/routes/settings.js +4 -0
  13. package/server/services/ai/history.js +1 -1
  14. package/server/services/ai/loop/agent_engine_core.js +64 -16
  15. package/server/services/ai/loop/completion_judge.js +211 -44
  16. package/server/services/ai/loop/conversation_loop.js +6 -11
  17. package/server/services/ai/loop/messaging_delivery.js +47 -0
  18. package/server/services/ai/messagingFallback.js +2 -2
  19. package/server/services/ai/systemPrompt.js +30 -128
  20. package/server/services/ai/taskAnalysis.js +47 -2
  21. package/server/services/ai/toolEvidence.js +65 -159
  22. package/server/services/ai/tools.js +60 -8
  23. package/server/services/behavior/config.js +251 -0
  24. package/server/services/behavior/defaults.js +68 -0
  25. package/server/services/behavior/delivery.js +176 -0
  26. package/server/services/behavior/index.js +43 -0
  27. package/server/services/behavior/model_client.js +35 -0
  28. package/server/services/behavior/modules/agent_identity.js +37 -0
  29. package/server/services/behavior/modules/channel_style.js +28 -0
  30. package/server/services/behavior/modules/index.js +29 -0
  31. package/server/services/behavior/modules/norms.js +101 -0
  32. package/server/services/behavior/modules/persona.js +48 -0
  33. package/server/services/behavior/modules/persona_prompt.js +33 -0
  34. package/server/services/behavior/modules/social_memory.js +86 -0
  35. package/server/services/behavior/modules/social_observability.js +94 -0
  36. package/server/services/behavior/modules/social_signals.js +41 -0
  37. package/server/services/behavior/modules/theory_of_mind.js +110 -0
  38. package/server/services/behavior/modules/turn_taking.js +237 -0
  39. package/server/services/behavior/pipeline.js +285 -0
  40. package/server/services/behavior/registry.js +78 -0
  41. package/server/services/behavior/signals.js +107 -0
  42. package/server/services/behavior/state.js +99 -0
  43. package/server/services/behavior/system_prompt.js +75 -0
  44. package/server/services/memory/manager.js +14 -33
  45. package/server/services/messaging/access_policy.js +10 -6
  46. package/server/services/messaging/automation.js +158 -27
  47. package/server/services/messaging/discord.js +11 -1
  48. package/server/services/messaging/formatting_guides.js +2 -4
  49. package/server/services/messaging/http_platforms.js +4 -3
  50. package/server/services/messaging/inbound_queue.js +74 -13
  51. package/server/services/messaging/inbound_store.js +33 -0
  52. package/server/services/messaging/manager.js +18 -0
  53. package/server/services/messaging/telegram.js +10 -1
  54. package/server/services/messaging/whatsapp.js +11 -0
  55. package/server/services/social_reach/channels/social_video.js +1 -1
  56. package/server/services/social_video/captions.js +2 -2
  57. package/server/services/social_video/service.js +194 -29
  58. package/server/services/voice/message.js +1 -1
  59. package/server/services/voice/runtime.js +2 -2
  60. package/server/utils/logger.js +19 -0
  61. package/server/services/ai/terminal_reply.js +0 -18
@@ -0,0 +1,285 @@
1
+ 'use strict';
2
+
3
+ const { resolveBehaviorConfig, isModuleEnabled } = require('./config');
4
+ const {
5
+ bumpTurnEpoch,
6
+ getThreadState,
7
+ isTurnCurrent,
8
+ markSpoke,
9
+ } = require('./state');
10
+ const { createBehaviorRegistry } = require('./registry');
11
+ const { BEHAVIOR_MODULES } = require('./modules');
12
+ const { createServiceLogger } = require('../../utils/logger');
13
+
14
+ const logger = createServiceLogger('Behavior');
15
+
16
+ function createBehaviorPipeline(deps = {}) {
17
+ const memoryManager = deps.memoryManager || null;
18
+ const agentEngine = deps.agentEngine || null;
19
+ const io = deps.io || null;
20
+ const registry = createBehaviorRegistry(BEHAVIOR_MODULES);
21
+
22
+ function effectiveConfig(userId, agentId, msg) {
23
+ const config = resolveBehaviorConfig(userId, agentId, {
24
+ platform: msg.platform,
25
+ chatId: msg.chatId,
26
+ isGroup: Boolean(msg.isGroup),
27
+ });
28
+ if (
29
+ msg.isGroup
30
+ && config.participationModeSource === 'default'
31
+ && msg.accessPolicyRequireMention === true
32
+ ) {
33
+ config.participationMode = 'mention_only';
34
+ }
35
+ return config;
36
+ }
37
+
38
+ function noteInbound({ userId, agentId, msg }) {
39
+ const state = bumpTurnEpoch(userId, agentId, msg.platform, msg.chatId);
40
+ msg.behaviorTurnEpoch = state.turnEpoch;
41
+ return state.turnEpoch;
42
+ }
43
+
44
+ function scheduleBackground(baseCtx) {
45
+ const task = async (backgroundSignal) => {
46
+ const ctx = { ...baseCtx, signal: backgroundSignal };
47
+ await registry.run('afterTurn', ctx);
48
+ };
49
+ const key = [
50
+ 'social-background',
51
+ baseCtx.userId,
52
+ baseCtx.agentId || 'main',
53
+ baseCtx.msg.platform,
54
+ baseCtx.msg.chatId,
55
+ ].join(':');
56
+ const promise = agentEngine?.trackBackgroundTask
57
+ ? agentEngine.trackBackgroundTask(task, { key, coalesce: true, signal: baseCtx.signal })
58
+ : Promise.resolve().then(() => task(baseCtx.signal));
59
+ promise.catch((error) => {
60
+ if (!baseCtx.signal?.aborted) logger.warn('background analysis failed:', error?.message || error);
61
+ });
62
+ }
63
+
64
+ async function handleInbound({ userId, agentId, msg, signal = null }) {
65
+ const config = effectiveConfig(userId, agentId, msg);
66
+ const turnEpoch = Number(msg.behaviorTurnEpoch)
67
+ || noteInbound({ userId, agentId, msg });
68
+ if (config.enabled === false) {
69
+ return {
70
+ engage: true,
71
+ decision: {
72
+ decision: 'speak',
73
+ needScore: 1,
74
+ confidence: 1,
75
+ reasonCodes: ['behavior_disabled'],
76
+ urgency: 'medium',
77
+ rationale: 'Behavior modules are disabled; using the standard response path.',
78
+ tokenPath: 'gate_skip',
79
+ latencyMs: 0,
80
+ turnEpoch,
81
+ },
82
+ config,
83
+ promptBlocks: [],
84
+ observeResult: null,
85
+ };
86
+ }
87
+
88
+ const baseCtx = {
89
+ userId,
90
+ agentId,
91
+ msg,
92
+ config,
93
+ signal,
94
+ memoryManager,
95
+ agentEngine,
96
+ turnEpoch,
97
+ isModuleEnabled: (moduleId) => isModuleEnabled(config, moduleId),
98
+ };
99
+
100
+ const observations = await registry.run('observe', baseCtx);
101
+ const observeResult = observations.find((item) => item.moduleId === 'social_memory')?.value || null;
102
+ if (msg.isGroup) scheduleBackground(baseCtx);
103
+
104
+ const memoryHints = [];
105
+ if (observeResult?.scopeId) memoryHints.push(`channel:${observeResult.scopeId}`);
106
+
107
+ let decision;
108
+ if (!isModuleEnabled(config, 'turn_taking')) {
109
+ decision = {
110
+ decision: 'speak',
111
+ needScore: 1,
112
+ confidence: 1,
113
+ reasonCodes: ['turn_taking_disabled'],
114
+ urgency: 'medium',
115
+ rationale: 'Turn-taking is disabled; using the standard response path.',
116
+ tokenPath: 'gate_skip',
117
+ latencyMs: 0,
118
+ turnEpoch,
119
+ };
120
+ } else {
121
+ decision = (await registry.run('decide', {
122
+ ...baseCtx,
123
+ memoryHints,
124
+ })).find((item) => item.moduleId === 'turn_taking')?.value;
125
+ }
126
+ if (!decision) {
127
+ throw new Error('The turn-taking module did not return a decision.');
128
+ }
129
+
130
+ if (io && userId) {
131
+ io.to(`user:${userId}`).emit('behavior:decision', {
132
+ platform: msg.platform,
133
+ chatId: msg.chatId,
134
+ agentId,
135
+ isGroup: Boolean(msg.isGroup),
136
+ decision: decision.decision,
137
+ confidence: decision.confidence,
138
+ needScore: decision.needScore,
139
+ reasonCodes: decision.reasonCodes || [],
140
+ urgency: decision.urgency,
141
+ rationale: decision.rationale || '',
142
+ tokenPath: decision.tokenPath || 'gate_only',
143
+ turnEpoch: decision.turnEpoch,
144
+ model: decision.model || null,
145
+ at: new Date().toISOString(),
146
+ });
147
+ }
148
+
149
+ if (decision.decision !== 'speak') {
150
+ return {
151
+ engage: false,
152
+ decision,
153
+ config,
154
+ promptBlocks: [],
155
+ observeResult,
156
+ };
157
+ }
158
+
159
+ const promptBlocks = await registry.composeContext(baseCtx);
160
+
161
+ return {
162
+ engage: true,
163
+ decision,
164
+ config,
165
+ promptBlocks,
166
+ observeResult,
167
+ };
168
+ }
169
+
170
+ async function refineAndMaybeDeliver({
171
+ userId,
172
+ agentId,
173
+ msg,
174
+ config,
175
+ draft,
176
+ messagingManager,
177
+ runId = null,
178
+ signal = null,
179
+ mediaPath = null,
180
+ deliver = false,
181
+ turnEpoch = null,
182
+ }) {
183
+ const expectedEpoch = Number(turnEpoch || msg.behaviorTurnEpoch || 0);
184
+ if (msg.isGroup && !isTurnCurrent(
185
+ userId,
186
+ agentId,
187
+ msg.platform,
188
+ msg.chatId,
189
+ expectedEpoch,
190
+ )) {
191
+ return {
192
+ action: 'suppress',
193
+ content: '[NO RESPONSE]',
194
+ delivered: false,
195
+ suppressed: true,
196
+ reasonCodes: ['stale_turn'],
197
+ };
198
+ }
199
+ const tom = await registry.get('theory_of_mind').refineDraft({
200
+ userId,
201
+ agentId,
202
+ msg,
203
+ config,
204
+ draft,
205
+ signal,
206
+ agentEngine,
207
+ });
208
+
209
+ const content = tom.content;
210
+ if (!deliver || !messagingManager) {
211
+ return { ...tom, delivered: false, content };
212
+ }
213
+
214
+ if (!content || content.toUpperCase() === '[NO RESPONSE]') {
215
+ return { ...tom, delivered: false, suppressed: true, content };
216
+ }
217
+
218
+ const deliveryConfig = isModuleEnabled(config, 'delivery')
219
+ ? config
220
+ : { ...config, deliveryStyle: 'single' };
221
+ const delivery = await registry.get('delivery').deliver({
222
+ messagingManager,
223
+ userId,
224
+ agentId,
225
+ platform: msg.platform,
226
+ chatId: msg.chatId,
227
+ content,
228
+ config: deliveryConfig,
229
+ runId,
230
+ signal,
231
+ mediaPath,
232
+ turnEpoch: expectedEpoch,
233
+ beforeBubble: () => !msg.isGroup || isTurnCurrent(
234
+ userId,
235
+ agentId,
236
+ msg.platform,
237
+ msg.chatId,
238
+ expectedEpoch,
239
+ ),
240
+ });
241
+
242
+ if (
243
+ (delivery?.success !== false && delivery?.suppressed !== true)
244
+ || Number(delivery?.deliveredBubbles || 0) > 0
245
+ ) {
246
+ markSpoke(userId, agentId, msg.platform, msg.chatId);
247
+ }
248
+
249
+ return {
250
+ ...tom,
251
+ delivered: delivery?.success !== false && delivery?.suppressed !== true,
252
+ suppressed: delivery?.suppressed === true,
253
+ delivery,
254
+ content,
255
+ };
256
+ }
257
+
258
+ function getDiagnostics(userId, agentId, platform, chatId) {
259
+ const config = resolveBehaviorConfig(userId, agentId, {
260
+ platform,
261
+ chatId,
262
+ isGroup: true,
263
+ });
264
+ const state = getThreadState(userId, agentId, platform, chatId);
265
+ return {
266
+ config,
267
+ state,
268
+ modules: Object.fromEntries(
269
+ Object.keys(config.modules || {}).map((id) => [id, isModuleEnabled(config, id)]),
270
+ ),
271
+ };
272
+ }
273
+
274
+ return {
275
+ registry,
276
+ noteInbound,
277
+ handleInbound,
278
+ refineAndMaybeDeliver,
279
+ getDiagnostics,
280
+ };
281
+ }
282
+
283
+ module.exports = {
284
+ createBehaviorPipeline,
285
+ };
@@ -0,0 +1,78 @@
1
+ 'use strict';
2
+
3
+ const LIFECYCLE_STAGES = Object.freeze([
4
+ 'observe',
5
+ 'decide',
6
+ 'composeContext',
7
+ 'composeSystemPrompt',
8
+ 'refineDraft',
9
+ 'deliver',
10
+ 'afterTurn',
11
+ ]);
12
+
13
+ function createBehaviorRegistry(modules) {
14
+ const byId = new Map();
15
+ for (const module of modules) {
16
+ const id = String(module?.id || '').trim();
17
+ if (!id) throw new Error('Behavior modules require a stable id.');
18
+ if (byId.has(id)) throw new Error(`Duplicate behavior module id: ${id}`);
19
+ byId.set(id, module);
20
+ }
21
+
22
+ function list() {
23
+ return [...byId.values()];
24
+ }
25
+
26
+ function get(id) {
27
+ return byId.get(id) || null;
28
+ }
29
+
30
+ async function run(stage, ctx) {
31
+ if (!LIFECYCLE_STAGES.includes(stage)) {
32
+ throw new Error(`Unknown behavior lifecycle stage: ${stage}`);
33
+ }
34
+ const results = [];
35
+ for (const module of byId.values()) {
36
+ if (ctx.isModuleEnabled && !ctx.isModuleEnabled(module.id)) continue;
37
+ if (typeof module[stage] !== 'function') continue;
38
+ results.push({ moduleId: module.id, value: await module[stage](ctx) });
39
+ }
40
+ return results;
41
+ }
42
+
43
+ async function composeContext(ctx) {
44
+ const contributions = [];
45
+ const keys = new Set();
46
+ for (const { moduleId, value } of await run('composeContext', ctx)) {
47
+ const entries = Array.isArray(value) ? value : [value];
48
+ for (const entry of entries) {
49
+ if (!entry?.content) continue;
50
+ const key = String(entry.key || moduleId).trim();
51
+ if (keys.has(key)) {
52
+ throw new Error(`Duplicate behavior prompt contribution: ${key}`);
53
+ }
54
+ keys.add(key);
55
+ contributions.push({
56
+ key,
57
+ priority: Number(entry.priority || 0),
58
+ content: String(entry.content).trim(),
59
+ });
60
+ }
61
+ }
62
+ return contributions
63
+ .sort((left, right) => right.priority - left.priority)
64
+ .map((entry) => entry.content);
65
+ }
66
+
67
+ return {
68
+ get,
69
+ list,
70
+ run,
71
+ composeContext,
72
+ };
73
+ }
74
+
75
+ module.exports = {
76
+ LIFECYCLE_STAGES,
77
+ createBehaviorRegistry,
78
+ };
@@ -0,0 +1,107 @@
1
+ 'use strict';
2
+
3
+ const db = require('../../db/database');
4
+
5
+ function truncate(text, max = 280) {
6
+ const value = String(text || '').replace(/\s+/g, ' ').trim();
7
+ if (value.length <= max) return value;
8
+ return `${value.slice(0, max - 1)}…`;
9
+ }
10
+
11
+ function buildChannelScopeId(platform, chatId) {
12
+ return `${String(platform || '').trim()}:${String(chatId || '').trim()}`;
13
+ }
14
+
15
+ function loadRecentRoomMessages({ userId, agentId, platform, chatId, limit = 12 }) {
16
+ const rows = db.prepare(
17
+ `SELECT role, content, created_at, metadata
18
+ FROM messages
19
+ WHERE user_id = ?
20
+ AND agent_id IS ?
21
+ AND platform = ?
22
+ AND platform_chat_id = ?
23
+ ORDER BY created_at DESC
24
+ LIMIT ?`,
25
+ ).all(userId, agentId, platform, String(chatId), Math.max(1, Math.min(Number(limit) || 12, 30)));
26
+
27
+ return rows.reverse().map((row) => {
28
+ let metadata = null;
29
+ try {
30
+ metadata = row.metadata ? JSON.parse(row.metadata) : null;
31
+ } catch {
32
+ metadata = null;
33
+ }
34
+ const sender = row.role === 'assistant'
35
+ ? 'assistant'
36
+ : (metadata?.senderDisplayName || metadata?.senderName || metadata?.sender || 'participant');
37
+ return {
38
+ role: row.role,
39
+ sender,
40
+ content: truncate(row.content, 320),
41
+ createdAt: row.created_at,
42
+ };
43
+ });
44
+ }
45
+
46
+ function buildDecisionPacket({
47
+ msg,
48
+ config,
49
+ threadState,
50
+ roomMessages = [],
51
+ localMemoryHints = [],
52
+ }) {
53
+ const recent = Array.isArray(msg.channelContext) && msg.channelContext.length
54
+ ? msg.channelContext.slice(-12).map((item) => ({
55
+ sender: item.author || item.sender || 'participant',
56
+ content: truncate(item.content, 280),
57
+ }))
58
+ : roomMessages.slice(-12).map((item) => ({
59
+ sender: item.sender,
60
+ content: item.content,
61
+ }));
62
+
63
+ const secondsSinceSpoke = threadState?.lastSpokeAt
64
+ ? Math.max(0, Math.round((Date.now() - Date.parse(threadState.lastSpokeAt)) / 1000))
65
+ : null;
66
+
67
+ return {
68
+ chat: {
69
+ platform: msg.platform,
70
+ chatId: String(msg.chatId || ''),
71
+ isGroup: Boolean(msg.isGroup),
72
+ groupName: msg.groupName || msg.guildName || msg.channelName || null,
73
+ },
74
+ sender: {
75
+ id: msg.sender || null,
76
+ name: msg.senderName || msg.senderDisplayName || msg.senderUsername || null,
77
+ username: msg.senderUsername || null,
78
+ tag: msg.senderTag || null,
79
+ },
80
+ event: {
81
+ content: truncate(msg.content, 800),
82
+ hasMedia: Boolean(msg.localMediaPath || msg.mediaType),
83
+ mediaType: msg.mediaType || null,
84
+ wasMentioned: msg.wasMentioned === true,
85
+ repliedToAgent: msg.repliedToAgent === true,
86
+ timestamp: msg.timestamp || new Date().toISOString(),
87
+ },
88
+ room: {
89
+ recentMessages: recent,
90
+ secondsSinceAgentSpoke: secondsSinceSpoke,
91
+ recentSilenceCount: Number(threadState?.recentSilenceCount || 0),
92
+ },
93
+ policy: {
94
+ participationMode: config.participationMode || 'automatic',
95
+ minimumNeedScore: Number(config.minimumNeedScore ?? 0.72),
96
+ groupDefaultPosture: 'prefer_hold_back',
97
+ },
98
+ roomHints: Array.isArray(localMemoryHints) ? localMemoryHints.slice(0, 4) : [],
99
+ };
100
+ }
101
+
102
+ module.exports = {
103
+ truncate,
104
+ buildChannelScopeId,
105
+ loadRecentRoomMessages,
106
+ buildDecisionPacket,
107
+ };
@@ -0,0 +1,99 @@
1
+ 'use strict';
2
+
3
+ const MAX_THREAD_STATES = 1000;
4
+ const THREAD_STATE_TTL_MS = 7 * 24 * 60 * 60 * 1000;
5
+ const threadStates = new Map();
6
+
7
+ function stateKey(userId, agentId, platform, chatId) {
8
+ return [
9
+ String(userId || ''),
10
+ String(agentId || 'main'),
11
+ String(platform || ''),
12
+ String(chatId || ''),
13
+ ].join('::');
14
+ }
15
+
16
+ function defaultState() {
17
+ return {
18
+ turnEpoch: 0,
19
+ lastDecision: null,
20
+ lastDecisionAt: null,
21
+ lastSpokeAt: null,
22
+ messageCountSinceNorms: 0,
23
+ normsPromptBlock: '',
24
+ normsUpdatedAt: null,
25
+ lastObservabilityAt: null,
26
+ lastObservabilitySummary: null,
27
+ messageCountSinceObservability: 0,
28
+ recentSilenceCount: 0,
29
+ recentSignals: [],
30
+ };
31
+ }
32
+
33
+ function evictThreadStates() {
34
+ const cutoff = Date.now() - THREAD_STATE_TTL_MS;
35
+ for (const [key, entry] of threadStates.entries()) {
36
+ if (entry.touchedAt < cutoff) threadStates.delete(key);
37
+ }
38
+ while (threadStates.size > MAX_THREAD_STATES) {
39
+ const oldest = threadStates.keys().next().value;
40
+ if (oldest == null) break;
41
+ threadStates.delete(oldest);
42
+ }
43
+ }
44
+
45
+ function getThreadState(userId, agentId, platform, chatId) {
46
+ const key = stateKey(userId, agentId, platform, chatId);
47
+ const entry = threadStates.get(key);
48
+ if (!entry) return defaultState();
49
+ threadStates.delete(key);
50
+ threadStates.set(key, entry);
51
+ entry.touchedAt = Date.now();
52
+ return { ...defaultState(), ...entry.value };
53
+ }
54
+
55
+ function setThreadState(userId, agentId, platform, chatId, patch = {}) {
56
+ const key = stateKey(userId, agentId, platform, chatId);
57
+ const current = getThreadState(userId, agentId, platform, chatId);
58
+ const next = {
59
+ ...current,
60
+ ...patch,
61
+ };
62
+ threadStates.delete(key);
63
+ threadStates.set(key, { value: next, touchedAt: Date.now() });
64
+ evictThreadStates();
65
+ return next;
66
+ }
67
+
68
+ function bumpTurnEpoch(userId, agentId, platform, chatId) {
69
+ const current = getThreadState(userId, agentId, platform, chatId);
70
+ return setThreadState(userId, agentId, platform, chatId, {
71
+ turnEpoch: Number(current.turnEpoch || 0) + 1,
72
+ });
73
+ }
74
+
75
+ function isTurnCurrent(userId, agentId, platform, chatId, turnEpoch) {
76
+ return Number(getThreadState(userId, agentId, platform, chatId).turnEpoch || 0)
77
+ === Number(turnEpoch || 0);
78
+ }
79
+
80
+ function markSpoke(userId, agentId, platform, chatId) {
81
+ return setThreadState(userId, agentId, platform, chatId, {
82
+ lastSpokeAt: new Date().toISOString(),
83
+ recentSilenceCount: 0,
84
+ });
85
+ }
86
+
87
+ function clearThreadStates() {
88
+ threadStates.clear();
89
+ }
90
+
91
+ module.exports = {
92
+ getThreadState,
93
+ setThreadState,
94
+ bumpTurnEpoch,
95
+ isTurnCurrent,
96
+ markSpoke,
97
+ clearThreadStates,
98
+ stateKey,
99
+ };
@@ -0,0 +1,75 @@
1
+ 'use strict';
2
+
3
+ const { resolveBehaviorConfig, isModuleEnabled } = require('./config');
4
+ const { createBehaviorRegistry } = require('./registry');
5
+ const { BEHAVIOR_MODULES } = require('./modules');
6
+
7
+ async function buildBehaviorSystemPrompt({
8
+ userId,
9
+ agentId,
10
+ triggerSource,
11
+ context,
12
+ memoryManager,
13
+ }) {
14
+ const config = resolveBehaviorConfig(userId, agentId, {
15
+ platform: context.source || null,
16
+ chatId: context.chatId || null,
17
+ isGroup: context.memoryAudience === 'shared' || context.socialIntelligence?.isGroup === true,
18
+ });
19
+ const ctx = {
20
+ ...context,
21
+ userId,
22
+ agentId,
23
+ triggerSource,
24
+ audience: context.memoryAudience || 'owner',
25
+ config,
26
+ memoryManager,
27
+ };
28
+ const registry = createBehaviorRegistry(BEHAVIOR_MODULES);
29
+ const results = await registry.run('composeSystemPrompt', {
30
+ ...ctx,
31
+ isModuleEnabled: (moduleId) => isModuleEnabled(config, moduleId),
32
+ });
33
+ const priorities = {
34
+ channel_style: 100,
35
+ persona: 80,
36
+ agent_identity: 60,
37
+ };
38
+ const entries = [];
39
+ for (const entry of results) {
40
+ if (!entry.value) continue;
41
+ const priority = priorities[entry.moduleId] || 0;
42
+ if (typeof entry.value === 'string') {
43
+ entries.push({
44
+ moduleId: entry.moduleId,
45
+ content: entry.value,
46
+ section: entry.moduleId === 'channel_style' ? 'stable' : 'dynamic',
47
+ priority,
48
+ });
49
+ continue;
50
+ }
51
+ for (const section of ['stable', 'dynamic']) {
52
+ const values = Array.isArray(entry.value[section])
53
+ ? entry.value[section]
54
+ : [entry.value[section]];
55
+ for (const content of values) {
56
+ if (!content) continue;
57
+ entries.push({
58
+ moduleId: entry.moduleId,
59
+ content,
60
+ section,
61
+ priority,
62
+ });
63
+ }
64
+ }
65
+ }
66
+ entries.sort((left, right) => right.priority - left.priority);
67
+ return {
68
+ stable: entries.filter((entry) => entry.section === 'stable').map((entry) => entry.content),
69
+ dynamic: entries.filter((entry) => entry.section === 'dynamic').map((entry) => entry.content),
70
+ };
71
+ }
72
+
73
+ module.exports = {
74
+ buildBehaviorSystemPrompt,
75
+ };