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,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
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const db = require('../../../db/database');
|
|
4
|
+
const { buildAgentRosterPrompt } = require('../../agents/manager');
|
|
5
|
+
const { isModuleEnabled } = require('../config');
|
|
6
|
+
|
|
7
|
+
function clamp(text, maxChars) {
|
|
8
|
+
const value = String(text || '').trim();
|
|
9
|
+
if (!value || value.length <= maxChars) return value;
|
|
10
|
+
return `${value.slice(0, maxChars)}\n...[trimmed]`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function buildSystemPromptContribution(ctx) {
|
|
14
|
+
if (!ctx.agentId || !isModuleEnabled(ctx.config, 'agent_identity')) return '';
|
|
15
|
+
const agent = db.prepare(
|
|
16
|
+
`SELECT display_name, slug, description, responsibilities, instructions
|
|
17
|
+
FROM agents
|
|
18
|
+
WHERE user_id = ? AND id = ?`,
|
|
19
|
+
).get(ctx.userId, ctx.agentId);
|
|
20
|
+
if (!agent) return '';
|
|
21
|
+
const active = [
|
|
22
|
+
'## Active Agent',
|
|
23
|
+
`Name: ${agent.display_name} (${agent.slug})`,
|
|
24
|
+
agent.description ? `Description: ${clamp(agent.description, 600)}` : '',
|
|
25
|
+
agent.responsibilities ? `Responsibilities: ${clamp(agent.responsibilities, 1000)}` : '',
|
|
26
|
+
agent.instructions ? `Agent instructions: ${clamp(agent.instructions, 1600)}` : '',
|
|
27
|
+
].filter(Boolean).join('\n');
|
|
28
|
+
const roster = ctx.triggerSource === 'agent_delegation'
|
|
29
|
+
? ''
|
|
30
|
+
: buildAgentRosterPrompt(ctx.userId, ctx.agentId);
|
|
31
|
+
return [active, roster].filter(Boolean).join('\n\n');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = {
|
|
35
|
+
id: 'agent_identity',
|
|
36
|
+
composeSystemPrompt: buildSystemPromptContribution,
|
|
37
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { isModuleEnabled } = require('../config');
|
|
4
|
+
|
|
5
|
+
function buildSystemPromptContribution(ctx) {
|
|
6
|
+
if (!isModuleEnabled(ctx.config, 'channel_style')) return '';
|
|
7
|
+
if (ctx.widgetId) {
|
|
8
|
+
return 'CHANNEL RESPONSE GUIDE: Widget refreshes should produce structured snapshot data, not conversational filler.';
|
|
9
|
+
}
|
|
10
|
+
if (ctx.triggerSource === 'voice_live' || ctx.latencyProfile === 'voice') {
|
|
11
|
+
return 'CHANNEL RESPONSE GUIDE: Voice replies should usually fit in one or two concise spoken sentences unless detail is necessary.';
|
|
12
|
+
}
|
|
13
|
+
if (ctx.triggerSource === 'messaging' && ctx.audience === 'shared') {
|
|
14
|
+
return 'CHANNEL RESPONSE GUIDE: The turn-taking gate already decided this shared room needs a response. Make one brief, natural contribution, address the relevant participant or room rather than the owner, and do not dominate or send progress chatter.';
|
|
15
|
+
}
|
|
16
|
+
if (ctx.triggerSource === 'messaging') {
|
|
17
|
+
return 'CHANNEL RESPONSE GUIDE: Text like a natural contact. Prefer one concise reply, or a few short bubbles when the thought genuinely benefits from separate beats. A blank line marks an intentional bubble break, so do not add one mechanically. Keep dense information and lists together, and expand only when the task needs detail.';
|
|
18
|
+
}
|
|
19
|
+
if (ctx.triggerSource === 'wearable') {
|
|
20
|
+
return 'CHANNEL RESPONSE GUIDE: Wearable replies should be one or two short sentences with the result first.';
|
|
21
|
+
}
|
|
22
|
+
return 'CHANNEL RESPONSE GUIDE: Web chat may use short paragraphs and compact lists. Avoid padding and lead with the result.';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
module.exports = {
|
|
26
|
+
id: 'channel_style',
|
|
27
|
+
composeSystemPrompt: buildSystemPromptContribution,
|
|
28
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const turnTaking = require('./turn_taking');
|
|
4
|
+
const socialMemory = require('./social_memory');
|
|
5
|
+
const norms = require('./norms');
|
|
6
|
+
const persona = require('./persona');
|
|
7
|
+
const agentIdentity = require('./agent_identity');
|
|
8
|
+
const channelStyle = require('./channel_style');
|
|
9
|
+
const theoryOfMind = require('./theory_of_mind');
|
|
10
|
+
const socialSignals = require('./social_signals');
|
|
11
|
+
const socialObservability = require('./social_observability');
|
|
12
|
+
const delivery = require('../delivery');
|
|
13
|
+
|
|
14
|
+
const BEHAVIOR_MODULES = Object.freeze([
|
|
15
|
+
turnTaking,
|
|
16
|
+
socialMemory,
|
|
17
|
+
norms,
|
|
18
|
+
persona,
|
|
19
|
+
agentIdentity,
|
|
20
|
+
channelStyle,
|
|
21
|
+
theoryOfMind,
|
|
22
|
+
socialSignals,
|
|
23
|
+
socialObservability,
|
|
24
|
+
delivery,
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
module.exports = {
|
|
28
|
+
BEHAVIOR_MODULES,
|
|
29
|
+
};
|