neoagent 2.4.4-beta.6 → 2.4.4-beta.8
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/flutter_app/lib/main_security.dart +27 -31
- package/flutter_app/lib/main_settings.dart +30 -35
- package/lib/schema_migrations.js +27 -2
- package/package.json +1 -1
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +15617 -15621
- package/server/services/integrations/google/gmail.js +7 -7
|
@@ -111,17 +111,17 @@ _CategoryInfo _categoryInfo(String category) {
|
|
|
111
111
|
label: category,
|
|
112
112
|
subtitle: 'Controls access to $category tools.',
|
|
113
113
|
icon: Icons.lock_outline,
|
|
114
|
-
color:
|
|
114
|
+
color: const Color(0xFF888888),
|
|
115
115
|
riskLevel: 'medium',
|
|
116
116
|
);
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
Color _riskColor(String level) {
|
|
120
120
|
return switch (level) {
|
|
121
|
-
'critical' =>
|
|
122
|
-
'high' =>
|
|
123
|
-
'medium' =>
|
|
124
|
-
_ =>
|
|
121
|
+
'critical' => _danger,
|
|
122
|
+
'high' => _warning,
|
|
123
|
+
'medium' => _accent,
|
|
124
|
+
_ => _textSecondary,
|
|
125
125
|
};
|
|
126
126
|
}
|
|
127
127
|
|
|
@@ -295,7 +295,7 @@ class _MainSecurityState extends State<MainSecurity> {
|
|
|
295
295
|
setState(() => _mode = prev);
|
|
296
296
|
if (mounted) {
|
|
297
297
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
298
|
-
SnackBar(content: Text('Failed to save: $e'), backgroundColor:
|
|
298
|
+
SnackBar(content: Text('Failed to save: $e'), backgroundColor: _danger),
|
|
299
299
|
);
|
|
300
300
|
}
|
|
301
301
|
}
|
|
@@ -312,7 +312,7 @@ class _MainSecurityState extends State<MainSecurity> {
|
|
|
312
312
|
setState(() => _policies = {..._policies, category: prev ?? 'require_approval'});
|
|
313
313
|
if (mounted) {
|
|
314
314
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
315
|
-
SnackBar(content: Text('Failed to save: $e'), backgroundColor:
|
|
315
|
+
SnackBar(content: Text('Failed to save: $e'), backgroundColor: _danger),
|
|
316
316
|
);
|
|
317
317
|
}
|
|
318
318
|
}
|
|
@@ -360,11 +360,8 @@ class _MainSecurityState extends State<MainSecurity> {
|
|
|
360
360
|
),
|
|
361
361
|
const SizedBox(height: 4),
|
|
362
362
|
const Padding(
|
|
363
|
-
padding: EdgeInsets.
|
|
364
|
-
child:
|
|
365
|
-
'Per-category permissions',
|
|
366
|
-
style: TextStyle(fontWeight: FontWeight.w700, fontSize: 13),
|
|
367
|
-
),
|
|
363
|
+
padding: EdgeInsets.only(top: 4, bottom: 6),
|
|
364
|
+
child: _SectionTitle('Per-category permissions'),
|
|
368
365
|
),
|
|
369
366
|
..._policies.entries.map((e) => Padding(
|
|
370
367
|
padding: const EdgeInsets.only(bottom: 8),
|
|
@@ -393,11 +390,11 @@ class _ErrorView extends StatelessWidget {
|
|
|
393
390
|
child: Column(
|
|
394
391
|
mainAxisSize: MainAxisSize.min,
|
|
395
392
|
children: <Widget>[
|
|
396
|
-
|
|
393
|
+
Icon(Icons.error_outline, size: 48, color: _danger),
|
|
397
394
|
const SizedBox(height: 12),
|
|
398
395
|
Text('Failed to load policies', style: Theme.of(context).textTheme.titleMedium),
|
|
399
396
|
const SizedBox(height: 6),
|
|
400
|
-
Text(error, style:
|
|
397
|
+
Text(error, style: TextStyle(fontSize: 12, color: _textSecondary), textAlign: TextAlign.center),
|
|
401
398
|
const SizedBox(height: 16),
|
|
402
399
|
OutlinedButton.icon(onPressed: onRetry, icon: const Icon(Icons.refresh), label: const Text('Retry')),
|
|
403
400
|
],
|
|
@@ -447,7 +444,7 @@ class _GlobalModeCard extends StatelessWidget {
|
|
|
447
444
|
return Card(
|
|
448
445
|
elevation: 0,
|
|
449
446
|
shape: RoundedRectangleBorder(
|
|
450
|
-
borderRadius: BorderRadius.circular(
|
|
447
|
+
borderRadius: BorderRadius.circular(12),
|
|
451
448
|
side: BorderSide(color: colorScheme.outlineVariant),
|
|
452
449
|
),
|
|
453
450
|
child: Padding(
|
|
@@ -457,7 +454,7 @@ class _GlobalModeCard extends StatelessWidget {
|
|
|
457
454
|
children: <Widget>[
|
|
458
455
|
Row(
|
|
459
456
|
children: <Widget>[
|
|
460
|
-
Icon(Icons.tune_rounded, size: 18, color:
|
|
457
|
+
Icon(Icons.tune_rounded, size: 18, color: _accent),
|
|
461
458
|
const SizedBox(width: 8),
|
|
462
459
|
const Text('Global security mode', style: TextStyle(fontWeight: FontWeight.w700, fontSize: 14)),
|
|
463
460
|
],
|
|
@@ -469,7 +466,7 @@ class _GlobalModeCard extends StatelessWidget {
|
|
|
469
466
|
label: 'Allow all',
|
|
470
467
|
subtitle: 'No approval prompts — agent runs without interruption.',
|
|
471
468
|
icon: Icons.lock_open_rounded,
|
|
472
|
-
color:
|
|
469
|
+
color: _warning,
|
|
473
470
|
onTap: () => onChanged('allow_all'),
|
|
474
471
|
),
|
|
475
472
|
const SizedBox(height: 6),
|
|
@@ -479,7 +476,7 @@ class _GlobalModeCard extends StatelessWidget {
|
|
|
479
476
|
label: 'Default (recommended)',
|
|
480
477
|
subtitle: 'Use per-category settings below.',
|
|
481
478
|
icon: Icons.shield_outlined,
|
|
482
|
-
color:
|
|
479
|
+
color: _accentAlt,
|
|
483
480
|
onTap: () => onChanged('default'),
|
|
484
481
|
),
|
|
485
482
|
const SizedBox(height: 6),
|
|
@@ -489,7 +486,7 @@ class _GlobalModeCard extends StatelessWidget {
|
|
|
489
486
|
label: 'Always ask',
|
|
490
487
|
subtitle: 'Every sensitive tool requires approval, every time.',
|
|
491
488
|
icon: Icons.pan_tool_outlined,
|
|
492
|
-
color:
|
|
489
|
+
color: _info,
|
|
493
490
|
onTap: () => onChanged('always_ask'),
|
|
494
491
|
),
|
|
495
492
|
],
|
|
@@ -520,23 +517,22 @@ class _ModeOption extends StatelessWidget {
|
|
|
520
517
|
@override
|
|
521
518
|
Widget build(BuildContext context) {
|
|
522
519
|
final selected = value == current;
|
|
523
|
-
final colorScheme = Theme.of(context).colorScheme;
|
|
524
520
|
return GestureDetector(
|
|
525
521
|
onTap: onTap,
|
|
526
522
|
child: AnimatedContainer(
|
|
527
523
|
duration: const Duration(milliseconds: 150),
|
|
528
524
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
|
529
525
|
decoration: BoxDecoration(
|
|
530
|
-
color: selected ? color.withAlpha(
|
|
526
|
+
color: selected ? color.withAlpha(24) : Colors.transparent,
|
|
531
527
|
borderRadius: BorderRadius.circular(10),
|
|
532
528
|
border: Border.all(
|
|
533
|
-
color: selected ? color :
|
|
529
|
+
color: selected ? color : _border,
|
|
534
530
|
width: selected ? 1.5 : 1,
|
|
535
531
|
),
|
|
536
532
|
),
|
|
537
533
|
child: Row(
|
|
538
534
|
children: <Widget>[
|
|
539
|
-
Icon(icon, size: 18, color: selected ? color :
|
|
535
|
+
Icon(icon, size: 18, color: selected ? color : _textSecondary),
|
|
540
536
|
const SizedBox(width: 10),
|
|
541
537
|
Expanded(
|
|
542
538
|
child: Column(
|
|
@@ -548,7 +544,7 @@ class _ModeOption extends StatelessWidget {
|
|
|
548
544
|
fontSize: 13,
|
|
549
545
|
color: selected ? color : null,
|
|
550
546
|
)),
|
|
551
|
-
Text(subtitle, style:
|
|
547
|
+
Text(subtitle, style: TextStyle(fontSize: 11, color: _textSecondary)),
|
|
552
548
|
],
|
|
553
549
|
),
|
|
554
550
|
),
|
|
@@ -607,7 +603,7 @@ class _PolicyCard extends StatelessWidget {
|
|
|
607
603
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
608
604
|
children: <Widget>[
|
|
609
605
|
Text(info.label, style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 13)),
|
|
610
|
-
Text(info.subtitle, style:
|
|
606
|
+
Text(info.subtitle, style: TextStyle(fontSize: 11, color: _textSecondary)),
|
|
611
607
|
],
|
|
612
608
|
),
|
|
613
609
|
),
|
|
@@ -656,11 +652,11 @@ class _PolicyHint extends StatelessWidget {
|
|
|
656
652
|
@override
|
|
657
653
|
Widget build(BuildContext context) {
|
|
658
654
|
final (text, color) = switch (policy) {
|
|
659
|
-
'deny' => ('Completely blocked — the agent cannot use this category.',
|
|
660
|
-
'require_approval' => ('Agent pauses and asks you before running.',
|
|
661
|
-
'allow' => ('Allowed for this run — will ask again next session.',
|
|
662
|
-
'allow_always' => ('Permanently allowed — never asks again.',
|
|
663
|
-
_ => ('',
|
|
655
|
+
'deny' => ('Completely blocked — the agent cannot use this category.', _danger),
|
|
656
|
+
'require_approval' => ('Agent pauses and asks you before running.', _warning),
|
|
657
|
+
'allow' => ('Allowed for this run — will ask again next session.', _accentAlt),
|
|
658
|
+
'allow_always' => ('Permanently allowed — never asks again.', _info),
|
|
659
|
+
_ => ('', _textSecondary),
|
|
664
660
|
};
|
|
665
661
|
if (text.isEmpty) return const SizedBox.shrink();
|
|
666
662
|
return Text(text, style: TextStyle(fontSize: 11, color: color));
|
|
@@ -939,7 +935,7 @@ class _ToolApprovalSheetState extends State<ToolApprovalSheet>
|
|
|
939
935
|
child: OutlinedButton.icon(
|
|
940
936
|
icon: const Icon(Icons.history_rounded, size: 15),
|
|
941
937
|
label: const Text('Allow session'),
|
|
942
|
-
style: OutlinedButton.styleFrom(foregroundColor:
|
|
938
|
+
style: OutlinedButton.styleFrom(foregroundColor: _info),
|
|
943
939
|
onPressed: () => _decide('approved', 'session'),
|
|
944
940
|
),
|
|
945
941
|
),
|
|
@@ -1513,42 +1513,37 @@ class _SettingsPanelState extends State<SettingsPanel> {
|
|
|
1513
1513
|
|
|
1514
1514
|
Widget _buildSecuritySection(BuildContext context, NeoAgentController controller) {
|
|
1515
1515
|
return Card(
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
title: const Text('Security', style: TextStyle(fontWeight: FontWeight.bold)),
|
|
1527
|
-
subtitle: const Text('Tool allowlists, approval gates, and process isolation.'),
|
|
1528
|
-
trailing: const SizedBox.shrink(),
|
|
1529
|
-
),
|
|
1530
|
-
const Divider(height: 1),
|
|
1531
|
-
ListTile(
|
|
1532
|
-
leading: const Icon(Icons.checklist_outlined),
|
|
1533
|
-
title: const Text('Tool Permissions'),
|
|
1534
|
-
subtitle: const Text('Set block / ask / allow per tool category, or pick a global mode.'),
|
|
1535
|
-
trailing: Row(
|
|
1536
|
-
mainAxisSize: MainAxisSize.min,
|
|
1537
|
-
children: const <Widget>[
|
|
1538
|
-
Icon(Icons.shield_rounded, size: 16, color: Colors.green),
|
|
1539
|
-
SizedBox(width: 4),
|
|
1540
|
-
Icon(Icons.chevron_right),
|
|
1541
|
-
],
|
|
1516
|
+
child: Padding(
|
|
1517
|
+
padding: const EdgeInsets.all(20),
|
|
1518
|
+
child: Column(
|
|
1519
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
1520
|
+
children: <Widget>[
|
|
1521
|
+
const _SectionTitle('Security'),
|
|
1522
|
+
const SizedBox(height: 10),
|
|
1523
|
+
Text(
|
|
1524
|
+
'Per-tool permission policies, approval gates, and process isolation for shell execution.',
|
|
1525
|
+
style: TextStyle(color: _textSecondary, height: 1.45),
|
|
1542
1526
|
),
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1527
|
+
const SizedBox(height: 8),
|
|
1528
|
+
ListTile(
|
|
1529
|
+
contentPadding: EdgeInsets.zero,
|
|
1530
|
+
leading: Icon(Icons.checklist_outlined, color: _accentAlt),
|
|
1531
|
+
title: const Text('Tool Permissions'),
|
|
1532
|
+
subtitle: Text(
|
|
1533
|
+
'Set block / ask / allow per tool category, or pick a global mode.',
|
|
1534
|
+
style: TextStyle(color: _textSecondary),
|
|
1535
|
+
),
|
|
1536
|
+
trailing: const Icon(Icons.chevron_right),
|
|
1537
|
+
onTap: () {
|
|
1538
|
+
Navigator.of(context).push(
|
|
1539
|
+
MaterialPageRoute<void>(
|
|
1540
|
+
builder: (_) => MainSecurity(controller: controller),
|
|
1541
|
+
),
|
|
1542
|
+
);
|
|
1543
|
+
},
|
|
1544
|
+
),
|
|
1545
|
+
],
|
|
1546
|
+
),
|
|
1552
1547
|
),
|
|
1553
1548
|
);
|
|
1554
1549
|
}
|
package/lib/schema_migrations.js
CHANGED
|
@@ -196,7 +196,7 @@ function migrateToolPermissions(db) {
|
|
|
196
196
|
CREATE TABLE IF NOT EXISTS tool_policies (
|
|
197
197
|
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
198
198
|
category TEXT NOT NULL,
|
|
199
|
-
policy TEXT NOT NULL CHECK(policy IN ('allow','deny','require_approval')),
|
|
199
|
+
policy TEXT NOT NULL CHECK(policy IN ('allow','deny','require_approval','allow_always')),
|
|
200
200
|
updated_at TEXT DEFAULT (datetime('now')),
|
|
201
201
|
PRIMARY KEY (user_id, category)
|
|
202
202
|
);
|
|
@@ -208,7 +208,7 @@ function migrateToolPermissions(db) {
|
|
|
208
208
|
tool_name TEXT NOT NULL,
|
|
209
209
|
tool_args_json TEXT,
|
|
210
210
|
decision TEXT NOT NULL CHECK(decision IN ('approved','denied','timeout')),
|
|
211
|
-
scope TEXT NOT NULL CHECK(scope IN ('once','session')),
|
|
211
|
+
scope TEXT NOT NULL CHECK(scope IN ('once','session','always')),
|
|
212
212
|
decided_at TEXT DEFAULT (datetime('now'))
|
|
213
213
|
);
|
|
214
214
|
|
|
@@ -217,6 +217,29 @@ function migrateToolPermissions(db) {
|
|
|
217
217
|
`);
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
+
function migrateToolPoliciesAllowAlways(db) {
|
|
221
|
+
// SQLite doesn't support ALTER COLUMN — recreate the table to add 'allow_always'
|
|
222
|
+
// to the CHECK constraint on existing installations.
|
|
223
|
+
const tableInfo = db.prepare("PRAGMA table_info(tool_policies)").all();
|
|
224
|
+
if (!tableInfo.length) return; // table doesn't exist yet; migrateToolPermissions will create it correctly
|
|
225
|
+
const checkRow = db.prepare(
|
|
226
|
+
"SELECT sql FROM sqlite_master WHERE type='table' AND name='tool_policies'",
|
|
227
|
+
).get();
|
|
228
|
+
if (checkRow && checkRow.sql.includes("'allow_always'")) return; // already migrated
|
|
229
|
+
db.exec(`
|
|
230
|
+
CREATE TABLE IF NOT EXISTS tool_policies_v2 (
|
|
231
|
+
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
232
|
+
category TEXT NOT NULL,
|
|
233
|
+
policy TEXT NOT NULL CHECK(policy IN ('allow','deny','require_approval','allow_always')),
|
|
234
|
+
updated_at TEXT DEFAULT (datetime('now')),
|
|
235
|
+
PRIMARY KEY (user_id, category)
|
|
236
|
+
);
|
|
237
|
+
INSERT OR IGNORE INTO tool_policies_v2 SELECT * FROM tool_policies;
|
|
238
|
+
DROP TABLE tool_policies;
|
|
239
|
+
ALTER TABLE tool_policies_v2 RENAME TO tool_policies;
|
|
240
|
+
`);
|
|
241
|
+
}
|
|
242
|
+
|
|
220
243
|
function runSchemaMigrations(db) {
|
|
221
244
|
migrateMemoryEmbeddingIndex(db);
|
|
222
245
|
migrateMemoryProvenance(db);
|
|
@@ -224,6 +247,7 @@ function runSchemaMigrations(db) {
|
|
|
224
247
|
migrateMemoryRetrievalEvents(db);
|
|
225
248
|
migrateMemoryEmbeddingMetadata(db);
|
|
226
249
|
migrateToolPermissions(db);
|
|
250
|
+
migrateToolPoliciesAllowAlways(db);
|
|
227
251
|
}
|
|
228
252
|
|
|
229
253
|
module.exports = {
|
|
@@ -233,5 +257,6 @@ module.exports = {
|
|
|
233
257
|
migrateMemoryRetrievalEvents,
|
|
234
258
|
migrateMemoryEmbeddingMetadata,
|
|
235
259
|
migrateToolPermissions,
|
|
260
|
+
migrateToolPoliciesAllowAlways,
|
|
236
261
|
runSchemaMigrations,
|
|
237
262
|
};
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
7e73207d8404cf818eebed5972cebc61
|
|
Binary file
|
|
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"77e2e94772b6eb43759e34ed1ad7da4674e19c
|
|
|
37
37
|
|
|
38
38
|
_flutter.loader.load({
|
|
39
39
|
serviceWorkerSettings: {
|
|
40
|
-
serviceWorkerVersion: "
|
|
40
|
+
serviceWorkerVersion: "2478318604" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
|
|
41
41
|
}
|
|
42
42
|
});
|