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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/flutter_app/lib/main_controller.dart +46 -4
  2. package/flutter_app/lib/main_models.dart +4 -2
  3. package/flutter_app/lib/main_operations.dart +0 -49
  4. package/flutter_app/lib/main_settings.dart +338 -0
  5. package/flutter_app/lib/src/backend_client.dart +19 -0
  6. package/package.json +1 -1
  7. package/server/http/routes.js +1 -0
  8. package/server/public/.last_build_id +1 -1
  9. package/server/public/flutter_bootstrap.js +1 -1
  10. package/server/public/main.dart.js +65635 -65088
  11. package/server/routes/behavior.js +80 -0
  12. package/server/routes/settings.js +4 -0
  13. package/server/services/ai/history.js +1 -1
  14. package/server/services/ai/loop/agent_engine_core.js +64 -16
  15. package/server/services/ai/loop/completion_judge.js +211 -44
  16. package/server/services/ai/loop/conversation_loop.js +6 -11
  17. package/server/services/ai/loop/messaging_delivery.js +47 -0
  18. package/server/services/ai/messagingFallback.js +2 -2
  19. package/server/services/ai/systemPrompt.js +30 -128
  20. package/server/services/ai/taskAnalysis.js +47 -2
  21. package/server/services/ai/toolEvidence.js +65 -159
  22. package/server/services/ai/tools.js +60 -8
  23. package/server/services/behavior/config.js +251 -0
  24. package/server/services/behavior/defaults.js +68 -0
  25. package/server/services/behavior/delivery.js +176 -0
  26. package/server/services/behavior/index.js +43 -0
  27. package/server/services/behavior/model_client.js +35 -0
  28. package/server/services/behavior/modules/agent_identity.js +37 -0
  29. package/server/services/behavior/modules/channel_style.js +28 -0
  30. package/server/services/behavior/modules/index.js +29 -0
  31. package/server/services/behavior/modules/norms.js +101 -0
  32. package/server/services/behavior/modules/persona.js +48 -0
  33. package/server/services/behavior/modules/persona_prompt.js +33 -0
  34. package/server/services/behavior/modules/social_memory.js +86 -0
  35. package/server/services/behavior/modules/social_observability.js +94 -0
  36. package/server/services/behavior/modules/social_signals.js +41 -0
  37. package/server/services/behavior/modules/theory_of_mind.js +110 -0
  38. package/server/services/behavior/modules/turn_taking.js +237 -0
  39. package/server/services/behavior/pipeline.js +285 -0
  40. package/server/services/behavior/registry.js +78 -0
  41. package/server/services/behavior/signals.js +107 -0
  42. package/server/services/behavior/state.js +99 -0
  43. package/server/services/behavior/system_prompt.js +75 -0
  44. package/server/services/memory/manager.js +14 -33
  45. package/server/services/messaging/access_policy.js +10 -6
  46. package/server/services/messaging/automation.js +158 -27
  47. package/server/services/messaging/discord.js +11 -1
  48. package/server/services/messaging/formatting_guides.js +2 -4
  49. package/server/services/messaging/http_platforms.js +4 -3
  50. package/server/services/messaging/inbound_queue.js +74 -13
  51. package/server/services/messaging/inbound_store.js +33 -0
  52. package/server/services/messaging/manager.js +18 -0
  53. package/server/services/messaging/telegram.js +10 -1
  54. package/server/services/messaging/whatsapp.js +11 -0
  55. package/server/services/social_reach/channels/social_video.js +1 -1
  56. package/server/services/social_video/captions.js +2 -2
  57. package/server/services/social_video/service.js +194 -29
  58. package/server/services/voice/message.js +1 -1
  59. package/server/services/voice/runtime.js +2 -2
  60. package/server/utils/logger.js +19 -0
  61. package/server/services/ai/terminal_reply.js +0 -18
@@ -132,6 +132,7 @@ class NeoAgentController extends ChangeNotifier {
132
132
  const <LinkedAuthProviderItem>[];
133
133
  QrLoginChallenge? qrLoginChallenge;
134
134
  Map<String, dynamic> settings = const <String, dynamic>{};
135
+ Map<String, dynamic> behaviorConfig = const <String, dynamic>{};
135
136
  Map<String, dynamic>? versionInfo;
136
137
  Map<String, dynamic>? backendHealthStatus;
137
138
  HealthBridgeStatus? deviceHealthStatus;
@@ -1420,6 +1421,7 @@ class NeoAgentController extends ChangeNotifier {
1420
1421
  usageAndLimits = null;
1421
1422
  linkedAuthProviders = const <LinkedAuthProviderItem>[];
1422
1423
  settings = const <String, dynamic>{};
1424
+ behaviorConfig = const <String, dynamic>{};
1423
1425
  chatMessages = const <ChatEntry>[];
1424
1426
  _resetChatHistoryPagination();
1425
1427
  agentProfiles = const <AgentProfile>[];
@@ -1958,6 +1960,11 @@ class NeoAgentController extends ChangeNotifier {
1958
1960
  _backendClient.fetchSettings(backendUrl, agentId: agentId),
1959
1961
  const <String, dynamic>{},
1960
1962
  );
1963
+ final behaviorFuture = _softRefreshLoad<Map<String, dynamic>>(
1964
+ 'behavior_config',
1965
+ _backendClient.fetchBehaviorConfig(backendUrl, agentId: agentId),
1966
+ const <String, dynamic>{},
1967
+ );
1961
1968
  final runsFuture = _softRefreshLoad<Map<String, dynamic>>(
1962
1969
  'runs',
1963
1970
  _backendClient.fetchRuns(backendUrl, agentId: agentId),
@@ -2094,6 +2101,7 @@ class NeoAgentController extends ChangeNotifier {
2094
2101
  final modelsResponse = await modelsFuture;
2095
2102
  final providersResponse = await providersFuture;
2096
2103
  final settingsResponse = await settingsFuture;
2104
+ final behaviorResponse = await behaviorFuture;
2097
2105
  final runsResponse = await runsFuture;
2098
2106
  final timelineResponse = await timelineFuture;
2099
2107
  final versionResponse = await versionFuture;
@@ -2137,6 +2145,9 @@ class NeoAgentController extends ChangeNotifier {
2137
2145
  );
2138
2146
 
2139
2147
  settings = Map<String, dynamic>.from(settingsResponse);
2148
+ behaviorConfig = behaviorResponse['config'] is Map
2149
+ ? Map<String, dynamic>.from(behaviorResponse['config'] as Map)
2150
+ : const <String, dynamic>{};
2140
2151
  recentRuns = _decodeModelList(
2141
2152
  'runs',
2142
2153
  runsResponse['runs'],
@@ -4221,6 +4232,27 @@ class NeoAgentController extends ChangeNotifier {
4221
4232
  }
4222
4233
  }
4223
4234
 
4235
+ Future<void> saveBehaviorConfig(Map<String, dynamic> config) async {
4236
+ isSavingSettings = true;
4237
+ errorMessage = null;
4238
+ notifyListeners();
4239
+ try {
4240
+ final response = await _backendClient.saveBehaviorConfig(
4241
+ backendUrl,
4242
+ config,
4243
+ agentId: _scopedAgentId,
4244
+ );
4245
+ behaviorConfig = response['config'] is Map
4246
+ ? Map<String, dynamic>.from(response['config'] as Map)
4247
+ : Map<String, dynamic>.from(config);
4248
+ } catch (error) {
4249
+ errorMessage = _friendlyErrorMessage(error);
4250
+ } finally {
4251
+ isSavingSettings = false;
4252
+ notifyListeners();
4253
+ }
4254
+ }
4255
+
4224
4256
  void _applyAccountResponse(Map<String, dynamic> response) {
4225
4257
  if (response['user'] is Map) {
4226
4258
  user = Map<String, dynamic>.from(response['user'] as Map);
@@ -5338,10 +5370,20 @@ class NeoAgentController extends ChangeNotifier {
5338
5370
  }
5339
5371
 
5340
5372
  Future<void> updateAssistantBehaviorNotes(String content) async {
5341
- await _backendClient.saveSettings(backendUrl, <String, dynamic>{
5342
- 'assistant_behavior_notes': content,
5343
- }, agentId: _scopedAgentId);
5344
- await refreshMemory();
5373
+ isSavingSettings = true;
5374
+ errorMessage = null;
5375
+ notifyListeners();
5376
+ try {
5377
+ await _backendClient.saveSettings(backendUrl, <String, dynamic>{
5378
+ 'assistant_behavior_notes': content,
5379
+ }, agentId: _scopedAgentId);
5380
+ await refreshMemory();
5381
+ } catch (error) {
5382
+ errorMessage = _friendlyErrorMessage(error);
5383
+ } finally {
5384
+ isSavingSettings = false;
5385
+ notifyListeners();
5386
+ }
5345
5387
  }
5346
5388
 
5347
5389
  Future<void> updateCoreMemory(String key, String value) async {
@@ -2548,7 +2548,8 @@ class OfficialIntegrationAppItem {
2548
2548
  String get effectiveStatus =>
2549
2549
  !isConnected && hasExpiredAccounts ? 'expired' : connection.status;
2550
2550
 
2551
- String get statusLabel => _titleCase(effectiveStatus.replaceAll('_', ' '));
2551
+ String get statusLabel =>
2552
+ effectiveStatus == 'expired' ? 'Expired' : connection.statusLabel;
2552
2553
  }
2553
2554
 
2554
2555
  class OfficialIntegrationEnvStatus {
@@ -2780,7 +2781,8 @@ class OfficialIntegrationItem {
2780
2781
  String get effectiveStatus =>
2781
2782
  !isConnected && hasExpiredAccounts ? 'expired' : connection.status;
2782
2783
 
2783
- String get statusLabel => _titleCase(effectiveStatus.replaceAll('_', ' '));
2784
+ String get statusLabel =>
2785
+ effectiveStatus == 'expired' ? 'Expired' : connection.statusLabel;
2784
2786
  }
2785
2787
 
2786
2788
  class SkillDocument {
@@ -1663,11 +1663,6 @@ class _MemoryPanelState extends State<MemoryPanel>
1663
1663
  icon: Icon(Icons.bug_report_outlined),
1664
1664
  label: Text('Inspect'),
1665
1665
  ),
1666
- OutlinedButton.icon(
1667
- onPressed: () => _openBehaviorNotesEditor(context, controller),
1668
- icon: Icon(Icons.edit_outlined),
1669
- label: Text('Behavior Notes'),
1670
- ),
1671
1666
  FilledButton.icon(
1672
1667
  onPressed: () => _openMemoryCreator(context, controller),
1673
1668
  icon: Icon(Icons.add),
@@ -2291,50 +2286,6 @@ class _MemoryPanelState extends State<MemoryPanel>
2291
2286
  );
2292
2287
  }
2293
2288
 
2294
- Future<void> _openBehaviorNotesEditor(
2295
- BuildContext context,
2296
- NeoAgentController controller,
2297
- ) async {
2298
- final contentController = TextEditingController(
2299
- text: controller.memoryOverview.assistantBehaviorNotes,
2300
- );
2301
- await showDialog<void>(
2302
- context: context,
2303
- builder: (context) {
2304
- return AlertDialog(
2305
- backgroundColor: _bgCard,
2306
- title: Text('Edit Assistant Behavior Notes'),
2307
- content: SizedBox(
2308
- width: 720,
2309
- child: TextField(
2310
- controller: contentController,
2311
- minLines: 16,
2312
- maxLines: 24,
2313
- decoration: const InputDecoration(
2314
- labelText: 'assistant_behavior_notes',
2315
- ),
2316
- ),
2317
- ),
2318
- actions: <Widget>[
2319
- TextButton(
2320
- onPressed: () => Navigator.of(context).pop(),
2321
- child: Text('Cancel'),
2322
- ),
2323
- FilledButton(
2324
- onPressed: () async {
2325
- await controller.updateAssistantBehaviorNotes(
2326
- contentController.text,
2327
- );
2328
- if (context.mounted) Navigator.of(context).pop();
2329
- },
2330
- child: Text('Save'),
2331
- ),
2332
- ],
2333
- );
2334
- },
2335
- );
2336
- }
2337
-
2338
2289
  Future<void> _openCoreMemoryEditor(
2339
2290
  BuildContext context,
2340
2291
  NeoAgentController controller, {
@@ -106,6 +106,18 @@ const _workspaceSettingsSection = _SettingsSection('workspace', <String>[
106
106
  'routing',
107
107
  ]);
108
108
 
109
+ const _behaviorSettingsSection = _SettingsSection('behavior', <String>[
110
+ 'behavior',
111
+ 'persona',
112
+ 'social intelligence',
113
+ 'turn taking',
114
+ 'groups',
115
+ 'memory',
116
+ 'norms',
117
+ 'theory of mind',
118
+ 'delivery',
119
+ ]);
120
+
109
121
  const _modelsSettingsSection = _SettingsSection('models', <String>[
110
122
  'models',
111
123
  'providers',
@@ -173,6 +185,7 @@ const _securitySettingsSection = _SettingsSection('security', <String>[
173
185
 
174
186
  const List<_SettingsSection> _settingsSearchSections = <_SettingsSection>[
175
187
  _overviewSettingsSection,
188
+ _behaviorSettingsSection,
176
189
  _workspaceSettingsSection,
177
190
  _socialReachSettingsSection,
178
191
  _modelsSettingsSection,
@@ -200,6 +213,17 @@ class _SettingsPanelState extends State<SettingsPanel> {
200
213
  final Map<String, bool> _providerEnabled = <String, bool>{};
201
214
  final Map<String, TextEditingController> _providerBaseUrlControllers =
202
215
  <String, TextEditingController>{};
216
+ late final TextEditingController _behaviorNotesController;
217
+ late bool _behaviorEnabled;
218
+ late String _behaviorParticipationMode;
219
+ late double _behaviorMinimumNeedScore;
220
+ late double _behaviorBatchWindowMs;
221
+ late String _behaviorDecisionModelId;
222
+ late String _behaviorDeliveryStyle;
223
+ late bool _behaviorTheoryOfMindEnabled;
224
+ late bool _behaviorSocialMemoryEnabled;
225
+ late bool _behaviorNormsEnabled;
226
+ late bool _behaviorObservabilityEnabled;
203
227
 
204
228
  bool _hasUnsavedChanges = false;
205
229
 
@@ -218,12 +242,14 @@ class _SettingsPanelState extends State<SettingsPanel> {
218
242
  void initState() {
219
243
  super.initState();
220
244
  _searchController = TextEditingController();
245
+ _behaviorNotesController = TextEditingController();
221
246
  _hydrate();
222
247
  }
223
248
 
224
249
  @override
225
250
  void dispose() {
226
251
  _searchController.dispose();
252
+ _behaviorNotesController.dispose();
227
253
  for (final controller in _providerBaseUrlControllers.values) {
228
254
  controller.dispose();
229
255
  }
@@ -234,6 +260,10 @@ class _SettingsPanelState extends State<SettingsPanel> {
234
260
  void didUpdateWidget(covariant SettingsPanel oldWidget) {
235
261
  super.didUpdateWidget(oldWidget);
236
262
  if (oldWidget.controller.settings != widget.controller.settings ||
263
+ oldWidget.controller.behaviorConfig !=
264
+ widget.controller.behaviorConfig ||
265
+ oldWidget.controller.memoryOverview !=
266
+ widget.controller.memoryOverview ||
237
267
  oldWidget.controller.aiProviders != widget.controller.aiProviders ||
238
268
  oldWidget.controller.supportedModels !=
239
269
  widget.controller.supportedModels) {
@@ -270,6 +300,45 @@ class _SettingsPanelState extends State<SettingsPanel> {
270
300
  _voiceLiveProvider = controller.voiceLiveProvider;
271
301
  _voiceLiveModel = controller.voiceLiveModel;
272
302
  _voiceLiveVoice = controller.voiceLiveVoice;
303
+ final behavior = controller.behaviorConfig;
304
+ final modules = behavior['modules'] is Map
305
+ ? Map<String, dynamic>.from(behavior['modules'] as Map)
306
+ : const <String, dynamic>{};
307
+ bool moduleEnabled(String id) {
308
+ final module = modules[id];
309
+ return module is! Map || module['enabled'] != false;
310
+ }
311
+
312
+ _behaviorEnabled = behavior['enabled'] != false;
313
+ _behaviorParticipationMode =
314
+ <String>{
315
+ 'automatic',
316
+ 'mention_only',
317
+ 'always',
318
+ }.contains(behavior['participationMode']?.toString())
319
+ ? behavior['participationMode'].toString()
320
+ : 'automatic';
321
+ _behaviorMinimumNeedScore =
322
+ ((behavior['minimumNeedScore'] as num?)?.toDouble() ?? 0.72)
323
+ .clamp(0.0, 1.0)
324
+ .toDouble();
325
+ _behaviorBatchWindowMs =
326
+ ((behavior['batchWindowMs'] as num?)?.toDouble() ?? 900)
327
+ .clamp(0.0, 5000.0)
328
+ .toDouble();
329
+ _behaviorDecisionModelId =
330
+ behavior['decisionModelId']?.toString().trim() ?? '';
331
+ _behaviorDeliveryStyle = behavior['deliveryStyle'] == 'single'
332
+ ? 'single'
333
+ : 'natural_bubbles';
334
+ _behaviorTheoryOfMindEnabled = moduleEnabled('theory_of_mind');
335
+ _behaviorSocialMemoryEnabled = moduleEnabled('social_memory');
336
+ _behaviorNormsEnabled = moduleEnabled('norms');
337
+ _behaviorObservabilityEnabled = moduleEnabled('social_observability');
338
+ final behaviorNotes = controller.memoryOverview.assistantBehaviorNotes;
339
+ if (_behaviorNotesController.text != behaviorNotes) {
340
+ _behaviorNotesController.text = behaviorNotes;
341
+ }
273
342
  if (!_voiceLiveModelsByProvider.containsKey(_voiceLiveProvider)) {
274
343
  _voiceLiveProvider = 'openai';
275
344
  }
@@ -401,6 +470,13 @@ class _SettingsPanelState extends State<SettingsPanel> {
401
470
  _buildSettingsOverview(controller, availableModels.length),
402
471
  const SizedBox(height: 16),
403
472
  ],
473
+ if (_matchesSettingsSection(
474
+ searchQuery,
475
+ _behaviorSettingsSection,
476
+ )) ...<Widget>[
477
+ _buildBehaviorSection(controller, routingModels),
478
+ const SizedBox(height: 16),
479
+ ],
404
480
  if (_matchesSettingsSection(
405
481
  searchQuery,
406
482
  _workspaceSettingsSection,
@@ -522,6 +598,42 @@ class _SettingsPanelState extends State<SettingsPanel> {
522
598
  voiceLiveVoice: _voiceLiveVoice,
523
599
  aiProviderConfigs: _buildProviderPayload(),
524
600
  );
601
+ if (controller.errorMessage != null) return;
602
+ final existingModules = controller.behaviorConfig['modules'] is Map
603
+ ? Map<String, dynamic>.from(controller.behaviorConfig['modules'] as Map)
604
+ : <String, dynamic>{};
605
+ existingModules.addAll(<String, dynamic>{
606
+ 'theory_of_mind': <String, dynamic>{
607
+ 'enabled': _behaviorTheoryOfMindEnabled,
608
+ },
609
+ 'social_memory': <String, dynamic>{
610
+ 'enabled': _behaviorSocialMemoryEnabled,
611
+ },
612
+ 'norms': <String, dynamic>{'enabled': _behaviorNormsEnabled},
613
+ 'social_observability': <String, dynamic>{
614
+ 'enabled': _behaviorObservabilityEnabled,
615
+ },
616
+ });
617
+ await controller.saveBehaviorConfig(<String, dynamic>{
618
+ ...controller.behaviorConfig,
619
+ 'enabled': _behaviorEnabled,
620
+ 'participationMode': _behaviorParticipationMode,
621
+ 'minimumNeedScore': _behaviorMinimumNeedScore,
622
+ 'batchWindowMs': _behaviorBatchWindowMs.round(),
623
+ 'decisionModelId': _behaviorDecisionModelId.isEmpty
624
+ ? null
625
+ : _behaviorDecisionModelId,
626
+ 'deliveryStyle': _behaviorDeliveryStyle,
627
+ 'modules': existingModules,
628
+ });
629
+ if (controller.errorMessage != null) return;
630
+ if (_behaviorNotesController.text !=
631
+ controller.memoryOverview.assistantBehaviorNotes) {
632
+ await controller.updateAssistantBehaviorNotes(
633
+ _behaviorNotesController.text,
634
+ );
635
+ if (controller.errorMessage != null) return;
636
+ }
525
637
  if (mounted) setState(() => _hasUnsavedChanges = false);
526
638
  }
527
639
 
@@ -627,6 +739,232 @@ class _SettingsPanelState extends State<SettingsPanel> {
627
739
  );
628
740
  }
629
741
 
742
+ Widget _buildBehaviorSection(
743
+ NeoAgentController controller,
744
+ List<ModelMeta> routingModels,
745
+ ) {
746
+ final modelIds = <String>{
747
+ if (_behaviorDecisionModelId.isNotEmpty) _behaviorDecisionModelId,
748
+ ...routingModels.map((model) => model.id),
749
+ }.toList();
750
+ return Card(
751
+ child: Padding(
752
+ padding: const EdgeInsets.all(20),
753
+ child: Column(
754
+ crossAxisAlignment: CrossAxisAlignment.start,
755
+ children: <Widget>[
756
+ const _SectionTitle('Behavior Modules'),
757
+ const SizedBox(height: 10),
758
+ Text(
759
+ 'One runtime controls persona, group turn-taking, room memory, norms, Theory of Mind, and delivery.',
760
+ style: TextStyle(color: _textSecondary, height: 1.45),
761
+ ),
762
+ const SizedBox(height: 12),
763
+ SwitchListTile.adaptive(
764
+ contentPadding: EdgeInsets.zero,
765
+ title: const Text('Enable behavior modules'),
766
+ subtitle: const Text(
767
+ 'Direct messages remain responsive. Allowlisted groups use the participation mode below.',
768
+ ),
769
+ value: _behaviorEnabled,
770
+ onChanged: (value) => setState(() {
771
+ _behaviorEnabled = value;
772
+ _hasUnsavedChanges = true;
773
+ }),
774
+ ),
775
+ const SizedBox(height: 8),
776
+ DropdownButtonFormField<String>(
777
+ initialValue: _behaviorParticipationMode,
778
+ decoration: const InputDecoration(
779
+ labelText: 'Default group participation',
780
+ helperText:
781
+ 'Automatic reads the room and normally holds back. Mention-only makes no decision call until directly addressed.',
782
+ ),
783
+ items: const <DropdownMenuItem<String>>[
784
+ DropdownMenuItem(
785
+ value: 'automatic',
786
+ child: Text('Automatic, reserved'),
787
+ ),
788
+ DropdownMenuItem(
789
+ value: 'mention_only',
790
+ child: Text('Mention or reply only'),
791
+ ),
792
+ DropdownMenuItem(value: 'always', child: Text('Always engage')),
793
+ ],
794
+ onChanged: !_behaviorEnabled
795
+ ? null
796
+ : (value) {
797
+ if (value == null) return;
798
+ setState(() {
799
+ _behaviorParticipationMode = value;
800
+ _hasUnsavedChanges = true;
801
+ });
802
+ },
803
+ ),
804
+ const SizedBox(height: 18),
805
+ Text(
806
+ 'Minimum contribution value: ${_behaviorMinimumNeedScore.toStringAsFixed(2)}',
807
+ style: TextStyle(
808
+ color: _textPrimary,
809
+ fontWeight: FontWeight.w600,
810
+ ),
811
+ ),
812
+ Slider(
813
+ value: _behaviorMinimumNeedScore,
814
+ min: 0,
815
+ max: 1,
816
+ divisions: 20,
817
+ label: _behaviorMinimumNeedScore.toStringAsFixed(2),
818
+ onChanged: !_behaviorEnabled
819
+ ? null
820
+ : (value) => setState(() {
821
+ _behaviorMinimumNeedScore = value;
822
+ _hasUnsavedChanges = true;
823
+ }),
824
+ ),
825
+ Text(
826
+ 'Higher values make NeoAgent more selective in groups.',
827
+ style: TextStyle(color: _textSecondary, fontSize: 12),
828
+ ),
829
+ const SizedBox(height: 14),
830
+ Text(
831
+ 'Room batch window: ${_behaviorBatchWindowMs.round()} ms',
832
+ style: TextStyle(
833
+ color: _textPrimary,
834
+ fontWeight: FontWeight.w600,
835
+ ),
836
+ ),
837
+ Slider(
838
+ value: _behaviorBatchWindowMs,
839
+ min: 0,
840
+ max: 5000,
841
+ divisions: 20,
842
+ label: '${_behaviorBatchWindowMs.round()} ms',
843
+ onChanged: !_behaviorEnabled
844
+ ? null
845
+ : (value) => setState(() {
846
+ _behaviorBatchWindowMs = value;
847
+ _hasUnsavedChanges = true;
848
+ }),
849
+ ),
850
+ const SizedBox(height: 12),
851
+ DropdownButtonFormField<String>(
852
+ initialValue: _behaviorDecisionModelId,
853
+ decoration: const InputDecoration(
854
+ labelText: 'Turn-taking model',
855
+ helperText:
856
+ 'Automatic selects a fast model through the normal model catalog.',
857
+ ),
858
+ items: <DropdownMenuItem<String>>[
859
+ const DropdownMenuItem(
860
+ value: '',
861
+ child: Text('Automatic (fast)'),
862
+ ),
863
+ ...modelIds.map(
864
+ (id) => DropdownMenuItem(value: id, child: Text(id)),
865
+ ),
866
+ ],
867
+ onChanged: !_behaviorEnabled
868
+ ? null
869
+ : (value) {
870
+ if (value == null) return;
871
+ setState(() {
872
+ _behaviorDecisionModelId = value;
873
+ _hasUnsavedChanges = true;
874
+ });
875
+ },
876
+ ),
877
+ const SizedBox(height: 12),
878
+ DropdownButtonFormField<String>(
879
+ initialValue: _behaviorDeliveryStyle,
880
+ decoration: const InputDecoration(
881
+ labelText: 'Messaging delivery',
882
+ ),
883
+ items: const <DropdownMenuItem<String>>[
884
+ DropdownMenuItem(
885
+ value: 'natural_bubbles',
886
+ child: Text('Natural bubbles'),
887
+ ),
888
+ DropdownMenuItem(
889
+ value: 'single',
890
+ child: Text('Single message'),
891
+ ),
892
+ ],
893
+ onChanged: !_behaviorEnabled
894
+ ? null
895
+ : (value) {
896
+ if (value == null) return;
897
+ setState(() {
898
+ _behaviorDeliveryStyle = value;
899
+ _hasUnsavedChanges = true;
900
+ });
901
+ },
902
+ ),
903
+ const SizedBox(height: 10),
904
+ SwitchListTile.adaptive(
905
+ contentPadding: EdgeInsets.zero,
906
+ title: const Text('Theory of Mind refinement'),
907
+ value: _behaviorTheoryOfMindEnabled,
908
+ onChanged: !_behaviorEnabled
909
+ ? null
910
+ : (value) => setState(() {
911
+ _behaviorTheoryOfMindEnabled = value;
912
+ _hasUnsavedChanges = true;
913
+ }),
914
+ ),
915
+ SwitchListTile.adaptive(
916
+ contentPadding: EdgeInsets.zero,
917
+ title: const Text('Channel-scoped social memory'),
918
+ value: _behaviorSocialMemoryEnabled,
919
+ onChanged: !_behaviorEnabled
920
+ ? null
921
+ : (value) => setState(() {
922
+ _behaviorSocialMemoryEnabled = value;
923
+ _hasUnsavedChanges = true;
924
+ }),
925
+ ),
926
+ SwitchListTile.adaptive(
927
+ contentPadding: EdgeInsets.zero,
928
+ title: const Text('Learn room norms'),
929
+ value: _behaviorNormsEnabled,
930
+ onChanged: !_behaviorEnabled
931
+ ? null
932
+ : (value) => setState(() {
933
+ _behaviorNormsEnabled = value;
934
+ _hasUnsavedChanges = true;
935
+ }),
936
+ ),
937
+ SwitchListTile.adaptive(
938
+ contentPadding: EdgeInsets.zero,
939
+ title: const Text('Social observability'),
940
+ value: _behaviorObservabilityEnabled,
941
+ onChanged: !_behaviorEnabled
942
+ ? null
943
+ : (value) => setState(() {
944
+ _behaviorObservabilityEnabled = value;
945
+ _hasUnsavedChanges = true;
946
+ }),
947
+ ),
948
+ const SizedBox(height: 12),
949
+ TextField(
950
+ controller: _behaviorNotesController,
951
+ minLines: 4,
952
+ maxLines: 10,
953
+ onChanged: (_) => setState(() {
954
+ _hasUnsavedChanges = true;
955
+ }),
956
+ decoration: const InputDecoration(
957
+ labelText: 'Persona behavior notes',
958
+ helperText:
959
+ 'Durable instructions for voice and interaction style. Safety and execution rules still take priority.',
960
+ ),
961
+ ),
962
+ ],
963
+ ),
964
+ ),
965
+ );
966
+ }
967
+
630
968
  Widget _buildWorkspaceSection(NeoAgentController controller) {
631
969
  return Card(
632
970
  child: Padding(
@@ -514,6 +514,25 @@ class BackendClient {
514
514
  return putMap(baseUrl, '/api/settings', _withAgentId(payload, agentId));
515
515
  }
516
516
 
517
+ Future<Map<String, dynamic>> fetchBehaviorConfig(
518
+ String baseUrl, {
519
+ String? agentId,
520
+ }) async {
521
+ return getMap(baseUrl, _withAgentQuery('/api/behavior', agentId));
522
+ }
523
+
524
+ Future<Map<String, dynamic>> saveBehaviorConfig(
525
+ String baseUrl,
526
+ Map<String, dynamic> config, {
527
+ String? agentId,
528
+ }) async {
529
+ return putMap(
530
+ baseUrl,
531
+ '/api/behavior',
532
+ _withAgentId(<String, dynamic>{'config': config}, agentId),
533
+ );
534
+ }
535
+
517
536
  Future<Map<String, dynamic>> fetchTokenUsageSummary(
518
537
  String baseUrl, {
519
538
  String? agentId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neoagent",
3
- "version": "3.2.1-beta.10",
3
+ "version": "3.2.1-beta.11",
4
4
  "description": "Self-hosted AI agent for long-running tasks, automation, messaging, device control, and local memory",
5
5
  "license": "AGPL-3.0-only",
6
6
  "main": "server/index.js",
@@ -11,6 +11,7 @@ const routeRegistry = [
11
11
  { basePath: null, modulePath: '../routes/auth' },
12
12
  { basePath: '/api/account', modulePath: '../routes/account' },
13
13
  { basePath: '/api/settings', modulePath: '../routes/settings' },
14
+ { basePath: '/api/behavior', modulePath: '../routes/behavior' },
14
15
  { basePath: '/api/agent-profiles', modulePath: '../routes/agent_profiles' },
15
16
  { basePath: '/api/agents', modulePath: '../routes/agents' },
16
17
  { basePath: '/api/messaging', modulePath: '../routes/messaging' },
@@ -1 +1 @@
1
- 4e43f3e74e006c3c97b866593fa01262
1
+ 92ce0d4ca56361c182ca287a33b13bce
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"0cd610717bde95fd88343c64f81c11ba4e5c00
37
37
 
38
38
  _flutter.loader.load({
39
39
  serviceWorkerSettings: {
40
- serviceWorkerVersion: "525885793" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
40
+ serviceWorkerVersion: "1130135668" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
41
41
  }
42
42
  });