@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
@@ -0,0 +1,195 @@
1
+ import { defineAction } from "@agent-native/core";
2
+ import {
3
+ readAppState,
4
+ writeAppState,
5
+ } from "@agent-native/core/application-state";
6
+ import { and, eq, sql } from "drizzle-orm";
7
+ import { z } from "zod";
8
+
9
+ import { isPrivateClip } from "../app/lib/rewind-visibility.js";
10
+ import { parseEdits, serializeEdits } from "../app/lib/timestamp-mapping.js";
11
+ import { getDb, schema } from "../server/db/index.js";
12
+ import {
13
+ getCurrentOwnerEmail,
14
+ ownerEmailMatches,
15
+ } from "../server/lib/recordings.js";
16
+ import { parseTranscriptSegments } from "../shared/transcript-segments.js";
17
+ import { assertNoDirectRecordingShares } from "./make-recording-private-for-rewind.js";
18
+ import {
19
+ rewindExtensionKey,
20
+ type RewindExtensionRequest,
21
+ } from "./request-rewind-extension.js";
22
+
23
+ function shiftedJsonArray(
24
+ raw: string | null | undefined,
25
+ addedMs: number,
26
+ fields: string[],
27
+ ): string {
28
+ try {
29
+ const parsed = JSON.parse(raw || "[]");
30
+ if (!Array.isArray(parsed)) return raw || "[]";
31
+ return JSON.stringify(
32
+ parsed.map((item) => {
33
+ if (!item || typeof item !== "object") return item;
34
+ const next = { ...item } as Record<string, unknown>;
35
+ for (const field of fields) {
36
+ if (typeof next[field] === "number") {
37
+ next[field] = Math.max(0, Math.round(next[field] + addedMs));
38
+ }
39
+ }
40
+ return next;
41
+ }),
42
+ );
43
+ } catch {
44
+ return raw || "[]";
45
+ }
46
+ }
47
+
48
+ export default defineAction({
49
+ description:
50
+ "Replace an owned Clip with an explicitly prepended local Rewind range while shifting its timestamped editor data and preserving the original-start marker.",
51
+ schema: z.object({
52
+ recordingId: z.string(),
53
+ requestId: z.string(),
54
+ preRollRecordingId: z.string(),
55
+ videoUrl: z.string().min(1),
56
+ durationMs: z.number().int().positive(),
57
+ addedMs: z
58
+ .number()
59
+ .int()
60
+ .positive()
61
+ .max(5 * 60_000),
62
+ }),
63
+ run: async (args) => {
64
+ if (args.videoUrl.startsWith("data:")) {
65
+ throw new Error(
66
+ "The combined Clip must be uploaded before it is applied.",
67
+ );
68
+ }
69
+ const db = getDb();
70
+ const ownerEmail = getCurrentOwnerEmail();
71
+ const [recording] = await db
72
+ .select()
73
+ .from(schema.recordings)
74
+ .where(
75
+ and(
76
+ eq(schema.recordings.id, args.recordingId),
77
+ ownerEmailMatches(schema.recordings.ownerEmail, ownerEmail),
78
+ ),
79
+ );
80
+ const [preRoll] = await db
81
+ .select()
82
+ .from(schema.recordings)
83
+ .where(
84
+ and(
85
+ eq(schema.recordings.id, args.preRollRecordingId),
86
+ ownerEmailMatches(schema.recordings.ownerEmail, ownerEmail),
87
+ ),
88
+ );
89
+ if (!recording || !preRoll) {
90
+ throw new Error("The Clip or its local Rewind pre-roll is unavailable.");
91
+ }
92
+ if (
93
+ !isPrivateClip(recording.visibility) ||
94
+ !isPrivateClip(preRoll.visibility)
95
+ ) {
96
+ throw new Error(
97
+ "Rewind history can only be added to a private Clip from a private pre-roll.",
98
+ );
99
+ }
100
+ await assertNoDirectRecordingShares(args.recordingId);
101
+ const key = rewindExtensionKey(args.recordingId);
102
+ const request = (await readAppState(key)) as RewindExtensionRequest | null;
103
+ if (
104
+ !request ||
105
+ request.requestId !== args.requestId ||
106
+ request.status !== "ready" ||
107
+ request.preRollRecordingId !== args.preRollRecordingId
108
+ ) {
109
+ throw new Error("The Rewind pre-roll request is not ready to apply.");
110
+ }
111
+ const expectedDuration = recording.durationMs + args.addedMs;
112
+ if (Math.abs(args.durationMs - expectedDuration) > 2_000) {
113
+ throw new Error("The combined Clip duration does not match its sources.");
114
+ }
115
+
116
+ const edits = parseEdits(recording.editsJson);
117
+ edits.trims = edits.trims.map((trim) => ({
118
+ ...trim,
119
+ startMs: trim.startMs + args.addedMs,
120
+ endMs: trim.endMs + args.addedMs,
121
+ }));
122
+ edits.blurs = edits.blurs.map((blur) => ({
123
+ ...blur,
124
+ startMs: blur.startMs + args.addedMs,
125
+ endMs: blur.endMs + args.addedMs,
126
+ }));
127
+ edits.rewindOriginalStartMs = args.addedMs;
128
+
129
+ const [transcript] = await db
130
+ .select()
131
+ .from(schema.recordingTranscripts)
132
+ .where(eq(schema.recordingTranscripts.recordingId, args.recordingId));
133
+ const shiftedTranscript = transcript
134
+ ? JSON.stringify(
135
+ parseTranscriptSegments(transcript.segmentsJson).map((segment) => ({
136
+ ...segment,
137
+ startMs: segment.startMs + args.addedMs,
138
+ endMs: segment.endMs + args.addedMs,
139
+ })),
140
+ )
141
+ : null;
142
+ const now = new Date().toISOString();
143
+
144
+ await db.transaction(async (tx) => {
145
+ await tx
146
+ .update(schema.recordings)
147
+ .set({
148
+ videoUrl: args.videoUrl,
149
+ videoFormat: "mp4",
150
+ durationMs: args.durationMs,
151
+ editsJson: serializeEdits(edits),
152
+ chaptersJson: shiftedJsonArray(recording.chaptersJson, args.addedMs, [
153
+ "startMs",
154
+ ]),
155
+ thumbnailUrl: null,
156
+ animatedThumbnailUrl: null,
157
+ updatedAt: now,
158
+ })
159
+ .where(eq(schema.recordings.id, args.recordingId));
160
+ if (transcript && shiftedTranscript) {
161
+ await tx
162
+ .update(schema.recordingTranscripts)
163
+ .set({ segmentsJson: shiftedTranscript, updatedAt: now })
164
+ .where(eq(schema.recordingTranscripts.recordingId, args.recordingId));
165
+ }
166
+ await tx
167
+ .update(schema.recordingComments)
168
+ .set({
169
+ videoTimestampMs: sql`${schema.recordingComments.videoTimestampMs} + ${args.addedMs}`,
170
+ updatedAt: now,
171
+ })
172
+ .where(eq(schema.recordingComments.recordingId, args.recordingId));
173
+ await tx
174
+ .update(schema.recordingReactions)
175
+ .set({
176
+ videoTimestampMs: sql`${schema.recordingReactions.videoTimestampMs} + ${args.addedMs}`,
177
+ })
178
+ .where(eq(schema.recordingReactions.recordingId, args.recordingId));
179
+ await tx
180
+ .update(schema.recordings)
181
+ .set({ trashedAt: now, expiresAt: now, updatedAt: now })
182
+ .where(eq(schema.recordings.id, args.preRollRecordingId));
183
+ });
184
+
185
+ const applied: RewindExtensionRequest = {
186
+ ...request,
187
+ status: "applied",
188
+ actualDurationMs: args.addedMs,
189
+ updatedAt: now,
190
+ };
191
+ await writeAppState(key, applied);
192
+ await writeAppState("refresh-signal", { ts: Date.now() });
193
+ return { recordingId: args.recordingId, request: applied };
194
+ },
195
+ });
@@ -0,0 +1,64 @@
1
+ import { defineAction } from "@agent-native/core";
2
+ import { assertAccess } from "@agent-native/core/sharing";
3
+ import { eq } from "drizzle-orm";
4
+ import { z } from "zod";
5
+
6
+ import { getDb, schema } from "../server/db/index.js";
7
+ import deleteRecordingPermanent from "./delete-recording-permanent.js";
8
+
9
+ export default defineAction({
10
+ description:
11
+ "Delete an expired agent-created Clip only if it is still private and has not been renamed, shared, commented on, reacted to, tagged, archived, or trashed.",
12
+ schema: z.object({ id: z.string() }),
13
+ run: async ({ id }) => {
14
+ await assertAccess("recording", id, "editor");
15
+ const db = getDb();
16
+ const [recording] = await db
17
+ .select()
18
+ .from(schema.recordings)
19
+ .where(eq(schema.recordings.id, id));
20
+ if (!recording) return { id, deleted: true, reason: "already-missing" };
21
+
22
+ const [share, comment, reaction, tag] = await Promise.all([
23
+ db
24
+ .select({ id: schema.recordingShares.id })
25
+ .from(schema.recordingShares)
26
+ .where(eq(schema.recordingShares.resourceId, id))
27
+ .limit(1),
28
+ db
29
+ .select({ id: schema.recordingComments.id })
30
+ .from(schema.recordingComments)
31
+ .where(eq(schema.recordingComments.recordingId, id))
32
+ .limit(1),
33
+ db
34
+ .select({ id: schema.recordingReactions.id })
35
+ .from(schema.recordingReactions)
36
+ .where(eq(schema.recordingReactions.recordingId, id))
37
+ .limit(1),
38
+ db
39
+ .select({ id: schema.recordingTags.id })
40
+ .from(schema.recordingTags)
41
+ .where(eq(schema.recordingTags.recordingId, id))
42
+ .limit(1),
43
+ ]);
44
+
45
+ const promoted =
46
+ recording.visibility !== "private" ||
47
+ recording.titleSource === "manual" ||
48
+ Boolean(recording.archivedAt || recording.trashedAt) ||
49
+ share.length > 0 ||
50
+ comment.length > 0 ||
51
+ reaction.length > 0 ||
52
+ tag.length > 0;
53
+ if (promoted) {
54
+ await db
55
+ .update(schema.recordings)
56
+ .set({ expiresAt: null, updatedAt: new Date().toISOString() })
57
+ .where(eq(schema.recordings.id, id));
58
+ return { id, deleted: false, reason: "promoted" };
59
+ }
60
+
61
+ await deleteRecordingPermanent.run({ id });
62
+ return { id, deleted: true, reason: "retention-expired" };
63
+ },
64
+ });
@@ -0,0 +1,22 @@
1
+ import { defineAction } from "@agent-native/core";
2
+ import { readAppState } from "@agent-native/core/application-state";
3
+ import { assertAccess } from "@agent-native/core/sharing";
4
+ import { z } from "zod";
5
+
6
+ import {
7
+ rewindExtensionKey,
8
+ type RewindExtensionRequest,
9
+ } from "./request-rewind-extension.js";
10
+
11
+ export default defineAction({
12
+ description: "Read the current explicit Rewind pre-roll request for a Clip.",
13
+ schema: z.object({ recordingId: z.string() }),
14
+ http: { method: "GET" },
15
+ run: async ({ recordingId }) => {
16
+ await assertAccess("recording", recordingId, "owner");
17
+ const request = (await readAppState(
18
+ rewindExtensionKey(recordingId),
19
+ )) as RewindExtensionRequest | null;
20
+ return { request };
21
+ },
22
+ });
@@ -4,9 +4,25 @@ import { z } from "zod";
4
4
 
5
5
  export default defineAction({
6
6
  description:
7
- "Check local Clips Screen Memory status for this machine: enabled/paused state, local storage files, and capture recency. Screen Memory is disabled by default and local-only.",
7
+ "Check local Clips Screen Memory status for this machine: enabled/paused state, local metadata files, and capture recency. Screen Memory is disabled by default, local-only, and does not expose media bytes or images.",
8
8
  schema: z.object({}),
9
9
  http: { method: "GET" },
10
10
  readOnly: true,
11
- run: async () => readScreenMemoryStatus(),
11
+ run: async () => {
12
+ const status = await readScreenMemoryStatus();
13
+ return {
14
+ feature: status.feature,
15
+ localOnly: status.localOnly,
16
+ enabled: status.enabled,
17
+ paused: status.paused,
18
+ state: status.state,
19
+ captureMode: status.config.captureMode,
20
+ retentionHours: status.config.retentionHours,
21
+ captureCount: status.captureCount,
22
+ storageBytes: status.storageBytes,
23
+ oldestCaptureAt: status.oldestCaptureAt,
24
+ newestCaptureAt: status.newestCaptureAt,
25
+ note: status.note,
26
+ };
27
+ },
12
28
  });
@@ -0,0 +1,52 @@
1
+ import { defineAction } from "@agent-native/core";
2
+ import { listAppState } from "@agent-native/core/application-state";
3
+ import { and, eq, inArray } from "drizzle-orm";
4
+ import { z } from "zod";
5
+
6
+ import { getDb, schema } from "../server/db/index.js";
7
+ import {
8
+ getCurrentOwnerEmail,
9
+ ownerEmailMatches,
10
+ } from "../server/lib/recordings.js";
11
+ import {
12
+ REWIND_EXTENSION_PREFIX,
13
+ type RewindExtensionRequest,
14
+ } from "./request-rewind-extension.js";
15
+
16
+ export default defineAction({
17
+ description:
18
+ "List pending local Rewind pre-roll requests for the signed-in Clips Alpha app.",
19
+ schema: z.object({}),
20
+ http: { method: "GET" },
21
+ run: async () => {
22
+ const entries = await listAppState(REWIND_EXTENSION_PREFIX);
23
+ const staleProcessingCutoff = Date.now() - 2 * 60_000;
24
+ const requests = entries
25
+ .map((entry) => entry.value as unknown as RewindExtensionRequest)
26
+ .filter(
27
+ (request) =>
28
+ request &&
29
+ typeof request.recordingId === "string" &&
30
+ (request.status === "pending" ||
31
+ (request.status === "processing" &&
32
+ Date.parse(request.updatedAt) < staleProcessingCutoff)),
33
+ );
34
+ if (!requests.length) return { requests: [] };
35
+ const ids = [...new Set(requests.map((request) => request.recordingId))];
36
+ const ownerEmail = getCurrentOwnerEmail();
37
+ const accessible = await getDb()
38
+ .select({ id: schema.recordings.id })
39
+ .from(schema.recordings)
40
+ .where(
41
+ and(
42
+ ownerEmailMatches(schema.recordings.ownerEmail, ownerEmail),
43
+ inArray(schema.recordings.id, ids),
44
+ eq(schema.recordings.status, "ready"),
45
+ ),
46
+ );
47
+ const allowed = new Set(accessible.map((row) => row.id));
48
+ return {
49
+ requests: requests.filter((request) => allowed.has(request.recordingId)),
50
+ };
51
+ },
52
+ });
@@ -0,0 +1,55 @@
1
+ import { defineAction } from "@agent-native/core";
2
+ import { assertAccess } from "@agent-native/core/sharing";
3
+ import { and, eq, notExists } from "drizzle-orm";
4
+ import { z } from "zod";
5
+
6
+ import { getDb, schema } from "../server/db/index.js";
7
+
8
+ export const DIRECT_SHARE_REWIND_ERROR =
9
+ "This Clip is still shared directly with other people. Remove their access in Share before adding local Rewind history.";
10
+
11
+ export async function assertNoDirectRecordingShares(
12
+ recordingId: string,
13
+ ): Promise<void> {
14
+ const [share] = await getDb()
15
+ .select({ id: schema.recordingShares.id })
16
+ .from(schema.recordingShares)
17
+ .where(eq(schema.recordingShares.resourceId, recordingId))
18
+ .limit(1);
19
+ if (share) throw new Error(DIRECT_SHARE_REWIND_ERROR);
20
+ }
21
+
22
+ export default defineAction({
23
+ description:
24
+ "Make an owned recording private for a Rewind extension, refusing to continue while anyone still has direct access.",
25
+ schema: z.object({ recordingId: z.string() }),
26
+ run: async ({ recordingId }) => {
27
+ await assertAccess("recording", recordingId, "owner");
28
+
29
+ const now = new Date().toISOString();
30
+ const db = getDb();
31
+ const [recording] = await db
32
+ .update(schema.recordings)
33
+ .set({ visibility: "private", updatedAt: now })
34
+ .where(
35
+ and(
36
+ eq(schema.recordings.id, recordingId),
37
+ notExists(
38
+ db
39
+ .select({ id: schema.recordingShares.id })
40
+ .from(schema.recordingShares)
41
+ .where(eq(schema.recordingShares.resourceId, recordingId)),
42
+ ),
43
+ ),
44
+ )
45
+ .returning({ id: schema.recordings.id });
46
+ if (!recording) {
47
+ // The conditional UPDATE is the invariant. This follow-up read only
48
+ // chooses the useful error message after the atomic mutation declined.
49
+ await assertNoDirectRecordingShares(recordingId);
50
+ throw new Error("This Clip is unavailable.");
51
+ }
52
+
53
+ return { recordingId, visibility: "private" as const };
54
+ },
55
+ });
@@ -1,10 +1,10 @@
1
1
  import { defineAction } from "@agent-native/core";
2
- import { queryScreenMemoryContext } from "@agent-native/core/mcp-client";
2
+ import { queryScreenMemoryForAgent } from "@agent-native/core/mcp-client";
3
3
  import { z } from "zod";
4
4
 
5
5
  export default defineAction({
6
6
  description:
7
- "Search recent local Clips Screen Memory context from this machine. Returns bounded OCR/context snippets only; use get-screen-memory-status first if availability is unclear.",
7
+ "Search bounded local Clips Screen Memory evidence from this machine. Returns typed app-context, OCR, or transcript excerpts only when present in local files, plus coverage, gaps, segment references, jump targets, and truncation; never media bytes or images. Use get-screen-memory-status first if availability is unclear.",
8
8
  schema: z.object({
9
9
  query: z
10
10
  .string()
@@ -21,9 +21,9 @@ export default defineAction({
21
21
  .min(1)
22
22
  .max(50)
23
23
  .default(10)
24
- .describe("Maximum number of context snippets to return"),
24
+ .describe("Maximum number of bounded local evidence items to return"),
25
25
  }),
26
26
  http: { method: "GET" },
27
27
  readOnly: true,
28
- run: async (args) => queryScreenMemoryContext(args),
28
+ run: async (args) => queryScreenMemoryForAgent(args),
29
29
  });
@@ -0,0 +1,81 @@
1
+ import { defineAction } from "@agent-native/core";
2
+ import {
3
+ readAppState,
4
+ writeAppState,
5
+ } from "@agent-native/core/application-state";
6
+ import { assertAccess } from "@agent-native/core/sharing";
7
+ import { eq } from "drizzle-orm";
8
+ import { z } from "zod";
9
+
10
+ import { isPrivateClip } from "../app/lib/rewind-visibility.js";
11
+ import { getDb, schema } from "../server/db/index.js";
12
+ import { nanoid } from "../server/lib/recordings.js";
13
+ import { assertNoDirectRecordingShares } from "./make-recording-private-for-rewind.js";
14
+
15
+ export const REWIND_EXTENSION_PREFIX = "rewind-extension-request-";
16
+
17
+ export interface RewindExtensionRequest {
18
+ [key: string]: unknown;
19
+ requestId: string;
20
+ recordingId: string;
21
+ seconds: 30 | 300;
22
+ status:
23
+ | "pending"
24
+ | "processing"
25
+ | "ready"
26
+ | "applying"
27
+ | "applied"
28
+ | "failed";
29
+ requestedAt: string;
30
+ updatedAt: string;
31
+ preRollRecordingId?: string;
32
+ actualDurationMs?: number;
33
+ error?: string;
34
+ }
35
+
36
+ export function rewindExtensionKey(recordingId: string): string {
37
+ return `${REWIND_EXTENSION_PREFIX}${recordingId}`;
38
+ }
39
+
40
+ export default defineAction({
41
+ description:
42
+ "Ask the signed-in Clips Alpha app to retrieve an explicit 30-second or five-minute pre-roll from local Rewind for one owned recording.",
43
+ schema: z.object({
44
+ recordingId: z.string(),
45
+ seconds: z.union([z.literal(30), z.literal(300)]),
46
+ }),
47
+ run: async ({ recordingId, seconds }) => {
48
+ await assertAccess("recording", recordingId, "owner");
49
+ const [recording] = await getDb()
50
+ .select({ visibility: schema.recordings.visibility })
51
+ .from(schema.recordings)
52
+ .where(eq(schema.recordings.id, recordingId));
53
+ if (!recording || !isPrivateClip(recording.visibility)) {
54
+ throw new Error(
55
+ "Make this Clip private before adding local Rewind history.",
56
+ );
57
+ }
58
+ await assertNoDirectRecordingShares(recordingId);
59
+ const key = rewindExtensionKey(recordingId);
60
+ const existing = (await readAppState(key)) as RewindExtensionRequest | null;
61
+ if (existing && !["failed", "applied"].includes(existing.status)) {
62
+ return existing;
63
+ }
64
+ if (existing?.status === "applied") {
65
+ throw new Error(
66
+ "This Clip already includes an explicit Rewind pre-roll.",
67
+ );
68
+ }
69
+ const now = new Date().toISOString();
70
+ const request: RewindExtensionRequest = {
71
+ requestId: `rewind-extend-${nanoid()}`,
72
+ recordingId,
73
+ seconds,
74
+ status: "pending",
75
+ requestedAt: now,
76
+ updatedAt: now,
77
+ };
78
+ await writeAppState(key, request);
79
+ return request;
80
+ },
81
+ });
@@ -0,0 +1,47 @@
1
+ import { defineAction } from "@agent-native/core";
2
+ import {
3
+ readAppState,
4
+ writeAppState,
5
+ } from "@agent-native/core/application-state";
6
+ import { assertAccess } from "@agent-native/core/sharing";
7
+ import { z } from "zod";
8
+
9
+ import {
10
+ rewindExtensionKey,
11
+ type RewindExtensionRequest,
12
+ } from "./request-rewind-extension.js";
13
+
14
+ export default defineAction({
15
+ description:
16
+ "Update the local Clips Alpha processing state for an explicit Rewind pre-roll request.",
17
+ schema: z.object({
18
+ recordingId: z.string(),
19
+ requestId: z.string(),
20
+ status: z.enum(["processing", "ready", "failed"]),
21
+ preRollRecordingId: z.string().optional(),
22
+ actualDurationMs: z.number().int().positive().optional(),
23
+ error: z.string().max(1200).optional(),
24
+ }),
25
+ run: async (args) => {
26
+ await assertAccess("recording", args.recordingId, "owner");
27
+ const key = rewindExtensionKey(args.recordingId);
28
+ const current = (await readAppState(key)) as RewindExtensionRequest | null;
29
+ if (!current || current.requestId !== args.requestId) {
30
+ throw new Error("Rewind extension request is no longer current.");
31
+ }
32
+ const next: RewindExtensionRequest = {
33
+ ...current,
34
+ status: args.status,
35
+ updatedAt: new Date().toISOString(),
36
+ ...(args.preRollRecordingId
37
+ ? { preRollRecordingId: args.preRollRecordingId }
38
+ : {}),
39
+ ...(args.actualDurationMs
40
+ ? { actualDurationMs: args.actualDurationMs }
41
+ : {}),
42
+ ...(args.error ? { error: args.error } : {}),
43
+ };
44
+ await writeAppState(key, next);
45
+ return next;
46
+ },
47
+ });
@@ -49,6 +49,7 @@ import {
49
49
  readPlaybackSpeedPreference,
50
50
  savePlaybackSpeedPreference,
51
51
  } from "@/lib/playback-speed";
52
+ import { canOfferRewindHistory } from "@/lib/rewind-visibility";
52
53
  import {
53
54
  parseEdits,
54
55
  getExcludedRanges,
@@ -62,6 +63,7 @@ import { computePeaks, type WaveformPeaks } from "@/lib/waveform-peaks";
62
63
  import { ChaptersEditor } from "./chapters-editor";
63
64
  import { defaultSelectionRange } from "./editor-selection";
64
65
  import { EditorToolbar } from "./editor-toolbar";
66
+ import { RewindExtensionDialog } from "./rewind-extension-dialog";
65
67
  import { StitchManager } from "./stitch-manager";
66
68
  import { ThumbnailPicker } from "./thumbnail-picker";
67
69
  import { Timeline } from "./timeline";
@@ -219,6 +221,7 @@ export function EditorLayout({ recordingId, className }: EditorLayoutProps) {
219
221
 
220
222
  const [thumbOpen, setThumbOpen] = useState(false);
221
223
  const [stitchOpen, setStitchOpen] = useState(false);
224
+ const [rewindOpen, setRewindOpen] = useState(false);
222
225
  const [chaptersOpen, setChaptersOpen] = useState(false);
223
226
 
224
227
  const containerRef = useRef<HTMLDivElement | null>(null);
@@ -636,6 +639,10 @@ export function EditorLayout({ recordingId, className }: EditorLayoutProps) {
636
639
  onOpenThumbnailPicker={() => setThumbOpen(true)}
637
640
  onOpenChapters={() => setChaptersOpen((v) => !v)}
638
641
  onOpenStitch={() => setStitchOpen(true)}
642
+ onOpenRewind={() => setRewindOpen(true)}
643
+ rewindAlreadyAdded={Boolean(edits.rewindOriginalStartMs)}
644
+ rewindAvailable={canOfferRewindHistory(playerData?.role)}
645
+ rewindRequiresPrivate={recording?.visibility !== "private"}
639
646
  chaptersOpen={chaptersOpen}
640
647
  />
641
648
 
@@ -739,6 +746,7 @@ export function EditorLayout({ recordingId, className }: EditorLayoutProps) {
739
746
  playheadMs={playheadMs}
740
747
  chapters={chapters}
741
748
  splitPoints={splitPoints}
749
+ originalStartMs={edits.rewindOriginalStartMs}
742
750
  onSeek={seek}
743
751
  onClickChapter={(c) => seek(c.startMs)}
744
752
  />
@@ -787,6 +795,23 @@ export function EditorLayout({ recordingId, className }: EditorLayoutProps) {
787
795
  onOpenChange={setStitchOpen}
788
796
  seedRecordingId={recordingId}
789
797
  />
798
+ {canOfferRewindHistory(playerData?.role) ? (
799
+ <RewindExtensionDialog
800
+ open={rewindOpen}
801
+ onOpenChange={setRewindOpen}
802
+ recordingId={recordingId}
803
+ durationMs={durationMs}
804
+ videoFormat={videoFormat}
805
+ hasAudio={Boolean(recording.hasAudio)}
806
+ visibility={recording.visibility}
807
+ onVisibilityChanged={async () => {
808
+ await playerDataQuery.refetch();
809
+ }}
810
+ onApplied={async () => {
811
+ await playerDataQuery.refetch();
812
+ }}
813
+ />
814
+ ) : null}
790
815
  </div>
791
816
  );
792
817
  }