neoagent 2.4.1-beta.9 → 2.4.1

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 (234) hide show
  1. package/.env.example +33 -3
  2. package/LICENSE +111 -56
  3. package/README.md +8 -3
  4. package/docs/configuration.md +8 -0
  5. package/docs/getting-started.md +9 -3
  6. package/docs/index.md +4 -0
  7. package/extensions/chrome-browser/background.mjs +36 -4
  8. package/extensions/chrome-browser/icons/icon128.png +0 -0
  9. package/extensions/chrome-browser/icons/icon16.png +0 -0
  10. package/extensions/chrome-browser/icons/icon48.png +0 -0
  11. package/extensions/chrome-browser/icons/logo.svg +39 -8
  12. package/extensions/chrome-browser/manifest.json +3 -3
  13. package/extensions/chrome-browser/popup.html +5 -1
  14. package/extensions/chrome-browser/popup.js +13 -0
  15. package/flutter_app/android/app/src/main/AndroidManifest.xml +2 -1
  16. package/flutter_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  17. package/flutter_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  18. package/flutter_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  19. package/flutter_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  20. package/flutter_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  21. package/flutter_app/assets/branding/app_icon_1024.png +0 -0
  22. package/flutter_app/assets/branding/app_icon_128.png +0 -0
  23. package/flutter_app/assets/branding/app_icon_192.png +0 -0
  24. package/flutter_app/assets/branding/app_icon_256.png +0 -0
  25. package/flutter_app/assets/branding/app_icon_32.png +0 -0
  26. package/flutter_app/assets/branding/app_icon_512.png +0 -0
  27. package/flutter_app/assets/branding/app_icon_64.png +0 -0
  28. package/flutter_app/assets/branding/app_icon_light_1024.png +0 -0
  29. package/flutter_app/assets/branding/app_icon_light_128.png +0 -0
  30. package/flutter_app/assets/branding/app_icon_light_192.png +0 -0
  31. package/flutter_app/assets/branding/app_icon_light_256.png +0 -0
  32. package/flutter_app/assets/branding/app_icon_light_32.png +0 -0
  33. package/flutter_app/assets/branding/app_icon_light_512.png +0 -0
  34. package/flutter_app/assets/branding/app_icon_light_64.png +0 -0
  35. package/flutter_app/assets/branding/onboarding_intro.mp4 +0 -0
  36. package/flutter_app/assets/branding/tray_icon_light_template.png +0 -0
  37. package/flutter_app/assets/branding/tray_icon_template.png +0 -0
  38. package/flutter_app/lib/features/location/location_service.dart +3 -0
  39. package/flutter_app/lib/features/onboarding/onboarding_chrome.dart +391 -382
  40. package/flutter_app/lib/features/onboarding/onboarding_companion_step.dart +743 -0
  41. package/flutter_app/lib/features/onboarding/onboarding_messaging_step.dart +18 -16
  42. package/flutter_app/lib/features/onboarding/onboarding_model_step.dart +19 -18
  43. package/flutter_app/lib/features/onboarding/onboarding_shell.dart +8 -1
  44. package/flutter_app/lib/features/onboarding/onboarding_video_step.dart +16 -13
  45. package/flutter_app/lib/features/onboarding/onboarding_welcome_step.dart +17 -13
  46. package/flutter_app/lib/main.dart +2 -0
  47. package/flutter_app/lib/main_account_settings.dart +10 -34
  48. package/flutter_app/lib/main_admin.dart +14 -1
  49. package/flutter_app/lib/main_app_shell.dart +377 -340
  50. package/flutter_app/lib/main_chat.dart +707 -227
  51. package/flutter_app/lib/main_controller.dart +197 -42
  52. package/flutter_app/lib/main_devices.dart +436 -4
  53. package/flutter_app/lib/main_integrations.dart +255 -6
  54. package/flutter_app/lib/main_launcher.dart +1 -1
  55. package/flutter_app/lib/main_model_picker.dart +685 -0
  56. package/flutter_app/lib/main_models.dart +212 -0
  57. package/flutter_app/lib/main_navigation.dart +1 -9
  58. package/flutter_app/lib/main_operations.dart +1417 -614
  59. package/flutter_app/lib/main_settings.dart +673 -871
  60. package/flutter_app/lib/main_shared.dart +971 -443
  61. package/flutter_app/lib/main_spacing.dart +4 -4
  62. package/flutter_app/lib/main_theme.dart +22 -14
  63. package/flutter_app/lib/main_unified.dart +5 -72
  64. package/flutter_app/lib/src/android_apk_drop_zone.dart +24 -0
  65. package/flutter_app/lib/src/android_apk_drop_zone_stub.dart +13 -0
  66. package/flutter_app/lib/src/android_apk_drop_zone_web.dart +219 -0
  67. package/flutter_app/lib/src/backend_client.dart +46 -0
  68. package/flutter_app/lib/src/desktop_companion_actions.dart +30 -7
  69. package/flutter_app/lib/src/desktop_companion_io.dart +71 -1
  70. package/flutter_app/lib/src/security/password_strength.dart +84 -0
  71. package/flutter_app/lib/src/stream_renderer.dart +42 -3
  72. package/flutter_app/lib/src/theme/palette.dart +76 -34
  73. package/flutter_app/linux/runner/resources/app_icon.png +0 -0
  74. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png +0 -0
  75. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png +0 -0
  76. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png +0 -0
  77. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png +0 -0
  78. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png +0 -0
  79. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png +0 -0
  80. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png +0 -0
  81. package/flutter_app/pubspec.lock +2 -2
  82. package/flutter_app/pubspec.yaml +7 -1
  83. package/flutter_app/tool/branding_source/neoagent-icon-1024.png +0 -0
  84. package/flutter_app/tool/branding_source/neoagent-icon-128.png +0 -0
  85. package/flutter_app/tool/branding_source/neoagent-icon-16.png +0 -0
  86. package/flutter_app/tool/branding_source/neoagent-icon-180.png +0 -0
  87. package/flutter_app/tool/branding_source/neoagent-icon-192.png +0 -0
  88. package/flutter_app/tool/branding_source/neoagent-icon-256.png +0 -0
  89. package/flutter_app/tool/branding_source/neoagent-icon-32.png +0 -0
  90. package/flutter_app/tool/branding_source/neoagent-icon-48.png +0 -0
  91. package/flutter_app/tool/branding_source/neoagent-icon-512.png +0 -0
  92. package/flutter_app/tool/branding_source/neoagent-icon-64.png +0 -0
  93. package/flutter_app/tool/branding_source/neoagent-icon.svg +43 -0
  94. package/flutter_app/tool/generate_desktop_branding.py +154 -152
  95. package/flutter_app/web/favicon.png +0 -0
  96. package/flutter_app/web/favicon.svg +40 -9
  97. package/flutter_app/web/favicon_light.svg +43 -0
  98. package/flutter_app/web/icons/Icon-192-light.png +0 -0
  99. package/flutter_app/web/icons/Icon-192.png +0 -0
  100. package/flutter_app/web/icons/Icon-512-light.png +0 -0
  101. package/flutter_app/web/icons/Icon-512.png +0 -0
  102. package/flutter_app/web/icons/Icon-maskable-192-light.png +0 -0
  103. package/flutter_app/web/icons/Icon-maskable-192.png +0 -0
  104. package/flutter_app/web/icons/Icon-maskable-512-light.png +0 -0
  105. package/flutter_app/web/icons/Icon-maskable-512.png +0 -0
  106. package/flutter_app/windows/runner/main.cpp +7 -1
  107. package/flutter_app/windows/runner/resources/app_icon.ico +0 -0
  108. package/lib/manager.js +445 -83
  109. package/package.json +17 -3
  110. package/runtime/paths.js +3 -1
  111. package/server/admin/access.js +198 -0
  112. package/server/admin/admin.css +268 -0
  113. package/server/admin/admin.js +348 -0
  114. package/server/admin/analytics.js +128 -0
  115. package/server/admin/index.html +1015 -0
  116. package/server/admin/login.html +290 -0
  117. package/server/admin/logo.svg +43 -0
  118. package/server/admin/sql.js +134 -0
  119. package/server/admin/users.js +147 -0
  120. package/server/config/origins.js +3 -1
  121. package/server/db/database.js +92 -0
  122. package/server/db/ftsQuery.js +27 -0
  123. package/server/http/routes.js +1 -0
  124. package/server/http/static.js +23 -6
  125. package/server/index.js +1 -1
  126. package/server/middleware/adminAuth.js +48 -0
  127. package/server/middleware/auth.js +1 -40
  128. package/server/public/.last_build_id +1 -1
  129. package/server/public/app_icon.png +0 -0
  130. package/server/public/assets/AssetManifest.bin +1 -1
  131. package/server/public/assets/AssetManifest.bin.json +1 -1
  132. package/server/public/assets/assets/branding/app_icon_1024.png +0 -0
  133. package/server/public/assets/assets/branding/app_icon_256.png +0 -0
  134. package/server/public/assets/assets/branding/app_icon_512.png +0 -0
  135. package/server/public/assets/assets/branding/app_icon_light_1024.png +0 -0
  136. package/server/public/assets/assets/branding/app_icon_light_256.png +0 -0
  137. package/server/public/assets/assets/branding/app_icon_light_512.png +0 -0
  138. package/server/public/assets/assets/branding/onboarding_intro.mp4 +0 -0
  139. package/server/public/assets/assets/branding/tray_icon_light_template.png +0 -0
  140. package/server/public/assets/assets/branding/tray_icon_template.png +0 -0
  141. package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
  142. package/server/public/assets/web/icons/Icon-192.png +0 -0
  143. package/server/public/favicon.png +0 -0
  144. package/server/public/favicon.svg +40 -9
  145. package/server/public/favicon_light.svg +43 -0
  146. package/server/public/flutter_bootstrap.js +2 -2
  147. package/server/public/icons/Icon-192-light.png +0 -0
  148. package/server/public/icons/Icon-192.png +0 -0
  149. package/server/public/icons/Icon-512-light.png +0 -0
  150. package/server/public/icons/Icon-512.png +0 -0
  151. package/server/public/icons/Icon-maskable-192-light.png +0 -0
  152. package/server/public/icons/Icon-maskable-192.png +0 -0
  153. package/server/public/icons/Icon-maskable-512-light.png +0 -0
  154. package/server/public/icons/Icon-maskable-512.png +0 -0
  155. package/server/public/main.dart.js +88537 -85936
  156. package/server/routes/admin.js +632 -0
  157. package/server/routes/agent_profiles.js +3 -0
  158. package/server/routes/agents.js +1 -1
  159. package/server/routes/auth.js +3 -1
  160. package/server/routes/mcp.js +29 -13
  161. package/server/routes/memory.js +23 -1
  162. package/server/routes/messaging.js +2 -0
  163. package/server/routes/screenHistory.js +14 -8
  164. package/server/routes/settings.js +10 -2
  165. package/server/routes/stream.js +1 -1
  166. package/server/routes/triggers.js +4 -4
  167. package/server/routes/voice_assistant.js +36 -1
  168. package/server/routes/workspace.js +86 -0
  169. package/server/services/account/admin_two_factor.js +132 -0
  170. package/server/services/account/password_policy.js +6 -1
  171. package/server/services/ai/completion.js +44 -0
  172. package/server/services/ai/engine.js +321 -500
  173. package/server/services/ai/imageAnalysis.js +9 -5
  174. package/server/services/ai/logFormat.js +46 -0
  175. package/server/services/ai/loopPolicy.js +11 -0
  176. package/server/services/ai/messagingFallback.js +228 -0
  177. package/server/services/ai/models.js +194 -55
  178. package/server/services/ai/providerRetry.js +169 -0
  179. package/server/services/ai/providers/anthropic.js +15 -4
  180. package/server/services/ai/providers/google.js +38 -11
  181. package/server/services/ai/providers/grok.js +19 -68
  182. package/server/services/ai/providers/grokOauth.js +141 -0
  183. package/server/services/ai/providers/nvidia.js +154 -0
  184. package/server/services/ai/providers/ollama.js +76 -39
  185. package/server/services/ai/providers/openai.js +20 -31
  186. package/server/services/ai/providers/openaiCodex.js +6 -2
  187. package/server/services/ai/providers/openaiCompatible.js +70 -0
  188. package/server/services/ai/providers/openrouter.js +162 -0
  189. package/server/services/ai/settings.js +30 -0
  190. package/server/services/ai/systemPrompt.js +51 -29
  191. package/server/services/ai/taskAnalysis.js +60 -1
  192. package/server/services/ai/toolEvidence.js +207 -0
  193. package/server/services/ai/toolSelector.js +8 -1
  194. package/server/services/ai/tools.js +78 -13
  195. package/server/services/browser/extension/registry.js +94 -9
  196. package/server/services/desktop/registry.js +104 -1
  197. package/server/services/desktop/screenRecorder.js +208 -98
  198. package/server/services/desktop/screen_recorder_support.js +46 -0
  199. package/server/services/integrations/google/provider.js +13 -0
  200. package/server/services/integrations/home_assistant/constants.js +88 -0
  201. package/server/services/integrations/home_assistant/network.js +207 -0
  202. package/server/services/integrations/home_assistant/provider.js +249 -0
  203. package/server/services/integrations/home_assistant/snapshot.js +98 -0
  204. package/server/services/integrations/home_assistant/tools.js +101 -0
  205. package/server/services/integrations/manager.js +5 -0
  206. package/server/services/integrations/registry.js +2 -0
  207. package/server/services/integrations/trello/provider.js +8 -2
  208. package/server/services/manager.js +51 -53
  209. package/server/services/mcp/client.js +208 -268
  210. package/server/services/mcp/client_support.js +172 -0
  211. package/server/services/mcp/recovery.js +116 -0
  212. package/server/services/mcp/tool_operations.js +123 -0
  213. package/server/services/memory/ingestion.js +185 -370
  214. package/server/services/memory/ingestion_coverage.js +129 -0
  215. package/server/services/memory/ingestion_documents.js +123 -0
  216. package/server/services/memory/ingestion_support.js +171 -0
  217. package/server/services/memory/intelligence.js +181 -0
  218. package/server/services/memory/manager.js +476 -32
  219. package/server/services/messaging/automation.js +71 -134
  220. package/server/services/messaging/formatting_guides.js +26 -1
  221. package/server/services/messaging/inbound_queue.js +111 -0
  222. package/server/services/messaging/manager.js +25 -8
  223. package/server/services/messaging/typing_keepalive.js +110 -0
  224. package/server/services/streaming/android-stream.js +293 -40
  225. package/server/services/tasks/integration_runtime.js +4 -1
  226. package/server/services/tasks/runtime.js +415 -56
  227. package/server/services/tasks/task_repository.js +48 -6
  228. package/server/services/websocket.js +8 -2
  229. package/server/services/widgets/service.js +17 -1
  230. package/server/services/workspace/manager.js +116 -0
  231. package/server/utils/logger.js +44 -6
  232. package/server/utils/security.js +3 -0
  233. package/server/utils/version.js +29 -19
  234. package/server/services/memory/openhuman_uplift.test.js +0 -98
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
2
2
  import 'package:flutter_animate/flutter_animate.dart';
3
3
 
4
4
  import '../../main.dart';
5
+ import '../../src/theme/palette.dart';
5
6
  import 'onboarding_chrome.dart';
6
7
 
7
8
  class OnboardingMessagingStep extends StatefulWidget {
@@ -29,7 +30,6 @@ class _OnboardingMessagingStepState extends State<OnboardingMessagingStep> {
29
30
  : messagingPlatforms;
30
31
  final width = MediaQuery.sizeOf(context).width;
31
32
  final useGrid = width >= 700;
32
- final columns = width >= 1050 ? 3 : (useGrid ? 2 : 1);
33
33
 
34
34
  return OnboardingScaffold(
35
35
  step: 2,
@@ -79,11 +79,11 @@ class _OnboardingMessagingStepState extends State<OnboardingMessagingStep> {
79
79
  ),
80
80
  child: useGrid
81
81
  ? GridView.builder(
82
- gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
83
- crossAxisCount: columns,
82
+ gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
83
+ maxCrossAxisExtent: 360,
84
84
  crossAxisSpacing: 14,
85
85
  mainAxisSpacing: 14,
86
- childAspectRatio: width >= 1050 ? 1.5 : 1.35,
86
+ mainAxisExtent: 160,
87
87
  ),
88
88
  itemCount: platforms.length,
89
89
  itemBuilder: (context, index) {
@@ -133,6 +133,7 @@ class _MessagingPlatformCard extends StatelessWidget {
133
133
 
134
134
  @override
135
135
  Widget build(BuildContext context) {
136
+ final p = paletteOf(context);
136
137
  final iconSize = compact ? 24.0 : 30.0;
137
138
  final shellSize = compact ? 48.0 : 58.0;
138
139
  return OnboardingOptionCard(
@@ -172,10 +173,10 @@ class _MessagingPlatformCard extends StatelessWidget {
172
173
  platform.label,
173
174
  maxLines: 1,
174
175
  overflow: TextOverflow.ellipsis,
175
- style: const TextStyle(
176
- color: Colors.white,
177
- fontSize: 18,
178
- fontWeight: FontWeight.w800,
176
+ style: TextStyle(
177
+ color: p.textPrimary,
178
+ fontSize: 16,
179
+ fontWeight: FontWeight.w700,
179
180
  ),
180
181
  ),
181
182
  const SizedBox(height: 4),
@@ -184,9 +185,9 @@ class _MessagingPlatformCard extends StatelessWidget {
184
185
  maxLines: 2,
185
186
  overflow: TextOverflow.ellipsis,
186
187
  style: TextStyle(
187
- color: Colors.white.withValues(alpha: 0.68),
188
+ color: p.textMuted,
188
189
  fontSize: 13,
189
- height: 1.35,
190
+ height: 1.4,
190
191
  ),
191
192
  ),
192
193
  ],
@@ -213,17 +214,17 @@ class _MessagingPlatformCard extends StatelessWidget {
213
214
  children: <Widget>[
214
215
  Text(
215
216
  platform.label,
216
- style: const TextStyle(
217
- color: Colors.white,
218
- fontSize: 20,
219
- fontWeight: FontWeight.w800,
217
+ style: TextStyle(
218
+ color: p.textPrimary,
219
+ fontSize: 18,
220
+ fontWeight: FontWeight.w700,
220
221
  ),
221
222
  ),
222
223
  const SizedBox(height: 5),
223
224
  Text(
224
225
  platform.subtitle,
225
226
  style: TextStyle(
226
- color: Colors.white.withValues(alpha: 0.68),
227
+ color: p.textMuted,
227
228
  fontSize: 14,
228
229
  height: 1.45,
229
230
  ),
@@ -255,6 +256,7 @@ class _SelectionIcon extends StatelessWidget {
255
256
 
256
257
  @override
257
258
  Widget build(BuildContext context) {
259
+ final p = paletteOf(context);
258
260
  return AnimatedSwitcher(
259
261
  duration: const Duration(milliseconds: 220),
260
262
  child: selected
@@ -267,7 +269,7 @@ class _SelectionIcon extends StatelessWidget {
267
269
  : Icon(
268
270
  Icons.add_circle_outline_rounded,
269
271
  key: ValueKey<String>('idle-$id'),
270
- color: Colors.white.withValues(alpha: 0.26),
272
+ color: p.textMuted.withValues(alpha: 0.5),
271
273
  size: 28,
272
274
  ),
273
275
  );
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
2
2
  import 'package:flutter_animate/flutter_animate.dart';
3
3
 
4
4
  import '../../main.dart';
5
+ import '../../src/theme/palette.dart';
5
6
  import 'onboarding_chrome.dart';
6
7
 
7
8
  class OnboardingModelStep extends StatefulWidget {
@@ -76,14 +77,13 @@ class _OnboardingModelStepState extends State<OnboardingModelStep> {
76
77
  Widget build(BuildContext context) {
77
78
  final width = MediaQuery.sizeOf(context).width;
78
79
  final useGrid = width >= 720;
79
- final columns = width >= 1150 ? 3 : (useGrid ? 2 : 1);
80
80
 
81
81
  return OnboardingScaffold(
82
82
  step: 3,
83
83
  totalSteps: 4,
84
84
  eyebrow: 'INTELLIGENCE',
85
85
  title: 'Choose your\ndefault model.',
86
- description: 'Pick the model NeoOS should use by default.',
86
+ description: 'Pick the model NeoAgent should use by default.',
87
87
  footer: Row(
88
88
  mainAxisAlignment: MainAxisAlignment.end,
89
89
  children: <Widget>[
@@ -100,7 +100,7 @@ class _OnboardingModelStepState extends State<OnboardingModelStep> {
100
100
  'No available models found.\nYou can configure providers later in Settings.',
101
101
  textAlign: TextAlign.center,
102
102
  style: TextStyle(
103
- color: Colors.white.withValues(alpha: 0.6),
103
+ color: paletteOf(context).textMuted,
104
104
  fontSize: 16,
105
105
  height: 1.5,
106
106
  ),
@@ -108,11 +108,11 @@ class _OnboardingModelStepState extends State<OnboardingModelStep> {
108
108
  )
109
109
  : useGrid
110
110
  ? GridView.builder(
111
- gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
112
- crossAxisCount: columns,
111
+ gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
112
+ maxCrossAxisExtent: 380,
113
113
  crossAxisSpacing: 14,
114
114
  mainAxisSpacing: 14,
115
- childAspectRatio: width >= 1150 ? 1.38 : 1.18,
115
+ mainAxisExtent: 214,
116
116
  ),
117
117
  itemCount: _models.length,
118
118
  itemBuilder: (context, index) {
@@ -193,6 +193,7 @@ class _ModelChoiceCard extends StatelessWidget {
193
193
 
194
194
  @override
195
195
  Widget build(BuildContext context) {
196
+ final p = paletteOf(context);
196
197
  final shellSize = compact ? 48.0 : 58.0;
197
198
  final iconSize = compact ? 24.0 : 30.0;
198
199
  final titleSize = compact ? 17.0 : 20.0;
@@ -231,7 +232,7 @@ class _ModelChoiceCard extends StatelessWidget {
231
232
  : Icon(
232
233
  Icons.radio_button_unchecked_rounded,
233
234
  key: ValueKey<String>('idle-${model.id}'),
234
- color: Colors.white.withValues(alpha: 0.25),
235
+ color: p.textMuted.withValues(alpha: 0.5),
235
236
  size: 28,
236
237
  ),
237
238
  ),
@@ -243,9 +244,9 @@ class _ModelChoiceCard extends StatelessWidget {
243
244
  maxLines: 1,
244
245
  overflow: TextOverflow.ellipsis,
245
246
  style: TextStyle(
246
- color: Colors.white,
247
+ color: p.textPrimary,
247
248
  fontSize: titleSize,
248
- fontWeight: FontWeight.w800,
249
+ fontWeight: FontWeight.w700,
249
250
  ),
250
251
  ),
251
252
  const SizedBox(height: 6),
@@ -255,7 +256,7 @@ class _ModelChoiceCard extends StatelessWidget {
255
256
  vertical: 5,
256
257
  ),
257
258
  decoration: BoxDecoration(
258
- color: Colors.white.withValues(alpha: 0.08),
259
+ color: p.bgSecondary,
259
260
  borderRadius: BorderRadius.circular(999),
260
261
  ),
261
262
  child: Text(
@@ -263,7 +264,7 @@ class _ModelChoiceCard extends StatelessWidget {
263
264
  maxLines: 1,
264
265
  overflow: TextOverflow.ellipsis,
265
266
  style: TextStyle(
266
- color: Colors.white.withValues(alpha: 0.72),
267
+ color: p.textMuted,
267
268
  fontSize: 11,
268
269
  fontWeight: FontWeight.w700,
269
270
  ),
@@ -275,7 +276,7 @@ class _ModelChoiceCard extends StatelessWidget {
275
276
  maxLines: 3,
276
277
  overflow: TextOverflow.ellipsis,
277
278
  style: TextStyle(
278
- color: Colors.white.withValues(alpha: 0.68),
279
+ color: p.textMuted,
279
280
  fontSize: purposeSize,
280
281
  height: 1.35,
281
282
  ),
@@ -306,9 +307,9 @@ class _ModelChoiceCard extends StatelessWidget {
306
307
  Text(
307
308
  model.label,
308
309
  style: TextStyle(
309
- color: Colors.white,
310
+ color: p.textPrimary,
310
311
  fontSize: titleSize,
311
- fontWeight: FontWeight.w800,
312
+ fontWeight: FontWeight.w700,
312
313
  ),
313
314
  ),
314
315
  Container(
@@ -317,13 +318,13 @@ class _ModelChoiceCard extends StatelessWidget {
317
318
  vertical: 5,
318
319
  ),
319
320
  decoration: BoxDecoration(
320
- color: Colors.white.withValues(alpha: 0.08),
321
+ color: p.bgSecondary,
321
322
  borderRadius: BorderRadius.circular(999),
322
323
  ),
323
324
  child: Text(
324
325
  model.provider,
325
326
  style: TextStyle(
326
- color: Colors.white.withValues(alpha: 0.72),
327
+ color: p.textMuted,
327
328
  fontSize: 12,
328
329
  fontWeight: FontWeight.w700,
329
330
  ),
@@ -335,7 +336,7 @@ class _ModelChoiceCard extends StatelessWidget {
335
336
  Text(
336
337
  model.purpose,
337
338
  style: TextStyle(
338
- color: Colors.white.withValues(alpha: 0.68),
339
+ color: p.textMuted,
339
340
  fontSize: purposeSize,
340
341
  height: 1.45,
341
342
  ),
@@ -355,7 +356,7 @@ class _ModelChoiceCard extends StatelessWidget {
355
356
  : Icon(
356
357
  Icons.radio_button_unchecked_rounded,
357
358
  key: ValueKey<String>('idle-${model.id}'),
358
- color: Colors.white.withValues(alpha: 0.25),
359
+ color: p.textMuted.withValues(alpha: 0.5),
359
360
  size: 28,
360
361
  ),
361
362
  ),
@@ -1,7 +1,9 @@
1
1
  import 'package:flutter/material.dart';
2
2
  import '../../main.dart';
3
+ import '../../src/theme/palette.dart';
3
4
  import 'onboarding_video_step.dart';
4
5
  import 'onboarding_welcome_step.dart';
6
+ import 'onboarding_companion_step.dart';
5
7
  import 'onboarding_messaging_step.dart';
6
8
  import 'onboarding_model_step.dart';
7
9
 
@@ -37,7 +39,7 @@ class _OnboardingShellState extends State<OnboardingShell> {
37
39
  @override
38
40
  Widget build(BuildContext context) {
39
41
  return Scaffold(
40
- backgroundColor: Colors.black, // Deep black for video and transitions
42
+ backgroundColor: paletteOf(context).bgPrimary,
41
43
  body: PageView(
42
44
  controller: _pageController,
43
45
  physics:
@@ -45,6 +47,10 @@ class _OnboardingShellState extends State<OnboardingShell> {
45
47
  children: <Widget>[
46
48
  OnboardingVideoStep(onComplete: _nextStep),
47
49
  OnboardingWelcomeStep(onNext: _nextStep),
50
+ OnboardingCompanionStep(
51
+ onNext: _nextStep,
52
+ controller: widget.controller,
53
+ ),
48
54
  OnboardingMessagingStep(
49
55
  onNext: _nextStep,
50
56
  controller: widget.controller,
@@ -55,3 +61,4 @@ class _OnboardingShellState extends State<OnboardingShell> {
55
61
  );
56
62
  }
57
63
  }
64
+
@@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
5
5
  import 'package:flutter_animate/flutter_animate.dart';
6
6
  import 'package:video_player/video_player.dart';
7
7
 
8
+ import '../../src/theme/palette.dart';
8
9
  import 'onboarding_chrome.dart';
9
10
 
10
11
  class OnboardingVideoStep extends StatefulWidget {
@@ -75,26 +76,27 @@ class _OnboardingVideoStepState extends State<OnboardingVideoStep> {
75
76
  final orientation = MediaQuery.orientationOf(context);
76
77
 
77
78
  if (_hasError) {
79
+ final p = paletteOf(context);
78
80
  return Scaffold(
79
- backgroundColor: Colors.black,
81
+ backgroundColor: p.bgPrimary,
80
82
  body: Center(
81
83
  child: Padding(
82
84
  padding: const EdgeInsets.all(32),
83
85
  child: Column(
84
86
  mainAxisSize: MainAxisSize.min,
85
87
  children: <Widget>[
86
- const Icon(
88
+ Icon(
87
89
  Icons.play_circle_outline_rounded,
88
- color: Colors.white70,
90
+ color: p.accent,
89
91
  size: 56,
90
92
  ),
91
93
  const SizedBox(height: 18),
92
- const Text(
94
+ Text(
93
95
  'Continue to setup',
94
96
  style: TextStyle(
95
- color: Colors.white,
97
+ color: p.textPrimary,
96
98
  fontSize: 26,
97
- fontWeight: FontWeight.w800,
99
+ fontWeight: FontWeight.w700,
98
100
  ),
99
101
  ),
100
102
  const SizedBox(height: 12),
@@ -102,7 +104,7 @@ class _OnboardingVideoStepState extends State<OnboardingVideoStep> {
102
104
  'The intro is not available on this device.',
103
105
  textAlign: TextAlign.center,
104
106
  style: TextStyle(
105
- color: Colors.white.withValues(alpha: 0.68),
107
+ color: p.textMuted,
106
108
  fontSize: 15,
107
109
  height: 1.5,
108
110
  ),
@@ -135,7 +137,7 @@ class _OnboardingVideoStepState extends State<OnboardingVideoStep> {
135
137
  }
136
138
 
137
139
  return Scaffold(
138
- backgroundColor: Colors.black,
140
+ backgroundColor: portrait ? paletteOf(context).bgPrimary : Colors.black,
139
141
  body: portrait
140
142
  ? const _RotatePrompt()
141
143
  : Stack(
@@ -183,21 +185,22 @@ class _RotatePrompt extends StatelessWidget {
183
185
 
184
186
  @override
185
187
  Widget build(BuildContext context) {
188
+ final p = paletteOf(context);
186
189
  return Center(
187
190
  child: Padding(
188
191
  padding: const EdgeInsets.all(32),
189
192
  child: Column(
190
193
  mainAxisSize: MainAxisSize.min,
191
194
  children: <Widget>[
192
- const Icon(Icons.screen_rotation, color: Colors.white, size: 54),
195
+ Icon(Icons.screen_rotation, color: p.accent, size: 54),
193
196
  const SizedBox(height: 18),
194
- const Text(
197
+ Text(
195
198
  'Rotate to continue',
196
199
  textAlign: TextAlign.center,
197
200
  style: TextStyle(
198
- color: Colors.white,
201
+ color: p.textPrimary,
199
202
  fontSize: 24,
200
- fontWeight: FontWeight.w800,
203
+ fontWeight: FontWeight.w700,
201
204
  ),
202
205
  ),
203
206
  const SizedBox(height: 10),
@@ -205,7 +208,7 @@ class _RotatePrompt extends StatelessWidget {
205
208
  'This intro is designed for full-screen landscape playback.',
206
209
  textAlign: TextAlign.center,
207
210
  style: TextStyle(
208
- color: Colors.white.withValues(alpha: 0.7),
211
+ color: p.textMuted,
209
212
  fontSize: 15,
210
213
  height: 1.5,
211
214
  ),
@@ -1,6 +1,7 @@
1
1
  import 'package:flutter/material.dart';
2
2
  import 'package:flutter_animate/flutter_animate.dart';
3
3
 
4
+ import '../../src/theme/palette.dart';
4
5
  import 'onboarding_chrome.dart';
5
6
 
6
7
  class OnboardingWelcomeStep extends StatelessWidget {
@@ -11,10 +12,10 @@ class OnboardingWelcomeStep extends StatelessWidget {
11
12
  @override
12
13
  Widget build(BuildContext context) {
13
14
  return OnboardingScaffold(
14
- step: 1,
15
+ step: 0,
15
16
  totalSteps: 4,
16
17
  eyebrow: 'WELCOME',
17
- title: 'Welcome to\nNeoOS',
18
+ title: 'Welcome to\nNeoAgent',
18
19
  description: 'Your assistant layer for capture, context, and action.',
19
20
  footer: Row(
20
21
  mainAxisAlignment: MainAxisAlignment.end,
@@ -29,17 +30,20 @@ class OnboardingWelcomeStep extends StatelessWidget {
29
30
  .slideY(begin: 0.2),
30
31
  ],
31
32
  ),
32
- child: Align(
33
- alignment: Alignment.topLeft,
34
- child: Text(
35
- 'Set up your workspace in a few steps and start using NeoOS immediately.',
36
- style: TextStyle(
37
- color: Colors.white.withValues(alpha: 0.72),
38
- fontSize: 18,
39
- height: 1.55,
40
- fontWeight: FontWeight.w500,
41
- ),
42
- ).animate().fadeIn(duration: 600.ms, delay: 380.ms),
33
+ child: Builder(
34
+ builder: (context) {
35
+ final p = paletteOf(context);
36
+ return Align(
37
+ alignment: Alignment.topLeft,
38
+ child: Text(
39
+ 'Set up your workspace in a few steps and start using NeoAgent immediately.',
40
+ style: Theme.of(context).textTheme.bodyLarge?.copyWith(
41
+ color: p.textSecondary,
42
+ height: 1.6,
43
+ ),
44
+ ).animate().fadeIn(duration: 600.ms, delay: 380.ms),
45
+ );
46
+ },
43
47
  ),
44
48
  );
45
49
  }
@@ -39,6 +39,7 @@ import 'src/messaging_access_summary.dart';
39
39
  import 'src/oauth_launcher.dart';
40
40
  import 'src/recording_bridge.dart';
41
41
  import 'src/recording_payloads.dart';
42
+ import 'src/security/password_strength.dart';
42
43
  import 'src/stream_renderer.dart';
43
44
  import 'src/theme/palette.dart';
44
45
  import 'src/web_app_update_monitor.dart';
@@ -65,6 +66,7 @@ part 'main_recordings.dart';
65
66
  part 'main_chat.dart';
66
67
  part 'main_account_settings.dart';
67
68
  part 'main_settings.dart';
69
+ part 'main_model_picker.dart';
68
70
  part 'main_operations.dart';
69
71
  part 'main_admin.dart';
70
72
  part 'main_unified.dart';
@@ -28,38 +28,14 @@ _PasswordStrengthInfo _passwordStrengthInfo({
28
28
  color: _borderLight,
29
29
  );
30
30
  }
31
- final lower = RegExp(r'[a-z]').hasMatch(value);
32
- final upper = RegExp(r'[A-Z]').hasMatch(value);
33
- final digits = RegExp(r'[0-9]').hasMatch(value);
34
- final symbols = RegExp(r'[^A-Za-z0-9]').hasMatch(value);
35
- final variety = <bool>[
36
- lower,
37
- upper,
38
- digits,
39
- symbols,
40
- ].where((item) => item).length;
41
- final normalized = value.toLowerCase();
42
- final userHints = <String>{
43
- username.trim().toLowerCase(),
44
- email.trim().toLowerCase(),
45
- email.trim().toLowerCase().split('@').first,
46
- }.where((item) => item.length >= 3);
47
- final containsUserInfo = userHints.any(normalized.contains);
48
- final obviousPattern =
49
- RegExp(r'(.)\1\1').hasMatch(value) ||
50
- normalized.contains('password') ||
51
- normalized.contains('1234') ||
52
- normalized.contains('qwerty');
53
-
54
- var score = 0;
55
- if (value.length >= 8) score += 1;
56
- if (value.length >= 12) score += 1;
57
- if (variety >= 3) score += 1;
58
- if (variety == 4 || value.length >= 16) score += 1;
59
- if (containsUserInfo || obviousPattern) score -= 1;
60
- score = score.clamp(0, 4);
31
+ final evaluation = evaluatePasswordStrength(
32
+ password: password,
33
+ username: username,
34
+ email: email,
35
+ );
36
+ final score = evaluation.score;
61
37
 
62
- if (value.length < 8) {
38
+ if (!evaluation.hasMinimumLength) {
63
39
  return _PasswordStrengthInfo(
64
40
  score: 1,
65
41
  label: 'Weak',
@@ -67,7 +43,7 @@ _PasswordStrengthInfo _passwordStrengthInfo({
67
43
  color: _danger,
68
44
  );
69
45
  }
70
- if (containsUserInfo) {
46
+ if (evaluation.containsUserInfo) {
71
47
  return _PasswordStrengthInfo(
72
48
  score: 2,
73
49
  label: 'Fair',
@@ -75,7 +51,7 @@ _PasswordStrengthInfo _passwordStrengthInfo({
75
51
  color: _warning,
76
52
  );
77
53
  }
78
- if (obviousPattern) {
54
+ if (evaluation.obviousPattern) {
79
55
  return _PasswordStrengthInfo(
80
56
  score: 2,
81
57
  label: 'Fair',
@@ -1197,7 +1173,7 @@ class _QrLoginScannerDialogState extends State<_QrLoginScannerDialog> {
1197
1173
  const SizedBox(height: 28),
1198
1174
  Text(
1199
1175
  'Scan a NeoAgent login QR',
1200
- style: GoogleFonts.spaceGrotesk(
1176
+ style: GoogleFonts.geist(
1201
1177
  fontSize: 28,
1202
1178
  fontWeight: FontWeight.w700,
1203
1179
  color: Colors.white,
@@ -429,6 +429,8 @@ class McpPanel extends StatelessWidget {
429
429
  label: server.status,
430
430
  color: server.status == 'running'
431
431
  ? _success
432
+ : server.hasError
433
+ ? _danger
432
434
  : _textSecondary,
433
435
  ),
434
436
  ],
@@ -437,7 +439,7 @@ class McpPanel extends StatelessWidget {
437
439
  Text(
438
440
  server.command,
439
441
  style: TextStyle(
440
- fontFamily: GoogleFonts.jetBrainsMono().fontFamily,
442
+ fontFamily: GoogleFonts.geistMono().fontFamily,
441
443
  color: _textSecondary,
442
444
  ),
443
445
  ),
@@ -465,6 +467,17 @@ class McpPanel extends StatelessWidget {
465
467
  ),
466
468
  ],
467
469
  ),
470
+ if (server.hasError) ...<Widget>[
471
+ const SizedBox(height: 12),
472
+ _InlineError(message: server.error!),
473
+ if (server.retryLabel.isNotEmpty) ...<Widget>[
474
+ const SizedBox(height: 8),
475
+ Text(
476
+ server.retryLabel,
477
+ style: TextStyle(color: _textSecondary),
478
+ ),
479
+ ],
480
+ ],
468
481
  const SizedBox(height: 14),
469
482
  Wrap(
470
483
  spacing: 10,