neoagent 3.2.1-beta.9 → 3.3.1-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +67 -80
- package/README.md +7 -1
- package/docs/licensing.md +44 -0
- package/flutter_app/lib/main.dart +1 -0
- package/flutter_app/lib/main_app_shell.dart +186 -40
- package/flutter_app/lib/main_chat.dart +542 -80
- package/flutter_app/lib/main_controller.dart +66 -5
- package/flutter_app/lib/main_install.dart +9 -7
- package/flutter_app/lib/main_models.dart +171 -22
- package/flutter_app/lib/main_operations.dart +0 -49
- package/flutter_app/lib/main_settings.dart +338 -0
- package/flutter_app/lib/src/backend_client.dart +19 -0
- package/flutter_app/lib/src/local_runtime_paths.dart +60 -0
- package/lib/manager.js +10 -2
- package/package.json +1 -1
- package/runtime/paths.js +70 -0
- package/server/db/database.js +2 -2
- package/server/http/routes.js +1 -0
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +84940 -83737
- package/server/routes/behavior.js +80 -0
- package/server/routes/settings.js +4 -0
- package/server/services/agents/manager.js +1 -1
- package/server/services/ai/history.js +1 -1
- package/server/services/ai/loop/agent_engine_core.js +110 -17
- package/server/services/ai/loop/blank_recovery.js +5 -4
- package/server/services/ai/loop/completion_judge.js +226 -33
- package/server/services/ai/loop/conversation_loop.js +92 -34
- package/server/services/ai/loop/messaging_delivery.js +47 -0
- package/server/services/ai/loop/progress_classification.js +2 -0
- package/server/services/ai/loopPolicy.js +24 -2
- package/server/services/ai/messagingFallback.js +17 -17
- package/server/services/ai/model_failure_cache.js +7 -0
- package/server/services/ai/systemPrompt.js +31 -122
- package/server/services/ai/taskAnalysis.js +86 -12
- package/server/services/ai/toolEvidence.js +68 -224
- package/server/services/ai/tools.js +60 -19
- package/server/services/behavior/config.js +251 -0
- package/server/services/behavior/defaults.js +68 -0
- package/server/services/behavior/delivery.js +176 -0
- package/server/services/behavior/index.js +43 -0
- package/server/services/behavior/model_client.js +35 -0
- package/server/services/behavior/modules/agent_identity.js +37 -0
- package/server/services/behavior/modules/channel_style.js +28 -0
- package/server/services/behavior/modules/index.js +29 -0
- package/server/services/behavior/modules/norms.js +101 -0
- package/server/services/behavior/modules/persona.js +172 -0
- package/server/services/behavior/modules/persona_prompt.js +238 -0
- package/server/services/behavior/modules/social_memory.js +86 -0
- package/server/services/behavior/modules/social_observability.js +94 -0
- package/server/services/behavior/modules/social_signals.js +41 -0
- package/server/services/behavior/modules/theory_of_mind.js +118 -0
- package/server/services/behavior/modules/turn_taking.js +238 -0
- package/server/services/behavior/pipeline.js +341 -0
- package/server/services/behavior/registry.js +78 -0
- package/server/services/behavior/signals.js +107 -0
- package/server/services/behavior/state.js +99 -0
- package/server/services/behavior/system_prompt.js +75 -0
- package/server/services/memory/manager.js +14 -33
- package/server/services/messaging/access_policy.js +269 -74
- package/server/services/messaging/automation.js +158 -27
- package/server/services/messaging/discord.js +11 -1
- package/server/services/messaging/formatting_guides.js +5 -4
- package/server/services/messaging/http_platforms.js +73 -28
- package/server/services/messaging/inbound_queue.js +74 -13
- package/server/services/messaging/inbound_store.js +33 -0
- package/server/services/messaging/manager.js +57 -5
- package/server/services/messaging/telegram.js +10 -1
- package/server/services/messaging/whatsapp.js +11 -0
- package/server/services/social_reach/channels/social_video.js +1 -1
- package/server/services/social_video/captions.js +2 -2
- package/server/services/social_video/service.js +194 -29
- package/server/services/voice/message.js +1 -1
- package/server/services/voice/runtime.js +2 -2
- package/server/utils/logger.js +19 -0
- package/server/services/ai/terminal_reply.js +0 -57
|
@@ -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,
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
class LocalRuntimePaths {
|
|
2
|
+
LocalRuntimePaths._({
|
|
3
|
+
required this.homeDirectory,
|
|
4
|
+
required this.runtimeHome,
|
|
5
|
+
required this.dataDirectory,
|
|
6
|
+
required this.envFile,
|
|
7
|
+
required this.separator,
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
factory LocalRuntimePaths.fromEnvironment(
|
|
11
|
+
Map<String, String> environment, {
|
|
12
|
+
required bool isWindows,
|
|
13
|
+
}) {
|
|
14
|
+
final separator = isWindows ? r'\' : '/';
|
|
15
|
+
final homeKey = isWindows ? 'USERPROFILE' : 'HOME';
|
|
16
|
+
final homeDirectory = (environment[homeKey] ?? '').trim();
|
|
17
|
+
if (homeDirectory.isEmpty) {
|
|
18
|
+
throw StateError('$homeKey is required to locate NeoAgent runtime data.');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
final configuredRuntimeHome = (environment['NEOAGENT_HOME'] ?? '').trim();
|
|
22
|
+
final runtimeHome = configuredRuntimeHome.isNotEmpty
|
|
23
|
+
? configuredRuntimeHome
|
|
24
|
+
: _join(homeDirectory, '.neoagent', separator);
|
|
25
|
+
final configuredDataDirectory = (environment['NEOAGENT_DATA_DIR'] ?? '')
|
|
26
|
+
.trim();
|
|
27
|
+
final dataDirectory = configuredDataDirectory.isNotEmpty
|
|
28
|
+
? configuredDataDirectory
|
|
29
|
+
: _join(runtimeHome, 'data', separator);
|
|
30
|
+
final configuredEnvFile = (environment['NEOAGENT_ENV_FILE'] ?? '').trim();
|
|
31
|
+
final envFile = configuredEnvFile.isNotEmpty
|
|
32
|
+
? configuredEnvFile
|
|
33
|
+
: _join(runtimeHome, '.env', separator);
|
|
34
|
+
|
|
35
|
+
return LocalRuntimePaths._(
|
|
36
|
+
homeDirectory: homeDirectory,
|
|
37
|
+
runtimeHome: runtimeHome,
|
|
38
|
+
dataDirectory: dataDirectory,
|
|
39
|
+
envFile: envFile,
|
|
40
|
+
separator: separator,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
final String homeDirectory;
|
|
45
|
+
final String runtimeHome;
|
|
46
|
+
final String dataDirectory;
|
|
47
|
+
final String envFile;
|
|
48
|
+
final String separator;
|
|
49
|
+
|
|
50
|
+
String get logDirectory => _join(dataDirectory, 'logs', separator);
|
|
51
|
+
String get logFile => _join(logDirectory, 'neoagent.log', separator);
|
|
52
|
+
String get pidFile => _join(dataDirectory, 'neoagent.pid', separator);
|
|
53
|
+
|
|
54
|
+
static String _join(String parent, String child, String separator) {
|
|
55
|
+
if (parent.endsWith('/') || parent.endsWith(r'\')) {
|
|
56
|
+
return '$parent$child';
|
|
57
|
+
}
|
|
58
|
+
return '$parent$separator$child';
|
|
59
|
+
}
|
|
60
|
+
}
|
package/lib/manager.js
CHANGED
|
@@ -575,6 +575,8 @@ function defaultEnvLines(current = {}) {
|
|
|
575
575
|
`NEOAGENT_VM_MEMORY_MB=${current.NEOAGENT_VM_MEMORY_MB || '4096'}`,
|
|
576
576
|
`NEOAGENT_VM_CPUS=${current.NEOAGENT_VM_CPUS || '2'}`,
|
|
577
577
|
`NEOAGENT_VM_GUEST_TOKEN=${current.NEOAGENT_VM_GUEST_TOKEN || randomSecret()}`,
|
|
578
|
+
`ADMIN_USERNAME=${current.ADMIN_USERNAME || 'admin'}`,
|
|
579
|
+
`ADMIN_PASSWORD=${current.ADMIN_PASSWORD || randomSecret()}`,
|
|
578
580
|
current.XAI_BASE_URL ? `XAI_BASE_URL=${current.XAI_BASE_URL}` : 'XAI_BASE_URL=https://api.x.ai/v1',
|
|
579
581
|
current.OLLAMA_URL ? `OLLAMA_URL=${current.OLLAMA_URL}` : 'OLLAMA_URL=http://localhost:11434',
|
|
580
582
|
current.DEEPGRAM_BASE_URL ? `DEEPGRAM_BASE_URL=${current.DEEPGRAM_BASE_URL}` : 'DEEPGRAM_BASE_URL=https://api.deepgram.com',
|
|
@@ -586,8 +588,13 @@ function defaultEnvLines(current = {}) {
|
|
|
586
588
|
function writeDefaultEnvFile() {
|
|
587
589
|
ensureRuntimeDirs();
|
|
588
590
|
const current = Object.fromEntries(parseEnv(readEnvFileRaw()).entries());
|
|
589
|
-
|
|
590
|
-
|
|
591
|
+
for (const line of defaultEnvLines(current)) {
|
|
592
|
+
const separatorIndex = line.indexOf('=');
|
|
593
|
+
const key = line.slice(0, separatorIndex);
|
|
594
|
+
if (String(current[key] || '').trim()) continue;
|
|
595
|
+
upsertEnvValue(key, line.slice(separatorIndex + 1));
|
|
596
|
+
}
|
|
597
|
+
logOk(`Ensured default config at ${ENV_FILE}`);
|
|
591
598
|
rememberInstallAction('Add provider keys with `neoagent setup`, `neoagent env set KEY VALUE`, or the login commands when you are ready.');
|
|
592
599
|
}
|
|
593
600
|
|
|
@@ -1560,6 +1567,7 @@ async function cmdInstall() {
|
|
|
1560
1567
|
writeDefaultEnvFile();
|
|
1561
1568
|
}
|
|
1562
1569
|
} else {
|
|
1570
|
+
writeDefaultEnvFile();
|
|
1563
1571
|
logOk(`Using config ${ENV_FILE}`);
|
|
1564
1572
|
}
|
|
1565
1573
|
|
package/package.json
CHANGED
package/runtime/paths.js
CHANGED
|
@@ -17,6 +17,15 @@ const PID_FILE = path.join(DATA_DIR, 'neoagent.pid');
|
|
|
17
17
|
const LEGACY_ENV_FILE = path.join(APP_DIR, '.env');
|
|
18
18
|
const LEGACY_DATA_DIR = path.join(APP_DIR, 'data');
|
|
19
19
|
const LEGACY_AGENT_DATA_DIR = path.join(APP_DIR, 'agent-data');
|
|
20
|
+
const LEGACY_DESKTOP_RUNTIME_DIR = path.join(HOME_DIR, '.neoagent', 'runtime');
|
|
21
|
+
const LEGACY_DESKTOP_ENV_FILE = path.join(LEGACY_DESKTOP_RUNTIME_DIR, '.env');
|
|
22
|
+
const PERSISTENT_IDENTITY_ENV_KEYS = new Set([
|
|
23
|
+
'SESSION_SECRET',
|
|
24
|
+
'NEOAGENT_ENCRYPTION_KEY',
|
|
25
|
+
'NEOAGENT_VM_GUEST_TOKEN',
|
|
26
|
+
'ADMIN_USERNAME',
|
|
27
|
+
'ADMIN_PASSWORD',
|
|
28
|
+
]);
|
|
20
29
|
const DEFAULT_VM_BASE_IMAGE_URLS = Object.freeze({
|
|
21
30
|
arm64: 'https://cloud-images.ubuntu.com/releases/24.04/release/ubuntu-24.04-server-cloudimg-arm64.img',
|
|
22
31
|
x64: 'https://cloud-images.ubuntu.com/releases/24.04/release/ubuntu-24.04-server-cloudimg-amd64.img',
|
|
@@ -47,6 +56,39 @@ function copyDirMerge(src, dest) {
|
|
|
47
56
|
return true;
|
|
48
57
|
}
|
|
49
58
|
|
|
59
|
+
function migrationBackupPath(sourcePath) {
|
|
60
|
+
const basePath = `${sourcePath}.migrated`;
|
|
61
|
+
if (!fs.existsSync(basePath)) return basePath;
|
|
62
|
+
return `${basePath}.${Date.now()}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function migrateLegacyDesktopEnv({
|
|
66
|
+
legacyEnvFile = LEGACY_DESKTOP_ENV_FILE,
|
|
67
|
+
envFile = ENV_FILE,
|
|
68
|
+
logger = () => {},
|
|
69
|
+
} = {}) {
|
|
70
|
+
if (path.resolve(legacyEnvFile) === path.resolve(envFile)) return false;
|
|
71
|
+
if (!fs.existsSync(legacyEnvFile)) return false;
|
|
72
|
+
|
|
73
|
+
const legacyValues = parseEnv(readEnvFileRaw(legacyEnvFile));
|
|
74
|
+
if (legacyValues.size === 0) return false;
|
|
75
|
+
|
|
76
|
+
const currentValues = parseEnv(readEnvFileRaw(envFile));
|
|
77
|
+
for (const [key, value] of legacyValues.entries()) {
|
|
78
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) continue;
|
|
79
|
+
const currentValue = String(currentValues.get(key) || '').trim();
|
|
80
|
+
if (PERSISTENT_IDENTITY_ENV_KEYS.has(key) && currentValue) continue;
|
|
81
|
+
upsertEnvValue(envFile, key, value);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const backupPath = migrationBackupPath(legacyEnvFile);
|
|
85
|
+
fs.renameSync(legacyEnvFile, backupPath);
|
|
86
|
+
logger(
|
|
87
|
+
`migrated desktop config ${legacyEnvFile} -> ${envFile} (backup: ${backupPath})`,
|
|
88
|
+
);
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
|
|
50
92
|
function migrateLegacyRuntime(logger = () => {}) {
|
|
51
93
|
ensureRuntimeDirs();
|
|
52
94
|
let changed = false;
|
|
@@ -89,6 +131,15 @@ function migrateLegacyRuntime(logger = () => {}) {
|
|
|
89
131
|
log(`migrated ${LEGACY_ENV_FILE} -> ${ENV_FILE}`);
|
|
90
132
|
changed = true;
|
|
91
133
|
}
|
|
134
|
+
try {
|
|
135
|
+
if (migrateLegacyDesktopEnv({ logger: log })) {
|
|
136
|
+
changed = true;
|
|
137
|
+
}
|
|
138
|
+
} catch (error) {
|
|
139
|
+
logError(
|
|
140
|
+
`failed to migrate desktop runtime config ${LEGACY_DESKTOP_ENV_FILE} -> ${ENV_FILE}: ${error.message}`,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
92
143
|
if (copyDirMerge(LEGACY_DATA_DIR, DATA_DIR)) {
|
|
93
144
|
log(`migrated ${LEGACY_DATA_DIR} -> ${DATA_DIR}`);
|
|
94
145
|
changed = true;
|
|
@@ -248,6 +299,22 @@ function ensureSecureRuntimeEnv({ envFile = ENV_FILE, env = process.env, logger
|
|
|
248
299
|
}
|
|
249
300
|
env.SESSION_SECRET = sessionSecret;
|
|
250
301
|
|
|
302
|
+
let adminUsername = String(env.ADMIN_USERNAME || parsed.get('ADMIN_USERNAME') || '').trim();
|
|
303
|
+
if (!adminUsername) {
|
|
304
|
+
adminUsername = 'admin';
|
|
305
|
+
upsertEnvValue(envFile, 'ADMIN_USERNAME', adminUsername);
|
|
306
|
+
changes.push('ADMIN_USERNAME');
|
|
307
|
+
}
|
|
308
|
+
env.ADMIN_USERNAME = adminUsername;
|
|
309
|
+
|
|
310
|
+
let adminPassword = String(env.ADMIN_PASSWORD || parsed.get('ADMIN_PASSWORD') || '').trim();
|
|
311
|
+
if (!adminPassword) {
|
|
312
|
+
adminPassword = generateSecret(16);
|
|
313
|
+
upsertEnvValue(envFile, 'ADMIN_PASSWORD', adminPassword);
|
|
314
|
+
changes.push('ADMIN_PASSWORD');
|
|
315
|
+
}
|
|
316
|
+
env.ADMIN_PASSWORD = adminPassword;
|
|
317
|
+
|
|
251
318
|
let guestToken = String(env.NEOAGENT_VM_GUEST_TOKEN || parsed.get('NEOAGENT_VM_GUEST_TOKEN') || '').trim();
|
|
252
319
|
if (!isValidVmGuestToken(guestToken)) {
|
|
253
320
|
guestToken = generateSecret(32);
|
|
@@ -285,10 +352,13 @@ module.exports = {
|
|
|
285
352
|
LEGACY_ENV_FILE,
|
|
286
353
|
LEGACY_DATA_DIR,
|
|
287
354
|
LEGACY_AGENT_DATA_DIR,
|
|
355
|
+
LEGACY_DESKTOP_RUNTIME_DIR,
|
|
356
|
+
LEGACY_DESKTOP_ENV_FILE,
|
|
288
357
|
DEFAULT_VM_BASE_IMAGE_URLS,
|
|
289
358
|
ensureRuntimeDirs,
|
|
290
359
|
ensureSecureRuntimeEnv,
|
|
291
360
|
getDefaultVmBaseImageUrl,
|
|
361
|
+
migrateLegacyDesktopEnv,
|
|
292
362
|
migrateLegacyRuntime,
|
|
293
363
|
removeEnvValue,
|
|
294
364
|
upsertEnvValue,
|
package/server/db/database.js
CHANGED
|
@@ -1311,8 +1311,8 @@ function getMainAgentId(userId) {
|
|
|
1311
1311
|
id, user_id, slug, display_name, description, responsibilities, instructions,
|
|
1312
1312
|
is_default, can_delegate, can_be_delegated_to, delegate_targets_json
|
|
1313
1313
|
)
|
|
1314
|
-
VALUES (?, ?, 'main', 'Main', 'Default personal
|
|
1315
|
-
'Handle general requests and delegate to specialist agents only when there is a clear match.',
|
|
1314
|
+
VALUES (?, ?, 'main', 'Main', 'Default personal AI contact and fallback agent.',
|
|
1315
|
+
'Handle general requests like a proactive favorite contact and delegate to specialist agents only when there is a clear match.',
|
|
1316
1316
|
'', 1, 1, 0, '[]'
|
|
1317
1317
|
)`
|
|
1318
1318
|
).run(id, userId);
|
package/server/http/routes.js
CHANGED
|
@@ -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
|
-
|
|
1
|
+
0ad3e52b5fbb378630b99ccbf102fadc
|
|
Binary file
|
|
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"0cd610717bde95fd88343c64f81c11ba4e5c00
|
|
|
37
37
|
|
|
38
38
|
_flutter.loader.load({
|
|
39
39
|
serviceWorkerSettings: {
|
|
40
|
-
serviceWorkerVersion: "
|
|
40
|
+
serviceWorkerVersion: "1676388530" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
|
|
41
41
|
}
|
|
42
42
|
});
|