neoagent 3.2.1-beta.9 → 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 +110 -17
- package/server/services/ai/loop/blank_recovery.js +5 -4
- package/server/services/ai/loop/completion_judge.js +226 -33
- 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 +86 -12
- package/server/services/ai/toolEvidence.js +68 -224
- 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
|
@@ -12,7 +12,6 @@ const {
|
|
|
12
12
|
normalizeOutgoingMessageForPlatform,
|
|
13
13
|
} = require('../messaging/formatting_guides');
|
|
14
14
|
const { INTERIM_KINDS, normalizeInterimKind } = require('./interim');
|
|
15
|
-
const { isDeferredWorkReply } = require('./terminal_reply');
|
|
16
15
|
const { normalizeWhatsAppId } = require('../../utils/whatsapp');
|
|
17
16
|
const {
|
|
18
17
|
executeIntegratedTool,
|
|
@@ -2401,6 +2400,13 @@ async function executeTool(toolName, args, context, engine) {
|
|
|
2401
2400
|
if (!engine || !runId) {
|
|
2402
2401
|
return { error: 'Interim updates require an active run.' };
|
|
2403
2402
|
}
|
|
2403
|
+
if (engine.getRunMeta(runId)?.messagingContext?.behavior?.isGroup === true) {
|
|
2404
|
+
return {
|
|
2405
|
+
sent: false,
|
|
2406
|
+
skipped: true,
|
|
2407
|
+
reason: 'Interim progress updates are suppressed in shared rooms.',
|
|
2408
|
+
};
|
|
2409
|
+
}
|
|
2404
2410
|
const interimContent = typeof args.content === 'string' ? args.content : '';
|
|
2405
2411
|
const expectsReply = args.expects_reply === true;
|
|
2406
2412
|
const deferFollowUp = args.defer_follow_up === true;
|
|
@@ -2438,16 +2444,6 @@ async function executeTool(toolName, args, context, engine) {
|
|
|
2438
2444
|
platform: args.platform,
|
|
2439
2445
|
to: args.to,
|
|
2440
2446
|
});
|
|
2441
|
-
if (
|
|
2442
|
-
!suppressReply
|
|
2443
|
-
&& triggerSource === 'messaging'
|
|
2444
|
-
&& originDelivery
|
|
2445
|
-
&& isDeferredWorkReply(normalizedMessage)
|
|
2446
|
-
) {
|
|
2447
|
-
return {
|
|
2448
|
-
error: 'send_message cannot end the run with a promise or progress-only reply. Continue the work, or use send_interim_update for a factual interim update.',
|
|
2449
|
-
};
|
|
2450
|
-
}
|
|
2451
2447
|
if (isProactiveTrigger(triggerSource)) {
|
|
2452
2448
|
const proactiveValidation = validateProactiveSendMessageArgs({
|
|
2453
2449
|
purpose: args.purpose,
|
|
@@ -2506,13 +2502,58 @@ async function executeTool(toolName, args, context, engine) {
|
|
|
2506
2502
|
if (triggerSource === 'messaging' && originDelivery) {
|
|
2507
2503
|
await engine?.stopMessagingProgressSupervisor?.(runId);
|
|
2508
2504
|
}
|
|
2509
|
-
const
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2505
|
+
const behavior = runState?.messagingContext?.behavior;
|
|
2506
|
+
const behaviorPipeline = app?.locals?.behaviorPipeline;
|
|
2507
|
+
let deliveredContent = normalizedMessage;
|
|
2508
|
+
let sendResult;
|
|
2509
|
+
if (
|
|
2510
|
+
originDelivery
|
|
2511
|
+
&& behavior
|
|
2512
|
+
&& behavior.enabled !== false
|
|
2513
|
+
&& behaviorPipeline
|
|
2514
|
+
&& typeof behaviorPipeline.refineAndMaybeDeliver === 'function'
|
|
2515
|
+
) {
|
|
2516
|
+
const behaviorResult = await behaviorPipeline.refineAndMaybeDeliver({
|
|
2517
|
+
userId,
|
|
2518
|
+
agentId,
|
|
2519
|
+
msg: behavior.message,
|
|
2520
|
+
config: behavior.config,
|
|
2521
|
+
draft: normalizedMessage,
|
|
2522
|
+
messagingManager: manager,
|
|
2523
|
+
runId,
|
|
2524
|
+
signal,
|
|
2525
|
+
mediaPath: args.media_path,
|
|
2526
|
+
turnEpoch: behavior.turnEpoch,
|
|
2527
|
+
deliver: true,
|
|
2528
|
+
});
|
|
2529
|
+
deliveredContent = behaviorResult.content;
|
|
2530
|
+
if (behaviorResult.delivered) {
|
|
2531
|
+
sendResult = { success: true, behavior: true, result: behaviorResult.delivery };
|
|
2532
|
+
} else if (behaviorResult.suppressed) {
|
|
2533
|
+
markProactiveNoResponse({ runState, deliveryState });
|
|
2534
|
+
sendResult = {
|
|
2535
|
+
success: true,
|
|
2536
|
+
sent: false,
|
|
2537
|
+
suppressed: true,
|
|
2538
|
+
reason: behaviorResult.reasonCodes?.[0] || 'behavior_suppressed',
|
|
2539
|
+
};
|
|
2540
|
+
} else {
|
|
2541
|
+
sendResult = {
|
|
2542
|
+
success: false,
|
|
2543
|
+
error: behaviorResult.delivery?.error
|
|
2544
|
+
|| behaviorResult.delivery?.reason
|
|
2545
|
+
|| 'Behavior delivery was not confirmed.',
|
|
2546
|
+
};
|
|
2547
|
+
}
|
|
2548
|
+
} else {
|
|
2549
|
+
sendResult = await manager.sendMessage(userId, args.platform, args.to, args.content, {
|
|
2550
|
+
agentId,
|
|
2551
|
+
mediaPath: args.media_path,
|
|
2552
|
+
runId,
|
|
2553
|
+
persistConversation: triggerSource === 'schedule' || triggerSource === 'tasks',
|
|
2554
|
+
signal,
|
|
2555
|
+
});
|
|
2556
|
+
}
|
|
2516
2557
|
// Track that the agent explicitly sent a message during this run
|
|
2517
2558
|
if (
|
|
2518
2559
|
!suppressReply
|
|
@@ -2520,7 +2561,7 @@ async function executeTool(toolName, args, context, engine) {
|
|
|
2520
2561
|
&& sendResult?.suppressed !== true
|
|
2521
2562
|
&& originDelivery
|
|
2522
2563
|
) {
|
|
2523
|
-
markProactiveMessageSent({ runState, deliveryState, content:
|
|
2564
|
+
markProactiveMessageSent({ runState, deliveryState, content: deliveredContent });
|
|
2524
2565
|
if (runState && triggerSource === 'messaging') {
|
|
2525
2566
|
runState.explicitMessageSent = true;
|
|
2526
2567
|
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const db = require('../../db/database');
|
|
4
|
+
const { isMainAgent } = require('../agents/manager');
|
|
5
|
+
const { MODULE_IDS, cloneDefaults, DEFAULT_MODULE_CONFIG } = require('./defaults');
|
|
6
|
+
|
|
7
|
+
const SETTINGS_KEY = 'behavior_modules_config';
|
|
8
|
+
const PARTICIPATION_MODES = new Set(['automatic', 'mention_only', 'always']);
|
|
9
|
+
const MODEL_PURPOSES = new Set(['fast', 'general']);
|
|
10
|
+
const DELIVERY_STYLES = new Set(['single', 'natural_bubbles']);
|
|
11
|
+
|
|
12
|
+
function clampNumber(value, min, max, fallback) {
|
|
13
|
+
const number = Number(value);
|
|
14
|
+
if (!Number.isFinite(number)) return fallback;
|
|
15
|
+
return Math.min(max, Math.max(min, number));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function asObject(value) {
|
|
19
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function normalizeModules(rawModules, fallbackEnabled = true, sparse = false) {
|
|
23
|
+
const source = asObject(rawModules);
|
|
24
|
+
const modules = {};
|
|
25
|
+
for (const id of MODULE_IDS) {
|
|
26
|
+
if (sparse && !Object.prototype.hasOwnProperty.call(source, id)) continue;
|
|
27
|
+
const entry = asObject(source[id]);
|
|
28
|
+
modules[id] = {
|
|
29
|
+
enabled: entry.enabled == null ? fallbackEnabled : entry.enabled !== false,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return modules;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function normalizeOptionalString(value, fallback = null) {
|
|
36
|
+
if (value == null) return fallback;
|
|
37
|
+
const normalized = String(value).trim();
|
|
38
|
+
return normalized || null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function normalizeLeafConfig(raw = {}, base = cloneDefaults(), sparse = false) {
|
|
42
|
+
const input = asObject(raw);
|
|
43
|
+
const output = {};
|
|
44
|
+
const assign = (key, value) => {
|
|
45
|
+
if (!sparse || Object.prototype.hasOwnProperty.call(input, key)) output[key] = value;
|
|
46
|
+
};
|
|
47
|
+
const enabled = input.enabled == null ? base.enabled !== false : input.enabled !== false;
|
|
48
|
+
assign('enabled', enabled);
|
|
49
|
+
if (!sparse || Object.prototype.hasOwnProperty.call(input, 'modules')) {
|
|
50
|
+
output.modules = normalizeModules(input.modules, enabled, sparse);
|
|
51
|
+
}
|
|
52
|
+
assign(
|
|
53
|
+
'participationMode',
|
|
54
|
+
PARTICIPATION_MODES.has(String(input.participationMode || ''))
|
|
55
|
+
? String(input.participationMode)
|
|
56
|
+
: (base.participationMode || DEFAULT_MODULE_CONFIG.participationMode),
|
|
57
|
+
);
|
|
58
|
+
assign('minimumNeedScore', clampNumber(
|
|
59
|
+
input.minimumNeedScore,
|
|
60
|
+
0,
|
|
61
|
+
1,
|
|
62
|
+
base.minimumNeedScore ?? DEFAULT_MODULE_CONFIG.minimumNeedScore,
|
|
63
|
+
));
|
|
64
|
+
assign('batchWindowMs', Math.round(clampNumber(
|
|
65
|
+
input.batchWindowMs,
|
|
66
|
+
0,
|
|
67
|
+
5000,
|
|
68
|
+
base.batchWindowMs ?? DEFAULT_MODULE_CONFIG.batchWindowMs,
|
|
69
|
+
)));
|
|
70
|
+
assign('decisionContextMessageLimit', Math.round(clampNumber(
|
|
71
|
+
input.decisionContextMessageLimit,
|
|
72
|
+
4,
|
|
73
|
+
30,
|
|
74
|
+
base.decisionContextMessageLimit ?? DEFAULT_MODULE_CONFIG.decisionContextMessageLimit,
|
|
75
|
+
)));
|
|
76
|
+
assign(
|
|
77
|
+
'decisionModelId',
|
|
78
|
+
normalizeOptionalString(input.decisionModelId, base.decisionModelId || null),
|
|
79
|
+
);
|
|
80
|
+
assign(
|
|
81
|
+
'decisionModelPurpose',
|
|
82
|
+
MODEL_PURPOSES.has(String(input.decisionModelPurpose || ''))
|
|
83
|
+
? String(input.decisionModelPurpose)
|
|
84
|
+
: (base.decisionModelPurpose || DEFAULT_MODULE_CONFIG.decisionModelPurpose),
|
|
85
|
+
);
|
|
86
|
+
assign(
|
|
87
|
+
'deliveryStyle',
|
|
88
|
+
DELIVERY_STYLES.has(String(input.deliveryStyle || ''))
|
|
89
|
+
? String(input.deliveryStyle)
|
|
90
|
+
: (base.deliveryStyle || DEFAULT_MODULE_CONFIG.deliveryStyle),
|
|
91
|
+
);
|
|
92
|
+
assign('maxBubbles', Math.round(clampNumber(
|
|
93
|
+
input.maxBubbles,
|
|
94
|
+
1,
|
|
95
|
+
5,
|
|
96
|
+
base.maxBubbles ?? DEFAULT_MODULE_CONFIG.maxBubbles,
|
|
97
|
+
)));
|
|
98
|
+
assign('bubbleGapMs', Math.round(clampNumber(
|
|
99
|
+
input.bubbleGapMs,
|
|
100
|
+
0,
|
|
101
|
+
5000,
|
|
102
|
+
base.bubbleGapMs ?? DEFAULT_MODULE_CONFIG.bubbleGapMs,
|
|
103
|
+
)));
|
|
104
|
+
assign('normsRefreshMessageGap', Math.round(clampNumber(
|
|
105
|
+
input.normsRefreshMessageGap,
|
|
106
|
+
3,
|
|
107
|
+
200,
|
|
108
|
+
base.normsRefreshMessageGap ?? DEFAULT_MODULE_CONFIG.normsRefreshMessageGap,
|
|
109
|
+
)));
|
|
110
|
+
assign('observabilityIntervalMinutes', Math.round(clampNumber(
|
|
111
|
+
input.observabilityIntervalMinutes,
|
|
112
|
+
30,
|
|
113
|
+
10080,
|
|
114
|
+
base.observabilityIntervalMinutes ?? DEFAULT_MODULE_CONFIG.observabilityIntervalMinutes,
|
|
115
|
+
)));
|
|
116
|
+
return output;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function normalizeStoredConfig(raw) {
|
|
120
|
+
const base = cloneDefaults();
|
|
121
|
+
const input = asObject(raw);
|
|
122
|
+
const normalized = {
|
|
123
|
+
schemaVersion: DEFAULT_MODULE_CONFIG.schemaVersion,
|
|
124
|
+
...normalizeLeafConfig(input, base),
|
|
125
|
+
};
|
|
126
|
+
const platformOverrides = {};
|
|
127
|
+
for (const [platform, value] of Object.entries(asObject(input.platformOverrides))) {
|
|
128
|
+
const key = String(platform || '').trim();
|
|
129
|
+
if (!key) continue;
|
|
130
|
+
platformOverrides[key] = normalizeLeafConfig(value, normalized, true);
|
|
131
|
+
}
|
|
132
|
+
const roomOverrides = {};
|
|
133
|
+
const rawRoomOverrides = Object.keys(asObject(input.roomOverrides)).length
|
|
134
|
+
? asObject(input.roomOverrides)
|
|
135
|
+
: asObject(input.groupOverrides);
|
|
136
|
+
for (const [roomKey, value] of Object.entries(rawRoomOverrides)) {
|
|
137
|
+
const key = String(roomKey || '').trim();
|
|
138
|
+
if (!key) continue;
|
|
139
|
+
roomOverrides[key] = normalizeLeafConfig(value, normalized, true);
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
...normalized,
|
|
143
|
+
platformOverrides,
|
|
144
|
+
roomOverrides,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function readSettingRow(userId, agentId) {
|
|
149
|
+
const row = db.prepare(
|
|
150
|
+
'SELECT value FROM agent_settings WHERE user_id = ? AND agent_id = ? AND key = ?',
|
|
151
|
+
).get(userId, agentId, SETTINGS_KEY);
|
|
152
|
+
if (row?.value != null) return row.value;
|
|
153
|
+
if (isMainAgent(userId, agentId)) {
|
|
154
|
+
const legacy = db.prepare(
|
|
155
|
+
'SELECT value FROM user_settings WHERE user_id = ? AND key = ?',
|
|
156
|
+
).get(userId, SETTINGS_KEY);
|
|
157
|
+
return legacy?.value;
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function parseStoredValue(value) {
|
|
163
|
+
if (value == null || value === '') return cloneDefaults();
|
|
164
|
+
if (typeof value === 'object') return normalizeStoredConfig(value);
|
|
165
|
+
try {
|
|
166
|
+
return normalizeStoredConfig(JSON.parse(value));
|
|
167
|
+
} catch {
|
|
168
|
+
return cloneDefaults();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function getBehaviorConfig(userId, agentId = null) {
|
|
173
|
+
return parseStoredValue(readSettingRow(userId, agentId));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function setBehaviorConfig(userId, agentId, config) {
|
|
177
|
+
const normalized = normalizeStoredConfig(config);
|
|
178
|
+
db.prepare(
|
|
179
|
+
`INSERT INTO agent_settings (user_id, agent_id, key, value)
|
|
180
|
+
VALUES (?, ?, ?, ?)
|
|
181
|
+
ON CONFLICT(user_id, agent_id, key) DO UPDATE SET value = excluded.value`,
|
|
182
|
+
).run(userId, agentId, SETTINGS_KEY, JSON.stringify(normalized));
|
|
183
|
+
return normalized;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function roomConfigKey(platform, chatId) {
|
|
187
|
+
return `${String(platform || '').trim()}::${String(chatId || '').trim()}`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function mergeConfigs(base, override) {
|
|
191
|
+
if (!override) return base;
|
|
192
|
+
return {
|
|
193
|
+
...base,
|
|
194
|
+
...override,
|
|
195
|
+
modules: {
|
|
196
|
+
...asObject(base.modules),
|
|
197
|
+
...asObject(override.modules),
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function resolveBehaviorConfig(userId, agentId, { platform = null, chatId = null, isGroup = false } = {}) {
|
|
203
|
+
const storedValue = readSettingRow(userId, agentId);
|
|
204
|
+
const stored = parseStoredValue(storedValue);
|
|
205
|
+
let effective = normalizeLeafConfig(stored, cloneDefaults());
|
|
206
|
+
let participationModeSource = storedValue == null ? 'default' : 'agent';
|
|
207
|
+
const platformKey = String(platform || '').trim();
|
|
208
|
+
if (platformKey && stored.platformOverrides?.[platformKey]) {
|
|
209
|
+
effective = mergeConfigs(effective, stored.platformOverrides[platformKey]);
|
|
210
|
+
if (Object.prototype.hasOwnProperty.call(stored.platformOverrides[platformKey], 'participationMode')) {
|
|
211
|
+
participationModeSource = 'platform';
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (isGroup && platformKey && chatId) {
|
|
215
|
+
const key = roomConfigKey(platformKey, chatId);
|
|
216
|
+
if (stored.roomOverrides?.[key]) {
|
|
217
|
+
effective = mergeConfigs(effective, stored.roomOverrides[key]);
|
|
218
|
+
if (Object.prototype.hasOwnProperty.call(stored.roomOverrides[key], 'participationMode')) {
|
|
219
|
+
participationModeSource = 'room';
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
effective.modules = normalizeModules(effective.modules, effective.enabled !== false);
|
|
224
|
+
effective.platformOverrides = stored.platformOverrides || {};
|
|
225
|
+
effective.roomOverrides = stored.roomOverrides || {};
|
|
226
|
+
effective.participationModeSource = participationModeSource;
|
|
227
|
+
effective.scope = {
|
|
228
|
+
platform: platformKey || null,
|
|
229
|
+
chatId: chatId ? String(chatId) : null,
|
|
230
|
+
isGroup: Boolean(isGroup),
|
|
231
|
+
roomKey: isGroup && platformKey && chatId ? roomConfigKey(platformKey, chatId) : null,
|
|
232
|
+
};
|
|
233
|
+
return effective;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function isModuleEnabled(config, moduleId) {
|
|
237
|
+
if (!config || config.enabled === false) return false;
|
|
238
|
+
const entry = config.modules?.[moduleId];
|
|
239
|
+
if (!entry) return false;
|
|
240
|
+
return entry.enabled !== false;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
module.exports = {
|
|
244
|
+
SETTINGS_KEY,
|
|
245
|
+
getBehaviorConfig,
|
|
246
|
+
setBehaviorConfig,
|
|
247
|
+
resolveBehaviorConfig,
|
|
248
|
+
normalizeStoredConfig,
|
|
249
|
+
roomConfigKey,
|
|
250
|
+
isModuleEnabled,
|
|
251
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const MODULE_IDS = Object.freeze([
|
|
4
|
+
'turn_taking',
|
|
5
|
+
'social_memory',
|
|
6
|
+
'norms',
|
|
7
|
+
'persona',
|
|
8
|
+
'agent_identity',
|
|
9
|
+
'channel_style',
|
|
10
|
+
'theory_of_mind',
|
|
11
|
+
'social_signals',
|
|
12
|
+
'social_observability',
|
|
13
|
+
'delivery',
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
const DEFAULT_MODULE_CONFIG = Object.freeze({
|
|
17
|
+
schemaVersion: 1,
|
|
18
|
+
enabled: true,
|
|
19
|
+
participationMode: 'automatic',
|
|
20
|
+
minimumNeedScore: 0.72,
|
|
21
|
+
batchWindowMs: 900,
|
|
22
|
+
decisionContextMessageLimit: 12,
|
|
23
|
+
decisionModelId: null,
|
|
24
|
+
decisionModelPurpose: 'general',
|
|
25
|
+
deliveryStyle: 'natural_bubbles',
|
|
26
|
+
maxBubbles: 4,
|
|
27
|
+
bubbleGapMs: 650,
|
|
28
|
+
normsRefreshMessageGap: 18,
|
|
29
|
+
observabilityIntervalMinutes: 360,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
function cloneDefaults() {
|
|
33
|
+
return {
|
|
34
|
+
schemaVersion: DEFAULT_MODULE_CONFIG.schemaVersion,
|
|
35
|
+
enabled: DEFAULT_MODULE_CONFIG.enabled,
|
|
36
|
+
modules: {
|
|
37
|
+
turn_taking: { enabled: true },
|
|
38
|
+
social_memory: { enabled: true },
|
|
39
|
+
norms: { enabled: true },
|
|
40
|
+
persona: { enabled: true },
|
|
41
|
+
agent_identity: { enabled: true },
|
|
42
|
+
channel_style: { enabled: true },
|
|
43
|
+
theory_of_mind: { enabled: true },
|
|
44
|
+
social_signals: { enabled: true },
|
|
45
|
+
social_observability: { enabled: true },
|
|
46
|
+
delivery: { enabled: true },
|
|
47
|
+
},
|
|
48
|
+
participationMode: DEFAULT_MODULE_CONFIG.participationMode,
|
|
49
|
+
minimumNeedScore: DEFAULT_MODULE_CONFIG.minimumNeedScore,
|
|
50
|
+
batchWindowMs: DEFAULT_MODULE_CONFIG.batchWindowMs,
|
|
51
|
+
decisionContextMessageLimit: DEFAULT_MODULE_CONFIG.decisionContextMessageLimit,
|
|
52
|
+
decisionModelId: DEFAULT_MODULE_CONFIG.decisionModelId,
|
|
53
|
+
decisionModelPurpose: DEFAULT_MODULE_CONFIG.decisionModelPurpose,
|
|
54
|
+
deliveryStyle: DEFAULT_MODULE_CONFIG.deliveryStyle,
|
|
55
|
+
maxBubbles: DEFAULT_MODULE_CONFIG.maxBubbles,
|
|
56
|
+
bubbleGapMs: DEFAULT_MODULE_CONFIG.bubbleGapMs,
|
|
57
|
+
normsRefreshMessageGap: DEFAULT_MODULE_CONFIG.normsRefreshMessageGap,
|
|
58
|
+
observabilityIntervalMinutes: DEFAULT_MODULE_CONFIG.observabilityIntervalMinutes,
|
|
59
|
+
platformOverrides: {},
|
|
60
|
+
roomOverrides: {},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
module.exports = {
|
|
65
|
+
MODULE_IDS,
|
|
66
|
+
DEFAULT_MODULE_CONFIG,
|
|
67
|
+
cloneDefaults,
|
|
68
|
+
};
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
splitOutgoingMessageForPlatform,
|
|
5
|
+
normalizeOutgoingMessageForPlatform,
|
|
6
|
+
} = require('../messaging/formatting_guides');
|
|
7
|
+
|
|
8
|
+
function sleep(ms, signal = null) {
|
|
9
|
+
const delay = Math.max(0, Number(ms) || 0);
|
|
10
|
+
if (!delay) return Promise.resolve();
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
const timer = setTimeout(() => {
|
|
13
|
+
cleanup();
|
|
14
|
+
resolve();
|
|
15
|
+
}, delay);
|
|
16
|
+
const onAbort = () => {
|
|
17
|
+
cleanup();
|
|
18
|
+
const error = new Error('Delivery aborted.');
|
|
19
|
+
error.name = 'AbortError';
|
|
20
|
+
reject(error);
|
|
21
|
+
};
|
|
22
|
+
const cleanup = () => {
|
|
23
|
+
clearTimeout(timer);
|
|
24
|
+
if (signal) signal.removeEventListener('abort', onAbort);
|
|
25
|
+
};
|
|
26
|
+
if (signal) {
|
|
27
|
+
if (signal.aborted) {
|
|
28
|
+
cleanup();
|
|
29
|
+
const error = new Error('Delivery aborted.');
|
|
30
|
+
error.name = 'AbortError';
|
|
31
|
+
reject(error);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function splitIntoNaturalBubbles(platform, content, { maxBubbles = 4 } = {}) {
|
|
40
|
+
const normalized = normalizeOutgoingMessageForPlatform(platform, content, {
|
|
41
|
+
stripNoResponseMarker: false,
|
|
42
|
+
});
|
|
43
|
+
if (!normalized) return [];
|
|
44
|
+
if (normalized.toUpperCase() === '[NO RESPONSE]') return [normalized];
|
|
45
|
+
|
|
46
|
+
const paragraphChunks = splitOutgoingMessageForPlatform(platform, normalized);
|
|
47
|
+
const bubbles = [];
|
|
48
|
+
for (const chunk of paragraphChunks) {
|
|
49
|
+
const bubble = String(chunk).trim();
|
|
50
|
+
if (bubble) bubbles.push(bubble);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const limited = [];
|
|
54
|
+
for (const bubble of bubbles) {
|
|
55
|
+
if (!bubble) continue;
|
|
56
|
+
if (limited.length < maxBubbles) {
|
|
57
|
+
limited.push(bubble);
|
|
58
|
+
} else {
|
|
59
|
+
limited[limited.length - 1] = `${limited[limited.length - 1]} ${bubble}`.trim();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return limited.length ? limited : [normalized];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function deliverSocialReply({
|
|
66
|
+
messagingManager,
|
|
67
|
+
userId,
|
|
68
|
+
agentId,
|
|
69
|
+
platform,
|
|
70
|
+
chatId,
|
|
71
|
+
content,
|
|
72
|
+
config,
|
|
73
|
+
runId = null,
|
|
74
|
+
signal = null,
|
|
75
|
+
mediaPath = null,
|
|
76
|
+
turnEpoch = null,
|
|
77
|
+
beforeBubble = null,
|
|
78
|
+
}) {
|
|
79
|
+
const style = config?.deliveryStyle === 'single' ? 'single' : 'natural_bubbles';
|
|
80
|
+
if (style === 'single' || mediaPath) {
|
|
81
|
+
if (beforeBubble && !(await beforeBubble(0))) {
|
|
82
|
+
return { success: false, suppressed: true, reason: 'stale_turn', turnEpoch };
|
|
83
|
+
}
|
|
84
|
+
return messagingManager.sendMessage(userId, platform, chatId, content, {
|
|
85
|
+
agentId,
|
|
86
|
+
runId,
|
|
87
|
+
mediaPath,
|
|
88
|
+
signal,
|
|
89
|
+
deliveryKind: 'final',
|
|
90
|
+
idempotencyKey: runId ? `${runId}:social:0` : undefined,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const bubbles = splitIntoNaturalBubbles(platform, content, {
|
|
95
|
+
maxBubbles: config?.maxBubbles ?? 4,
|
|
96
|
+
});
|
|
97
|
+
if (bubbles.length <= 1) {
|
|
98
|
+
if (beforeBubble && !(await beforeBubble(0))) {
|
|
99
|
+
return { success: false, suppressed: true, reason: 'stale_turn', turnEpoch };
|
|
100
|
+
}
|
|
101
|
+
return messagingManager.sendMessage(userId, platform, chatId, bubbles[0] || content, {
|
|
102
|
+
agentId,
|
|
103
|
+
runId,
|
|
104
|
+
signal,
|
|
105
|
+
deliveryKind: 'final',
|
|
106
|
+
idempotencyKey: runId ? `${runId}:social:0` : undefined,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
let lastResult = null;
|
|
112
|
+
for (let index = 0; index < bubbles.length; index += 1) {
|
|
113
|
+
if (signal?.aborted) {
|
|
114
|
+
const error = new Error('Delivery aborted.');
|
|
115
|
+
error.name = 'AbortError';
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
if (index > 0) {
|
|
119
|
+
await sleep(config?.bubbleGapMs ?? 650, signal);
|
|
120
|
+
}
|
|
121
|
+
if (beforeBubble && !(await beforeBubble(index))) {
|
|
122
|
+
return {
|
|
123
|
+
success: false,
|
|
124
|
+
suppressed: true,
|
|
125
|
+
reason: 'stale_turn',
|
|
126
|
+
turnEpoch,
|
|
127
|
+
deliveredBubbles: index,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
await messagingManager.sendTyping?.(userId, platform, chatId, true, { agentId, runId, signal });
|
|
132
|
+
} catch {
|
|
133
|
+
// typing is best-effort
|
|
134
|
+
}
|
|
135
|
+
lastResult = await messagingManager.sendMessage(userId, platform, chatId, bubbles[index], {
|
|
136
|
+
agentId,
|
|
137
|
+
runId,
|
|
138
|
+
signal,
|
|
139
|
+
idempotencyKey: runId ? `${runId}:social:${index}` : undefined,
|
|
140
|
+
deliveryKind: index === bubbles.length - 1 ? 'final' : 'partial',
|
|
141
|
+
metadata: {
|
|
142
|
+
socialDelivery: true,
|
|
143
|
+
bubbleIndex: index,
|
|
144
|
+
bubbleCount: bubbles.length,
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
if (lastResult?.success === false || lastResult?.suppressed === true) {
|
|
148
|
+
return {
|
|
149
|
+
success: false,
|
|
150
|
+
error: lastResult?.error || lastResult?.reason || 'Messaging platform rejected delivery.',
|
|
151
|
+
result: lastResult,
|
|
152
|
+
deliveredBubbles: index,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
success: true,
|
|
158
|
+
bubbled: true,
|
|
159
|
+
bubbleCount: bubbles.length,
|
|
160
|
+
result: lastResult,
|
|
161
|
+
};
|
|
162
|
+
} finally {
|
|
163
|
+
try {
|
|
164
|
+
await messagingManager.sendTyping?.(userId, platform, chatId, false, { agentId, runId, signal });
|
|
165
|
+
} catch {
|
|
166
|
+
// typing is best-effort
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
module.exports = {
|
|
172
|
+
id: 'delivery',
|
|
173
|
+
deliver: deliverSocialReply,
|
|
174
|
+
splitIntoNaturalBubbles,
|
|
175
|
+
deliverSocialReply,
|
|
176
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
getBehaviorConfig,
|
|
5
|
+
setBehaviorConfig,
|
|
6
|
+
resolveBehaviorConfig,
|
|
7
|
+
normalizeStoredConfig,
|
|
8
|
+
roomConfigKey,
|
|
9
|
+
isModuleEnabled,
|
|
10
|
+
SETTINGS_KEY,
|
|
11
|
+
} = require('./config');
|
|
12
|
+
const { MODULE_IDS, cloneDefaults, DEFAULT_MODULE_CONFIG } = require('./defaults');
|
|
13
|
+
const { createBehaviorPipeline } = require('./pipeline');
|
|
14
|
+
const {
|
|
15
|
+
getThreadState,
|
|
16
|
+
setThreadState,
|
|
17
|
+
isTurnCurrent,
|
|
18
|
+
markSpoke,
|
|
19
|
+
} = require('./state');
|
|
20
|
+
const { createBehaviorRegistry, LIFECYCLE_STAGES } = require('./registry');
|
|
21
|
+
const { splitIntoNaturalBubbles, deliverSocialReply } = require('./delivery');
|
|
22
|
+
|
|
23
|
+
module.exports = {
|
|
24
|
+
MODULE_IDS,
|
|
25
|
+
DEFAULT_MODULE_CONFIG,
|
|
26
|
+
SETTINGS_KEY,
|
|
27
|
+
cloneDefaults,
|
|
28
|
+
getBehaviorConfig,
|
|
29
|
+
setBehaviorConfig,
|
|
30
|
+
resolveBehaviorConfig,
|
|
31
|
+
normalizeStoredConfig,
|
|
32
|
+
roomConfigKey,
|
|
33
|
+
isModuleEnabled,
|
|
34
|
+
createBehaviorPipeline,
|
|
35
|
+
createBehaviorRegistry,
|
|
36
|
+
LIFECYCLE_STAGES,
|
|
37
|
+
getThreadState,
|
|
38
|
+
setThreadState,
|
|
39
|
+
isTurnCurrent,
|
|
40
|
+
markSpoke,
|
|
41
|
+
splitIntoNaturalBubbles,
|
|
42
|
+
deliverSocialReply,
|
|
43
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
async function requestStructuredJson({
|
|
4
|
+
agentEngine,
|
|
5
|
+
userId,
|
|
6
|
+
agentId,
|
|
7
|
+
modelId = null,
|
|
8
|
+
purpose = 'fast',
|
|
9
|
+
system,
|
|
10
|
+
prompt,
|
|
11
|
+
signal = null,
|
|
12
|
+
maxTokens = 220,
|
|
13
|
+
fallback = {},
|
|
14
|
+
}) {
|
|
15
|
+
if (!agentEngine || typeof agentEngine.inferStructured !== 'function') {
|
|
16
|
+
const error = new Error('Behavior inference requires the central AI engine.');
|
|
17
|
+
error.code = 'BEHAVIOR_ENGINE_UNAVAILABLE';
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
return agentEngine.inferStructured({
|
|
21
|
+
userId,
|
|
22
|
+
agentId,
|
|
23
|
+
modelId,
|
|
24
|
+
purpose,
|
|
25
|
+
system,
|
|
26
|
+
prompt,
|
|
27
|
+
maxTokens,
|
|
28
|
+
fallback,
|
|
29
|
+
signal,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = {
|
|
34
|
+
requestStructuredJson,
|
|
35
|
+
};
|