@agent-native/core 0.79.7 → 0.79.9
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 +13 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/run-manager.ts +9 -1
- package/corpus/core/src/client/agent-chat-adapter.ts +24 -16
- package/corpus/core/src/client/session-replay.ts +67 -13
- package/corpus/core/src/onboarding/default-steps.ts +3 -3
- package/corpus/core/src/server/agent-chat-plugin.ts +82 -162
- package/corpus/core/src/server/agent-run-context.ts +204 -0
- package/corpus/core/src/server/credential-provider.ts +20 -4
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +14 -4
- package/corpus/templates/analytics/changelog/2026-06-26-session-replay-uploads-now-accept-compressed-payloads-so-lar.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-26-sql-chart-tooltips-now-stay-stable-while-moving-across-dense.md +6 -0
- package/corpus/templates/analytics/server/handlers/session-replay.ts +56 -8
- package/corpus/templates/clips/app/components/player/transcript-panel.tsx +1 -1
- package/corpus/templates/clips/app/components/recorder/storage-setup-card.tsx +44 -16
- package/corpus/templates/clips/app/i18n/ar-SA.ts +12 -9
- package/corpus/templates/clips/app/i18n/de-DE.ts +12 -9
- package/corpus/templates/clips/app/i18n/en-US.ts +19 -16
- package/corpus/templates/clips/app/i18n/es-ES.ts +12 -9
- package/corpus/templates/clips/app/i18n/fr-FR.ts +12 -9
- package/corpus/templates/clips/app/i18n/hi-IN.ts +13 -9
- package/corpus/templates/clips/app/i18n/ja-JP.ts +12 -9
- package/corpus/templates/clips/app/i18n/ko-KR.ts +12 -9
- package/corpus/templates/clips/app/i18n/pt-BR.ts +12 -9
- package/corpus/templates/clips/app/i18n/zh-CN.ts +19 -14
- package/corpus/templates/clips/app/i18n/zh-TW.ts +19 -14
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +7 -0
- package/corpus/templates/clips/app/routes/record.tsx +2 -2
- package/corpus/templates/clips/changelog/2026-06-26-desktop-recordings-recover-more-reliably-when-macos-reports-.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-26-slack-unfurls-now-recognize-recording-dashboard-links-pasted.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-26-storage-setup-now-explains-why-clips-needs-connected-storage.md +6 -0
- package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
- package/corpus/templates/clips/chrome-extension/src/background.ts +24 -3
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +28 -2
- package/corpus/templates/clips/chrome-extension/src/popup.html +16 -0
- package/corpus/templates/clips/chrome-extension/src/popup.ts +59 -7
- package/corpus/templates/clips/chrome-extension/src/sentry.ts +28 -1
- package/corpus/templates/clips/chrome-extension/src/styles.css +74 -0
- package/corpus/templates/clips/desktop/src/app.tsx +85 -18
- package/corpus/templates/clips/desktop/src/styles.css +55 -0
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +88 -51
- package/corpus/templates/clips/desktop/src-tauri/src/native_speech.rs +12 -3
- package/corpus/templates/clips/server/lib/slack-unfurls.ts +5 -2
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +12 -1
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +15 -8
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/session-replay.d.ts.map +1 -1
- package/dist/client/session-replay.js +55 -14
- package/dist/client/session-replay.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +6 -6
- package/dist/onboarding/default-steps.js +3 -3
- package/dist/onboarding/default-steps.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +69 -154
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +2 -2
- package/dist/server/agent-run-context.d.ts +35 -0
- package/dist/server/agent-run-context.d.ts.map +1 -0
- package/dist/server/agent-run-context.js +137 -0
- package/dist/server/agent-run-context.js.map +1 -0
- package/dist/server/credential-provider.d.ts.map +1 -1
- package/dist/server/credential-provider.js +15 -4
- package/dist/server/credential-provider.js.map +1 -1
- package/package.json +1 -1
- package/corpus/templates/.retired/README.md +0 -9
- package/corpus/templates/.retired/calls/package.json +0 -8
- package/corpus/templates/.retired/code/package.json +0 -8
- package/corpus/templates/.retired/contracts/package.json +0 -8
- package/corpus/templates/.retired/images/package.json +0 -8
- package/corpus/templates/.retired/issues/package.json +0 -8
- package/corpus/templates/.retired/meeting-notes/package.json +0 -8
- package/corpus/templates/.retired/migration/package.json +0 -8
- package/corpus/templates/.retired/recruiting/package.json +0 -8
- package/corpus/templates/.retired/scheduling/package.json +0 -8
- package/corpus/templates/.retired/visual-plans/package.json +0 -8
- package/corpus/templates/.retired/voice/package.json +0 -8
- package/corpus/templates/.retired/workbench/package.json +0 -8
|
@@ -574,6 +574,80 @@ svg {
|
|
|
574
574
|
color: var(--danger);
|
|
575
575
|
}
|
|
576
576
|
|
|
577
|
+
.storage-help {
|
|
578
|
+
position: relative;
|
|
579
|
+
display: flex;
|
|
580
|
+
min-height: 18px;
|
|
581
|
+
align-items: center;
|
|
582
|
+
justify-content: center;
|
|
583
|
+
gap: 5px;
|
|
584
|
+
color: var(--fg-subtle);
|
|
585
|
+
font-size: 11px;
|
|
586
|
+
line-height: 1.3;
|
|
587
|
+
text-align: center;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
.storage-help[hidden] {
|
|
591
|
+
display: none;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
.storage-help-open,
|
|
595
|
+
.storage-help-why {
|
|
596
|
+
border: 0;
|
|
597
|
+
background: transparent;
|
|
598
|
+
color: var(--fg-muted);
|
|
599
|
+
font: inherit;
|
|
600
|
+
padding: 0;
|
|
601
|
+
text-decoration: underline;
|
|
602
|
+
text-decoration-style: dotted;
|
|
603
|
+
text-underline-offset: 3px;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
.storage-help-open {
|
|
607
|
+
cursor: pointer;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
.storage-help-why {
|
|
611
|
+
position: relative;
|
|
612
|
+
cursor: help;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
.storage-help-open:hover,
|
|
616
|
+
.storage-help-open:focus-visible,
|
|
617
|
+
.storage-help-why:hover,
|
|
618
|
+
.storage-help-why:focus-visible {
|
|
619
|
+
color: var(--fg);
|
|
620
|
+
outline: none;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
.storage-help-details {
|
|
624
|
+
position: absolute;
|
|
625
|
+
right: 0;
|
|
626
|
+
bottom: calc(100% + 8px);
|
|
627
|
+
z-index: 70;
|
|
628
|
+
width: 310px;
|
|
629
|
+
padding: 8px 10px;
|
|
630
|
+
border-radius: var(--radius-sm);
|
|
631
|
+
background: var(--bg);
|
|
632
|
+
box-shadow: var(--shadow-md);
|
|
633
|
+
color: var(--fg);
|
|
634
|
+
font-size: 12px;
|
|
635
|
+
line-height: 1.45;
|
|
636
|
+
opacity: 0;
|
|
637
|
+
pointer-events: none;
|
|
638
|
+
text-align: left;
|
|
639
|
+
transform: translateY(3px);
|
|
640
|
+
transition:
|
|
641
|
+
opacity 120ms ease-out,
|
|
642
|
+
transform 120ms ease-out;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
.storage-help-why:hover .storage-help-details,
|
|
646
|
+
.storage-help-why:focus-visible .storage-help-details {
|
|
647
|
+
opacity: 1;
|
|
648
|
+
transform: translateY(0);
|
|
649
|
+
}
|
|
650
|
+
|
|
577
651
|
.bottom-row {
|
|
578
652
|
display: grid;
|
|
579
653
|
grid-template-columns: repeat(3, 1fr);
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
IconArrowLeft,
|
|
4
4
|
IconCircleCheck,
|
|
5
5
|
IconDownload,
|
|
6
|
+
IconExternalLink,
|
|
6
7
|
IconFolderOpen,
|
|
7
8
|
IconPencil,
|
|
8
9
|
IconInfoCircle,
|
|
@@ -118,6 +119,15 @@ type MeetingTranscriptionMode = "manual" | "ask" | "auto";
|
|
|
118
119
|
type CaptureMode = "screen" | "screen-camera" | "camera";
|
|
119
120
|
type VideoStorageStatus = "checking" | "configured" | "missing";
|
|
120
121
|
|
|
122
|
+
const STORAGE_SETUP_HELP_TEXT =
|
|
123
|
+
"Clips is 100% free and open source, so you need to hook up a way to store your clips. Connect storage with Builder.io for free-tier storage and AI, or use S3-compatible object storage and your own LLM keys.";
|
|
124
|
+
const STORAGE_SETUP_FAILURE_RE =
|
|
125
|
+
/video storage is not connected|no video storage configured|file upload provider|storage provider|connect builder|s3-compatible/i;
|
|
126
|
+
|
|
127
|
+
function isStorageSetupFailureMessage(message: string | null | undefined) {
|
|
128
|
+
return STORAGE_SETUP_FAILURE_RE.test(message ?? "");
|
|
129
|
+
}
|
|
130
|
+
|
|
121
131
|
const STORAGE_KEY = "clips:server-url";
|
|
122
132
|
const MODE_KEY = "clips:last-mode";
|
|
123
133
|
const VOICE_SHORTCUT_KEY = "clips:voice-shortcut";
|
|
@@ -1559,7 +1569,11 @@ export function App() {
|
|
|
1559
1569
|
} catch (err) {
|
|
1560
1570
|
const message = err instanceof Error ? err.message : String(err);
|
|
1561
1571
|
console.error("[clips-tray] retry saved upload failed:", err);
|
|
1562
|
-
setRecError(
|
|
1572
|
+
setRecError(
|
|
1573
|
+
isStorageSetupFailureMessage(message)
|
|
1574
|
+
? "Connect storage to finish uploading this saved clip: Builder.io (free tier storage + AI) or S3-compatible storage."
|
|
1575
|
+
: message,
|
|
1576
|
+
);
|
|
1563
1577
|
await loadPendingUploads();
|
|
1564
1578
|
} finally {
|
|
1565
1579
|
setRetryingUploadId(null);
|
|
@@ -1636,20 +1650,23 @@ export function App() {
|
|
|
1636
1650
|
});
|
|
1637
1651
|
}
|
|
1638
1652
|
|
|
1639
|
-
const openVideoStorageSetup = useCallback(
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
"Connect Builder.io or S3-compatible storage before recording from desktop. Opening storage setup...",
|
|
1643
|
-
);
|
|
1644
|
-
void openExternal(`${base}/record`).catch((err) => {
|
|
1653
|
+
const openVideoStorageSetup = useCallback(
|
|
1654
|
+
(targetServerUrl?: string) => {
|
|
1655
|
+
const base = (targetServerUrl?.trim() || serverUrl).replace(/\/+$/, "");
|
|
1645
1656
|
setRecError(
|
|
1646
|
-
|
|
1647
|
-
? err.message
|
|
1648
|
-
: "Could not open Clips storage setup.",
|
|
1657
|
+
"Connect storage before recording from desktop: Builder.io (free tier storage + AI) or S3-compatible storage. Opening storage setup...",
|
|
1649
1658
|
);
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1659
|
+
void openExternal(`${base}/record`).catch((err) => {
|
|
1660
|
+
setRecError(
|
|
1661
|
+
err instanceof Error
|
|
1662
|
+
? err.message
|
|
1663
|
+
: "Could not open Clips storage setup.",
|
|
1664
|
+
);
|
|
1665
|
+
});
|
|
1666
|
+
void refreshVideoStorageStatus();
|
|
1667
|
+
},
|
|
1668
|
+
[refreshVideoStorageStatus, serverUrl],
|
|
1669
|
+
);
|
|
1653
1670
|
|
|
1654
1671
|
async function handleStartRecording(options?: {
|
|
1655
1672
|
ignoreActiveRecorder?: boolean;
|
|
@@ -2148,6 +2165,7 @@ export function App() {
|
|
|
2148
2165
|
onRetry={retryPendingUpload}
|
|
2149
2166
|
onDiscard={discardPendingUpload}
|
|
2150
2167
|
onOpenFolder={openPendingUploadFolder}
|
|
2168
|
+
onConnectStorage={(upload) => openVideoStorageSetup(upload.serverUrl)}
|
|
2151
2169
|
/>
|
|
2152
2170
|
) : null}
|
|
2153
2171
|
|
|
@@ -2407,6 +2425,7 @@ function PendingUploadBanner({
|
|
|
2407
2425
|
onRetry,
|
|
2408
2426
|
onDiscard,
|
|
2409
2427
|
onOpenFolder,
|
|
2428
|
+
onConnectStorage,
|
|
2410
2429
|
}: {
|
|
2411
2430
|
uploads: PendingDesktopUpload[];
|
|
2412
2431
|
retryingUploadId: string | null;
|
|
@@ -2416,11 +2435,13 @@ function PendingUploadBanner({
|
|
|
2416
2435
|
onRetry: (upload: PendingDesktopUpload) => void;
|
|
2417
2436
|
onDiscard: (upload: PendingDesktopUpload) => void;
|
|
2418
2437
|
onOpenFolder: (upload: PendingDesktopUpload) => void;
|
|
2438
|
+
onConnectStorage: (upload: PendingDesktopUpload) => void;
|
|
2419
2439
|
}) {
|
|
2420
2440
|
const latest = uploads[0];
|
|
2421
2441
|
if (!latest) return null;
|
|
2422
2442
|
|
|
2423
2443
|
const retrying = retryingUploadId === latest.recordingId;
|
|
2444
|
+
const storageSetupFailure = isStorageSetupFailureMessage(latest.lastError);
|
|
2424
2445
|
const exporting = exportingUploadId === latest.recordingId;
|
|
2425
2446
|
const canOpenFolder = latest.kind === "native" && !!latest.folderPath;
|
|
2426
2447
|
const canExport = latest.kind === "browser";
|
|
@@ -2430,6 +2451,16 @@ function PendingUploadBanner({
|
|
|
2430
2451
|
uploads.length === 1
|
|
2431
2452
|
? "1 Clip saved locally"
|
|
2432
2453
|
: `${uploads.length} Clips saved locally`;
|
|
2454
|
+
const nativeCorrupt = latest.kind === "native" && !!latest.corrupt;
|
|
2455
|
+
const title = nativeCorrupt
|
|
2456
|
+
? uploads.length === 1
|
|
2457
|
+
? "Clip could not be finalized"
|
|
2458
|
+
: "Some Clips could not be finalized"
|
|
2459
|
+
: storageSetupFailure
|
|
2460
|
+
? uploads.length === 1
|
|
2461
|
+
? "Connect storage to upload saved Clip"
|
|
2462
|
+
: "Connect storage to upload saved Clips"
|
|
2463
|
+
: savedLabel;
|
|
2433
2464
|
const details = [
|
|
2434
2465
|
latest.savedAt ? `saved ${formatAgo(latest.savedAt)}` : null,
|
|
2435
2466
|
formatFileSize(latest.bytes),
|
|
@@ -2444,11 +2475,36 @@ function PendingUploadBanner({
|
|
|
2444
2475
|
<IconUpload size={17} stroke={1.8} />
|
|
2445
2476
|
</div>
|
|
2446
2477
|
<div className="pending-upload-copy">
|
|
2447
|
-
<div className="pending-upload-title">{
|
|
2448
|
-
<div
|
|
2449
|
-
{
|
|
2450
|
-
|
|
2478
|
+
<div className="pending-upload-title">{title}</div>
|
|
2479
|
+
<div
|
|
2480
|
+
className={
|
|
2481
|
+
storageSetupFailure
|
|
2482
|
+
? "pending-upload-sub pending-upload-sub-wrap"
|
|
2483
|
+
: "pending-upload-sub"
|
|
2484
|
+
}
|
|
2485
|
+
>
|
|
2486
|
+
{nativeCorrupt
|
|
2487
|
+
? `${details.join(" · ")} · discard and record again`
|
|
2488
|
+
: storageSetupFailure
|
|
2489
|
+
? `${details.join(" · ")} · your clip is safe locally`
|
|
2490
|
+
: `${details.join(" · ")}${errorText ? ` · ${errorText}` : ""}`}
|
|
2451
2491
|
</div>
|
|
2492
|
+
{storageSetupFailure ? (
|
|
2493
|
+
<Tooltip>
|
|
2494
|
+
<TooltipTrigger asChild>
|
|
2495
|
+
<button type="button" className="pending-upload-why">
|
|
2496
|
+
Why am I seeing this?
|
|
2497
|
+
</button>
|
|
2498
|
+
</TooltipTrigger>
|
|
2499
|
+
<TooltipContent
|
|
2500
|
+
side="bottom"
|
|
2501
|
+
align="start"
|
|
2502
|
+
className="tooltip-content-wide"
|
|
2503
|
+
>
|
|
2504
|
+
{STORAGE_SETUP_HELP_TEXT}
|
|
2505
|
+
</TooltipContent>
|
|
2506
|
+
</Tooltip>
|
|
2507
|
+
) : null}
|
|
2452
2508
|
</div>
|
|
2453
2509
|
<div className="pending-upload-actions">
|
|
2454
2510
|
{canOpenFolder ? (
|
|
@@ -2488,6 +2544,17 @@ function PendingUploadBanner({
|
|
|
2488
2544
|
</button>
|
|
2489
2545
|
) : (
|
|
2490
2546
|
<>
|
|
2547
|
+
{storageSetupFailure ? (
|
|
2548
|
+
<button
|
|
2549
|
+
type="button"
|
|
2550
|
+
className="pending-upload-connect"
|
|
2551
|
+
disabled={actionsDisabled}
|
|
2552
|
+
onClick={() => onConnectStorage(latest)}
|
|
2553
|
+
>
|
|
2554
|
+
<IconExternalLink size={14} stroke={2} />
|
|
2555
|
+
Connect
|
|
2556
|
+
</button>
|
|
2557
|
+
) : null}
|
|
2491
2558
|
<button
|
|
2492
2559
|
type="button"
|
|
2493
2560
|
className="pending-upload-retry"
|
|
@@ -3705,7 +3772,7 @@ function Setup({
|
|
|
3705
3772
|
className="secondary"
|
|
3706
3773
|
onClick={connectBuilder}
|
|
3707
3774
|
>
|
|
3708
|
-
|
|
3775
|
+
Use Builder.io (free)
|
|
3709
3776
|
</button>
|
|
3710
3777
|
) : null}
|
|
3711
3778
|
</div>
|
|
@@ -1448,6 +1448,10 @@ body[data-clips-route="recording-pill"] #root {
|
|
|
1448
1448
|
transform-origin: var(--radix-tooltip-content-transform-origin);
|
|
1449
1449
|
}
|
|
1450
1450
|
|
|
1451
|
+
.tooltip-content-wide {
|
|
1452
|
+
max-width: 320px;
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1451
1455
|
.tooltip-content[data-state="delayed-open"],
|
|
1452
1456
|
.tooltip-content[data-state="instant-open"] {
|
|
1453
1457
|
animation: tooltip-in 120ms ease-out;
|
|
@@ -1720,12 +1724,38 @@ body[data-clips-route="recording-pill"] #root {
|
|
|
1720
1724
|
white-space: nowrap;
|
|
1721
1725
|
}
|
|
1722
1726
|
|
|
1727
|
+
.pending-upload-sub-wrap {
|
|
1728
|
+
overflow: visible;
|
|
1729
|
+
white-space: normal;
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
.pending-upload-why {
|
|
1733
|
+
margin-top: 3px;
|
|
1734
|
+
padding: 0;
|
|
1735
|
+
border: 0;
|
|
1736
|
+
background: transparent;
|
|
1737
|
+
color: #64748b;
|
|
1738
|
+
cursor: help;
|
|
1739
|
+
font-size: 11px;
|
|
1740
|
+
line-height: 1.2;
|
|
1741
|
+
text-decoration: underline;
|
|
1742
|
+
text-decoration-style: dotted;
|
|
1743
|
+
text-underline-offset: 3px;
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
.pending-upload-why:hover,
|
|
1747
|
+
.pending-upload-why:focus-visible {
|
|
1748
|
+
color: #334155;
|
|
1749
|
+
outline: none;
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1723
1752
|
.pending-upload-actions {
|
|
1724
1753
|
display: inline-flex;
|
|
1725
1754
|
align-items: center;
|
|
1726
1755
|
gap: 6px;
|
|
1727
1756
|
}
|
|
1728
1757
|
|
|
1758
|
+
.pending-upload-connect,
|
|
1729
1759
|
.pending-upload-retry {
|
|
1730
1760
|
height: 28px;
|
|
1731
1761
|
border: 1px solid #bfdbfe;
|
|
@@ -1746,6 +1776,10 @@ body[data-clips-route="recording-pill"] #root {
|
|
|
1746
1776
|
opacity 120ms;
|
|
1747
1777
|
}
|
|
1748
1778
|
|
|
1779
|
+
.pending-upload-connect {
|
|
1780
|
+
padding: 0 8px;
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1749
1783
|
.pending-upload-discard {
|
|
1750
1784
|
width: 28px;
|
|
1751
1785
|
height: 28px;
|
|
@@ -1789,6 +1823,11 @@ body[data-clips-route="recording-pill"] #root {
|
|
|
1789
1823
|
border-color: #93c5fd;
|
|
1790
1824
|
}
|
|
1791
1825
|
|
|
1826
|
+
.pending-upload-connect:hover {
|
|
1827
|
+
background: #dbeafe;
|
|
1828
|
+
border-color: #93c5fd;
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1792
1831
|
.pending-upload-folder:hover {
|
|
1793
1832
|
background: #dbeafe;
|
|
1794
1833
|
border-color: #93c5fd;
|
|
@@ -1801,6 +1840,7 @@ body[data-clips-route="recording-pill"] #root {
|
|
|
1801
1840
|
}
|
|
1802
1841
|
|
|
1803
1842
|
.pending-upload-retry:disabled,
|
|
1843
|
+
.pending-upload-connect:disabled,
|
|
1804
1844
|
.pending-upload-folder:disabled,
|
|
1805
1845
|
.pending-upload-discard:disabled {
|
|
1806
1846
|
cursor: default;
|
|
@@ -1972,6 +2012,16 @@ body[data-clips-route="recording-pill"] #root {
|
|
|
1972
2012
|
color: #93c5fd;
|
|
1973
2013
|
}
|
|
1974
2014
|
|
|
2015
|
+
.pending-upload-why {
|
|
2016
|
+
color: #94a3b8;
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
.pending-upload-why:hover,
|
|
2020
|
+
.pending-upload-why:focus-visible {
|
|
2021
|
+
color: #cbd5e1;
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
.pending-upload-connect,
|
|
1975
2025
|
.pending-upload-retry {
|
|
1976
2026
|
background: rgba(15, 23, 42, 0.7);
|
|
1977
2027
|
border-color: rgba(96, 165, 250, 0.38);
|
|
@@ -1995,6 +2045,11 @@ body[data-clips-route="recording-pill"] #root {
|
|
|
1995
2045
|
border-color: rgba(147, 197, 253, 0.62);
|
|
1996
2046
|
}
|
|
1997
2047
|
|
|
2048
|
+
.pending-upload-connect:hover {
|
|
2049
|
+
background: rgba(30, 64, 175, 0.34);
|
|
2050
|
+
border-color: rgba(147, 197, 253, 0.62);
|
|
2051
|
+
}
|
|
2052
|
+
|
|
1998
2053
|
.pending-upload-folder:hover {
|
|
1999
2054
|
background: rgba(30, 64, 175, 0.34);
|
|
2000
2055
|
border-color: rgba(147, 197, 253, 0.62);
|
|
@@ -179,7 +179,7 @@ enum NativeFullscreenBackend {
|
|
|
179
179
|
}
|
|
180
180
|
|
|
181
181
|
/// `SCRecordingOutput` finalizes the MP4 *asynchronously*: after
|
|
182
|
-
/// `
|
|
182
|
+
/// `remove_recording_output()` / `stop_capture()` it still has to flush its
|
|
183
183
|
/// last buffered sample fragment and write the `moov` atom, then it calls
|
|
184
184
|
/// `recording_did_finish` (or `recording_did_fail`). If we move the file
|
|
185
185
|
/// before that callback we lose the trailing fragment — a consistent
|
|
@@ -770,7 +770,8 @@ pub async fn native_fullscreen_recording_stop_and_upload(
|
|
|
770
770
|
has_audio,
|
|
771
771
|
has_camera,
|
|
772
772
|
)?;
|
|
773
|
-
|
|
773
|
+
let stop_error = stop_outcome.err();
|
|
774
|
+
if let Some(stop_err) = &stop_error {
|
|
774
775
|
saved.last_error = Some(stop_err.clone());
|
|
775
776
|
// Only mark corrupt when the SCK delegate explicitly called recording_did_fail
|
|
776
777
|
// (error contains "finalize failed"). Transient stop_capture /
|
|
@@ -779,11 +780,37 @@ pub async fn native_fullscreen_recording_stop_and_upload(
|
|
|
779
780
|
// "finalization callback failed" is unique to the delegate path;
|
|
780
781
|
// "recording finalize failed" also appears on remove_recording_output errors.
|
|
781
782
|
let is_definitive = stop_err.contains("finalization callback failed");
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
783
|
+
match mp4_has_moov(&saved.file_path) {
|
|
784
|
+
Some(false) => {
|
|
785
|
+
if is_definitive {
|
|
786
|
+
saved.corrupt = true;
|
|
787
|
+
eprintln!(
|
|
788
|
+
"[clips-tray] recording marked corrupt: definitive finalize error + missing moov atom"
|
|
789
|
+
);
|
|
790
|
+
} else {
|
|
791
|
+
eprintln!(
|
|
792
|
+
"[clips-tray] native stop reported an error and MP4 is missing moov; saving for retry"
|
|
793
|
+
);
|
|
794
|
+
}
|
|
795
|
+
write_saved_recording_metadata(&app, &saved)?;
|
|
796
|
+
emit_native_upload_progress(&app, "failed", "Upload paused", None, None);
|
|
797
|
+
let suffix = if saved.corrupt {
|
|
798
|
+
"The local file is incomplete and cannot be recovered. Discard it from the Clips menu and record again."
|
|
799
|
+
} else {
|
|
800
|
+
"The clip was saved locally and can be retried from the Clips menu."
|
|
801
|
+
};
|
|
802
|
+
return Err(format!("{stop_err}. {suffix}"));
|
|
803
|
+
}
|
|
804
|
+
Some(true) => {
|
|
805
|
+
eprintln!(
|
|
806
|
+
"[clips-tray] native stop reported an error but MP4 metadata is present; continuing upload: {stop_err}"
|
|
807
|
+
);
|
|
808
|
+
}
|
|
809
|
+
None => {
|
|
810
|
+
eprintln!(
|
|
811
|
+
"[clips-tray] native stop reported an error and MP4 metadata could not be verified; continuing upload: {stop_err}"
|
|
812
|
+
);
|
|
813
|
+
}
|
|
787
814
|
}
|
|
788
815
|
} else if mp4_has_moov(&saved.file_path) == Some(false) {
|
|
789
816
|
// stop_outcome was Ok but the finalize callback timed out — SCK may still be
|
|
@@ -791,26 +818,19 @@ pub async fn native_fullscreen_recording_stop_and_upload(
|
|
|
791
818
|
// in the UI, then bail out before upload_recording_file re-checks moov and
|
|
792
819
|
// permanently marks it corrupt.
|
|
793
820
|
saved.last_error = Some(
|
|
794
|
-
"Recorded MP4 is missing playback metadata. Please retry the recording."
|
|
795
|
-
.to_string(),
|
|
821
|
+
"Recorded MP4 is missing playback metadata. Please retry the recording.".to_string(),
|
|
796
822
|
);
|
|
797
823
|
eprintln!("[clips-tray] recording missing moov after Ok stop outcome (likely finalize timeout) — saving as retryable, skipping upload");
|
|
798
824
|
write_saved_recording_metadata(&app, &saved)?;
|
|
799
825
|
emit_native_upload_progress(&app, "failed", "Upload paused", None, None);
|
|
800
826
|
return Err(
|
|
801
|
-
"Recorded MP4 is missing playback metadata. Please retry the recording."
|
|
802
|
-
.to_string(),
|
|
827
|
+
"Recorded MP4 is missing playback metadata. Please retry the recording.".to_string(),
|
|
803
828
|
);
|
|
804
829
|
} else if let Err(merge_err) = &consolidate_outcome {
|
|
805
830
|
saved.last_error = Some(merge_err.clone());
|
|
806
831
|
}
|
|
807
832
|
write_saved_recording_metadata(&app, &saved)?;
|
|
808
833
|
emit_native_upload_progress(&app, "preparing", "Optimizing clip", None, None);
|
|
809
|
-
if let Err(stop_err) = stop_outcome {
|
|
810
|
-
return Err(format!(
|
|
811
|
-
"{stop_err}. The clip was saved locally and can be retried from the Clips menu."
|
|
812
|
-
));
|
|
813
|
-
}
|
|
814
834
|
if multi_segment {
|
|
815
835
|
if let Err(merge_err) = consolidate_outcome {
|
|
816
836
|
return Err(format!(
|
|
@@ -2338,14 +2358,10 @@ fn stop_native_recording(
|
|
|
2338
2358
|
finish,
|
|
2339
2359
|
..
|
|
2340
2360
|
} => {
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
//
|
|
2345
|
-
// StreamError("Failed due to an invalid parameter") when the audio
|
|
2346
|
-
// tap or stream state hasn't fully drained yet. We retry once after
|
|
2347
|
-
// a 200ms drain delay; the sleep is in the error branch only so
|
|
2348
|
-
// healthy clips pay no extra stop latency.
|
|
2361
|
+
// Removing the recording output is the clean "stop recording" path.
|
|
2362
|
+
// It lets SCRecordingOutput finish while the stream is still alive;
|
|
2363
|
+
// stopping the stream first can make ReplayKit report -5814 while
|
|
2364
|
+
// writing the MP4's final metadata.
|
|
2349
2365
|
let remove_result = stream
|
|
2350
2366
|
.remove_recording_output(recording)
|
|
2351
2367
|
.map_err(|e| format!("ScreenCaptureKit recording finalize failed: {e:?}"));
|
|
@@ -2361,28 +2377,42 @@ fn stop_native_recording(
|
|
|
2361
2377
|
})
|
|
2362
2378
|
}
|
|
2363
2379
|
};
|
|
2364
|
-
//
|
|
2380
|
+
// remove_recording_output()/stop_capture() only *trigger* the
|
|
2365
2381
|
// async finalize; the MP4 isn't complete until the delegate's
|
|
2366
|
-
// recording_did_finish fires.
|
|
2367
|
-
// the
|
|
2368
|
-
//
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
let
|
|
2372
|
-
|
|
2373
|
-
let finalize_outcome = if waited_for_finalize {
|
|
2382
|
+
// recording_did_finish fires. Prefer waiting after removal while
|
|
2383
|
+
// the stream is alive, then stop the stream after the output has
|
|
2384
|
+
// had a chance to flush.
|
|
2385
|
+
let remove_ok = remove_result.is_ok();
|
|
2386
|
+
let mut waited_for_finalize = false;
|
|
2387
|
+
let mut finalize_outcome = if wait_for_finalize && remove_ok {
|
|
2388
|
+
waited_for_finalize = true;
|
|
2374
2389
|
let outcome = finish.wait(SCK_FINALIZE_TIMEOUT);
|
|
2375
2390
|
if outcome.is_none() {
|
|
2376
2391
|
eprintln!(
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2392
|
+
"[clips-tray] SCRecordingOutput finalize callback did not fire within {}s after remove_recording_output; saving file as-is",
|
|
2393
|
+
SCK_FINALIZE_TIMEOUT.as_secs()
|
|
2394
|
+
);
|
|
2380
2395
|
}
|
|
2381
2396
|
outcome
|
|
2382
2397
|
} else {
|
|
2383
2398
|
None
|
|
2384
2399
|
};
|
|
2385
2400
|
|
|
2401
|
+
let stop_result = stream
|
|
2402
|
+
.stop_capture()
|
|
2403
|
+
.map_err(|e| format!("ScreenCaptureKit stop failed: {e:?}"));
|
|
2404
|
+
|
|
2405
|
+
if wait_for_finalize && !waited_for_finalize && stop_result.is_ok() {
|
|
2406
|
+
waited_for_finalize = true;
|
|
2407
|
+
finalize_outcome = finish.wait(SCK_FINALIZE_TIMEOUT);
|
|
2408
|
+
if finalize_outcome.is_none() {
|
|
2409
|
+
eprintln!(
|
|
2410
|
+
"[clips-tray] SCRecordingOutput finalize callback did not fire within {}s after stop_capture; saving file as-is",
|
|
2411
|
+
SCK_FINALIZE_TIMEOUT.as_secs()
|
|
2412
|
+
);
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
|
|
2386
2416
|
// Check the delegate outcome BEFORE returning on stop_result. When
|
|
2387
2417
|
// stop_capture() fails AND recording_did_fail fires, callers must see
|
|
2388
2418
|
// the "finalization callback failed" prefix to correctly identify
|
|
@@ -2391,12 +2421,11 @@ fn stop_native_recording(
|
|
|
2391
2421
|
eprintln!("[clips-tray] SCK finalize failed: {err}");
|
|
2392
2422
|
// Use a unique prefix so callers can distinguish the SCK delegate
|
|
2393
2423
|
// reporting failure (recording_did_fail) from teardown API errors.
|
|
2394
|
-
return Err(format!(
|
|
2424
|
+
return Err(format!(
|
|
2425
|
+
"ScreenCaptureKit finalization callback failed: {err}"
|
|
2426
|
+
));
|
|
2395
2427
|
}
|
|
2396
2428
|
|
|
2397
|
-
if let Err(err) = stop_result {
|
|
2398
|
-
return Err(err);
|
|
2399
|
-
}
|
|
2400
2429
|
if waited_for_finalize && finalize_outcome.is_none() {
|
|
2401
2430
|
eprintln!(
|
|
2402
2431
|
"[clips-tray] SCK finalize timed out after {}s — moov atom may be missing",
|
|
@@ -2404,21 +2433,32 @@ fn stop_native_recording(
|
|
|
2404
2433
|
);
|
|
2405
2434
|
}
|
|
2406
2435
|
|
|
2436
|
+
let remove_recoverable = matches!(finalize_outcome.as_ref(), Some(Ok(())))
|
|
2437
|
+
|| (waited_for_finalize && finalize_outcome.is_none());
|
|
2407
2438
|
match remove_result {
|
|
2408
|
-
Ok(()) =>
|
|
2439
|
+
Ok(()) => {}
|
|
2409
2440
|
Err(remove_err) => {
|
|
2410
|
-
if
|
|
2411
|
-
|| (waited_for_finalize && finalize_outcome.is_none())
|
|
2412
|
-
{
|
|
2441
|
+
if remove_recoverable {
|
|
2413
2442
|
eprintln!(
|
|
2414
2443
|
"[clips-tray] ScreenCaptureKit recording output removal reported an error after finalize completed or timed out; continuing upload: {remove_err}"
|
|
2415
2444
|
);
|
|
2416
|
-
Ok(())
|
|
2417
2445
|
} else {
|
|
2418
|
-
Err(remove_err)
|
|
2446
|
+
return Err(remove_err);
|
|
2419
2447
|
}
|
|
2420
2448
|
}
|
|
2421
2449
|
}
|
|
2450
|
+
|
|
2451
|
+
if let Err(stop_err) = stop_result {
|
|
2452
|
+
if remove_ok || matches!(finalize_outcome.as_ref(), Some(Ok(()))) {
|
|
2453
|
+
eprintln!(
|
|
2454
|
+
"[clips-tray] ScreenCaptureKit stop_capture reported an error after recording output teardown; continuing upload: {stop_err}"
|
|
2455
|
+
);
|
|
2456
|
+
} else {
|
|
2457
|
+
return Err(stop_err);
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
|
|
2461
|
+
Ok(())
|
|
2422
2462
|
}
|
|
2423
2463
|
}
|
|
2424
2464
|
}
|
|
@@ -2827,8 +2867,7 @@ fn is_moov_corrupt_error(err: &str) -> bool {
|
|
|
2827
2867
|
// Matches both the native prepare_recording_file error and the server-side
|
|
2828
2868
|
// finalize-recording.ts error so the corrupt flag is set regardless of
|
|
2829
2869
|
// which layer first detected the missing moov atom.
|
|
2830
|
-
err.contains("video is missing required metadata")
|
|
2831
|
-
|| err.contains("corrupted or incomplete")
|
|
2870
|
+
err.contains("video is missing required metadata") || err.contains("corrupted or incomplete")
|
|
2832
2871
|
}
|
|
2833
2872
|
|
|
2834
2873
|
/// Walk the top-level ISO BMFF boxes of a file and return `Some(true)` when a
|
|
@@ -2925,9 +2964,7 @@ fn prepare_recording_file(
|
|
|
2925
2964
|
// full chunked upload that the server will reject anyway.
|
|
2926
2965
|
if mime_type == "video/mp4" || mime_type == "video/quicktime" {
|
|
2927
2966
|
if mp4_has_moov(path) == Some(false) {
|
|
2928
|
-
eprintln!(
|
|
2929
|
-
"[clips-tray] native recording corrupt moov check failed — skipping upload"
|
|
2930
|
-
);
|
|
2967
|
+
eprintln!("[clips-tray] native recording corrupt moov check failed — skipping upload");
|
|
2931
2968
|
return Err(
|
|
2932
2969
|
"Recorded file is corrupted or incomplete — the video is missing required \
|
|
2933
2970
|
metadata. Please record again."
|
|
@@ -748,7 +748,10 @@ pub(crate) mod macos {
|
|
|
748
748
|
if !is_cancelled && !transient {
|
|
749
749
|
let _ = app.emit(
|
|
750
750
|
"voice:speech-error",
|
|
751
|
-
ErrorPayload {
|
|
751
|
+
ErrorPayload {
|
|
752
|
+
error: msg,
|
|
753
|
+
source: "mic",
|
|
754
|
+
},
|
|
752
755
|
);
|
|
753
756
|
}
|
|
754
757
|
clear_session_slot();
|
|
@@ -766,11 +769,17 @@ pub(crate) mod macos {
|
|
|
766
769
|
return;
|
|
767
770
|
}
|
|
768
771
|
if let Err(e) = native_speech_start_impl(
|
|
769
|
-
app.clone(),
|
|
772
|
+
app.clone(),
|
|
773
|
+
locale,
|
|
774
|
+
mic_device_id,
|
|
775
|
+
mic_device_label,
|
|
770
776
|
) {
|
|
771
777
|
let _ = app.emit(
|
|
772
778
|
"voice:speech-error",
|
|
773
|
-
ErrorPayload {
|
|
779
|
+
ErrorPayload {
|
|
780
|
+
error: e,
|
|
781
|
+
source: "mic",
|
|
782
|
+
},
|
|
774
783
|
);
|
|
775
784
|
}
|
|
776
785
|
});
|
|
@@ -172,13 +172,16 @@ export function extractShareLink(urlValue: string): {
|
|
|
172
172
|
|
|
173
173
|
const parts = url.pathname.split("/").filter(Boolean);
|
|
174
174
|
const shareIndex = parts.indexOf("share");
|
|
175
|
-
const
|
|
175
|
+
const recordingIndex = parts.indexOf("r");
|
|
176
|
+
const resourceIndex = shareIndex >= 0 ? shareIndex : recordingIndex;
|
|
177
|
+
const id = resourceIndex >= 0 ? parts[resourceIndex + 1] : undefined;
|
|
176
178
|
if (!id) return null;
|
|
177
179
|
|
|
178
180
|
return {
|
|
179
181
|
id: decodeURIComponent(id),
|
|
180
182
|
origin: url.origin,
|
|
181
|
-
basePath:
|
|
183
|
+
basePath:
|
|
184
|
+
resourceIndex > 0 ? `/${parts.slice(0, resourceIndex).join("/")}` : "",
|
|
182
185
|
};
|
|
183
186
|
}
|
|
184
187
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-manager.d.ts","sourceRoot":"","sources":["../../src/agent/run-manager.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"run-manager.d.ts","sourceRoot":"","sources":["../../src/agent/run-manager.ts"],"names":[],"mappings":"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"}
|