@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.
Files changed (84) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +13 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/agent/run-manager.ts +9 -1
  5. package/corpus/core/src/client/agent-chat-adapter.ts +24 -16
  6. package/corpus/core/src/client/session-replay.ts +67 -13
  7. package/corpus/core/src/onboarding/default-steps.ts +3 -3
  8. package/corpus/core/src/server/agent-chat-plugin.ts +82 -162
  9. package/corpus/core/src/server/agent-run-context.ts +204 -0
  10. package/corpus/core/src/server/credential-provider.ts +20 -4
  11. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +14 -4
  12. package/corpus/templates/analytics/changelog/2026-06-26-session-replay-uploads-now-accept-compressed-payloads-so-lar.md +6 -0
  13. package/corpus/templates/analytics/changelog/2026-06-26-sql-chart-tooltips-now-stay-stable-while-moving-across-dense.md +6 -0
  14. package/corpus/templates/analytics/server/handlers/session-replay.ts +56 -8
  15. package/corpus/templates/clips/app/components/player/transcript-panel.tsx +1 -1
  16. package/corpus/templates/clips/app/components/recorder/storage-setup-card.tsx +44 -16
  17. package/corpus/templates/clips/app/i18n/ar-SA.ts +12 -9
  18. package/corpus/templates/clips/app/i18n/de-DE.ts +12 -9
  19. package/corpus/templates/clips/app/i18n/en-US.ts +19 -16
  20. package/corpus/templates/clips/app/i18n/es-ES.ts +12 -9
  21. package/corpus/templates/clips/app/i18n/fr-FR.ts +12 -9
  22. package/corpus/templates/clips/app/i18n/hi-IN.ts +13 -9
  23. package/corpus/templates/clips/app/i18n/ja-JP.ts +12 -9
  24. package/corpus/templates/clips/app/i18n/ko-KR.ts +12 -9
  25. package/corpus/templates/clips/app/i18n/pt-BR.ts +12 -9
  26. package/corpus/templates/clips/app/i18n/zh-CN.ts +19 -14
  27. package/corpus/templates/clips/app/i18n/zh-TW.ts +19 -14
  28. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +7 -0
  29. package/corpus/templates/clips/app/routes/record.tsx +2 -2
  30. package/corpus/templates/clips/changelog/2026-06-26-desktop-recordings-recover-more-reliably-when-macos-reports-.md +6 -0
  31. package/corpus/templates/clips/changelog/2026-06-26-slack-unfurls-now-recognize-recording-dashboard-links-pasted.md +6 -0
  32. package/corpus/templates/clips/changelog/2026-06-26-storage-setup-now-explains-why-clips-needs-connected-storage.md +6 -0
  33. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
  34. package/corpus/templates/clips/chrome-extension/src/background.ts +24 -3
  35. package/corpus/templates/clips/chrome-extension/src/offscreen.ts +28 -2
  36. package/corpus/templates/clips/chrome-extension/src/popup.html +16 -0
  37. package/corpus/templates/clips/chrome-extension/src/popup.ts +59 -7
  38. package/corpus/templates/clips/chrome-extension/src/sentry.ts +28 -1
  39. package/corpus/templates/clips/chrome-extension/src/styles.css +74 -0
  40. package/corpus/templates/clips/desktop/src/app.tsx +85 -18
  41. package/corpus/templates/clips/desktop/src/styles.css +55 -0
  42. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +88 -51
  43. package/corpus/templates/clips/desktop/src-tauri/src/native_speech.rs +12 -3
  44. package/corpus/templates/clips/server/lib/slack-unfurls.ts +5 -2
  45. package/dist/agent/run-manager.d.ts.map +1 -1
  46. package/dist/agent/run-manager.js +12 -1
  47. package/dist/agent/run-manager.js.map +1 -1
  48. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  49. package/dist/client/agent-chat-adapter.js +15 -8
  50. package/dist/client/agent-chat-adapter.js.map +1 -1
  51. package/dist/client/session-replay.d.ts.map +1 -1
  52. package/dist/client/session-replay.js +55 -14
  53. package/dist/client/session-replay.js.map +1 -1
  54. package/dist/collab/awareness.d.ts +2 -2
  55. package/dist/collab/awareness.d.ts.map +1 -1
  56. package/dist/notifications/routes.d.ts +3 -3
  57. package/dist/observability/routes.d.ts +6 -6
  58. package/dist/onboarding/default-steps.js +3 -3
  59. package/dist/onboarding/default-steps.js.map +1 -1
  60. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  61. package/dist/server/agent-chat-plugin.js +69 -154
  62. package/dist/server/agent-chat-plugin.js.map +1 -1
  63. package/dist/server/agent-engine-api-key-route.d.ts +2 -2
  64. package/dist/server/agent-run-context.d.ts +35 -0
  65. package/dist/server/agent-run-context.d.ts.map +1 -0
  66. package/dist/server/agent-run-context.js +137 -0
  67. package/dist/server/agent-run-context.js.map +1 -0
  68. package/dist/server/credential-provider.d.ts.map +1 -1
  69. package/dist/server/credential-provider.js +15 -4
  70. package/dist/server/credential-provider.js.map +1 -1
  71. package/package.json +1 -1
  72. package/corpus/templates/.retired/README.md +0 -9
  73. package/corpus/templates/.retired/calls/package.json +0 -8
  74. package/corpus/templates/.retired/code/package.json +0 -8
  75. package/corpus/templates/.retired/contracts/package.json +0 -8
  76. package/corpus/templates/.retired/images/package.json +0 -8
  77. package/corpus/templates/.retired/issues/package.json +0 -8
  78. package/corpus/templates/.retired/meeting-notes/package.json +0 -8
  79. package/corpus/templates/.retired/migration/package.json +0 -8
  80. package/corpus/templates/.retired/recruiting/package.json +0 -8
  81. package/corpus/templates/.retired/scheduling/package.json +0 -8
  82. package/corpus/templates/.retired/visual-plans/package.json +0 -8
  83. package/corpus/templates/.retired/voice/package.json +0 -8
  84. 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(message);
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
- const base = serverUrl.replace(/\/+$/, "");
1641
- setRecError(
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
- err instanceof Error
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
- void refreshVideoStorageStatus();
1652
- }, [refreshVideoStorageStatus, serverUrl]);
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">{savedLabel}</div>
2448
- <div className="pending-upload-sub">
2449
- {details.join(" · ")}
2450
- {errorText ? ` · ${errorText}` : ""}
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
- Connect Builder.io
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
- /// `stop_capture()` / `remove_recording_output()` it still has to flush its
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
- if let Err(stop_err) = &stop_outcome {
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
- if is_definitive && mp4_has_moov(&saved.file_path) == Some(false) {
783
- saved.corrupt = true;
784
- eprintln!(
785
- "[clips-tray] recording marked corrupt: definitive finalize error + missing moov atom"
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
- let stop_result = stream
2342
- .stop_capture()
2343
- .map_err(|e| format!("ScreenCaptureKit stop failed: {e:?}"));
2344
- // remove_recording_output() occasionally fails with
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
- // stop_capture()/remove_recording_output() only *trigger* the
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. Block on it before the caller moves
2367
- // the file without this we move it mid-flush and lose the last
2368
- // buffered fragment (a consistent multi-second tail truncation).
2369
- // Skip the wait only when both teardown calls hard-failed (the
2370
- // delegate won't fire, so don't burn the timeout for nothing).
2371
- let waited_for_finalize =
2372
- wait_for_finalize && (stop_result.is_ok() || remove_result.is_ok());
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
- "[clips-tray] SCRecordingOutput finalize callback did not fire within {}s; saving file as-is",
2378
- SCK_FINALIZE_TIMEOUT.as_secs()
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!("ScreenCaptureKit finalization callback failed: {err}"));
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(()) => Ok(()),
2439
+ Ok(()) => {}
2409
2440
  Err(remove_err) => {
2410
- if matches!(finalize_outcome.as_ref(), Some(Ok(())))
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 { error: msg, source: "mic" },
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(), locale, mic_device_id, mic_device_label,
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 { error: e, source: "mic" },
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 id = shareIndex >= 0 ? parts[shareIndex + 1] : undefined;
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: shareIndex > 0 ? `/${parts.slice(0, shareIndex).join("/")}` : "",
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":"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"}
1
+ {"version":3,"file":"run-manager.d.ts","sourceRoot":"","sources":["../../src/agent/run-manager.ts"],"names":[],"mappings":"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"}