@agent-native/core 0.114.5 → 0.114.7

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 (210) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +14 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/cli/mcp-config-writers.ts +6 -4
  5. package/corpus/core/src/cli/mcp.ts +124 -0
  6. package/corpus/core/src/cli/skills-content/help.ts +4 -3
  7. package/corpus/core/src/cli/skills-content/index.ts +1 -0
  8. package/corpus/core/src/cli/skills-content/rewind-skill.ts +52 -0
  9. package/corpus/core/src/cli/skills.ts +115 -13
  10. package/corpus/core/src/client/resources/ResourceTree.tsx +11 -5
  11. package/corpus/core/src/client/resources/ResourcesPanel.tsx +141 -18
  12. package/corpus/core/src/mcp/screen-memory-stdio.ts +1360 -104
  13. package/corpus/core/src/mcp-client/index.ts +9 -0
  14. package/corpus/core/src/mcp-client/screen-memory-local.ts +847 -13
  15. package/corpus/templates/clips/actions/apply-rewind-extension.ts +195 -0
  16. package/corpus/templates/clips/actions/delete-agent-recording-if-unpromoted.ts +64 -0
  17. package/corpus/templates/clips/actions/get-rewind-extension-request.ts +22 -0
  18. package/corpus/templates/clips/actions/get-screen-memory-status.ts +18 -2
  19. package/corpus/templates/clips/actions/list-rewind-extension-requests.ts +52 -0
  20. package/corpus/templates/clips/actions/make-recording-private-for-rewind.ts +55 -0
  21. package/corpus/templates/clips/actions/query-screen-memory-context.ts +4 -4
  22. package/corpus/templates/clips/actions/request-rewind-extension.ts +81 -0
  23. package/corpus/templates/clips/actions/update-rewind-extension-request.ts +47 -0
  24. package/corpus/templates/clips/app/components/editor/editor-layout.tsx +25 -0
  25. package/corpus/templates/clips/app/components/editor/editor-toolbar.tsx +36 -0
  26. package/corpus/templates/clips/app/components/editor/rewind-extension-dialog.tsx +338 -0
  27. package/corpus/templates/clips/app/components/editor/stitch-manager.tsx +1 -16
  28. package/corpus/templates/clips/app/components/editor/timeline.tsx +26 -0
  29. package/corpus/templates/clips/app/components/meetings/meeting-card.tsx +1 -2
  30. package/corpus/templates/clips/app/i18n/en-US.ts +18 -0
  31. package/corpus/templates/clips/app/lib/ffmpeg-export.ts +19 -12
  32. package/corpus/templates/clips/app/lib/rewind-visibility.ts +31 -0
  33. package/corpus/templates/clips/app/lib/timestamp-mapping.ts +7 -0
  34. package/corpus/templates/clips/app/lib/upload-file-client.ts +17 -0
  35. package/corpus/templates/clips/changelog/2026-07-14-rewind-keeps-recent-screen-context-local-and-turns-it-into-clips.md +6 -0
  36. package/corpus/templates/clips/changelog/2026-07-17-active-clips-keep-rewind-stable-and-settings-clear.md +12 -0
  37. package/corpus/templates/clips/changelog/2026-07-17-clicking-the-clips-icon-during-a-recording-now-opens-the-app.md +6 -0
  38. package/corpus/templates/clips/changelog/2026-07-17-default-mic-now-uses-the-mac-default-and-transcription-shares-one-capture.md +6 -0
  39. package/corpus/templates/clips/changelog/2026-07-17-full-screen-clips-finalize-even-when-screencapturekit-stop-hangs.md +6 -0
  40. package/corpus/templates/clips/changelog/2026-07-17-normal-full-screen-clips-no-longer-stop-immediately-after-th.md +6 -0
  41. package/corpus/templates/clips/changelog/2026-07-17-rewind-can-hand-one-bounded-private-clip-to-your-agent.md +12 -0
  42. package/corpus/templates/clips/changelog/2026-07-17-rewind-home-can-copy-a-local-agent-prompt.md +6 -0
  43. package/corpus/templates/clips/changelog/2026-07-17-rewind-now-has-a-calm-home-status-focused-first-run-consent-.md +6 -0
  44. package/corpus/templates/clips/changelog/2026-07-17-rewind-settings-are-smaller-clearer-and-agent-first.md +6 -0
  45. package/corpus/templates/clips/changelog/2026-07-17-the-full-clips-interface-stays-available-while-recording.md +6 -0
  46. package/corpus/templates/clips/changelog/2026-07-18-rewind-organizes-recent-work-into-local-searchable-chapters.md +8 -0
  47. package/corpus/templates/clips/changelog/2026-07-18-searchable-rewind-chapters.md +6 -0
  48. package/corpus/templates/clips/changelog/2026-07-19-rewind-backed-recordings-now-start-immediately-after-the-cou.md +6 -0
  49. package/corpus/templates/clips/changelog/2026-07-20-opening-zoom-meeting-launches-native-app.md +6 -0
  50. package/corpus/templates/clips/changelog/2026-07-20-rewind-can-set-up-your-local-agent-once-so-future-requests-w.md +6 -0
  51. package/corpus/templates/clips/desktop/package.json +1 -0
  52. package/corpus/templates/clips/desktop/scripts/sign-macos-local.ts +12 -1
  53. package/corpus/templates/clips/desktop/src/app.tsx +2478 -223
  54. package/corpus/templates/clips/desktop/src/components/ReadinessPanel.tsx +21 -1
  55. package/corpus/templates/clips/desktop/src/components/Switch.tsx +3 -0
  56. package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +156 -24
  57. package/corpus/templates/clips/desktop/src/lib/native-recording-warm.ts +6 -7
  58. package/corpus/templates/clips/desktop/src/lib/open-meeting-join-url.ts +22 -0
  59. package/corpus/templates/clips/desktop/src/lib/recorder.ts +1763 -1160
  60. package/corpus/templates/clips/desktop/src/lib/rewind-agent-prompt.ts +9 -0
  61. package/corpus/templates/clips/desktop/src/lib/rewind-recording-start.ts +20 -0
  62. package/corpus/templates/clips/desktop/src/main.tsx +3 -0
  63. package/corpus/templates/clips/desktop/src/overlays/countdown.tsx +14 -14
  64. package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +2 -3
  65. package/corpus/templates/clips/desktop/src/overlays/onboarding.tsx +4 -4
  66. package/corpus/templates/clips/desktop/src/overlays/preparing.tsx +13 -0
  67. package/corpus/templates/clips/desktop/src/overlays/toolbar.tsx +27 -2
  68. package/corpus/templates/clips/desktop/src/shared/config.ts +16 -0
  69. package/corpus/templates/clips/desktop/src/styles.css +956 -134
  70. package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +5 -0
  71. package/corpus/templates/clips/desktop/src-tauri/build.rs +69 -1
  72. package/corpus/templates/clips/desktop/src-tauri/src/accessibility.rs +386 -12
  73. package/corpus/templates/clips/desktop/src-tauri/src/capture_audio_bus.rs +382 -0
  74. package/corpus/templates/clips/desktop/src-tauri/src/capture_graph.rs +777 -0
  75. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +249 -22
  76. package/corpus/templates/clips/desktop/src-tauri/src/config.rs +148 -1
  77. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +58 -4
  78. package/corpus/templates/clips/desktop/src-tauri/src/logfile.rs +13 -1
  79. package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +1505 -51
  80. package/corpus/templates/clips/desktop/src-tauri/src/native_screen/live_upload.rs +124 -0
  81. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +1006 -95
  82. package/corpus/templates/clips/desktop/src-tauri/src/rewind_capture_suspension.rs +165 -0
  83. package/corpus/templates/clips/desktop/src-tauri/src/rewind_chapters.rs +2434 -0
  84. package/corpus/templates/clips/desktop/src-tauri/src/rewind_clip.rs +1650 -0
  85. package/corpus/templates/clips/desktop/src-tauri/src/rewind_egress.rs +643 -0
  86. package/corpus/templates/clips/desktop/src-tauri/src/rewind_local_ask.rs +803 -0
  87. package/corpus/templates/clips/desktop/src-tauri/src/rewind_meeting_history.rs +595 -0
  88. package/corpus/templates/clips/desktop/src-tauri/src/screen_memory.rs +2900 -91
  89. package/corpus/templates/clips/desktop/src-tauri/src/screen_memory_ocr.rs +360 -0
  90. package/corpus/templates/clips/desktop/src-tauri/src/screen_memory_transcript.rs +408 -0
  91. package/corpus/templates/clips/desktop/src-tauri/src/shortcuts.rs +114 -33
  92. package/corpus/templates/clips/desktop/src-tauri/src/state.rs +2 -2
  93. package/corpus/templates/clips/desktop/src-tauri/src/system_audio.rs +13 -3
  94. package/corpus/templates/clips/desktop/src-tauri/src/tray.rs +4 -1
  95. package/corpus/templates/clips/desktop/src-tauri/src/util.rs +7 -13
  96. package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +198 -11
  97. package/corpus/templates/clips/desktop/src-tauri/tauri.alpha.conf.json +17 -0
  98. package/corpus/templates/clips/shared/meeting-join-url.ts +0 -5
  99. package/corpus/templates/content/.agents/skills/document-editing/SKILL.md +9 -1
  100. package/corpus/templates/content/AGENTS.md +4 -1
  101. package/corpus/templates/content/actions/_content-spaces.ts +6 -1
  102. package/corpus/templates/content/actions/_database-row-batch.ts +15 -2
  103. package/corpus/templates/content/actions/_database-utils.ts +83 -15
  104. package/corpus/templates/content/actions/add-database-item.ts +3 -0
  105. package/corpus/templates/content/actions/connect-local-folder-source.ts +2 -1
  106. package/corpus/templates/content/actions/delete-content-database.ts +12 -9
  107. package/corpus/templates/content/actions/delete-database-items.ts +3 -2
  108. package/corpus/templates/content/actions/delete-document.ts +233 -6
  109. package/corpus/templates/content/actions/duplicate-database-item.ts +1 -0
  110. package/corpus/templates/content/actions/get-document.ts +4 -1
  111. package/corpus/templates/content/actions/list-content-databases.ts +1 -0
  112. package/corpus/templates/content/actions/list-documents.ts +1 -0
  113. package/corpus/templates/content/actions/list-trashed-content-databases.ts +8 -2
  114. package/corpus/templates/content/actions/list-trashed-documents.ts +50 -0
  115. package/corpus/templates/content/actions/permanently-delete-document.ts +28 -0
  116. package/corpus/templates/content/actions/pull-document.ts +15 -2
  117. package/corpus/templates/content/actions/restore-content-database.ts +48 -11
  118. package/corpus/templates/content/actions/restore-document.ts +28 -0
  119. package/corpus/templates/content/actions/search-documents.ts +2 -1
  120. package/corpus/templates/content/actions/view-screen.ts +2 -1
  121. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +149 -55
  122. package/corpus/templates/content/app/components/editor/database/GalleryView.tsx +7 -2
  123. package/corpus/templates/content/app/components/editor/database/ListView.tsx +7 -2
  124. package/corpus/templates/content/app/components/editor/database/TimelineView.tsx +3 -0
  125. package/corpus/templates/content/app/components/editor/database/sidebar.tsx +26 -5
  126. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +243 -163
  127. package/corpus/templates/content/app/components/sidebar/WorkspaceSourceMenu.tsx +154 -0
  128. package/corpus/templates/content/app/components/sidebar/select-content-space.ts +11 -0
  129. package/corpus/templates/content/app/hooks/use-content-database.ts +6 -0
  130. package/corpus/templates/content/app/hooks/use-documents.ts +57 -0
  131. package/corpus/templates/content/app/i18n-data.ts +83 -2
  132. package/corpus/templates/content/app/routes/_app.local-files.tsx +8 -1
  133. package/corpus/templates/content/changelog/2026-07-19-pages-now-move-to-a-reversible-trash-and-the-organization-pi.md +6 -0
  134. package/corpus/templates/content/changelog/2026-07-20-trash-actions-now-preserve-independent-archived-pages-enforc.md +6 -0
  135. package/corpus/templates/content/changelog/2026-07-20-workspace-toggles-now-stay-independently-open-or-closed-and-.md +6 -0
  136. package/corpus/templates/content/changelog/2026-07-20-workspaces-can-be-added-from-a-blank-workspace-or-a-connecte.md +6 -0
  137. package/corpus/templates/content/parity/matrix.md +1 -1
  138. package/corpus/templates/content/parity/matrix.ts +3 -0
  139. package/corpus/templates/content/server/db/schema.ts +2 -0
  140. package/corpus/templates/content/server/lib/public-documents.ts +8 -2
  141. package/corpus/templates/content/server/plugins/db.ts +52 -0
  142. package/corpus/templates/content/server/routes/api/document-agent-context.json.get.ts +2 -2
  143. package/corpus/templates/content/shared/api.ts +10 -0
  144. package/corpus/toolkit/CHANGELOG.md +6 -0
  145. package/corpus/toolkit/package.json +1 -1
  146. package/corpus/toolkit/src/ui/progress.tsx +3 -2
  147. package/dist/cli/mcp-config-writers.d.ts +2 -2
  148. package/dist/cli/mcp-config-writers.d.ts.map +1 -1
  149. package/dist/cli/mcp-config-writers.js +6 -6
  150. package/dist/cli/mcp-config-writers.js.map +1 -1
  151. package/dist/cli/mcp.d.ts +14 -0
  152. package/dist/cli/mcp.d.ts.map +1 -1
  153. package/dist/cli/mcp.js +79 -0
  154. package/dist/cli/mcp.js.map +1 -1
  155. package/dist/cli/skills-content/help.d.ts +1 -1
  156. package/dist/cli/skills-content/help.d.ts.map +1 -1
  157. package/dist/cli/skills-content/help.js +4 -3
  158. package/dist/cli/skills-content/help.js.map +1 -1
  159. package/dist/cli/skills-content/index.d.ts +1 -0
  160. package/dist/cli/skills-content/index.d.ts.map +1 -1
  161. package/dist/cli/skills-content/index.js +1 -0
  162. package/dist/cli/skills-content/index.js.map +1 -1
  163. package/dist/cli/skills-content/rewind-skill.d.ts +2 -0
  164. package/dist/cli/skills-content/rewind-skill.d.ts.map +1 -0
  165. package/dist/cli/skills-content/rewind-skill.js +53 -0
  166. package/dist/cli/skills-content/rewind-skill.js.map +1 -0
  167. package/dist/cli/skills.d.ts +6 -0
  168. package/dist/cli/skills.d.ts.map +1 -1
  169. package/dist/cli/skills.js +90 -14
  170. package/dist/cli/skills.js.map +1 -1
  171. package/dist/client/resources/ResourceTree.d.ts +3 -1
  172. package/dist/client/resources/ResourceTree.d.ts.map +1 -1
  173. package/dist/client/resources/ResourceTree.js +4 -4
  174. package/dist/client/resources/ResourceTree.js.map +1 -1
  175. package/dist/client/resources/ResourcesPanel.d.ts.map +1 -1
  176. package/dist/client/resources/ResourcesPanel.js +55 -6
  177. package/dist/client/resources/ResourcesPanel.js.map +1 -1
  178. package/dist/collab/routes.d.ts +1 -1
  179. package/dist/collab/struct-routes.d.ts +1 -1
  180. package/dist/mcp/screen-memory-stdio.d.ts +365 -0
  181. package/dist/mcp/screen-memory-stdio.d.ts.map +1 -1
  182. package/dist/mcp/screen-memory-stdio.js +1014 -91
  183. package/dist/mcp/screen-memory-stdio.js.map +1 -1
  184. package/dist/mcp-client/index.d.ts +1 -1
  185. package/dist/mcp-client/index.d.ts.map +1 -1
  186. package/dist/mcp-client/index.js +1 -1
  187. package/dist/mcp-client/index.js.map +1 -1
  188. package/dist/mcp-client/screen-memory-local.d.ts +90 -0
  189. package/dist/mcp-client/screen-memory-local.d.ts.map +1 -1
  190. package/dist/mcp-client/screen-memory-local.js +590 -12
  191. package/dist/mcp-client/screen-memory-local.js.map +1 -1
  192. package/dist/notifications/routes.d.ts +2 -2
  193. package/dist/observability/routes.d.ts +5 -5
  194. package/dist/progress/routes.d.ts +1 -1
  195. package/dist/provider-api/actions/provider-api.d.ts +4 -4
  196. package/dist/resources/handlers.d.ts +1 -1
  197. package/dist/secrets/routes.d.ts +9 -9
  198. package/dist/server/transcribe-voice.d.ts +1 -1
  199. package/package.json +2 -2
  200. package/src/cli/mcp-config-writers.ts +6 -4
  201. package/src/cli/mcp.ts +124 -0
  202. package/src/cli/skills-content/help.ts +4 -3
  203. package/src/cli/skills-content/index.ts +1 -0
  204. package/src/cli/skills-content/rewind-skill.ts +52 -0
  205. package/src/cli/skills.ts +115 -13
  206. package/src/client/resources/ResourceTree.tsx +11 -5
  207. package/src/client/resources/ResourcesPanel.tsx +141 -18
  208. package/src/mcp/screen-memory-stdio.ts +1360 -104
  209. package/src/mcp-client/index.ts +9 -0
  210. package/src/mcp-client/screen-memory-local.ts +847 -13
@@ -16,6 +16,7 @@ import {
16
16
  IconDownload,
17
17
  IconLoader2,
18
18
  IconTrash,
19
+ IconHistory,
19
20
  } from "@tabler/icons-react";
20
21
  import { useState } from "react";
21
22
  import { toast } from "sonner";
@@ -84,6 +85,10 @@ export interface EditorToolbarProps {
84
85
  onOpenThumbnailPicker: () => void;
85
86
  onOpenChapters: () => void;
86
87
  onOpenStitch: () => void;
88
+ onOpenRewind: () => void;
89
+ rewindAlreadyAdded?: boolean;
90
+ rewindAvailable?: boolean;
91
+ rewindRequiresPrivate?: boolean;
87
92
  chaptersOpen?: boolean;
88
93
  }
89
94
 
@@ -103,6 +108,10 @@ export function EditorToolbar({
103
108
  onOpenThumbnailPicker,
104
109
  onOpenChapters,
105
110
  onOpenStitch,
111
+ onOpenRewind,
112
+ rewindAlreadyAdded,
113
+ rewindAvailable = true,
114
+ rewindRequiresPrivate = false,
106
115
  chaptersOpen,
107
116
  }: EditorToolbarProps) {
108
117
  const t = useT();
@@ -482,6 +491,19 @@ export function EditorToolbar({
482
491
  <IconPuzzle className="mr-2 h-4 w-4" />
483
492
  {t("editorToolbar.stitchClips")}
484
493
  </DropdownMenuItem>
494
+ {rewindAvailable ? (
495
+ <DropdownMenuItem
496
+ disabled={rewindAlreadyAdded}
497
+ onSelect={onOpenRewind}
498
+ >
499
+ <IconHistory className="mr-2 h-4 w-4" />
500
+ {rewindAlreadyAdded
501
+ ? "Rewind history added"
502
+ : rewindRequiresPrivate
503
+ ? "Make private and add Rewind history…"
504
+ : "Add what happened before…"}
505
+ </DropdownMenuItem>
506
+ ) : null}
485
507
  <DropdownMenuSeparator />
486
508
  <DropdownMenuItem onSelect={() => setClearOpen(true)}>
487
509
  <IconTrash className="mr-2 h-4 w-4" />
@@ -490,6 +512,20 @@ export function EditorToolbar({
490
512
  </DropdownMenuContent>
491
513
  </DropdownMenu>
492
514
 
515
+ {rewindAvailable && !rewindAlreadyAdded ? (
516
+ <Button
517
+ size="sm"
518
+ variant="ghost"
519
+ className="hidden shrink-0 gap-1.5 xl:inline-flex"
520
+ onClick={onOpenRewind}
521
+ >
522
+ <IconHistory className="h-4 w-4" />
523
+ {rewindRequiresPrivate
524
+ ? "Make private + add earlier"
525
+ : "Add earlier…"}
526
+ </Button>
527
+ ) : null}
528
+
493
529
  <div className="min-w-3 flex-1" />
494
530
 
495
531
  <Separator orientation="vertical" className="mx-1 h-6" />
@@ -0,0 +1,338 @@
1
+ import {
2
+ agentNativePath,
3
+ appBasePath,
4
+ } from "@agent-native/core/client/api-path";
5
+ import { useActionMutation } from "@agent-native/core/client/hooks";
6
+ import { useT } from "@agent-native/core/client/i18n";
7
+ import { IconHistory, IconLoader2 } from "@tabler/icons-react";
8
+ import { useCallback, useEffect, useRef, useState } from "react";
9
+ import { toast } from "sonner";
10
+
11
+ import { Button } from "@/components/ui/button";
12
+ import {
13
+ Dialog,
14
+ DialogContent,
15
+ DialogDescription,
16
+ DialogFooter,
17
+ DialogHeader,
18
+ DialogTitle,
19
+ } from "@/components/ui/dialog";
20
+ import { exportConcat } from "@/lib/ffmpeg-export";
21
+ import { uploadFileClient } from "@/lib/upload-file-client";
22
+
23
+ interface RewindExtensionRequest {
24
+ requestId: string;
25
+ recordingId: string;
26
+ seconds: 30 | 300;
27
+ status:
28
+ | "pending"
29
+ | "processing"
30
+ | "ready"
31
+ | "applying"
32
+ | "applied"
33
+ | "failed";
34
+ preRollRecordingId?: string;
35
+ actualDurationMs?: number;
36
+ error?: string;
37
+ }
38
+
39
+ interface RewindExtensionDialogProps {
40
+ open: boolean;
41
+ onOpenChange: (open: boolean) => void;
42
+ recordingId: string;
43
+ durationMs: number;
44
+ videoFormat: "webm" | "mp4";
45
+ hasAudio: boolean;
46
+ visibility: "private" | "org" | "public";
47
+ onVisibilityChanged: () => void | Promise<void>;
48
+ onApplied: () => void | Promise<void>;
49
+ }
50
+
51
+ function wait(ms: number): Promise<void> {
52
+ return new Promise((resolve) => window.setTimeout(resolve, ms));
53
+ }
54
+
55
+ async function readRequest(
56
+ recordingId: string,
57
+ ): Promise<RewindExtensionRequest | null> {
58
+ const params = new URLSearchParams({ recordingId });
59
+ const response = await fetch(
60
+ agentNativePath(
61
+ `/_agent-native/actions/get-rewind-extension-request?${params}`,
62
+ ),
63
+ );
64
+ const json = await response.json().catch(() => null);
65
+ if (!response.ok) {
66
+ throw new Error(json?.error || json?.message || "Could not check Rewind.");
67
+ }
68
+ return (json?.result ?? json)?.request ?? null;
69
+ }
70
+
71
+ export function RewindExtensionDialog({
72
+ open,
73
+ onOpenChange,
74
+ recordingId,
75
+ durationMs,
76
+ videoFormat,
77
+ hasAudio,
78
+ visibility,
79
+ onVisibilityChanged,
80
+ onApplied,
81
+ }: RewindExtensionDialogProps) {
82
+ const t = useT();
83
+ const makePrivateForRewind = useActionMutation(
84
+ "make-recording-private-for-rewind",
85
+ );
86
+ const requestExtension = useActionMutation("request-rewind-extension");
87
+ const applyExtension = useActionMutation("apply-rewind-extension");
88
+ const requestTranscript = useActionMutation("request-transcript" as any);
89
+ const [busy, setBusy] = useState(false);
90
+ const [status, setStatus] = useState<string | null>(null);
91
+ const [progress, setProgress] = useState(0);
92
+ const [privacyConfirmed, setPrivacyConfirmed] = useState(false);
93
+ const mounted = useRef(true);
94
+
95
+ useEffect(() => {
96
+ return () => {
97
+ mounted.current = false;
98
+ };
99
+ }, []);
100
+
101
+ useEffect(() => {
102
+ if (!open && !busy) {
103
+ setStatus(null);
104
+ setProgress(0);
105
+ setPrivacyConfirmed(false);
106
+ }
107
+ }, [busy, open]);
108
+
109
+ const makePrivate = useCallback(async () => {
110
+ setBusy(true);
111
+ setStatus("Making this Clip private…");
112
+ try {
113
+ await makePrivateForRewind.mutateAsync({ recordingId });
114
+ await onVisibilityChanged();
115
+ setPrivacyConfirmed(true);
116
+ setStatus(null);
117
+ toast.success(t("rewindExtension.privateReady"));
118
+ } catch (error) {
119
+ toast.error(error instanceof Error ? error.message : String(error));
120
+ setStatus(null);
121
+ } finally {
122
+ setBusy(false);
123
+ }
124
+ }, [makePrivateForRewind, onVisibilityChanged, recordingId, t]);
125
+
126
+ const addFromRewind = useCallback(
127
+ async (seconds: 30 | 300) => {
128
+ setBusy(true);
129
+ setProgress(0);
130
+ try {
131
+ setStatus("Asking Clips Alpha for local Rewind history…");
132
+ const created = (await requestExtension.mutateAsync({
133
+ recordingId,
134
+ seconds,
135
+ })) as RewindExtensionRequest;
136
+ let request = created;
137
+ for (let attempt = 0; attempt < 160; attempt += 1) {
138
+ if (request.status === "failed") {
139
+ throw new Error(
140
+ request.error || "Clips Alpha could not read Rewind.",
141
+ );
142
+ }
143
+ if (
144
+ request.status === "ready" &&
145
+ request.preRollRecordingId &&
146
+ request.actualDurationMs
147
+ ) {
148
+ break;
149
+ }
150
+ setStatus(
151
+ request.status === "processing"
152
+ ? "Clips Alpha is preparing that local interval…"
153
+ : "Waiting for Clips Alpha…",
154
+ );
155
+ await wait(1_500);
156
+ request = (await readRequest(recordingId)) ?? request;
157
+ }
158
+ if (
159
+ request.status !== "ready" ||
160
+ !request.preRollRecordingId ||
161
+ !request.actualDurationMs
162
+ ) {
163
+ throw new Error(
164
+ "Clips Alpha did not finish the Rewind request in time.",
165
+ );
166
+ }
167
+
168
+ setStatus("Combining the selected history with this Clip…");
169
+ const blob = await exportConcat(
170
+ [
171
+ {
172
+ url: `${appBasePath()}/api/video/${encodeURIComponent(request.preRollRecordingId)}`,
173
+ format: "mp4",
174
+ hasAudio,
175
+ },
176
+ {
177
+ url: `${appBasePath()}/api/video/${encodeURIComponent(recordingId)}`,
178
+ format: videoFormat,
179
+ hasAudio,
180
+ },
181
+ ],
182
+ (next) => mounted.current && setProgress(next.progress),
183
+ );
184
+ setStatus("Saving the longer Clip…");
185
+ const upload = await uploadFileClient(
186
+ blob,
187
+ `rewind-${recordingId}.mp4`,
188
+ );
189
+ if (!upload?.url) {
190
+ throw new Error(
191
+ "Connect video storage before adding Rewind history.",
192
+ );
193
+ }
194
+ await applyExtension.mutateAsync({
195
+ recordingId,
196
+ requestId: request.requestId,
197
+ preRollRecordingId: request.preRollRecordingId,
198
+ videoUrl: upload.url,
199
+ durationMs: durationMs + request.actualDurationMs,
200
+ addedMs: request.actualDurationMs,
201
+ });
202
+ if (hasAudio) {
203
+ void requestTranscript
204
+ .mutateAsync({ recordingId, force: true, regenerate: true })
205
+ .catch(() => {
206
+ toast.info(
207
+ "The longer Clip was saved; its transcript can be regenerated later.",
208
+ );
209
+ });
210
+ }
211
+ await onApplied();
212
+ toast.success(
213
+ `${request.actualDurationMs >= 60_000 ? "Five minutes" : "Thirty seconds"} added from Rewind.`,
214
+ );
215
+ onOpenChange(false);
216
+ } catch (error) {
217
+ toast.error(error instanceof Error ? error.message : String(error));
218
+ setStatus(null);
219
+ } finally {
220
+ if (mounted.current) {
221
+ setBusy(false);
222
+ setProgress(0);
223
+ }
224
+ }
225
+ },
226
+ [
227
+ applyExtension,
228
+ durationMs,
229
+ hasAudio,
230
+ onApplied,
231
+ onOpenChange,
232
+ recordingId,
233
+ requestExtension,
234
+ requestTranscript,
235
+ videoFormat,
236
+ ],
237
+ );
238
+
239
+ return (
240
+ <Dialog open={open} onOpenChange={(next) => !busy && onOpenChange(next)}>
241
+ <DialogContent className="sm:max-w-md">
242
+ <DialogHeader>
243
+ <DialogTitle className="flex items-center gap-2">
244
+ <IconHistory className="h-4 w-4 text-primary" />
245
+ {t("rewindExtension.title")}
246
+ </DialogTitle>
247
+ <DialogDescription>
248
+ {t("rewindExtension.description")}
249
+ </DialogDescription>
250
+ </DialogHeader>
251
+
252
+ {busy ? (
253
+ <div
254
+ className="rounded-lg border bg-muted/30 p-4 text-sm"
255
+ role="status"
256
+ aria-live="polite"
257
+ >
258
+ <div className="flex items-center gap-2 font-medium">
259
+ <IconLoader2
260
+ className="h-4 w-4 animate-spin"
261
+ aria-hidden="true"
262
+ />
263
+ {status}
264
+ </div>
265
+ {progress > 0 ? (
266
+ <div className="mt-3 h-1.5 overflow-hidden rounded-full bg-muted">
267
+ <div
268
+ className="h-full bg-primary transition-[width]"
269
+ role="progressbar"
270
+ aria-label={t("rewindExtension.progressLabel")}
271
+ aria-valuemin={0}
272
+ aria-valuemax={100}
273
+ aria-valuenow={Math.round(progress * 100)}
274
+ style={{ width: `${Math.round(progress * 100)}%` }}
275
+ />
276
+ </div>
277
+ ) : null}
278
+ </div>
279
+ ) : visibility !== "private" && !privacyConfirmed ? (
280
+ <div className="grid gap-3">
281
+ <div className="rounded-lg border border-amber-500/30 bg-amber-500/10 p-3 text-sm">
282
+ <strong className="block">
283
+ {t("rewindExtension.privateFirstTitle")}
284
+ </strong>
285
+ <span className="mt-1 block text-muted-foreground">
286
+ {t("rewindExtension.privateFirstDescription")}
287
+ </span>
288
+ </div>
289
+ <Button onClick={() => void makePrivate()}>
290
+ {t("rewindExtension.makePrivateContinue")}
291
+ </Button>
292
+ </div>
293
+ ) : (
294
+ <div className="grid gap-2">
295
+ <Button
296
+ variant="outline"
297
+ className="h-auto justify-start py-3 text-left"
298
+ onClick={() => void addFromRewind(30)}
299
+ >
300
+ <span>
301
+ <strong className="block">
302
+ {t("rewindExtension.add30Seconds")}
303
+ </strong>
304
+ <span className="text-xs font-normal text-muted-foreground">
305
+ Useful when you clicked Record just after the important bit.
306
+ </span>
307
+ </span>
308
+ </Button>
309
+ <Button
310
+ variant="outline"
311
+ className="h-auto justify-start py-3 text-left"
312
+ onClick={() => void addFromRewind(300)}
313
+ >
314
+ <span>
315
+ <strong className="block">
316
+ {t("rewindExtension.add5Minutes")}
317
+ </strong>
318
+ <span className="text-xs font-normal text-muted-foreground">
319
+ {t("rewindExtension.add5MinutesDescription")}
320
+ </span>
321
+ </span>
322
+ </Button>
323
+ </div>
324
+ )}
325
+
326
+ <DialogFooter>
327
+ <Button
328
+ variant="ghost"
329
+ disabled={busy}
330
+ onClick={() => onOpenChange(false)}
331
+ >
332
+ Cancel
333
+ </Button>
334
+ </DialogFooter>
335
+ </DialogContent>
336
+ </Dialog>
337
+ );
338
+ }
@@ -24,24 +24,9 @@ import {
24
24
  import { ScrollArea } from "@/components/ui/scroll-area";
25
25
  import { exportConcat } from "@/lib/ffmpeg-export";
26
26
  import { formatMs } from "@/lib/timestamp-mapping";
27
+ import { uploadFileClient } from "@/lib/upload-file-client";
27
28
  import { cn } from "@/lib/utils";
28
29
 
29
- /** Client-side upload via the framework's auto-mounted `/file-upload` route. */
30
- async function uploadFileClient(
31
- blob: Blob,
32
- filename: string,
33
- ): Promise<{ url: string } | null> {
34
- const form = new FormData();
35
- form.append("file", blob, filename);
36
- const res = await fetch(agentNativePath("/_agent-native/file-upload"), {
37
- method: "POST",
38
- body: form,
39
- });
40
- if (!res.ok) return null;
41
- const json = await res.json();
42
- return json?.url ? { url: json.url as string } : null;
43
- }
44
-
45
30
  export interface StitchManagerProps {
46
31
  open: boolean;
47
32
  onOpenChange: (open: boolean) => void;
@@ -1,3 +1,4 @@
1
+ import { useT } from "@agent-native/core/client/i18n";
1
2
  import { useMemo } from "react";
2
3
 
3
4
  import {
@@ -21,6 +22,8 @@ export interface TimelineProps {
21
22
  chapters?: TimelineChapter[];
22
23
  excludedRanges?: Array<{ startMs: number; endMs: number }>;
23
24
  splitPoints?: number[];
25
+ /** Countdown-complete start after explicit Rewind history was prepended. */
26
+ originalStartMs?: number;
24
27
  onSeek?: (originalMs: number) => void;
25
28
  onClickChapter?: (chapter: TimelineChapter) => void;
26
29
  className?: string;
@@ -44,10 +47,12 @@ export function Timeline({
44
47
  chapters = [],
45
48
  excludedRanges = [],
46
49
  splitPoints = [],
50
+ originalStartMs,
47
51
  onSeek,
48
52
  onClickChapter,
49
53
  className,
50
54
  }: TimelineProps) {
55
+ const t = useT();
51
56
  const ticks = useMemo(() => {
52
57
  if (durationMs <= 0) return [];
53
58
  // Target ~1 tick per 100px at the current zoom, rounded to a human interval.
@@ -133,6 +138,27 @@ export function Timeline({
133
138
  );
134
139
  })}
135
140
 
141
+ {typeof originalStartMs === "number" && originalStartMs > 0 ? (
142
+ <Tooltip>
143
+ <TooltipTrigger asChild>
144
+ <div
145
+ className="absolute top-0 z-10 h-full w-0.5 cursor-help bg-amber-500"
146
+ style={{
147
+ left: (originalStartMs / Math.max(durationMs, 1)) * width - 1,
148
+ }}
149
+ aria-label={`Original Clip start at ${formatMs(originalStartMs)}`}
150
+ >
151
+ <span className="absolute left-1 top-0 whitespace-nowrap rounded-sm bg-amber-500 px-1 text-[9px] font-semibold text-black">
152
+ {t("timeline.clipStartedHere")}
153
+ </span>
154
+ </div>
155
+ </TooltipTrigger>
156
+ <TooltipContent>
157
+ Recording began after the countdown · {formatMs(originalStartMs)}
158
+ </TooltipContent>
159
+ </Tooltip>
160
+ ) : null}
161
+
136
162
  {/* Playhead */}
137
163
  <div
138
164
  className="absolute top-0 h-full pointer-events-none"
@@ -1,5 +1,4 @@
1
1
  import { useT } from "@agent-native/core/client/i18n";
2
- import { resolveNativeMeetingJoinUrl } from "@shared/meeting-join-url";
3
2
  import {
4
3
  IconCheck,
5
4
  IconClock,
@@ -285,7 +284,7 @@ export function UpcomingMeetingCard({
285
284
  <div className="flex items-center gap-1.5">
286
285
  {meeting.joinUrl && (
287
286
  <a
288
- href={resolveNativeMeetingJoinUrl(meeting.joinUrl)}
287
+ href={meeting.joinUrl}
289
288
  target="_blank"
290
289
  rel="noopener noreferrer"
291
290
  onClick={(e) => e.stopPropagation()}
@@ -1493,6 +1493,24 @@ All notable user-facing changes to Clips are documented here. Open it any time f
1493
1493
  guideStartDescription:
1494
1494
  "Use the desktop reminder or the menu-bar Start Meeting Notes item when the call begins.",
1495
1495
  },
1496
+ rewindExtension: {
1497
+ title: "Add what happened before",
1498
+ description:
1499
+ "Pull a specific interval from local Rewind and add it to the start of this Clip. Nothing is added automatically.",
1500
+ progressLabel: "Rewind history processing progress",
1501
+ privateFirstTitle: "Make this Clip private first",
1502
+ privateFirstDescription:
1503
+ "Local Rewind history can contain context from before you chose to record. This changes the Clip to private. If anyone still has direct access, Clips will stop here so you can remove them in Share first.",
1504
+ makePrivateContinue: "Make private and continue",
1505
+ add30Seconds: "Add the previous 30 seconds",
1506
+ add5Minutes: "Add the previous 5 minutes",
1507
+ add5MinutesDescription:
1508
+ "Good for recovering the lead-in to a longer explanation.",
1509
+ privateReady: "This Clip is private. You can now add local Rewind history.",
1510
+ },
1511
+ timeline: {
1512
+ clipStartedHere: "Clip started here",
1513
+ },
1496
1514
  };
1497
1515
 
1498
1516
  export default messages;
@@ -349,7 +349,11 @@ export async function exportGif(
349
349
  * before calling the `stitch-recordings` action with the uploaded URL.
350
350
  */
351
351
  export async function exportConcat(
352
- sources: Array<{ url: string; format?: "webm" | "mp4" }>,
352
+ sources: Array<{
353
+ url: string;
354
+ format?: "webm" | "mp4";
355
+ hasAudio?: boolean;
356
+ }>,
353
357
  onProgress?: (p: ExportProgress) => void,
354
358
  ): Promise<Blob> {
355
359
  if (sources.length < 2) {
@@ -377,38 +381,41 @@ export async function exportConcat(
377
381
  "-i",
378
382
  `src${i}.${s.format ?? "webm"}`,
379
383
  ]);
384
+ const includesAudio = sources.every((source) => source.hasAudio !== false);
380
385
  const filterParts: string[] = [];
381
386
  const concatInputs: string[] = [];
382
387
  for (let i = 0; i < sources.length; i++) {
383
388
  filterParts.push(`[${i}:v]setpts=PTS-STARTPTS[v${i}]`);
384
- filterParts.push(`[${i}:a]asetpts=PTS-STARTPTS[a${i}]`);
385
- concatInputs.push(`[v${i}][a${i}]`);
389
+ if (includesAudio) {
390
+ filterParts.push(`[${i}:a]asetpts=PTS-STARTPTS[a${i}]`);
391
+ concatInputs.push(`[v${i}][a${i}]`);
392
+ } else {
393
+ concatInputs.push(`[v${i}]`);
394
+ }
386
395
  }
387
396
  filterParts.push(
388
- `${concatInputs.join("")}concat=n=${sources.length}:v=1:a=1[outv][outa]`,
397
+ `${concatInputs.join("")}concat=n=${sources.length}:v=1:a=${includesAudio ? 1 : 0}[outv]${includesAudio ? "[outa]" : ""}`,
389
398
  );
390
- await ffmpeg.exec([
399
+ const outputArgs = [
391
400
  ...inputArgs,
392
401
  "-filter_complex",
393
402
  filterParts.join(";"),
394
403
  "-map",
395
404
  "[outv]",
396
- "-map",
397
- "[outa]",
398
405
  "-c:v",
399
406
  "libx264",
400
407
  "-preset",
401
408
  "veryfast",
402
409
  "-crf",
403
410
  "22",
404
- "-c:a",
405
- "aac",
406
- "-b:a",
407
- "128k",
411
+ ...(includesAudio
412
+ ? ["-map", "[outa]", "-c:a", "aac", "-b:a", "128k"]
413
+ : []),
408
414
  "-movflags",
409
415
  "+faststart",
410
416
  "stitched.mp4",
411
- ]);
417
+ ];
418
+ await ffmpeg.exec(outputArgs);
412
419
 
413
420
  const data = (await ffmpeg.readFile("stitched.mp4")) as Uint8Array;
414
421
  const blob = new Blob([data as BlobPart], { type: "video/mp4" });
@@ -0,0 +1,31 @@
1
+ export type ClipVisibility = "private" | "org" | "public";
2
+
3
+ export function isPrivateClip(
4
+ visibility: ClipVisibility | string | null | undefined,
5
+ ): boolean {
6
+ return visibility === "private";
7
+ }
8
+
9
+ export function canAddRewindHistory(
10
+ role: string | null | undefined,
11
+ visibility: ClipVisibility | string | null | undefined,
12
+ ): boolean {
13
+ return role === "owner" && isPrivateClip(visibility);
14
+ }
15
+
16
+ export function canOfferRewindHistory(
17
+ role: string | null | undefined,
18
+ ): boolean {
19
+ return role === "owner";
20
+ }
21
+
22
+ export function rewindHistoryUnavailableReason(
23
+ role: string | null | undefined,
24
+ visibility: ClipVisibility | string | null | undefined,
25
+ ): string | undefined {
26
+ if (role !== "owner") return "Only the owner can add Rewind history";
27
+ if (visibility !== "private") {
28
+ return "Make this Clip private before adding Rewind history";
29
+ }
30
+ return undefined;
31
+ }
@@ -45,6 +45,8 @@ export interface EditsJson {
45
45
  thumbnail?: ThumbnailSpec | null;
46
46
  /** Provenance: source recording IDs when this recording was created via stitch-recordings. */
47
47
  stitchedFrom?: string[];
48
+ /** Original countdown-complete boundary after an explicit Rewind pre-roll was prepended. */
49
+ rewindOriginalStartMs?: number;
48
50
  }
49
51
 
50
52
  export const DEFAULT_EDITS: EditsJson = {
@@ -73,6 +75,11 @@ export function parseEdits(raw: string | null | undefined): EditsJson {
73
75
  ...(Array.isArray(j.stitchedFrom)
74
76
  ? { stitchedFrom: j.stitchedFrom as string[] }
75
77
  : {}),
78
+ ...(typeof j.rewindOriginalStartMs === "number" &&
79
+ Number.isFinite(j.rewindOriginalStartMs) &&
80
+ j.rewindOriginalStartMs > 0
81
+ ? { rewindOriginalStartMs: Math.round(j.rewindOriginalStartMs) }
82
+ : {}),
76
83
  };
77
84
  } catch {
78
85
  return { ...DEFAULT_EDITS };
@@ -0,0 +1,17 @@
1
+ import { agentNativePath } from "@agent-native/core/client/api-path";
2
+
3
+ /** Upload a browser-produced file through Clips' authenticated file route. */
4
+ export async function uploadFileClient(
5
+ blob: Blob,
6
+ filename: string,
7
+ ): Promise<{ url: string } | null> {
8
+ const form = new FormData();
9
+ form.append("file", blob, filename);
10
+ const response = await fetch(agentNativePath("/_agent-native/file-upload"), {
11
+ method: "POST",
12
+ body: form,
13
+ });
14
+ if (!response.ok) return null;
15
+ const json = await response.json();
16
+ return json?.url ? { url: json.url as string } : null;
17
+ }
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: added
3
+ date: 2026-07-14
4
+ ---
5
+
6
+ Rewind can keep recent screen context local, make it searchable on your Mac, and explicitly include the previous 30 seconds or 5 minutes in a Clip without starting another recorder
@@ -0,0 +1,12 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-17
4
+ ---
5
+
6
+ Active Clips now keep Rewind's shared screen and audio capture stable until the Clip ends. Rewind's on/off, pause, and capture-mode controls visibly lock during recording, with a native safeguard for non-UI callers, so changing Rewind can no longer strand a Clip in optimization with a coverage gap.
7
+
8
+ Rewind Settings also use application exclusions as the single privacy model, return Back through the surface that opened them, describe local-memory maintenance before asking someone to act, and speak plainly about local agents finding moments on request.
9
+
10
+ The recording toolbar's timer now opens Clips without stopping the active recording, and Dock, tray, and shortcut opens all restore the active popover. Rewind-derived hosted Clips now create the resumable storage session they need before recording, and exact-range encoding uses an interactive-speed preset so Stop no longer appears frozen for roughly the Clip's full duration. The compact Rewind settings rows also reserve separate label and control columns to prevent overlap.
11
+
12
+ Recorder Home now keeps its bottom navigation fixed while the content region adapts to the available popover height. After Rewind's first-time setup, its single Home switch means remembering or paused; the duplicate Pause button and active-recording lock paragraph are gone. Full disable and capture-mode setup remain in Rewind Settings, while the active-Clip switch uses an unmistakable disabled treatment until recording ends.