@agent-native/core 0.173.1 → 0.173.2-nightly-20260825163448

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 (33) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/clips/desktop/src/app.tsx +77 -7
  3. package/corpus/templates/clips/desktop/src/lib/recorder.ts +91 -25
  4. package/corpus/templates/clips/desktop/src/lib/upload-recovery.ts +20 -2
  5. package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +7 -1
  6. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +1 -0
  7. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +372 -70
  8. package/corpus/templates/clips/server/lib/resumable-session.ts +15 -0
  9. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +245 -118
  10. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/reset-chunks.post.ts +21 -11
  11. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/resume.get.ts +132 -25
  12. package/corpus/templates/content/actions/_database-property-input.ts +211 -0
  13. package/corpus/templates/content/actions/_database-row-mutation.ts +151 -26
  14. package/corpus/templates/content/actions/add-database-item.ts +15 -5
  15. package/corpus/templates/content/actions/update-database-item.ts +15 -7
  16. package/corpus/templates/content/actions/upsert-database-item-by-key.ts +15 -5
  17. package/corpus/templates/content/package.json +1 -0
  18. package/corpus/templates/content/parity/README.md +11 -2
  19. package/corpus/templates/content/parity/eval-scenarios.ts +52 -0
  20. package/corpus/templates/content/parity/run-database-create-property-preservation.ts +140 -0
  21. package/corpus/templates/content/parity/scenario-to-eval.ts +390 -0
  22. package/dist/agent/run-store.js +0 -1
  23. package/dist/client/agent-engine-key.js +26 -4
  24. package/dist/client/settings/ProviderSetupForm.js +1 -1
  25. package/dist/collab/struct-routes.d.ts +1 -1
  26. package/dist/eval/agent-runner.js +23 -0
  27. package/dist/eval/types.d.ts +11 -0
  28. package/dist/observability/routes.d.ts +1 -1
  29. package/dist/resources/handlers.d.ts +1 -1
  30. package/dist/server/agent-engine-api-key-route.d.ts +7 -0
  31. package/dist/server/agent-engine-api-key-route.js +38 -0
  32. package/dist/server/realtime-token.d.ts +1 -1
  33. package/package.json +1 -1
package/corpus/README.md CHANGED
@@ -31,4 +31,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
31
31
 
32
32
  ## Generated Counts
33
33
 
34
- - template files: 8609
34
+ - template files: 8611
@@ -198,6 +198,10 @@ interface PendingNativeUpload {
198
198
 
199
199
  type PendingDesktopUpload = PendingNativeUpload | PendingBrowserRecordingUpload;
200
200
 
201
+ type NativeUploadProgress = {
202
+ message?: string;
203
+ };
204
+
201
205
  type PopoverView =
202
206
  | "recorder"
203
207
  | "memory"
@@ -1137,6 +1141,26 @@ export function App({
1137
1141
  const [retryingUploadStatus, setRetryingUploadStatus] = useState<
1138
1142
  string | null
1139
1143
  >(null);
1144
+ const retryUploadAbortRef = useRef<AbortController | null>(null);
1145
+ const retryingUploadKindRef = useRef<PendingDesktopUpload["kind"] | null>(
1146
+ null,
1147
+ );
1148
+ useEffect(() => {
1149
+ if (!retryingUploadId) return;
1150
+ let disposed = false;
1151
+ let unlisten: (() => void) | null = null;
1152
+ listen<NativeUploadProgress>("clips:native-upload-progress", (event) => {
1153
+ const message = event.payload?.message?.trim();
1154
+ if (message) setRetryingUploadStatus(message);
1155
+ }).then((cleanup) => {
1156
+ if (disposed) cleanup();
1157
+ else unlisten = cleanup;
1158
+ });
1159
+ return () => {
1160
+ disposed = true;
1161
+ unlisten?.();
1162
+ };
1163
+ }, [retryingUploadId]);
1140
1164
  const [exportingUploadId, setExportingUploadId] = useState<string | null>(
1141
1165
  null,
1142
1166
  );
@@ -3107,6 +3131,9 @@ export function App({
3107
3131
  const targetServerUrl = serverUrlForPendingUpload(upload, serverUrl);
3108
3132
  setRecError(null);
3109
3133
  setRetryingUploadId(upload.recordingId);
3134
+ const abortController = new AbortController();
3135
+ retryUploadAbortRef.current = abortController;
3136
+ retryingUploadKindRef.current = upload.kind;
3110
3137
  try {
3111
3138
  const authToken = loadDesktopAuthToken(targetServerUrl);
3112
3139
  if (upload.kind === "native") {
@@ -3132,13 +3159,16 @@ export function App({
3132
3159
  recordingId: upload.recordingId,
3133
3160
  serverUrl: targetServerUrl,
3134
3161
  authToken,
3162
+ signal: abortController.signal,
3135
3163
  onRecoveryDecision: ({ action, progress }) => {
3136
3164
  setRetryingUploadStatus(
3137
3165
  action === "resume"
3138
3166
  ? `Resuming · ${Math.round(progress * 100)}% already uploaded`
3139
- : action === "restart"
3140
- ? "Restarting upload"
3141
- : "Finishing upload",
3167
+ : action === "wait"
3168
+ ? "Waiting for prior retry"
3169
+ : action === "restart"
3170
+ ? "Restarting upload"
3171
+ : "Finishing upload",
3142
3172
  );
3143
3173
  },
3144
3174
  });
@@ -3152,6 +3182,14 @@ export function App({
3152
3182
  emit("clips:popover-visible", false).catch(() => {});
3153
3183
  } catch (err) {
3154
3184
  const message = err instanceof Error ? err.message : String(err);
3185
+ if (
3186
+ abortController.signal.aborted ||
3187
+ (err instanceof DOMException && err.name === "AbortError") ||
3188
+ message === "native recording upload retry cancelled"
3189
+ ) {
3190
+ await loadPendingUploads();
3191
+ return;
3192
+ }
3155
3193
  console.error("[clips-tray] retry saved upload failed:", err);
3156
3194
  setRecError(
3157
3195
  isStorageSetupFailureMessage(message)
@@ -3160,11 +3198,28 @@ export function App({
3160
3198
  );
3161
3199
  await loadPendingUploads();
3162
3200
  } finally {
3201
+ if (retryUploadAbortRef.current === abortController) {
3202
+ retryUploadAbortRef.current = null;
3203
+ retryingUploadKindRef.current = null;
3204
+ }
3163
3205
  setRetryingUploadId(null);
3164
3206
  setRetryingUploadStatus(null);
3165
3207
  }
3166
3208
  }
3167
3209
 
3210
+ function cancelPendingUploadRetry(upload: PendingDesktopUpload) {
3211
+ if (retryingUploadId !== upload.recordingId) return;
3212
+ retryUploadAbortRef.current?.abort();
3213
+ if (retryingUploadKindRef.current === "native") {
3214
+ invoke("native_fullscreen_recording_cancel_retry", {
3215
+ recordingId: upload.recordingId,
3216
+ }).catch((err) => {
3217
+ console.error("[clips-tray] cancel saved upload retry failed:", err);
3218
+ });
3219
+ }
3220
+ setRetryingUploadStatus("Cancelling retry");
3221
+ }
3222
+
3168
3223
  async function exportPendingUpload(upload: PendingDesktopUpload) {
3169
3224
  if (retryingUploadId || exportingUploadId || dismissingUploadId) return;
3170
3225
  setRecError(null);
@@ -3873,6 +3928,7 @@ export function App({
3873
3928
  dismissingUploadId={dismissingUploadId}
3874
3929
  onExport={exportPendingUpload}
3875
3930
  onRetry={retryPendingUpload}
3931
+ onCancelRetry={cancelPendingUploadRetry}
3876
3932
  onDismiss={dismissPendingUpload}
3877
3933
  onOpenFolder={openPendingUploadFolder}
3878
3934
  onConnectStorage={(upload) => openVideoStorageSetup(upload.serverUrl)}
@@ -4637,6 +4693,7 @@ function PendingUploadBanner({
4637
4693
  dismissingUploadId,
4638
4694
  onExport,
4639
4695
  onRetry,
4696
+ onCancelRetry,
4640
4697
  onDismiss,
4641
4698
  onOpenFolder,
4642
4699
  onConnectStorage,
@@ -4648,6 +4705,7 @@ function PendingUploadBanner({
4648
4705
  dismissingUploadId: string | null;
4649
4706
  onExport: (upload: PendingDesktopUpload) => void;
4650
4707
  onRetry: (upload: PendingDesktopUpload) => void;
4708
+ onCancelRetry: (upload: PendingDesktopUpload) => void;
4651
4709
  onDismiss: (upload: PendingDesktopUpload) => void;
4652
4710
  onOpenFolder: (upload: PendingDesktopUpload) => void;
4653
4711
  onConnectStorage: (upload: PendingDesktopUpload) => void;
@@ -4656,6 +4714,8 @@ function PendingUploadBanner({
4656
4714
  if (!latest) return null;
4657
4715
 
4658
4716
  const retrying = retryingUploadId === latest.recordingId;
4717
+ const retryCancelling =
4718
+ retrying && retryingUploadStatus === "Cancelling retry";
4659
4719
  const storageSetupFailure = isStorageSetupFailureMessage(latest.lastError);
4660
4720
 
4661
4721
  const canOpenFolder = latest.kind === "native" && !!latest.folderPath;
@@ -4762,12 +4822,22 @@ function PendingUploadBanner({
4762
4822
  <button
4763
4823
  type="button"
4764
4824
  className={`pending-upload-retry${retrying ? " pending-upload-retry-spinning" : ""}`}
4765
- disabled={actionsDisabled}
4766
- onClick={() => onRetry(latest)}
4825
+ disabled={retrying ? retryCancelling : actionsDisabled}
4826
+ onClick={() =>
4827
+ retrying ? onCancelRetry(latest) : onRetry(latest)
4828
+ }
4767
4829
  aria-busy={retrying}
4768
4830
  >
4769
- <IconRefresh size={14} stroke={2} />
4770
- {retrying ? (retryingUploadStatus ?? "Retrying") : "Retry"}
4831
+ {retrying ? (
4832
+ <IconX size={14} stroke={2} />
4833
+ ) : (
4834
+ <IconRefresh size={14} stroke={2} />
4835
+ )}
4836
+ {retryCancelling
4837
+ ? "Cancelling retry"
4838
+ : retrying
4839
+ ? "Cancel retry"
4840
+ : "Retry"}
4771
4841
  </button>
4772
4842
  </>
4773
4843
  )}
@@ -104,6 +104,7 @@ import {
104
104
  planStreamingRecovery,
105
105
  retryAttemptIdAfterRestartSignal,
106
106
  retryAttemptIdAfterResumeResponse,
107
+ retryConflictDelay,
107
108
  type UploadResumeResponse,
108
109
  } from "./upload-recovery";
109
110
  import {
@@ -1061,6 +1062,7 @@ async function postBackupChunk(
1061
1062
  url: string,
1062
1063
  blob: Blob,
1063
1064
  authToken?: string,
1065
+ signal?: AbortSignal,
1064
1066
  ): Promise<FinalizeReceipt | null> {
1065
1067
  const res = await fetch(url, {
1066
1068
  method: "POST",
@@ -1070,6 +1072,7 @@ async function postBackupChunk(
1070
1072
  ),
1071
1073
  credentials: "include",
1072
1074
  body: blob,
1075
+ signal,
1073
1076
  });
1074
1077
  const body = await res.text().catch(() => "");
1075
1078
  if (!res.ok) {
@@ -1103,6 +1106,7 @@ async function resetBrowserRecordingBackupUpload(
1103
1106
  authToken?: string,
1104
1107
  attemptId?: string,
1105
1108
  uploadGenerationId?: string,
1109
+ signal?: AbortSignal,
1106
1110
  ): Promise<{ uploadMode: UploadMode; uploadGenerationId?: string }> {
1107
1111
  const res = await fetch(
1108
1112
  `${meta.serverUrl.replace(/\/+$/, "")}/api/uploads/${meta.recordingId}/reset-chunks`,
@@ -1120,6 +1124,7 @@ async function resetBrowserRecordingBackupUpload(
1120
1124
  ...(attemptId ? { attemptId } : {}),
1121
1125
  ...(uploadGenerationId ? { uploadGenerationId } : {}),
1122
1126
  }),
1127
+ signal,
1123
1128
  },
1124
1129
  );
1125
1130
  if (!res.ok) {
@@ -1147,32 +1152,60 @@ async function getBrowserRecordingUploadResume(
1147
1152
  meta: BrowserRecordingBackupMeta,
1148
1153
  attemptId: string,
1149
1154
  authToken?: string,
1155
+ onWaiting?: (delayMs: number) => void,
1156
+ signal?: AbortSignal,
1150
1157
  ): Promise<UploadResumeResponse> {
1151
1158
  const resumeUrl = new URL(
1152
1159
  `${meta.serverUrl.replace(/\/+$/, "")}/api/uploads/${meta.recordingId}/resume`,
1153
1160
  );
1154
1161
  resumeUrl.searchParams.set("attemptId", attemptId);
1155
- const res = await fetch(resumeUrl, {
1156
- method: "GET",
1157
- headers: buildRetryHeaders("application/json", authToken),
1158
- credentials: "include",
1159
- });
1160
- const body = await res.text().catch(() => "");
1161
- if (!res.ok) {
1162
- throw new Error(
1163
- `Upload resume check failed (${res.status}): ${body.slice(0, 200)}`,
1164
- );
1165
- }
1166
- let parsed: UploadResumeResponse;
1167
- try {
1168
- parsed = JSON.parse(body) as UploadResumeResponse;
1169
- } catch {
1170
- throw new Error("Upload resume check returned an unreadable response");
1171
- }
1172
- if (parsed.resumable && parsed.attemptId !== attemptId) {
1173
- throw new Error("Upload resume check returned a mismatched retry token");
1162
+ const deadline = Date.now() + 5 * 60_000;
1163
+ for (;;) {
1164
+ const res = await fetch(resumeUrl, {
1165
+ method: "GET",
1166
+ headers: buildRetryHeaders("application/json", authToken),
1167
+ credentials: "include",
1168
+ signal,
1169
+ });
1170
+ let body: string;
1171
+ try {
1172
+ body = await res.text();
1173
+ } catch {
1174
+ throw new Error("Upload resume check response could not be read");
1175
+ }
1176
+ let parsed: UploadResumeResponse;
1177
+ try {
1178
+ parsed = JSON.parse(body) as UploadResumeResponse;
1179
+ } catch {
1180
+ throw new Error("Upload resume check returned an unreadable response");
1181
+ }
1182
+ if (!res.ok) {
1183
+ const delayMs = retryConflictDelay(parsed);
1184
+ if (delayMs !== null && Date.now() + delayMs <= deadline) {
1185
+ onWaiting?.(delayMs);
1186
+ await abortableWait(delayMs, signal);
1187
+ continue;
1188
+ }
1189
+ if (!parsed.resumable && parsed.reason === "retry_already_active") {
1190
+ throw new Error(
1191
+ "Another upload retry is still active. Wait a moment and try again.",
1192
+ );
1193
+ }
1194
+ if (
1195
+ !parsed.resumable &&
1196
+ parsed.reason === "retry_claim_liveness_unavailable"
1197
+ ) {
1198
+ throw new Error(
1199
+ "Clips could not verify whether another retry is active. Your local clip is safe; try again.",
1200
+ );
1201
+ }
1202
+ throw new Error(`Upload resume check failed (${res.status})`);
1203
+ }
1204
+ if (parsed.resumable && parsed.attemptId !== attemptId) {
1205
+ throw new Error("Upload resume check returned a mismatched retry token");
1206
+ }
1207
+ return parsed;
1174
1208
  }
1175
- return parsed;
1176
1209
  }
1177
1210
 
1178
1211
  async function replayBrowserBackupToResumableSession(
@@ -1185,6 +1218,7 @@ async function replayBrowserBackupToResumableSession(
1185
1218
  bytesReceived: 0,
1186
1219
  nextChunkIndex: 0,
1187
1220
  },
1221
+ signal?: AbortSignal,
1188
1222
  ): Promise<FinalizeReceipt | null> {
1189
1223
  // The backup is stored in raw MediaRecorder blobs, which have arbitrary
1190
1224
  // boundaries. A resumable provider needs every non-final request aligned,
@@ -1217,6 +1251,7 @@ async function replayBrowserBackupToResumableSession(
1217
1251
  }),
1218
1252
  body,
1219
1253
  authToken,
1254
+ signal,
1220
1255
  );
1221
1256
  }
1222
1257
 
@@ -1243,6 +1278,7 @@ async function replayBrowserBackupToResumableSession(
1243
1278
  }),
1244
1279
  finalBody,
1245
1280
  authToken,
1281
+ signal,
1246
1282
  );
1247
1283
  }
1248
1284
 
@@ -1250,8 +1286,9 @@ export async function retryBrowserRecordingBackup(input: {
1250
1286
  recordingId: string;
1251
1287
  serverUrl?: string;
1252
1288
  authToken?: string;
1289
+ signal?: AbortSignal;
1253
1290
  onRecoveryDecision?: (decision: {
1254
- action: "resume" | "restart" | "reconcile";
1291
+ action: "wait" | "resume" | "restart" | "reconcile";
1255
1292
  progress: number;
1256
1293
  }) => void;
1257
1294
  }): Promise<{ recordingId: string; viewUrl: string }> {
@@ -1284,6 +1321,8 @@ export async function retryBrowserRecordingBackup(input: {
1284
1321
  meta,
1285
1322
  activeAttemptId,
1286
1323
  input.authToken,
1324
+ () => input.onRecoveryDecision?.({ action: "wait", progress: 0 }),
1325
+ input.signal,
1287
1326
  );
1288
1327
  const recoveryPlan = planStreamingRecovery({
1289
1328
  response: resumeResponse,
@@ -1294,7 +1333,7 @@ export async function retryBrowserRecordingBackup(input: {
1294
1333
  activeAttemptId,
1295
1334
  resumeResponse,
1296
1335
  );
1297
- activeUploadGenerationId = resumeResponse.resumable
1336
+ activeUploadGenerationId = activeAttemptId
1298
1337
  ? resumeResponse.uploadGenerationId
1299
1338
  : undefined;
1300
1339
  if (recoveryPlan.action === "reconcile") {
@@ -1343,6 +1382,7 @@ export async function retryBrowserRecordingBackup(input: {
1343
1382
  input.authToken,
1344
1383
  activeAttemptId,
1345
1384
  activeUploadGenerationId,
1385
+ input.signal,
1346
1386
  );
1347
1387
  uploadMode = reset.uploadMode;
1348
1388
  activeUploadGenerationId = reset.uploadGenerationId;
@@ -1358,6 +1398,7 @@ export async function retryBrowserRecordingBackup(input: {
1358
1398
  activeAttemptId,
1359
1399
  activeUploadGenerationId,
1360
1400
  resumeFrom,
1401
+ input.signal,
1361
1402
  );
1362
1403
  } catch (err) {
1363
1404
  if (err instanceof UploadRestartRequiredError) {
@@ -1365,9 +1406,6 @@ export async function retryBrowserRecordingBackup(input: {
1365
1406
  activeAttemptId,
1366
1407
  err.recoveryEnabled,
1367
1408
  );
1368
- if (err.recoveryEnabled === false) {
1369
- activeUploadGenerationId = undefined;
1370
- }
1371
1409
  input.onRecoveryDecision?.({ action: "restart", progress: 0 });
1372
1410
  console.info("[clips-recorder] restarting expired upload session", {
1373
1411
  recordingId: meta.recordingId,
@@ -1378,6 +1416,7 @@ export async function retryBrowserRecordingBackup(input: {
1378
1416
  input.authToken,
1379
1417
  activeAttemptId,
1380
1418
  activeUploadGenerationId,
1419
+ input.signal,
1381
1420
  );
1382
1421
  uploadMode = reset.uploadMode;
1383
1422
  activeUploadGenerationId = reset.uploadGenerationId;
@@ -1388,6 +1427,8 @@ export async function retryBrowserRecordingBackup(input: {
1388
1427
  input.authToken,
1389
1428
  activeAttemptId,
1390
1429
  activeUploadGenerationId,
1430
+ undefined,
1431
+ input.signal,
1391
1432
  );
1392
1433
  }
1393
1434
  } else if (
@@ -1439,6 +1480,7 @@ export async function retryBrowserRecordingBackup(input: {
1439
1480
  }),
1440
1481
  chunk.blob,
1441
1482
  input.authToken,
1483
+ input.signal,
1442
1484
  );
1443
1485
  }
1444
1486
 
@@ -1466,6 +1508,7 @@ export async function retryBrowserRecordingBackup(input: {
1466
1508
  finalChunkUrl,
1467
1509
  new Blob([], { type: meta.mimeType }),
1468
1510
  input.authToken,
1511
+ input.signal,
1469
1512
  );
1470
1513
  const receiptStatus = verifyFinalizeReceipt(receipt, meta);
1471
1514
  if (receiptStatus === "processing") {
@@ -1498,6 +1541,12 @@ export async function retryBrowserRecordingBackup(input: {
1498
1541
  await deleteBrowserRecordingBackup(meta.recordingId);
1499
1542
  return { recordingId: meta.recordingId, viewUrl: `/r/${meta.recordingId}` };
1500
1543
  } catch (err) {
1544
+ if (
1545
+ input.signal?.aborted ||
1546
+ (err instanceof DOMException && err.name === "AbortError")
1547
+ ) {
1548
+ throw err;
1549
+ }
1501
1550
  const message = err instanceof Error ? err.message : String(err);
1502
1551
  if (
1503
1552
  await recoverAcceptedRecordingAfterFinalizeError({
@@ -1638,6 +1687,23 @@ function wait(ms: number): Promise<void> {
1638
1687
  return new Promise((resolve) => window.setTimeout(resolve, ms));
1639
1688
  }
1640
1689
 
1690
+ function abortableWait(ms: number, signal?: AbortSignal): Promise<void> {
1691
+ if (!signal) return wait(ms);
1692
+ if (signal.aborted)
1693
+ return Promise.reject(new DOMException("Aborted", "AbortError"));
1694
+ return new Promise((resolve, reject) => {
1695
+ const timer = window.setTimeout(() => {
1696
+ signal.removeEventListener("abort", onAbort);
1697
+ resolve();
1698
+ }, ms);
1699
+ const onAbort = () => {
1700
+ window.clearTimeout(timer);
1701
+ reject(new DOMException("Aborted", "AbortError"));
1702
+ };
1703
+ signal.addEventListener("abort", onAbort, { once: true });
1704
+ });
1705
+ }
1706
+
1641
1707
  interface NativeFullscreenUploadResult {
1642
1708
  recordingId: string;
1643
1709
  durationMs: number;
@@ -17,8 +17,25 @@ export type UploadResumeResponse =
17
17
  videoUrl?: string | null;
18
18
  reason?: string;
19
19
  attemptId?: string;
20
+ uploadGenerationId?: string;
21
+ retryAfterMs?: number;
20
22
  };
21
23
 
24
+ export function retryConflictDelay(
25
+ response: UploadResumeResponse,
26
+ ): number | null {
27
+ if (
28
+ response.resumable ||
29
+ response.recoveryEnabled !== true ||
30
+ response.reason !== "retry_already_active" ||
31
+ !Number.isSafeInteger(response.retryAfterMs) ||
32
+ (response.retryAfterMs ?? 0) <= 0
33
+ ) {
34
+ return null;
35
+ }
36
+ return Math.min(Math.max(response.retryAfterMs!, 250), 30_000);
37
+ }
38
+
22
39
  export type StreamingRecoveryPlan =
23
40
  | {
24
41
  action: "resume";
@@ -38,15 +55,16 @@ export interface StreamingReplayRequest {
38
55
 
39
56
  export function retryAttemptIdAfterRestartSignal(
40
57
  attemptId: string | undefined,
41
- recoveryEnabled: unknown,
58
+ _recoveryEnabled: unknown,
42
59
  ): string | undefined {
43
- return recoveryEnabled === false ? undefined : attemptId;
60
+ return attemptId;
44
61
  }
45
62
 
46
63
  export function retryAttemptIdAfterResumeResponse(
47
64
  attemptId: string | undefined,
48
65
  response: UploadResumeResponse,
49
66
  ): string | undefined {
67
+ if (response.recoveryEnabled === false) return response.attemptId;
50
68
  return response.resumable && response.attemptId === attemptId
51
69
  ? attemptId
52
70
  : undefined;
@@ -67,7 +67,13 @@ reqwest = { version = "0.12", default-features = false, features = [
67
67
  "rustls-tls",
68
68
  "json",
69
69
  ] }
70
- tokio = { version = "1", features = ["rt", "rt-multi-thread", "time", "sync"] }
70
+ tokio = { version = "1", features = [
71
+ "rt",
72
+ "rt-multi-thread",
73
+ "time",
74
+ "sync",
75
+ "macros",
76
+ ] }
71
77
  chrono = { version = "0.4", features = ["serde"] }
72
78
  # Verify the integrity of the Whisper model we download from HuggingFace.
73
79
  sha2 = "0.10"
@@ -161,6 +161,7 @@ pub fn run() {
161
161
  native_screen::native_fullscreen_pending_uploads,
162
162
  native_screen::native_fullscreen_recover_orphaned_uploads,
163
163
  native_screen::native_fullscreen_recording_retry_upload,
164
+ native_screen::native_fullscreen_recording_cancel_retry,
164
165
  native_screen::native_fullscreen_recording_mark_upload_error,
165
166
  native_screen::native_fullscreen_recording_clear_upload,
166
167
  native_screen::native_fullscreen_recording_dismiss_upload,