@agent-native/core 0.168.6 → 0.168.7

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.
@@ -14,6 +14,7 @@ import {
14
14
  } from "drizzle-orm";
15
15
  import { z } from "zod";
16
16
 
17
+ import { effectiveDuration, parseEdits } from "../app/lib/timestamp-mapping.js";
17
18
  import { getDb, schema } from "../server/db/index.js";
18
19
  import {
19
20
  agentRecordingAccessFilter,
@@ -303,6 +304,9 @@ export default defineAction({
303
304
  thumbnailUrl: schema.recordings.thumbnailUrl,
304
305
  animatedThumbnailUrl: schema.recordings.animatedThumbnailUrl,
305
306
  durationMs: schema.recordings.durationMs,
307
+ // Needed to derive the edited-length badge below; dropped before the
308
+ // response is built so the raw edits blob never reaches the client.
309
+ editsJson: schema.recordings.editsJson,
306
310
  status: schema.recordings.status,
307
311
  uploadProgress: schema.recordings.uploadProgress,
308
312
  failureReason: schema.recordings.failureReason,
@@ -417,7 +421,17 @@ export default defineAction({
417
421
  description: r.description,
418
422
  thumbnailUrl: r.thumbnailUrl,
419
423
  animatedThumbnailUrl: r.animatedThumbnailUrl,
424
+ // Raw source length. StitchManager sums this across queued
425
+ // recordings to size the concatenated export, which always
426
+ // includes each source's full untrimmed media — trims are applied
427
+ // at export/playback time, not by dropping bytes from the source.
420
428
  durationMs: r.durationMs,
429
+ // Edited length, not the original recorded length — matches what
430
+ // the clip page itself shows once trims/cuts are applied.
431
+ effectiveDurationMs: effectiveDuration(
432
+ r.durationMs,
433
+ parseEdits(r.editsJson),
434
+ ),
421
435
  status: r.status,
422
436
  uploadProgress: r.uploadProgress,
423
437
  failureReason: r.failureReason,
@@ -98,8 +98,8 @@ export function RecordingCard({
98
98
  const pendingTrashRef = useRef(false);
99
99
 
100
100
  const duration = useMemo(
101
- () => formatDuration(recording.durationMs),
102
- [recording.durationMs],
101
+ () => formatDuration(recording.effectiveDurationMs),
102
+ [recording.effectiveDurationMs],
103
103
  );
104
104
  const relative = useMemo(() => {
105
105
  const date = new Date(recording.createdAt);
@@ -51,6 +51,7 @@ export function RecordingOptionsMenu({
51
51
  const [open, setOpen] = useState(false);
52
52
  const [menuOpen, setMenuOpen] = useState(false);
53
53
  const deletedWhileOpenRef = useRef(false);
54
+ const pendingDeleteConfirmRef = useRef(false);
54
55
  const showDownload = canDownload && Boolean(onDownload);
55
56
  const showDelete = canDelete;
56
57
  const trashRecording = useActionMutation<any, { id: string }>(
@@ -99,7 +100,22 @@ export function RecordingOptionsMenu({
99
100
  <IconDotsVertical className="h-4 w-4" />
100
101
  </Button>
101
102
  </DropdownMenuTrigger>
102
- <DropdownMenuContent align="end" className="w-44">
103
+ <DropdownMenuContent
104
+ align="end"
105
+ className="w-44"
106
+ onCloseAutoFocus={(event) => {
107
+ // Opening the AlertDialog while this menu is still tearing down
108
+ // leaves `pointer-events: none` stuck on <body>: two dismissable
109
+ // layers overlap and the survivor never restores the style. Wait
110
+ // for the menu to finish closing, and keep focus off the trigger
111
+ // so the dialog owns it.
112
+ if (pendingDeleteConfirmRef.current) {
113
+ event.preventDefault();
114
+ pendingDeleteConfirmRef.current = false;
115
+ setOpen(true);
116
+ }
117
+ }}
118
+ >
103
119
  {showDownload ? (
104
120
  <DropdownMenuItem
105
121
  onSelect={handleDownload}
@@ -116,7 +132,8 @@ export function RecordingOptionsMenu({
116
132
  <DropdownMenuItem
117
133
  onSelect={(event) => {
118
134
  event.preventDefault();
119
- setOpen(true);
135
+ pendingDeleteConfirmRef.current = true;
136
+ setMenuOpen(false);
120
137
  }}
121
138
  className="text-destructive focus:text-destructive"
122
139
  >
@@ -132,7 +149,7 @@ export function RecordingOptionsMenu({
132
149
  if (!deletedWhileOpenRef.current) return;
133
150
  deletedWhileOpenRef.current = false;
134
151
  event.preventDefault();
135
- setTimeout(() => onDeleted?.(), 0);
152
+ onDeleted?.();
136
153
  }}
137
154
  >
138
155
  <AlertDialogHeader>
@@ -15,6 +15,7 @@ export interface RecordingSummary {
15
15
  thumbnailUrl: string | null;
16
16
  animatedThumbnailUrl: string | null;
17
17
  durationMs: number;
18
+ effectiveDurationMs: number;
18
19
  status: "uploading" | "processing" | "ready" | "failed";
19
20
  uploadProgress?: number;
20
21
  failureReason?: string | null;
@@ -247,9 +247,8 @@ function nativeSaveFailureMessage(reason: string | null | undefined): string {
247
247
  return "The desktop recorder finished and saved a local copy, but Clips could not upload it. You can retry from the Clips menu without recording again.";
248
248
  }
249
249
 
250
- export function BackToLibraryButton() {
250
+ export function BackButton({ onBack }: { onBack: () => void }) {
251
251
  const t = useT();
252
- const navigate = useNavigate();
253
252
 
254
253
  return (
255
254
  <Tooltip>
@@ -258,7 +257,7 @@ export function BackToLibraryButton() {
258
257
  variant="ghost"
259
258
  size="icon"
260
259
  className="shrink-0"
261
- onClick={() => navigate("/library", { replace: true })}
260
+ onClick={onBack}
262
261
  aria-label={t("recordingPage.backToLibrary")}
263
262
  >
264
263
  <IconArrowLeft className="h-4 w-4 rtl:-scale-x-100" />
@@ -1053,7 +1052,9 @@ export default function RecordingPage() {
1053
1052
  return (
1054
1053
  <div className="flex min-h-screen w-full flex-col bg-background">
1055
1054
  <header className="flex min-w-0 shrink-0 items-center gap-2 border-b border-border px-3 py-2 sm:px-4 sm:py-3">
1056
- <BackToLibraryButton />
1055
+ <BackButton
1056
+ onBack={() => navigate("/library", { replace: true })}
1057
+ />
1057
1058
  <div className="min-w-0 flex-1">
1058
1059
  <p className="truncate text-sm font-medium">{visibleTitle}</p>
1059
1060
  <p className="truncate text-xs text-muted-foreground">
@@ -1369,7 +1370,13 @@ export default function RecordingPage() {
1369
1370
  {/* Main video column */}
1370
1371
  <div className="flex w-full min-w-0 flex-col xl:flex-1">
1371
1372
  <header className="flex min-w-0 shrink-0 items-center gap-2 border-b border-border px-3 py-2 sm:px-4 sm:py-3">
1372
- <BackToLibraryButton />
1373
+ <BackButton
1374
+ onBack={
1375
+ editing
1376
+ ? () => setEditing(false)
1377
+ : () => navigate("/library", { replace: true })
1378
+ }
1379
+ />
1373
1380
  <div className="flex-1 min-w-0">
1374
1381
  <EditableRecordingTitle
1375
1382
  recordingId={recording.id}
@@ -26,8 +26,8 @@ export declare const getCollabState: import("h3").EventHandlerWithFetch<import("
26
26
  * Body: { update: string (base64), requestSource?: string }
27
27
  */
28
28
  export declare const postCollabUpdate: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
29
- error: string;
30
29
  ok?: undefined;
30
+ error: string;
31
31
  } | {
32
32
  error?: undefined;
33
33
  ok: boolean;
@@ -13,8 +13,8 @@
13
13
  * Body: { json: any, fieldName?: string, type?: "map"|"array", requestSource?: string }
14
14
  */
15
15
  export declare const postCollabJson: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
16
- ok?: undefined;
17
16
  error: string;
17
+ ok?: undefined;
18
18
  } | {
19
19
  error?: undefined;
20
20
  ok: boolean;
@@ -17,11 +17,11 @@ declare const _default: import("../../action.js").ActionDefinition<{
17
17
  id?: undefined;
18
18
  provider?: undefined;
19
19
  } | {
20
- error?: undefined;
21
20
  configured?: undefined;
22
21
  connectPath?: undefined;
23
22
  url: string;
24
23
  id: string;
25
24
  provider: string;
25
+ error?: undefined;
26
26
  }>;
27
27
  export default _default;
@@ -28,6 +28,6 @@ export declare function createNotificationsHandler(): import("h3").EventHandlerW
28
28
  } | {
29
29
  count?: undefined;
30
30
  updated?: undefined;
31
- ok: boolean;
32
31
  error?: undefined;
32
+ ok: boolean;
33
33
  }>>;
@@ -41,16 +41,16 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
41
41
  thumbsUpRate: number;
42
42
  avgEvalScore: number;
43
43
  } | {
44
- error?: undefined;
45
44
  summary: import("./types.js").TraceSummary;
46
45
  spans: import("./types.js").TraceSpan[];
47
46
  id?: undefined;
47
+ error?: undefined;
48
48
  ok?: undefined;
49
49
  } | {
50
- error?: undefined;
51
50
  summary?: undefined;
52
51
  spans?: undefined;
53
52
  id: string;
53
+ error?: undefined;
54
54
  ok?: undefined;
55
55
  } | {
56
56
  summary?: undefined;
@@ -59,9 +59,9 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
59
59
  error: any;
60
60
  ok?: undefined;
61
61
  } | {
62
- error?: undefined;
63
62
  summary?: undefined;
64
63
  spans?: undefined;
65
64
  id?: undefined;
65
+ error?: undefined;
66
66
  ok: boolean;
67
67
  }>>;
@@ -48,11 +48,11 @@ export declare function handleUpdateResource(event: any): Promise<import("./stor
48
48
  }>;
49
49
  /** DELETE /_agent-native/resources/:id — delete a resource */
50
50
  export declare function handleDeleteResource(event: any): Promise<{
51
- error: string;
52
51
  ok?: undefined;
52
+ error: string;
53
53
  } | {
54
- ok: boolean;
55
54
  error?: undefined;
55
+ ok: boolean;
56
56
  }>;
57
57
  /** POST /_agent-native/resources/upload — upload a file as a resource */
58
58
  export declare function handleUploadResource(event: any): Promise<import("./store.js").Resource | {
@@ -73,10 +73,10 @@ export declare function handleUploadResource(event: any): Promise<import("./stor
73
73
  runId: string | null;
74
74
  expiresAt: number | null;
75
75
  metadata: string | null;
76
+ error?: undefined;
76
77
  url: string;
77
78
  provider: string;
78
79
  storageSetupRequired?: undefined;
79
- error?: undefined;
80
80
  } | {
81
81
  error: string;
82
82
  storageSetupRequired: boolean;
@@ -37,17 +37,17 @@ export declare function createWriteSecretHandler(): import("h3").EventHandlerWit
37
37
  ok?: undefined;
38
38
  status?: undefined;
39
39
  } | {
40
- error?: undefined;
41
40
  ok: boolean;
42
41
  status: string;
42
+ error?: undefined;
43
43
  } | {
44
44
  ok?: undefined;
45
45
  error: string;
46
46
  removed?: undefined;
47
47
  } | {
48
- error?: undefined;
49
48
  ok: boolean;
50
49
  removed: boolean;
50
+ error?: undefined;
51
51
  }>>;
52
52
  /**
53
53
  * POST /_agent-native/secrets/:key/test — validate an optional candidate value
@@ -58,13 +58,13 @@ export declare function createTestSecretHandler(): import("h3").EventHandlerWith
58
58
  error: string;
59
59
  note?: undefined;
60
60
  } | {
61
- error?: undefined;
62
61
  ok: boolean;
63
62
  note?: undefined;
64
- } | {
65
63
  error?: undefined;
64
+ } | {
66
65
  ok: boolean;
67
66
  note: string;
67
+ error?: undefined;
68
68
  } | {
69
69
  note?: undefined;
70
70
  ok: boolean;
@@ -95,11 +95,11 @@ export interface AdHocSecretPayload {
95
95
  export declare function createAdHocSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<AdHocSecretPayload[] | {
96
96
  error: string;
97
97
  } | {
98
- error?: undefined;
99
98
  ok: boolean;
100
99
  key: string;
101
- } | {
102
100
  error?: undefined;
101
+ } | {
103
102
  ok: boolean;
104
103
  removed: boolean;
104
+ error?: undefined;
105
105
  }>>;
@@ -26,8 +26,8 @@ export declare function createRealtimeTokenHandler(): import("h3").EventHandlerW
26
26
  expiresAt?: undefined;
27
27
  ttlSeconds?: undefined;
28
28
  } | {
29
- error?: undefined;
30
29
  token: string;
31
30
  expiresAt: string;
32
31
  ttlSeconds: number;
32
+ error?: undefined;
33
33
  }>>;
@@ -157,8 +157,26 @@ function injectDefaultSocialImageMeta(html, imageUrl) {
157
157
  return html;
158
158
  return html.slice(0, headCloseIdx) + tags.join("") + html.slice(headCloseIdx);
159
159
  }
160
+ /**
161
+ * A "not found" shell is exactly as impersonal as a 200 shell, and leaving it
162
+ * uncached is expensive in a way that is invisible until it is not: every dead
163
+ * link, stale bookmark, renamed slug and crawler miss re-invoked the render
164
+ * function, and Netlify runs one request per container. Measured on
165
+ * www.agent-native.com, `/docs/<anything>` cost ~5s and cost the SAME ~5s on
166
+ * the very next identical request, because `no-cache` meant nothing was ever
167
+ * stored. Those invocations draw from the account-wide concurrency pool every
168
+ * other site shares, so one crawler walking dead links slowed unrelated apps.
169
+ *
170
+ * Only 404/410 join the shared policy. A 5xx is transient and must stay
171
+ * uncacheable — pinning one at the edge for the SWR window would turn a blip
172
+ * into an outage. 401/403 stay out because an auth-shaped response is the one
173
+ * error that could carry viewer-specific meaning.
174
+ */
175
+ const CACHEABLE_ERROR_STATUSES = new Set([404, 410]);
160
176
  function isSsrHtmlOrDataResponse(headers, status, pathname) {
161
- if (status < 200 || status >= 400)
177
+ if (status < 200)
178
+ return false;
179
+ if (status >= 400 && !CACHEABLE_ERROR_STATUSES.has(status))
162
180
  return false;
163
181
  const contentType = headers.get("content-type")?.toLowerCase() ?? "";
164
182
  if (contentType.includes("text/html"))
@@ -20,6 +20,6 @@ export declare function createTranscribeVoiceHandler(): import("h3").EventHandle
20
20
  error: string;
21
21
  text?: undefined;
22
22
  } | {
23
- error?: undefined;
24
23
  text: string;
24
+ error?: undefined;
25
25
  }>>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.168.6",
3
+ "version": "0.168.7",
4
4
  "description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {