@agent-native/core 0.101.2 → 0.101.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +25 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/client/RunStuckBanner.tsx +12 -5
- package/corpus/core/src/client/sse-event-processor.ts +36 -1
- package/corpus/core/src/client/use-run-stuck-detection.ts +162 -25
- package/corpus/core/src/credentials/index.ts +41 -8
- package/corpus/core/src/deploy/build.ts +78 -25
- package/corpus/core/src/extensions/fetch-tool.ts +33 -3
- package/corpus/core/src/notifications/channels.ts +20 -6
- package/corpus/core/src/secrets/crypto.ts +26 -10
- package/corpus/core/src/secrets/storage.ts +16 -3
- package/corpus/core/src/server/agent-chat-plugin.ts +60 -7
- package/corpus/core/src/server/derived-secret.ts +5 -1
- package/corpus/templates/analytics/changelog/2026-07-14-long-running-cross-app-analyses-now-continue-reliably-in-the.md +6 -0
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +8 -4
- package/corpus/templates/chat/changelog/2026-07-14-chat-opens-reliably-on-hosted-deployments-instead-of-failing.md +6 -0
- package/corpus/templates/clips/app/components/player/media-duration.ts +24 -0
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +44 -81
- package/corpus/templates/clips/app/components/player/video-player.tsx +9 -18
- package/corpus/templates/clips/app/i18n/ar-SA.ts +0 -8
- package/corpus/templates/clips/app/i18n/de-DE.ts +0 -9
- package/corpus/templates/clips/app/i18n/en-US.ts +0 -8
- package/corpus/templates/clips/app/i18n/es-ES.ts +0 -9
- package/corpus/templates/clips/app/i18n/fr-FR.ts +0 -9
- package/corpus/templates/clips/app/i18n/hi-IN.ts +0 -8
- package/corpus/templates/clips/app/i18n/ja-JP.ts +0 -9
- package/corpus/templates/clips/app/i18n/ko-KR.ts +0 -9
- package/corpus/templates/clips/app/i18n/pt-BR.ts +0 -9
- package/corpus/templates/clips/app/i18n/zh-CN.ts +0 -7
- package/corpus/templates/clips/app/i18n/zh-TW.ts +0 -7
- package/corpus/templates/clips/changelog/2026-07-14-paused-time-no-longer-counts-toward-chrome-extension-recordi.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-14-share-dialogs-show-separate-links-for-people-and-agents.md +6 -0
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +30 -4
- package/corpus/templates/clips/chrome-extension/src/recording-duration.ts +32 -0
- package/corpus/templates/dispatch/changelog/2026-07-14-dispatch-now-distinguishes-connected-agents-from-locally-mou.md +6 -0
- package/corpus/templates/slides/actions/_uploaded-files.ts +13 -2
- package/corpus/templates/slides/actions/import-docx.ts +3 -9
- package/corpus/templates/slides/actions/import-file.ts +11 -14
- package/corpus/templates/slides/actions/import-pptx.ts +3 -9
- package/corpus/templates/slides/changelog/2026-07-14-powerpoint-template-uploads-now-work-in-hosted-slides-deploy.md +6 -0
- package/corpus/templates/slides/server/handlers/uploads.ts +100 -50
- package/corpus/templates/slides/server/lib/tenant-files.ts +38 -11
- package/corpus/templates/slides/server/lib/uploaded-reference-storage.ts +108 -0
- package/dist/client/RunStuckBanner.d.ts.map +1 -1
- package/dist/client/RunStuckBanner.js +9 -5
- package/dist/client/RunStuckBanner.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +33 -1
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/client/use-run-stuck-detection.d.ts +4 -4
- package/dist/client/use-run-stuck-detection.d.ts.map +1 -1
- package/dist/client/use-run-stuck-detection.js +104 -19
- package/dist/client/use-run-stuck-detection.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/credentials/index.d.ts +12 -7
- package/dist/credentials/index.d.ts.map +1 -1
- package/dist/credentials/index.js +33 -8
- package/dist/credentials/index.js.map +1 -1
- package/dist/deploy/build.d.ts +15 -14
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +64 -23
- package/dist/deploy/build.js.map +1 -1
- package/dist/extensions/fetch-tool.d.ts +13 -1
- package/dist/extensions/fetch-tool.d.ts.map +1 -1
- package/dist/extensions/fetch-tool.js +9 -2
- package/dist/extensions/fetch-tool.js.map +1 -1
- package/dist/notifications/channels.js +20 -4
- package/dist/notifications/channels.js.map +1 -1
- package/dist/observability/routes.d.ts +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/crypto.d.ts +15 -7
- package/dist/secrets/crypto.d.ts.map +1 -1
- package/dist/secrets/crypto.js +23 -10
- package/dist/secrets/crypto.js.map +1 -1
- package/dist/secrets/storage.d.ts.map +1 -1
- package/dist/secrets/storage.js +16 -3
- package/dist/secrets/storage.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts +20 -0
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +38 -4
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/derived-secret.d.ts +1 -1
- package/dist/server/derived-secret.d.ts.map +1 -1
- package/dist/server/derived-secret.js.map +1 -1
- package/package.json +2 -2
- package/corpus/templates/clips/app/components/sharing/slack-share-hint.tsx +0 -68
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
useState,
|
|
20
20
|
} from "react";
|
|
21
21
|
|
|
22
|
+
import { resolveMediaDurationMs } from "@/components/player/media-duration";
|
|
22
23
|
import {
|
|
23
24
|
DropdownMenu,
|
|
24
25
|
DropdownMenuContent,
|
|
@@ -824,22 +825,16 @@ export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
|
|
|
824
825
|
durationProbedRef.current = false;
|
|
825
826
|
}, [activeVideoSrc, durationMs]);
|
|
826
827
|
|
|
827
|
-
//
|
|
828
|
-
//
|
|
829
|
-
//
|
|
830
|
-
//
|
|
831
|
-
// shorter length than the actual recording on first watch.
|
|
832
|
-
const hasReliableDurationProp =
|
|
833
|
-
Number.isFinite(durationMs) && durationMs > 0;
|
|
834
|
-
|
|
828
|
+
// Prefer the recorder's elapsed-time counter for ordinary WebM timeslice
|
|
829
|
+
// drift, but let clearly different playable-media metadata win. This also
|
|
830
|
+
// repairs playback controls for older clips whose stored duration counted
|
|
831
|
+
// time spent paused.
|
|
835
832
|
const probeDurationIfNeeded = useCallback(
|
|
836
833
|
(v: HTMLVideoElement) => {
|
|
837
834
|
if (durationProbedRef.current) return;
|
|
838
835
|
if (Number.isFinite(v.duration) && v.duration > 0) {
|
|
839
836
|
durationProbedRef.current = true;
|
|
840
|
-
|
|
841
|
-
setResolvedDurationMs(Math.round(v.duration * 1000));
|
|
842
|
-
}
|
|
837
|
+
setResolvedDurationMs(resolveMediaDurationMs(durationMs, v.duration));
|
|
843
838
|
return;
|
|
844
839
|
}
|
|
845
840
|
if (playAttemptPendingRef.current || !v.paused) return;
|
|
@@ -855,7 +850,7 @@ export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
|
|
|
855
850
|
// picks up the real duration.
|
|
856
851
|
}
|
|
857
852
|
},
|
|
858
|
-
[
|
|
853
|
+
[durationMs],
|
|
859
854
|
);
|
|
860
855
|
|
|
861
856
|
// Resolve the WebM-duration-is-Infinity Chrome quirk: when a video created
|
|
@@ -873,11 +868,7 @@ export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
|
|
|
873
868
|
|
|
874
869
|
const onDurationChange = () => {
|
|
875
870
|
if (Number.isFinite(v.duration) && v.duration > 0) {
|
|
876
|
-
|
|
877
|
-
// cluster-estimated WebM duration; only adopt it as a fallback.
|
|
878
|
-
if (!hasReliableDurationProp) {
|
|
879
|
-
setResolvedDurationMs(Math.round(v.duration * 1000));
|
|
880
|
-
}
|
|
871
|
+
setResolvedDurationMs(resolveMediaDurationMs(durationMs, v.duration));
|
|
881
872
|
// After we've resolved the real duration, rewind back to 0 so the
|
|
882
873
|
// user isn't sitting at the end of the clip.
|
|
883
874
|
if (durationProbedRef.current && v.currentTime > v.duration) {
|
|
@@ -900,7 +891,7 @@ export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
|
|
|
900
891
|
v.removeEventListener("loadedmetadata", onLoadedMetadata);
|
|
901
892
|
v.removeEventListener("durationchange", onDurationChange);
|
|
902
893
|
};
|
|
903
|
-
}, [activeVideoSrc,
|
|
894
|
+
}, [activeVideoSrc, durationMs, probeDurationIfNeeded]);
|
|
904
895
|
|
|
905
896
|
// Reset the thumbnail-capture flag when the source changes (e.g. the
|
|
906
897
|
// player is reused for a different recording via React Router).
|
|
@@ -887,14 +887,6 @@ const messages = {
|
|
|
887
887
|
"سيفقد {{email}} الوصول إلى هذه المؤسسة. يمكنك دائمًا دعوته مرة أخرى.",
|
|
888
888
|
remove: "إزالة",
|
|
889
889
|
},
|
|
890
|
-
slackShareHint: {
|
|
891
|
-
playsInline: "يعمل داخل Slack",
|
|
892
|
-
connectedDescription:
|
|
893
|
-
"الصق هذا الرابط في أي مساحة عمل متصلة لتشغيله داخلها.",
|
|
894
|
-
makeInline: "اجعله يعمل داخل Slack",
|
|
895
|
-
connectDescription: "قم بتوصيل مساحة عمل حتى يظهر هذا الرابط كفيديو.",
|
|
896
|
-
connect: "اتصال",
|
|
897
|
-
},
|
|
898
890
|
commentsPanel: {
|
|
899
891
|
disabled: "تم تعطيل التعليقات لهذا التسجيل.",
|
|
900
892
|
beFirst: "كن أول من يعلق",
|
|
@@ -911,15 +911,6 @@ Alle sichtbaren Änderungen für Clips-Nutzer werden hier dokumentiert. Du kanns
|
|
|
911
911
|
"{{email}} verliert den Zugriff auf diese Organisation. Du kannst die Person jederzeit erneut einladen.",
|
|
912
912
|
remove: "Entfernen",
|
|
913
913
|
},
|
|
914
|
-
slackShareHint: {
|
|
915
|
-
playsInline: "Wird inline in Slack abgespielt",
|
|
916
|
-
connectedDescription:
|
|
917
|
-
"Füge diesen Link in einem verbundenen Workspace ein, um ihn inline abzuspielen.",
|
|
918
|
-
makeInline: "Inline-Wiedergabe in Slack aktivieren",
|
|
919
|
-
connectDescription:
|
|
920
|
-
"Verbinde einen Workspace, damit dieser Link als Video angezeigt wird.",
|
|
921
|
-
connect: "Verbinden",
|
|
922
|
-
},
|
|
923
914
|
commentsPanel: {
|
|
924
915
|
disabled: "Kommentare sind für diese Aufzeichnung deaktiviert.",
|
|
925
916
|
beFirst: "Schreibe den ersten Kommentar",
|
|
@@ -882,14 +882,6 @@ All notable user-facing changes to Clips are documented here. Open it any time f
|
|
|
882
882
|
"{{email}} will lose access to this organization. You can always invite them back.",
|
|
883
883
|
remove: "Remove",
|
|
884
884
|
},
|
|
885
|
-
slackShareHint: {
|
|
886
|
-
playsInline: "Plays inline in Slack",
|
|
887
|
-
connectedDescription:
|
|
888
|
-
"Paste this link in any connected workspace to play it inline.",
|
|
889
|
-
makeInline: "Make it play inline in Slack",
|
|
890
|
-
connectDescription: "Connect a workspace so this link unfurls as a video.",
|
|
891
|
-
connect: "Connect",
|
|
892
|
-
},
|
|
893
885
|
commentsPanel: {
|
|
894
886
|
disabled: "Comments are disabled for this recording.",
|
|
895
887
|
beFirst: "Be the first to comment",
|
|
@@ -904,15 +904,6 @@ Todos los cambios visibles para los usuarios de Clips se documentan aquí. Puede
|
|
|
904
904
|
"{{email}} perderá acceso a esta organización. Siempre puedes volver a invitarlo.",
|
|
905
905
|
remove: "Eliminar",
|
|
906
906
|
},
|
|
907
|
-
slackShareHint: {
|
|
908
|
-
playsInline: "Se reproduce inline en Slack",
|
|
909
|
-
connectedDescription:
|
|
910
|
-
"Pega este enlace en cualquier espacio conectado para reproducirlo inline.",
|
|
911
|
-
makeInline: "Haz que se reproduzca inline en Slack",
|
|
912
|
-
connectDescription:
|
|
913
|
-
"Conecta un espacio para que este enlace se despliegue como video.",
|
|
914
|
-
connect: "Conectar",
|
|
915
|
-
},
|
|
916
907
|
commentsPanel: {
|
|
917
908
|
disabled: "Los comentarios están desactivados para esta grabación.",
|
|
918
909
|
beFirst: "Sé la primera persona en comentar",
|
|
@@ -906,15 +906,6 @@ Tous les changements visibles par les utilisateurs de Clips sont documentés ici
|
|
|
906
906
|
"{{email}} perdra l’accès à cette organisation. Vous pourrez toujours l’inviter à nouveau.",
|
|
907
907
|
remove: "Retirer",
|
|
908
908
|
},
|
|
909
|
-
slackShareHint: {
|
|
910
|
-
playsInline: "Lecture inline dans Slack",
|
|
911
|
-
connectedDescription:
|
|
912
|
-
"Collez ce lien dans n’importe quel espace connecté pour le lire inline.",
|
|
913
|
-
makeInline: "Le lire inline dans Slack",
|
|
914
|
-
connectDescription:
|
|
915
|
-
"Connectez un espace pour que ce lien se déplie en vidéo.",
|
|
916
|
-
connect: "Connecter",
|
|
917
|
-
},
|
|
918
909
|
commentsPanel: {
|
|
919
910
|
disabled: "Les commentaires sont désactivés pour cet enregistrement.",
|
|
920
911
|
beFirst: "Soyez le premier à commenter",
|
|
@@ -876,14 +876,6 @@ Clips में उपयोगकर्ताओं को दिखने व
|
|
|
876
876
|
"{{email}} इस संगठन तक पहुंच खो देगा। आप उन्हें हमेशा वापस आमंत्रित कर सकते हैं।",
|
|
877
877
|
remove: "हटाएं",
|
|
878
878
|
},
|
|
879
|
-
slackShareHint: {
|
|
880
|
-
playsInline: "Slack में इनलाइन चलता है",
|
|
881
|
-
connectedDescription:
|
|
882
|
-
"इसे इनलाइन चलाने के लिए यह लिंक किसी भी जुड़े हुए वर्कस्पेस में पेस्ट करें।",
|
|
883
|
-
makeInline: "इसे Slack में इनलाइन चलाएं",
|
|
884
|
-
connectDescription: "एक वर्कस्पेस कनेक्ट करें ताकि यह लिंक वीडियो के रूप में खुले।",
|
|
885
|
-
connect: "कनेक्ट करें",
|
|
886
|
-
},
|
|
887
879
|
commentsPanel: {
|
|
888
880
|
disabled: "इस रिकॉर्डिंग के लिए टिप्पणियां बंद हैं।",
|
|
889
881
|
beFirst: "पहली टिप्पणी करें",
|
|
@@ -893,15 +893,6 @@ Clips のユーザー向けの主な変更はここに記録されます。コ
|
|
|
893
893
|
"{{email}} はこの組織へのアクセスを失います。いつでも再招待できます。",
|
|
894
894
|
remove: "削除",
|
|
895
895
|
},
|
|
896
|
-
slackShareHint: {
|
|
897
|
-
playsInline: "Slack 内でインライン再生",
|
|
898
|
-
connectedDescription:
|
|
899
|
-
"このリンクを接続済みワークスペースに貼り付けるとインライン再生されます。",
|
|
900
|
-
makeInline: "Slack でインライン再生する",
|
|
901
|
-
connectDescription:
|
|
902
|
-
"ワークスペースを接続すると、このリンクが動画として展開されます。",
|
|
903
|
-
connect: "接続",
|
|
904
|
-
},
|
|
905
896
|
commentsPanel: {
|
|
906
897
|
disabled: "この録画ではコメントが無効です。",
|
|
907
898
|
beFirst: "最初にコメントする",
|
|
@@ -883,15 +883,6 @@ Clips의 모든 사용자 대상 변경 사항은 여기에 기록됩니다. 명
|
|
|
883
883
|
"{{email}}은 이 조직에 대한 액세스 권한을 잃습니다. 언제든 다시 초대할 수 있습니다.",
|
|
884
884
|
remove: "제거",
|
|
885
885
|
},
|
|
886
|
-
slackShareHint: {
|
|
887
|
-
playsInline: "Slack에서 인라인 재생",
|
|
888
|
-
connectedDescription:
|
|
889
|
-
"이 링크를 연결된 워크스페이스에 붙여넣으면 인라인으로 재생됩니다.",
|
|
890
|
-
makeInline: "Slack에서 인라인 재생하기",
|
|
891
|
-
connectDescription:
|
|
892
|
-
"워크스페이스를 연결하면 이 링크가 동영상으로 펼쳐집니다.",
|
|
893
|
-
connect: "연결",
|
|
894
|
-
},
|
|
895
886
|
commentsPanel: {
|
|
896
887
|
disabled: "이 녹화에서는 댓글이 비활성화되어 있습니다.",
|
|
897
888
|
beFirst: "첫 댓글을 남겨보세요",
|
|
@@ -900,15 +900,6 @@ Todas as mudanças visíveis para usuários do Clips são documentadas aqui. Voc
|
|
|
900
900
|
"{{email}} perderá acesso a esta organização. Você sempre pode convidá-lo novamente.",
|
|
901
901
|
remove: "Remover",
|
|
902
902
|
},
|
|
903
|
-
slackShareHint: {
|
|
904
|
-
playsInline: "Reproduz inline no Slack",
|
|
905
|
-
connectedDescription:
|
|
906
|
-
"Cole este link em qualquer workspace conectado para reproduzi-lo inline.",
|
|
907
|
-
makeInline: "Faça reproduzir inline no Slack",
|
|
908
|
-
connectDescription:
|
|
909
|
-
"Conecte um workspace para que este link abra como vídeo.",
|
|
910
|
-
connect: "Conectar",
|
|
911
|
-
},
|
|
912
903
|
commentsPanel: {
|
|
913
904
|
disabled: "Os comentários estão desativados para esta gravação.",
|
|
914
905
|
beFirst: "Seja a primeira pessoa a comentar",
|
|
@@ -849,13 +849,6 @@ Clips 中所有面向用户的重要更改都会记录在这里。你可以随
|
|
|
849
849
|
"{{email}} 将失去对此组织的访问权限。你随时可以重新邀请他们。",
|
|
850
850
|
remove: "移除",
|
|
851
851
|
},
|
|
852
|
-
slackShareHint: {
|
|
853
|
-
playsInline: "可在 Slack 中内联播放",
|
|
854
|
-
connectedDescription: "将此链接粘贴到任何已连接工作区即可内联播放。",
|
|
855
|
-
makeInline: "让它在 Slack 中内联播放",
|
|
856
|
-
connectDescription: "连接一个工作区,让此链接展开为视频。",
|
|
857
|
-
connect: "连接",
|
|
858
|
-
},
|
|
859
852
|
commentsPanel: {
|
|
860
853
|
disabled: "此录制已禁用评论。",
|
|
861
854
|
beFirst: "成为第一个评论的人",
|
|
@@ -842,13 +842,6 @@ const messages = {
|
|
|
842
842
|
"{{email}} 將失去對此組織的存取權限。你隨時可以重新邀請他們。",
|
|
843
843
|
remove: "移除",
|
|
844
844
|
},
|
|
845
|
-
slackShareHint: {
|
|
846
|
-
playsInline: "可在 Slack 中行內播放",
|
|
847
|
-
connectedDescription: "將此連結貼上到任何已連線工作區即可行內播放。",
|
|
848
|
-
makeInline: "讓它在 Slack 中行內播放",
|
|
849
|
-
connectDescription: "連線一個工作區,讓此連結展開為影片。",
|
|
850
|
-
connect: "連線",
|
|
851
|
-
},
|
|
852
845
|
commentsPanel: {
|
|
853
846
|
disabled: "此錄製已停用評論。",
|
|
854
847
|
beFirst: "成為第一個評論的人",
|
|
@@ -36,6 +36,13 @@ import {
|
|
|
36
36
|
import { MAX_UPLOAD_BYTES } from "@shared/upload-limits";
|
|
37
37
|
|
|
38
38
|
import { waitForReadyRecordingAfterFinalizeError } from "./finalize-recovery";
|
|
39
|
+
import {
|
|
40
|
+
createRecordingDurationState,
|
|
41
|
+
pauseRecordingDuration,
|
|
42
|
+
resumeRecordingDuration,
|
|
43
|
+
startRecordingDuration,
|
|
44
|
+
type RecordingDurationState,
|
|
45
|
+
} from "./recording-duration";
|
|
39
46
|
import { captureExtensionError, initExtensionSentry } from "./sentry";
|
|
40
47
|
|
|
41
48
|
initExtensionSentry("offscreen");
|
|
@@ -187,6 +194,7 @@ type ActiveRecording = {
|
|
|
187
194
|
authToken: string | null;
|
|
188
195
|
mode: CaptureMode;
|
|
189
196
|
startedAtMs: number;
|
|
197
|
+
duration: RecordingDurationState;
|
|
190
198
|
mimeType: string;
|
|
191
199
|
recorder: MediaRecorder;
|
|
192
200
|
outputStream: MediaStream;
|
|
@@ -1218,6 +1226,7 @@ async function begin(message: BeginMessage): Promise<{
|
|
|
1218
1226
|
authToken: message.authToken ?? null,
|
|
1219
1227
|
mode: ready.mode,
|
|
1220
1228
|
startedAtMs: 0,
|
|
1229
|
+
duration: createRecordingDurationState(),
|
|
1221
1230
|
mimeType,
|
|
1222
1231
|
recorder,
|
|
1223
1232
|
outputStream,
|
|
@@ -1350,6 +1359,7 @@ function startRecorderNow(recording: ActiveRecording): void {
|
|
|
1350
1359
|
playStartChime();
|
|
1351
1360
|
recording.recorder.start(2000);
|
|
1352
1361
|
recording.startedAtMs = Date.now();
|
|
1362
|
+
recording.duration = startRecordingDuration(recording.startedAtMs);
|
|
1353
1363
|
console.log("[clips-offscreen] recorder.start ok");
|
|
1354
1364
|
reportStatus(recording.sessionId, "recording", {
|
|
1355
1365
|
recordingId: recording.recordingId,
|
|
@@ -1446,6 +1456,11 @@ async function finalizeStop(recording: ActiveRecording): Promise<void> {
|
|
|
1446
1456
|
recording.resolveStopped({ ok: true, status: "cancelled" });
|
|
1447
1457
|
return;
|
|
1448
1458
|
}
|
|
1459
|
+
// Freeze the media duration as soon as MediaRecorder stops. Waiting for
|
|
1460
|
+
// outstanding uploads below must not make the clip appear longer, and time
|
|
1461
|
+
// spent paused is excluded by pauseRecordingDuration().
|
|
1462
|
+
recording.duration = pauseRecordingDuration(recording.duration, Date.now());
|
|
1463
|
+
const durationMs = recording.duration.elapsedMs;
|
|
1449
1464
|
reportStatus(recording.sessionId, "uploading", {
|
|
1450
1465
|
recordingId: recording.recordingId,
|
|
1451
1466
|
});
|
|
@@ -1460,7 +1475,6 @@ async function finalizeStop(recording: ActiveRecording): Promise<void> {
|
|
|
1460
1475
|
? rejected.reason
|
|
1461
1476
|
: new Error(String(rejected.reason));
|
|
1462
1477
|
}
|
|
1463
|
-
const durationMs = Math.max(0, Date.now() - recording.startedAtMs);
|
|
1464
1478
|
// Surface WHY a finalize might fail before the server's cryptic "No chunks
|
|
1465
1479
|
// found": 0 chunks means the recorder emitted no non-empty data (empty
|
|
1466
1480
|
// capture / never started), which is a different problem than an auth 401.
|
|
@@ -1549,7 +1563,7 @@ async function finalizeStop(recording: ActiveRecording): Promise<void> {
|
|
|
1549
1563
|
recordingId: recording.recordingId,
|
|
1550
1564
|
chunkCount: recording.chunkIndex,
|
|
1551
1565
|
mimeType: recording.mimeType,
|
|
1552
|
-
durationMs
|
|
1566
|
+
durationMs,
|
|
1553
1567
|
},
|
|
1554
1568
|
});
|
|
1555
1569
|
// The upload failed — save the buffered recording to disk so it isn't lost.
|
|
@@ -1574,7 +1588,13 @@ async function finalizeStop(recording: ActiveRecording): Promise<void> {
|
|
|
1574
1588
|
function pause(message: SimpleMessage): { ok: boolean } {
|
|
1575
1589
|
const recording = activeRecording;
|
|
1576
1590
|
if (recording && recording.sessionId === message.sessionId) {
|
|
1577
|
-
if (recording.recorder.state === "recording")
|
|
1591
|
+
if (recording.recorder.state === "recording") {
|
|
1592
|
+
recording.recorder.pause();
|
|
1593
|
+
recording.duration = pauseRecordingDuration(
|
|
1594
|
+
recording.duration,
|
|
1595
|
+
Date.now(),
|
|
1596
|
+
);
|
|
1597
|
+
}
|
|
1578
1598
|
reportStatus(recording.sessionId, "paused", {
|
|
1579
1599
|
recordingId: recording.recordingId,
|
|
1580
1600
|
});
|
|
@@ -1585,7 +1605,13 @@ function pause(message: SimpleMessage): { ok: boolean } {
|
|
|
1585
1605
|
function resume(message: SimpleMessage): { ok: boolean } {
|
|
1586
1606
|
const recording = activeRecording;
|
|
1587
1607
|
if (recording && recording.sessionId === message.sessionId) {
|
|
1588
|
-
if (recording.recorder.state === "paused")
|
|
1608
|
+
if (recording.recorder.state === "paused") {
|
|
1609
|
+
recording.recorder.resume();
|
|
1610
|
+
recording.duration = resumeRecordingDuration(
|
|
1611
|
+
recording.duration,
|
|
1612
|
+
Date.now(),
|
|
1613
|
+
);
|
|
1614
|
+
}
|
|
1589
1615
|
reportStatus(recording.sessionId, "recording", {
|
|
1590
1616
|
recordingId: recording.recordingId,
|
|
1591
1617
|
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export type RecordingDurationState = {
|
|
2
|
+
elapsedMs: number;
|
|
3
|
+
activeSinceMs: number | null;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export function createRecordingDurationState(): RecordingDurationState {
|
|
7
|
+
return { elapsedMs: 0, activeSinceMs: null };
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function startRecordingDuration(nowMs: number): RecordingDurationState {
|
|
11
|
+
return { elapsedMs: 0, activeSinceMs: nowMs };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function pauseRecordingDuration(
|
|
15
|
+
state: RecordingDurationState,
|
|
16
|
+
nowMs: number,
|
|
17
|
+
): RecordingDurationState {
|
|
18
|
+
if (state.activeSinceMs === null) return state;
|
|
19
|
+
return {
|
|
20
|
+
elapsedMs:
|
|
21
|
+
state.elapsedMs + Math.max(0, Math.round(nowMs - state.activeSinceMs)),
|
|
22
|
+
activeSinceMs: null,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function resumeRecordingDuration(
|
|
27
|
+
state: RecordingDurationState,
|
|
28
|
+
nowMs: number,
|
|
29
|
+
): RecordingDurationState {
|
|
30
|
+
if (state.activeSinceMs !== null) return state;
|
|
31
|
+
return { ...state, activeSinceMs: nowMs };
|
|
32
|
+
}
|
|
@@ -4,11 +4,19 @@ import path from "path";
|
|
|
4
4
|
import { getRequestUserEmail } from "@agent-native/core/server/request-context";
|
|
5
5
|
|
|
6
6
|
import { tenantUploadDir } from "../server/lib/tenant-files.js";
|
|
7
|
+
import { readUploadedReferenceBlob } from "../server/lib/uploaded-reference-storage.js";
|
|
7
8
|
|
|
8
|
-
export function
|
|
9
|
+
export async function readUserUploadedFile(
|
|
10
|
+
filePath: string,
|
|
11
|
+
): Promise<{ data: Buffer; filename: string }> {
|
|
9
12
|
const email = getRequestUserEmail();
|
|
10
13
|
if (!email) throw new Error("no authenticated user");
|
|
11
14
|
|
|
15
|
+
const privateUpload = await readUploadedReferenceBlob(filePath, email);
|
|
16
|
+
if (privateUpload) {
|
|
17
|
+
return privateUpload;
|
|
18
|
+
}
|
|
19
|
+
|
|
12
20
|
const allowedDir = tenantUploadDir(email);
|
|
13
21
|
const absPath = path.isAbsolute(filePath)
|
|
14
22
|
? filePath
|
|
@@ -23,5 +31,8 @@ export function resolveUserUploadedFile(filePath: string): string {
|
|
|
23
31
|
if (!fs.existsSync(resolved)) {
|
|
24
32
|
throw new Error(`File not found: ${filePath}`);
|
|
25
33
|
}
|
|
26
|
-
return
|
|
34
|
+
return {
|
|
35
|
+
data: await fs.promises.readFile(resolved),
|
|
36
|
+
filename: path.basename(resolved),
|
|
37
|
+
};
|
|
27
38
|
}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
|
|
3
1
|
import { defineAction } from "@agent-native/core";
|
|
4
2
|
import { z } from "zod";
|
|
5
3
|
|
|
6
4
|
import { parseDocx } from "../server/handlers/import/docx-parser.js";
|
|
7
5
|
import { convertSectionsToSlides } from "../server/handlers/import/html-converter.js";
|
|
8
|
-
import {
|
|
6
|
+
import { readUserUploadedFile } from "./_uploaded-files.js";
|
|
9
7
|
|
|
10
8
|
export default defineAction({
|
|
11
9
|
description:
|
|
@@ -16,9 +14,7 @@ export default defineAction({
|
|
|
16
14
|
schema: z.object({
|
|
17
15
|
filePath: z
|
|
18
16
|
.string()
|
|
19
|
-
.describe(
|
|
20
|
-
"Server path to the uploaded DOCX file (e.g. data/uploads/document.docx)",
|
|
21
|
-
),
|
|
17
|
+
.describe("Uploaded DOCX path or opaque hosted upload reference"),
|
|
22
18
|
convertToSlides: z
|
|
23
19
|
.boolean()
|
|
24
20
|
.optional()
|
|
@@ -28,9 +24,7 @@ export default defineAction({
|
|
|
28
24
|
),
|
|
29
25
|
}),
|
|
30
26
|
run: async ({ filePath, convertToSlides }) => {
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
const fileBuffer = await fs.promises.readFile(absPath);
|
|
27
|
+
const { data: fileBuffer } = await readUserUploadedFile(filePath);
|
|
34
28
|
const doc = await parseDocx(fileBuffer);
|
|
35
29
|
|
|
36
30
|
const result: Record<string, unknown> = {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
1
|
import path from "path";
|
|
3
2
|
|
|
4
3
|
import { defineAction } from "@agent-native/core";
|
|
@@ -15,7 +14,7 @@ import { z } from "zod";
|
|
|
15
14
|
import { getDb, schema } from "../server/db/index.js";
|
|
16
15
|
import { notifyClients } from "../server/handlers/decks.js";
|
|
17
16
|
import { upsertBuilderProxyDesignSystem } from "../server/lib/builder-design-system-proxy.js";
|
|
18
|
-
import {
|
|
17
|
+
import { readUserUploadedFile } from "./_uploaded-files.js";
|
|
19
18
|
|
|
20
19
|
const DEFAULT_MAX_SOURCE_CHARS = 60_000;
|
|
21
20
|
|
|
@@ -30,9 +29,7 @@ export default defineAction({
|
|
|
30
29
|
schema: z.object({
|
|
31
30
|
filePath: z
|
|
32
31
|
.string()
|
|
33
|
-
.describe(
|
|
34
|
-
"Server path to the uploaded file (e.g. data/uploads/file.pptx)",
|
|
35
|
-
),
|
|
32
|
+
.describe("Uploaded file path or opaque hosted upload reference"),
|
|
36
33
|
format: z
|
|
37
34
|
.enum(["pptx", "docx", "pdf", "fig", "auto"])
|
|
38
35
|
.optional()
|
|
@@ -60,15 +57,15 @@ export default defineAction({
|
|
|
60
57
|
),
|
|
61
58
|
}),
|
|
62
59
|
run: async ({ filePath, format, deckId, importIntoDeck, maxChars }) => {
|
|
63
|
-
const
|
|
60
|
+
const uploaded = await readUserUploadedFile(filePath);
|
|
64
61
|
const sourceLimit = maxChars ?? DEFAULT_MAX_SOURCE_CHARS;
|
|
65
|
-
|
|
66
|
-
const
|
|
62
|
+
const fileBuffer = uploaded.data;
|
|
63
|
+
const filename = uploaded.filename;
|
|
67
64
|
|
|
68
65
|
// Detect format from extension if auto
|
|
69
66
|
let detectedFormat = format;
|
|
70
67
|
if (detectedFormat === "auto") {
|
|
71
|
-
const ext = path.extname(
|
|
68
|
+
const ext = path.extname(filename).toLowerCase();
|
|
72
69
|
if (ext === ".pptx") detectedFormat = "pptx";
|
|
73
70
|
else if (ext === ".docx") detectedFormat = "docx";
|
|
74
71
|
else if (ext === ".pdf") detectedFormat = "pdf";
|
|
@@ -86,12 +83,12 @@ export default defineAction({
|
|
|
86
83
|
"Figma .fig imports start Builder design-system indexing, not slide replacements. Re-run without importIntoDeck.",
|
|
87
84
|
);
|
|
88
85
|
}
|
|
89
|
-
const title = titleFromPath(
|
|
86
|
+
const title = titleFromPath(filename);
|
|
90
87
|
const result = await startBuilderDesignSystemIndex({
|
|
91
88
|
projectName: title,
|
|
92
89
|
files: [
|
|
93
90
|
{
|
|
94
|
-
name: path.basename(
|
|
91
|
+
name: path.basename(filename),
|
|
95
92
|
data: fileBuffer,
|
|
96
93
|
mimeType: "application/octet-stream",
|
|
97
94
|
},
|
|
@@ -126,7 +123,7 @@ export default defineAction({
|
|
|
126
123
|
const { convertToSlideHtml } =
|
|
127
124
|
await import("../server/handlers/import/html-converter.js");
|
|
128
125
|
const presentation = await parsePptx(fileBuffer);
|
|
129
|
-
const title = presentation.title || titleFromPath(
|
|
126
|
+
const title = presentation.title || titleFromPath(filename);
|
|
130
127
|
|
|
131
128
|
if (importIntoDeck) {
|
|
132
129
|
if (!deckId) throw new Error("deckId is required to import into deck");
|
|
@@ -172,7 +169,7 @@ export default defineAction({
|
|
|
172
169
|
await import("../server/handlers/import/html-converter.js");
|
|
173
170
|
const doc = await parseDocx(fileBuffer);
|
|
174
171
|
const slideHtmlArray = convertSectionsToSlides(doc.sections);
|
|
175
|
-
const title = doc.title || titleFromPath(
|
|
172
|
+
const title = doc.title || titleFromPath(filename);
|
|
176
173
|
|
|
177
174
|
if (importIntoDeck) {
|
|
178
175
|
if (!deckId) throw new Error("deckId is required to import into deck");
|
|
@@ -221,7 +218,7 @@ export default defineAction({
|
|
|
221
218
|
const result = await pdf.getText();
|
|
222
219
|
const pages = normalizePdfPages(result);
|
|
223
220
|
const textPages = pages.filter((p) => p.text.trim());
|
|
224
|
-
const title = titleFromPath(
|
|
221
|
+
const title = titleFromPath(filename);
|
|
225
222
|
|
|
226
223
|
if (textPages.length === 0) {
|
|
227
224
|
throw new Error(
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
|
|
3
1
|
import { defineAction } from "@agent-native/core";
|
|
4
2
|
import { writeAppState } from "@agent-native/core/application-state";
|
|
5
3
|
import {
|
|
@@ -15,7 +13,7 @@ import { notifyClients } from "../server/handlers/decks.js";
|
|
|
15
13
|
import { convertToSlideHtml } from "../server/handlers/import/html-converter.js";
|
|
16
14
|
import { parsePptx } from "../server/handlers/import/pptx-parser.js";
|
|
17
15
|
import { getDeckUrl } from "./_app-url.js";
|
|
18
|
-
import {
|
|
16
|
+
import { readUserUploadedFile } from "./_uploaded-files.js";
|
|
19
17
|
|
|
20
18
|
export default defineAction({
|
|
21
19
|
description:
|
|
@@ -26,9 +24,7 @@ export default defineAction({
|
|
|
26
24
|
schema: z.object({
|
|
27
25
|
filePath: z
|
|
28
26
|
.string()
|
|
29
|
-
.describe(
|
|
30
|
-
"Server path to the uploaded PPTX file (e.g. data/uploads/presentation.pptx)",
|
|
31
|
-
),
|
|
27
|
+
.describe("Uploaded PPTX path or opaque hosted upload reference"),
|
|
32
28
|
deckId: z
|
|
33
29
|
.string()
|
|
34
30
|
.optional()
|
|
@@ -43,9 +39,7 @@ export default defineAction({
|
|
|
43
39
|
),
|
|
44
40
|
}),
|
|
45
41
|
run: async ({ filePath, deckId, title }) => {
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
const fileBuffer = await fs.promises.readFile(absPath);
|
|
42
|
+
const { data: fileBuffer } = await readUserUploadedFile(filePath);
|
|
49
43
|
const presentation = await parsePptx(fileBuffer);
|
|
50
44
|
|
|
51
45
|
const deckTitle = title || presentation.title || "Imported Presentation";
|