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
@@ -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
  }
@@ -343,10 +412,11 @@ class _SettingsPanelState extends State<SettingsPanel> {
343
412
  onPopInvokedWithResult: (didPop, result) async {
344
413
  if (didPop) return;
345
414
  final action = await _showLeaveDialog(context);
346
- if (action == _LeaveAction.save && mounted) {
415
+ if (!context.mounted) return;
416
+ if (action == _LeaveAction.save) {
347
417
  await _doSave();
348
- if (mounted) Navigator.of(context).pop();
349
- } else if (action == _LeaveAction.discard && mounted) {
418
+ if (context.mounted) Navigator.of(context).pop();
419
+ } else if (action == _LeaveAction.discard) {
350
420
  _hydrate();
351
421
  setState(() => _hasUnsavedChanges = false);
352
422
  Navigator.of(context).pop();
@@ -400,6 +470,13 @@ class _SettingsPanelState extends State<SettingsPanel> {
400
470
  _buildSettingsOverview(controller, availableModels.length),
401
471
  const SizedBox(height: 16),
402
472
  ],
473
+ if (_matchesSettingsSection(
474
+ searchQuery,
475
+ _behaviorSettingsSection,
476
+ )) ...<Widget>[
477
+ _buildBehaviorSection(controller, routingModels),
478
+ const SizedBox(height: 16),
479
+ ],
403
480
  if (_matchesSettingsSection(
404
481
  searchQuery,
405
482
  _workspaceSettingsSection,
@@ -521,6 +598,42 @@ class _SettingsPanelState extends State<SettingsPanel> {
521
598
  voiceLiveVoice: _voiceLiveVoice,
522
599
  aiProviderConfigs: _buildProviderPayload(),
523
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
+ }
524
637
  if (mounted) setState(() => _hasUnsavedChanges = false);
525
638
  }
526
639
 
@@ -626,6 +739,232 @@ class _SettingsPanelState extends State<SettingsPanel> {
626
739
  );
627
740
  }
628
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
+
629
968
  Widget _buildWorkspaceSection(NeoAgentController controller) {
630
969
  return Card(
631
970
  child: Padding(
@@ -345,7 +345,6 @@ class _EntranceMotionState extends State<_EntranceMotion> {
345
345
 
346
346
  class _GlassSurface extends StatelessWidget {
347
347
  const _GlassSurface({
348
- super.key,
349
348
  required this.child,
350
349
  this.width,
351
350
  this.padding,
@@ -2850,17 +2849,24 @@ String _ensureModelValue(
2850
2849
  if (allowAuto && value == 'auto') {
2851
2850
  return 'auto';
2852
2851
  }
2853
- for (final model in models) {
2854
- if (model.id == value) {
2855
- return value;
2856
- }
2857
- }
2852
+ final model = _modelForValue(value, models);
2853
+ if (model != null) return model.id;
2858
2854
  if (allowAuto) {
2859
2855
  return 'auto';
2860
2856
  }
2861
2857
  return models.isNotEmpty ? models.first.id : value;
2862
2858
  }
2863
2859
 
2860
+ ModelMeta? _modelForValue(String value, List<ModelMeta> models) {
2861
+ for (final model in models) {
2862
+ if (model.id == value) return model;
2863
+ }
2864
+ for (final model in models) {
2865
+ if (model.modelId == value) return model;
2866
+ }
2867
+ return null;
2868
+ }
2869
+
2864
2870
  String _firstAvailableModelId(List<ModelMeta> models) {
2865
2871
  for (final model in models) {
2866
2872
  if (model.available) {
@@ -2874,11 +2880,8 @@ String _modelLabelForValue(String value, List<ModelMeta> models) {
2874
2880
  if (value == 'auto' || value.trim().isEmpty) {
2875
2881
  return 'Auto';
2876
2882
  }
2877
- for (final model in models) {
2878
- if (model.id == value) {
2879
- return model.label;
2880
- }
2881
- }
2883
+ final model = _modelForValue(value, models);
2884
+ if (model != null) return model.label;
2882
2885
  return value;
2883
2886
  }
2884
2887
 
@@ -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,
@@ -1359,6 +1378,84 @@ class BackendClient {
1359
1378
  return _asMap(_decodeJson(response.body));
1360
1379
  }
1361
1380
 
1381
+ Future<Map<String, dynamic>> unlockBitwarden(
1382
+ String baseUrl, {
1383
+ required String masterPassword,
1384
+ required int idleTimeoutMinutes,
1385
+ String? agentId,
1386
+ }) {
1387
+ return postMap(
1388
+ baseUrl,
1389
+ '/api/integrations/bitwarden/unlock',
1390
+ _withAgentId(<String, dynamic>{
1391
+ 'masterPassword': masterPassword,
1392
+ 'idleTimeoutMinutes': idleTimeoutMinutes,
1393
+ }, agentId),
1394
+ );
1395
+ }
1396
+
1397
+ Future<Map<String, dynamic>> lockBitwarden(
1398
+ String baseUrl, {
1399
+ String? agentId,
1400
+ }) {
1401
+ return postMap(
1402
+ baseUrl,
1403
+ '/api/integrations/bitwarden/lock',
1404
+ _withAgentId(<String, dynamic>{}, agentId),
1405
+ );
1406
+ }
1407
+
1408
+ Future<Map<String, dynamic>> fetchBitwardenItems(
1409
+ String baseUrl, {
1410
+ String? agentId,
1411
+ }) {
1412
+ return getMap(
1413
+ baseUrl,
1414
+ _withAgentQuery('/api/integrations/bitwarden/items', agentId),
1415
+ );
1416
+ }
1417
+
1418
+ Future<Map<String, dynamic>> fetchCredentialBindings(
1419
+ String baseUrl, {
1420
+ String? agentId,
1421
+ }) {
1422
+ return getMap(
1423
+ baseUrl,
1424
+ _withAgentQuery('/api/integrations/bitwarden/bindings', agentId),
1425
+ );
1426
+ }
1427
+
1428
+ Future<Map<String, dynamic>> createCredentialBinding(
1429
+ String baseUrl, {
1430
+ required Map<String, dynamic> binding,
1431
+ String? agentId,
1432
+ }) {
1433
+ return postMap(
1434
+ baseUrl,
1435
+ '/api/integrations/bitwarden/bindings',
1436
+ _withAgentId(binding, agentId),
1437
+ );
1438
+ }
1439
+
1440
+ Future<Map<String, dynamic>> deleteCredentialBinding(
1441
+ String baseUrl,
1442
+ String bindingId, {
1443
+ String? agentId,
1444
+ }) async {
1445
+ final response = await _httpClient.delete(
1446
+ _resolveUri(
1447
+ baseUrl,
1448
+ _withAgentQuery(
1449
+ '/api/integrations/bitwarden/bindings/${Uri.encodeComponent(bindingId)}',
1450
+ agentId,
1451
+ ),
1452
+ ),
1453
+ headers: const <String, String>{'Accept': 'application/json'},
1454
+ );
1455
+ _throwIfError(response);
1456
+ return _asMap(_decodeJson(response.body));
1457
+ }
1458
+
1362
1459
  Future<Map<String, dynamic>> fetchMessagingStatus(
1363
1460
  String baseUrl, {
1364
1461
  String? agentId,