@agent-native/core 0.114.6 → 0.114.8

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 (155) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +15 -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/mcp/screen-memory-stdio.ts +1360 -104
  11. package/corpus/core/src/mcp-client/index.ts +9 -0
  12. package/corpus/core/src/mcp-client/screen-memory-local.ts +847 -13
  13. package/corpus/core/src/server/agent-chat-plugin.ts +16 -9
  14. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +19 -7
  15. package/corpus/templates/analytics/app/hooks/use-chart-tooltip-portal.ts +60 -0
  16. package/corpus/templates/analytics/app/routes/chart.tsx +1 -1
  17. package/corpus/templates/analytics/changelog/2026-07-20-dashboard-requests-no-longer-stall-without-progress.md +6 -0
  18. package/corpus/templates/analytics/server/plugins/agent-chat.ts +6 -0
  19. package/corpus/templates/clips/actions/apply-rewind-extension.ts +195 -0
  20. package/corpus/templates/clips/actions/delete-agent-recording-if-unpromoted.ts +64 -0
  21. package/corpus/templates/clips/actions/get-rewind-extension-request.ts +22 -0
  22. package/corpus/templates/clips/actions/get-screen-memory-status.ts +18 -2
  23. package/corpus/templates/clips/actions/list-rewind-extension-requests.ts +52 -0
  24. package/corpus/templates/clips/actions/make-recording-private-for-rewind.ts +55 -0
  25. package/corpus/templates/clips/actions/query-screen-memory-context.ts +4 -4
  26. package/corpus/templates/clips/actions/request-rewind-extension.ts +81 -0
  27. package/corpus/templates/clips/actions/update-rewind-extension-request.ts +47 -0
  28. package/corpus/templates/clips/app/components/editor/editor-layout.tsx +25 -0
  29. package/corpus/templates/clips/app/components/editor/editor-toolbar.tsx +36 -0
  30. package/corpus/templates/clips/app/components/editor/rewind-extension-dialog.tsx +338 -0
  31. package/corpus/templates/clips/app/components/editor/stitch-manager.tsx +1 -16
  32. package/corpus/templates/clips/app/components/editor/timeline.tsx +26 -0
  33. package/corpus/templates/clips/app/i18n/en-US.ts +18 -0
  34. package/corpus/templates/clips/app/lib/ffmpeg-export.ts +19 -12
  35. package/corpus/templates/clips/app/lib/rewind-visibility.ts +31 -0
  36. package/corpus/templates/clips/app/lib/timestamp-mapping.ts +7 -0
  37. package/corpus/templates/clips/app/lib/upload-file-client.ts +17 -0
  38. package/corpus/templates/clips/changelog/2026-07-14-rewind-keeps-recent-screen-context-local-and-turns-it-into-clips.md +6 -0
  39. package/corpus/templates/clips/changelog/2026-07-17-active-clips-keep-rewind-stable-and-settings-clear.md +12 -0
  40. package/corpus/templates/clips/changelog/2026-07-17-clicking-the-clips-icon-during-a-recording-now-opens-the-app.md +6 -0
  41. package/corpus/templates/clips/changelog/2026-07-17-default-mic-now-uses-the-mac-default-and-transcription-shares-one-capture.md +6 -0
  42. package/corpus/templates/clips/changelog/2026-07-17-full-screen-clips-finalize-even-when-screencapturekit-stop-hangs.md +6 -0
  43. package/corpus/templates/clips/changelog/2026-07-17-normal-full-screen-clips-no-longer-stop-immediately-after-th.md +6 -0
  44. package/corpus/templates/clips/changelog/2026-07-17-rewind-can-hand-one-bounded-private-clip-to-your-agent.md +12 -0
  45. package/corpus/templates/clips/changelog/2026-07-17-rewind-home-can-copy-a-local-agent-prompt.md +6 -0
  46. package/corpus/templates/clips/changelog/2026-07-17-rewind-now-has-a-calm-home-status-focused-first-run-consent-.md +6 -0
  47. package/corpus/templates/clips/changelog/2026-07-17-rewind-settings-are-smaller-clearer-and-agent-first.md +6 -0
  48. package/corpus/templates/clips/changelog/2026-07-17-the-full-clips-interface-stays-available-while-recording.md +6 -0
  49. package/corpus/templates/clips/changelog/2026-07-18-rewind-organizes-recent-work-into-local-searchable-chapters.md +8 -0
  50. package/corpus/templates/clips/changelog/2026-07-18-searchable-rewind-chapters.md +6 -0
  51. package/corpus/templates/clips/changelog/2026-07-19-rewind-backed-recordings-now-start-immediately-after-the-cou.md +6 -0
  52. package/corpus/templates/clips/changelog/2026-07-20-rewind-can-set-up-your-local-agent-once-so-future-requests-w.md +6 -0
  53. package/corpus/templates/clips/desktop/package.json +1 -0
  54. package/corpus/templates/clips/desktop/scripts/sign-macos-local.ts +12 -1
  55. package/corpus/templates/clips/desktop/src/app.tsx +2498 -239
  56. package/corpus/templates/clips/desktop/src/components/ReadinessPanel.tsx +21 -1
  57. package/corpus/templates/clips/desktop/src/components/Switch.tsx +3 -0
  58. package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +156 -24
  59. package/corpus/templates/clips/desktop/src/lib/native-recording-warm.ts +6 -7
  60. package/corpus/templates/clips/desktop/src/lib/recorder.ts +1763 -1160
  61. package/corpus/templates/clips/desktop/src/lib/rewind-agent-prompt.ts +9 -0
  62. package/corpus/templates/clips/desktop/src/lib/rewind-recording-start.ts +20 -0
  63. package/corpus/templates/clips/desktop/src/main.tsx +3 -0
  64. package/corpus/templates/clips/desktop/src/overlays/countdown.tsx +14 -14
  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/toolkit/CHANGELOG.md +6 -0
  99. package/corpus/toolkit/package.json +1 -1
  100. package/dist/cli/mcp-config-writers.d.ts +2 -2
  101. package/dist/cli/mcp-config-writers.d.ts.map +1 -1
  102. package/dist/cli/mcp-config-writers.js +6 -6
  103. package/dist/cli/mcp-config-writers.js.map +1 -1
  104. package/dist/cli/mcp.d.ts +14 -0
  105. package/dist/cli/mcp.d.ts.map +1 -1
  106. package/dist/cli/mcp.js +79 -0
  107. package/dist/cli/mcp.js.map +1 -1
  108. package/dist/cli/skills-content/help.d.ts +1 -1
  109. package/dist/cli/skills-content/help.d.ts.map +1 -1
  110. package/dist/cli/skills-content/help.js +4 -3
  111. package/dist/cli/skills-content/help.js.map +1 -1
  112. package/dist/cli/skills-content/index.d.ts +1 -0
  113. package/dist/cli/skills-content/index.d.ts.map +1 -1
  114. package/dist/cli/skills-content/index.js +1 -0
  115. package/dist/cli/skills-content/index.js.map +1 -1
  116. package/dist/cli/skills-content/rewind-skill.d.ts +2 -0
  117. package/dist/cli/skills-content/rewind-skill.d.ts.map +1 -0
  118. package/dist/cli/skills-content/rewind-skill.js +53 -0
  119. package/dist/cli/skills-content/rewind-skill.js.map +1 -0
  120. package/dist/cli/skills.d.ts +6 -0
  121. package/dist/cli/skills.d.ts.map +1 -1
  122. package/dist/cli/skills.js +90 -14
  123. package/dist/cli/skills.js.map +1 -1
  124. package/dist/collab/struct-routes.d.ts +1 -1
  125. package/dist/mcp/screen-memory-stdio.d.ts +365 -0
  126. package/dist/mcp/screen-memory-stdio.d.ts.map +1 -1
  127. package/dist/mcp/screen-memory-stdio.js +1014 -91
  128. package/dist/mcp/screen-memory-stdio.js.map +1 -1
  129. package/dist/mcp-client/index.d.ts +1 -1
  130. package/dist/mcp-client/index.d.ts.map +1 -1
  131. package/dist/mcp-client/index.js +1 -1
  132. package/dist/mcp-client/index.js.map +1 -1
  133. package/dist/mcp-client/screen-memory-local.d.ts +90 -0
  134. package/dist/mcp-client/screen-memory-local.d.ts.map +1 -1
  135. package/dist/mcp-client/screen-memory-local.js +590 -12
  136. package/dist/mcp-client/screen-memory-local.js.map +1 -1
  137. package/dist/observability/routes.d.ts +1 -1
  138. package/dist/progress/routes.d.ts +1 -1
  139. package/dist/provider-api/actions/provider-api.d.ts +4 -4
  140. package/dist/resources/handlers.d.ts +1 -1
  141. package/dist/server/agent-chat-plugin.d.ts +5 -0
  142. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  143. package/dist/server/agent-chat-plugin.js +10 -9
  144. package/dist/server/agent-chat-plugin.js.map +1 -1
  145. package/package.json +2 -2
  146. package/src/cli/mcp-config-writers.ts +6 -4
  147. package/src/cli/mcp.ts +124 -0
  148. package/src/cli/skills-content/help.ts +4 -3
  149. package/src/cli/skills-content/index.ts +1 -0
  150. package/src/cli/skills-content/rewind-skill.ts +52 -0
  151. package/src/cli/skills.ts +115 -13
  152. package/src/mcp/screen-memory-stdio.ts +1360 -104
  153. package/src/mcp-client/index.ts +9 -0
  154. package/src/mcp-client/screen-memory-local.ts +847 -13
  155. package/src/server/agent-chat-plugin.ts +16 -9
@@ -332,6 +332,19 @@ export { loadRunCodeToolEntries };
332
332
  export { shouldDisableRecurringJobsRuntime };
333
333
  export { finalizeClaimedAgentChatProcessRunFailure };
334
334
 
335
+ export function resolveInteractiveAgentRunOptions(
336
+ options?: Pick<
337
+ AgentChatPluginOptions,
338
+ "runSoftTimeoutMs" | "runNoProgressTimeoutMs" | "durableBackgroundRuns"
339
+ >,
340
+ ) {
341
+ return {
342
+ runSoftTimeoutMs: options?.runSoftTimeoutMs,
343
+ runNoProgressTimeoutMs: options?.runNoProgressTimeoutMs,
344
+ durableBackgroundRuns: options?.durableBackgroundRuns,
345
+ };
346
+ }
347
+
335
348
  export function createSerializedA2ATaskStatusWriter(
336
349
  taskId: string,
337
350
  writeStatus: (
@@ -2909,9 +2922,7 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
2909
2922
  model: options?.model,
2910
2923
  appId: options?.appId,
2911
2924
  apiKey: options?.apiKey,
2912
- runSoftTimeoutMs: options?.runSoftTimeoutMs,
2913
- runNoProgressTimeoutMs: options?.runNoProgressTimeoutMs,
2914
- durableBackgroundRuns: options?.durableBackgroundRuns,
2925
+ ...resolveInteractiveAgentRunOptions(options),
2915
2926
  finalResponseGuard: options?.finalResponseGuard,
2916
2927
  prepareRequest: async (details) => {
2917
2928
  if (details.threadId && details.ownerEmail) {
@@ -3017,9 +3028,7 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
3017
3028
  model: options?.model,
3018
3029
  appId: options?.appId,
3019
3030
  apiKey: options?.apiKey,
3020
- runSoftTimeoutMs: options?.runSoftTimeoutMs,
3021
- runNoProgressTimeoutMs: options?.runNoProgressTimeoutMs,
3022
- durableBackgroundRuns: options?.durableBackgroundRuns,
3031
+ ...resolveInteractiveAgentRunOptions(options),
3023
3032
  finalResponseGuard: options?.finalResponseGuard,
3024
3033
  prepareRequest: options?.prepareRequest,
3025
3034
  skipFilesContext: true,
@@ -3176,9 +3185,7 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
3176
3185
  model: options?.model,
3177
3186
  appId: options?.appId,
3178
3187
  apiKey: options?.apiKey,
3179
- runSoftTimeoutMs: options?.runSoftTimeoutMs,
3180
- runNoProgressTimeoutMs: options?.runNoProgressTimeoutMs,
3181
- durableBackgroundRuns: options?.durableBackgroundRuns,
3188
+ ...resolveInteractiveAgentRunOptions(options),
3182
3189
  finalResponseGuard: options?.finalResponseGuard,
3183
3190
  prepareRequest: async (details) => {
3184
3191
  if (details.threadId && details.ownerEmail) {
@@ -26,6 +26,7 @@ import {
26
26
  type CSSProperties,
27
27
  type ReactNode,
28
28
  } from "react";
29
+ import { createPortal } from "react-dom";
29
30
  import { Link } from "react-router";
30
31
  import {
31
32
  Area,
@@ -60,7 +61,7 @@ import {
60
61
  SelectValue,
61
62
  } from "@/components/ui/select";
62
63
  import { Skeleton } from "@/components/ui/skeleton";
63
- import { useChartTooltipFlip } from "@/hooks/use-chart-tooltip-flip";
64
+ import { useChartTooltipPortalPosition } from "@/hooks/use-chart-tooltip-portal";
64
65
 
65
66
  const PAGE_SIZE_OPTIONS = [10, 25, 50, 100];
66
67
  import { createDemoChartTrendRows } from "@/lib/demo-chart-trend";
@@ -86,7 +87,7 @@ const DEFAULT_COLORS = [
86
87
  ];
87
88
 
88
89
  const CHART_TOOLTIP_WRAPPER_STYLE: CSSProperties = {
89
- zIndex: 60,
90
+ zIndex: 9999,
90
91
  pointerEvents: "none",
91
92
  };
92
93
 
@@ -897,6 +898,7 @@ export function ChartTooltip({
897
898
  active,
898
899
  payload,
899
900
  label,
901
+ coordinate,
900
902
  labelFormatter,
901
903
  seriesNameFormatter,
902
904
  valueFormatter,
@@ -909,11 +911,11 @@ export function ChartTooltip({
909
911
  value?: unknown;
910
912
  }>;
911
913
  label?: unknown;
914
+ coordinate?: { x?: number; y?: number };
912
915
  labelFormatter?: (value: string) => string;
913
916
  seriesNameFormatter?: (value: string) => string;
914
917
  valueFormatter?: (value: number) => string;
915
918
  }) {
916
- const tooltipRef = useChartTooltipFlip<HTMLDivElement>(active);
917
919
  const items = useMemo(
918
920
  () =>
919
921
  sortTooltipPayloadItems(
@@ -922,6 +924,11 @@ export function ChartTooltip({
922
924
  ),
923
925
  [payload],
924
926
  );
927
+ const isVisible = Boolean(active) && items.length > 0;
928
+ const { anchorRef, boxRef } = useChartTooltipPortalPosition(
929
+ isVisible,
930
+ coordinate,
931
+ );
925
932
 
926
933
  const labelText =
927
934
  label == null
@@ -930,13 +937,13 @@ export function ChartTooltip({
930
937
  ? labelFormatter(String(label))
931
938
  : String(label);
932
939
 
933
- if (!active || items.length === 0) return null;
940
+ if (!isVisible) return null;
934
941
 
935
942
  const tooltip = (
936
943
  <div
937
- ref={tooltipRef}
944
+ ref={boxRef}
938
945
  role="tooltip"
939
- className="min-w-40 max-w-[280px] rounded-md border border-border bg-card px-3 py-2 text-xs text-foreground shadow-lg"
946
+ className="fixed z-[9999] min-w-40 max-w-[280px] rounded-md border border-border bg-card px-3 py-2 text-xs text-foreground shadow-lg pointer-events-none"
940
947
  >
941
948
  {labelText && (
942
949
  <div className="mb-1.5 truncate font-medium text-foreground">
@@ -971,7 +978,12 @@ export function ChartTooltip({
971
978
  </div>
972
979
  );
973
980
 
974
- return tooltip;
981
+ return (
982
+ <>
983
+ <span ref={anchorRef} aria-hidden="true" />
984
+ {createPortal(tooltip, document.body)}
985
+ </>
986
+ );
975
987
  }
976
988
 
977
989
  function detectKeys(
@@ -0,0 +1,60 @@
1
+ import { useLayoutEffect, useRef } from "react";
2
+
3
+ const CHART_EDGE_PADDING = 8;
4
+ const CURSOR_OFFSET = 14;
5
+
6
+ type TooltipCoordinate = { x?: number; y?: number } | undefined;
7
+
8
+ /**
9
+ * Ancestors of a dashboard chart (the scrollable app shell, the dashboard
10
+ * grid's inline-size container) clip any content that overflows their box,
11
+ * so the tooltip content is rendered through a portal to `document.body`.
12
+ * Recharts positions its own tooltip wrapper by measuring that wrapper's
13
+ * child content size — with the real content portaled away the wrapper has
14
+ * no size to measure and never gets a transform, so we can't read a live
15
+ * position off it. Instead, position the portaled box ourselves from the
16
+ * `coordinate` Recharts already passes to custom tooltip content (the exact
17
+ * pixel the cursor is over, relative to the chart) plus the chart's own
18
+ * `.recharts-wrapper` rect, and clamp against that same rect so the tooltip
19
+ * tracks the cursor but never crosses the chart's own edges.
20
+ */
21
+ export function useChartTooltipPortalPosition(
22
+ isVisible: boolean,
23
+ coordinate: TooltipCoordinate,
24
+ ) {
25
+ const anchorRef = useRef<HTMLSpanElement | null>(null);
26
+ const boxRef = useRef<HTMLDivElement | null>(null);
27
+
28
+ useLayoutEffect(() => {
29
+ if (!isVisible) return;
30
+ const anchor = anchorRef.current;
31
+ const box = boxRef.current;
32
+ if (!anchor || !box) return;
33
+ const chartEl = anchor.closest(".recharts-wrapper");
34
+ if (!chartEl) return;
35
+
36
+ const chartRect = chartEl.getBoundingClientRect();
37
+ const boxRect = box.getBoundingClientRect();
38
+
39
+ const pointX = chartRect.left + (coordinate?.x ?? 0);
40
+ const pointY = chartRect.top + (coordinate?.y ?? 0);
41
+
42
+ const minLeft = chartRect.left + CHART_EDGE_PADDING;
43
+ const maxLeft = chartRect.right - CHART_EDGE_PADDING - boxRect.width;
44
+ let left = pointX + CURSOR_OFFSET;
45
+ if (left > maxLeft) left = pointX - CURSOR_OFFSET - boxRect.width;
46
+ left = Math.min(Math.max(left, minLeft), Math.max(minLeft, maxLeft));
47
+
48
+ const minTop = chartRect.top + CHART_EDGE_PADDING;
49
+ const maxTop = chartRect.bottom - CHART_EDGE_PADDING - boxRect.height;
50
+ const top = Math.min(
51
+ Math.max(pointY - boxRect.height / 2, minTop),
52
+ Math.max(minTop, maxTop),
53
+ );
54
+
55
+ box.style.left = `${left}px`;
56
+ box.style.top = `${top}px`;
57
+ }, [isVisible, coordinate?.x, coordinate?.y]);
58
+
59
+ return { anchorRef, boxRef };
60
+ }
@@ -105,7 +105,7 @@ export default function ChartRoute() {
105
105
  }
106
106
 
107
107
  return (
108
- <div className="flex h-screen w-screen flex-col overflow-hidden bg-transparent p-2">
108
+ <div className="flex h-screen w-screen flex-col overflow-visible bg-transparent p-2">
109
109
  {result.title && (
110
110
  <div className="mb-1 px-1 text-xs font-medium text-muted-foreground">
111
111
  {result.title}
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-20
4
+ ---
5
+
6
+ Fixed Analytics dashboard requests that stall without visible progress.
@@ -34,6 +34,11 @@ import {
34
34
  } from "../lib/real-data-actions";
35
35
 
36
36
  const ANALYTICS_BACKGROUND_RUN_SOFT_TIMEOUT_MS = 13 * 60_000;
37
+ // A background job may legitimately spend minutes inside a provider/tool call,
38
+ // which the shared watchdog already excludes. Outside a tool call, however,
39
+ // silence means the model transport or worker has wedged; recover the chunk
40
+ // promptly instead of holding the dashboard composer for the 12-minute default.
41
+ export const ANALYTICS_BACKGROUND_RUN_NO_PROGRESS_TIMEOUT_MS = 3 * 60_000;
37
42
 
38
43
  const ANALYTICS_DATA_SOURCES_LINK = buildDeepLink({
39
44
  app: "analytics",
@@ -684,6 +689,7 @@ export default createAgentChatPlugin({
684
689
  // standard serverless request budget without orphaning the task.
685
690
  durableBackgroundRuns: true,
686
691
  runSoftTimeoutMs: ANALYTICS_BACKGROUND_RUN_SOFT_TIMEOUT_MS,
692
+ runNoProgressTimeoutMs: ANALYTICS_BACKGROUND_RUN_NO_PROGRESS_TIMEOUT_MS,
687
693
  connectorCatalog: [...ANALYTICS_CONNECTOR_CATALOG],
688
694
  externalAgents: {
689
695
  // Keep the direct MCP surface deliberately curated. External agents
@@ -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
  });