neoagent 3.2.1-beta.9 → 3.3.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.
Files changed (70) hide show
  1. package/flutter_app/lib/main_app_shell.dart +178 -34
  2. package/flutter_app/lib/main_chat.dart +542 -80
  3. package/flutter_app/lib/main_controller.dart +59 -4
  4. package/flutter_app/lib/main_models.dart +171 -22
  5. package/flutter_app/lib/main_operations.dart +0 -49
  6. package/flutter_app/lib/main_settings.dart +338 -0
  7. package/flutter_app/lib/src/backend_client.dart +19 -0
  8. package/package.json +1 -1
  9. package/server/db/database.js +2 -2
  10. package/server/http/routes.js +1 -0
  11. package/server/public/.last_build_id +1 -1
  12. package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
  13. package/server/public/flutter_bootstrap.js +1 -1
  14. package/server/public/main.dart.js +85079 -83904
  15. package/server/routes/behavior.js +80 -0
  16. package/server/routes/settings.js +4 -0
  17. package/server/services/agents/manager.js +1 -1
  18. package/server/services/ai/history.js +1 -1
  19. package/server/services/ai/loop/agent_engine_core.js +110 -17
  20. package/server/services/ai/loop/blank_recovery.js +5 -4
  21. package/server/services/ai/loop/completion_judge.js +226 -33
  22. package/server/services/ai/loop/conversation_loop.js +92 -34
  23. package/server/services/ai/loop/messaging_delivery.js +47 -0
  24. package/server/services/ai/loop/progress_classification.js +2 -0
  25. package/server/services/ai/loopPolicy.js +24 -2
  26. package/server/services/ai/messagingFallback.js +17 -17
  27. package/server/services/ai/model_failure_cache.js +7 -0
  28. package/server/services/ai/systemPrompt.js +31 -122
  29. package/server/services/ai/taskAnalysis.js +86 -12
  30. package/server/services/ai/toolEvidence.js +68 -224
  31. package/server/services/ai/tools.js +60 -19
  32. package/server/services/behavior/config.js +251 -0
  33. package/server/services/behavior/defaults.js +68 -0
  34. package/server/services/behavior/delivery.js +176 -0
  35. package/server/services/behavior/index.js +43 -0
  36. package/server/services/behavior/model_client.js +35 -0
  37. package/server/services/behavior/modules/agent_identity.js +37 -0
  38. package/server/services/behavior/modules/channel_style.js +28 -0
  39. package/server/services/behavior/modules/index.js +29 -0
  40. package/server/services/behavior/modules/norms.js +101 -0
  41. package/server/services/behavior/modules/persona.js +172 -0
  42. package/server/services/behavior/modules/persona_prompt.js +238 -0
  43. package/server/services/behavior/modules/social_memory.js +86 -0
  44. package/server/services/behavior/modules/social_observability.js +94 -0
  45. package/server/services/behavior/modules/social_signals.js +41 -0
  46. package/server/services/behavior/modules/theory_of_mind.js +118 -0
  47. package/server/services/behavior/modules/turn_taking.js +238 -0
  48. package/server/services/behavior/pipeline.js +341 -0
  49. package/server/services/behavior/registry.js +78 -0
  50. package/server/services/behavior/signals.js +107 -0
  51. package/server/services/behavior/state.js +99 -0
  52. package/server/services/behavior/system_prompt.js +75 -0
  53. package/server/services/memory/manager.js +14 -33
  54. package/server/services/messaging/access_policy.js +269 -74
  55. package/server/services/messaging/automation.js +158 -27
  56. package/server/services/messaging/discord.js +11 -1
  57. package/server/services/messaging/formatting_guides.js +5 -4
  58. package/server/services/messaging/http_platforms.js +73 -28
  59. package/server/services/messaging/inbound_queue.js +74 -13
  60. package/server/services/messaging/inbound_store.js +33 -0
  61. package/server/services/messaging/manager.js +57 -5
  62. package/server/services/messaging/telegram.js +10 -1
  63. package/server/services/messaging/whatsapp.js +11 -0
  64. package/server/services/social_reach/channels/social_video.js +1 -1
  65. package/server/services/social_video/captions.js +2 -2
  66. package/server/services/social_video/service.js +194 -29
  67. package/server/services/voice/message.js +1 -1
  68. package/server/services/voice/runtime.js +2 -2
  69. package/server/utils/logger.js +19 -0
  70. package/server/services/ai/terminal_reply.js +0 -57
@@ -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>[];
@@ -1764,6 +1766,9 @@ class NeoAgentController extends ChangeNotifier {
1764
1766
  );
1765
1767
  case 'sharedActorRules':
1766
1768
  return policy.copyWith(
1769
+ directPolicy: policy.directPolicy == 'disabled'
1770
+ ? 'allowlist'
1771
+ : policy.directPolicy,
1767
1772
  sharedPolicy: policy.sharedPolicy == 'disabled'
1768
1773
  ? 'allowlist'
1769
1774
  : policy.sharedPolicy,
@@ -1772,6 +1777,16 @@ class NeoAgentController extends ChangeNotifier {
1772
1777
  suggestion.rule,
1773
1778
  ]),
1774
1779
  );
1780
+ case 'sharedMemberRules':
1781
+ return policy.copyWith(
1782
+ sharedPolicy: policy.sharedPolicy == 'disabled'
1783
+ ? 'allowlist'
1784
+ : policy.sharedPolicy,
1785
+ sharedMemberRules: _dedupeAccessRules(<MessagingAccessRule>[
1786
+ ...policy.sharedMemberRules,
1787
+ suggestion.rule,
1788
+ ]),
1789
+ );
1775
1790
  default:
1776
1791
  return policy.copyWith(
1777
1792
  sharedPolicy: policy.sharedPolicy == 'disabled'
@@ -1958,6 +1973,11 @@ class NeoAgentController extends ChangeNotifier {
1958
1973
  _backendClient.fetchSettings(backendUrl, agentId: agentId),
1959
1974
  const <String, dynamic>{},
1960
1975
  );
1976
+ final behaviorFuture = _softRefreshLoad<Map<String, dynamic>>(
1977
+ 'behavior_config',
1978
+ _backendClient.fetchBehaviorConfig(backendUrl, agentId: agentId),
1979
+ const <String, dynamic>{},
1980
+ );
1961
1981
  final runsFuture = _softRefreshLoad<Map<String, dynamic>>(
1962
1982
  'runs',
1963
1983
  _backendClient.fetchRuns(backendUrl, agentId: agentId),
@@ -2094,6 +2114,7 @@ class NeoAgentController extends ChangeNotifier {
2094
2114
  final modelsResponse = await modelsFuture;
2095
2115
  final providersResponse = await providersFuture;
2096
2116
  final settingsResponse = await settingsFuture;
2117
+ final behaviorResponse = await behaviorFuture;
2097
2118
  final runsResponse = await runsFuture;
2098
2119
  final timelineResponse = await timelineFuture;
2099
2120
  final versionResponse = await versionFuture;
@@ -2137,6 +2158,9 @@ class NeoAgentController extends ChangeNotifier {
2137
2158
  );
2138
2159
 
2139
2160
  settings = Map<String, dynamic>.from(settingsResponse);
2161
+ behaviorConfig = behaviorResponse['config'] is Map
2162
+ ? Map<String, dynamic>.from(behaviorResponse['config'] as Map)
2163
+ : const <String, dynamic>{};
2140
2164
  recentRuns = _decodeModelList(
2141
2165
  'runs',
2142
2166
  runsResponse['runs'],
@@ -4221,6 +4245,27 @@ class NeoAgentController extends ChangeNotifier {
4221
4245
  }
4222
4246
  }
4223
4247
 
4248
+ Future<void> saveBehaviorConfig(Map<String, dynamic> config) async {
4249
+ isSavingSettings = true;
4250
+ errorMessage = null;
4251
+ notifyListeners();
4252
+ try {
4253
+ final response = await _backendClient.saveBehaviorConfig(
4254
+ backendUrl,
4255
+ config,
4256
+ agentId: _scopedAgentId,
4257
+ );
4258
+ behaviorConfig = response['config'] is Map
4259
+ ? Map<String, dynamic>.from(response['config'] as Map)
4260
+ : Map<String, dynamic>.from(config);
4261
+ } catch (error) {
4262
+ errorMessage = _friendlyErrorMessage(error);
4263
+ } finally {
4264
+ isSavingSettings = false;
4265
+ notifyListeners();
4266
+ }
4267
+ }
4268
+
4224
4269
  void _applyAccountResponse(Map<String, dynamic> response) {
4225
4270
  if (response['user'] is Map) {
4226
4271
  user = Map<String, dynamic>.from(response['user'] as Map);
@@ -5338,10 +5383,20 @@ class NeoAgentController extends ChangeNotifier {
5338
5383
  }
5339
5384
 
5340
5385
  Future<void> updateAssistantBehaviorNotes(String content) async {
5341
- await _backendClient.saveSettings(backendUrl, <String, dynamic>{
5342
- 'assistant_behavior_notes': content,
5343
- }, agentId: _scopedAgentId);
5344
- await refreshMemory();
5386
+ isSavingSettings = true;
5387
+ errorMessage = null;
5388
+ notifyListeners();
5389
+ try {
5390
+ await _backendClient.saveSettings(backendUrl, <String, dynamic>{
5391
+ 'assistant_behavior_notes': content,
5392
+ }, agentId: _scopedAgentId);
5393
+ await refreshMemory();
5394
+ } catch (error) {
5395
+ errorMessage = _friendlyErrorMessage(error);
5396
+ } finally {
5397
+ isSavingSettings = false;
5398
+ notifyListeners();
5399
+ }
5345
5400
  }
5346
5401
 
5347
5402
  Future<void> updateCoreMemory(String key, String value) async {
@@ -719,6 +719,9 @@ class MessagingAccessRule {
719
719
  required this.scope,
720
720
  required this.value,
721
721
  this.label,
722
+ this.spaceScope,
723
+ this.spaceValue,
724
+ this.spaceLabel,
722
725
  });
723
726
 
724
727
  factory MessagingAccessRule.fromJson(Map<String, dynamic> json) {
@@ -726,23 +729,38 @@ class MessagingAccessRule {
726
729
  scope: json['scope']?.toString() ?? 'chat',
727
730
  value: json['value']?.toString() ?? '',
728
731
  label: json['label']?.toString(),
732
+ spaceScope: json['spaceScope']?.toString(),
733
+ spaceValue: json['spaceValue']?.toString(),
734
+ spaceLabel: json['spaceLabel']?.toString(),
729
735
  );
730
736
  }
731
737
 
732
738
  final String scope;
733
739
  final String value;
734
740
  final String? label;
741
+ final String? spaceScope;
742
+ final String? spaceValue;
743
+ final String? spaceLabel;
735
744
 
736
745
  Map<String, dynamic> toJson() => <String, dynamic>{
737
746
  'scope': scope,
738
747
  'value': value,
739
748
  if (label != null && label!.trim().isNotEmpty) 'label': label,
749
+ if (spaceScope != null && spaceScope!.trim().isNotEmpty)
750
+ 'spaceScope': spaceScope,
751
+ if (spaceValue != null && spaceValue!.trim().isNotEmpty)
752
+ 'spaceValue': spaceValue,
753
+ if (spaceLabel != null && spaceLabel!.trim().isNotEmpty)
754
+ 'spaceLabel': spaceLabel,
740
755
  };
741
756
 
742
- String get id => '$scope:$value';
757
+ String get id => '$scope:$value:${spaceScope ?? ''}:${spaceValue ?? ''}';
743
758
 
744
759
  String get displayLabel => label?.ifEmpty(value) ?? value;
745
760
 
761
+ String get spaceDisplayLabel =>
762
+ spaceLabel?.ifEmpty(spaceValue ?? '') ?? spaceValue ?? '';
763
+
746
764
  String get scopeLabel {
747
765
  switch (scope) {
748
766
  case 'phone_number':
@@ -767,23 +785,63 @@ class MessagingAccessRule {
767
785
  }
768
786
  }
769
787
 
788
+ class MessagingSharedParticipationRule {
789
+ const MessagingSharedParticipationRule({
790
+ required this.scope,
791
+ required this.value,
792
+ required this.allowUntagged,
793
+ this.label,
794
+ });
795
+
796
+ factory MessagingSharedParticipationRule.fromJson(Map<String, dynamic> json) {
797
+ return MessagingSharedParticipationRule(
798
+ scope: json['scope']?.toString() ?? 'chat',
799
+ value: json['value']?.toString() ?? '',
800
+ allowUntagged: json['allowUntagged'] == true,
801
+ label: json['label']?.toString(),
802
+ );
803
+ }
804
+
805
+ final String scope;
806
+ final String value;
807
+ final bool allowUntagged;
808
+ final String? label;
809
+
810
+ String get id => '$scope:$value';
811
+ String get displayLabel => label?.ifEmpty(value) ?? value;
812
+
813
+ Map<String, dynamic> toJson() => <String, dynamic>{
814
+ 'scope': scope,
815
+ 'value': value,
816
+ 'allowUntagged': allowUntagged,
817
+ if (label != null && label!.trim().isNotEmpty) 'label': label,
818
+ };
819
+ }
820
+
770
821
  class MessagingAccessPolicy {
771
822
  const MessagingAccessPolicy({
823
+ required this.schemaVersion,
772
824
  required this.directPolicy,
773
825
  required this.sharedPolicy,
774
- required this.requireMentionInShared,
826
+ required this.defaultAllowUntaggedInShared,
775
827
  required this.directRules,
776
828
  required this.sharedSpaceRules,
777
829
  required this.sharedActorRules,
830
+ required this.sharedMemberRules,
831
+ required this.sharedParticipationRules,
778
832
  });
779
833
 
780
834
  factory MessagingAccessPolicy.fromJson(Map<String, dynamic> json) {
835
+ final schemaVersion = (json['schemaVersion'] as num?)?.toInt() ?? 2;
781
836
  return MessagingAccessPolicy(
837
+ schemaVersion: schemaVersion,
782
838
  directPolicy:
783
839
  json['directPolicy']?.toString().ifEmpty('allowlist') ?? 'allowlist',
784
840
  sharedPolicy:
785
841
  json['sharedPolicy']?.toString().ifEmpty('allowlist') ?? 'allowlist',
786
- requireMentionInShared: json['requireMentionInShared'] == true,
842
+ defaultAllowUntaggedInShared: schemaVersion < 3
843
+ ? json['requireMentionInShared'] != true
844
+ : json['defaultAllowUntaggedInShared'] != false,
787
845
  directRules:
788
846
  (json['directRules'] is List
789
847
  ? json['directRules'] as List
@@ -817,48 +875,85 @@ class MessagingAccessPolicy {
817
875
  ),
818
876
  )
819
877
  .toList(growable: false),
878
+ sharedMemberRules:
879
+ (json['sharedMemberRules'] is List
880
+ ? json['sharedMemberRules'] as List
881
+ : const <dynamic>[])
882
+ .whereType<Map>()
883
+ .map(
884
+ (item) => MessagingAccessRule.fromJson(
885
+ Map<String, dynamic>.from(item),
886
+ ),
887
+ )
888
+ .toList(growable: false),
889
+ sharedParticipationRules:
890
+ (json['sharedParticipationRules'] is List
891
+ ? json['sharedParticipationRules'] as List
892
+ : const <dynamic>[])
893
+ .whereType<Map>()
894
+ .map(
895
+ (item) => MessagingSharedParticipationRule.fromJson(
896
+ Map<String, dynamic>.from(item),
897
+ ),
898
+ )
899
+ .where((item) => item.value.isNotEmpty)
900
+ .toList(growable: false),
820
901
  );
821
902
  }
822
903
 
823
904
  const MessagingAccessPolicy.defaults({
905
+ this.schemaVersion = 3,
824
906
  this.directPolicy = 'allowlist',
825
907
  this.sharedPolicy = 'allowlist',
826
- this.requireMentionInShared = true,
908
+ this.defaultAllowUntaggedInShared = true,
827
909
  this.directRules = const <MessagingAccessRule>[],
828
910
  this.sharedSpaceRules = const <MessagingAccessRule>[],
829
911
  this.sharedActorRules = const <MessagingAccessRule>[],
912
+ this.sharedMemberRules = const <MessagingAccessRule>[],
913
+ this.sharedParticipationRules = const <MessagingSharedParticipationRule>[],
830
914
  });
831
915
 
916
+ final int schemaVersion;
832
917
  final String directPolicy;
833
918
  final String sharedPolicy;
834
- final bool requireMentionInShared;
919
+ final bool defaultAllowUntaggedInShared;
835
920
  final List<MessagingAccessRule> directRules;
836
921
  final List<MessagingAccessRule> sharedSpaceRules;
837
922
  final List<MessagingAccessRule> sharedActorRules;
923
+ final List<MessagingAccessRule> sharedMemberRules;
924
+ final List<MessagingSharedParticipationRule> sharedParticipationRules;
838
925
 
839
926
  MessagingAccessPolicy copyWith({
927
+ int? schemaVersion,
840
928
  String? directPolicy,
841
929
  String? sharedPolicy,
842
- bool? requireMentionInShared,
930
+ bool? defaultAllowUntaggedInShared,
843
931
  List<MessagingAccessRule>? directRules,
844
932
  List<MessagingAccessRule>? sharedSpaceRules,
845
933
  List<MessagingAccessRule>? sharedActorRules,
934
+ List<MessagingAccessRule>? sharedMemberRules,
935
+ List<MessagingSharedParticipationRule>? sharedParticipationRules,
846
936
  }) {
847
937
  return MessagingAccessPolicy(
938
+ schemaVersion: schemaVersion ?? this.schemaVersion,
848
939
  directPolicy: directPolicy ?? this.directPolicy,
849
940
  sharedPolicy: sharedPolicy ?? this.sharedPolicy,
850
- requireMentionInShared:
851
- requireMentionInShared ?? this.requireMentionInShared,
941
+ defaultAllowUntaggedInShared:
942
+ defaultAllowUntaggedInShared ?? this.defaultAllowUntaggedInShared,
852
943
  directRules: directRules ?? this.directRules,
853
944
  sharedSpaceRules: sharedSpaceRules ?? this.sharedSpaceRules,
854
945
  sharedActorRules: sharedActorRules ?? this.sharedActorRules,
946
+ sharedMemberRules: sharedMemberRules ?? this.sharedMemberRules,
947
+ sharedParticipationRules:
948
+ sharedParticipationRules ?? this.sharedParticipationRules,
855
949
  );
856
950
  }
857
951
 
858
952
  Map<String, dynamic> toJson() => <String, dynamic>{
953
+ 'schemaVersion': schemaVersion,
859
954
  'directPolicy': directPolicy,
860
955
  'sharedPolicy': sharedPolicy,
861
- 'requireMentionInShared': requireMentionInShared,
956
+ 'defaultAllowUntaggedInShared': defaultAllowUntaggedInShared,
862
957
  'directRules': directRules
863
958
  .map((rule) => rule.toJson())
864
959
  .toList(growable: false),
@@ -868,10 +963,19 @@ class MessagingAccessPolicy {
868
963
  'sharedActorRules': sharedActorRules
869
964
  .map((rule) => rule.toJson())
870
965
  .toList(growable: false),
966
+ 'sharedMemberRules': sharedMemberRules
967
+ .map((rule) => rule.toJson())
968
+ .toList(growable: false),
969
+ 'sharedParticipationRules': sharedParticipationRules
970
+ .map((rule) => rule.toJson())
971
+ .toList(growable: false),
871
972
  };
872
973
 
873
974
  int get totalRuleCount =>
874
- directRules.length + sharedSpaceRules.length + sharedActorRules.length;
975
+ directRules.length +
976
+ sharedSpaceRules.length +
977
+ sharedActorRules.length +
978
+ sharedMemberRules.length;
875
979
  }
876
980
 
877
981
  class MessagingAccessCapabilities {
@@ -879,6 +983,7 @@ class MessagingAccessCapabilities {
879
983
  this.supportsDirectPolicy = true,
880
984
  this.supportsSharedPolicy = true,
881
985
  this.supportsMentionGate = false,
986
+ this.supportsUntaggedGroupToggle = true,
882
987
  this.supportsDiscovery = false,
883
988
  this.directRuleScopes = const <String>[],
884
989
  this.sharedSpaceRuleScopes = const <String>[],
@@ -899,6 +1004,7 @@ class MessagingAccessCapabilities {
899
1004
  supportsDirectPolicy: json['supportsDirectPolicy'] != false,
900
1005
  supportsSharedPolicy: json['supportsSharedPolicy'] != false,
901
1006
  supportsMentionGate: json['supportsMentionGate'] == true,
1007
+ supportsUntaggedGroupToggle: json['supportsUntaggedGroupToggle'] != false,
902
1008
  supportsDiscovery: json['supportsDiscovery'] == true,
903
1009
  directRuleScopes: stringList(json['directRuleScopes']),
904
1010
  sharedSpaceRuleScopes: stringList(json['sharedSpaceRuleScopes']),
@@ -910,6 +1016,7 @@ class MessagingAccessCapabilities {
910
1016
  final bool supportsDirectPolicy;
911
1017
  final bool supportsSharedPolicy;
912
1018
  final bool supportsMentionGate;
1019
+ final bool supportsUntaggedGroupToggle;
913
1020
  final bool supportsDiscovery;
914
1021
  final List<String> directRuleScopes;
915
1022
  final List<String> sharedSpaceRuleScopes;
@@ -920,6 +1027,7 @@ class MessagingAccessCapabilities {
920
1027
  'supportsDirectPolicy': supportsDirectPolicy,
921
1028
  'supportsSharedPolicy': supportsSharedPolicy,
922
1029
  'supportsMentionGate': supportsMentionGate,
1030
+ 'supportsUntaggedGroupToggle': supportsUntaggedGroupToggle,
923
1031
  'supportsDiscovery': supportsDiscovery,
924
1032
  'directRuleScopes': directRuleScopes,
925
1033
  'sharedSpaceRuleScopes': sharedSpaceRuleScopes,
@@ -936,18 +1044,41 @@ class MessagingAccessTarget {
936
1044
  required this.value,
937
1045
  required this.label,
938
1046
  required this.subtitle,
1047
+ this.spaceScope,
1048
+ this.spaceValue,
1049
+ this.spaceLabel,
939
1050
  });
940
1051
 
941
1052
  factory MessagingAccessTarget.fromJson(Map<String, dynamic> json) {
1053
+ final nestedRule = _jsonMap(json['rule']);
1054
+ final scope =
1055
+ json['scope']?.toString() ?? nestedRule['scope']?.toString() ?? 'chat';
1056
+ final value =
1057
+ json['value']?.toString() ?? nestedRule['value']?.toString() ?? '';
942
1058
  return MessagingAccessTarget(
943
1059
  source: json['source']?.toString() ?? 'manual',
944
1060
  bucket: json['bucket']?.toString() ?? 'sharedSpaceRules',
945
- scope: json['scope']?.toString() ?? 'chat',
946
- value: json['value']?.toString() ?? '',
1061
+ scope: scope,
1062
+ value: value,
947
1063
  label:
948
- json['label']?.toString().ifEmpty(json['value']?.toString() ?? '') ??
949
- (json['value']?.toString() ?? ''),
950
- subtitle: json['subtitle']?.toString() ?? '',
1064
+ nestedRule['label']?.toString().ifEmpty(
1065
+ json['label']?.toString() ?? value,
1066
+ ) ??
1067
+ json['label']?.toString().ifEmpty(value) ??
1068
+ value,
1069
+ subtitle:
1070
+ json['subtitle']?.toString() ??
1071
+ nestedRule['spaceLabel']?.toString() ??
1072
+ '',
1073
+ spaceScope:
1074
+ json['spaceScope']?.toString() ??
1075
+ nestedRule['spaceScope']?.toString(),
1076
+ spaceValue:
1077
+ json['spaceValue']?.toString() ??
1078
+ nestedRule['spaceValue']?.toString(),
1079
+ spaceLabel:
1080
+ json['spaceLabel']?.toString() ??
1081
+ nestedRule['spaceLabel']?.toString(),
951
1082
  );
952
1083
  }
953
1084
 
@@ -957,11 +1088,21 @@ class MessagingAccessTarget {
957
1088
  final String value;
958
1089
  final String label;
959
1090
  final String subtitle;
960
-
961
- MessagingAccessRule get asRule =>
962
- MessagingAccessRule(scope: scope, value: value, label: label);
963
-
964
- String get id => '$bucket:$scope:$value';
1091
+ final String? spaceScope;
1092
+ final String? spaceValue;
1093
+ final String? spaceLabel;
1094
+
1095
+ MessagingAccessRule get asRule => MessagingAccessRule(
1096
+ scope: scope,
1097
+ value: value,
1098
+ label: label,
1099
+ spaceScope: spaceScope,
1100
+ spaceValue: spaceValue,
1101
+ spaceLabel: spaceLabel,
1102
+ );
1103
+
1104
+ String get id =>
1105
+ '$bucket:$scope:$value:${spaceScope ?? ''}:${spaceValue ?? ''}';
965
1106
 
966
1107
  Map<String, dynamic> toJson() => <String, dynamic>{
967
1108
  'source': source,
@@ -970,6 +1111,12 @@ class MessagingAccessTarget {
970
1111
  'value': value,
971
1112
  'label': label,
972
1113
  'subtitle': subtitle,
1114
+ if (spaceScope != null && spaceScope!.trim().isNotEmpty)
1115
+ 'spaceScope': spaceScope,
1116
+ if (spaceValue != null && spaceValue!.trim().isNotEmpty)
1117
+ 'spaceValue': spaceValue,
1118
+ if (spaceLabel != null && spaceLabel!.trim().isNotEmpty)
1119
+ 'spaceLabel': spaceLabel,
973
1120
  };
974
1121
  }
975
1122
 
@@ -2548,7 +2695,8 @@ class OfficialIntegrationAppItem {
2548
2695
  String get effectiveStatus =>
2549
2696
  !isConnected && hasExpiredAccounts ? 'expired' : connection.status;
2550
2697
 
2551
- String get statusLabel => _titleCase(effectiveStatus.replaceAll('_', ' '));
2698
+ String get statusLabel =>
2699
+ effectiveStatus == 'expired' ? 'Expired' : connection.statusLabel;
2552
2700
  }
2553
2701
 
2554
2702
  class OfficialIntegrationEnvStatus {
@@ -2780,7 +2928,8 @@ class OfficialIntegrationItem {
2780
2928
  String get effectiveStatus =>
2781
2929
  !isConnected && hasExpiredAccounts ? 'expired' : connection.status;
2782
2930
 
2783
- String get statusLabel => _titleCase(effectiveStatus.replaceAll('_', ' '));
2931
+ String get statusLabel =>
2932
+ effectiveStatus == 'expired' ? 'Expired' : connection.statusLabel;
2784
2933
  }
2785
2934
 
2786
2935
  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, {