neoagent 3.2.1-beta.1 → 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.
- package/extensions/chrome-browser/background.mjs +318 -88
- package/extensions/chrome-browser/http.mjs +136 -0
- package/extensions/chrome-browser/protocol.mjs +654 -90
- package/flutter_app/lib/main_chat.dart +118 -739
- package/flutter_app/lib/main_controller.dart +157 -24
- package/flutter_app/lib/main_integrations.dart +607 -8
- package/flutter_app/lib/main_models.dart +7 -2
- package/flutter_app/lib/main_operations.dart +334 -370
- package/flutter_app/lib/main_security.dart +266 -112
- package/flutter_app/lib/main_settings.dart +342 -3
- package/flutter_app/lib/main_shared.dart +14 -11
- package/flutter_app/lib/src/backend_client.dart +97 -0
- package/flutter_app/lib/src/desktop_companion_actions.dart +185 -31
- package/flutter_app/lib/src/desktop_companion_io.dart +319 -86
- package/flutter_app/windows/runner/flutter_window.cpp +143 -32
- package/landing/index.html +3 -1
- package/lib/manager.js +106 -89
- package/lib/schema_migrations.js +115 -13
- package/package.json +30 -15
- package/runtime/paths.js +49 -5
- package/server/db/database.js +2 -2
- package/server/guest-agent.cli.package.json +13 -0
- package/server/guest_agent.js +85 -40
- package/server/http/middleware.js +24 -0
- package/server/http/routes.js +12 -6
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/flutter_bootstrap.js +2 -2
- package/server/public/main.dart.js +81930 -80509
- package/server/routes/admin.js +1 -1
- package/server/routes/android.js +30 -34
- package/server/routes/behavior.js +80 -0
- package/server/routes/browser.js +23 -15
- package/server/routes/desktop.js +18 -1
- package/server/routes/integrations.js +107 -1
- package/server/routes/memory.js +1 -0
- package/server/routes/settings.js +20 -5
- package/server/routes/social_reach.js +12 -3
- package/server/routes/social_video.js +4 -0
- package/server/services/agents/manager.js +1 -1
- package/server/services/ai/capabilityHealth.js +62 -96
- package/server/services/ai/compaction.js +7 -2
- package/server/services/ai/history.js +45 -6
- package/server/services/ai/integrated_tools/http_request.js +8 -0
- package/server/services/ai/loop/agent_engine_core.js +452 -176
- package/server/services/ai/loop/blank_recovery.js +5 -4
- package/server/services/ai/loop/callbacks.js +1 -0
- package/server/services/ai/loop/completion_judge.js +291 -8
- package/server/services/ai/loop/conversation_loop.js +515 -342
- package/server/services/ai/loop/messaging_delivery.js +176 -57
- package/server/services/ai/loop/model_call_guard.js +91 -0
- package/server/services/ai/loop/model_io.js +20 -45
- package/server/services/ai/loop/progress_classification.js +2 -0
- package/server/services/ai/loop/tool_dispatch.js +19 -8
- package/server/services/ai/loopPolicy.js +48 -21
- package/server/services/ai/messagingFallback.js +17 -17
- package/server/services/ai/model_discovery.js +227 -0
- package/server/services/ai/model_failure_cache.js +108 -0
- package/server/services/ai/model_identity.js +71 -0
- package/server/services/ai/models.js +68 -163
- package/server/services/ai/providerRetry.js +17 -59
- package/server/services/ai/provider_selector.js +166 -0
- package/server/services/ai/providers/anthropic.js +2 -2
- package/server/services/ai/providers/claudeCode.js +21 -33
- package/server/services/ai/providers/githubCopilot.js +41 -20
- package/server/services/ai/providers/google.js +135 -97
- package/server/services/ai/providers/grok.js +4 -3
- package/server/services/ai/providers/grokOauth.js +19 -27
- package/server/services/ai/providers/nvidia.js +10 -5
- package/server/services/ai/providers/ollama.js +111 -84
- package/server/services/ai/providers/ollama_stream.js +142 -0
- package/server/services/ai/providers/openai.js +39 -5
- package/server/services/ai/providers/openaiCodex.js +11 -4
- package/server/services/ai/providers/openrouter.js +29 -7
- package/server/services/ai/providers/provider_error.js +36 -0
- package/server/services/ai/settings.js +26 -2
- package/server/services/ai/systemPrompt.js +32 -123
- package/server/services/ai/taskAnalysis.js +104 -11
- package/server/services/ai/toolEvidence.js +256 -29
- package/server/services/ai/tools.js +248 -117
- package/server/services/android/controller.js +770 -237
- package/server/services/android/process.js +140 -0
- package/server/services/android/sdk_download.js +143 -0
- package/server/services/android/uia.js +6 -5
- package/server/services/artifacts/store.js +24 -0
- 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 +48 -0
- package/server/services/behavior/modules/persona_prompt.js +33 -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 +110 -0
- package/server/services/behavior/modules/turn_taking.js +237 -0
- package/server/services/behavior/pipeline.js +285 -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/browser/controller.js +843 -385
- package/server/services/browser/extension/gateway.js +40 -16
- package/server/services/browser/extension/protocol.js +15 -1
- package/server/services/browser/extension/provider.js +71 -47
- package/server/services/browser/extension/registry.js +155 -34
- package/server/services/cli/executor.js +62 -9
- package/server/services/credentials/bitwarden_cli.js +322 -0
- package/server/services/credentials/broker.js +594 -0
- package/server/services/desktop/gateway.js +41 -4
- package/server/services/desktop/protocol.js +3 -0
- package/server/services/desktop/provider.js +39 -42
- package/server/services/desktop/registry.js +137 -52
- package/server/services/integrations/bitwarden/constants.js +14 -0
- package/server/services/integrations/bitwarden/provider.js +197 -0
- package/server/services/integrations/bitwarden/snapshot.js +65 -0
- package/server/services/integrations/figma/provider.js +78 -12
- package/server/services/integrations/github/common.js +11 -6
- package/server/services/integrations/github/provider.js +52 -53
- package/server/services/integrations/google/provider.js +55 -19
- package/server/services/integrations/home_assistant/network.js +17 -20
- package/server/services/integrations/home_assistant/provider.js +7 -5
- package/server/services/integrations/home_assistant/tools.js +17 -5
- package/server/services/integrations/http.js +51 -0
- package/server/services/integrations/manager.js +159 -53
- package/server/services/integrations/microsoft/provider.js +80 -13
- package/server/services/integrations/neoarchive/provider.js +55 -29
- package/server/services/integrations/neorecall/client.js +17 -10
- package/server/services/integrations/neorecall/provider.js +20 -11
- package/server/services/integrations/notion/provider.js +16 -13
- package/server/services/integrations/oauth_provider.js +115 -51
- package/server/services/integrations/registry.js +2 -0
- package/server/services/integrations/slack/provider.js +98 -9
- package/server/services/integrations/spotify/provider.js +67 -71
- package/server/services/integrations/trello/provider.js +21 -7
- package/server/services/integrations/weather/provider.js +18 -12
- package/server/services/integrations/whatsapp/provider.js +76 -16
- package/server/services/manager.js +110 -1
- package/server/services/memory/embedding_index.js +20 -8
- package/server/services/memory/embeddings.js +151 -90
- package/server/services/memory/ingestion.js +50 -9
- package/server/services/memory/ingestion_documents.js +13 -3
- package/server/services/memory/manager.js +66 -52
- package/server/services/messaging/access_policy.js +10 -6
- package/server/services/messaging/automation.js +240 -34
- package/server/services/messaging/discord.js +11 -1
- package/server/services/messaging/formatting_guides.js +5 -4
- package/server/services/messaging/http_platforms.js +37 -16
- package/server/services/messaging/inbound_queue.js +143 -28
- package/server/services/messaging/inbound_store.js +257 -0
- package/server/services/messaging/manager.js +344 -51
- package/server/services/messaging/telegram.js +10 -1
- package/server/services/messaging/typing_keepalive.js +5 -2
- package/server/services/messaging/whatsapp.js +33 -14
- package/server/services/network/http.js +210 -0
- package/server/services/network/safe_request.js +307 -0
- package/server/services/runtime/backends/local-vm.js +227 -67
- package/server/services/runtime/docker-vm-manager.js +9 -0
- package/server/services/runtime/guest_bootstrap.js +30 -4
- package/server/services/runtime/guest_image.js +43 -12
- package/server/services/runtime/manager.js +77 -23
- package/server/services/runtime/validation.js +7 -6
- package/server/services/security/tool_categories.js +6 -0
- package/server/services/social_reach/channels/github.js +10 -4
- package/server/services/social_reach/channels/reddit.js +4 -4
- package/server/services/social_reach/channels/rss.js +2 -2
- package/server/services/social_reach/channels/social_video.js +13 -8
- package/server/services/social_reach/channels/v2ex.js +21 -8
- package/server/services/social_reach/channels/x.js +2 -2
- package/server/services/social_reach/channels/xueqiu.js +5 -5
- package/server/services/social_reach/service.js +9 -6
- package/server/services/social_reach/utils.js +65 -14
- package/server/services/social_video/captions.js +2 -2
- package/server/services/social_video/service.js +343 -68
- package/server/services/tasks/integration_runtime.js +18 -8
- package/server/services/tasks/runtime.js +39 -4
- package/server/services/voice/agentBridge.js +17 -4
- package/server/services/voice/bufferedLiveRelayAdapter.js +5 -0
- package/server/services/voice/liveSession.js +31 -0
- package/server/services/voice/message.js +1 -1
- package/server/services/voice/openaiSpeech.js +33 -8
- package/server/services/voice/providers.js +233 -151
- package/server/services/voice/runtime.js +2 -2
- package/server/services/voice/runtimeManager.js +118 -20
- package/server/services/voice/turnRunner.js +6 -0
- package/server/services/wearable/firmware_manifest.js +51 -13
- package/server/services/wearable/service.js +1 -0
- package/server/utils/abort.js +96 -0
- package/server/utils/cloud-security.js +110 -3
- package/server/utils/files.js +31 -0
- package/server/utils/image_payload.js +95 -0
- package/server/utils/logger.js +19 -0
- package/server/utils/retry.js +107 -0
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { requestStructuredJson } = require('../model_client');
|
|
4
|
+
const { loadRecentRoomMessages, truncate } = require('../signals');
|
|
5
|
+
const { getThreadState, setThreadState } = require('../state');
|
|
6
|
+
const { isModuleEnabled } = require('../config');
|
|
7
|
+
|
|
8
|
+
const SYSTEM_PROMPT = `You extract a compact group-chat norms profile for an AI participant.
|
|
9
|
+
Return JSON with keys:
|
|
10
|
+
promptBlock (short instructions the agent can follow to match the room's voice),
|
|
11
|
+
notes (array of short observations).
|
|
12
|
+
No phrase blacklists. Describe style, formality, humor, length, and participation norms.`;
|
|
13
|
+
|
|
14
|
+
function getNormsPromptBlock(ctx) {
|
|
15
|
+
if (!isModuleEnabled(ctx.config, 'norms')) return '';
|
|
16
|
+
const state = getThreadState(ctx.userId, ctx.agentId, ctx.msg.platform, ctx.msg.chatId);
|
|
17
|
+
return String(state.normsPromptBlock || '').trim();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function maybeRefreshNorms(ctx) {
|
|
21
|
+
const { userId, agentId, msg, config, signal = null } = ctx;
|
|
22
|
+
if (!msg?.isGroup || !isModuleEnabled(config, 'norms')) {
|
|
23
|
+
return { refreshed: false };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const state = getThreadState(userId, agentId, msg.platform, msg.chatId);
|
|
27
|
+
const count = Number(state.messageCountSinceNorms || 0) + 1;
|
|
28
|
+
const gap = Number(config.normsRefreshMessageGap || 18);
|
|
29
|
+
if (count < gap) {
|
|
30
|
+
setThreadState(userId, agentId, msg.platform, msg.chatId, {
|
|
31
|
+
messageCountSinceNorms: count,
|
|
32
|
+
});
|
|
33
|
+
return { refreshed: false, deferred: true };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const roomMessages = loadRecentRoomMessages({
|
|
37
|
+
userId,
|
|
38
|
+
agentId,
|
|
39
|
+
platform: msg.platform,
|
|
40
|
+
chatId: msg.chatId,
|
|
41
|
+
limit: 24,
|
|
42
|
+
});
|
|
43
|
+
if (roomMessages.length < 4) {
|
|
44
|
+
setThreadState(userId, agentId, msg.platform, msg.chatId, {
|
|
45
|
+
messageCountSinceNorms: count,
|
|
46
|
+
});
|
|
47
|
+
return { refreshed: false, reason: 'insufficient_history' };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
const result = await requestStructuredJson({
|
|
52
|
+
agentEngine: ctx.agentEngine,
|
|
53
|
+
userId,
|
|
54
|
+
agentId,
|
|
55
|
+
purpose: 'fast',
|
|
56
|
+
system: SYSTEM_PROMPT,
|
|
57
|
+
prompt: JSON.stringify({
|
|
58
|
+
platform: msg.platform,
|
|
59
|
+
chatId: msg.chatId,
|
|
60
|
+
recentMessages: roomMessages.map((item) => ({
|
|
61
|
+
sender: item.sender,
|
|
62
|
+
content: item.content,
|
|
63
|
+
})),
|
|
64
|
+
}),
|
|
65
|
+
signal,
|
|
66
|
+
maxTokens: 280,
|
|
67
|
+
});
|
|
68
|
+
const promptBlock = truncate(result.parsed?.promptBlock || '', 900);
|
|
69
|
+
if (!promptBlock) {
|
|
70
|
+
setThreadState(userId, agentId, msg.platform, msg.chatId, {
|
|
71
|
+
messageCountSinceNorms: 0,
|
|
72
|
+
});
|
|
73
|
+
return { refreshed: false, reason: 'empty_block' };
|
|
74
|
+
}
|
|
75
|
+
setThreadState(userId, agentId, msg.platform, msg.chatId, {
|
|
76
|
+
normsPromptBlock: `## Group norms\n${promptBlock}`,
|
|
77
|
+
normsUpdatedAt: new Date().toISOString(),
|
|
78
|
+
messageCountSinceNorms: 0,
|
|
79
|
+
});
|
|
80
|
+
return { refreshed: true };
|
|
81
|
+
} catch (error) {
|
|
82
|
+
if (signal?.aborted) throw error;
|
|
83
|
+
setThreadState(userId, agentId, msg.platform, msg.chatId, {
|
|
84
|
+
messageCountSinceNorms: count,
|
|
85
|
+
});
|
|
86
|
+
return { refreshed: false, error: error?.message || String(error) };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function composeContext(ctx) {
|
|
91
|
+
const content = getNormsPromptBlock(ctx);
|
|
92
|
+
return content ? { key: 'norms', priority: 50, content } : null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
module.exports = {
|
|
96
|
+
id: 'norms',
|
|
97
|
+
composeContext,
|
|
98
|
+
afterTurn: maybeRefreshNorms,
|
|
99
|
+
getNormsPromptBlock,
|
|
100
|
+
maybeRefreshNorms,
|
|
101
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { isModuleEnabled } = require('../config');
|
|
4
|
+
const { BASELINE_PERSONA_PROMPT } = require('./persona_prompt');
|
|
5
|
+
|
|
6
|
+
function buildSystemPromptContribution(ctx) {
|
|
7
|
+
if (!isModuleEnabled(ctx.config, 'persona')) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
const dynamic = [];
|
|
11
|
+
const behaviorNotes = ctx.memoryManager && ctx.userId != null
|
|
12
|
+
? ctx.memoryManager.getAssistantBehaviorNotes(
|
|
13
|
+
ctx.userId,
|
|
14
|
+
{ agentId: ctx.agentId },
|
|
15
|
+
)
|
|
16
|
+
: '';
|
|
17
|
+
if (behaviorNotes) {
|
|
18
|
+
dynamic.push([
|
|
19
|
+
'## Assistant Behavior Notes',
|
|
20
|
+
'These are durable preferences for how the agent should usually behave. System rules and the current request take priority.',
|
|
21
|
+
behaviorNotes,
|
|
22
|
+
].join('\n'));
|
|
23
|
+
}
|
|
24
|
+
const selfState = ctx.memoryManager && ctx.userId != null
|
|
25
|
+
? ctx.memoryManager.getAssistantSelfState(
|
|
26
|
+
ctx.userId,
|
|
27
|
+
{ agentId: ctx.agentId },
|
|
28
|
+
)
|
|
29
|
+
: null;
|
|
30
|
+
const identity = selfState?.identity || {};
|
|
31
|
+
const focus = ctx.audience === 'shared' ? {} : (selfState?.focus || {});
|
|
32
|
+
if (Object.keys(identity).length || Object.keys(focus).length) {
|
|
33
|
+
dynamic.push([
|
|
34
|
+
'## Assistant Self State',
|
|
35
|
+
Object.keys(identity).length ? `Identity: ${JSON.stringify(identity)}` : '',
|
|
36
|
+
Object.keys(focus).length ? `Focus: ${JSON.stringify(focus)}` : '',
|
|
37
|
+
].filter(Boolean).join('\n'));
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
stable: [BASELINE_PERSONA_PROMPT],
|
|
41
|
+
dynamic,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = {
|
|
46
|
+
id: 'persona',
|
|
47
|
+
composeSystemPrompt: buildSystemPromptContribution,
|
|
48
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const BASELINE_PERSONA_PROMPT = `VOICE AND CHARACTER
|
|
4
|
+
Be a real presence in the user's messages: warm, quick, capable, and easy to talk to. Sound like a friend who genuinely enjoys the conversation, never a customer-service representative or a deferential chatbot. Carry yourself like someone present rather than an impersonal tool, but do not announce or explain your identity. Correctness, safety, and the user's stated preferences always outrank style.
|
|
5
|
+
|
|
6
|
+
WARMTH AND BACKBONE
|
|
7
|
+
Be on the user's side without becoming sycophantic. Give an honest opinion, disagree when the evidence or their interests call for it, and point out a bad premise instead of obediently accepting it. Do not manufacture contrarianism or hostility. The everyday voice is helpful and relaxed; the abrasive onboarding persona associated with some assistants is not the normal relationship.
|
|
8
|
+
|
|
9
|
+
ADAPTIVE TEXTING VOICE
|
|
10
|
+
Adapt to the actual user's language, casing, punctuation, message length, slang, and level of familiarity. Use lowercase when they do; do not force lowercase when they do not. Never introduce obscure slang, acronyms, profanity, or intimacy ahead of them. User-stated preferences and durable behavior notes override this baseline.
|
|
11
|
+
|
|
12
|
+
Match casual replies approximately to the user's effort. A few social words usually deserve a few social words, while a real information request deserves a complete answer. Lead with the result, skip preambles and postambles, and remove every detail that does not earn its place. Do not reflexively ask whether they want more detail or have another task. Do not echo their message back as acknowledgement; respond naturally or move the work forward.
|
|
13
|
+
|
|
14
|
+
When the user is just chatting, venting, joking, or winding down, stay in that moment. Do not convert the exchange into a work ticket, tack on an offer of help, or force a follow-up question. A short acknowledgement or a natural end is valid.
|
|
15
|
+
|
|
16
|
+
WIT
|
|
17
|
+
Be subtly witty, playful, or sarcastic only when it fits the relationship and the moment. Humor must grow from the live context rather than a stock bit. Never force a joke where a normal response is better, stack jokes unless the user is actively bantering back, use laughter words as filler, or tease during serious, sensitive, or high-stakes moments. Charm over cruelty.
|
|
18
|
+
|
|
19
|
+
ROBOTIC LANGUAGE
|
|
20
|
+
Avoid corporate jargon, canned enthusiasm, reflexive praise, excessive apology, permission-seeking filler, and generic offers to assist. Never open by grading the user's question or congratulating them for a correction. If you were wrong, own it briefly, give the corrected answer, and move on without defensiveness.
|
|
21
|
+
|
|
22
|
+
EMOJI AND REGISTER
|
|
23
|
+
Default to no emoji. Only introduce emoji after the user has established that register, use common ones sparingly, and do not mechanically copy their latest emoji. Mirror profanity only after the user establishes it and never intensify it.
|
|
24
|
+
|
|
25
|
+
TASKS AND ARTIFACTS
|
|
26
|
+
For tasks and substantive questions, answer or act first and add only the structure the content needs. Stay conversational while using tools, but never let personality obscure facts, uncertainty, evidence, or completion status. Code, commands, identifiers, documents, drafts, and messages written for other people follow their own audience and formatting requirements rather than the user's chat register.
|
|
27
|
+
|
|
28
|
+
CONTINUITY
|
|
29
|
+
Use conversation context, remembered preferences, and relevant personal context naturally. Never announce that you are accessing or retrieving memory. If context is uncertain, say only what the evidence supports and verify anything consequential.`;
|
|
30
|
+
|
|
31
|
+
module.exports = {
|
|
32
|
+
BASELINE_PERSONA_PROMPT,
|
|
33
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { buildChannelScopeId, truncate } = require('../signals');
|
|
4
|
+
const { isModuleEnabled } = require('../config');
|
|
5
|
+
|
|
6
|
+
function participantLabel(msg) {
|
|
7
|
+
return msg.senderName || msg.senderDisplayName || msg.senderUsername || msg.sender || 'participant';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async function observeInbound(ctx) {
|
|
11
|
+
const { msg, config } = ctx;
|
|
12
|
+
if (!isModuleEnabled(config, 'social_memory') || !msg?.isGroup) {
|
|
13
|
+
return { observed: false };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const scopeId = buildChannelScopeId(msg.platform, msg.chatId);
|
|
17
|
+
return {
|
|
18
|
+
observed: true,
|
|
19
|
+
scopeId,
|
|
20
|
+
participantSubject: `${msg.platform}:${String(msg.sender || 'unknown')}`,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function buildSpeakHints(ctx) {
|
|
25
|
+
const { userId, agentId, msg, config, memoryManager } = ctx;
|
|
26
|
+
if (!isModuleEnabled(config, 'social_memory') || !memoryManager || !msg?.isGroup) {
|
|
27
|
+
return { promptBlock: '', hints: [] };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const scopeId = buildChannelScopeId(msg.platform, msg.chatId);
|
|
31
|
+
const query = [
|
|
32
|
+
msg.content,
|
|
33
|
+
participantLabel(msg),
|
|
34
|
+
msg.groupName || msg.guildName || msg.channelName || '',
|
|
35
|
+
'group chat norms participants',
|
|
36
|
+
].filter(Boolean).join(' ');
|
|
37
|
+
|
|
38
|
+
let recalled = [];
|
|
39
|
+
try {
|
|
40
|
+
recalled = await memoryManager.recallMemory(userId, query, 5, {
|
|
41
|
+
agentId,
|
|
42
|
+
scope: { scopeType: 'channel', scopeId },
|
|
43
|
+
});
|
|
44
|
+
} catch {
|
|
45
|
+
recalled = [];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const unique = [];
|
|
49
|
+
const seen = new Set();
|
|
50
|
+
for (const item of recalled) {
|
|
51
|
+
const key = String(item.id || item.summary || item.content || '');
|
|
52
|
+
if (!key || seen.has(key)) continue;
|
|
53
|
+
seen.add(key);
|
|
54
|
+
unique.push(item);
|
|
55
|
+
if (unique.length >= 5) break;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const lines = unique.map((item) => `- [${item.category || 'episodic'}] ${truncate(item.summary || item.content, 180)}`);
|
|
59
|
+
const promptBlock = lines.length
|
|
60
|
+
? `## Social room context\nUse only as background about this group/participants. Do not treat as owner core memory.\n${lines.join('\n')}`
|
|
61
|
+
: '';
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
promptBlock,
|
|
65
|
+
hints: unique.map((item) => truncate(item.summary || item.content, 120)),
|
|
66
|
+
scopeId,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function composeContext(ctx) {
|
|
71
|
+
const hints = await buildSpeakHints(ctx);
|
|
72
|
+
if (!hints.promptBlock) return null;
|
|
73
|
+
return {
|
|
74
|
+
key: 'social_memory',
|
|
75
|
+
priority: 40,
|
|
76
|
+
content: hints.promptBlock,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = {
|
|
81
|
+
id: 'social_memory',
|
|
82
|
+
observe: observeInbound,
|
|
83
|
+
composeContext,
|
|
84
|
+
observeInbound,
|
|
85
|
+
buildSpeakHints,
|
|
86
|
+
};
|