@agent-native/core 0.109.1 → 0.109.3
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 +15 -0
- package/corpus/core/docs/content/integrations.mdx +87 -253
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/cli/create.ts +18 -12
- 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/require-session.tsx +19 -3
- package/corpus/core/src/client/use-action.ts +145 -2
- package/corpus/core/src/client/use-session.ts +79 -48
- package/corpus/core/src/db/client.ts +13 -7
- package/corpus/core/src/db/create-get-db.ts +20 -13
- package/corpus/core/src/db/request-telemetry.ts +154 -0
- package/corpus/core/src/deploy/build.ts +63 -3
- package/corpus/core/src/integrations/index.ts +1 -0
- package/corpus/core/src/integrations/plugin.ts +9 -3
- package/corpus/core/src/integrations/remote-push-delivery-job.ts +48 -0
- package/corpus/core/src/integrations/remote-push-delivery.ts +370 -0
- package/corpus/core/src/integrations/remote-push-store.ts +275 -26
- package/corpus/core/src/server/better-auth-instance.ts +11 -3
- package/corpus/core/src/server/core-routes-plugin.ts +36 -14
- package/corpus/core/src/server/framework-request-handler.ts +11 -1
- package/corpus/core/src/server/http-response-telemetry.ts +233 -31
- package/corpus/core/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +19 -0
- 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 +1 -1
- package/corpus/templates/clips/actions/get-recording-player-data.ts +16 -6
- package/corpus/templates/clips/actions/set-mobile-capture-state.ts +40 -0
- package/corpus/templates/clips/actions/update-dictation.ts +9 -1
- package/corpus/templates/clips/app/components/library/library-layout.tsx +1 -6
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +13 -2
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +13 -2
- 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-delayed-upload-verification-keeps-local-backups-retryable.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-18-new-recording-is-now-emphasized-as-the-primary-action-in-the.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-18-recordings-now-remain-safely-recoverable-until-the-stored-me.md +6 -0
- package/corpus/templates/clips/desktop/src/app.tsx +17 -0
- package/corpus/templates/clips/desktop/src/lib/processing-backup-recovery.ts +19 -0
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +60 -35
- package/corpus/templates/clips/desktop/src/lib/upload-verification.ts +12 -0
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +1 -0
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +14 -0
- package/corpus/templates/clips/server/db/schema.ts +9 -1
- package/corpus/templates/clips/server/lib/builder-media-compression.ts +59 -7
- package/corpus/templates/clips/server/lib/media-verification-state.ts +17 -0
- package/corpus/templates/clips/server/routes/api/public-recording.get.ts +7 -0
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/reset-chunks.post.ts +22 -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/corpus/templates/tasks/AGENTS.md +3 -0
- package/corpus/templates/tasks/actions/render-task-list-inline.ts +202 -0
- package/corpus/templates/tasks/changelog/2026-07-18-task-lists-can-now-appear-as-interactive-widgets-inline-in-c.md +6 -0
- package/corpus/templates/tasks/docs/features/f8-chat.mdx +2 -0
- package/corpus/templates/tasks/server/plugins/agent-chat.ts +2 -0
- package/dist/cli/create.d.ts +10 -1
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +17 -13
- package/dist/cli/create.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/require-session.d.ts.map +1 -1
- package/dist/client/require-session.js +6 -3
- package/dist/client/require-session.js.map +1 -1
- package/dist/client/use-action.d.ts.map +1 -1
- package/dist/client/use-action.js +117 -1
- package/dist/client/use-action.js.map +1 -1
- package/dist/client/use-session.d.ts.map +1 -1
- package/dist/client/use-session.js +72 -50
- package/dist/client/use-session.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +7 -1
- package/dist/db/client.js.map +1 -1
- package/dist/db/create-get-db.d.ts +3 -6
- package/dist/db/create-get-db.d.ts.map +1 -1
- package/dist/db/create-get-db.js +9 -2
- package/dist/db/create-get-db.js.map +1 -1
- package/dist/db/request-telemetry.d.ts +23 -0
- package/dist/db/request-telemetry.d.ts.map +1 -0
- package/dist/db/request-telemetry.js +95 -0
- package/dist/db/request-telemetry.js.map +1 -0
- 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/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/integrations/index.d.ts +1 -0
- package/dist/integrations/index.d.ts.map +1 -1
- package/dist/integrations/index.js +1 -0
- package/dist/integrations/index.js.map +1 -1
- package/dist/integrations/plugin.d.ts.map +1 -1
- package/dist/integrations/plugin.js +8 -3
- package/dist/integrations/plugin.js.map +1 -1
- package/dist/integrations/remote-push-delivery-job.d.ts +4 -0
- package/dist/integrations/remote-push-delivery-job.d.ts.map +1 -0
- package/dist/integrations/remote-push-delivery-job.js +44 -0
- package/dist/integrations/remote-push-delivery-job.js.map +1 -0
- package/dist/integrations/remote-push-delivery.d.ts +11 -0
- package/dist/integrations/remote-push-delivery.d.ts.map +1 -0
- package/dist/integrations/remote-push-delivery.js +265 -0
- package/dist/integrations/remote-push-delivery.js.map +1 -0
- package/dist/integrations/remote-push-store.d.ts +33 -0
- package/dist/integrations/remote-push-store.d.ts.map +1 -1
- package/dist/integrations/remote-push-store.js +195 -26
- package/dist/integrations/remote-push-store.js.map +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/server/better-auth-instance.js +7 -3
- package/dist/server/better-auth-instance.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts +8 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +23 -8
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/framework-request-handler.d.ts.map +1 -1
- package/dist/server/framework-request-handler.js +9 -1
- package/dist/server/framework-request-handler.js.map +1 -1
- package/dist/server/http-response-telemetry.d.ts +3 -2
- package/dist/server/http-response-telemetry.d.ts.map +1 -1
- package/dist/server/http-response-telemetry.js +160 -27
- package/dist/server/http-response-telemetry.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/templates/workspace-core/.agents/skills/tracking/SKILL.md +19 -0
- package/docs/content/integrations.mdx +87 -253
- package/package.json +1 -1
- package/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +19 -0
|
@@ -384,12 +384,7 @@ export function LibraryLayout({ children }: LibraryLayoutProps) {
|
|
|
384
384
|
) : (
|
|
385
385
|
<>
|
|
386
386
|
<div className="px-3 py-3">
|
|
387
|
-
<Button
|
|
388
|
-
className="w-full gap-1.5"
|
|
389
|
-
variant="outline"
|
|
390
|
-
size="sm"
|
|
391
|
-
asChild
|
|
392
|
-
>
|
|
387
|
+
<Button className="w-full gap-1.5" size="sm" asChild>
|
|
393
388
|
<NavLink to="/record">
|
|
394
389
|
<IconPlayerRecord className="h-4 w-4" />
|
|
395
390
|
{t("navigation.newRecording")}
|
|
@@ -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
|
|
@@ -478,6 +478,7 @@ export default function ShareRoute() {
|
|
|
478
478
|
});
|
|
479
479
|
|
|
480
480
|
const recording = dataQ.data?.data?.recording;
|
|
481
|
+
const verificationPending = recording?.verificationPending === true;
|
|
481
482
|
const comments = dataQ.data?.data?.comments ?? [];
|
|
482
483
|
const reactions = dataQ.data?.data?.reactions ?? [];
|
|
483
484
|
const chapters = dataQ.data?.data?.chapters ?? [];
|
|
@@ -560,6 +561,10 @@ export default function ShareRoute() {
|
|
|
560
561
|
setProcessingTimeout(false);
|
|
561
562
|
return;
|
|
562
563
|
}
|
|
564
|
+
if (verificationPending) {
|
|
565
|
+
setProcessingTimeout(false);
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
563
568
|
|
|
564
569
|
const timeoutMs =
|
|
565
570
|
recording.status === "processing"
|
|
@@ -567,7 +572,12 @@ export default function ShareRoute() {
|
|
|
567
572
|
: UPLOAD_STUCK_TIMEOUT_MS;
|
|
568
573
|
const handle = setTimeout(() => setProcessingTimeout(true), timeoutMs);
|
|
569
574
|
return () => clearTimeout(handle);
|
|
570
|
-
}, [
|
|
575
|
+
}, [
|
|
576
|
+
recording?.id,
|
|
577
|
+
recording?.status,
|
|
578
|
+
recording?.videoUrl,
|
|
579
|
+
verificationPending,
|
|
580
|
+
]);
|
|
571
581
|
|
|
572
582
|
usePlayerShortcuts({ playerRef });
|
|
573
583
|
|
|
@@ -712,7 +722,8 @@ export default function ShareRoute() {
|
|
|
712
722
|
const storageSetupFailure = isStorageSetupFailureReason(rawFailureReason);
|
|
713
723
|
const loomStorageSetupFailure =
|
|
714
724
|
storageSetupFailure && isLoomRecordingSource(recording);
|
|
715
|
-
const stuckFailure =
|
|
725
|
+
const stuckFailure =
|
|
726
|
+
!explicitFailure && !verificationPending && processingTimeout;
|
|
716
727
|
const isFailure = explicitFailure || storageSetupFailure || stuckFailure;
|
|
717
728
|
const canManageStorage = viewerCanEdit;
|
|
718
729
|
const signInHref = buildSignInHref(`/r/${recording.id}`);
|
|
@@ -1835,6 +1835,23 @@ export function App() {
|
|
|
1835
1835
|
);
|
|
1836
1836
|
}, []);
|
|
1837
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
|
+
|
|
1838
1855
|
useEffect(() => {
|
|
1839
1856
|
if (popoverView === "meetings" && popoverVisible) {
|
|
1840
1857
|
void fetchUpcomingMeetings();
|
|
@@ -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
|
+
}
|
|
@@ -78,6 +78,7 @@ import {
|
|
|
78
78
|
createPauseTransitionQueue,
|
|
79
79
|
type PauseTransitionQueue,
|
|
80
80
|
} from "./pause-transition";
|
|
81
|
+
import { reconcileProcessingBackup } from "./processing-backup-recovery";
|
|
81
82
|
import { buildCaptureTitle, type CaptureTitleResult } from "./recording-title";
|
|
82
83
|
import { singleFlight } from "./single-flight";
|
|
83
84
|
import {
|
|
@@ -86,6 +87,7 @@ import {
|
|
|
86
87
|
type TranscriptionCapture,
|
|
87
88
|
} from "./transcription-capture";
|
|
88
89
|
import {
|
|
90
|
+
parseFinalizeReceipt,
|
|
89
91
|
verifyFinalizeReceipt,
|
|
90
92
|
type FinalizeReceipt,
|
|
91
93
|
} from "./upload-verification";
|
|
@@ -744,6 +746,7 @@ export async function dismissBrowserRecordingBackup(
|
|
|
744
746
|
async function markBrowserRecordingBackupError(
|
|
745
747
|
recordingId: string,
|
|
746
748
|
error: string,
|
|
749
|
+
incrementRetryCount = true,
|
|
747
750
|
): Promise<void> {
|
|
748
751
|
const meta = await getBrowserRecordingBackupMeta(recordingId);
|
|
749
752
|
if (!meta) return;
|
|
@@ -751,7 +754,26 @@ async function markBrowserRecordingBackupError(
|
|
|
751
754
|
...meta,
|
|
752
755
|
lastAttemptAt: new Date().toISOString(),
|
|
753
756
|
lastError: error,
|
|
754
|
-
retryCount: meta.retryCount + 1,
|
|
757
|
+
retryCount: incrementRetryCount ? meta.retryCount + 1 : meta.retryCount,
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
const MEDIA_VERIFICATION_BACKUP_ERROR =
|
|
762
|
+
"The server could not finish verifying this upload. The local copy is still saved; retry to continue.";
|
|
763
|
+
|
|
764
|
+
async function flagBrowserBackupAfterProcessing(recordingId: string) {
|
|
765
|
+
await markBrowserRecordingBackupError(
|
|
766
|
+
recordingId,
|
|
767
|
+
MEDIA_VERIFICATION_BACKUP_ERROR,
|
|
768
|
+
false,
|
|
769
|
+
);
|
|
770
|
+
await emit("clips:pending-uploads-changed").catch(() => {});
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
async function flagNativeBackupAfterProcessing(recordingId: string) {
|
|
774
|
+
await invoke("native_fullscreen_recording_mark_upload_error", {
|
|
775
|
+
recordingId,
|
|
776
|
+
error: MEDIA_VERIFICATION_BACKUP_ERROR,
|
|
755
777
|
});
|
|
756
778
|
}
|
|
757
779
|
|
|
@@ -760,24 +782,26 @@ function scheduleBrowserBackupCleanupAfterProcessing(args: {
|
|
|
760
782
|
recordingId: string;
|
|
761
783
|
authToken?: string;
|
|
762
784
|
}): void {
|
|
763
|
-
void
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
.catch((err) => {
|
|
785
|
+
void reconcileProcessingBackup({
|
|
786
|
+
waitForReady: () =>
|
|
787
|
+
waitForReadyRecordingAfterFinalizeError({
|
|
788
|
+
uploadUrl: chunkUrl(args.serverUrl, args.recordingId, 0, false),
|
|
789
|
+
recordingId: args.recordingId,
|
|
790
|
+
authToken: args.authToken,
|
|
791
|
+
preferAuthenticated: true,
|
|
792
|
+
timeoutMs: 12 * 60 * 1000,
|
|
793
|
+
}),
|
|
794
|
+
onReady: () => deleteBrowserRecordingBackup(args.recordingId),
|
|
795
|
+
onUnresolved: () => flagBrowserBackupAfterProcessing(args.recordingId),
|
|
796
|
+
onPollError: (err) => {
|
|
776
797
|
console.warn(
|
|
777
798
|
"[clips-recorder] background backup cleanup check failed:",
|
|
778
799
|
err,
|
|
779
800
|
);
|
|
780
|
-
}
|
|
801
|
+
},
|
|
802
|
+
}).catch((err) => {
|
|
803
|
+
console.warn("[clips-recorder] background backup flag failed:", err);
|
|
804
|
+
});
|
|
781
805
|
}
|
|
782
806
|
|
|
783
807
|
export function scheduleNativeBackupCleanupAfterProcessing(args: {
|
|
@@ -785,23 +809,26 @@ export function scheduleNativeBackupCleanupAfterProcessing(args: {
|
|
|
785
809
|
recordingId: string;
|
|
786
810
|
authToken?: string;
|
|
787
811
|
}): void {
|
|
788
|
-
void
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
|
|
812
|
+
void reconcileProcessingBackup({
|
|
813
|
+
waitForReady: () =>
|
|
814
|
+
waitForReadyRecordingAfterFinalizeError({
|
|
815
|
+
uploadUrl: chunkUrl(args.serverUrl, args.recordingId, 0, false),
|
|
816
|
+
recordingId: args.recordingId,
|
|
817
|
+
authToken: args.authToken,
|
|
818
|
+
preferAuthenticated: true,
|
|
819
|
+
timeoutMs: 12 * 60 * 1000,
|
|
820
|
+
}),
|
|
821
|
+
onReady: () =>
|
|
822
|
+
invoke("native_fullscreen_recording_dismiss_upload", {
|
|
823
|
+
recordingId: args.recordingId,
|
|
824
|
+
}),
|
|
825
|
+
onUnresolved: () => flagNativeBackupAfterProcessing(args.recordingId),
|
|
826
|
+
onPollError: (err) => {
|
|
803
827
|
console.warn("[clips-recorder] native backup cleanup check failed:", err);
|
|
804
|
-
}
|
|
828
|
+
},
|
|
829
|
+
}).catch((err) => {
|
|
830
|
+
console.warn("[clips-recorder] native backup flag failed:", err);
|
|
831
|
+
});
|
|
805
832
|
}
|
|
806
833
|
|
|
807
834
|
async function recoverAcceptedRecordingAfterFinalizeError({
|
|
@@ -884,7 +911,7 @@ async function postBackupChunk(
|
|
|
884
911
|
`Upload retry failed (${res.status}): ${body.slice(0, 200)}`,
|
|
885
912
|
);
|
|
886
913
|
}
|
|
887
|
-
return
|
|
914
|
+
return parseFinalizeReceipt(body);
|
|
888
915
|
}
|
|
889
916
|
|
|
890
917
|
async function resetBrowserRecordingBackupUpload(
|
|
@@ -3965,9 +3992,7 @@ async function startRecordingInner(
|
|
|
3965
3992
|
`Finalize failed (${finalRes.status}): ${bodyText.slice(0, 200)}`,
|
|
3966
3993
|
);
|
|
3967
3994
|
}
|
|
3968
|
-
const receipt = bodyText
|
|
3969
|
-
? (JSON.parse(bodyText) as FinalizeReceipt)
|
|
3970
|
-
: null;
|
|
3995
|
+
const receipt = parseFinalizeReceipt(bodyText);
|
|
3971
3996
|
const receiptStatus = verifyFinalizeReceipt(receipt, {
|
|
3972
3997
|
bytes: backupBytes,
|
|
3973
3998
|
durationMs,
|
|
@@ -14,6 +14,18 @@ export interface LocalRecordingProof {
|
|
|
14
14
|
|
|
15
15
|
export type FinalizeReceiptStatus = "processing" | "ready";
|
|
16
16
|
|
|
17
|
+
export function parseFinalizeReceipt(body: string): FinalizeReceipt | null {
|
|
18
|
+
if (!body) return null;
|
|
19
|
+
try {
|
|
20
|
+
const parsed: unknown = JSON.parse(body);
|
|
21
|
+
return parsed && typeof parsed === "object"
|
|
22
|
+
? (parsed as FinalizeReceipt)
|
|
23
|
+
: null;
|
|
24
|
+
} catch {
|
|
25
|
+
throw new Error("Upload returned an invalid finalization response");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
17
29
|
export function verifyFinalizeReceipt(
|
|
18
30
|
receipt: FinalizeReceipt | null,
|
|
19
31
|
local: LocalRecordingProof,
|
|
@@ -141,6 +141,7 @@ pub fn run() {
|
|
|
141
141
|
native_screen::native_fullscreen_recording_rotate_segment,
|
|
142
142
|
native_screen::native_fullscreen_pending_uploads,
|
|
143
143
|
native_screen::native_fullscreen_recording_retry_upload,
|
|
144
|
+
native_screen::native_fullscreen_recording_mark_upload_error,
|
|
144
145
|
native_screen::native_fullscreen_recording_dismiss_upload,
|
|
145
146
|
native_screen::native_fullscreen_open_drafts_folder,
|
|
146
147
|
// local-only always-on screen memory compatibility helpers
|
|
@@ -2626,6 +2626,20 @@ pub async fn native_fullscreen_recording_retry_upload(
|
|
|
2626
2626
|
}
|
|
2627
2627
|
}
|
|
2628
2628
|
|
|
2629
|
+
#[tauri::command]
|
|
2630
|
+
pub async fn native_fullscreen_recording_mark_upload_error(
|
|
2631
|
+
app: AppHandle,
|
|
2632
|
+
recording_id: String,
|
|
2633
|
+
error: String,
|
|
2634
|
+
) -> Result<(), String> {
|
|
2635
|
+
let mut saved = read_saved_recording_metadata(&app, &recording_id)?;
|
|
2636
|
+
saved.last_attempt_at = Some(now_iso());
|
|
2637
|
+
saved.last_error = Some(error);
|
|
2638
|
+
write_saved_recording_metadata(&app, &saved)?;
|
|
2639
|
+
let _ = app.emit("clips:pending-uploads-changed", ());
|
|
2640
|
+
Ok(())
|
|
2641
|
+
}
|
|
2642
|
+
|
|
2629
2643
|
#[tauri::command]
|
|
2630
2644
|
pub async fn native_fullscreen_recording_dismiss_upload(
|
|
2631
2645
|
app: AppHandle,
|
|
@@ -586,7 +586,15 @@ export const dictations = table("clips_dictations", {
|
|
|
586
586
|
// are text-only since native recognition runs on-device.
|
|
587
587
|
audioUrl: text("audio_url"),
|
|
588
588
|
source: text("source", {
|
|
589
|
-
enum: [
|
|
589
|
+
enum: [
|
|
590
|
+
"fn-hold",
|
|
591
|
+
"cmd-shift-space",
|
|
592
|
+
"manual",
|
|
593
|
+
"mobile",
|
|
594
|
+
"other",
|
|
595
|
+
"fn",
|
|
596
|
+
"custom",
|
|
597
|
+
],
|
|
590
598
|
})
|
|
591
599
|
.notNull()
|
|
592
600
|
.default("fn-hold"),
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
resolveBuilderPrivateKey,
|
|
10
10
|
runWithRequestContext,
|
|
11
11
|
} from "@agent-native/core/server";
|
|
12
|
-
import { and, eq } from "drizzle-orm";
|
|
12
|
+
import { and, eq, isNull } from "drizzle-orm";
|
|
13
13
|
|
|
14
14
|
import type { MediaWorkerCallback } from "../../shared/media-worker-contract.js";
|
|
15
15
|
import { getDb, schema } from "../db/index.js";
|
|
@@ -958,7 +958,12 @@ export async function runBuilderMediaCompressionSweepOnce(): Promise<void> {
|
|
|
958
958
|
key?: unknown;
|
|
959
959
|
value?: unknown;
|
|
960
960
|
}>) {
|
|
961
|
-
const sessionId =
|
|
961
|
+
const sessionId =
|
|
962
|
+
typeof row.session_id === "string" ? row.session_id.trim() : "";
|
|
963
|
+
const key = typeof row.key === "string" ? row.key : "";
|
|
964
|
+
const recordingId = key.startsWith(BUILDER_MEDIA_COMPRESSION_STATE_PREFIX)
|
|
965
|
+
? key.slice(BUILDER_MEDIA_COMPRESSION_STATE_PREFIX.length)
|
|
966
|
+
: "";
|
|
962
967
|
const rawValue = typeof row.value === "string" ? row.value : "";
|
|
963
968
|
let value: Record<string, unknown> | null = null;
|
|
964
969
|
try {
|
|
@@ -967,14 +972,61 @@ export async function runBuilderMediaCompressionSweepOnce(): Promise<void> {
|
|
|
967
972
|
continue;
|
|
968
973
|
}
|
|
969
974
|
const state = parseState(value);
|
|
970
|
-
if (
|
|
971
|
-
|
|
972
|
-
|
|
975
|
+
if (
|
|
976
|
+
!sessionId ||
|
|
977
|
+
!recordingId ||
|
|
978
|
+
!state ||
|
|
979
|
+
state.recordingId !== recordingId ||
|
|
980
|
+
isFinalStatus(state.status) ||
|
|
981
|
+
!isDue(state)
|
|
982
|
+
) {
|
|
983
|
+
continue;
|
|
984
|
+
}
|
|
973
985
|
try {
|
|
986
|
+
const [recording] = await getDb()
|
|
987
|
+
.select({
|
|
988
|
+
ownerEmail: schema.recordings.ownerEmail,
|
|
989
|
+
orgId: schema.recordings.orgId,
|
|
990
|
+
videoUrl: schema.recordings.videoUrl,
|
|
991
|
+
})
|
|
992
|
+
.from(schema.recordings)
|
|
993
|
+
.where(
|
|
994
|
+
and(
|
|
995
|
+
eq(schema.recordings.id, recordingId),
|
|
996
|
+
ownerEmailMatches(schema.recordings.ownerEmail, sessionId),
|
|
997
|
+
eq(schema.recordings.status, "ready"),
|
|
998
|
+
isNull(schema.recordings.trashedAt),
|
|
999
|
+
),
|
|
1000
|
+
)
|
|
1001
|
+
.limit(1);
|
|
1002
|
+
const expectedTarget = extractBuilderMediaTarget(
|
|
1003
|
+
recording?.videoUrl ?? "",
|
|
1004
|
+
);
|
|
1005
|
+
if (
|
|
1006
|
+
!recording ||
|
|
1007
|
+
!expectedTarget ||
|
|
1008
|
+
state.sourceUrl !== expectedTarget.sourceUrl ||
|
|
1009
|
+
(state.mediaWorker &&
|
|
1010
|
+
(state.mediaWorker.jobId !==
|
|
1011
|
+
mediaWorkerCompressionJobId(recordingId) ||
|
|
1012
|
+
state.mediaWorker.outputUrl !== expectedTarget.compressedUrl))
|
|
1013
|
+
) {
|
|
1014
|
+
continue;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
const trustedState: BuilderMediaCompressionState = {
|
|
1018
|
+
...state,
|
|
1019
|
+
...expectedTarget,
|
|
1020
|
+
ownerEmail: recording.ownerEmail,
|
|
1021
|
+
orgId: recording.orgId,
|
|
1022
|
+
};
|
|
974
1023
|
await runWithRequestContext(
|
|
975
|
-
{
|
|
1024
|
+
{
|
|
1025
|
+
userEmail: recording.ownerEmail,
|
|
1026
|
+
orgId: recording.orgId ?? undefined,
|
|
1027
|
+
},
|
|
976
1028
|
async () => {
|
|
977
|
-
await processCompressionState(
|
|
1029
|
+
await processCompressionState(trustedState);
|
|
978
1030
|
},
|
|
979
1031
|
);
|
|
980
1032
|
} catch (err) {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { appStateGet } from "@agent-native/core/application-state";
|
|
2
|
+
|
|
1
3
|
export const MEDIA_VERIFICATION_STATE_PREFIX = "recording-media-verification-";
|
|
2
4
|
|
|
3
5
|
export type MediaVerificationMarker = {
|
|
@@ -36,3 +38,18 @@ export function parseMediaVerificationMarker(
|
|
|
36
38
|
}
|
|
37
39
|
return state as MediaVerificationMarker;
|
|
38
40
|
}
|
|
41
|
+
|
|
42
|
+
export async function isMediaVerificationPending(args: {
|
|
43
|
+
ownerEmail: string;
|
|
44
|
+
recordingId: string;
|
|
45
|
+
recordingStatus: string;
|
|
46
|
+
}): Promise<boolean> {
|
|
47
|
+
if (args.recordingStatus !== "processing") return false;
|
|
48
|
+
|
|
49
|
+
const value = await appStateGet(
|
|
50
|
+
args.ownerEmail,
|
|
51
|
+
mediaVerificationStateKey(args.recordingId),
|
|
52
|
+
).catch(() => null);
|
|
53
|
+
const marker = parseMediaVerificationMarker(value);
|
|
54
|
+
return marker?.recordingId === args.recordingId;
|
|
55
|
+
}
|
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
} from "../../../shared/transcript-segments.js";
|
|
43
43
|
import { resolveTranscriptPresentation } from "../../../shared/transcript-status.js";
|
|
44
44
|
import { getDb, schema } from "../../db/index.js";
|
|
45
|
+
import { isMediaVerificationPending } from "../../lib/media-verification-state.js";
|
|
45
46
|
import { resolvePlayerThumbnailUrl } from "../../lib/player-thumbnail-url.js";
|
|
46
47
|
import { resolvePlayerVideoUrl } from "../../lib/player-video-url.js";
|
|
47
48
|
import {
|
|
@@ -398,6 +399,11 @@ export default defineEventHandler(async (event) => {
|
|
|
398
399
|
// Referer of any outbound link the share page renders.
|
|
399
400
|
setResponseHeader(event, "Referrer-Policy", "no-referrer");
|
|
400
401
|
const transcriptPresentation = resolveTranscriptPresentation(transcript);
|
|
402
|
+
const verificationPending = await isMediaVerificationPending({
|
|
403
|
+
ownerEmail: rec.ownerEmail,
|
|
404
|
+
recordingId,
|
|
405
|
+
recordingStatus: rec.status,
|
|
406
|
+
});
|
|
401
407
|
|
|
402
408
|
return {
|
|
403
409
|
recording: {
|
|
@@ -416,6 +422,7 @@ export default defineEventHandler(async (event) => {
|
|
|
416
422
|
hasAudio: Boolean(rec.hasAudio),
|
|
417
423
|
hasCamera: Boolean(rec.hasCamera),
|
|
418
424
|
status: rec.status,
|
|
425
|
+
verificationPending,
|
|
419
426
|
uploadProgress: rec.uploadProgress,
|
|
420
427
|
failureReason: rec.failureReason,
|
|
421
428
|
// Don't leak the password to clients; just indicate whether one was set.
|
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
} from "h3";
|
|
43
43
|
|
|
44
44
|
import { getDb, schema } from "../../../../db/index.js";
|
|
45
|
+
import { isMediaVerificationPending } from "../../../../lib/media-verification-state.js";
|
|
45
46
|
import {
|
|
46
47
|
getEventOwnerContext,
|
|
47
48
|
ownerEmailMatches,
|
|
@@ -115,7 +116,11 @@ export default defineEventHandler(async (event: H3Event) => {
|
|
|
115
116
|
const db = getDb();
|
|
116
117
|
|
|
117
118
|
const [existing] = await db
|
|
118
|
-
.select({
|
|
119
|
+
.select({
|
|
120
|
+
id: schema.recordings.id,
|
|
121
|
+
status: schema.recordings.status,
|
|
122
|
+
videoUrl: schema.recordings.videoUrl,
|
|
123
|
+
})
|
|
119
124
|
.from(schema.recordings)
|
|
120
125
|
.where(
|
|
121
126
|
and(
|
|
@@ -129,6 +134,22 @@ export default defineEventHandler(async (event: H3Event) => {
|
|
|
129
134
|
return { error: "Recording not found" };
|
|
130
135
|
}
|
|
131
136
|
|
|
137
|
+
if (existing.status === "ready" && existing.videoUrl) {
|
|
138
|
+
setResponseStatus(event, 409);
|
|
139
|
+
return { error: "Recording is already ready" };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (
|
|
143
|
+
await isMediaVerificationPending({
|
|
144
|
+
ownerEmail,
|
|
145
|
+
recordingId,
|
|
146
|
+
recordingStatus: existing.status,
|
|
147
|
+
})
|
|
148
|
+
) {
|
|
149
|
+
setResponseStatus(event, 409);
|
|
150
|
+
return { error: "Recording is still being verified" };
|
|
151
|
+
}
|
|
152
|
+
|
|
132
153
|
const cleared = await deleteAppStateByPrefix(
|
|
133
154
|
`recording-chunks-${recordingId}-`,
|
|
134
155
|
);
|
|
@@ -367,6 +367,8 @@ export type PlanDiagramBlock = PlanBlockBase & {
|
|
|
367
367
|
*/
|
|
368
368
|
html?: string;
|
|
369
369
|
css?: string;
|
|
370
|
+
/** `design` forces clean HTML/CSS rendering without the sketch overlay. */
|
|
371
|
+
renderMode?: PlanVisualCanvasMode;
|
|
370
372
|
caption?: string;
|
|
371
373
|
/** Outer surface frame. `auto` lets the host choose the right default. */
|
|
372
374
|
frame?: PlanVisualFrame;
|
|
@@ -1396,6 +1398,7 @@ const diagramDataSchema: z.ZodType<PlanDiagramBlock["data"]> = z
|
|
|
1396
1398
|
message: "Diagram css must not include document or script tags.",
|
|
1397
1399
|
})
|
|
1398
1400
|
.optional(),
|
|
1401
|
+
renderMode: visualCanvasModeSchema.optional(),
|
|
1399
1402
|
caption: z.string().trim().max(600).optional(),
|
|
1400
1403
|
frame: visualFrameSchema.optional(),
|
|
1401
1404
|
nodes: z.array(diagramNodeSchema).max(80).optional(),
|
|
@@ -38,6 +38,7 @@ Tasks is a task-list-first agent-native app. The task list at `/tasks` is the de
|
|
|
38
38
|
| `update-visible-task-fields` | POST | Replace which custom fields appear on task cards (max 3) |
|
|
39
39
|
| `view-screen` | — | Read navigation, UI bulk selection, visible tasks, and inbox snapshot |
|
|
40
40
|
| `navigate` | — | Move UI to a view: `tasks`, `inbox`, `fields`, `extensions`, `team` (`home`/`ask` → `tasks`) |
|
|
41
|
+
| `render-task-list-inline` | — | Render an interactive task-list widget inline in chat without leaving the current view |
|
|
41
42
|
|
|
42
43
|
## Store Functions And Transactions
|
|
43
44
|
|
|
@@ -128,6 +129,8 @@ Default navigation shape on `/tasks`:
|
|
|
128
129
|
- Capture in chat → `create-inbox-item` by default; use `create-task` only when the user asks to add directly to the task list.
|
|
129
130
|
- Call `view-screen` before ambiguous edits when the user says "this task", "these tasks", "this inbox item", or "the list".
|
|
130
131
|
- On `/tasks` or `/inbox`, `view-screen` returns `list` (with `items`), optional `selectedItem` (`inListSnapshot`), and optional `selection` (`selectedItems`, `selectedIdsNotInVisibleList`) when bulk-select is active.
|
|
132
|
+
- When the user asks to see, review, or manage tasks while `navigation.view` is not `tasks`, call `render-task-list-inline` instead of navigating away. Pass `includeDone: true` when completed tasks should be included. The widget can add tasks and toggle completion through the existing task actions.
|
|
133
|
+
- When the user is already on `/tasks`, use `view-screen` and the native task list for task-list context unless the user explicitly asks for an inline widget.
|
|
131
134
|
- Prefer `selection.selectedItems` when the user has UI rows selected; fall back to `selectedItem` for a single deep-link highlight.
|
|
132
135
|
- `delete-task`, `bulk-delete-tasks`, and `delete-inbox-item` only after explicit user confirmation in chat.
|
|
133
136
|
- Use `navigation.includeDone` and `list` from `view-screen` to match what the user sees on `/tasks`.
|