@agent-native/core 0.84.55 → 0.84.57
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 +12 -0
- package/corpus/core/docs/content/locales/ar-SA/template-analytics.mdx +2 -0
- package/corpus/core/docs/content/locales/ar-SA/template-clips.mdx +2 -0
- package/corpus/core/docs/content/locales/de-DE/template-analytics.mdx +2 -0
- package/corpus/core/docs/content/locales/de-DE/template-clips.mdx +2 -0
- package/corpus/core/docs/content/locales/es-ES/template-analytics.mdx +2 -0
- package/corpus/core/docs/content/locales/es-ES/template-clips.mdx +2 -0
- package/corpus/core/docs/content/locales/fr-FR/template-analytics.mdx +2 -0
- package/corpus/core/docs/content/locales/fr-FR/template-clips.mdx +2 -0
- package/corpus/core/docs/content/locales/hi-IN/template-analytics.mdx +2 -0
- package/corpus/core/docs/content/locales/hi-IN/template-clips.mdx +2 -0
- package/corpus/core/docs/content/locales/ja-JP/template-analytics.mdx +2 -0
- package/corpus/core/docs/content/locales/ja-JP/template-clips.mdx +2 -0
- package/corpus/core/docs/content/locales/ko-KR/template-analytics.mdx +2 -0
- package/corpus/core/docs/content/locales/ko-KR/template-clips.mdx +2 -0
- package/corpus/core/docs/content/locales/pt-BR/template-analytics.mdx +2 -0
- package/corpus/core/docs/content/locales/pt-BR/template-clips.mdx +2 -0
- package/corpus/core/docs/content/locales/zh-CN/template-analytics.mdx +2 -0
- package/corpus/core/docs/content/locales/zh-CN/template-clips.mdx +2 -0
- package/corpus/core/docs/content/locales/zh-TW/template-analytics.mdx +2 -0
- package/corpus/core/docs/content/locales/zh-TW/template-clips.mdx +2 -0
- package/corpus/core/docs/content/template-analytics.mdx +2 -0
- package/corpus/core/docs/content/template-clips.mdx +22 -10
- package/corpus/core/docs/content/tracking.mdx +2 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/cli/recap.ts +146 -5
- package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +8 -0
- package/corpus/templates/analytics/AGENTS.md +5 -0
- package/corpus/templates/analytics/actions/create-session-replay-agent-link.ts +31 -0
- package/corpus/templates/analytics/app/i18n/zh-TW.ts +2 -0
- package/corpus/templates/analytics/app/i18n-data.ts +20 -0
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +65 -3
- package/corpus/templates/analytics/changelog/2026-07-02-session-replays-can-be-copied-as-temporary-private-links-for-agents.md +6 -0
- package/corpus/templates/analytics/server/handlers/session-replay.ts +78 -0
- package/corpus/templates/analytics/server/lib/session-replay-agent-context.ts +313 -0
- package/corpus/templates/analytics/server/lib/session-replay.ts +198 -3
- package/corpus/templates/analytics/server/routes/[...page].get.ts +104 -1
- package/corpus/templates/analytics/server/routes/api/session-replay/agent-context.json.get.ts +48 -0
- package/corpus/templates/analytics/server/routes/api/session-replay/agent-events.json.get.ts +63 -0
- package/corpus/templates/analytics/shared/session-replay-agent-access.ts +10 -0
- package/corpus/templates/clips/.agents/skills/video-sharing/SKILL.md +14 -8
- package/corpus/templates/clips/AGENTS.md +3 -2
- package/corpus/templates/clips/actions/create-recording-agent-link.ts +90 -0
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +43 -46
- package/corpus/templates/clips/app/components/sharing/share-ui.tsx +5 -3
- package/corpus/templates/clips/app/i18n/ar-SA.ts +1 -1
- package/corpus/templates/clips/app/i18n/de-DE.ts +1 -1
- package/corpus/templates/clips/app/i18n/en-US.ts +1 -1
- package/corpus/templates/clips/app/i18n/es-ES.ts +1 -1
- package/corpus/templates/clips/app/i18n/fr-FR.ts +1 -1
- package/corpus/templates/clips/app/i18n/hi-IN.ts +1 -1
- package/corpus/templates/clips/app/i18n/ja-JP.ts +1 -1
- package/corpus/templates/clips/app/i18n/ko-KR.ts +1 -1
- package/corpus/templates/clips/app/i18n/pt-BR.ts +1 -1
- package/corpus/templates/clips/app/i18n/zh-CN.ts +1 -1
- package/corpus/templates/clips/app/i18n/zh-TW.ts +1 -1
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +100 -46
- package/corpus/templates/clips/changelog/2026-07-02-private-recordings-can-be-shared-with-agents-through-temporary-links-without-making-them-public.md +6 -0
- package/corpus/templates/clips/server/lib/public-agent-context.ts +26 -13
- package/corpus/templates/clips/server/routes/api/agent-context.json.get.ts +2 -1
- package/corpus/templates/clips/server/routes/api/agent-frame.jpg.get.ts +2 -1
- package/corpus/templates/clips/server/routes/api/agent-transcript.json.get.ts +2 -1
- package/corpus/templates/clips/server/routes/api/public-recording.get.ts +7 -2
- package/corpus/templates/clips/shared/agent-context.ts +5 -0
- package/corpus/templates/plan/actions/create-visual-recap.ts +39 -0
- package/corpus/templates/plan/actions/list-visual-plans.ts +2 -0
- package/corpus/templates/plan/actions/view-screen.ts +2 -0
- package/corpus/templates/plan/app/pages/PlansPage.tsx +279 -114
- package/corpus/templates/plan/changelog/2026-07-02-plan-comment-shortcuts-now-open-comment-mode-from-the-keyboa.md +6 -0
- package/corpus/templates/plan/server/db/schema.ts +3 -0
- package/corpus/templates/plan/server/lib/comment-notifications.ts +21 -2
- package/corpus/templates/plan/server/plans.ts +6 -0
- package/corpus/templates/plan/server/plugins/db.ts +11 -0
- package/corpus/templates/plan/shared/comment-context.ts +1 -0
- package/corpus/templates/plan/shared/types.ts +4 -0
- package/dist/cli/recap.d.ts +14 -0
- package/dist/cli/recap.d.ts.map +1 -1
- package/dist/cli/recap.js +88 -5
- package/dist/cli/recap.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +4 -4
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/docs/content/locales/ar-SA/template-analytics.mdx +2 -0
- package/docs/content/locales/ar-SA/template-clips.mdx +2 -0
- package/docs/content/locales/de-DE/template-analytics.mdx +2 -0
- package/docs/content/locales/de-DE/template-clips.mdx +2 -0
- package/docs/content/locales/es-ES/template-analytics.mdx +2 -0
- package/docs/content/locales/es-ES/template-clips.mdx +2 -0
- package/docs/content/locales/fr-FR/template-analytics.mdx +2 -0
- package/docs/content/locales/fr-FR/template-clips.mdx +2 -0
- package/docs/content/locales/hi-IN/template-analytics.mdx +2 -0
- package/docs/content/locales/hi-IN/template-clips.mdx +2 -0
- package/docs/content/locales/ja-JP/template-analytics.mdx +2 -0
- package/docs/content/locales/ja-JP/template-clips.mdx +2 -0
- package/docs/content/locales/ko-KR/template-analytics.mdx +2 -0
- package/docs/content/locales/ko-KR/template-clips.mdx +2 -0
- package/docs/content/locales/pt-BR/template-analytics.mdx +2 -0
- package/docs/content/locales/pt-BR/template-clips.mdx +2 -0
- package/docs/content/locales/zh-CN/template-analytics.mdx +2 -0
- package/docs/content/locales/zh-CN/template-clips.mdx +2 -0
- package/docs/content/locales/zh-TW/template-analytics.mdx +2 -0
- package/docs/content/locales/zh-TW/template-clips.mdx +2 -0
- package/docs/content/template-analytics.mdx +2 -0
- package/docs/content/template-clips.mdx +22 -10
- package/docs/content/tracking.mdx +2 -0
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
|
-
appBasePath,
|
|
3
2
|
appPath,
|
|
3
|
+
useActionMutation,
|
|
4
4
|
useActionQuery,
|
|
5
5
|
useSession,
|
|
6
6
|
useT,
|
|
@@ -38,7 +38,6 @@ import {
|
|
|
38
38
|
import { Switch } from "@/components/ui/switch";
|
|
39
39
|
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
|
|
40
40
|
|
|
41
|
-
import { buildAgentApiUrls } from "../../../shared/agent-context";
|
|
42
41
|
import { isLoomEmbedUrl } from "../../../shared/loom";
|
|
43
42
|
import { withShareAttribution } from "../../../shared/share-attribution";
|
|
44
43
|
|
|
@@ -284,46 +283,27 @@ function LinkTab({
|
|
|
284
283
|
(data?.visibility as Visibility | null) ?? "private";
|
|
285
284
|
const isPublic = visibility === "public";
|
|
286
285
|
const isLoomRecording = isLoomRecordingProp || isLoomEmbedUrl(videoUrl);
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
origin: window.location.origin,
|
|
292
|
-
basePath: appBasePath(),
|
|
293
|
-
}).contextUrl;
|
|
294
|
-
const [tokenizedAgentContextUrl, setTokenizedAgentContextUrl] = useState("");
|
|
286
|
+
const createAgentLink = useActionMutation(
|
|
287
|
+
"create-recording-agent-link" as any,
|
|
288
|
+
);
|
|
289
|
+
const [agentContextUrl, setAgentContextUrl] = useState("");
|
|
295
290
|
|
|
296
291
|
useEffect(() => {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
? payload.apis.context.url
|
|
313
|
-
: "";
|
|
314
|
-
if (!cancelled) setTokenizedAgentContextUrl(contextUrl);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
void loadTokenizedAgentContextUrl();
|
|
318
|
-
return () => {
|
|
319
|
-
cancelled = true;
|
|
320
|
-
};
|
|
321
|
-
}, [hasPassword, isPublic, publicAgentContextUrl]);
|
|
322
|
-
|
|
323
|
-
const agentContextUrl = hasPassword
|
|
324
|
-
? tokenizedAgentContextUrl
|
|
325
|
-
: publicAgentContextUrl;
|
|
326
|
-
const agentShareDisabled = isPending || !isPublic || !agentContextUrl;
|
|
292
|
+
setAgentContextUrl("");
|
|
293
|
+
}, [recordingId, visibility]);
|
|
294
|
+
|
|
295
|
+
async function handleCreateAgentLink() {
|
|
296
|
+
if (createAgentLink.isPending) return;
|
|
297
|
+
const result = (await createAgentLink.mutateAsync({
|
|
298
|
+
recordingId,
|
|
299
|
+
})) as { url?: string };
|
|
300
|
+
if (!result?.url) return;
|
|
301
|
+
setAgentContextUrl(result.url);
|
|
302
|
+
copyToClipboard(result.url);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const agentShareDisabled =
|
|
306
|
+
isPending || createAgentLink.isPending || !agentContextUrl;
|
|
327
307
|
const agentPrompt = agentContextUrl
|
|
328
308
|
? t("shareDialog.agentPrompt", { agentContextUrl })
|
|
329
309
|
: "";
|
|
@@ -348,11 +328,28 @@ function LinkTab({
|
|
|
348
328
|
(and a connect link) instead of leaving it buried in Settings. */}
|
|
349
329
|
{isPublic ? <SlackShareHint canManage={canManage} /> : null}
|
|
350
330
|
|
|
351
|
-
<
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
331
|
+
<div className="space-y-2">
|
|
332
|
+
<div className="flex items-end justify-between gap-2">
|
|
333
|
+
<div className="text-xs font-medium text-muted-foreground">
|
|
334
|
+
{t("shareDialog.shareWithAgents")}
|
|
335
|
+
</div>
|
|
336
|
+
<Button
|
|
337
|
+
type="button"
|
|
338
|
+
variant="outline"
|
|
339
|
+
size="sm"
|
|
340
|
+
className="h-7"
|
|
341
|
+
onClick={() => void handleCreateAgentLink()}
|
|
342
|
+
disabled={isPending || createAgentLink.isPending}
|
|
343
|
+
>
|
|
344
|
+
{t("shareUi.copy")}
|
|
345
|
+
</Button>
|
|
346
|
+
</div>
|
|
347
|
+
<CopyField
|
|
348
|
+
label=""
|
|
349
|
+
value={agentContextUrl}
|
|
350
|
+
disabled={agentShareDisabled}
|
|
351
|
+
/>
|
|
352
|
+
</div>
|
|
356
353
|
|
|
357
354
|
<CopyField
|
|
358
355
|
label={t("shareDialog.copyAgentPrompt")}
|
|
@@ -360,7 +357,7 @@ function LinkTab({
|
|
|
360
357
|
disabled={agentShareDisabled}
|
|
361
358
|
/>
|
|
362
359
|
|
|
363
|
-
{
|
|
360
|
+
{agentContextUrl || hasPassword || !isPublic ? (
|
|
364
361
|
<p className="text-xs text-muted-foreground">
|
|
365
362
|
{t("shareDialog.agentTokenDescription")}
|
|
366
363
|
</p>
|
|
@@ -270,9 +270,11 @@ export function CopyField({
|
|
|
270
270
|
};
|
|
271
271
|
return (
|
|
272
272
|
<div>
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
273
|
+
{label ? (
|
|
274
|
+
<div className="mb-1 text-xs font-medium text-muted-foreground">
|
|
275
|
+
{label}
|
|
276
|
+
</div>
|
|
277
|
+
) : null}
|
|
276
278
|
<div className="flex items-stretch gap-2">
|
|
277
279
|
<Input
|
|
278
280
|
readOnly
|
|
@@ -419,7 +419,7 @@ const messages = {
|
|
|
419
419
|
agentPrompt:
|
|
420
420
|
"اجلب عنوان URL لسياق وكيل Clips هذا: {{agentContextUrl}}. استخدم transcript.segments للسياق المنطوق، واجلب recommendedFrames أو عناوين URL الخاصة بواجهة API للإطارات لرؤية الشاشة، وتحقق من browserDiagnostics إن وجدت لسجلات وحدة التحكم المنقحة وبيانات طلبات fetch/XHR الوصفية.",
|
|
421
421
|
agentTokenDescription:
|
|
422
|
-
"
|
|
422
|
+
"يسمح URL المؤقت هذا للوكلاء بقراءة المقطع دون جعله عاما. تنتهي صلاحيته بعد ساعتين.",
|
|
423
423
|
gifPreview: "معاينة GIF",
|
|
424
424
|
openPlayer: "مشغل مفتوح",
|
|
425
425
|
downloadMp4: "تحميل MP4",
|
|
@@ -436,7 +436,7 @@ const messages = {
|
|
|
436
436
|
agentPrompt:
|
|
437
437
|
"Rufe diese Clips-Agent-Kontext-URL ab: {{agentContextUrl}}. Verwende transcript.segments fuer den gesprochenen Kontext, rufe recommendedFrames oder die Frame-API-URLs ab, um den Bildschirm zu sehen, und pruefe browserDiagnostics, falls vorhanden, fuer redigierte Konsolenprotokolle und fetch/XHR-Anfragemetadaten.",
|
|
438
438
|
agentTokenDescription:
|
|
439
|
-
"
|
|
439
|
+
"Diese temporare Agent-URL lasst Agenten den Clip lesen, ohne ihn offentlich zu machen. Sie lauft nach zwei Stunden ab.",
|
|
440
440
|
gifPreview: "GIF-Vorschau",
|
|
441
441
|
openPlayer: "Spieler öffnen",
|
|
442
442
|
downloadMp4: "Laden Sie MP4 herunter",
|
|
@@ -419,7 +419,7 @@ const messages = {
|
|
|
419
419
|
agentPrompt:
|
|
420
420
|
"Fetch this Clips agent context URL: {{agentContextUrl}}. Use transcript.segments for spoken context, fetch recommendedFrames or the frame API URLs to see the screen, and check browserDiagnostics if present for redacted console logs and fetch/XHR request metadata.",
|
|
421
421
|
agentTokenDescription:
|
|
422
|
-
"This agent URL
|
|
422
|
+
"This temporary agent URL lets agents read the clip without making it public. It expires after two hours.",
|
|
423
423
|
gifPreview: "GIF preview",
|
|
424
424
|
openPlayer: "Open player",
|
|
425
425
|
downloadMp4: "Download MP4",
|
|
@@ -431,7 +431,7 @@ const messages = {
|
|
|
431
431
|
agentPrompt:
|
|
432
432
|
"Obtén esta URL de contexto para agentes de Clips: {{agentContextUrl}}. Usa transcript.segments para el contexto hablado, obtén recommendedFrames o las URLs de la API de fotogramas para ver la pantalla y revisa browserDiagnostics si está presente para ver registros de consola redactados y metadatos de solicitudes fetch/XHR.",
|
|
433
433
|
agentTokenDescription:
|
|
434
|
-
"
|
|
434
|
+
"Esta URL temporal para agentes permite leer el clip sin hacerlo publico. Caduca en dos horas.",
|
|
435
435
|
gifPreview: "vista previa de GIF",
|
|
436
436
|
openPlayer: "jugador abierto",
|
|
437
437
|
downloadMp4: "Descargar MP4",
|
|
@@ -431,7 +431,7 @@ const messages = {
|
|
|
431
431
|
agentPrompt:
|
|
432
432
|
"Récupère cette URL de contexte Clips pour agent : {{agentContextUrl}}. Utilise transcript.segments pour le contexte parlé, récupère recommendedFrames ou les URLs de l'API d'images pour voir l'écran, et consulte browserDiagnostics s'il est présent pour les journaux de console expurgés et les métadonnées de requêtes fetch/XHR.",
|
|
433
433
|
agentTokenDescription:
|
|
434
|
-
"
|
|
434
|
+
"Cette URL temporaire pour agents permet de lire le clip sans le rendre public. Elle expire dans deux heures.",
|
|
435
435
|
gifPreview: "aperçu de GIF",
|
|
436
436
|
openPlayer: "Joueur ouvert",
|
|
437
437
|
downloadMp4: "Télécharger MP4",
|
|
@@ -416,7 +416,7 @@ const messages = {
|
|
|
416
416
|
agentPrompt:
|
|
417
417
|
"यह Clips एजेंट संदर्भ URL प्राप्त करें: {{agentContextUrl}}। बोले गए संदर्भ के लिए transcript.segments का उपयोग करें, स्क्रीन देखने के लिए recommendedFrames या फ्रेम API URL प्राप्त करें, और यदि browserDiagnostics मौजूद हो तो संशोधित कंसोल लॉग और fetch/XHR अनुरोध मेटाडेटा जांचें।",
|
|
418
418
|
agentTokenDescription:
|
|
419
|
-
"यह एजेंट URL
|
|
419
|
+
"यह अस्थायी एजेंट URL क्लिप को सार्वजनिक किए बिना एजेंटों को इसे पढ़ने देता है। यह दो घंटे बाद समाप्त हो जाता है।",
|
|
420
420
|
gifPreview: "GIF पूर्वावलोकन",
|
|
421
421
|
openPlayer: "खुला खिलाड़ी",
|
|
422
422
|
downloadMp4: "MP4 डाउनलोड करें",
|
|
@@ -428,7 +428,7 @@ const messages = {
|
|
|
428
428
|
agentPrompt:
|
|
429
429
|
"この Clips エージェントコンテキスト URL を取得してください: {{agentContextUrl}}。音声の文脈には transcript.segments を使い、画面を見るために recommendedFrames またはフレーム API URL を取得し、browserDiagnostics がある場合は、編集済みのコンソールログと fetch/XHR リクエストのメタデータを確認してください。",
|
|
430
430
|
agentTokenDescription:
|
|
431
|
-
"
|
|
431
|
+
"この一時的なエージェント URL により、クリップを公開せずにエージェントが読み取れます。2 時間後に期限切れになります。",
|
|
432
432
|
gifPreview: "GIF プレビュー",
|
|
433
433
|
openPlayer: "プレーヤーを開く",
|
|
434
434
|
downloadMp4: "ダウンロード",
|
|
@@ -421,7 +421,7 @@ const messages = {
|
|
|
421
421
|
agentPrompt:
|
|
422
422
|
"이 Clips 에이전트 컨텍스트 URL을 가져오세요: {{agentContextUrl}}. 말한 내용의 맥락은 transcript.segments를 사용하고, 화면을 보기 위해 recommendedFrames 또는 프레임 API URL을 가져오며, browserDiagnostics가 있으면 수정된 콘솔 로그와 fetch/XHR 요청 메타데이터를 확인하세요.",
|
|
423
423
|
agentTokenDescription:
|
|
424
|
-
"이 에이전트 URL
|
|
424
|
+
"이 임시 에이전트 URL을 사용하면 클립을 공개하지 않고도 에이전트가 읽을 수 있습니다. 2시간 후 만료됩니다.",
|
|
425
425
|
gifPreview: "GIF 미리보기",
|
|
426
426
|
openPlayer: "플레이어 열기",
|
|
427
427
|
downloadMp4: "MP4 다운로드",
|
|
@@ -429,7 +429,7 @@ const messages = {
|
|
|
429
429
|
agentPrompt:
|
|
430
430
|
"Busque esta URL de contexto para agentes do Clips: {{agentContextUrl}}. Use transcript.segments para o contexto falado, busque recommendedFrames ou as URLs da API de quadros para ver a tela e confira browserDiagnostics, se presente, para logs de console redigidos e metadados de solicitações fetch/XHR.",
|
|
431
431
|
agentTokenDescription:
|
|
432
|
-
"
|
|
432
|
+
"Esta URL temporaria para agentes permite ler o clipe sem torna-lo publico. Ela expira em duas horas.",
|
|
433
433
|
gifPreview: "visualização de GIF",
|
|
434
434
|
openPlayer: "Jogador aberto",
|
|
435
435
|
downloadMp4: "Baixar MP4",
|
|
@@ -403,7 +403,7 @@ const messages = {
|
|
|
403
403
|
agentPrompt:
|
|
404
404
|
"获取这个 Clips 代理上下文 URL:{{agentContextUrl}}。使用 transcript.segments 读取语音上下文,获取 recommendedFrames 或帧 API URL 来查看屏幕,并在 browserDiagnostics 存在时检查经过脱敏的控制台日志和 fetch/XHR 请求元数据。",
|
|
405
405
|
agentTokenDescription:
|
|
406
|
-
"
|
|
406
|
+
"这个临时代理 URL 可让代理读取剪辑,而无需将其设为公开。它会在两小时后过期。",
|
|
407
407
|
gifPreview: "GIF 预览",
|
|
408
408
|
openPlayer: "开放玩家",
|
|
409
409
|
downloadMp4: "下载MP4",
|
|
@@ -403,7 +403,7 @@ const messages = {
|
|
|
403
403
|
agentPrompt:
|
|
404
404
|
"取得這個 Clips Agent 脈絡 URL:{{agentContextUrl}}。使用 transcript.segments 讀取語音脈絡,取得 recommendedFrames 或影格 API URL 來查看螢幕,並在 browserDiagnostics 存在時檢查已遮蔽的主控台記錄和 fetch/XHR 請求中繼資料。",
|
|
405
405
|
agentTokenDescription:
|
|
406
|
-
"
|
|
406
|
+
"這個臨時 Agent URL 可讓 Agent 讀取剪輯,而不必將其設為公開。它會在兩小時後過期。",
|
|
407
407
|
gifPreview: "GIF 預覽",
|
|
408
408
|
openPlayer: "開啟播放器",
|
|
409
409
|
downloadMp4: "下載 MP4",
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import {
|
|
12
12
|
getRequestUserEmail,
|
|
13
13
|
signShortLivedToken,
|
|
14
|
+
verifyShortLivedToken,
|
|
14
15
|
} from "@agent-native/core/server";
|
|
15
16
|
import { resolveAccess } from "@agent-native/core/sharing";
|
|
16
17
|
import {
|
|
@@ -32,7 +33,12 @@ import {
|
|
|
32
33
|
useState,
|
|
33
34
|
type ReactNode,
|
|
34
35
|
} from "react";
|
|
35
|
-
import
|
|
36
|
+
import {
|
|
37
|
+
data,
|
|
38
|
+
type HeadersArgs,
|
|
39
|
+
type LoaderFunctionArgs,
|
|
40
|
+
type MetaFunction,
|
|
41
|
+
} from "react-router";
|
|
36
42
|
import { useLoaderData, useNavigate, useParams } from "react-router";
|
|
37
43
|
|
|
38
44
|
import { CaptureInstallButton } from "@/components/capture-install-options";
|
|
@@ -65,7 +71,12 @@ import { parsePlaybackSpeed } from "@/lib/playback-speed";
|
|
|
65
71
|
import { isStorageSetupFailureReason } from "@/lib/storage-failures";
|
|
66
72
|
|
|
67
73
|
import { getDb, schema } from "../../server/db";
|
|
68
|
-
import {
|
|
74
|
+
import { CLIPS_AGENT_ACCESS_PARAM } from "../../server/lib/public-agent-context";
|
|
75
|
+
import {
|
|
76
|
+
agentAccessTokenResourceId,
|
|
77
|
+
buildAgentApiUrls,
|
|
78
|
+
safeJsonForHtml,
|
|
79
|
+
} from "../../shared/agent-context";
|
|
69
80
|
import {
|
|
70
81
|
isLoomEmbedBackedRecording,
|
|
71
82
|
isLoomRecordingSource,
|
|
@@ -99,6 +110,34 @@ type SharePageLoaderData = {
|
|
|
99
110
|
shareUrl: string | null;
|
|
100
111
|
};
|
|
101
112
|
|
|
113
|
+
const PRIVATE_AGENT_SHARE_HEADERS = {
|
|
114
|
+
"Cache-Control": "private, max-age=0, no-store",
|
|
115
|
+
"Referrer-Policy": "no-referrer",
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
function emptyLoaderData(url: URL): SharePageLoaderData {
|
|
119
|
+
return {
|
|
120
|
+
recording: null,
|
|
121
|
+
agentContextUrl: null,
|
|
122
|
+
origin: url.origin,
|
|
123
|
+
shareUrl: null,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function shareLoaderData(
|
|
128
|
+
payload: SharePageLoaderData,
|
|
129
|
+
privateAgentAccess = false,
|
|
130
|
+
) {
|
|
131
|
+
if (!privateAgentAccess) return payload;
|
|
132
|
+
return data(payload, {
|
|
133
|
+
headers: PRIVATE_AGENT_SHARE_HEADERS,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function headers({ loaderHeaders }: HeadersArgs) {
|
|
138
|
+
return loaderHeaders;
|
|
139
|
+
}
|
|
140
|
+
|
|
102
141
|
function failureDetail(reason: string | null | undefined): string | null {
|
|
103
142
|
const trimmed = reason?.trim();
|
|
104
143
|
if (!trimmed) return null;
|
|
@@ -132,13 +171,7 @@ function shouldShowGeneratedTitleSkeleton(
|
|
|
132
171
|
|
|
133
172
|
export async function loader({ params, url }: LoaderFunctionArgs) {
|
|
134
173
|
const id = params.shareId;
|
|
135
|
-
if (!id)
|
|
136
|
-
return {
|
|
137
|
-
recording: null,
|
|
138
|
-
agentContextUrl: null,
|
|
139
|
-
origin: url.origin,
|
|
140
|
-
shareUrl: null,
|
|
141
|
-
};
|
|
174
|
+
if (!id) return emptyLoaderData(url);
|
|
142
175
|
|
|
143
176
|
const [rec] = await getDb()
|
|
144
177
|
.select({
|
|
@@ -151,6 +184,7 @@ export async function loader({ params, url }: LoaderFunctionArgs) {
|
|
|
151
184
|
status: schema.recordings.status,
|
|
152
185
|
ownerEmail: schema.recordings.ownerEmail,
|
|
153
186
|
password: schema.recordings.password,
|
|
187
|
+
expiresAt: schema.recordings.expiresAt,
|
|
154
188
|
archivedAt: schema.recordings.archivedAt,
|
|
155
189
|
trashedAt: schema.recordings.trashedAt,
|
|
156
190
|
})
|
|
@@ -158,24 +192,25 @@ export async function loader({ params, url }: LoaderFunctionArgs) {
|
|
|
158
192
|
.where(eq(schema.recordings.id, id))
|
|
159
193
|
.limit(1);
|
|
160
194
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
195
|
+
const agentAccessToken = url.searchParams.get(CLIPS_AGENT_ACCESS_PARAM) ?? "";
|
|
196
|
+
const hasAgentAccessToken = Boolean(agentAccessToken);
|
|
197
|
+
const tokenGrantsAgentAccess = agentAccessToken
|
|
198
|
+
? verifyShortLivedToken(agentAccessToken, agentAccessTokenResourceId(id)).ok
|
|
199
|
+
: false;
|
|
200
|
+
|
|
201
|
+
if (!rec) return shareLoaderData(emptyLoaderData(url), hasAgentAccessToken);
|
|
202
|
+
|
|
203
|
+
if (rec.expiresAt) {
|
|
204
|
+
const expires = new Date(rec.expiresAt).getTime();
|
|
205
|
+
if (Number.isFinite(expires) && expires < Date.now()) {
|
|
206
|
+
return shareLoaderData(emptyLoaderData(url), hasAgentAccessToken);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
168
209
|
|
|
169
|
-
if (rec.visibility !== "public") {
|
|
210
|
+
if (rec.visibility !== "public" && !tokenGrantsAgentAccess) {
|
|
170
211
|
const userEmail = getRequestUserEmail();
|
|
171
212
|
const access = userEmail ? await resolveAccess("recording", id) : null;
|
|
172
|
-
if (!access)
|
|
173
|
-
return {
|
|
174
|
-
recording: null,
|
|
175
|
-
agentContextUrl: null,
|
|
176
|
-
origin: url.origin,
|
|
177
|
-
shareUrl: null,
|
|
178
|
-
};
|
|
213
|
+
if (!access) return emptyLoaderData(url);
|
|
179
214
|
}
|
|
180
215
|
|
|
181
216
|
const recording: SharePageMetaRecording = {
|
|
@@ -190,29 +225,37 @@ export async function loader({ params, url }: LoaderFunctionArgs) {
|
|
|
190
225
|
trashedAt: rec.trashedAt,
|
|
191
226
|
};
|
|
192
227
|
const canExposeAgentContext =
|
|
193
|
-
rec.visibility === "public"
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
228
|
+
(rec.visibility === "public" || tokenGrantsAgentAccess) &&
|
|
229
|
+
!rec.archivedAt &&
|
|
230
|
+
!rec.trashedAt;
|
|
231
|
+
const token = tokenGrantsAgentAccess
|
|
232
|
+
? agentAccessToken
|
|
233
|
+
: canExposeAgentContext &&
|
|
234
|
+
rec.password &&
|
|
235
|
+
getRequestUserEmail() === rec.ownerEmail
|
|
236
|
+
? signShortLivedToken({ resourceId: agentAccessTokenResourceId(id) })
|
|
199
237
|
: undefined;
|
|
200
238
|
const canExposeAnonymousAgentContext = canExposeAgentContext && !rec.password;
|
|
201
239
|
const canExposeOwnerAgentContext = canExposeAgentContext && Boolean(token);
|
|
202
|
-
return
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
240
|
+
return shareLoaderData(
|
|
241
|
+
{
|
|
242
|
+
recording,
|
|
243
|
+
origin: url.origin,
|
|
244
|
+
shareUrl: `${url.origin}${url.pathname}`,
|
|
245
|
+
agentContextUrl:
|
|
246
|
+
canExposeAnonymousAgentContext || canExposeOwnerAgentContext
|
|
247
|
+
? buildAgentApiUrls(id, {
|
|
248
|
+
origin: url.origin,
|
|
249
|
+
basePath:
|
|
250
|
+
process.env.VITE_APP_BASE_PATH ||
|
|
251
|
+
process.env.APP_BASE_PATH ||
|
|
252
|
+
"",
|
|
253
|
+
token,
|
|
254
|
+
}).contextUrl
|
|
255
|
+
: null,
|
|
256
|
+
},
|
|
257
|
+
hasAgentAccessToken || canExposeOwnerAgentContext,
|
|
258
|
+
);
|
|
216
259
|
}
|
|
217
260
|
|
|
218
261
|
export const meta: MetaFunction<typeof loader> = ({ loaderData }) => {
|
|
@@ -273,7 +316,7 @@ function AgentDiscovery({
|
|
|
273
316
|
{t("sharePage.agentReadableContext")}
|
|
274
317
|
</a>
|
|
275
318
|
<script
|
|
276
|
-
type="application/json"
|
|
319
|
+
type="application/agent-native+json"
|
|
277
320
|
id="clips-agent-context"
|
|
278
321
|
dangerouslySetInnerHTML={{ __html: safeJsonForHtml(payload) }}
|
|
279
322
|
/>
|
|
@@ -362,9 +405,17 @@ export default function ShareRoute() {
|
|
|
362
405
|
[],
|
|
363
406
|
);
|
|
364
407
|
const [downloading, setDownloading] = useState(false);
|
|
408
|
+
const agentAccessToken = useMemo(() => {
|
|
409
|
+
if (typeof window === "undefined") return "";
|
|
410
|
+
return (
|
|
411
|
+
new URLSearchParams(window.location.search).get(
|
|
412
|
+
CLIPS_AGENT_ACCESS_PARAM,
|
|
413
|
+
) ?? ""
|
|
414
|
+
);
|
|
415
|
+
}, []);
|
|
365
416
|
|
|
366
417
|
const dataQ = useQuery({
|
|
367
|
-
queryKey: ["public-recording", shareId, password],
|
|
418
|
+
queryKey: ["public-recording", shareId, password, agentAccessToken],
|
|
368
419
|
queryFn: async () => {
|
|
369
420
|
const url = new URL(
|
|
370
421
|
`${appBasePath()}/api/public-recording`,
|
|
@@ -372,6 +423,9 @@ export default function ShareRoute() {
|
|
|
372
423
|
);
|
|
373
424
|
url.searchParams.set("id", shareId ?? "");
|
|
374
425
|
if (password) url.searchParams.set("password", password);
|
|
426
|
+
if (agentAccessToken) {
|
|
427
|
+
url.searchParams.set(CLIPS_AGENT_ACCESS_PARAM, agentAccessToken);
|
|
428
|
+
}
|
|
375
429
|
const res = await fetch(url.toString());
|
|
376
430
|
const data = await res.json().catch(() => ({}));
|
|
377
431
|
return { ok: res.ok, status: res.status, data };
|
|
@@ -9,6 +9,7 @@ import { asc, eq } from "drizzle-orm";
|
|
|
9
9
|
import { getRequestURL, setResponseHeader, type H3Event } from "h3";
|
|
10
10
|
|
|
11
11
|
import {
|
|
12
|
+
agentAccessTokenResourceId,
|
|
12
13
|
buildAgentApiUrls,
|
|
13
14
|
buildRecommendedFrames,
|
|
14
15
|
CLIP_AGENT_CONTEXT_VERSION,
|
|
@@ -53,6 +54,8 @@ export type PublicAgentAccessResult =
|
|
|
53
54
|
| { ok: false; failure: PublicAgentFailure };
|
|
54
55
|
|
|
55
56
|
const DEFAULT_MAX_AGENT_FRAME_MEDIA_BYTES = 200 * 1024 * 1024;
|
|
57
|
+
export const CLIPS_AGENT_ACCESS_PARAM = "agent_access";
|
|
58
|
+
export const CLIPS_AGENT_ACCESS_TTL_SECONDS = 2 * 60 * 60;
|
|
56
59
|
|
|
57
60
|
function sameOwnerEmail(
|
|
58
61
|
a: string | null | undefined,
|
|
@@ -207,8 +210,20 @@ export async function loadPublicAgentAccess(
|
|
|
207
210
|
const viewerIsOwner = Boolean(
|
|
208
211
|
session?.email && sameOwnerEmail(session.email, recording.ownerEmail),
|
|
209
212
|
);
|
|
213
|
+
const suppliedToken = options.token ?? "";
|
|
214
|
+
const tokenAccess = suppliedToken
|
|
215
|
+
? verifyShortLivedToken(
|
|
216
|
+
suppliedToken,
|
|
217
|
+
agentAccessTokenResourceId(recording.id),
|
|
218
|
+
)
|
|
219
|
+
: null;
|
|
220
|
+
const tokenAllowsAgentAccess = Boolean(tokenAccess?.ok);
|
|
210
221
|
|
|
211
|
-
if (
|
|
222
|
+
if (
|
|
223
|
+
recording.visibility !== "public" &&
|
|
224
|
+
!viewerIsOwner &&
|
|
225
|
+
!tokenAllowsAgentAccess
|
|
226
|
+
) {
|
|
212
227
|
return {
|
|
213
228
|
ok: false,
|
|
214
229
|
failure: { status: 404, body: { error: "Not found" } },
|
|
@@ -229,26 +244,22 @@ export async function loadPublicAgentAccess(
|
|
|
229
244
|
}
|
|
230
245
|
|
|
231
246
|
let apiToken: string | null = null;
|
|
247
|
+
if (tokenAllowsAgentAccess) {
|
|
248
|
+
apiToken = suppliedToken;
|
|
249
|
+
}
|
|
232
250
|
if (
|
|
233
251
|
recording.password &&
|
|
234
252
|
recording.visibility === "public" &&
|
|
235
253
|
viewerIsOwner
|
|
236
254
|
) {
|
|
237
|
-
apiToken = signShortLivedToken({
|
|
255
|
+
apiToken = signShortLivedToken({
|
|
256
|
+
resourceId: agentAccessTokenResourceId(recording.id),
|
|
257
|
+
});
|
|
238
258
|
}
|
|
239
259
|
|
|
240
260
|
if (recording.password && !viewerIsOwner) {
|
|
241
|
-
const suppliedToken = options.token ?? "";
|
|
242
261
|
const suppliedPassword = options.password ?? "";
|
|
243
|
-
let allowed =
|
|
244
|
-
|
|
245
|
-
if (suppliedToken) {
|
|
246
|
-
const result = verifyShortLivedToken(suppliedToken, recording.id);
|
|
247
|
-
if (result.ok) {
|
|
248
|
-
allowed = true;
|
|
249
|
-
apiToken = suppliedToken;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
262
|
+
let allowed = tokenAllowsAgentAccess;
|
|
252
263
|
|
|
253
264
|
if (
|
|
254
265
|
!allowed &&
|
|
@@ -256,7 +267,9 @@ export async function loadPublicAgentAccess(
|
|
|
256
267
|
verifySharePassword(suppliedPassword, recording.password)
|
|
257
268
|
) {
|
|
258
269
|
allowed = true;
|
|
259
|
-
apiToken = signShortLivedToken({
|
|
270
|
+
apiToken = signShortLivedToken({
|
|
271
|
+
resourceId: agentAccessTokenResourceId(recording.id),
|
|
272
|
+
});
|
|
260
273
|
}
|
|
261
274
|
|
|
262
275
|
if (!allowed) {
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
loadPublicAgentAccess,
|
|
24
24
|
parseAgentChapters,
|
|
25
25
|
queryString,
|
|
26
|
+
CLIPS_AGENT_ACCESS_PARAM,
|
|
26
27
|
} from "../../lib/public-agent-context.js";
|
|
27
28
|
|
|
28
29
|
export default defineEventHandler(async (event: H3Event) => {
|
|
@@ -32,7 +33,7 @@ export default defineEventHandler(async (event: H3Event) => {
|
|
|
32
33
|
const id = queryString(query.id);
|
|
33
34
|
const accessResult = await loadPublicAgentAccess(event, id, {
|
|
34
35
|
password: queryString(query.password),
|
|
35
|
-
token: queryString(query.t),
|
|
36
|
+
token: queryString(query[CLIPS_AGENT_ACCESS_PARAM]) || queryString(query.t),
|
|
36
37
|
});
|
|
37
38
|
|
|
38
39
|
if (!accessResult.ok) {
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
} from "h3";
|
|
14
14
|
|
|
15
15
|
import {
|
|
16
|
+
CLIPS_AGENT_ACCESS_PARAM,
|
|
16
17
|
loadPublicAgentAccess,
|
|
17
18
|
loadRecordingMediaBytes,
|
|
18
19
|
queryString,
|
|
@@ -95,7 +96,7 @@ export default defineEventHandler(async (event: H3Event) => {
|
|
|
95
96
|
const id = queryString(query.id);
|
|
96
97
|
const accessResult = await loadPublicAgentAccess(event, id, {
|
|
97
98
|
password: queryString(query.password),
|
|
98
|
-
token: queryString(query.t),
|
|
99
|
+
token: queryString(query[CLIPS_AGENT_ACCESS_PARAM]) || queryString(query.t),
|
|
99
100
|
});
|
|
100
101
|
|
|
101
102
|
if (!accessResult.ok) {
|
|
@@ -16,6 +16,7 @@ import { buildAgentApiUrls } from "../../../shared/agent-context.js";
|
|
|
16
16
|
import { isLoomEmbedBackedRecording } from "../../../shared/loom.js";
|
|
17
17
|
import {
|
|
18
18
|
applyAgentJsonHeaders,
|
|
19
|
+
CLIPS_AGENT_ACCESS_PARAM,
|
|
19
20
|
getServerAppBasePath,
|
|
20
21
|
loadAgentTranscript,
|
|
21
22
|
loadPublicAgentAccess,
|
|
@@ -30,7 +31,7 @@ export default defineEventHandler(async (event: H3Event) => {
|
|
|
30
31
|
const id = queryString(query.id);
|
|
31
32
|
const accessResult = await loadPublicAgentAccess(event, id, {
|
|
32
33
|
password: queryString(query.password),
|
|
33
|
-
token: queryString(query.t),
|
|
34
|
+
token: queryString(query[CLIPS_AGENT_ACCESS_PARAM]) || queryString(query.t),
|
|
34
35
|
});
|
|
35
36
|
|
|
36
37
|
if (!accessResult.ok) {
|
|
@@ -26,7 +26,10 @@ import {
|
|
|
26
26
|
type H3Event,
|
|
27
27
|
} from "h3";
|
|
28
28
|
|
|
29
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
agentAccessTokenResourceId,
|
|
31
|
+
buildAgentApiUrls,
|
|
32
|
+
} from "../../../shared/agent-context.js";
|
|
30
33
|
import {
|
|
31
34
|
normalizeTranscriptSegments,
|
|
32
35
|
parseTranscriptSegments,
|
|
@@ -248,7 +251,9 @@ export default defineEventHandler(async (event) => {
|
|
|
248
251
|
rec.visibility === "public" && !rec.archivedAt && !rec.trashedAt;
|
|
249
252
|
const agentToken =
|
|
250
253
|
canExposeAgentContext && rec.password
|
|
251
|
-
? signShortLivedToken({
|
|
254
|
+
? signShortLivedToken({
|
|
255
|
+
resourceId: agentAccessTokenResourceId(recordingId),
|
|
256
|
+
})
|
|
252
257
|
: undefined;
|
|
253
258
|
const agentContextUrl = canExposeAgentContext
|
|
254
259
|
? buildAgentApiUrls(recordingId, {
|