@agent-native/core 0.84.59 → 0.84.61
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 +1 -1
- package/corpus/core/CHANGELOG.md +12 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/client/AssistantChat.tsx +49 -14
- package/corpus/core/src/db/client.ts +13 -1
- package/corpus/core/src/server/ssr-handler.ts +14 -16
- package/corpus/core/src/sharing/access.ts +79 -5
- package/corpus/templates/clips/app/i18n/ar-SA.ts +7 -0
- package/corpus/templates/clips/app/i18n/de-DE.ts +8 -0
- package/corpus/templates/clips/app/i18n/en-US.ts +7 -0
- package/corpus/templates/clips/app/i18n/es-ES.ts +7 -0
- package/corpus/templates/clips/app/i18n/fr-FR.ts +7 -0
- package/corpus/templates/clips/app/i18n/hi-IN.ts +6 -0
- package/corpus/templates/clips/app/i18n/ja-JP.ts +8 -0
- package/corpus/templates/clips/app/i18n/ko-KR.ts +7 -0
- package/corpus/templates/clips/app/i18n/pt-BR.ts +7 -0
- package/corpus/templates/clips/app/i18n/zh-CN.ts +5 -0
- package/corpus/templates/clips/app/i18n/zh-TW.ts +5 -0
- package/corpus/templates/clips/app/routes/_app.settings._index.tsx +174 -28
- package/corpus/templates/clips/app/routes/record.tsx +74 -6
- package/corpus/templates/plan/changelog/2026-07-02-fixed-hosted-visual-plans-getting-stuck-loading-when-a-datab.md +6 -0
- package/corpus/templates/plan/server/plugins/db.ts +25 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +42 -15
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.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 +11 -1
- package/dist/db/client.js.map +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/server/ssr-handler.d.ts.map +1 -1
- package/dist/server/ssr-handler.js +14 -16
- package/dist/server/ssr-handler.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/sharing/access.d.ts.map +1 -1
- package/dist/sharing/access.js +54 -5
- package/dist/sharing/access.js.map +1 -1
- package/package.json +1 -1
|
@@ -399,19 +399,23 @@ export default function SettingsIndexRoute() {
|
|
|
399
399
|
const [transcriptCleanupEnabled, setTranscriptCleanupEnabled] =
|
|
400
400
|
useState(true);
|
|
401
401
|
const [s3Values, setS3Values] = useState<Record<string, string>>({});
|
|
402
|
+
const [s3Errors, setS3Errors] = useState<Record<string, string>>({});
|
|
403
|
+
const [clearingS3, setClearingS3] = useState(false);
|
|
402
404
|
const [s3Expanded, setS3Expanded] = useState(false);
|
|
403
405
|
const [apiKeysExpanded, setApiKeysExpanded] = useState(false);
|
|
404
406
|
const [apiKeyValues, setApiKeyValues] = useState<Record<string, string>>({});
|
|
405
407
|
const [apiKeyStatus, setApiKeyStatus] = useState<Record<string, boolean>>({});
|
|
408
|
+
const [secretLast4, setSecretLast4] = useState<Record<string, string>>({});
|
|
406
409
|
const [apiKeyStatusLoading, setApiKeyStatusLoading] = useState(true);
|
|
407
410
|
const [savingApiKey, setSavingApiKey] = useState<string | null>(null);
|
|
408
411
|
|
|
409
412
|
const refreshApiKeyStatus = useCallback(async () => {
|
|
410
413
|
setApiKeyStatusLoading(true);
|
|
411
414
|
try {
|
|
412
|
-
const [envRes, secretsRes] = await Promise.all([
|
|
415
|
+
const [envRes, secretsRes, adhocRes] = await Promise.all([
|
|
413
416
|
fetch(agentNativePath("/_agent-native/env-status")),
|
|
414
417
|
fetch(agentNativePath("/_agent-native/secrets")),
|
|
418
|
+
fetch(agentNativePath("/_agent-native/secrets/adhoc")),
|
|
415
419
|
]);
|
|
416
420
|
const envData = envRes.ok
|
|
417
421
|
? ((await envRes.json()) as Array<{
|
|
@@ -425,12 +429,24 @@ export default function SettingsIndexRoute() {
|
|
|
425
429
|
status?: string;
|
|
426
430
|
}>)
|
|
427
431
|
: [];
|
|
432
|
+
const adhocData = adhocRes.ok
|
|
433
|
+
? ((await adhocRes.json()) as Array<{
|
|
434
|
+
name: string;
|
|
435
|
+
last4?: string;
|
|
436
|
+
}>)
|
|
437
|
+
: [];
|
|
428
438
|
const next = Object.fromEntries(
|
|
429
439
|
envData.map((entry) => [entry.key, Boolean(entry.configured)]),
|
|
430
440
|
);
|
|
431
441
|
for (const entry of secretsData) {
|
|
432
442
|
next[entry.key] = entry.status === "set";
|
|
433
443
|
}
|
|
444
|
+
const nextLast4: Record<string, string> = {};
|
|
445
|
+
for (const entry of adhocData) {
|
|
446
|
+
next[entry.name] = true;
|
|
447
|
+
if (entry.last4) nextLast4[entry.name] = entry.last4;
|
|
448
|
+
}
|
|
449
|
+
setSecretLast4(nextLast4);
|
|
434
450
|
setApiKeyStatus(next);
|
|
435
451
|
} catch {
|
|
436
452
|
setApiKeyStatus({});
|
|
@@ -477,7 +493,36 @@ export default function SettingsIndexRoute() {
|
|
|
477
493
|
}
|
|
478
494
|
}
|
|
479
495
|
|
|
496
|
+
function validateS3Values(
|
|
497
|
+
values: Record<string, string>,
|
|
498
|
+
): Record<string, string> {
|
|
499
|
+
const errors: Record<string, string> = {};
|
|
500
|
+
const urlFields = ["S3_ENDPOINT", "S3_PUBLIC_BASE_URL"];
|
|
501
|
+
for (const key of urlFields) {
|
|
502
|
+
const val = (values[key] ?? "").trim();
|
|
503
|
+
if (!val) continue;
|
|
504
|
+
try {
|
|
505
|
+
const parsed = new URL(val);
|
|
506
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
507
|
+
errors[key] = t("settings.s3UrlInvalid");
|
|
508
|
+
}
|
|
509
|
+
} catch {
|
|
510
|
+
errors[key] = t("settings.s3UrlInvalid");
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
const bucket = (values["S3_BUCKET"] ?? "").trim();
|
|
514
|
+
if (bucket && !/^[a-z0-9][a-z0-9\-.]{1,61}[a-z0-9]$/.test(bucket)) {
|
|
515
|
+
errors["S3_BUCKET"] = t("settings.s3BucketInvalid");
|
|
516
|
+
}
|
|
517
|
+
return errors;
|
|
518
|
+
}
|
|
519
|
+
|
|
480
520
|
async function handleSaveS3Storage() {
|
|
521
|
+
const validationErrors = validateS3Values(s3Values);
|
|
522
|
+
if (Object.keys(validationErrors).length > 0) {
|
|
523
|
+
setS3Errors(validationErrors);
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
481
526
|
const s3Configured = storageStatus.data?.activeProvider?.id === "s3";
|
|
482
527
|
const missing = s3Configured
|
|
483
528
|
? []
|
|
@@ -499,7 +544,7 @@ export default function SettingsIndexRoute() {
|
|
|
499
544
|
...current,
|
|
500
545
|
S3_SECRET_ACCESS_KEY: "",
|
|
501
546
|
}));
|
|
502
|
-
await storageStatus.refetch();
|
|
547
|
+
await Promise.all([storageStatus.refetch(), refreshApiKeyStatus()]);
|
|
503
548
|
toast.success(t("settings.storageSaved"));
|
|
504
549
|
} catch (err) {
|
|
505
550
|
toast.error(
|
|
@@ -510,6 +555,51 @@ export default function SettingsIndexRoute() {
|
|
|
510
555
|
}
|
|
511
556
|
}
|
|
512
557
|
|
|
558
|
+
async function handleClearAllS3() {
|
|
559
|
+
setClearingS3(true);
|
|
560
|
+
try {
|
|
561
|
+
const results = await Promise.all(
|
|
562
|
+
S3_STORAGE_FIELDS.filter((field) => apiKeyStatus[field.key]).map(
|
|
563
|
+
async (field) => {
|
|
564
|
+
const res = await fetch(
|
|
565
|
+
agentNativePath(
|
|
566
|
+
`/_agent-native/secrets/adhoc/${encodeURIComponent(field.key)}`,
|
|
567
|
+
),
|
|
568
|
+
{ method: "DELETE" },
|
|
569
|
+
);
|
|
570
|
+
if (!res.ok) {
|
|
571
|
+
const body = (await res.json().catch(() => null)) as {
|
|
572
|
+
error?: string;
|
|
573
|
+
} | null;
|
|
574
|
+
throw new Error(
|
|
575
|
+
body?.error ?? `Failed to clear ${field.key} (${res.status})`,
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
const body = (await res.json().catch(() => null)) as {
|
|
579
|
+
removed?: boolean;
|
|
580
|
+
} | null;
|
|
581
|
+
return { key: field.key, removed: body?.removed !== false };
|
|
582
|
+
},
|
|
583
|
+
),
|
|
584
|
+
);
|
|
585
|
+
const failed = results.filter((r) => !r.removed).map((r) => r.key);
|
|
586
|
+
if (failed.length > 0) {
|
|
587
|
+
throw new Error(
|
|
588
|
+
`Could not remove: ${failed.join(", ")}. You may not have permission.`,
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
setS3Values({});
|
|
592
|
+
await Promise.all([refreshApiKeyStatus(), storageStatus.refetch()]);
|
|
593
|
+
toast.success(t("settings.keyCleared"));
|
|
594
|
+
} catch (err) {
|
|
595
|
+
toast.error(
|
|
596
|
+
err instanceof Error ? err.message : t("settings.saveFailed"),
|
|
597
|
+
);
|
|
598
|
+
} finally {
|
|
599
|
+
setClearingS3(false);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
513
603
|
async function handleSaveApiKey(key: string) {
|
|
514
604
|
const value = (apiKeyValues[key] ?? "").trim();
|
|
515
605
|
if (!value) {
|
|
@@ -785,34 +875,90 @@ export default function SettingsIndexRoute() {
|
|
|
785
875
|
<CollapsibleContent>
|
|
786
876
|
<div className="space-y-4 border-t border-border px-3 py-4">
|
|
787
877
|
<div className="grid gap-4 sm:grid-cols-2">
|
|
788
|
-
{S3_STORAGE_FIELDS.map((field) =>
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
878
|
+
{S3_STORAGE_FIELDS.map((field) => {
|
|
879
|
+
const configured = Boolean(
|
|
880
|
+
apiKeyStatus[field.key],
|
|
881
|
+
);
|
|
882
|
+
const last4 = secretLast4[field.key];
|
|
883
|
+
return (
|
|
884
|
+
<div key={field.key} className="space-y-1.5">
|
|
885
|
+
<div className="flex items-center justify-between gap-2">
|
|
886
|
+
<Label htmlFor={field.key}>
|
|
887
|
+
{t(field.labelKey)}
|
|
888
|
+
</Label>
|
|
889
|
+
{configured ? (
|
|
890
|
+
<span className="flex items-center gap-1 text-[10px] font-medium text-primary">
|
|
891
|
+
<IconCheck className="h-3 w-3" />
|
|
892
|
+
{last4
|
|
893
|
+
? `••••${last4}`
|
|
894
|
+
: t("settings.keySet")}
|
|
895
|
+
</span>
|
|
896
|
+
) : null}
|
|
897
|
+
</div>
|
|
898
|
+
<Input
|
|
899
|
+
id={field.key}
|
|
900
|
+
type={
|
|
901
|
+
"secret" in field && field.secret
|
|
902
|
+
? "password"
|
|
903
|
+
: "text"
|
|
904
|
+
}
|
|
905
|
+
value={s3Values[field.key] ?? ""}
|
|
906
|
+
onChange={(event) => {
|
|
907
|
+
setS3Values((current) => ({
|
|
908
|
+
...current,
|
|
909
|
+
[field.key]: event.target.value,
|
|
910
|
+
}));
|
|
911
|
+
if (s3Errors[field.key]) {
|
|
912
|
+
setS3Errors((current) => {
|
|
913
|
+
const next = { ...current };
|
|
914
|
+
delete next[field.key];
|
|
915
|
+
return next;
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
}}
|
|
919
|
+
placeholder={
|
|
920
|
+
configured
|
|
921
|
+
? t("settings.replaceKey")
|
|
922
|
+
: field.placeholder
|
|
923
|
+
}
|
|
924
|
+
autoComplete="off"
|
|
925
|
+
disabled={savingStorage}
|
|
926
|
+
className={
|
|
927
|
+
s3Errors[field.key]
|
|
928
|
+
? "border-destructive"
|
|
929
|
+
: undefined
|
|
930
|
+
}
|
|
931
|
+
/>
|
|
932
|
+
{s3Errors[field.key] ? (
|
|
933
|
+
<p className="text-[11px] text-destructive">
|
|
934
|
+
{s3Errors[field.key]}
|
|
935
|
+
</p>
|
|
936
|
+
) : null}
|
|
937
|
+
</div>
|
|
938
|
+
);
|
|
939
|
+
})}
|
|
813
940
|
</div>
|
|
814
941
|
|
|
815
|
-
<div className="flex justify-end">
|
|
942
|
+
<div className="flex items-center justify-end gap-2">
|
|
943
|
+
{S3_STORAGE_FIELDS.some(
|
|
944
|
+
(field) => apiKeyStatus[field.key],
|
|
945
|
+
) ? (
|
|
946
|
+
<Button
|
|
947
|
+
type="button"
|
|
948
|
+
variant="ghost"
|
|
949
|
+
size="sm"
|
|
950
|
+
onClick={handleClearAllS3}
|
|
951
|
+
disabled={clearingS3 || savingStorage}
|
|
952
|
+
className="text-muted-foreground hover:text-destructive"
|
|
953
|
+
>
|
|
954
|
+
{clearingS3 ? (
|
|
955
|
+
<IconLoader2 className="h-4 w-4 animate-spin" />
|
|
956
|
+
) : (
|
|
957
|
+
<IconTrash className="h-4 w-4" />
|
|
958
|
+
)}
|
|
959
|
+
{t("settings.clearAllS3")}
|
|
960
|
+
</Button>
|
|
961
|
+
) : null}
|
|
816
962
|
<Button
|
|
817
963
|
onClick={handleSaveS3Storage}
|
|
818
964
|
disabled={
|
|
@@ -462,6 +462,16 @@ function uploadTooLargeMessage(size: number, detail?: string): string {
|
|
|
462
462
|
)}) after automatic compression. Trim or export a shorter copy and upload again.`;
|
|
463
463
|
}
|
|
464
464
|
|
|
465
|
+
/** Pre-upload size rejection for a picked file — no compression has been
|
|
466
|
+
* attempted yet, so the message must not imply it has. */
|
|
467
|
+
function fileTooLargeMessage(size: number): string {
|
|
468
|
+
return `This file is too large to upload (${formatMb(
|
|
469
|
+
size,
|
|
470
|
+
)}, limit is ${formatMb(
|
|
471
|
+
MAX_UPLOAD_BYTES,
|
|
472
|
+
)}). Trim it or export a shorter copy and try again.`;
|
|
473
|
+
}
|
|
474
|
+
|
|
465
475
|
function isUploadFailureError(error: string): boolean {
|
|
466
476
|
return (
|
|
467
477
|
isUploadSizeError(error) ||
|
|
@@ -471,7 +481,7 @@ function isUploadFailureError(error: string): boolean {
|
|
|
471
481
|
|
|
472
482
|
function friendlyRecordingErrorMessage(error: string): string {
|
|
473
483
|
if (isUploadSizeError(error)) {
|
|
474
|
-
return `This video is too large for Clips
|
|
484
|
+
return `This video is too large for Clips. Trim or export a shorter copy under ${formatMb(
|
|
475
485
|
MAX_UPLOAD_BYTES,
|
|
476
486
|
)} and upload again.`;
|
|
477
487
|
}
|
|
@@ -835,6 +845,12 @@ export default function RecordRoute() {
|
|
|
835
845
|
const [compressionProgress, setCompressionProgress] = useState<number | null>(
|
|
836
846
|
null,
|
|
837
847
|
);
|
|
848
|
+
// Fraction (0-1) of upload chunks confirmed sent so far. Chunks are fixed-size
|
|
849
|
+
// slices of the already-recorded blob, so chunksSent / totalChunks is a
|
|
850
|
+
// truthful proxy for bytes uploaded — not simulated. Null means the total
|
|
851
|
+
// chunk count isn't known yet (e.g. the brief live-streaming remainder
|
|
852
|
+
// upload), so the overlay falls back to an indeterminate spinner.
|
|
853
|
+
const [uploadProgress, setUploadProgress] = useState<number | null>(null);
|
|
838
854
|
|
|
839
855
|
const queryClient = useQueryClient();
|
|
840
856
|
const { isDesktopApp } = useDesktopPromo();
|
|
@@ -1103,19 +1119,29 @@ export default function RecordRoute() {
|
|
|
1103
1119
|
// upload — applies whether or not we just came from
|
|
1104
1120
|
// compressing.
|
|
1105
1121
|
setCompressionProgress(null);
|
|
1122
|
+
// Reset upload progress at the start of each upload attempt so
|
|
1123
|
+
// a retry doesn't briefly show the previous attempt's percent.
|
|
1124
|
+
setUploadProgress(null);
|
|
1106
1125
|
// Always sync the UI back to "uploading"; if we were already
|
|
1107
1126
|
// there from doStop's pre-stop transition, this is a no-op.
|
|
1108
1127
|
setUiState("uploading");
|
|
1109
1128
|
}
|
|
1110
1129
|
},
|
|
1111
|
-
onChunk: ({ index,
|
|
1130
|
+
onChunk: ({ index, total }) => {
|
|
1131
|
+
// `total` is only known once the full recording is sliced into
|
|
1132
|
+
// fixed-size chunks after stop(); the live per-chunk uploads
|
|
1133
|
+
// during recording report `total: null` and don't drive this bar.
|
|
1134
|
+
const fraction = total ? (index + 1) / total : null;
|
|
1135
|
+
setUploadProgress(fraction);
|
|
1112
1136
|
const recordingId = pendingRef.current?.id;
|
|
1113
1137
|
if (!recordingId) return;
|
|
1138
|
+
// Only expose a percentage here — this state is agent-visible, and
|
|
1139
|
+
// chunk/byte counts are an internal transport detail, not
|
|
1140
|
+
// something to surface to the user.
|
|
1114
1141
|
void writeAppState(`recording-upload-${recordingId}`, {
|
|
1115
1142
|
recordingId,
|
|
1116
1143
|
status: "uploading",
|
|
1117
|
-
|
|
1118
|
-
lastChunkBytes: bytes,
|
|
1144
|
+
progress: fraction !== null ? Math.round(fraction * 100) : null,
|
|
1119
1145
|
updatedAt: new Date().toISOString(),
|
|
1120
1146
|
}).catch(() => {});
|
|
1121
1147
|
},
|
|
@@ -1378,6 +1404,7 @@ export default function RecordRoute() {
|
|
|
1378
1404
|
setError(null);
|
|
1379
1405
|
setUiState("uploading");
|
|
1380
1406
|
setCompressionProgress(null);
|
|
1407
|
+
setUploadProgress(null);
|
|
1381
1408
|
|
|
1382
1409
|
const acceptedMime = new Set([
|
|
1383
1410
|
"video/mp4",
|
|
@@ -1406,6 +1433,22 @@ export default function RecordRoute() {
|
|
|
1406
1433
|
return;
|
|
1407
1434
|
}
|
|
1408
1435
|
|
|
1436
|
+
// Fail fast on oversized files before we probe metadata, attempt
|
|
1437
|
+
// compression, or open the upload session — no point spending time or
|
|
1438
|
+
// chunking bytes for a file the server will reject anyway. Uses the
|
|
1439
|
+
// same MAX_UPLOAD_BYTES ceiling as the (currently compression-gated)
|
|
1440
|
+
// post-compression check below and the server chunk/finalize routes.
|
|
1441
|
+
if (file.size > MAX_UPLOAD_BYTES) {
|
|
1442
|
+
const message = fileTooLargeMessage(file.size);
|
|
1443
|
+
if (fileUploadAbortRef.current === abort) {
|
|
1444
|
+
fileUploadAbortRef.current = null;
|
|
1445
|
+
}
|
|
1446
|
+
setError(message);
|
|
1447
|
+
setUiState("error");
|
|
1448
|
+
toast.error(message);
|
|
1449
|
+
return;
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1409
1452
|
let createdId: string | null = null;
|
|
1410
1453
|
try {
|
|
1411
1454
|
const status = await fetchVideoStorageStatus();
|
|
@@ -1628,6 +1671,7 @@ export default function RecordRoute() {
|
|
|
1628
1671
|
unknown
|
|
1629
1672
|
> | null) ?? null;
|
|
1630
1673
|
}
|
|
1674
|
+
setUploadProgress((i + 1) / totalChunks);
|
|
1631
1675
|
}
|
|
1632
1676
|
|
|
1633
1677
|
setUiState("complete");
|
|
@@ -1699,6 +1743,7 @@ export default function RecordRoute() {
|
|
|
1699
1743
|
fileUploadAbortRef.current = null;
|
|
1700
1744
|
}
|
|
1701
1745
|
setCompressionProgress(null);
|
|
1746
|
+
setUploadProgress(null);
|
|
1702
1747
|
}
|
|
1703
1748
|
},
|
|
1704
1749
|
[markStorageConfigured, navigate, probeVideoMetadata],
|
|
@@ -1863,6 +1908,7 @@ export default function RecordRoute() {
|
|
|
1863
1908
|
setCameraStream(null);
|
|
1864
1909
|
setPreviewStream(null);
|
|
1865
1910
|
setCompressionProgress(null);
|
|
1911
|
+
setUploadProgress(null);
|
|
1866
1912
|
setUiState("complete");
|
|
1867
1913
|
if (result.waitingForStorage) {
|
|
1868
1914
|
toast.info(t("recordRoute.recordingReadyToUpload"), {
|
|
@@ -2006,6 +2052,7 @@ export default function RecordRoute() {
|
|
|
2006
2052
|
|
|
2007
2053
|
setError(null);
|
|
2008
2054
|
setCompressionProgress(null);
|
|
2055
|
+
setUploadProgress(null);
|
|
2009
2056
|
setUiState("uploading");
|
|
2010
2057
|
try {
|
|
2011
2058
|
const retryResult = await engine.retryUpload();
|
|
@@ -2022,6 +2069,7 @@ export default function RecordRoute() {
|
|
|
2022
2069
|
body: JSON.stringify({ reason: message }),
|
|
2023
2070
|
}).catch(() => {});
|
|
2024
2071
|
setCompressionProgress(null);
|
|
2072
|
+
setUploadProgress(null);
|
|
2025
2073
|
setError(message);
|
|
2026
2074
|
setUiState("error");
|
|
2027
2075
|
toast.error(t("recordRoute.uploadFailed"), {
|
|
@@ -2085,6 +2133,7 @@ export default function RecordRoute() {
|
|
|
2085
2133
|
setPreviewStream(null);
|
|
2086
2134
|
setIsPaused(false);
|
|
2087
2135
|
setUiState("idle");
|
|
2136
|
+
setUploadProgress(null);
|
|
2088
2137
|
pendingRef.current = null;
|
|
2089
2138
|
engineRef.current = null;
|
|
2090
2139
|
}, [extensionCapture, liveTranscription]);
|
|
@@ -2427,7 +2476,9 @@ export default function RecordRoute() {
|
|
|
2427
2476
|
users wonder if the app froze). */}
|
|
2428
2477
|
{(uiState === "uploading" || uiState === "compressing") && (
|
|
2429
2478
|
<div className="fixed inset-0 z-[120] flex flex-col items-center justify-center gap-3 bg-black/70 text-white backdrop-blur">
|
|
2430
|
-
|
|
2479
|
+
{!(uiState === "uploading" && uploadProgress !== null) && (
|
|
2480
|
+
<Spinner className="h-10 w-10 text-white/70" />
|
|
2481
|
+
)}
|
|
2431
2482
|
{uiState === "compressing" ? (
|
|
2432
2483
|
<>
|
|
2433
2484
|
<div className="text-sm">
|
|
@@ -2441,7 +2492,24 @@ export default function RecordRoute() {
|
|
|
2441
2492
|
</div>
|
|
2442
2493
|
</>
|
|
2443
2494
|
) : (
|
|
2444
|
-
|
|
2495
|
+
<>
|
|
2496
|
+
<div className="text-sm">{t("recordRoute.savingRecording")}</div>
|
|
2497
|
+
{uploadProgress !== null && (
|
|
2498
|
+
<div className="flex w-48 flex-col items-center gap-1">
|
|
2499
|
+
<div className="h-1.5 w-full overflow-hidden rounded-full bg-white/20">
|
|
2500
|
+
<div
|
|
2501
|
+
className="h-full rounded-full bg-white transition-all"
|
|
2502
|
+
style={{
|
|
2503
|
+
width: `${Math.min(100, Math.max(0, Math.round(uploadProgress * 100)))}%`,
|
|
2504
|
+
}}
|
|
2505
|
+
/>
|
|
2506
|
+
</div>
|
|
2507
|
+
<div className="text-[11px] text-white/50">
|
|
2508
|
+
{Math.round(uploadProgress * 100)}%
|
|
2509
|
+
</div>
|
|
2510
|
+
</div>
|
|
2511
|
+
)}
|
|
2512
|
+
</>
|
|
2445
2513
|
)}
|
|
2446
2514
|
<button
|
|
2447
2515
|
onClick={doCancel}
|
|
@@ -325,6 +325,31 @@ ALTER TABLE plans ADD COLUMN source_author_name TEXT;
|
|
|
325
325
|
ALTER TABLE plans ADD COLUMN source_author_login TEXT`,
|
|
326
326
|
},
|
|
327
327
|
},
|
|
328
|
+
{
|
|
329
|
+
// Repair migration for hosted databases that recorded an earlier migration
|
|
330
|
+
// while still missing additive columns now present in schema.ts. Missing
|
|
331
|
+
// optional plan columns make Drizzle's full-row access lookup throw before
|
|
332
|
+
// plan pages can render or show a clean access error.
|
|
333
|
+
version: 36,
|
|
334
|
+
sql: {
|
|
335
|
+
postgres: `ALTER TABLE plans ADD COLUMN IF NOT EXISTS deleted_at TEXT;
|
|
336
|
+
ALTER TABLE plans ADD COLUMN IF NOT EXISTS deleted_by TEXT;
|
|
337
|
+
ALTER TABLE plans ADD COLUMN IF NOT EXISTS source_type TEXT;
|
|
338
|
+
ALTER TABLE plans ADD COLUMN IF NOT EXISTS source_repo TEXT;
|
|
339
|
+
ALTER TABLE plans ADD COLUMN IF NOT EXISTS source_pr_number INTEGER;
|
|
340
|
+
ALTER TABLE plans ADD COLUMN IF NOT EXISTS source_pr_state TEXT;
|
|
341
|
+
ALTER TABLE plans ADD COLUMN IF NOT EXISTS source_pr_merged_at TEXT;
|
|
342
|
+
ALTER TABLE plans ADD COLUMN IF NOT EXISTS source_author_email TEXT;
|
|
343
|
+
ALTER TABLE plans ADD COLUMN IF NOT EXISTS source_author_name TEXT;
|
|
344
|
+
ALTER TABLE plans ADD COLUMN IF NOT EXISTS source_author_login TEXT;
|
|
345
|
+
ALTER TABLE plan_comments ADD COLUMN IF NOT EXISTS deleted_at TEXT;
|
|
346
|
+
ALTER TABLE plan_comments ADD COLUMN IF NOT EXISTS deleted_by TEXT;
|
|
347
|
+
CREATE INDEX IF NOT EXISTS plans_owner_deleted_updated_idx ON plans(owner_email, deleted_at, updated_at);
|
|
348
|
+
CREATE INDEX IF NOT EXISTS plans_recap_pr_merged_idx ON plans(kind, source_type, source_pr_merged_at, updated_at);
|
|
349
|
+
CREATE INDEX IF NOT EXISTS plans_source_pr_idx ON plans(source_repo, source_pr_number);
|
|
350
|
+
CREATE INDEX IF NOT EXISTS plan_comments_plan_deleted_created_idx ON plan_comments(plan_id, deleted_at, created_at)`,
|
|
351
|
+
},
|
|
352
|
+
},
|
|
328
353
|
],
|
|
329
354
|
{ table: "plans_migrations" },
|
|
330
355
|
);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AssistantChat.d.ts","sourceRoot":"","sources":["../../src/client/AssistantChat.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAEV,gBAAgB,EAChB,yBAAyB,EAC1B,MAAM,qBAAqB,CAAC;AAW7B,OAAO,KASN,MAAM,OAAO,CAAC;AAGf,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAYrE,OAAO,EAEL,KAAK,oBAAoB,EAC1B,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAQL,KAAK,oBAAoB,EAC1B,MAAM,iBAAiB,CAAC;AAuCzB,OAAO,EAQL,KAAK,sBAAsB,EAG5B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAEL,KAAK,gBAAgB,EACtB,MAAM,mBAAmB,CAAC;AAiB3B,OAAO,EAEL,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAE5B,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EACV,0BAA0B,EAE3B,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAEL,KAAK,6BAA6B,EACnC,MAAM,0BAA0B,CAAC;AAKlC,OAAO,EAEL,KAAK,WAAW,EAIjB,MAAM,0BAA0B,CAAC;AAKlC,OAAO,KAAK,EACV,eAAe,EACf,kBAAkB,EACnB,MAAM,uBAAuB,CAAC;AAI/B,OAAO,EACL,iCAAiC,EACjC,kCAAkC,EAClC,qCAAqC,EACrC,mCAAmC,EACnC,4BAA4B,GAC7B,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAO1E,MAAM,MAAM,gBAAgB,GAAG,KAAK,GAAG,MAAM,CAAC;AAC9C,MAAM,MAAM,mBAAmB,GAAG,UAAU,GAAG,OAAO,CAAC;AACvD,MAAM,WAAW,wBAAwB;IACvC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,WAAW,CAAC,EAAE,gBAAgB,CAAC;CAChC;
|
|
1
|
+
{"version":3,"file":"AssistantChat.d.ts","sourceRoot":"","sources":["../../src/client/AssistantChat.tsx"],"names":[],"mappings":"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"}
|
|
@@ -76,6 +76,12 @@ const AUTO_RESUME_STATUS_TIMEOUT_MS = 30_000;
|
|
|
76
76
|
// the live label appears so a genuinely slow step reads as working, not hung.
|
|
77
77
|
const ACTIVITY_LABEL_REVEAL_DELAY_MS = 6_000;
|
|
78
78
|
const SUBMIT_ENGINE_STATUS_TIMEOUT_MS = 1000;
|
|
79
|
+
function isReplayableTerminalRun(runInfo) {
|
|
80
|
+
const dispatchMode = typeof runInfo.dispatchMode === "string" ? runInfo.dispatchMode : "";
|
|
81
|
+
return (runInfo.status !== "running" &&
|
|
82
|
+
dispatchMode.startsWith("background") &&
|
|
83
|
+
runInfo.terminalReason === "run_timeout");
|
|
84
|
+
}
|
|
79
85
|
function activeRunLooksStale(runInfo) {
|
|
80
86
|
const lastProgressAt = typeof runInfo.lastProgressAt === "number" ? runInfo.lastProgressAt : null;
|
|
81
87
|
const nowMs = typeof runInfo.serverNow === "number" ? runInfo.serverNow : Date.now();
|
|
@@ -1111,7 +1117,9 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
1111
1117
|
(!stopped.runId || !runId || stopped.runId === runId));
|
|
1112
1118
|
}, []);
|
|
1113
1119
|
const startReconnectToRun = useCallback((runInfo) => {
|
|
1114
|
-
if (!threadId ||
|
|
1120
|
+
if (!threadId ||
|
|
1121
|
+
!runInfo.runId ||
|
|
1122
|
+
(runInfo.status !== "running" && !isReplayableTerminalRun(runInfo))) {
|
|
1115
1123
|
return false;
|
|
1116
1124
|
}
|
|
1117
1125
|
const runId = String(runInfo.runId);
|
|
@@ -1139,6 +1147,7 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
1139
1147
|
}));
|
|
1140
1148
|
const abortCtrl = new AbortController();
|
|
1141
1149
|
reconnectAbortRef.current = abortCtrl;
|
|
1150
|
+
let reconnectTerminalReason = null;
|
|
1142
1151
|
const watchdog = setInterval(async () => {
|
|
1143
1152
|
try {
|
|
1144
1153
|
const res = await fetch(`${apiUrl}/runs/active?threadId=${encodeURIComponent(threadId)}`);
|
|
@@ -1148,6 +1157,9 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
1148
1157
|
return;
|
|
1149
1158
|
}
|
|
1150
1159
|
const info = (await res.json());
|
|
1160
|
+
if (isReplayableTerminalRun(info)) {
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1151
1163
|
if (info.status !== "running" || activeRunLooksStale(info)) {
|
|
1152
1164
|
abortCtrl.abort();
|
|
1153
1165
|
clearInterval(watchdog);
|
|
@@ -1171,6 +1183,8 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
1171
1183
|
lastReconnectProgressAt = Date.now();
|
|
1172
1184
|
};
|
|
1173
1185
|
const idleCheck = setInterval(() => {
|
|
1186
|
+
if (reconnectTerminalReason !== null)
|
|
1187
|
+
return;
|
|
1174
1188
|
if (!reconnectProgressTimedOut({
|
|
1175
1189
|
lastProgressAt: lastReconnectProgressAt,
|
|
1176
1190
|
now: Date.now(),
|
|
@@ -1274,6 +1288,11 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
1274
1288
|
if (err instanceof AgentAutoContinueSignal &&
|
|
1275
1289
|
err.reason === "no_progress") {
|
|
1276
1290
|
noProgressDuringReconnect = true;
|
|
1291
|
+
reconnectTerminalReason = err.reason;
|
|
1292
|
+
}
|
|
1293
|
+
else if (err instanceof AgentAutoContinueSignal) {
|
|
1294
|
+
noProgressDuringReconnect = true;
|
|
1295
|
+
reconnectTerminalReason = err.reason;
|
|
1277
1296
|
}
|
|
1278
1297
|
else if (reconnectTimedOut &&
|
|
1279
1298
|
err instanceof Error &&
|
|
@@ -1289,11 +1308,15 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
1289
1308
|
clearInterval(idleCheck);
|
|
1290
1309
|
}
|
|
1291
1310
|
if (noProgressDuringReconnect && reconnectRunIdRef.current === runId) {
|
|
1292
|
-
|
|
1311
|
+
const reconnectErrorCode = reconnectTerminalReason === "run_timeout"
|
|
1312
|
+
? "run_timeout"
|
|
1313
|
+
: "reconnect_no_progress";
|
|
1314
|
+
captureError(new Error(`agent-chat:${reconnectErrorCode}`), {
|
|
1293
1315
|
tags: {
|
|
1294
1316
|
context: "agent-native-chat",
|
|
1295
|
-
errorCode:
|
|
1317
|
+
errorCode: reconnectErrorCode,
|
|
1296
1318
|
reconnectTimedOut: String(reconnectTimedOut),
|
|
1319
|
+
reconnectTerminalReason: reconnectTerminalReason ?? undefined,
|
|
1297
1320
|
},
|
|
1298
1321
|
extra: {
|
|
1299
1322
|
runId,
|
|
@@ -1302,15 +1325,17 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
1302
1325
|
contentLength: latestContent.length,
|
|
1303
1326
|
},
|
|
1304
1327
|
});
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1328
|
+
if (reconnectTerminalReason !== "run_timeout") {
|
|
1329
|
+
try {
|
|
1330
|
+
await fetch(`${apiUrl}/runs/${encodeURIComponent(runId)}/abort`, {
|
|
1331
|
+
method: "POST",
|
|
1332
|
+
headers: { "Content-Type": "application/json" },
|
|
1333
|
+
body: JSON.stringify({ reason: "no_progress" }),
|
|
1334
|
+
});
|
|
1335
|
+
}
|
|
1336
|
+
catch {
|
|
1337
|
+
// Best effort — the important part is unwinding the UI.
|
|
1338
|
+
}
|
|
1314
1339
|
}
|
|
1315
1340
|
if (afterSeq > 0) {
|
|
1316
1341
|
// Tail-resume only replays new events; never freeze that slice as a
|
|
@@ -1327,8 +1352,10 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
1327
1352
|
reconnectCanMaterializeRef.current = latestContent.length > 0;
|
|
1328
1353
|
}
|
|
1329
1354
|
setRunErrorInfo({
|
|
1330
|
-
message:
|
|
1331
|
-
|
|
1355
|
+
message: reconnectTerminalReason === "run_timeout"
|
|
1356
|
+
? "The previous background agent run reached its time limit before finishing. The partial work was preserved; continue or retry to pick up from here."
|
|
1357
|
+
: "The previous agent run stopped producing visible progress during recovery, so it was stopped before it could keep looping.",
|
|
1358
|
+
errorCode: reconnectErrorCode,
|
|
1332
1359
|
recoverable: true,
|
|
1333
1360
|
runId,
|
|
1334
1361
|
});
|
|
@@ -1400,7 +1427,7 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
1400
1427
|
return false;
|
|
1401
1428
|
const runInfo = (await runRes.json());
|
|
1402
1429
|
if (!runInfo.active ||
|
|
1403
|
-
runInfo.status !== "running" ||
|
|
1430
|
+
(runInfo.status !== "running" && !isReplayableTerminalRun(runInfo)) ||
|
|
1404
1431
|
activeRunLooksStale(runInfo)) {
|
|
1405
1432
|
if (storedActiveRun?.threadId === threadId) {
|
|
1406
1433
|
clearActiveRunIfMatches(threadId, storedActiveRun.runId);
|