neoagent 2.3.1-beta.64 → 2.3.1-beta.65
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/docs/capabilities.md +1 -1
- package/docs/configuration.md +2 -2
- package/flutter_app/lib/main_app_shell.dart +1 -1
- package/flutter_app/lib/main_chat.dart +198 -9
- package/flutter_app/lib/main_controller.dart +27 -7
- package/flutter_app/lib/main_devices.dart +4 -6
- package/flutter_app/lib/main_models.dart +91 -1
- package/flutter_app/lib/main_navigation.dart +9 -0
- package/flutter_app/lib/main_settings.dart +6 -8
- package/flutter_app/lib/main_shared.dart +7 -3
- package/package.json +1 -1
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/AssetManifest.json +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 +52468 -52276
- package/server/routes/browser.js +1 -14
- package/server/routes/settings.js +1 -5
- package/server/services/ai/capabilityHealth.js +1 -10
- package/server/services/ai/deliverables/artifact_helpers.js +190 -0
- package/server/services/ai/deliverables/contracts.js +113 -0
- package/server/services/ai/deliverables/deliverables.test.js +76 -0
- package/server/services/ai/deliverables/index.js +20 -0
- package/server/services/ai/deliverables/selector.js +94 -0
- package/server/services/ai/deliverables/validator.js +63 -0
- package/server/services/ai/deliverables/workflows.js +195 -0
- package/server/services/ai/engine.js +173 -1
- package/server/services/ai/systemPrompt.js +6 -0
- package/server/services/ai/tools.js +1 -5
- package/server/services/manager.js +5 -56
- package/server/services/runtime/manager.js +2 -6
- package/server/services/runtime/settings.js +6 -12
- package/server/services/widgets/focus_widget.js +10 -2
- package/server/utils/deployment.js +4 -3
package/docs/capabilities.md
CHANGED
|
@@ -112,7 +112,7 @@ Runtime settings let operators choose where higher-risk work runs:
|
|
|
112
112
|
|
|
113
113
|
| Profile | Runtime shape |
|
|
114
114
|
|---|---|
|
|
115
|
-
| `trusted-host` | CLI
|
|
115
|
+
| `trusted-host` | CLI and Android tools run on the host; browser runs in the VM or paired extension |
|
|
116
116
|
| `secure-vm` | CLI, browser, and Android tools run through the local VM backend |
|
|
117
117
|
|
|
118
118
|
Production policy can require the secure VM profile and a strong VM guest token.
|
package/docs/configuration.md
CHANGED
|
@@ -130,11 +130,11 @@ Telnyx webhook verification is configured through the environment.
|
|
|
130
130
|
|
|
131
131
|
## Runtime Isolation
|
|
132
132
|
|
|
133
|
-
Runtime profile and backend selection are stored in user settings, not normally in `.env`. The main profiles are `trusted-host` and `secure-vm`.
|
|
133
|
+
Runtime profile and backend selection are stored in user settings, not normally in `.env`. The main profiles are `trusted-host` and `secure-vm`. CLI and Android tools may still follow the selected runtime profile, but browser control is always isolated: the non-extension browser backend runs in the VM, not on the host device.
|
|
134
134
|
|
|
135
135
|
Production policy can require the VM backend. In that case, set a strong `NEOAGENT_VM_GUEST_TOKEN` of at least 32 characters and avoid placeholder values.
|
|
136
136
|
|
|
137
|
-
The app exposes two browser backend choices:
|
|
137
|
+
The app exposes two browser backend choices: VM and Chrome extension. VM uses the local isolated runtime. Chrome extension uses the paired extension connection on the remote machine instead of the server-local browser. To install only the extension on a remote machine, open NeoAgent, download `/api/browser-extension/download`, unzip it, load the folder through `chrome://extensions` with Developer mode enabled, then pair after logging in to NeoAgent. Unpacked Chrome extensions cannot replace themselves automatically; use the extension popup's update check to compare against the server bundle, then download and reload the latest ZIP when needed.
|
|
138
138
|
|
|
139
139
|
## Secrets Guidance
|
|
140
140
|
|
|
@@ -1120,7 +1120,7 @@ class _HomeViewState extends State<HomeView> {
|
|
|
1120
1120
|
}
|
|
1121
1121
|
|
|
1122
1122
|
SidebarGroup? _sidebarGroupForSection(AppSection section) {
|
|
1123
|
-
final visibleSection = section.
|
|
1123
|
+
final visibleSection = section.sidebarSection;
|
|
1124
1124
|
if (!_mainSections(widget.controller).contains(visibleSection)) {
|
|
1125
1125
|
return null;
|
|
1126
1126
|
}
|
|
@@ -43,6 +43,23 @@ class _ChatPanelState extends State<ChatPanel> {
|
|
|
43
43
|
..selection = TextSelection.collapsed(offset: draft.length);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
void _scrollToBottom() {
|
|
47
|
+
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
48
|
+
if (!mounted || !_scrollController.hasClients) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
_scrollController.jumpTo(_scrollController.position.maxScrollExtent);
|
|
52
|
+
unawaited(
|
|
53
|
+
WidgetsBinding.instance.endOfFrame.then((_) {
|
|
54
|
+
if (!mounted || !_scrollController.hasClients) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
_scrollController.jumpTo(_scrollController.position.maxScrollExtent);
|
|
58
|
+
}),
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
46
63
|
@override
|
|
47
64
|
Widget build(BuildContext context) {
|
|
48
65
|
final controller = widget.controller;
|
|
@@ -53,15 +70,7 @@ class _ChatPanelState extends State<ChatPanel> {
|
|
|
53
70
|
_lastMessageCount = messages.length;
|
|
54
71
|
_lastToolCount = controller.toolEvents.length;
|
|
55
72
|
_lastStream = controller.streamingAssistant;
|
|
56
|
-
|
|
57
|
-
if (_scrollController.hasClients) {
|
|
58
|
-
_scrollController.animateTo(
|
|
59
|
-
_scrollController.position.maxScrollExtent,
|
|
60
|
-
duration: const Duration(milliseconds: 220),
|
|
61
|
-
curve: Curves.easeOut,
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
});
|
|
73
|
+
_scrollToBottom();
|
|
65
74
|
}
|
|
66
75
|
|
|
67
76
|
return Column(
|
|
@@ -259,6 +268,87 @@ class _ChatPanelState extends State<ChatPanel> {
|
|
|
259
268
|
}
|
|
260
269
|
}
|
|
261
270
|
|
|
271
|
+
class _TypingIndicatorBubble extends StatefulWidget {
|
|
272
|
+
const _TypingIndicatorBubble();
|
|
273
|
+
|
|
274
|
+
@override
|
|
275
|
+
State<_TypingIndicatorBubble> createState() => _TypingIndicatorBubbleState();
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
class _TypingIndicatorBubbleState extends State<_TypingIndicatorBubble>
|
|
279
|
+
with SingleTickerProviderStateMixin {
|
|
280
|
+
late final AnimationController _controller;
|
|
281
|
+
|
|
282
|
+
@override
|
|
283
|
+
void initState() {
|
|
284
|
+
super.initState();
|
|
285
|
+
_controller = AnimationController(
|
|
286
|
+
vsync: this,
|
|
287
|
+
duration: const Duration(milliseconds: 1200),
|
|
288
|
+
)..repeat();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
@override
|
|
292
|
+
void dispose() {
|
|
293
|
+
_controller.dispose();
|
|
294
|
+
super.dispose();
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
@override
|
|
298
|
+
Widget build(BuildContext context) {
|
|
299
|
+
return Row(
|
|
300
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
301
|
+
children: <Widget>[
|
|
302
|
+
const _MessageAvatar(assistant: true),
|
|
303
|
+
const SizedBox(width: 12),
|
|
304
|
+
Flexible(
|
|
305
|
+
child: Container(
|
|
306
|
+
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
|
307
|
+
decoration: BoxDecoration(
|
|
308
|
+
color: _bgCard,
|
|
309
|
+
borderRadius: BorderRadius.circular(14),
|
|
310
|
+
border: Border.all(color: _border),
|
|
311
|
+
),
|
|
312
|
+
child: AnimatedBuilder(
|
|
313
|
+
animation: _controller,
|
|
314
|
+
builder: (context, _) {
|
|
315
|
+
return Row(
|
|
316
|
+
mainAxisSize: MainAxisSize.min,
|
|
317
|
+
children: List<Widget>.generate(3, (index) {
|
|
318
|
+
final phase = ((_controller.value * 3) - index).clamp(
|
|
319
|
+
0.0,
|
|
320
|
+
1.0,
|
|
321
|
+
);
|
|
322
|
+
final offset = Curves.easeOut.transform(
|
|
323
|
+
phase > 0.5 ? 1 - phase : phase,
|
|
324
|
+
);
|
|
325
|
+
return Padding(
|
|
326
|
+
padding: EdgeInsets.only(
|
|
327
|
+
right: index == 2 ? 0 : 6,
|
|
328
|
+
top: (1 - offset) * 6,
|
|
329
|
+
),
|
|
330
|
+
child: Container(
|
|
331
|
+
width: 8,
|
|
332
|
+
height: 8,
|
|
333
|
+
decoration: BoxDecoration(
|
|
334
|
+
color: _textSecondary.withValues(
|
|
335
|
+
alpha: 0.45 + (offset * 0.5),
|
|
336
|
+
),
|
|
337
|
+
shape: BoxShape.circle,
|
|
338
|
+
),
|
|
339
|
+
),
|
|
340
|
+
);
|
|
341
|
+
}),
|
|
342
|
+
);
|
|
343
|
+
},
|
|
344
|
+
),
|
|
345
|
+
),
|
|
346
|
+
),
|
|
347
|
+
],
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
262
352
|
class MessagingPanel extends StatefulWidget {
|
|
263
353
|
const MessagingPanel({super.key, required this.controller});
|
|
264
354
|
|
|
@@ -2493,6 +2583,13 @@ class _RunHistoryRow extends StatelessWidget {
|
|
|
2493
2583
|
'${run.modelLabel} • ${run.totalTokensLabel} tokens',
|
|
2494
2584
|
style: TextStyle(color: _textSecondary, fontSize: 12),
|
|
2495
2585
|
),
|
|
2586
|
+
if (run.deliverableType.trim().isNotEmpty) ...<Widget>[
|
|
2587
|
+
const SizedBox(height: 4),
|
|
2588
|
+
Text(
|
|
2589
|
+
'Deliverable • ${run.deliverableType.replaceAll('_', ' ')}',
|
|
2590
|
+
style: TextStyle(color: _accent, fontSize: 12),
|
|
2591
|
+
),
|
|
2592
|
+
],
|
|
2496
2593
|
if (run.error.trim().isNotEmpty) ...<Widget>[
|
|
2497
2594
|
const SizedBox(height: 8),
|
|
2498
2595
|
Text(
|
|
@@ -2624,6 +2721,10 @@ class _RunDetailWorkspace extends StatelessWidget {
|
|
|
2624
2721
|
response: snapshot.response,
|
|
2625
2722
|
onCopy: () => onCopyResponse(snapshot.response),
|
|
2626
2723
|
),
|
|
2724
|
+
if (snapshot.run.deliverableType.trim().isNotEmpty) ...<Widget>[
|
|
2725
|
+
const SizedBox(height: 16),
|
|
2726
|
+
_DeliverableSummaryCard(run: snapshot.run),
|
|
2727
|
+
],
|
|
2627
2728
|
const SizedBox(height: 16),
|
|
2628
2729
|
_RunTimelineCard(steps: snapshot.steps, loading: loading),
|
|
2629
2730
|
const SizedBox(height: 16),
|
|
@@ -2687,6 +2788,11 @@ class _RunHeroCard extends StatelessWidget {
|
|
|
2687
2788
|
label: run.modelLabel,
|
|
2688
2789
|
icon: Icons.memory_outlined,
|
|
2689
2790
|
),
|
|
2791
|
+
if (run.deliverableType.trim().isNotEmpty)
|
|
2792
|
+
_MetaPill(
|
|
2793
|
+
label: run.deliverableType.replaceAll('_', ' '),
|
|
2794
|
+
icon: Icons.inventory_2_outlined,
|
|
2795
|
+
),
|
|
2690
2796
|
],
|
|
2691
2797
|
),
|
|
2692
2798
|
const SizedBox(height: 16),
|
|
@@ -2812,6 +2918,89 @@ class _RunResponseCard extends StatelessWidget {
|
|
|
2812
2918
|
}
|
|
2813
2919
|
}
|
|
2814
2920
|
|
|
2921
|
+
class _DeliverableSummaryCard extends StatelessWidget {
|
|
2922
|
+
const _DeliverableSummaryCard({required this.run});
|
|
2923
|
+
|
|
2924
|
+
final RunSummary run;
|
|
2925
|
+
|
|
2926
|
+
@override
|
|
2927
|
+
Widget build(BuildContext context) {
|
|
2928
|
+
final artifacts = run.deliverableArtifacts;
|
|
2929
|
+
return Card(
|
|
2930
|
+
child: Padding(
|
|
2931
|
+
padding: const EdgeInsets.all(18),
|
|
2932
|
+
child: Column(
|
|
2933
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
2934
|
+
children: <Widget>[
|
|
2935
|
+
_SectionTitle('Deliverable'),
|
|
2936
|
+
const SizedBox(height: 12),
|
|
2937
|
+
Text(
|
|
2938
|
+
run.deliverableSummary.ifEmpty(
|
|
2939
|
+
'Workflow: ${run.deliverableType.replaceAll('_', ' ')}',
|
|
2940
|
+
),
|
|
2941
|
+
style: TextStyle(color: _textPrimary, height: 1.45),
|
|
2942
|
+
),
|
|
2943
|
+
if (artifacts.isNotEmpty) ...<Widget>[
|
|
2944
|
+
const SizedBox(height: 14),
|
|
2945
|
+
...artifacts.map((artifact) {
|
|
2946
|
+
final meta = <String>[
|
|
2947
|
+
artifact.kind,
|
|
2948
|
+
if (artifact.mimeType.trim().isNotEmpty) artifact.mimeType,
|
|
2949
|
+
if (artifact.size > 0) '${artifact.size} bytes',
|
|
2950
|
+
].join(' • ');
|
|
2951
|
+
final location = artifact.uri.ifEmpty(artifact.path);
|
|
2952
|
+
return Padding(
|
|
2953
|
+
padding: const EdgeInsets.only(bottom: 10),
|
|
2954
|
+
child: Container(
|
|
2955
|
+
width: double.infinity,
|
|
2956
|
+
padding: const EdgeInsets.all(12),
|
|
2957
|
+
decoration: BoxDecoration(
|
|
2958
|
+
color: _bgSecondary,
|
|
2959
|
+
borderRadius: BorderRadius.circular(14),
|
|
2960
|
+
border: Border.all(color: _border),
|
|
2961
|
+
),
|
|
2962
|
+
child: Column(
|
|
2963
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
2964
|
+
children: <Widget>[
|
|
2965
|
+
Text(
|
|
2966
|
+
artifact.displayLabel,
|
|
2967
|
+
style: TextStyle(fontWeight: FontWeight.w700),
|
|
2968
|
+
),
|
|
2969
|
+
if (meta.trim().isNotEmpty) ...<Widget>[
|
|
2970
|
+
const SizedBox(height: 4),
|
|
2971
|
+
Text(
|
|
2972
|
+
meta,
|
|
2973
|
+
style: TextStyle(
|
|
2974
|
+
color: _textSecondary,
|
|
2975
|
+
fontSize: 12,
|
|
2976
|
+
),
|
|
2977
|
+
),
|
|
2978
|
+
],
|
|
2979
|
+
if (location.trim().isNotEmpty) ...<Widget>[
|
|
2980
|
+
const SizedBox(height: 6),
|
|
2981
|
+
SelectableText(
|
|
2982
|
+
location,
|
|
2983
|
+
style: TextStyle(
|
|
2984
|
+
color: _textSecondary,
|
|
2985
|
+
fontSize: 12,
|
|
2986
|
+
fontFamily:
|
|
2987
|
+
GoogleFonts.jetBrainsMono().fontFamily,
|
|
2988
|
+
),
|
|
2989
|
+
),
|
|
2990
|
+
],
|
|
2991
|
+
],
|
|
2992
|
+
),
|
|
2993
|
+
),
|
|
2994
|
+
);
|
|
2995
|
+
}),
|
|
2996
|
+
],
|
|
2997
|
+
],
|
|
2998
|
+
),
|
|
2999
|
+
),
|
|
3000
|
+
);
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
|
|
2815
3004
|
class _RunTimelineCard extends StatelessWidget {
|
|
2816
3005
|
const _RunTimelineCard({required this.steps, required this.loading});
|
|
2817
3006
|
|
|
@@ -5572,9 +5572,18 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
5572
5572
|
return;
|
|
5573
5573
|
}
|
|
5574
5574
|
_pendingChatDraft = normalized;
|
|
5575
|
-
|
|
5575
|
+
if (!_isMobilePlatform) {
|
|
5576
|
+
setSelectedSection(AppSection.chat);
|
|
5577
|
+
} else {
|
|
5578
|
+
notifyListeners();
|
|
5579
|
+
}
|
|
5576
5580
|
}
|
|
5577
5581
|
|
|
5582
|
+
bool get _isMobilePlatform =>
|
|
5583
|
+
!kIsWeb &&
|
|
5584
|
+
(defaultTargetPlatform == TargetPlatform.android ||
|
|
5585
|
+
defaultTargetPlatform == TargetPlatform.iOS);
|
|
5586
|
+
|
|
5578
5587
|
String? takePendingChatDraft() {
|
|
5579
5588
|
final draft = _pendingChatDraft;
|
|
5580
5589
|
_pendingChatDraft = null;
|
|
@@ -5953,7 +5962,7 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
5953
5962
|
settings['headless_browser'] != 'false';
|
|
5954
5963
|
|
|
5955
5964
|
String get browserBackend =>
|
|
5956
|
-
settings['browser_backend']?.toString().trim().toLowerCase() ?? '
|
|
5965
|
+
settings['browser_backend']?.toString().trim().toLowerCase() ?? 'vm';
|
|
5957
5966
|
|
|
5958
5967
|
String get cloudBrowserBackend {
|
|
5959
5968
|
final browser = browserBackend;
|
|
@@ -5969,11 +5978,10 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
5969
5978
|
profile == 'secure-vm') {
|
|
5970
5979
|
return 'vm';
|
|
5971
5980
|
}
|
|
5972
|
-
if (browser == '
|
|
5973
|
-
|
|
5974
|
-
}
|
|
5981
|
+
if (browser == 'extension') return 'vm';
|
|
5982
|
+
if (browser == 'vm') return 'vm';
|
|
5975
5983
|
if (runtime == 'vm') return 'vm';
|
|
5976
|
-
return '
|
|
5984
|
+
return 'vm';
|
|
5977
5985
|
}
|
|
5978
5986
|
|
|
5979
5987
|
bool get browserExtensionConnected =>
|
|
@@ -6158,7 +6166,19 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
6158
6166
|
|
|
6159
6167
|
List<ChatEntry> get visibleChatMessages {
|
|
6160
6168
|
final entries = <ChatEntry>[...chatMessages];
|
|
6161
|
-
if (streamingAssistant.trim().
|
|
6169
|
+
if (activeRun != null && streamingAssistant.trim().isEmpty) {
|
|
6170
|
+
entries.add(
|
|
6171
|
+
ChatEntry(
|
|
6172
|
+
id: '',
|
|
6173
|
+
role: 'assistant',
|
|
6174
|
+
content: '',
|
|
6175
|
+
platform: 'live',
|
|
6176
|
+
createdAt: DateTime.now(),
|
|
6177
|
+
transient: true,
|
|
6178
|
+
typing: true,
|
|
6179
|
+
),
|
|
6180
|
+
);
|
|
6181
|
+
} else if (streamingAssistant.trim().isNotEmpty) {
|
|
6162
6182
|
entries.add(
|
|
6163
6183
|
ChatEntry(
|
|
6164
6184
|
id: '',
|
|
@@ -378,9 +378,7 @@ class _DevicesPanelState extends State<DevicesPanel> {
|
|
|
378
378
|
final prefersExtension = controller.browserBackend == 'extension';
|
|
379
379
|
final extensionConnected = controller.browserExtensionConnected;
|
|
380
380
|
final usingExtension = prefersExtension && extensionConnected;
|
|
381
|
-
final browserFallbackLabel =
|
|
382
|
-
? 'cloud VM'
|
|
383
|
-
: 'local host';
|
|
381
|
+
final browserFallbackLabel = 'cloud browser runtime';
|
|
384
382
|
final browserPageInfo = browserStatus['pageInfo'] is Map<dynamic, dynamic>
|
|
385
383
|
? Map<String, dynamic>.from(browserStatus['pageInfo'] as Map)
|
|
386
384
|
: const <String, dynamic>{};
|
|
@@ -682,9 +680,9 @@ class _DeviceSurfaceHeader extends StatelessWidget {
|
|
|
682
680
|
: 'Desktop Companion',
|
|
683
681
|
};
|
|
684
682
|
final subtitle = switch (surface) {
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
? 'No extension device is active. Using the $browserFallbackLabel
|
|
683
|
+
_DeviceSurface.browser =>
|
|
684
|
+
browserExtensionPreferred && !browserExtensionActive
|
|
685
|
+
? 'No extension device is active. Using the $browserFallbackLabel.'
|
|
688
686
|
: (browserPageInfo['url']?.toString() ??
|
|
689
687
|
'Ready for navigation'),
|
|
690
688
|
_DeviceSurface.android =>
|
|
@@ -1475,6 +1475,38 @@ class RunDetailSnapshot {
|
|
|
1475
1475
|
steps.where((step) => step.isPlanningRelated).length;
|
|
1476
1476
|
}
|
|
1477
1477
|
|
|
1478
|
+
class ArtifactContractItem {
|
|
1479
|
+
const ArtifactContractItem({
|
|
1480
|
+
required this.kind,
|
|
1481
|
+
required this.path,
|
|
1482
|
+
required this.uri,
|
|
1483
|
+
required this.label,
|
|
1484
|
+
required this.mimeType,
|
|
1485
|
+
required this.size,
|
|
1486
|
+
});
|
|
1487
|
+
|
|
1488
|
+
factory ArtifactContractItem.fromJson(Map<dynamic, dynamic> json) {
|
|
1489
|
+
return ArtifactContractItem(
|
|
1490
|
+
kind: json['kind']?.toString() ?? 'artifact',
|
|
1491
|
+
path: json['path']?.toString() ?? '',
|
|
1492
|
+
uri: json['uri']?.toString() ?? json['url']?.toString() ?? '',
|
|
1493
|
+
label: json['label']?.toString() ?? '',
|
|
1494
|
+
mimeType:
|
|
1495
|
+
json['mimeType']?.toString() ?? json['mime_type']?.toString() ?? '',
|
|
1496
|
+
size: _asInt(json['size'] ?? json['byte_size']),
|
|
1497
|
+
);
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
final String kind;
|
|
1501
|
+
final String path;
|
|
1502
|
+
final String uri;
|
|
1503
|
+
final String label;
|
|
1504
|
+
final String mimeType;
|
|
1505
|
+
final int size;
|
|
1506
|
+
|
|
1507
|
+
String get displayLabel => label.ifEmpty(path.ifEmpty(uri.ifEmpty(kind)));
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1478
1510
|
class RunEventItem {
|
|
1479
1511
|
const RunEventItem({
|
|
1480
1512
|
required this.id,
|
|
@@ -1519,6 +1551,18 @@ class RunEventItem {
|
|
|
1519
1551
|
|
|
1520
1552
|
String get title {
|
|
1521
1553
|
switch (eventType) {
|
|
1554
|
+
case 'deliverable_workflow_selected':
|
|
1555
|
+
return 'Deliverable selected';
|
|
1556
|
+
case 'deliverable_execution_started':
|
|
1557
|
+
return 'Deliverable execution started';
|
|
1558
|
+
case 'deliverable_artifact_produced':
|
|
1559
|
+
return 'Deliverable artifact produced';
|
|
1560
|
+
case 'deliverable_validation_started':
|
|
1561
|
+
return 'Deliverable validation started';
|
|
1562
|
+
case 'deliverable_validation_failed':
|
|
1563
|
+
return 'Deliverable validation failed';
|
|
1564
|
+
case 'deliverable_completed':
|
|
1565
|
+
return 'Deliverable completed';
|
|
1522
1566
|
case 'run_started':
|
|
1523
1567
|
return 'Run started';
|
|
1524
1568
|
case 'memory_injected':
|
|
@@ -1554,6 +1598,13 @@ class RunEventItem {
|
|
|
1554
1598
|
if (preview.trim().isNotEmpty) return preview;
|
|
1555
1599
|
final error = payload['error']?.toString() ?? '';
|
|
1556
1600
|
if (error.trim().isNotEmpty) return error;
|
|
1601
|
+
final artifactLabel = payload['artifact'] is Map
|
|
1602
|
+
? (payload['artifact']['label']?.toString() ??
|
|
1603
|
+
payload['artifact']['path']?.toString() ??
|
|
1604
|
+
payload['artifact']['uri']?.toString() ??
|
|
1605
|
+
'')
|
|
1606
|
+
: '';
|
|
1607
|
+
if (artifactLabel.trim().isNotEmpty) return artifactLabel;
|
|
1557
1608
|
final titleValue = payload['title']?.toString() ?? '';
|
|
1558
1609
|
return titleValue;
|
|
1559
1610
|
}
|
|
@@ -1745,6 +1796,17 @@ dynamic _decodeMaybeJson(dynamic value) {
|
|
|
1745
1796
|
return value;
|
|
1746
1797
|
}
|
|
1747
1798
|
|
|
1799
|
+
Map<String, dynamic> _decodeJsonMap(
|
|
1800
|
+
String? value, {
|
|
1801
|
+
Map<String, dynamic> fallback = const <String, dynamic>{},
|
|
1802
|
+
}) {
|
|
1803
|
+
final decoded = _decodeMaybeJson(value);
|
|
1804
|
+
if (decoded is Map) {
|
|
1805
|
+
return Map<String, dynamic>.from(decoded);
|
|
1806
|
+
}
|
|
1807
|
+
return fallback;
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1748
1810
|
class ChatEntry {
|
|
1749
1811
|
const ChatEntry({
|
|
1750
1812
|
required this.id,
|
|
@@ -1757,6 +1819,7 @@ class ChatEntry {
|
|
|
1757
1819
|
this.metadata = const <String, dynamic>{},
|
|
1758
1820
|
this.toolCalls = const <Map<String, dynamic>>[],
|
|
1759
1821
|
this.transient = false,
|
|
1822
|
+
this.typing = false,
|
|
1760
1823
|
});
|
|
1761
1824
|
|
|
1762
1825
|
factory ChatEntry.fromJson(Map<dynamic, dynamic> json) {
|
|
@@ -1786,6 +1849,7 @@ class ChatEntry {
|
|
|
1786
1849
|
final List<Map<String, dynamic>> toolCalls;
|
|
1787
1850
|
final DateTime createdAt;
|
|
1788
1851
|
final bool transient;
|
|
1852
|
+
final bool typing;
|
|
1789
1853
|
|
|
1790
1854
|
String get createdAtLabel => _formatTimestamp(createdAt);
|
|
1791
1855
|
|
|
@@ -2059,9 +2123,16 @@ class RunSummary {
|
|
|
2059
2123
|
required this.createdAt,
|
|
2060
2124
|
this.completedAt,
|
|
2061
2125
|
this.error = '',
|
|
2126
|
+
this.metadata = const <String, dynamic>{},
|
|
2062
2127
|
});
|
|
2063
2128
|
|
|
2064
2129
|
factory RunSummary.fromJson(Map<dynamic, dynamic> json) {
|
|
2130
|
+
final metadata = _decodeJsonMap(
|
|
2131
|
+
json['metadata_json']?.toString(),
|
|
2132
|
+
fallback: json['metadata'] is Map
|
|
2133
|
+
? Map<String, dynamic>.from(json['metadata'] as Map)
|
|
2134
|
+
: const <String, dynamic>{},
|
|
2135
|
+
);
|
|
2065
2136
|
return RunSummary(
|
|
2066
2137
|
id: json['id']?.toString() ?? '',
|
|
2067
2138
|
title: json['title']?.toString() ?? 'Untitled',
|
|
@@ -2072,6 +2143,7 @@ class RunSummary {
|
|
|
2072
2143
|
createdAt: _parseTimestamp(json['created_at']?.toString()),
|
|
2073
2144
|
completedAt: _parseOptionalTimestamp(json['completed_at']?.toString()),
|
|
2074
2145
|
error: json['error']?.toString() ?? '',
|
|
2146
|
+
metadata: metadata,
|
|
2075
2147
|
);
|
|
2076
2148
|
}
|
|
2077
2149
|
|
|
@@ -2084,6 +2156,24 @@ class RunSummary {
|
|
|
2084
2156
|
final DateTime createdAt;
|
|
2085
2157
|
final DateTime? completedAt;
|
|
2086
2158
|
final String error;
|
|
2159
|
+
final Map<String, dynamic> metadata;
|
|
2160
|
+
|
|
2161
|
+
Map<String, dynamic> get deliverable => metadata['deliverable'] is Map
|
|
2162
|
+
? Map<String, dynamic>.from(metadata['deliverable'] as Map)
|
|
2163
|
+
: const <String, dynamic>{};
|
|
2164
|
+
|
|
2165
|
+
String get deliverableType => deliverable['type']?.toString() ?? '';
|
|
2166
|
+
|
|
2167
|
+
String get deliverableSummary => deliverable['summary']?.toString() ?? '';
|
|
2168
|
+
|
|
2169
|
+
List<ArtifactContractItem> get deliverableArtifacts {
|
|
2170
|
+
final raw = deliverable['artifacts'];
|
|
2171
|
+
if (raw is! List) return const <ArtifactContractItem>[];
|
|
2172
|
+
return raw
|
|
2173
|
+
.whereType<Map>()
|
|
2174
|
+
.map(ArtifactContractItem.fromJson)
|
|
2175
|
+
.toList(growable: false);
|
|
2176
|
+
}
|
|
2087
2177
|
|
|
2088
2178
|
bool get isFailure => status == 'failed' || status == 'error';
|
|
2089
2179
|
|
|
@@ -2257,7 +2347,7 @@ class UpdateStatusSnapshot {
|
|
|
2257
2347
|
deploymentProfile.toLowerCase() == 'prod' ? 'Production' : 'Private';
|
|
2258
2348
|
|
|
2259
2349
|
String get runtimeModeLabel => deploymentProfile.toLowerCase() == 'prod'
|
|
2260
|
-
? '
|
|
2350
|
+
? 'Cloud runtime'
|
|
2261
2351
|
: 'Trusted host runtime';
|
|
2262
2352
|
|
|
2263
2353
|
String get runtimeValidationLabel =>
|
|
@@ -179,6 +179,15 @@ extension AppSectionX on AppSection {
|
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
+
AppSection get sidebarSection {
|
|
183
|
+
switch (this) {
|
|
184
|
+
case AppSection.accountSettings:
|
|
185
|
+
return AppSection.settings;
|
|
186
|
+
default:
|
|
187
|
+
return canonicalSection;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
182
191
|
String get navigationTitle {
|
|
183
192
|
final effectiveSection = canonicalSection;
|
|
184
193
|
final groupLabel = effectiveSection.group.label;
|
|
@@ -279,7 +279,7 @@ class _SettingsPanelState extends State<SettingsPanel> {
|
|
|
279
279
|
|
|
280
280
|
String _normalizeBrowserBackend(String value) {
|
|
281
281
|
final normalized = value.trim().toLowerCase();
|
|
282
|
-
return normalized == 'extension' ? 'extension' : '
|
|
282
|
+
return normalized == 'extension' ? 'extension' : 'vm';
|
|
283
283
|
}
|
|
284
284
|
|
|
285
285
|
@override
|
|
@@ -459,7 +459,7 @@ class _SettingsPanelState extends State<SettingsPanel> {
|
|
|
459
459
|
headlessBrowser: _headlessBrowser,
|
|
460
460
|
browserBackend: _browserBackend == 'extension'
|
|
461
461
|
? 'extension'
|
|
462
|
-
:
|
|
462
|
+
: 'vm',
|
|
463
463
|
smarterSelector: _smarterSelector,
|
|
464
464
|
enabledModels: _enabledModels.toList(),
|
|
465
465
|
defaultChatModel: _defaultChatModel,
|
|
@@ -606,12 +606,12 @@ class _SettingsPanelState extends State<SettingsPanel> {
|
|
|
606
606
|
decoration: const InputDecoration(
|
|
607
607
|
labelText: 'Browser backend',
|
|
608
608
|
helperText:
|
|
609
|
-
'Cloud uses
|
|
609
|
+
'Cloud uses the isolated browser runtime. Extension uses a paired Chrome browser on the remote machine.',
|
|
610
610
|
),
|
|
611
611
|
items: const <DropdownMenuItem<String>>[
|
|
612
612
|
DropdownMenuItem<String>(
|
|
613
|
-
value: '
|
|
614
|
-
child: Text('Cloud
|
|
613
|
+
value: 'vm',
|
|
614
|
+
child: Text('Cloud'),
|
|
615
615
|
),
|
|
616
616
|
DropdownMenuItem<String>(
|
|
617
617
|
value: 'extension',
|
|
@@ -630,9 +630,7 @@ class _SettingsPanelState extends State<SettingsPanel> {
|
|
|
630
630
|
? (controller.browserExtensionConnected
|
|
631
631
|
? 'Chrome extension connected.'
|
|
632
632
|
: 'Chrome extension selected. Download it here, load it unpacked in Chrome on the remote machine, then pair after login.')
|
|
633
|
-
:
|
|
634
|
-
? "Cloud uses this deployment's isolated VM browser runtime."
|
|
635
|
-
: "Cloud uses this deployment's local host browser runtime.",
|
|
633
|
+
: 'Cloud browser runtime is active.',
|
|
636
634
|
style: TextStyle(color: _textSecondary, height: 1.4),
|
|
637
635
|
),
|
|
638
636
|
const SizedBox(height: 10),
|
|
@@ -253,7 +253,7 @@ List<Widget> _buildSidebarItems(
|
|
|
253
253
|
final widgets = <Widget>[];
|
|
254
254
|
final mainSections = _mainSections(controller);
|
|
255
255
|
final selectedSidebarSection = mainSections.contains(
|
|
256
|
-
controller.selectedSection.
|
|
256
|
+
controller.selectedSection.sidebarSection,
|
|
257
257
|
);
|
|
258
258
|
for (final group in SidebarGroup.values) {
|
|
259
259
|
final sections = mainSections
|
|
@@ -265,7 +265,7 @@ List<Widget> _buildSidebarItems(
|
|
|
265
265
|
|
|
266
266
|
final active =
|
|
267
267
|
selectedSidebarSection &&
|
|
268
|
-
controller.selectedSection.
|
|
268
|
+
controller.selectedSection.sidebarSection.group == group;
|
|
269
269
|
final defaultSection = sections.first;
|
|
270
270
|
final hasChildren = sections.length > 1;
|
|
271
271
|
final expanded = expandedGroup == group;
|
|
@@ -297,7 +297,7 @@ List<Widget> _buildSidebarItems(
|
|
|
297
297
|
_SidebarButton(
|
|
298
298
|
label: section.label,
|
|
299
299
|
icon: section.icon,
|
|
300
|
-
active: controller.selectedSection.
|
|
300
|
+
active: controller.selectedSection.sidebarSection == section,
|
|
301
301
|
indent: 18,
|
|
302
302
|
iconSize: 16,
|
|
303
303
|
fontSize: 12,
|
|
@@ -1133,6 +1133,10 @@ class _ChatBubble extends StatelessWidget {
|
|
|
1133
1133
|
final isUser = entry.role == 'user';
|
|
1134
1134
|
final isTransient = entry.transient;
|
|
1135
1135
|
|
|
1136
|
+
if (entry.typing) {
|
|
1137
|
+
return const _TypingIndicatorBubble();
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1136
1140
|
return Opacity(
|
|
1137
1141
|
opacity: isTransient ? 0.92 : 1,
|
|
1138
1142
|
child: Row(
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
f5b685e19d2bcb3cb22b0f5b5df858c9
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"assets/branding/app_icon_256.png":["assets/branding/app_icon_256.png"],"assets/branding/tray_icon_template.png":["assets/branding/tray_icon_template.png"],"packages/cupertino_icons/assets/CupertinoIcons.ttf":["packages/cupertino_icons/assets/CupertinoIcons.ttf"],"packages/record_web/assets/js/record.fixwebmduration.js":["packages/record_web/assets/js/record.fixwebmduration.js"],"packages/record_web/assets/js/record.worklet.js":["packages/record_web/assets/js/record.worklet.js"],"web/icons/Icon-192.png":["web/icons/Icon-192.png"]}
|
|
1
|
+
{"assets/branding/app_icon_256.png":["assets/branding/app_icon_256.png"],"assets/branding/onboarding_intro.mp4":["assets/branding/onboarding_intro.mp4"],"assets/branding/tray_icon_template.png":["assets/branding/tray_icon_template.png"],"packages/cupertino_icons/assets/CupertinoIcons.ttf":["packages/cupertino_icons/assets/CupertinoIcons.ttf"],"packages/mixpanel_flutter/assets/mixpanel.js":["packages/mixpanel_flutter/assets/mixpanel.js"],"packages/record_web/assets/js/record.fixwebmduration.js":["packages/record_web/assets/js/record.fixwebmduration.js"],"packages/record_web/assets/js/record.worklet.js":["packages/record_web/assets/js/record.worklet.js"],"web/icons/Icon-192.png":["web/icons/Icon-192.png"]}
|
|
Binary file
|
|
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"42d3d75a56efe1a2e9902f52dc8006099c45d9
|
|
|
37
37
|
|
|
38
38
|
_flutter.loader.load({
|
|
39
39
|
serviceWorkerSettings: {
|
|
40
|
-
serviceWorkerVersion: "
|
|
40
|
+
serviceWorkerVersion: "1598577597" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
|
|
41
41
|
}
|
|
42
42
|
});
|