neoagent 3.2.1-beta.1 → 3.2.1-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/extensions/chrome-browser/background.mjs +318 -88
- package/extensions/chrome-browser/http.mjs +136 -0
- package/extensions/chrome-browser/protocol.mjs +654 -90
- package/flutter_app/lib/main_chat.dart +118 -739
- package/flutter_app/lib/main_controller.dart +111 -20
- package/flutter_app/lib/main_integrations.dart +607 -8
- package/flutter_app/lib/main_models.dart +3 -0
- package/flutter_app/lib/main_operations.dart +334 -321
- package/flutter_app/lib/main_security.dart +266 -112
- package/flutter_app/lib/main_settings.dart +4 -3
- package/flutter_app/lib/main_shared.dart +14 -11
- package/flutter_app/lib/src/backend_client.dart +78 -0
- package/flutter_app/lib/src/desktop_companion_actions.dart +185 -31
- package/flutter_app/lib/src/desktop_companion_io.dart +319 -86
- package/flutter_app/windows/runner/flutter_window.cpp +143 -32
- package/landing/index.html +3 -1
- package/lib/manager.js +106 -89
- package/lib/schema_migrations.js +115 -13
- package/package.json +30 -15
- package/runtime/paths.js +49 -5
- package/server/db/database.js +2 -2
- package/server/guest-agent.cli.package.json +13 -0
- package/server/guest_agent.js +85 -40
- package/server/http/middleware.js +24 -0
- package/server/http/routes.js +11 -6
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/flutter_bootstrap.js +2 -2
- package/server/public/main.dart.js +73083 -72209
- package/server/routes/admin.js +1 -1
- package/server/routes/android.js +30 -34
- package/server/routes/browser.js +23 -15
- package/server/routes/desktop.js +18 -1
- package/server/routes/integrations.js +107 -1
- package/server/routes/memory.js +1 -0
- package/server/routes/settings.js +16 -5
- package/server/routes/social_reach.js +12 -3
- package/server/routes/social_video.js +4 -0
- package/server/services/agents/manager.js +1 -1
- package/server/services/ai/capabilityHealth.js +62 -96
- package/server/services/ai/compaction.js +7 -2
- package/server/services/ai/history.js +45 -6
- package/server/services/ai/integrated_tools/http_request.js +8 -0
- package/server/services/ai/loop/agent_engine_core.js +390 -162
- package/server/services/ai/loop/blank_recovery.js +5 -4
- package/server/services/ai/loop/callbacks.js +1 -0
- package/server/services/ai/loop/completion_judge.js +121 -5
- package/server/services/ai/loop/conversation_loop.js +510 -332
- package/server/services/ai/loop/messaging_delivery.js +129 -57
- package/server/services/ai/loop/model_call_guard.js +91 -0
- package/server/services/ai/loop/model_io.js +20 -45
- package/server/services/ai/loop/progress_classification.js +2 -0
- package/server/services/ai/loop/tool_dispatch.js +19 -8
- package/server/services/ai/loopPolicy.js +48 -21
- package/server/services/ai/messagingFallback.js +17 -17
- package/server/services/ai/model_discovery.js +227 -0
- package/server/services/ai/model_failure_cache.js +108 -0
- package/server/services/ai/model_identity.js +71 -0
- package/server/services/ai/models.js +68 -163
- package/server/services/ai/providerRetry.js +17 -59
- package/server/services/ai/provider_selector.js +166 -0
- package/server/services/ai/providers/anthropic.js +2 -2
- package/server/services/ai/providers/claudeCode.js +21 -33
- package/server/services/ai/providers/githubCopilot.js +41 -20
- package/server/services/ai/providers/google.js +135 -97
- package/server/services/ai/providers/grok.js +4 -3
- package/server/services/ai/providers/grokOauth.js +19 -27
- package/server/services/ai/providers/nvidia.js +10 -5
- package/server/services/ai/providers/ollama.js +111 -84
- package/server/services/ai/providers/ollama_stream.js +142 -0
- package/server/services/ai/providers/openai.js +39 -5
- package/server/services/ai/providers/openaiCodex.js +11 -4
- package/server/services/ai/providers/openrouter.js +29 -7
- package/server/services/ai/providers/provider_error.js +36 -0
- package/server/services/ai/settings.js +26 -2
- package/server/services/ai/systemPrompt.js +19 -12
- package/server/services/ai/taskAnalysis.js +58 -10
- package/server/services/ai/terminal_reply.js +18 -0
- package/server/services/ai/toolEvidence.js +350 -29
- package/server/services/ai/tools.js +190 -111
- package/server/services/android/controller.js +770 -237
- package/server/services/android/process.js +140 -0
- package/server/services/android/sdk_download.js +143 -0
- package/server/services/android/uia.js +6 -5
- package/server/services/artifacts/store.js +24 -0
- package/server/services/browser/controller.js +843 -385
- package/server/services/browser/extension/gateway.js +40 -16
- package/server/services/browser/extension/protocol.js +15 -1
- package/server/services/browser/extension/provider.js +71 -47
- package/server/services/browser/extension/registry.js +155 -34
- package/server/services/cli/executor.js +62 -9
- package/server/services/credentials/bitwarden_cli.js +322 -0
- package/server/services/credentials/broker.js +594 -0
- package/server/services/desktop/gateway.js +41 -4
- package/server/services/desktop/protocol.js +3 -0
- package/server/services/desktop/provider.js +39 -42
- package/server/services/desktop/registry.js +137 -52
- package/server/services/integrations/bitwarden/constants.js +14 -0
- package/server/services/integrations/bitwarden/provider.js +197 -0
- package/server/services/integrations/bitwarden/snapshot.js +65 -0
- package/server/services/integrations/figma/provider.js +78 -12
- package/server/services/integrations/github/common.js +11 -6
- package/server/services/integrations/github/provider.js +52 -53
- package/server/services/integrations/google/provider.js +55 -19
- package/server/services/integrations/home_assistant/network.js +17 -20
- package/server/services/integrations/home_assistant/provider.js +7 -5
- package/server/services/integrations/home_assistant/tools.js +17 -5
- package/server/services/integrations/http.js +51 -0
- package/server/services/integrations/manager.js +159 -53
- package/server/services/integrations/microsoft/provider.js +80 -13
- package/server/services/integrations/neoarchive/provider.js +55 -29
- package/server/services/integrations/neorecall/client.js +17 -10
- package/server/services/integrations/neorecall/provider.js +20 -11
- package/server/services/integrations/notion/provider.js +16 -13
- package/server/services/integrations/oauth_provider.js +115 -51
- package/server/services/integrations/registry.js +2 -0
- package/server/services/integrations/slack/provider.js +98 -9
- package/server/services/integrations/spotify/provider.js +67 -71
- package/server/services/integrations/trello/provider.js +21 -7
- package/server/services/integrations/weather/provider.js +18 -12
- package/server/services/integrations/whatsapp/provider.js +76 -16
- package/server/services/manager.js +110 -1
- package/server/services/memory/embedding_index.js +20 -8
- package/server/services/memory/embeddings.js +151 -90
- package/server/services/memory/ingestion.js +50 -9
- package/server/services/memory/ingestion_documents.js +13 -3
- package/server/services/memory/manager.js +52 -19
- package/server/services/messaging/automation.js +85 -10
- package/server/services/messaging/formatting_guides.js +7 -4
- package/server/services/messaging/http_platforms.js +33 -13
- package/server/services/messaging/inbound_queue.js +78 -24
- package/server/services/messaging/inbound_store.js +224 -0
- package/server/services/messaging/manager.js +326 -51
- package/server/services/messaging/typing_keepalive.js +5 -2
- package/server/services/messaging/whatsapp.js +22 -14
- package/server/services/network/http.js +210 -0
- package/server/services/network/safe_request.js +307 -0
- package/server/services/runtime/backends/local-vm.js +227 -67
- package/server/services/runtime/docker-vm-manager.js +9 -0
- package/server/services/runtime/guest_bootstrap.js +30 -4
- package/server/services/runtime/guest_image.js +43 -12
- package/server/services/runtime/manager.js +77 -23
- package/server/services/runtime/validation.js +7 -6
- package/server/services/security/tool_categories.js +6 -0
- package/server/services/social_reach/channels/github.js +10 -4
- package/server/services/social_reach/channels/reddit.js +4 -4
- package/server/services/social_reach/channels/rss.js +2 -2
- package/server/services/social_reach/channels/social_video.js +12 -7
- package/server/services/social_reach/channels/v2ex.js +21 -8
- package/server/services/social_reach/channels/x.js +2 -2
- package/server/services/social_reach/channels/xueqiu.js +5 -5
- package/server/services/social_reach/service.js +9 -6
- package/server/services/social_reach/utils.js +65 -14
- package/server/services/social_video/service.js +160 -50
- package/server/services/tasks/integration_runtime.js +18 -8
- package/server/services/tasks/runtime.js +39 -4
- package/server/services/voice/agentBridge.js +17 -4
- package/server/services/voice/bufferedLiveRelayAdapter.js +5 -0
- package/server/services/voice/liveSession.js +31 -0
- package/server/services/voice/message.js +1 -1
- package/server/services/voice/openaiSpeech.js +33 -8
- package/server/services/voice/providers.js +233 -151
- package/server/services/voice/runtime.js +2 -2
- package/server/services/voice/runtimeManager.js +118 -20
- package/server/services/voice/turnRunner.js +6 -0
- package/server/services/wearable/firmware_manifest.js +51 -13
- package/server/services/wearable/service.js +1 -0
- package/server/utils/abort.js +96 -0
- package/server/utils/cloud-security.js +110 -3
- package/server/utils/files.js +31 -0
- package/server/utils/image_payload.js +95 -0
- package/server/utils/retry.js +107 -0
|
@@ -23,7 +23,7 @@ class ToolApprovalRequest {
|
|
|
23
23
|
category: json['category']?.toString() ?? 'unknown',
|
|
24
24
|
expiresAt: json['expiresAt'] != null
|
|
25
25
|
? DateTime.tryParse(json['expiresAt'].toString()) ??
|
|
26
|
-
|
|
26
|
+
DateTime.now().add(const Duration(seconds: 30))
|
|
27
27
|
: DateTime.now().add(const Duration(seconds: 30)),
|
|
28
28
|
);
|
|
29
29
|
}
|
|
@@ -89,6 +89,14 @@ const _kCategoryInfo = <String, _CategoryInfo>{
|
|
|
89
89
|
color: Color(0xFF8E24AA),
|
|
90
90
|
riskLevel: 'high',
|
|
91
91
|
),
|
|
92
|
+
'credential_use': _CategoryInfo(
|
|
93
|
+
label: 'Credential Use',
|
|
94
|
+
subtitle:
|
|
95
|
+
'Fill approved logins or authenticate requests without showing secrets to the AI.',
|
|
96
|
+
icon: Icons.password_rounded,
|
|
97
|
+
color: Color(0xFF5E35B1),
|
|
98
|
+
riskLevel: 'high',
|
|
99
|
+
),
|
|
92
100
|
'network_write': _CategoryInfo(
|
|
93
101
|
label: 'Network Write Requests',
|
|
94
102
|
subtitle: 'Send POST / PUT / DELETE requests to external APIs.',
|
|
@@ -105,7 +113,8 @@ const _kCategoryInfo = <String, _CategoryInfo>{
|
|
|
105
113
|
),
|
|
106
114
|
'external': _CategoryInfo(
|
|
107
115
|
label: 'External & MCP Tools',
|
|
108
|
-
subtitle:
|
|
116
|
+
subtitle:
|
|
117
|
+
'Tools not built into NeoAgent, including connected MCP servers and custom tool providers.',
|
|
109
118
|
icon: Icons.hub_rounded,
|
|
110
119
|
color: Color(0xFF6D4C41),
|
|
111
120
|
riskLevel: 'high',
|
|
@@ -146,7 +155,9 @@ class _SecurityNotificationService {
|
|
|
146
155
|
if (_plugin != null) return _plugin;
|
|
147
156
|
try {
|
|
148
157
|
final plugin = FlutterLocalNotificationsPlugin();
|
|
149
|
-
final androidSettings = const AndroidInitializationSettings(
|
|
158
|
+
final androidSettings = const AndroidInitializationSettings(
|
|
159
|
+
'@mipmap/ic_launcher',
|
|
160
|
+
);
|
|
150
161
|
final darwinSettings = DarwinInitializationSettings(
|
|
151
162
|
requestAlertPermission: false,
|
|
152
163
|
requestBadgePermission: false,
|
|
@@ -156,10 +167,13 @@ class _SecurityNotificationService {
|
|
|
156
167
|
'tool_approval',
|
|
157
168
|
actions: <DarwinNotificationAction>[
|
|
158
169
|
DarwinNotificationAction.plain(_approveActionId, 'Allow'),
|
|
159
|
-
DarwinNotificationAction.plain(
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
170
|
+
DarwinNotificationAction.plain(
|
|
171
|
+
_denyActionId,
|
|
172
|
+
'Deny',
|
|
173
|
+
options: <DarwinNotificationActionOption>{
|
|
174
|
+
DarwinNotificationActionOption.destructive,
|
|
175
|
+
},
|
|
176
|
+
),
|
|
163
177
|
],
|
|
164
178
|
),
|
|
165
179
|
],
|
|
@@ -171,7 +185,8 @@ class _SecurityNotificationService {
|
|
|
171
185
|
macOS: darwinSettings,
|
|
172
186
|
),
|
|
173
187
|
onDidReceiveNotificationResponse: _onNotificationResponse,
|
|
174
|
-
onDidReceiveBackgroundNotificationResponse:
|
|
188
|
+
onDidReceiveBackgroundNotificationResponse:
|
|
189
|
+
_onBackgroundNotificationResponse,
|
|
175
190
|
);
|
|
176
191
|
_plugin = plugin;
|
|
177
192
|
return plugin;
|
|
@@ -189,7 +204,11 @@ class _SecurityNotificationService {
|
|
|
189
204
|
_handleNotificationAction(response.id, response.actionId, response.payload);
|
|
190
205
|
}
|
|
191
206
|
|
|
192
|
-
static void _handleNotificationAction(
|
|
207
|
+
static void _handleNotificationAction(
|
|
208
|
+
int? id,
|
|
209
|
+
String? actionId,
|
|
210
|
+
String? payload,
|
|
211
|
+
) {
|
|
193
212
|
// No-op in background; the app will handle it on resume via foreground listener.
|
|
194
213
|
// Foreground case is handled directly by the approval gate service.
|
|
195
214
|
}
|
|
@@ -199,10 +218,14 @@ class _SecurityNotificationService {
|
|
|
199
218
|
if (plugin == null) return;
|
|
200
219
|
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS || Platform.isMacOS)) {
|
|
201
220
|
await plugin
|
|
202
|
-
.resolvePlatformSpecificImplementation<
|
|
221
|
+
.resolvePlatformSpecificImplementation<
|
|
222
|
+
AndroidFlutterLocalNotificationsPlugin
|
|
223
|
+
>()
|
|
203
224
|
?.requestNotificationsPermission();
|
|
204
225
|
await plugin
|
|
205
|
-
.resolvePlatformSpecificImplementation<
|
|
226
|
+
.resolvePlatformSpecificImplementation<
|
|
227
|
+
IOSFlutterLocalNotificationsPlugin
|
|
228
|
+
>()
|
|
206
229
|
?.requestPermissions(alert: true, badge: false, sound: false);
|
|
207
230
|
}
|
|
208
231
|
}
|
|
@@ -237,7 +260,11 @@ class _SecurityNotificationService {
|
|
|
237
260
|
req.approvalId.hashCode.abs() % 100000,
|
|
238
261
|
'${info.label} approval needed',
|
|
239
262
|
body,
|
|
240
|
-
NotificationDetails(
|
|
263
|
+
NotificationDetails(
|
|
264
|
+
android: androidDetails,
|
|
265
|
+
iOS: darwinDetails,
|
|
266
|
+
macOS: darwinDetails,
|
|
267
|
+
),
|
|
241
268
|
payload: req.approvalId,
|
|
242
269
|
);
|
|
243
270
|
}
|
|
@@ -272,14 +299,14 @@ class _MainSecurityState extends State<MainSecurity> {
|
|
|
272
299
|
|
|
273
300
|
Future<void> _load() async {
|
|
274
301
|
try {
|
|
275
|
-
final result = await widget.controller.backendClient
|
|
276
|
-
|
|
277
|
-
);
|
|
302
|
+
final result = await widget.controller.backendClient
|
|
303
|
+
.fetchSecurityPolicies(widget.controller.backendUrl);
|
|
278
304
|
final raw = result['policies'];
|
|
279
305
|
Map<String, String> loaded = const <String, String>{};
|
|
280
306
|
if (raw is Map) {
|
|
281
307
|
loaded = Map<String, String>.from(
|
|
282
|
-
|
|
308
|
+
raw.map((k, v) => MapEntry(k.toString(), v.toString())),
|
|
309
|
+
);
|
|
283
310
|
}
|
|
284
311
|
setState(() {
|
|
285
312
|
_policies = loaded;
|
|
@@ -287,7 +314,10 @@ class _MainSecurityState extends State<MainSecurity> {
|
|
|
287
314
|
_loading = false;
|
|
288
315
|
});
|
|
289
316
|
} catch (e) {
|
|
290
|
-
setState(() {
|
|
317
|
+
setState(() {
|
|
318
|
+
_error = e.toString();
|
|
319
|
+
_loading = false;
|
|
320
|
+
});
|
|
291
321
|
}
|
|
292
322
|
}
|
|
293
323
|
|
|
@@ -296,13 +326,17 @@ class _MainSecurityState extends State<MainSecurity> {
|
|
|
296
326
|
setState(() => _mode = mode);
|
|
297
327
|
try {
|
|
298
328
|
await widget.controller.backendClient.saveSecurityMode(
|
|
299
|
-
widget.controller.backendUrl,
|
|
329
|
+
widget.controller.backendUrl,
|
|
330
|
+
mode,
|
|
300
331
|
);
|
|
301
332
|
} catch (e) {
|
|
302
333
|
setState(() => _mode = prev);
|
|
303
334
|
if (mounted) {
|
|
304
335
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
305
|
-
SnackBar(
|
|
336
|
+
SnackBar(
|
|
337
|
+
content: Text('Failed to save: $e'),
|
|
338
|
+
backgroundColor: _danger,
|
|
339
|
+
),
|
|
306
340
|
);
|
|
307
341
|
}
|
|
308
342
|
}
|
|
@@ -313,13 +347,20 @@ class _MainSecurityState extends State<MainSecurity> {
|
|
|
313
347
|
setState(() => _policies = {..._policies, category: policy});
|
|
314
348
|
try {
|
|
315
349
|
await widget.controller.backendClient.saveSecurityPolicy(
|
|
316
|
-
widget.controller.backendUrl,
|
|
350
|
+
widget.controller.backendUrl,
|
|
351
|
+
category: category,
|
|
352
|
+
policy: policy,
|
|
317
353
|
);
|
|
318
354
|
} catch (e) {
|
|
319
|
-
setState(
|
|
355
|
+
setState(
|
|
356
|
+
() => _policies = {..._policies, category: prev ?? 'require_approval'},
|
|
357
|
+
);
|
|
320
358
|
if (mounted) {
|
|
321
359
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
322
|
-
SnackBar(
|
|
360
|
+
SnackBar(
|
|
361
|
+
content: Text('Failed to save: $e'),
|
|
362
|
+
backgroundColor: _danger,
|
|
363
|
+
),
|
|
323
364
|
);
|
|
324
365
|
}
|
|
325
366
|
}
|
|
@@ -342,46 +383,50 @@ class _MainSecurityState extends State<MainSecurity> {
|
|
|
342
383
|
body: _loading
|
|
343
384
|
? const Center(child: CircularProgressIndicator.adaptive())
|
|
344
385
|
: _error != null
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
386
|
+
? _ErrorView(error: _error!, onRetry: _load)
|
|
387
|
+
: ListView(
|
|
388
|
+
padding: const EdgeInsets.fromLTRB(16, 8, 16, 32),
|
|
389
|
+
children: <Widget>[
|
|
390
|
+
_GlobalModeCard(mode: _mode, onChanged: _setMode),
|
|
391
|
+
const SizedBox(height: 16),
|
|
392
|
+
if (_mode == 'allow_all')
|
|
393
|
+
_InfoBanner(
|
|
394
|
+
icon: Icons.warning_amber_rounded,
|
|
395
|
+
color: colorScheme.errorContainer,
|
|
396
|
+
textColor: colorScheme.onErrorContainer,
|
|
397
|
+
message:
|
|
398
|
+
'All tools are allowed — the agent can use any capability without asking. '
|
|
399
|
+
'Switch to "Default" or "Always ask" to re-enable approval checks.',
|
|
400
|
+
)
|
|
401
|
+
else ...<Widget>[
|
|
402
|
+
if (_mode == 'always_ask')
|
|
403
|
+
_InfoBanner(
|
|
404
|
+
icon: Icons.info_outline_rounded,
|
|
405
|
+
color: colorScheme.secondaryContainer,
|
|
406
|
+
textColor: colorScheme.onSecondaryContainer,
|
|
407
|
+
message:
|
|
408
|
+
'The agent will ask before every sensitive tool, '
|
|
409
|
+
'regardless of per-category settings below.',
|
|
410
|
+
),
|
|
411
|
+
const SizedBox(height: 4),
|
|
412
|
+
const Padding(
|
|
413
|
+
padding: EdgeInsets.only(top: 4, bottom: 6),
|
|
414
|
+
child: _SectionTitle('Per-category permissions'),
|
|
415
|
+
),
|
|
416
|
+
..._policies.entries.map(
|
|
417
|
+
(e) => Padding(
|
|
418
|
+
padding: const EdgeInsets.only(bottom: 8),
|
|
419
|
+
child: _PolicyCard(
|
|
420
|
+
category: e.key,
|
|
421
|
+
policy: e.value,
|
|
422
|
+
dimmed: _mode == 'always_ask',
|
|
423
|
+
onChanged: (p) => _setPolicy(e.key, p),
|
|
372
424
|
),
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
dimmed: _mode == 'always_ask',
|
|
379
|
-
onChanged: (p) => _setPolicy(e.key, p),
|
|
380
|
-
),
|
|
381
|
-
)),
|
|
382
|
-
],
|
|
383
|
-
],
|
|
384
|
-
),
|
|
425
|
+
),
|
|
426
|
+
),
|
|
427
|
+
],
|
|
428
|
+
],
|
|
429
|
+
),
|
|
385
430
|
);
|
|
386
431
|
}
|
|
387
432
|
}
|
|
@@ -399,11 +444,22 @@ class _ErrorView extends StatelessWidget {
|
|
|
399
444
|
children: <Widget>[
|
|
400
445
|
Icon(Icons.error_outline, size: 48, color: _danger),
|
|
401
446
|
const SizedBox(height: 12),
|
|
402
|
-
Text(
|
|
447
|
+
Text(
|
|
448
|
+
'Failed to load policies',
|
|
449
|
+
style: Theme.of(context).textTheme.titleMedium,
|
|
450
|
+
),
|
|
403
451
|
const SizedBox(height: 6),
|
|
404
|
-
Text(
|
|
452
|
+
Text(
|
|
453
|
+
error,
|
|
454
|
+
style: TextStyle(fontSize: 12, color: _textSecondary),
|
|
455
|
+
textAlign: TextAlign.center,
|
|
456
|
+
),
|
|
405
457
|
const SizedBox(height: 16),
|
|
406
|
-
OutlinedButton.icon(
|
|
458
|
+
OutlinedButton.icon(
|
|
459
|
+
onPressed: onRetry,
|
|
460
|
+
icon: const Icon(Icons.refresh),
|
|
461
|
+
label: const Text('Retry'),
|
|
462
|
+
),
|
|
407
463
|
],
|
|
408
464
|
),
|
|
409
465
|
);
|
|
@@ -427,13 +483,21 @@ class _InfoBanner extends StatelessWidget {
|
|
|
427
483
|
return Container(
|
|
428
484
|
margin: const EdgeInsets.only(bottom: 8),
|
|
429
485
|
padding: const EdgeInsets.all(12),
|
|
430
|
-
decoration: BoxDecoration(
|
|
486
|
+
decoration: BoxDecoration(
|
|
487
|
+
color: color,
|
|
488
|
+
borderRadius: BorderRadius.circular(12),
|
|
489
|
+
),
|
|
431
490
|
child: Row(
|
|
432
491
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
433
492
|
children: <Widget>[
|
|
434
493
|
Icon(icon, color: textColor, size: 18),
|
|
435
494
|
const SizedBox(width: 10),
|
|
436
|
-
Expanded(
|
|
495
|
+
Expanded(
|
|
496
|
+
child: Text(
|
|
497
|
+
message,
|
|
498
|
+
style: TextStyle(color: textColor, fontSize: 13),
|
|
499
|
+
),
|
|
500
|
+
),
|
|
437
501
|
],
|
|
438
502
|
),
|
|
439
503
|
);
|
|
@@ -463,7 +527,10 @@ class _GlobalModeCard extends StatelessWidget {
|
|
|
463
527
|
children: <Widget>[
|
|
464
528
|
Icon(Icons.tune_rounded, size: 18, color: _accent),
|
|
465
529
|
const SizedBox(width: 8),
|
|
466
|
-
const Text(
|
|
530
|
+
const Text(
|
|
531
|
+
'Global security mode',
|
|
532
|
+
style: TextStyle(fontWeight: FontWeight.w700, fontSize: 14),
|
|
533
|
+
),
|
|
467
534
|
],
|
|
468
535
|
),
|
|
469
536
|
const SizedBox(height: 12),
|
|
@@ -471,7 +538,8 @@ class _GlobalModeCard extends StatelessWidget {
|
|
|
471
538
|
value: 'allow_all',
|
|
472
539
|
current: mode,
|
|
473
540
|
label: 'Allow all',
|
|
474
|
-
subtitle:
|
|
541
|
+
subtitle:
|
|
542
|
+
'No approval prompts — agent runs without interruption.',
|
|
475
543
|
icon: Icons.lock_open_rounded,
|
|
476
544
|
color: _warning,
|
|
477
545
|
onTap: () => onChanged('allow_all'),
|
|
@@ -545,13 +613,18 @@ class _ModeOption extends StatelessWidget {
|
|
|
545
613
|
child: Column(
|
|
546
614
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
547
615
|
children: <Widget>[
|
|
548
|
-
Text(
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
616
|
+
Text(
|
|
617
|
+
label,
|
|
618
|
+
style: TextStyle(
|
|
619
|
+
fontWeight: selected ? FontWeight.w700 : FontWeight.w500,
|
|
620
|
+
fontSize: 13,
|
|
621
|
+
color: selected ? color : null,
|
|
622
|
+
),
|
|
623
|
+
),
|
|
624
|
+
Text(
|
|
625
|
+
subtitle,
|
|
626
|
+
style: TextStyle(fontSize: 11, color: _textSecondary),
|
|
627
|
+
),
|
|
555
628
|
],
|
|
556
629
|
),
|
|
557
630
|
),
|
|
@@ -609,20 +682,37 @@ class _PolicyCard extends StatelessWidget {
|
|
|
609
682
|
child: Column(
|
|
610
683
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
611
684
|
children: <Widget>[
|
|
612
|
-
Text(
|
|
613
|
-
|
|
685
|
+
Text(
|
|
686
|
+
info.label,
|
|
687
|
+
style: const TextStyle(
|
|
688
|
+
fontWeight: FontWeight.w600,
|
|
689
|
+
fontSize: 13,
|
|
690
|
+
),
|
|
691
|
+
),
|
|
692
|
+
Text(
|
|
693
|
+
info.subtitle,
|
|
694
|
+
style: TextStyle(fontSize: 11, color: _textSecondary),
|
|
695
|
+
),
|
|
614
696
|
],
|
|
615
697
|
),
|
|
616
698
|
),
|
|
617
699
|
Container(
|
|
618
|
-
padding: const EdgeInsets.symmetric(
|
|
700
|
+
padding: const EdgeInsets.symmetric(
|
|
701
|
+
horizontal: 7,
|
|
702
|
+
vertical: 3,
|
|
703
|
+
),
|
|
619
704
|
decoration: BoxDecoration(
|
|
620
705
|
color: riskColor.withAlpha(20),
|
|
621
706
|
borderRadius: BorderRadius.circular(20),
|
|
622
707
|
),
|
|
623
708
|
child: Text(
|
|
624
709
|
info.riskLevel.toUpperCase(),
|
|
625
|
-
style: TextStyle(
|
|
710
|
+
style: TextStyle(
|
|
711
|
+
fontSize: 9,
|
|
712
|
+
fontWeight: FontWeight.w700,
|
|
713
|
+
color: riskColor,
|
|
714
|
+
letterSpacing: 0.5,
|
|
715
|
+
),
|
|
626
716
|
),
|
|
627
717
|
),
|
|
628
718
|
],
|
|
@@ -630,16 +720,34 @@ class _PolicyCard extends StatelessWidget {
|
|
|
630
720
|
const SizedBox(height: 12),
|
|
631
721
|
SegmentedButton<String>(
|
|
632
722
|
segments: const <ButtonSegment<String>>[
|
|
633
|
-
ButtonSegment<String>(
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
723
|
+
ButtonSegment<String>(
|
|
724
|
+
value: 'deny',
|
|
725
|
+
label: Text('Block'),
|
|
726
|
+
icon: Icon(Icons.block_rounded, size: 13),
|
|
727
|
+
),
|
|
728
|
+
ButtonSegment<String>(
|
|
729
|
+
value: 'require_approval',
|
|
730
|
+
label: Text('Ask me'),
|
|
731
|
+
icon: Icon(Icons.pan_tool_outlined, size: 13),
|
|
732
|
+
),
|
|
733
|
+
ButtonSegment<String>(
|
|
734
|
+
value: 'allow',
|
|
735
|
+
label: Text('Allow'),
|
|
736
|
+
icon: Icon(Icons.check_rounded, size: 13),
|
|
737
|
+
),
|
|
738
|
+
ButtonSegment<String>(
|
|
739
|
+
value: 'allow_always',
|
|
740
|
+
label: Text('Always'),
|
|
741
|
+
icon: Icon(Icons.verified_rounded, size: 13),
|
|
742
|
+
),
|
|
637
743
|
],
|
|
638
744
|
selected: <String>{policy},
|
|
639
745
|
onSelectionChanged: dimmed ? null : (s) => onChanged(s.first),
|
|
640
746
|
style: ButtonStyle(
|
|
641
747
|
visualDensity: VisualDensity.compact,
|
|
642
|
-
textStyle: WidgetStateProperty.all(
|
|
748
|
+
textStyle: WidgetStateProperty.all(
|
|
749
|
+
const TextStyle(fontSize: 11),
|
|
750
|
+
),
|
|
643
751
|
),
|
|
644
752
|
),
|
|
645
753
|
const SizedBox(height: 6),
|
|
@@ -659,9 +767,18 @@ class _PolicyHint extends StatelessWidget {
|
|
|
659
767
|
@override
|
|
660
768
|
Widget build(BuildContext context) {
|
|
661
769
|
final (text, color) = switch (policy) {
|
|
662
|
-
'deny' => (
|
|
663
|
-
|
|
664
|
-
|
|
770
|
+
'deny' => (
|
|
771
|
+
'Completely blocked — the agent cannot use this category.',
|
|
772
|
+
_danger,
|
|
773
|
+
),
|
|
774
|
+
'require_approval' => (
|
|
775
|
+
'Agent pauses and asks you before running.',
|
|
776
|
+
_warning,
|
|
777
|
+
),
|
|
778
|
+
'allow' => (
|
|
779
|
+
'Allowed for this run — will ask again next session.',
|
|
780
|
+
_accentAlt,
|
|
781
|
+
),
|
|
665
782
|
'allow_always' => ('Permanently allowed — never asks again.', _info),
|
|
666
783
|
_ => ('', _textSecondary),
|
|
667
784
|
};
|
|
@@ -713,7 +830,9 @@ class _ToolApprovalSheetState extends State<ToolApprovalSheet>
|
|
|
713
830
|
});
|
|
714
831
|
|
|
715
832
|
// Cancel the notification now that the sheet is showing
|
|
716
|
-
_SecurityNotificationService.cancelApprovalNotification(
|
|
833
|
+
_SecurityNotificationService.cancelApprovalNotification(
|
|
834
|
+
widget.request.approvalId,
|
|
835
|
+
);
|
|
717
836
|
}
|
|
718
837
|
|
|
719
838
|
@override
|
|
@@ -744,10 +863,7 @@ class _ToolApprovalSheetState extends State<ToolApprovalSheet>
|
|
|
744
863
|
widget.controller.clearPendingApproval();
|
|
745
864
|
if (mounted) {
|
|
746
865
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
747
|
-
SnackBar(
|
|
748
|
-
content: Text(error.message),
|
|
749
|
-
backgroundColor: _warning,
|
|
750
|
-
),
|
|
866
|
+
SnackBar(content: Text(error.message), backgroundColor: _warning),
|
|
751
867
|
);
|
|
752
868
|
}
|
|
753
869
|
}
|
|
@@ -769,7 +885,15 @@ class _ToolApprovalSheetState extends State<ToolApprovalSheet>
|
|
|
769
885
|
}
|
|
770
886
|
|
|
771
887
|
String _redact(String key, dynamic value) {
|
|
772
|
-
const sensitive = <String>[
|
|
888
|
+
const sensitive = <String>[
|
|
889
|
+
'token',
|
|
890
|
+
'secret',
|
|
891
|
+
'password',
|
|
892
|
+
'key',
|
|
893
|
+
'api_key',
|
|
894
|
+
'auth',
|
|
895
|
+
'credential',
|
|
896
|
+
];
|
|
773
897
|
if (sensitive.any((s) => key.toLowerCase().contains(s))) return '••••••';
|
|
774
898
|
return value?.toString() ?? 'null';
|
|
775
899
|
}
|
|
@@ -793,7 +917,8 @@ class _ToolApprovalSheetState extends State<ToolApprovalSheet>
|
|
|
793
917
|
Center(
|
|
794
918
|
child: Container(
|
|
795
919
|
margin: const EdgeInsets.only(top: 10, bottom: 6),
|
|
796
|
-
width: 38,
|
|
920
|
+
width: 38,
|
|
921
|
+
height: 4,
|
|
797
922
|
decoration: BoxDecoration(
|
|
798
923
|
color: colorScheme.outlineVariant,
|
|
799
924
|
borderRadius: BorderRadius.circular(2),
|
|
@@ -807,7 +932,8 @@ class _ToolApprovalSheetState extends State<ToolApprovalSheet>
|
|
|
807
932
|
children: <Widget>[
|
|
808
933
|
// Countdown ring
|
|
809
934
|
SizedBox(
|
|
810
|
-
width: 52,
|
|
935
|
+
width: 52,
|
|
936
|
+
height: 52,
|
|
811
937
|
child: Stack(
|
|
812
938
|
alignment: Alignment.center,
|
|
813
939
|
children: <Widget>[
|
|
@@ -816,7 +942,8 @@ class _ToolApprovalSheetState extends State<ToolApprovalSheet>
|
|
|
816
942
|
builder: (_, __) => CircularProgressIndicator(
|
|
817
943
|
value: 1 - _ringController.value,
|
|
818
944
|
strokeWidth: 3.5,
|
|
819
|
-
backgroundColor:
|
|
945
|
+
backgroundColor:
|
|
946
|
+
colorScheme.surfaceContainerHighest,
|
|
820
947
|
color: ringColor,
|
|
821
948
|
),
|
|
822
949
|
),
|
|
@@ -837,8 +964,13 @@ class _ToolApprovalSheetState extends State<ToolApprovalSheet>
|
|
|
837
964
|
child: Column(
|
|
838
965
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
839
966
|
children: <Widget>[
|
|
840
|
-
const Text(
|
|
841
|
-
|
|
967
|
+
const Text(
|
|
968
|
+
'Approval required',
|
|
969
|
+
style: TextStyle(
|
|
970
|
+
fontWeight: FontWeight.w700,
|
|
971
|
+
fontSize: 16,
|
|
972
|
+
),
|
|
973
|
+
),
|
|
842
974
|
const SizedBox(height: 4),
|
|
843
975
|
Row(
|
|
844
976
|
children: <Widget>[
|
|
@@ -848,15 +980,21 @@ class _ToolApprovalSheetState extends State<ToolApprovalSheet>
|
|
|
848
980
|
color: info.color.withAlpha(22),
|
|
849
981
|
borderRadius: BorderRadius.circular(6),
|
|
850
982
|
),
|
|
851
|
-
child: Icon(
|
|
983
|
+
child: Icon(
|
|
984
|
+
info.icon,
|
|
985
|
+
size: 13,
|
|
986
|
+
color: info.color,
|
|
987
|
+
),
|
|
852
988
|
),
|
|
853
989
|
const SizedBox(width: 6),
|
|
854
|
-
Text(
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
990
|
+
Text(
|
|
991
|
+
info.label,
|
|
992
|
+
style: TextStyle(
|
|
993
|
+
fontSize: 12,
|
|
994
|
+
color: info.color,
|
|
995
|
+
fontWeight: FontWeight.w600,
|
|
996
|
+
),
|
|
997
|
+
),
|
|
860
998
|
],
|
|
861
999
|
),
|
|
862
1000
|
],
|
|
@@ -882,8 +1020,12 @@ class _ToolApprovalSheetState extends State<ToolApprovalSheet>
|
|
|
882
1020
|
Row(
|
|
883
1021
|
children: <Widget>[
|
|
884
1022
|
Container(
|
|
885
|
-
width: 7,
|
|
886
|
-
|
|
1023
|
+
width: 7,
|
|
1024
|
+
height: 7,
|
|
1025
|
+
decoration: BoxDecoration(
|
|
1026
|
+
color: info.color,
|
|
1027
|
+
shape: BoxShape.circle,
|
|
1028
|
+
),
|
|
887
1029
|
),
|
|
888
1030
|
const SizedBox(width: 6),
|
|
889
1031
|
Text(
|
|
@@ -932,7 +1074,9 @@ class _ToolApprovalSheetState extends State<ToolApprovalSheet>
|
|
|
932
1074
|
label: const Text('Deny'),
|
|
933
1075
|
style: OutlinedButton.styleFrom(
|
|
934
1076
|
foregroundColor: colorScheme.error,
|
|
935
|
-
side: BorderSide(
|
|
1077
|
+
side: BorderSide(
|
|
1078
|
+
color: colorScheme.error.withAlpha(100),
|
|
1079
|
+
),
|
|
936
1080
|
),
|
|
937
1081
|
onPressed: () => _decide('denied', 'once'),
|
|
938
1082
|
),
|
|
@@ -940,7 +1084,10 @@ class _ToolApprovalSheetState extends State<ToolApprovalSheet>
|
|
|
940
1084
|
const SizedBox(width: 8),
|
|
941
1085
|
Expanded(
|
|
942
1086
|
child: OutlinedButton.icon(
|
|
943
|
-
icon: const Icon(
|
|
1087
|
+
icon: const Icon(
|
|
1088
|
+
Icons.check_circle_outline,
|
|
1089
|
+
size: 15,
|
|
1090
|
+
),
|
|
944
1091
|
label: const Text('Allow once'),
|
|
945
1092
|
onPressed: () => _decide('approved', 'once'),
|
|
946
1093
|
),
|
|
@@ -954,7 +1101,9 @@ class _ToolApprovalSheetState extends State<ToolApprovalSheet>
|
|
|
954
1101
|
child: OutlinedButton.icon(
|
|
955
1102
|
icon: const Icon(Icons.history_rounded, size: 15),
|
|
956
1103
|
label: const Text('Allow session'),
|
|
957
|
-
style: OutlinedButton.styleFrom(
|
|
1104
|
+
style: OutlinedButton.styleFrom(
|
|
1105
|
+
foregroundColor: _info,
|
|
1106
|
+
),
|
|
958
1107
|
onPressed: () => _decide('approved', 'session'),
|
|
959
1108
|
),
|
|
960
1109
|
),
|
|
@@ -963,7 +1112,9 @@ class _ToolApprovalSheetState extends State<ToolApprovalSheet>
|
|
|
963
1112
|
child: FilledButton.icon(
|
|
964
1113
|
icon: const Icon(Icons.verified_rounded, size: 15),
|
|
965
1114
|
label: const Text('Always allow'),
|
|
966
|
-
style: FilledButton.styleFrom(
|
|
1115
|
+
style: FilledButton.styleFrom(
|
|
1116
|
+
backgroundColor: info.color,
|
|
1117
|
+
),
|
|
967
1118
|
onPressed: () => _decide('approved', 'always'),
|
|
968
1119
|
),
|
|
969
1120
|
),
|
|
@@ -973,7 +1124,10 @@ class _ToolApprovalSheetState extends State<ToolApprovalSheet>
|
|
|
973
1124
|
Text(
|
|
974
1125
|
'"Always allow" saves the policy permanently — you can change it in Settings.',
|
|
975
1126
|
textAlign: TextAlign.center,
|
|
976
|
-
style: TextStyle(
|
|
1127
|
+
style: TextStyle(
|
|
1128
|
+
fontSize: 10,
|
|
1129
|
+
color: colorScheme.onSurfaceVariant,
|
|
1130
|
+
),
|
|
977
1131
|
),
|
|
978
1132
|
],
|
|
979
1133
|
),
|
|
@@ -343,10 +343,11 @@ class _SettingsPanelState extends State<SettingsPanel> {
|
|
|
343
343
|
onPopInvokedWithResult: (didPop, result) async {
|
|
344
344
|
if (didPop) return;
|
|
345
345
|
final action = await _showLeaveDialog(context);
|
|
346
|
-
if (
|
|
346
|
+
if (!context.mounted) return;
|
|
347
|
+
if (action == _LeaveAction.save) {
|
|
347
348
|
await _doSave();
|
|
348
|
-
if (mounted) Navigator.of(context).pop();
|
|
349
|
-
} else if (action == _LeaveAction.discard
|
|
349
|
+
if (context.mounted) Navigator.of(context).pop();
|
|
350
|
+
} else if (action == _LeaveAction.discard) {
|
|
350
351
|
_hydrate();
|
|
351
352
|
setState(() => _hasUnsavedChanges = false);
|
|
352
353
|
Navigator.of(context).pop();
|