@agent-native/core 0.175.0 → 0.175.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 (36) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/assets/app/root.tsx +5 -2
  3. package/corpus/templates/clips/actions/create-recording.ts +8 -1
  4. package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +3 -1
  5. package/corpus/templates/clips/app/components/player/share-dialog.tsx +11 -0
  6. package/corpus/templates/clips/app/components/player/timestamped-comment-button.tsx +2 -41
  7. package/corpus/templates/clips/app/components/sharing/share-ui.tsx +48 -4
  8. package/corpus/templates/clips/app/i18n/en-US.ts +1 -2
  9. package/corpus/templates/clips/app/lib/fmp4-seek.ts +274 -0
  10. package/corpus/templates/clips/app/lib/fmp4.ts +169 -0
  11. package/corpus/templates/clips/app/lib/mse-video-loader.ts +351 -109
  12. package/corpus/templates/clips/app/root.tsx +1 -1
  13. package/corpus/templates/clips/changelog/2026-08-25-seeking-into-a-long-recording-no-longer-sticks-on-buffering.md +6 -0
  14. package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +103 -13
  15. package/corpus/templates/clips/server/lib/resumable-upload-provider.ts +2 -2
  16. package/corpus/templates/clips/server/lib/video-storage.ts +2 -2
  17. package/corpus/templates/forms/app/components/builder/FieldRenderer.tsx +162 -157
  18. package/corpus/templates/tasks/app/root.tsx +5 -2
  19. package/dist/collab/awareness.d.ts +2 -2
  20. package/dist/file-upload/builder.d.ts +2 -2
  21. package/dist/file-upload/builder.js +28 -26
  22. package/dist/file-upload/registry.js +11 -8
  23. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  24. package/dist/notifications/routes.d.ts +3 -3
  25. package/dist/observability/routes.d.ts +5 -5
  26. package/dist/secrets/routes.d.ts +9 -9
  27. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  28. package/dist/server/builder-api-auth.d.ts +31 -0
  29. package/dist/server/builder-api-auth.js +62 -0
  30. package/dist/server/builder-oauth.d.ts +5 -3
  31. package/dist/server/builder-oauth.js +40 -12
  32. package/dist/server/core-routes-plugin.js +5 -2
  33. package/dist/server/index.d.ts +2 -0
  34. package/dist/server/index.js +2 -0
  35. package/dist/server/transcribe-voice.d.ts +1 -1
  36. package/package.json +3 -3
@@ -12,6 +12,7 @@ use super::*;
12
12
  use screencapturekit::error::SCError;
13
13
  use screencapturekit::stream::delegate_trait::SCStreamDelegateTrait;
14
14
  use std::io::{Seek, SeekFrom, Write};
15
+ use std::sync::RwLock;
15
16
 
16
17
  /// `AVAssetWriter.status` raw value for `.completed`.
17
18
  const AV_WRITER_STATUS_COMPLETED: i64 = 2;
@@ -1011,6 +1012,13 @@ unsafe impl Send for CustomScreenCaptureWriterState {}
1011
1012
  /// keeps receiving samples over the whole recording.
1012
1013
  struct CustomScreenCaptureOutputHandler {
1013
1014
  writer: CustomScreenCaptureWriter,
1015
+ /// Only callbacks from this stream generation may reach the writer. A
1016
+ /// ScreenCaptureKit stream can still have callbacks queued after stop; a
1017
+ /// replacement must discard those stale samples before they corrupt the
1018
+ /// writer's timeline.
1019
+ stream_generation: u64,
1020
+ active_stream_generation: Arc<AtomicU64>,
1021
+ callback_admission: Arc<RwLock<()>>,
1014
1022
  /// At most one temporary Clips writer may mirror this physical producer.
1015
1023
  /// It deliberately lives beside the callback handler rather than the
1016
1024
  /// audio bus: the bus has one producer contract and must never publish a
@@ -1283,12 +1291,61 @@ impl Clone for CustomScreenCaptureWriter {
1283
1291
  }
1284
1292
  }
1285
1293
 
1294
+ impl CustomScreenCaptureOutputHandler {
1295
+ fn replacement_stream(&self) -> Self {
1296
+ let _admission = self
1297
+ .callback_admission
1298
+ .write()
1299
+ .unwrap_or_else(|error| error.into_inner());
1300
+ let stream_generation = self.active_stream_generation.fetch_add(1, Ordering::SeqCst) + 1;
1301
+ Self {
1302
+ writer: self.writer.clone(),
1303
+ stream_generation,
1304
+ active_stream_generation: Arc::clone(&self.active_stream_generation),
1305
+ callback_admission: Arc::clone(&self.callback_admission),
1306
+ clip_sink: Arc::clone(&self.clip_sink),
1307
+ recording_enabled: Arc::clone(&self.recording_enabled),
1308
+ mic_ready: self.mic_ready.clone(),
1309
+ watch: Arc::clone(&self.watch),
1310
+ }
1311
+ }
1312
+
1313
+ fn invalidate_stream(&self) {
1314
+ let _admission = self
1315
+ .callback_admission
1316
+ .write()
1317
+ .unwrap_or_else(|error| error.into_inner());
1318
+ self.active_stream_generation.fetch_add(1, Ordering::SeqCst);
1319
+ }
1320
+
1321
+ fn is_current(&self) -> bool {
1322
+ stream_generation_is_current(
1323
+ self.active_stream_generation.load(Ordering::SeqCst),
1324
+ self.stream_generation,
1325
+ )
1326
+ }
1327
+ }
1328
+
1329
+ fn stream_generation_is_current(active_generation: u64, sample_generation: u64) -> bool {
1330
+ active_generation == sample_generation
1331
+ }
1332
+
1333
+ fn sample_pts_advances(previous: Option<f64>, current: f64) -> bool {
1334
+ previous.is_none_or(|last| current > last)
1335
+ }
1336
+
1286
1337
  impl SCStreamOutputTrait for CustomScreenCaptureOutputHandler {
1287
1338
  fn did_output_sample_buffer(
1288
1339
  &self,
1289
1340
  sample_buffer: screencapturekit::cm::CMSampleBuffer,
1290
1341
  of_type: SCStreamOutputType,
1291
1342
  ) {
1343
+ let Ok(_admission) = self.callback_admission.read() else {
1344
+ return;
1345
+ };
1346
+ if !self.is_current() {
1347
+ return;
1348
+ }
1292
1349
  if matches!(of_type, SCStreamOutputType::Microphone) {
1293
1350
  if let Some(mic_ready) = &self.mic_ready {
1294
1351
  mic_ready.store(true, Ordering::Relaxed);
@@ -2038,21 +2095,20 @@ impl CustomScreenCaptureWriter {
2038
2095
  }
2039
2096
  return;
2040
2097
  }
2041
- // Record the timeline BEFORE the append so a failure report describes
2042
- // the sample that was actually rejected, not the last good one.
2043
2098
  let stats = guard.append_stats.entry(track).or_default();
2044
2099
  let previous_pts = stats.last_pts_seconds;
2045
2100
  if let Some(pts) = pts_seconds {
2046
- if previous_pts.is_some_and(|last| pts <= last) {
2101
+ if !sample_pts_advances(previous_pts, pts) {
2047
2102
  stats.pts_regressions += 1;
2048
2103
  let regressions = stats.pts_regressions;
2049
2104
  if regressions == 1 || regressions % 100 == 0 {
2050
2105
  crate::logfile::diagnostic(&format!(
2051
- "[capture-health] {track} PTS did not advance: {:.6}s after {:.6}s ({regressions} so far)",
2106
+ "[capture-health] dropped {track} sample with non-advancing PTS: {:.6}s after {:.6}s ({regressions} so far)",
2052
2107
  pts,
2053
2108
  previous_pts.unwrap_or(f64::NAN)
2054
2109
  ));
2055
2110
  }
2111
+ return;
2056
2112
  }
2057
2113
  stats.last_pts_seconds = Some(pts);
2058
2114
  }
@@ -3755,6 +3811,7 @@ impl CustomCaptureResume {
3755
3811
  /// hold so it never reads the silence as a stall and rebuilds.
3756
3812
  pub(crate) fn pause(&self) {
3757
3813
  self.watch.set_paused(true);
3814
+ self.handler.invalidate_stream();
3758
3815
  if let Ok(guard) = self.stream.lock() {
3759
3816
  let _ = guard.stop_capture();
3760
3817
  }
@@ -3774,7 +3831,8 @@ impl CustomCaptureResume {
3774
3831
  // timeline. The session stays paused and can be retried — and a retry
3775
3832
  // re-measures `paused_for` from the same (uncleared) pause instant, so
3776
3833
  // advancing the offset here would compound on every failed attempt.
3777
- let new_stream = build_custom_scstream(&self.params, &self.handler, &self.watch)?;
3834
+ let replacement_handler = self.handler.replacement_stream();
3835
+ let new_stream = build_custom_scstream(&self.params, &replacement_handler, &self.watch)?;
3778
3836
 
3779
3837
  // Apply the pause gap just before the stream starts delivering, so the
3780
3838
  // first rebased frame already skips it. Roll back if startup fails so
@@ -4130,6 +4188,7 @@ fn spawn_capture_watchdog(
4130
4188
  // Stop the dead/wedged stream before starting a replacement so two
4131
4189
  // streams never feed the writer at once. Slow build/start work is
4132
4190
  // done without holding the stream lock.
4191
+ let replacement_handler = handler.replacement_stream();
4133
4192
  if let Ok(guard) = stream.lock() {
4134
4193
  let _ = guard.stop_capture();
4135
4194
  }
@@ -4137,21 +4196,35 @@ fn spawn_capture_watchdog(
4137
4196
  return;
4138
4197
  }
4139
4198
 
4140
- match build_custom_scstream(&params, &handler, &watch).and_then(|s| {
4199
+ match build_custom_scstream(&params, &replacement_handler, &watch).and_then(|s| {
4141
4200
  s.start_capture()
4142
4201
  .map(|()| s)
4143
4202
  .map_err(|e| format!("start_capture failed: {e:?}"))
4144
4203
  }) {
4145
4204
  Ok(new_stream) => {
4146
- if shutdown.load(Ordering::SeqCst)
4147
- || writer.appends_closed.load(Ordering::SeqCst)
4148
- {
4149
- let _ = new_stream.stop_capture();
4205
+ let mut pending_stream = Some(new_stream);
4206
+ let install = if let Ok(mut guard) = stream.lock() {
4207
+ if shutdown.load(Ordering::SeqCst)
4208
+ || writer.appends_closed.load(Ordering::SeqCst)
4209
+ || watch.is_paused()
4210
+ || !replacement_handler.is_current()
4211
+ {
4212
+ false
4213
+ } else {
4214
+ *guard = pending_stream
4215
+ .take()
4216
+ .expect("pending watchdog stream is available");
4217
+ true
4218
+ }
4219
+ } else {
4220
+ false
4221
+ };
4222
+ if !install {
4223
+ if let Some(stream) = pending_stream {
4224
+ let _ = stream.stop_capture();
4225
+ }
4150
4226
  return;
4151
4227
  }
4152
- if let Ok(mut guard) = stream.lock() {
4153
- *guard = new_stream;
4154
- }
4155
4228
  // Discard any stop note the deliberate teardown of the old
4156
4229
  // stream raised; otherwise a later poll would consume it as
4157
4230
  // a fresh failure and rebuild the healthy stream again.
@@ -4256,6 +4329,9 @@ pub(crate) fn start_custom_screencapturekit_backend_at(
4256
4329
  let watch = Arc::new(CaptureWatch::new());
4257
4330
  let handler = CustomScreenCaptureOutputHandler {
4258
4331
  writer: writer.clone(),
4332
+ stream_generation: 0,
4333
+ active_stream_generation: Arc::new(AtomicU64::new(0)),
4334
+ callback_admission: Arc::new(RwLock::new(())),
4259
4335
  clip_sink: Arc::clone(&clip_sink),
4260
4336
  recording_enabled: Arc::clone(&recording_enabled),
4261
4337
  mic_ready: mic_ready.clone(),
@@ -4491,6 +4567,20 @@ mod fragment_fence_tests {
4491
4567
  assert_eq!(mixer.compute_safe_end(false), 0);
4492
4568
  }
4493
4569
 
4570
+ #[test]
4571
+ fn replacement_stream_rejects_callbacks_from_the_prior_generation() {
4572
+ assert!(stream_generation_is_current(1, 1));
4573
+ assert!(!stream_generation_is_current(1, 0));
4574
+ }
4575
+
4576
+ #[test]
4577
+ fn timestamp_admission_rejects_equal_and_regressing_samples() {
4578
+ assert!(sample_pts_advances(None, 1.0));
4579
+ assert!(sample_pts_advances(Some(1.0), 1.01));
4580
+ assert!(!sample_pts_advances(Some(1.0), 1.0));
4581
+ assert!(!sample_pts_advances(Some(1.0), 0.99));
4582
+ }
4583
+
4494
4584
  #[test]
4495
4585
  fn clip_sink_gate_orders_prepare_activate_and_logical_close() {
4496
4586
  let gate = ClipSinkGate::new();
@@ -4,7 +4,7 @@ import {
4
4
  listFileUploadProviders,
5
5
  type FileUploadProvider,
6
6
  } from "@agent-native/core/file-upload";
7
- import { resolveHasBuilderPrivateKey } from "@agent-native/core/server";
7
+ import { hasBuilderApiCredentialCustody } from "@agent-native/core/server";
8
8
 
9
9
  async function isConfiguredForRequest(
10
10
  provider: FileUploadProvider,
@@ -34,7 +34,7 @@ export async function resolveResumableUploadProvider(
34
34
 
35
35
  if (providerId === builderFileUploadProvider.id) {
36
36
  try {
37
- if (await resolveHasBuilderPrivateKey()) {
37
+ if (await hasBuilderApiCredentialCustody()) {
38
38
  return builderFileUploadProvider;
39
39
  }
40
40
  } catch {
@@ -1,6 +1,6 @@
1
1
  import { listFileUploadProviders } from "@agent-native/core/file-upload";
2
2
  import {
3
- resolveHasBuilderPrivateKey,
3
+ hasBuilderApiCredentialCustody,
4
4
  runWithRequestContext,
5
5
  } from "@agent-native/core/server";
6
6
 
@@ -51,7 +51,7 @@ export async function hasRequestVideoStorage(
51
51
  }
52
52
 
53
53
  try {
54
- return await resolveHasBuilderPrivateKey();
54
+ return await hasBuilderApiCredentialCustody();
55
55
  } catch {
56
56
  return false;
57
57
  }
@@ -68,173 +68,178 @@ export function FieldRenderer({
68
68
  <p className="text-xs text-muted-foreground">{field.description}</p>
69
69
  )}
70
70
 
71
- {field.type === "text" && (
72
- <Input
73
- placeholder={field.placeholder || ""}
74
- value={(value as string) || ""}
75
- onChange={(e) => handleChange(e.target.value)}
76
- disabled={disabled}
77
- />
78
- )}
79
-
80
- {field.type === "email" && (
81
- <Input
82
- type="email"
83
- placeholder={field.placeholder || "you@example.com"}
84
- value={(value as string) || ""}
85
- onChange={(e) => handleChange(e.target.value)}
86
- disabled={disabled}
87
- />
88
- )}
89
-
90
- {field.type === "number" && (
91
- <Input
92
- type="number"
93
- placeholder={field.placeholder || ""}
94
- value={(value as string) || ""}
95
- onChange={(e) => handleChange(e.target.value)}
96
- min={field.validation?.min}
97
- max={field.validation?.max}
98
- disabled={disabled}
99
- />
100
- )}
71
+ <div
72
+ inert={preview ? true : undefined}
73
+ className={preview ? "pointer-events-none" : undefined}
74
+ >
75
+ {field.type === "text" && (
76
+ <Input
77
+ placeholder={field.placeholder || ""}
78
+ value={(value as string) || ""}
79
+ onChange={(e) => handleChange(e.target.value)}
80
+ disabled={disabled}
81
+ />
82
+ )}
83
+
84
+ {field.type === "email" && (
85
+ <Input
86
+ type="email"
87
+ placeholder={field.placeholder || "you@example.com"}
88
+ value={(value as string) || ""}
89
+ onChange={(e) => handleChange(e.target.value)}
90
+ disabled={disabled}
91
+ />
92
+ )}
93
+
94
+ {field.type === "number" && (
95
+ <Input
96
+ type="number"
97
+ placeholder={field.placeholder || ""}
98
+ value={(value as string) || ""}
99
+ onChange={(e) => handleChange(e.target.value)}
100
+ min={field.validation?.min}
101
+ max={field.validation?.max}
102
+ disabled={disabled}
103
+ />
104
+ )}
105
+
106
+ {field.type === "textarea" && (
107
+ <Textarea
108
+ placeholder={field.placeholder || ""}
109
+ value={(value as string) || ""}
110
+ onChange={(e) => handleChange(e.target.value)}
111
+ rows={4}
112
+ disabled={disabled}
113
+ />
114
+ )}
101
115
 
102
- {field.type === "textarea" && (
103
- <Textarea
104
- placeholder={field.placeholder || ""}
105
- value={(value as string) || ""}
106
- onChange={(e) => handleChange(e.target.value)}
107
- rows={4}
108
- disabled={disabled}
109
- />
110
- )}
116
+ {field.type === "select" && (
117
+ <Select
118
+ value={(value as string) || ""}
119
+ onValueChange={handleChange}
120
+ disabled={disabled}
121
+ >
122
+ <SelectTrigger>
123
+ <SelectValue placeholder={field.placeholder || "Select..."} />
124
+ </SelectTrigger>
125
+ <SelectContent>
126
+ {dedupeRenderableOptions(field.options).map((opt, i) => (
127
+ <SelectItem key={`${opt}-${i}`} value={opt}>
128
+ {opt}
129
+ </SelectItem>
130
+ ))}
131
+ </SelectContent>
132
+ </Select>
133
+ )}
134
+
135
+ {field.type === "multiselect" && (
136
+ <div className="space-y-1">
137
+ {dedupeRenderableOptions(field.options).map((opt, i) => {
138
+ const selected = Array.isArray(value) ? value : [];
139
+ return (
140
+ <label
141
+ key={`${opt}-${i}`}
142
+ className="flex items-center gap-2.5 text-sm min-h-[44px] py-1"
143
+ >
144
+ <Checkbox
145
+ checked={selected.includes(opt)}
146
+ onCheckedChange={(checked) => {
147
+ const next = checked
148
+ ? [...selected, opt]
149
+ : selected.filter((s: string) => s !== opt);
150
+ handleChange(next);
151
+ }}
152
+ disabled={disabled}
153
+ />
154
+ {opt}
155
+ </label>
156
+ );
157
+ })}
158
+ </div>
159
+ )}
111
160
 
112
- {field.type === "select" && (
113
- <Select
114
- value={(value as string) || ""}
115
- onValueChange={handleChange}
116
- disabled={disabled}
117
- >
118
- <SelectTrigger>
119
- <SelectValue placeholder={field.placeholder || "Select..."} />
120
- </SelectTrigger>
121
- <SelectContent>
161
+ {field.type === "checkbox" && (
162
+ <label className="flex items-center gap-2.5 text-sm min-h-[44px]">
163
+ <Checkbox
164
+ checked={!!value}
165
+ onCheckedChange={handleChange}
166
+ disabled={disabled}
167
+ />
168
+ {field.placeholder || field.label}
169
+ </label>
170
+ )}
171
+
172
+ {field.type === "radio" && (
173
+ <RadioGroup
174
+ value={(value as string) || ""}
175
+ onValueChange={handleChange}
176
+ disabled={disabled}
177
+ >
122
178
  {dedupeRenderableOptions(field.options).map((opt, i) => (
123
- <SelectItem key={`${opt}-${i}`} value={opt}>
124
- {opt}
125
- </SelectItem>
126
- ))}
127
- </SelectContent>
128
- </Select>
129
- )}
130
-
131
- {field.type === "multiselect" && (
132
- <div className="space-y-1">
133
- {dedupeRenderableOptions(field.options).map((opt, i) => {
134
- const selected = Array.isArray(value) ? value : [];
135
- return (
136
- <label
179
+ <div
137
180
  key={`${opt}-${i}`}
138
- className="flex items-center gap-2.5 text-sm min-h-[44px] py-1"
181
+ className="flex items-center gap-2.5 min-h-[44px]"
139
182
  >
140
- <Checkbox
141
- checked={selected.includes(opt)}
142
- onCheckedChange={(checked) => {
143
- const next = checked
144
- ? [...selected, opt]
145
- : selected.filter((s: string) => s !== opt);
146
- handleChange(next);
147
- }}
148
- disabled={disabled}
149
- />
150
- {opt}
151
- </label>
152
- );
153
- })}
154
- </div>
155
- )}
156
-
157
- {field.type === "checkbox" && (
158
- <label className="flex items-center gap-2.5 text-sm min-h-[44px]">
159
- <Checkbox
160
- checked={!!value}
161
- onCheckedChange={handleChange}
183
+ <RadioGroupItem value={opt} id={`${field.id}-${opt}-${i}`} />
184
+ <Label
185
+ htmlFor={`${field.id}-${opt}-${i}`}
186
+ className="font-normal"
187
+ >
188
+ {opt}
189
+ </Label>
190
+ </div>
191
+ ))}
192
+ </RadioGroup>
193
+ )}
194
+
195
+ {field.type === "date" && (
196
+ <Input
197
+ type="date"
198
+ value={(value as string) || ""}
199
+ onChange={(e) => handleChange(e.target.value)}
162
200
  disabled={disabled}
163
201
  />
164
- {field.placeholder || field.label}
165
- </label>
166
- )}
167
-
168
- {field.type === "radio" && (
169
- <RadioGroup
170
- value={(value as string) || ""}
171
- onValueChange={handleChange}
172
- disabled={disabled}
173
- >
174
- {dedupeRenderableOptions(field.options).map((opt, i) => (
175
- <div
176
- key={`${opt}-${i}`}
177
- className="flex items-center gap-2.5 min-h-[44px]"
178
- >
179
- <RadioGroupItem value={opt} id={`${field.id}-${opt}-${i}`} />
180
- <Label
181
- htmlFor={`${field.id}-${opt}-${i}`}
182
- className="font-normal"
202
+ )}
203
+
204
+ {field.type === "rating" && (
205
+ <div className="flex gap-0.5 sm:gap-1">
206
+ {[1, 2, 3, 4, 5].map((star) => (
207
+ <button
208
+ key={star}
209
+ type="button"
210
+ onClick={() => !disabled && handleChange(star)}
211
+ className="flex size-11 cursor-pointer items-center justify-center rounded-md p-2 transition-[color,transform] duration-150 ease-out active:scale-[0.96] motion-reduce:active:scale-100 disabled:cursor-not-allowed sm:size-10 sm:p-1.5"
212
+ disabled={disabled}
213
+ aria-label={`${star} star${star !== 1 ? "s" : ""}`}
183
214
  >
184
- {opt}
185
- </Label>
186
- </div>
187
- ))}
188
- </RadioGroup>
189
- )}
190
-
191
- {field.type === "date" && (
192
- <Input
193
- type="date"
194
- value={(value as string) || ""}
195
- onChange={(e) => handleChange(e.target.value)}
196
- disabled={disabled}
197
- />
198
- )}
199
-
200
- {field.type === "rating" && (
201
- <div className="flex gap-0.5 sm:gap-1">
202
- {[1, 2, 3, 4, 5].map((star) => (
203
- <button
204
- key={star}
205
- type="button"
206
- onClick={() => !disabled && handleChange(star)}
207
- className="flex size-11 cursor-pointer items-center justify-center rounded-md p-2 transition-[color,transform] duration-150 ease-out active:scale-[0.96] motion-reduce:active:scale-100 disabled:cursor-not-allowed sm:size-10 sm:p-1.5"
215
+ {(value as number) >= star ? (
216
+ <IconStarFilled className="h-7 w-7 text-amber-400 sm:h-6 sm:w-6" />
217
+ ) : (
218
+ <IconStar className="h-7 w-7 text-muted-foreground/30 sm:h-6 sm:w-6" />
219
+ )}
220
+ </button>
221
+ ))}
222
+ </div>
223
+ )}
224
+
225
+ {field.type === "scale" && (
226
+ <div className="pt-2">
227
+ <Slider
228
+ value={[(value as number) || field.validation?.min || 1]}
229
+ onValueChange={([v]) => handleChange(v)}
230
+ min={field.validation?.min || 1}
231
+ max={field.validation?.max || 10}
232
+ step={1}
208
233
  disabled={disabled}
209
- aria-label={`${star} star${star !== 1 ? "s" : ""}`}
210
- >
211
- {(value as number) >= star ? (
212
- <IconStarFilled className="h-7 w-7 text-amber-400 sm:h-6 sm:w-6" />
213
- ) : (
214
- <IconStar className="h-7 w-7 text-muted-foreground/30 sm:h-6 sm:w-6" />
215
- )}
216
- </button>
217
- ))}
218
- </div>
219
- )}
220
-
221
- {field.type === "scale" && (
222
- <div className="pt-2">
223
- <Slider
224
- value={[(value as number) || field.validation?.min || 1]}
225
- onValueChange={([v]) => handleChange(v)}
226
- min={field.validation?.min || 1}
227
- max={field.validation?.max || 10}
228
- step={1}
229
- disabled={disabled}
230
- />
231
- <div className="flex justify-between text-xs text-muted-foreground mt-1">
232
- <span>{field.validation?.min || 1}</span>
233
- <span>{String(value ?? "-")}</span>
234
- <span>{field.validation?.max || 10}</span>
234
+ />
235
+ <div className="flex justify-between text-xs text-muted-foreground mt-1">
236
+ <span>{field.validation?.min || 1}</span>
237
+ <span>{String(value ?? "-")}</span>
238
+ <span>{field.validation?.max || 10}</span>
239
+ </div>
235
240
  </div>
236
- </div>
237
- )}
241
+ )}
242
+ </div>
238
243
  </div>
239
244
  );
240
245
  }
@@ -150,7 +150,6 @@ function AppContent() {
150
150
  <ThemeToggleItem />
151
151
  </CommandMenu.Group>
152
152
  </CommandMenu>
153
- <Toaster position="bottom-left" />
154
153
  <AppLayout>
155
154
  <Outlet />
156
155
  </AppLayout>
@@ -161,7 +160,11 @@ function AppContent() {
161
160
  export default function Root() {
162
161
  const [queryClient] = useState(() => createAgentNativeQueryClient());
163
162
  return (
164
- <AppProviders queryClient={queryClient} i18n={{ catalog: i18nCatalog }}>
163
+ <AppProviders
164
+ queryClient={queryClient}
165
+ toaster={<Toaster position="bottom-left" />}
166
+ i18n={{ catalog: i18nCatalog }}
167
+ >
165
168
  <AppToolkitProvider>
166
169
  <DbSyncSetup />
167
170
  <AppContent />
@@ -62,11 +62,11 @@ export declare const postAwareness: import("h3").EventHandlerWithFetch<import("h
62
62
  error: string;
63
63
  states?: undefined;
64
64
  } | {
65
- error?: undefined;
66
65
  states: {
67
66
  clientId: number;
68
67
  state: string;
69
68
  }[];
69
+ error?: undefined;
70
70
  }>>;
71
71
  /**
72
72
  * GET /_agent-native/collab/:docId/users
@@ -77,9 +77,9 @@ export declare const getActiveUsers: import("h3").EventHandlerWithFetch<import("
77
77
  error: string;
78
78
  users?: undefined;
79
79
  } | {
80
- error?: undefined;
81
80
  users: {
82
81
  clientId: number;
83
82
  lastSeen: number;
84
83
  }[];
84
+ error?: undefined;
85
85
  }>>;
@@ -1,8 +1,8 @@
1
1
  import type { FileUploadProvider } from "./types.js";
2
2
  /**
3
3
  * Built-in Builder.io file upload provider.
4
- * Uses the same BUILDER_PRIVATE_KEY as the browser/background-agent flows,
5
- * so connecting Builder once (via the sidebar "Connect Builder" action)
4
+ * Uses the same Builder connection as the browser/background-agent flows, so
5
+ * connecting Builder once (via the sidebar "Connect Builder" action)
6
6
  * automatically enables file uploads.
7
7
  *
8
8
  * Upload API: https://www.builder.io/c/docs/upload-api