@agent-native/core 0.109.0 → 0.109.2
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/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +14 -0
- package/corpus/core/docs/content/integrations.mdx +87 -253
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/client.ts +24 -2
- package/corpus/core/src/a2a/correlation.ts +50 -0
- package/corpus/core/src/a2a/handlers.ts +140 -14
- package/corpus/core/src/a2a/index.ts +1 -0
- package/corpus/core/src/a2a/invoke.ts +10 -1
- package/corpus/core/src/a2a/task-store.ts +146 -6
- package/corpus/core/src/a2a/types.ts +16 -0
- package/corpus/core/src/action.ts +2 -0
- package/corpus/core/src/agent/production-agent.ts +23 -0
- package/corpus/core/src/client/blocks/library/diagram.config.ts +10 -1
- package/corpus/core/src/client/blocks/library/diagram.tsx +9 -4
- package/corpus/core/src/client/use-run-stuck-detection.ts +27 -1
- package/corpus/core/src/db/client.ts +55 -0
- package/corpus/core/src/deploy/build.ts +63 -3
- package/corpus/core/src/observability/traces.ts +172 -19
- package/corpus/core/src/scripts/call-agent.ts +53 -2
- package/corpus/core/src/server/agent-chat/action-filters-a2a.ts +72 -25
- package/corpus/core/src/server/agent-chat-plugin.ts +107 -6
- package/corpus/core/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +21 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/observability/SKILL.md +7 -1
- package/corpus/core/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -1
- package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +1 -1
- package/corpus/templates/analytics/changelog/2026-07-17-analytics-now-includes-a-default-alert-for-spikes-in-stuck-agent-chats.md +6 -0
- package/corpus/templates/analytics/docs/schemas/first-party-analytics.md +23 -14
- package/corpus/templates/analytics/docs/schemas/tracked-events.md +5 -1
- package/corpus/templates/analytics/server/jobs/analytics-alerts.ts +3 -3
- package/corpus/templates/analytics/server/lib/analytics-alerts.ts +209 -87
- package/corpus/templates/clips/.agents/skills/dictate/SKILL.md +28 -5
- package/corpus/templates/clips/.agents/skills/meetings/SKILL.md +7 -0
- package/corpus/templates/clips/.agents/skills/recording/SKILL.md +23 -0
- package/corpus/templates/clips/AGENTS.md +10 -0
- package/corpus/templates/clips/actions/create-dictation.ts +29 -4
- package/corpus/templates/clips/actions/finalize-recording.ts +542 -65
- package/corpus/templates/clips/actions/get-recording-player-data.ts +16 -6
- package/corpus/templates/clips/actions/set-mobile-capture-state.ts +37 -0
- package/corpus/templates/clips/actions/update-dictation.ts +9 -1
- package/corpus/templates/clips/app/components/player/playback-comment-overlay.tsx +17 -2
- package/corpus/templates/clips/app/components/player/video-player.tsx +14 -2
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +2 -2
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +14 -3
- package/corpus/templates/clips/app/routes/record.tsx +3 -3
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +19 -5
- package/corpus/templates/clips/changelog/2026-07-17-capture-dictation-background-meeting-audio-and-camera-videos.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-17-clips-now-retries-storage-verification-in-the-background-ins.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-17-delayed-upload-verification-keeps-local-backups-retryable.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-17-fixed-broken-thumbnail-icons-appearing-over-playable-clip-em.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-17-playback-comments-stay-readable-at-faster-playback-speeds.md +6 -0
- package/corpus/templates/clips/chrome-extension/src/finalize-recovery.ts +3 -0
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +3 -3
- package/corpus/templates/clips/desktop/src/app.tsx +35 -6
- package/corpus/templates/clips/desktop/src/lib/processing-backup-recovery.ts +19 -0
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +141 -18
- package/corpus/templates/clips/desktop/src/lib/upload-verification.ts +28 -7
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +3 -4
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen/live_upload.rs +32 -15
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +59 -18
- package/corpus/templates/clips/server/db/schema.ts +9 -1
- package/corpus/templates/clips/server/jobs/media-verification.ts +130 -0
- package/corpus/templates/clips/server/lib/builder-media-compression.ts +74 -7
- package/corpus/templates/clips/server/lib/media-verification-state.ts +55 -0
- package/corpus/templates/clips/server/lib/player-thumbnail-url.ts +31 -0
- package/corpus/templates/clips/server/lib/post-finalize-dispatch.ts +10 -3
- package/corpus/templates/clips/server/plugins/auth.ts +5 -5
- package/corpus/templates/clips/server/plugins/calendar-jobs.ts +2 -0
- package/corpus/templates/clips/server/routes/api/_agent-native-background/post-finalize-worker.post.ts +12 -2
- package/corpus/templates/clips/server/routes/api/public-recording.get.ts +13 -1
- package/corpus/templates/clips/server/routes/api/thumbnail/[recordingId].get.ts +340 -0
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +24 -7
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +91 -7
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/reset-chunks.post.ts +22 -1
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/status.get.ts +14 -0
- package/corpus/templates/clips/shared/finalize-recovery.ts +61 -2
- package/corpus/templates/clips/shared/share-meta.ts +1 -1
- package/corpus/templates/plan/changelog/2026-07-17-diagrams-can-opt-into-a-polished-clean-rendering-mode-withou.md +6 -0
- package/corpus/templates/plan/shared/plan-content.ts +3 -0
- package/dist/a2a/client.d.ts +11 -2
- package/dist/a2a/client.d.ts.map +1 -1
- package/dist/a2a/client.js +16 -3
- package/dist/a2a/client.js.map +1 -1
- package/dist/a2a/correlation.d.ts +10 -0
- package/dist/a2a/correlation.d.ts.map +1 -0
- package/dist/a2a/correlation.js +40 -0
- package/dist/a2a/correlation.js.map +1 -0
- package/dist/a2a/handlers.d.ts.map +1 -1
- package/dist/a2a/handlers.js +100 -15
- package/dist/a2a/handlers.js.map +1 -1
- package/dist/a2a/index.d.ts +1 -1
- package/dist/a2a/index.d.ts.map +1 -1
- package/dist/a2a/index.js.map +1 -1
- package/dist/a2a/invoke.d.ts +4 -1
- package/dist/a2a/invoke.d.ts.map +1 -1
- package/dist/a2a/invoke.js +3 -0
- package/dist/a2a/invoke.js.map +1 -1
- package/dist/a2a/task-store.d.ts +12 -1
- package/dist/a2a/task-store.d.ts.map +1 -1
- package/dist/a2a/task-store.js +96 -6
- package/dist/a2a/task-store.js.map +1 -1
- package/dist/a2a/types.d.ts +15 -0
- package/dist/a2a/types.d.ts.map +1 -1
- package/dist/a2a/types.js.map +1 -1
- package/dist/action.d.ts +2 -0
- package/dist/action.d.ts.map +1 -1
- package/dist/action.js.map +1 -1
- package/dist/agent/production-agent.d.ts +9 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +14 -0
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/client/blocks/library/diagram.config.d.ts +2 -0
- package/dist/client/blocks/library/diagram.config.d.ts.map +1 -1
- package/dist/client/blocks/library/diagram.config.js +8 -1
- package/dist/client/blocks/library/diagram.config.js.map +1 -1
- package/dist/client/blocks/library/diagram.d.ts.map +1 -1
- package/dist/client/blocks/library/diagram.js +9 -4
- package/dist/client/blocks/library/diagram.js.map +1 -1
- package/dist/client/use-run-stuck-detection.d.ts.map +1 -1
- package/dist/client/use-run-stuck-detection.js +25 -1
- package/dist/client/use-run-stuck-detection.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/db/client.d.ts +7 -0
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +33 -0
- package/dist/db/client.js.map +1 -1
- package/dist/deploy/build.d.ts +3 -0
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +45 -1
- package/dist/deploy/build.js.map +1 -1
- package/dist/observability/routes.d.ts +1 -1
- package/dist/observability/traces.d.ts +9 -0
- package/dist/observability/traces.d.ts.map +1 -1
- package/dist/observability/traces.js +137 -19
- package/dist/observability/traces.js.map +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/scripts/call-agent.d.ts.map +1 -1
- package/dist/scripts/call-agent.js +35 -3
- package/dist/scripts/call-agent.js.map +1 -1
- package/dist/server/agent-chat/action-filters-a2a.d.ts +18 -2
- package/dist/server/agent-chat/action-filters-a2a.d.ts.map +1 -1
- package/dist/server/agent-chat/action-filters-a2a.js +35 -7
- package/dist/server/agent-chat/action-filters-a2a.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +93 -6
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +21 -0
- package/dist/templates/workspace-core/.agents/skills/observability/SKILL.md +7 -1
- package/dist/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -1
- package/docs/content/integrations.mdx +87 -253
- package/package.json +1 -1
- package/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +21 -0
- package/src/templates/workspace-core/.agents/skills/observability/SKILL.md +7 -1
- package/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -1
|
@@ -29,6 +29,7 @@ import { and, asc, eq, or, sql } from "drizzle-orm";
|
|
|
29
29
|
import { z } from "zod";
|
|
30
30
|
|
|
31
31
|
import { getDb, schema } from "../server/db/index.js";
|
|
32
|
+
import { isMediaVerificationPending } from "../server/lib/media-verification-state.js";
|
|
32
33
|
import { resolvePlayerVideoUrl } from "../server/lib/player-video-url.js";
|
|
33
34
|
import {
|
|
34
35
|
canOpenDirectRecordingPage,
|
|
@@ -178,12 +179,20 @@ export default defineAction({
|
|
|
178
179
|
access.role === "owner" ||
|
|
179
180
|
access.role === "admin" ||
|
|
180
181
|
access.role === "editor";
|
|
181
|
-
const [cleanupStateRaw, builderCreditsRaw] =
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
182
|
+
const [cleanupStateRaw, builderCreditsRaw, verificationPending] =
|
|
183
|
+
await Promise.all([
|
|
184
|
+
readAppState(`transcript-cleanup-${args.recordingId}`).catch(
|
|
185
|
+
() => null,
|
|
186
|
+
),
|
|
187
|
+
canEditRecording
|
|
188
|
+
? readAppState(CLIPS_BUILDER_CREDITS_STATE_KEY).catch(() => null)
|
|
189
|
+
: Promise.resolve(null),
|
|
190
|
+
isMediaVerificationPending({
|
|
191
|
+
ownerEmail: rec.ownerEmail,
|
|
192
|
+
recordingId: args.recordingId,
|
|
193
|
+
recordingStatus: rec.status,
|
|
194
|
+
}),
|
|
195
|
+
]);
|
|
187
196
|
const cleanupState =
|
|
188
197
|
cleanupStateRaw && typeof cleanupStateRaw === "object"
|
|
189
198
|
? (cleanupStateRaw as Record<string, unknown>)
|
|
@@ -326,6 +335,7 @@ export default defineAction({
|
|
|
326
335
|
hasAudio: Boolean(rec.hasAudio),
|
|
327
336
|
hasCamera: Boolean(rec.hasCamera),
|
|
328
337
|
status: rec.status,
|
|
338
|
+
verificationPending,
|
|
329
339
|
uploadProgress: rec.uploadProgress,
|
|
330
340
|
failureReason: rec.failureReason,
|
|
331
341
|
// Don't leak the password to clients (especially to MCP hosts that
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core/action";
|
|
2
|
+
import { writeAppState } from "@agent-native/core/application-state";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
export const MOBILE_CAPTURE_STATE_KEY = "mobile-capture-state";
|
|
6
|
+
|
|
7
|
+
const mobileCaptureStateSchema = z
|
|
8
|
+
.object({
|
|
9
|
+
view: z.enum(["home", "dictate", "meeting", "video"]),
|
|
10
|
+
phase: z.enum([
|
|
11
|
+
"idle",
|
|
12
|
+
"ready",
|
|
13
|
+
"recording",
|
|
14
|
+
"paused",
|
|
15
|
+
"saving",
|
|
16
|
+
"processing",
|
|
17
|
+
"review",
|
|
18
|
+
"error",
|
|
19
|
+
]),
|
|
20
|
+
captureId: z.string().trim().min(1).max(200).optional(),
|
|
21
|
+
})
|
|
22
|
+
.strict();
|
|
23
|
+
|
|
24
|
+
export default defineAction({
|
|
25
|
+
description:
|
|
26
|
+
"Record the privacy-safe view and phase of the Agent Native mobile capture companion.",
|
|
27
|
+
agentTool: false,
|
|
28
|
+
schema: mobileCaptureStateSchema,
|
|
29
|
+
run: async (args) => {
|
|
30
|
+
const state = {
|
|
31
|
+
...args,
|
|
32
|
+
updatedAt: new Date().toISOString(),
|
|
33
|
+
};
|
|
34
|
+
await writeAppState(MOBILE_CAPTURE_STATE_KEY, state);
|
|
35
|
+
return state;
|
|
36
|
+
},
|
|
37
|
+
});
|
|
@@ -20,7 +20,15 @@ export default defineAction({
|
|
|
20
20
|
fullText: z.string().optional(),
|
|
21
21
|
cleanedText: z.string().nullable().optional(),
|
|
22
22
|
source: z
|
|
23
|
-
.enum([
|
|
23
|
+
.enum([
|
|
24
|
+
"fn-hold",
|
|
25
|
+
"cmd-shift-space",
|
|
26
|
+
"manual",
|
|
27
|
+
"mobile",
|
|
28
|
+
"other",
|
|
29
|
+
"fn",
|
|
30
|
+
"custom",
|
|
31
|
+
])
|
|
24
32
|
.optional(),
|
|
25
33
|
targetApp: z.string().nullable().optional(),
|
|
26
34
|
}),
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
export const PLAYBACK_COMMENT_VISIBLE_MS = 4_000;
|
|
2
2
|
|
|
3
|
+
export function getPlaybackCommentVisibleMs(playbackRate = 1): number {
|
|
4
|
+
const safePlaybackRate =
|
|
5
|
+
Number.isFinite(playbackRate) && playbackRate > 0 ? playbackRate : 1;
|
|
6
|
+
return PLAYBACK_COMMENT_VISIBLE_MS * safePlaybackRate;
|
|
7
|
+
}
|
|
8
|
+
|
|
3
9
|
export interface PlaybackComment {
|
|
4
10
|
id: string;
|
|
5
11
|
content: string;
|
|
@@ -13,11 +19,14 @@ export interface PlaybackComment {
|
|
|
13
19
|
export function getActivePlaybackComments(
|
|
14
20
|
comments: PlaybackComment[] | undefined,
|
|
15
21
|
currentMs: number,
|
|
22
|
+
playbackRate = 1,
|
|
16
23
|
): PlaybackComment[] {
|
|
17
24
|
if (!comments?.length || !Number.isFinite(currentMs) || currentMs < 0) {
|
|
18
25
|
return [];
|
|
19
26
|
}
|
|
20
27
|
|
|
28
|
+
const visibleMs = getPlaybackCommentVisibleMs(playbackRate);
|
|
29
|
+
|
|
21
30
|
return comments
|
|
22
31
|
.filter((comment) => {
|
|
23
32
|
const timestamp = comment.videoTimestampMs;
|
|
@@ -28,7 +37,7 @@ export function getActivePlaybackComments(
|
|
|
28
37
|
Number.isFinite(timestamp) &&
|
|
29
38
|
timestamp >= 0 &&
|
|
30
39
|
currentMs >= timestamp &&
|
|
31
|
-
currentMs < timestamp +
|
|
40
|
+
currentMs < timestamp + visibleMs
|
|
32
41
|
);
|
|
33
42
|
})
|
|
34
43
|
.sort(
|
|
@@ -40,11 +49,17 @@ export function getActivePlaybackComments(
|
|
|
40
49
|
export function PlaybackCommentOverlay({
|
|
41
50
|
comments,
|
|
42
51
|
currentMs,
|
|
52
|
+
playbackRate = 1,
|
|
43
53
|
}: {
|
|
44
54
|
comments: PlaybackComment[] | undefined;
|
|
45
55
|
currentMs: number;
|
|
56
|
+
playbackRate?: number;
|
|
46
57
|
}) {
|
|
47
|
-
const activeComments = getActivePlaybackComments(
|
|
58
|
+
const activeComments = getActivePlaybackComments(
|
|
59
|
+
comments,
|
|
60
|
+
currentMs,
|
|
61
|
+
playbackRate,
|
|
62
|
+
);
|
|
48
63
|
if (activeComments.length === 0) return null;
|
|
49
64
|
|
|
50
65
|
return (
|
|
@@ -318,6 +318,7 @@ export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
|
|
|
318
318
|
// Whether we've already captured-and-uploaded a still-frame thumbnail for
|
|
319
319
|
// this clip. Owner-only and once per player lifecycle.
|
|
320
320
|
const thumbnailCapturedRef = useRef(false);
|
|
321
|
+
const [thumbnailLoadFailed, setThumbnailLoadFailed] = useState(false);
|
|
321
322
|
// "Preparing your clip…" overlay — shown while the browser buffers the
|
|
322
323
|
// first frame of a freshly-finalized clip so the user doesn't see a blank
|
|
323
324
|
// black rectangle. Hidden on loadeddata / canplay / currentTime > 0, or
|
|
@@ -933,6 +934,10 @@ export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
|
|
|
933
934
|
setPlayError(null);
|
|
934
935
|
}, [activeVideoSourceIdentity, recordingId]);
|
|
935
936
|
|
|
937
|
+
useEffect(() => {
|
|
938
|
+
setThumbnailLoadFailed(false);
|
|
939
|
+
}, [recordingId, thumbnailUrl]);
|
|
940
|
+
|
|
936
941
|
useEffect(() => {
|
|
937
942
|
let cancelled = false;
|
|
938
943
|
setShouldRefreshAutoThumbnail(false);
|
|
@@ -1188,10 +1193,11 @@ export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
|
|
|
1188
1193
|
// retry on a format that will never play. Show the poster with a
|
|
1189
1194
|
// clear, non-looping explanation instead.
|
|
1190
1195
|
<div className="relative flex h-full w-full items-center justify-center bg-black">
|
|
1191
|
-
{thumbnailUrl ? (
|
|
1196
|
+
{thumbnailUrl && !thumbnailLoadFailed ? (
|
|
1192
1197
|
<img
|
|
1193
1198
|
src={resolveLocalUrl(thumbnailUrl)}
|
|
1194
1199
|
alt=""
|
|
1200
|
+
onError={() => setThumbnailLoadFailed(true)}
|
|
1195
1201
|
className={cn(
|
|
1196
1202
|
"absolute inset-0 h-full w-full",
|
|
1197
1203
|
cover ? "object-cover" : "object-contain",
|
|
@@ -1447,6 +1453,7 @@ export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
|
|
|
1447
1453
|
)}
|
|
1448
1454
|
|
|
1449
1455
|
{thumbnailUrl &&
|
|
1456
|
+
!thumbnailLoadFailed &&
|
|
1450
1457
|
!autoPlay &&
|
|
1451
1458
|
!hasPlaybackStarted &&
|
|
1452
1459
|
(!startMs || startMs <= 0) ? (
|
|
@@ -1454,6 +1461,7 @@ export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
|
|
|
1454
1461
|
src={resolveLocalUrl(thumbnailUrl)}
|
|
1455
1462
|
alt=""
|
|
1456
1463
|
aria-hidden="true"
|
|
1464
|
+
onError={() => setThumbnailLoadFailed(true)}
|
|
1457
1465
|
className={cn(
|
|
1458
1466
|
"pointer-events-none absolute inset-0 z-[1] h-full w-full",
|
|
1459
1467
|
cover ? "object-cover" : "object-contain",
|
|
@@ -1495,7 +1503,11 @@ export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
|
|
|
1495
1503
|
|
|
1496
1504
|
{/* Timestamped comments */}
|
|
1497
1505
|
{!hideChrome && !isLoomEmbed && hasPlaybackStarted ? (
|
|
1498
|
-
<PlaybackCommentOverlay
|
|
1506
|
+
<PlaybackCommentOverlay
|
|
1507
|
+
comments={comments}
|
|
1508
|
+
currentMs={currentMs}
|
|
1509
|
+
playbackRate={speed}
|
|
1510
|
+
/>
|
|
1499
1511
|
) : null}
|
|
1500
1512
|
|
|
1501
1513
|
{/* Floating CTA (throughout placement) */}
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
captureClientException,
|
|
12
12
|
trackEvent,
|
|
13
13
|
} from "@agent-native/core/client";
|
|
14
|
-
import {
|
|
14
|
+
import { waitForAcceptedRecordingAfterFinalizeError } from "@shared/finalize-recovery";
|
|
15
15
|
import {
|
|
16
16
|
chooseFallbackAudioInput,
|
|
17
17
|
enumerateAudioInputDevices,
|
|
@@ -2231,7 +2231,7 @@ export class RecorderEngine {
|
|
|
2231
2231
|
private async recoverReadyAfterFinalUploadError(
|
|
2232
2232
|
signal?: AbortSignal,
|
|
2233
2233
|
): Promise<Record<string, unknown> | null> {
|
|
2234
|
-
return
|
|
2234
|
+
return waitForAcceptedRecordingAfterFinalizeError({
|
|
2235
2235
|
uploadUrl: this.opts.uploadUrl,
|
|
2236
2236
|
recordingId: this.opts.recordingId,
|
|
2237
2237
|
preferAuthenticated: true,
|
|
@@ -92,7 +92,7 @@ import { cn } from "@/lib/utils";
|
|
|
92
92
|
import { STALE_PENDING_TRANSCRIPT_REASON } from "../../shared/transcript-status";
|
|
93
93
|
|
|
94
94
|
const UPLOAD_STUCK_TIMEOUT_MS = 5 * 60 * 1000;
|
|
95
|
-
const PROCESSING_STUCK_TIMEOUT_MS =
|
|
95
|
+
const PROCESSING_STUCK_TIMEOUT_MS = 12 * 60 * 1000;
|
|
96
96
|
const READY_MEDIA_SETTLE_POLL_MS = 20 * 1000;
|
|
97
97
|
const READY_MEDIA_SETTLE_POLL_INTERVAL_MS = 1000;
|
|
98
98
|
|
|
@@ -376,6 +376,7 @@ export default function RecordingPage() {
|
|
|
376
376
|
}, [recordingId, playerDataForbidden, navigate]);
|
|
377
377
|
|
|
378
378
|
const recording = playerDataQ.data?.recording;
|
|
379
|
+
const verificationPending = recording?.verificationPending === true;
|
|
379
380
|
const role = playerDataQ.data?.role as
|
|
380
381
|
| "owner"
|
|
381
382
|
| "admin"
|
|
@@ -781,13 +782,22 @@ export default function RecordingPage() {
|
|
|
781
782
|
setProcessingTimeout(false);
|
|
782
783
|
return;
|
|
783
784
|
}
|
|
785
|
+
if (verificationPending) {
|
|
786
|
+
setProcessingTimeout(false);
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
784
789
|
const timeoutMs =
|
|
785
790
|
recording.status === "processing"
|
|
786
791
|
? PROCESSING_STUCK_TIMEOUT_MS
|
|
787
792
|
: UPLOAD_STUCK_TIMEOUT_MS;
|
|
788
793
|
const handle = setTimeout(() => setProcessingTimeout(true), timeoutMs);
|
|
789
794
|
return () => clearTimeout(handle);
|
|
790
|
-
}, [
|
|
795
|
+
}, [
|
|
796
|
+
recording?.status,
|
|
797
|
+
recording?.videoUrl,
|
|
798
|
+
recordingId,
|
|
799
|
+
verificationPending,
|
|
800
|
+
]);
|
|
791
801
|
|
|
792
802
|
usePlayerShortcuts({ playerRef, chapters });
|
|
793
803
|
|
|
@@ -873,7 +883,8 @@ export default function RecordingPage() {
|
|
|
873
883
|
isNativeSaveFailureReason(rawFailureReason);
|
|
874
884
|
// Give a long-running desktop save an actionable recovery state without
|
|
875
885
|
// claiming the upload failed while its bounded final request is still live.
|
|
876
|
-
const stuckFailure =
|
|
886
|
+
const stuckFailure =
|
|
887
|
+
!explicitFailure && !verificationPending && processingTimeout;
|
|
877
888
|
const isFailure = explicitFailure || waitingForStorage || nativeSaveFailed;
|
|
878
889
|
const showRecoveryState = isFailure || stuckFailure;
|
|
879
890
|
const displayReason = explicitFailure
|
|
@@ -9,7 +9,7 @@ import { useLiveTranscription } from "@agent-native/core/client/transcription/us
|
|
|
9
9
|
import type { BrowserDiagnosticsData } from "@shared/browser-diagnostics";
|
|
10
10
|
import {
|
|
11
11
|
isStoredButUnservableFinalizeError,
|
|
12
|
-
|
|
12
|
+
waitForAcceptedRecordingAfterFinalizeError,
|
|
13
13
|
} from "@shared/finalize-recovery";
|
|
14
14
|
import {
|
|
15
15
|
chunkUploadParallelism,
|
|
@@ -1733,7 +1733,7 @@ export default function RecordRoute() {
|
|
|
1733
1733
|
createdId &&
|
|
1734
1734
|
(err as { name?: string } | null)?.name !== "AbortError"
|
|
1735
1735
|
) {
|
|
1736
|
-
const recovered = await
|
|
1736
|
+
const recovered = await waitForAcceptedRecordingAfterFinalizeError({
|
|
1737
1737
|
uploadUrl: uploadBase,
|
|
1738
1738
|
recordingId: createdId,
|
|
1739
1739
|
preferAuthenticated: true,
|
|
@@ -1764,7 +1764,7 @@ export default function RecordRoute() {
|
|
|
1764
1764
|
chunkRes.status !== 413 &&
|
|
1765
1765
|
!isUploadSizeError(error.message)
|
|
1766
1766
|
) {
|
|
1767
|
-
const recovered = await
|
|
1767
|
+
const recovered = await waitForAcceptedRecordingAfterFinalizeError({
|
|
1768
1768
|
uploadUrl: uploadBase,
|
|
1769
1769
|
recordingId: createdId,
|
|
1770
1770
|
preferAuthenticated: true,
|
|
@@ -67,6 +67,7 @@ import { parsePlaybackSpeed } from "@/lib/playback-speed";
|
|
|
67
67
|
import { isStorageSetupFailureReason } from "@/lib/storage-failures";
|
|
68
68
|
|
|
69
69
|
import { getDb, schema } from "../../server/db";
|
|
70
|
+
import { resolvePlayerThumbnailUrl } from "../../server/lib/player-thumbnail-url";
|
|
70
71
|
import {
|
|
71
72
|
buildAgentApiUrls,
|
|
72
73
|
CLIPS_AGENT_ACCESS_PARAM,
|
|
@@ -226,8 +227,10 @@ export async function loader({ params, url }: LoaderFunctionArgs) {
|
|
|
226
227
|
id: rec.id,
|
|
227
228
|
title: rec.title,
|
|
228
229
|
description: rec.description,
|
|
229
|
-
thumbnailUrl: rec.
|
|
230
|
-
|
|
230
|
+
thumbnailUrl: rec.password
|
|
231
|
+
? null
|
|
232
|
+
: resolvePlayerThumbnailUrl(rec, { appPath }),
|
|
233
|
+
animatedThumbnailUrl: null,
|
|
231
234
|
visibility: rec.visibility,
|
|
232
235
|
status: rec.status,
|
|
233
236
|
archivedAt: rec.archivedAt,
|
|
@@ -286,7 +289,7 @@ const CLIPS_TEMPLATE_URL = "https://www.agent-native.com/templates/clips";
|
|
|
286
289
|
const CLIPS_AGENT_DOCS_URL =
|
|
287
290
|
"https://www.agent-native.com/docs/template-clips#agent-readable-clips";
|
|
288
291
|
const UPLOAD_STUCK_TIMEOUT_MS = 5 * 60 * 1000;
|
|
289
|
-
const PROCESSING_STUCK_TIMEOUT_MS =
|
|
292
|
+
const PROCESSING_STUCK_TIMEOUT_MS = 12 * 60 * 1000;
|
|
290
293
|
|
|
291
294
|
type ViewerPlatform = "mac" | "windows" | "linux";
|
|
292
295
|
|
|
@@ -475,6 +478,7 @@ export default function ShareRoute() {
|
|
|
475
478
|
});
|
|
476
479
|
|
|
477
480
|
const recording = dataQ.data?.data?.recording;
|
|
481
|
+
const verificationPending = recording?.verificationPending === true;
|
|
478
482
|
const comments = dataQ.data?.data?.comments ?? [];
|
|
479
483
|
const reactions = dataQ.data?.data?.reactions ?? [];
|
|
480
484
|
const chapters = dataQ.data?.data?.chapters ?? [];
|
|
@@ -557,6 +561,10 @@ export default function ShareRoute() {
|
|
|
557
561
|
setProcessingTimeout(false);
|
|
558
562
|
return;
|
|
559
563
|
}
|
|
564
|
+
if (verificationPending) {
|
|
565
|
+
setProcessingTimeout(false);
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
560
568
|
|
|
561
569
|
const timeoutMs =
|
|
562
570
|
recording.status === "processing"
|
|
@@ -564,7 +572,12 @@ export default function ShareRoute() {
|
|
|
564
572
|
: UPLOAD_STUCK_TIMEOUT_MS;
|
|
565
573
|
const handle = setTimeout(() => setProcessingTimeout(true), timeoutMs);
|
|
566
574
|
return () => clearTimeout(handle);
|
|
567
|
-
}, [
|
|
575
|
+
}, [
|
|
576
|
+
recording?.id,
|
|
577
|
+
recording?.status,
|
|
578
|
+
recording?.videoUrl,
|
|
579
|
+
verificationPending,
|
|
580
|
+
]);
|
|
568
581
|
|
|
569
582
|
usePlayerShortcuts({ playerRef });
|
|
570
583
|
|
|
@@ -709,7 +722,8 @@ export default function ShareRoute() {
|
|
|
709
722
|
const storageSetupFailure = isStorageSetupFailureReason(rawFailureReason);
|
|
710
723
|
const loomStorageSetupFailure =
|
|
711
724
|
storageSetupFailure && isLoomRecordingSource(recording);
|
|
712
|
-
const stuckFailure =
|
|
725
|
+
const stuckFailure =
|
|
726
|
+
!explicitFailure && !verificationPending && processingTimeout;
|
|
713
727
|
const isFailure = explicitFailure || storageSetupFailure || stuckFailure;
|
|
714
728
|
const canManageStorage = viewerCanEdit;
|
|
715
729
|
const signInHref = buildSignInHref(`/r/${recording.id}`);
|
|
@@ -2,6 +2,9 @@ export {
|
|
|
2
2
|
authenticatedRecordingStatusUrl,
|
|
3
3
|
publicRecordingStatusUrl,
|
|
4
4
|
readyRecordingFromPublicPayload,
|
|
5
|
+
waitForAcceptedRecordingAfterFinalizeError,
|
|
5
6
|
waitForReadyRecordingAfterFinalizeError,
|
|
7
|
+
type RecoveredFinalizeRecording,
|
|
8
|
+
type RecoveredProcessingRecording,
|
|
6
9
|
type RecoveredReadyRecording,
|
|
7
10
|
} from "@shared/finalize-recovery";
|
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
} from "@shared/recording-core";
|
|
36
36
|
import { MAX_UPLOAD_BYTES } from "@shared/upload-limits";
|
|
37
37
|
|
|
38
|
-
import {
|
|
38
|
+
import { waitForAcceptedRecordingAfterFinalizeError } from "./finalize-recovery";
|
|
39
39
|
import {
|
|
40
40
|
createNativeTranscriptionCapture,
|
|
41
41
|
type NativeTranscriptionCapture,
|
|
@@ -875,7 +875,7 @@ async function uploadChunk(
|
|
|
875
875
|
if (extra.isFinal && res.status === 504) {
|
|
876
876
|
triedFinalUploadRecovery = true;
|
|
877
877
|
await res.text().catch(() => "");
|
|
878
|
-
const recovered = await
|
|
878
|
+
const recovered = await waitForAcceptedRecordingAfterFinalizeError({
|
|
879
879
|
uploadUrl: recording.uploadUrl,
|
|
880
880
|
recordingId: recording.recordingId,
|
|
881
881
|
authToken: recording.authToken,
|
|
@@ -1619,7 +1619,7 @@ async function finalizeStop(recording: ActiveRecording): Promise<void> {
|
|
|
1619
1619
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
1620
1620
|
|
|
1621
1621
|
if (isFinalUploadRecoveryCandidate(error)) {
|
|
1622
|
-
const recovered = await
|
|
1622
|
+
const recovered = await waitForAcceptedRecordingAfterFinalizeError({
|
|
1623
1623
|
uploadUrl: recording.uploadUrl,
|
|
1624
1624
|
recordingId: recording.recordingId,
|
|
1625
1625
|
authToken: recording.authToken,
|
|
@@ -70,6 +70,7 @@ import {
|
|
|
70
70
|
exportBrowserRecordingBackup,
|
|
71
71
|
listBrowserRecordingBackups,
|
|
72
72
|
retryBrowserRecordingBackup,
|
|
73
|
+
scheduleNativeBackupCleanupAfterProcessing,
|
|
73
74
|
shouldUseNativeFullscreenRecording,
|
|
74
75
|
startRecording,
|
|
75
76
|
type LocalExportedFile,
|
|
@@ -1834,6 +1835,23 @@ export function App() {
|
|
|
1834
1835
|
);
|
|
1835
1836
|
}, []);
|
|
1836
1837
|
|
|
1838
|
+
useEffect(() => {
|
|
1839
|
+
let unlisten: (() => void) | null = null;
|
|
1840
|
+
let cancelled = false;
|
|
1841
|
+
listen("clips:pending-uploads-changed", () => {
|
|
1842
|
+
void loadPendingUploads();
|
|
1843
|
+
})
|
|
1844
|
+
.then((stop) => {
|
|
1845
|
+
if (cancelled) stop();
|
|
1846
|
+
else unlisten = stop;
|
|
1847
|
+
})
|
|
1848
|
+
.catch(() => {});
|
|
1849
|
+
return () => {
|
|
1850
|
+
cancelled = true;
|
|
1851
|
+
unlisten?.();
|
|
1852
|
+
};
|
|
1853
|
+
}, [loadPendingUploads]);
|
|
1854
|
+
|
|
1837
1855
|
useEffect(() => {
|
|
1838
1856
|
if (popoverView === "meetings" && popoverVisible) {
|
|
1839
1857
|
void fetchUpcomingMeetings();
|
|
@@ -1894,12 +1912,23 @@ export function App() {
|
|
|
1894
1912
|
try {
|
|
1895
1913
|
const authToken = loadDesktopAuthToken(targetServerUrl);
|
|
1896
1914
|
if (upload.kind === "native") {
|
|
1897
|
-
await invoke(
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1915
|
+
const result = await invoke<{ verificationPending?: boolean }>(
|
|
1916
|
+
"native_fullscreen_recording_retry_upload",
|
|
1917
|
+
{
|
|
1918
|
+
serverUrl: targetServerUrl,
|
|
1919
|
+
recordingId: upload.recordingId,
|
|
1920
|
+
authToken,
|
|
1921
|
+
cookie:
|
|
1922
|
+
typeof document !== "undefined" ? document.cookie || "" : "",
|
|
1923
|
+
},
|
|
1924
|
+
);
|
|
1925
|
+
if (result.verificationPending) {
|
|
1926
|
+
scheduleNativeBackupCleanupAfterProcessing({
|
|
1927
|
+
serverUrl: targetServerUrl,
|
|
1928
|
+
recordingId: upload.recordingId,
|
|
1929
|
+
authToken,
|
|
1930
|
+
});
|
|
1931
|
+
}
|
|
1903
1932
|
} else {
|
|
1904
1933
|
await retryBrowserRecordingBackup({
|
|
1905
1934
|
recordingId: upload.recordingId,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export async function reconcileProcessingBackup(args: {
|
|
2
|
+
waitForReady: () => Promise<{ status?: string } | null>;
|
|
3
|
+
onReady: () => Promise<unknown>;
|
|
4
|
+
onUnresolved: () => Promise<unknown>;
|
|
5
|
+
onPollError?: (error: unknown) => void;
|
|
6
|
+
}): Promise<"ready" | "unresolved"> {
|
|
7
|
+
try {
|
|
8
|
+
const recovered = await args.waitForReady();
|
|
9
|
+
if (recovered?.status === "ready") {
|
|
10
|
+
await args.onReady();
|
|
11
|
+
return "ready";
|
|
12
|
+
}
|
|
13
|
+
} catch (error) {
|
|
14
|
+
args.onPollError?.(error);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
await args.onUnresolved();
|
|
18
|
+
return "unresolved";
|
|
19
|
+
}
|