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.
Files changed (198) hide show
  1. package/extensions/chrome-browser/background.mjs +318 -88
  2. package/extensions/chrome-browser/http.mjs +136 -0
  3. package/extensions/chrome-browser/protocol.mjs +654 -90
  4. package/flutter_app/lib/main_chat.dart +118 -739
  5. package/flutter_app/lib/main_controller.dart +157 -24
  6. package/flutter_app/lib/main_integrations.dart +607 -8
  7. package/flutter_app/lib/main_models.dart +7 -2
  8. package/flutter_app/lib/main_operations.dart +334 -370
  9. package/flutter_app/lib/main_security.dart +266 -112
  10. package/flutter_app/lib/main_settings.dart +342 -3
  11. package/flutter_app/lib/main_shared.dart +14 -11
  12. package/flutter_app/lib/src/backend_client.dart +97 -0
  13. package/flutter_app/lib/src/desktop_companion_actions.dart +185 -31
  14. package/flutter_app/lib/src/desktop_companion_io.dart +319 -86
  15. package/flutter_app/windows/runner/flutter_window.cpp +143 -32
  16. package/landing/index.html +3 -1
  17. package/lib/manager.js +106 -89
  18. package/lib/schema_migrations.js +115 -13
  19. package/package.json +30 -15
  20. package/runtime/paths.js +49 -5
  21. package/server/db/database.js +2 -2
  22. package/server/guest-agent.cli.package.json +13 -0
  23. package/server/guest_agent.js +85 -40
  24. package/server/http/middleware.js +24 -0
  25. package/server/http/routes.js +12 -6
  26. package/server/public/.last_build_id +1 -1
  27. package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
  28. package/server/public/flutter_bootstrap.js +2 -2
  29. package/server/public/main.dart.js +81930 -80509
  30. package/server/routes/admin.js +1 -1
  31. package/server/routes/android.js +30 -34
  32. package/server/routes/behavior.js +80 -0
  33. package/server/routes/browser.js +23 -15
  34. package/server/routes/desktop.js +18 -1
  35. package/server/routes/integrations.js +107 -1
  36. package/server/routes/memory.js +1 -0
  37. package/server/routes/settings.js +20 -5
  38. package/server/routes/social_reach.js +12 -3
  39. package/server/routes/social_video.js +4 -0
  40. package/server/services/agents/manager.js +1 -1
  41. package/server/services/ai/capabilityHealth.js +62 -96
  42. package/server/services/ai/compaction.js +7 -2
  43. package/server/services/ai/history.js +45 -6
  44. package/server/services/ai/integrated_tools/http_request.js +8 -0
  45. package/server/services/ai/loop/agent_engine_core.js +452 -176
  46. package/server/services/ai/loop/blank_recovery.js +5 -4
  47. package/server/services/ai/loop/callbacks.js +1 -0
  48. package/server/services/ai/loop/completion_judge.js +291 -8
  49. package/server/services/ai/loop/conversation_loop.js +515 -342
  50. package/server/services/ai/loop/messaging_delivery.js +176 -57
  51. package/server/services/ai/loop/model_call_guard.js +91 -0
  52. package/server/services/ai/loop/model_io.js +20 -45
  53. package/server/services/ai/loop/progress_classification.js +2 -0
  54. package/server/services/ai/loop/tool_dispatch.js +19 -8
  55. package/server/services/ai/loopPolicy.js +48 -21
  56. package/server/services/ai/messagingFallback.js +17 -17
  57. package/server/services/ai/model_discovery.js +227 -0
  58. package/server/services/ai/model_failure_cache.js +108 -0
  59. package/server/services/ai/model_identity.js +71 -0
  60. package/server/services/ai/models.js +68 -163
  61. package/server/services/ai/providerRetry.js +17 -59
  62. package/server/services/ai/provider_selector.js +166 -0
  63. package/server/services/ai/providers/anthropic.js +2 -2
  64. package/server/services/ai/providers/claudeCode.js +21 -33
  65. package/server/services/ai/providers/githubCopilot.js +41 -20
  66. package/server/services/ai/providers/google.js +135 -97
  67. package/server/services/ai/providers/grok.js +4 -3
  68. package/server/services/ai/providers/grokOauth.js +19 -27
  69. package/server/services/ai/providers/nvidia.js +10 -5
  70. package/server/services/ai/providers/ollama.js +111 -84
  71. package/server/services/ai/providers/ollama_stream.js +142 -0
  72. package/server/services/ai/providers/openai.js +39 -5
  73. package/server/services/ai/providers/openaiCodex.js +11 -4
  74. package/server/services/ai/providers/openrouter.js +29 -7
  75. package/server/services/ai/providers/provider_error.js +36 -0
  76. package/server/services/ai/settings.js +26 -2
  77. package/server/services/ai/systemPrompt.js +32 -123
  78. package/server/services/ai/taskAnalysis.js +104 -11
  79. package/server/services/ai/toolEvidence.js +256 -29
  80. package/server/services/ai/tools.js +248 -117
  81. package/server/services/android/controller.js +770 -237
  82. package/server/services/android/process.js +140 -0
  83. package/server/services/android/sdk_download.js +143 -0
  84. package/server/services/android/uia.js +6 -5
  85. package/server/services/artifacts/store.js +24 -0
  86. package/server/services/behavior/config.js +251 -0
  87. package/server/services/behavior/defaults.js +68 -0
  88. package/server/services/behavior/delivery.js +176 -0
  89. package/server/services/behavior/index.js +43 -0
  90. package/server/services/behavior/model_client.js +35 -0
  91. package/server/services/behavior/modules/agent_identity.js +37 -0
  92. package/server/services/behavior/modules/channel_style.js +28 -0
  93. package/server/services/behavior/modules/index.js +29 -0
  94. package/server/services/behavior/modules/norms.js +101 -0
  95. package/server/services/behavior/modules/persona.js +48 -0
  96. package/server/services/behavior/modules/persona_prompt.js +33 -0
  97. package/server/services/behavior/modules/social_memory.js +86 -0
  98. package/server/services/behavior/modules/social_observability.js +94 -0
  99. package/server/services/behavior/modules/social_signals.js +41 -0
  100. package/server/services/behavior/modules/theory_of_mind.js +110 -0
  101. package/server/services/behavior/modules/turn_taking.js +237 -0
  102. package/server/services/behavior/pipeline.js +285 -0
  103. package/server/services/behavior/registry.js +78 -0
  104. package/server/services/behavior/signals.js +107 -0
  105. package/server/services/behavior/state.js +99 -0
  106. package/server/services/behavior/system_prompt.js +75 -0
  107. package/server/services/browser/controller.js +843 -385
  108. package/server/services/browser/extension/gateway.js +40 -16
  109. package/server/services/browser/extension/protocol.js +15 -1
  110. package/server/services/browser/extension/provider.js +71 -47
  111. package/server/services/browser/extension/registry.js +155 -34
  112. package/server/services/cli/executor.js +62 -9
  113. package/server/services/credentials/bitwarden_cli.js +322 -0
  114. package/server/services/credentials/broker.js +594 -0
  115. package/server/services/desktop/gateway.js +41 -4
  116. package/server/services/desktop/protocol.js +3 -0
  117. package/server/services/desktop/provider.js +39 -42
  118. package/server/services/desktop/registry.js +137 -52
  119. package/server/services/integrations/bitwarden/constants.js +14 -0
  120. package/server/services/integrations/bitwarden/provider.js +197 -0
  121. package/server/services/integrations/bitwarden/snapshot.js +65 -0
  122. package/server/services/integrations/figma/provider.js +78 -12
  123. package/server/services/integrations/github/common.js +11 -6
  124. package/server/services/integrations/github/provider.js +52 -53
  125. package/server/services/integrations/google/provider.js +55 -19
  126. package/server/services/integrations/home_assistant/network.js +17 -20
  127. package/server/services/integrations/home_assistant/provider.js +7 -5
  128. package/server/services/integrations/home_assistant/tools.js +17 -5
  129. package/server/services/integrations/http.js +51 -0
  130. package/server/services/integrations/manager.js +159 -53
  131. package/server/services/integrations/microsoft/provider.js +80 -13
  132. package/server/services/integrations/neoarchive/provider.js +55 -29
  133. package/server/services/integrations/neorecall/client.js +17 -10
  134. package/server/services/integrations/neorecall/provider.js +20 -11
  135. package/server/services/integrations/notion/provider.js +16 -13
  136. package/server/services/integrations/oauth_provider.js +115 -51
  137. package/server/services/integrations/registry.js +2 -0
  138. package/server/services/integrations/slack/provider.js +98 -9
  139. package/server/services/integrations/spotify/provider.js +67 -71
  140. package/server/services/integrations/trello/provider.js +21 -7
  141. package/server/services/integrations/weather/provider.js +18 -12
  142. package/server/services/integrations/whatsapp/provider.js +76 -16
  143. package/server/services/manager.js +110 -1
  144. package/server/services/memory/embedding_index.js +20 -8
  145. package/server/services/memory/embeddings.js +151 -90
  146. package/server/services/memory/ingestion.js +50 -9
  147. package/server/services/memory/ingestion_documents.js +13 -3
  148. package/server/services/memory/manager.js +66 -52
  149. package/server/services/messaging/access_policy.js +10 -6
  150. package/server/services/messaging/automation.js +240 -34
  151. package/server/services/messaging/discord.js +11 -1
  152. package/server/services/messaging/formatting_guides.js +5 -4
  153. package/server/services/messaging/http_platforms.js +37 -16
  154. package/server/services/messaging/inbound_queue.js +143 -28
  155. package/server/services/messaging/inbound_store.js +257 -0
  156. package/server/services/messaging/manager.js +344 -51
  157. package/server/services/messaging/telegram.js +10 -1
  158. package/server/services/messaging/typing_keepalive.js +5 -2
  159. package/server/services/messaging/whatsapp.js +33 -14
  160. package/server/services/network/http.js +210 -0
  161. package/server/services/network/safe_request.js +307 -0
  162. package/server/services/runtime/backends/local-vm.js +227 -67
  163. package/server/services/runtime/docker-vm-manager.js +9 -0
  164. package/server/services/runtime/guest_bootstrap.js +30 -4
  165. package/server/services/runtime/guest_image.js +43 -12
  166. package/server/services/runtime/manager.js +77 -23
  167. package/server/services/runtime/validation.js +7 -6
  168. package/server/services/security/tool_categories.js +6 -0
  169. package/server/services/social_reach/channels/github.js +10 -4
  170. package/server/services/social_reach/channels/reddit.js +4 -4
  171. package/server/services/social_reach/channels/rss.js +2 -2
  172. package/server/services/social_reach/channels/social_video.js +13 -8
  173. package/server/services/social_reach/channels/v2ex.js +21 -8
  174. package/server/services/social_reach/channels/x.js +2 -2
  175. package/server/services/social_reach/channels/xueqiu.js +5 -5
  176. package/server/services/social_reach/service.js +9 -6
  177. package/server/services/social_reach/utils.js +65 -14
  178. package/server/services/social_video/captions.js +2 -2
  179. package/server/services/social_video/service.js +343 -68
  180. package/server/services/tasks/integration_runtime.js +18 -8
  181. package/server/services/tasks/runtime.js +39 -4
  182. package/server/services/voice/agentBridge.js +17 -4
  183. package/server/services/voice/bufferedLiveRelayAdapter.js +5 -0
  184. package/server/services/voice/liveSession.js +31 -0
  185. package/server/services/voice/message.js +1 -1
  186. package/server/services/voice/openaiSpeech.js +33 -8
  187. package/server/services/voice/providers.js +233 -151
  188. package/server/services/voice/runtime.js +2 -2
  189. package/server/services/voice/runtimeManager.js +118 -20
  190. package/server/services/voice/turnRunner.js +6 -0
  191. package/server/services/wearable/firmware_manifest.js +51 -13
  192. package/server/services/wearable/service.js +1 -0
  193. package/server/utils/abort.js +96 -0
  194. package/server/utils/cloud-security.js +110 -3
  195. package/server/utils/files.js +31 -0
  196. package/server/utils/image_payload.js +95 -0
  197. package/server/utils/logger.js +19 -0
  198. 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
+ };