@agent-native/core 0.155.0 → 0.157.1
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/templates/analytics/server/jobs/analytics-bigquery-backfill.ts +19 -4
- package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +7 -2
- package/corpus/templates/assets/app/components/asset/AssetPreviewDialog.tsx +3 -1
- package/corpus/templates/assets/app/global.css +4 -0
- package/corpus/templates/assets/app/i18n-data.ts +21 -1
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +18 -15
- package/corpus/templates/brain/app/routes/sources.tsx +1 -2
- package/corpus/templates/calendar/app/hooks/use-bookings.ts +12 -2
- package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +64 -2
- package/corpus/templates/calendar/server/handlers/bookings.ts +219 -93
- package/corpus/templates/calendar/server/plugins/auth.ts +0 -2
- package/corpus/templates/clips/actions/create-folder.ts +7 -1
- package/corpus/templates/clips/actions/create-meeting.ts +6 -2
- package/corpus/templates/clips/actions/create-recording.ts +5 -3
- package/corpus/templates/clips/actions/finalize-recording.ts +97 -65
- package/corpus/templates/clips/actions/import-loom-recording.ts +5 -3
- package/corpus/templates/clips/actions/request-recording-access.ts +272 -0
- package/corpus/templates/clips/actions/stitch-recordings.ts +5 -3
- package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +38 -63
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +177 -134
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +3 -21
- package/corpus/templates/clips/app/components/sharing/share-ui.tsx +13 -30
- package/corpus/templates/clips/app/i18n/en-US.ts +10 -0
- package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +5 -5
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +24 -17
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +151 -20
- package/corpus/templates/clips/changelog/2026-08-14-long-resumable-uploads-now-abort-cleanly-and-show-errors.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-14-standardized-share-popovers.md +6 -0
- package/corpus/templates/clips/chrome-extension/public/manifest.json +11 -3
- package/corpus/templates/clips/chrome-extension/src/github-preview-content.ts +37 -8
- package/corpus/templates/clips/chrome-extension/src/github-preview.ts +3 -3
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +8 -25
- package/corpus/templates/clips/desktop/src/app.tsx +24 -0
- package/corpus/templates/clips/desktop/src/lib/media-capture-constraints.ts +42 -13
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +11 -18
- package/corpus/templates/clips/desktop/src/overlays/onboarding.tsx +1 -0
- package/corpus/templates/clips/desktop/src/shared/config.ts +1 -0
- package/corpus/templates/clips/desktop/src/styles.css +24 -9
- package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +3 -0
- package/corpus/templates/clips/desktop/src-tauri/src/config.rs +8 -0
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +154 -176
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +115 -31
- package/corpus/templates/clips/desktop/src-tauri/src/rewind_clip.rs +7 -0
- package/corpus/templates/clips/server/db/index.ts +1 -0
- package/corpus/templates/clips/server/db/schema.ts +1 -0
- package/corpus/templates/clips/server/lib/calendar-event-meetings.ts +6 -1
- package/corpus/templates/clips/server/lib/emails.ts +23 -0
- package/corpus/templates/clips/server/lib/public-agent-context.ts +55 -0
- package/corpus/templates/clips/server/lib/recordings.ts +2 -1
- package/corpus/templates/clips/server/lib/resumable-upload-cleanup.ts +49 -0
- package/corpus/templates/clips/server/lib/s3-upload-provider.ts +34 -7
- package/corpus/templates/clips/server/lib/upload-interruption.ts +15 -1
- package/corpus/templates/clips/server/lib/upload-lease.ts +94 -9
- package/corpus/templates/clips/server/routes/api/agent-context.json.get.ts +82 -7
- package/corpus/templates/clips/server/routes/api/public-recording.get.ts +1 -0
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +71 -12
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/interrupt.post.ts +4 -3
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/reset-chunks.post.ts +19 -0
- package/corpus/templates/clips/server/routes/api/uploads/reap.post.ts +8 -1
- package/corpus/templates/clips/shared/recording-link.ts +4 -0
- package/corpus/templates/clips/shared/share-loader-response.ts +2 -1
- package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +7 -16
- package/corpus/templates/factory/AGENTS.md +5 -1
- package/corpus/templates/factory/README.md +6 -2
- package/corpus/templates/factory/actions/view-screen.ts +15 -0
- package/corpus/templates/factory/app/components/factory/FactoryAgentsView.tsx +58 -189
- package/corpus/templates/factory/app/components/layout/Header.tsx +1 -0
- package/corpus/templates/factory/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/factory/app/components/layout/Sidebar.tsx +7 -0
- package/corpus/templates/factory/app/hooks/use-navigation-state.ts +3 -0
- package/corpus/templates/factory/app/i18n/en-US.ts +3 -1
- package/corpus/templates/factory/app/routes/agents.tsx +9 -0
- package/corpus/templates/mail/server/plugins/auth.ts +0 -2
- package/corpus/templates/plan/app/components/plan/LocalCodebasePicker.tsx +50 -14
- package/corpus/templates/plan/app/pages/PlansPage.tsx +9 -19
- package/corpus/templates/slides/app/components/editor/AddSlidePopover.tsx +98 -29
- package/corpus/templates/slides/app/components/editor/EditorActionCluster.tsx +45 -91
- package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +94 -2
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +17 -29
- package/corpus/templates/slides/app/components/editor/HistoryPanel.tsx +7 -1
- package/corpus/templates/slides/app/components/editor/SlideContextToolbar.tsx +1 -1
- package/corpus/templates/slides/app/context/DeckContext.tsx +108 -19
- package/corpus/templates/slides/app/global.css +7 -40
- package/corpus/templates/slides/app/i18n/en-US.ts +4 -0
- package/corpus/templates/slides/app/lib/editor-shortcuts.ts +46 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +143 -45
- package/dist/agent/index.d.ts +1 -1
- package/dist/agent/production-agent.d.ts +30 -0
- package/dist/agent/production-agent.js +100 -8
- package/dist/agent/types.d.ts +9 -0
- package/dist/client/AgentPanel.d.ts +13 -1
- package/dist/client/AgentPanel.js +22 -17
- package/dist/client/AssistantChat.d.ts +15 -1
- package/dist/client/AssistantChat.js +135 -48
- package/dist/client/HighlightedCodeBlock.js +12 -2
- package/dist/client/MultiTabAssistantChat.js +44 -27
- package/dist/client/agent-chat/index.d.ts +1 -0
- package/dist/client/agent-chat/index.js +1 -0
- package/dist/client/chat/markdown-renderer.js +20 -5
- package/dist/client/chat/message-components.d.ts +2 -2
- package/dist/client/chat/message-components.js +69 -35
- package/dist/client/chat/run-recovery.d.ts +4 -1
- package/dist/client/chat/run-recovery.js +62 -30
- package/dist/client/chat/tool-call-display.d.ts +12 -3
- package/dist/client/chat/tool-call-display.js +79 -40
- package/dist/client/chat/widgets/DataChartRenderer.js +3 -1
- package/dist/client/chat/widgets/DataChartWidget.js +8 -2
- package/dist/client/chat/widgets/DataInsightsWidget.js +3 -1
- package/dist/client/chat/widgets/DataTableWidget.js +23 -9
- package/dist/client/chat/widgets/builtin-tool-renderers.js +3 -1
- package/dist/client/chat-first/apps-rail.js +1 -1
- package/dist/client/components/ui/message-scroller.d.ts +1 -1
- package/dist/client/components/ui/message-scroller.js +7 -3
- package/dist/client/composer/runtime-adapters.js +3 -2
- package/dist/client/context-xray/ContextMeter.js +5 -1
- package/dist/client/context-xray/ContextXRayPanel.js +5 -5
- package/dist/client/error-format.d.ts +4 -0
- package/dist/client/error-format.js +93 -5
- package/dist/client/i18n.js +83 -17
- package/dist/client/observability/ThumbsFeedback.js +10 -8
- package/dist/client/settings/AccountSettingsCard.js +48 -19
- package/dist/client/sharing/ShareButton.d.ts +1 -2
- package/dist/client/sharing/ShareButton.js +151 -81
- package/dist/client/sharing/ShareDialog.js +5 -4
- package/dist/client/use-chat-threads.js +10 -14
- package/dist/coding-tools/run-code.js +2 -1
- package/dist/coding-tools/sandbox/background.d.ts +1 -0
- package/dist/coding-tools/sandbox/background.js +8 -1
- package/dist/coding-tools/sandbox/executions-store.d.ts +2 -0
- package/dist/coding-tools/sandbox/executions-store.js +41 -3
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/localization/core-messages/ar-SA.d.ts +3 -0
- package/dist/localization/core-messages/ar-SA.js +541 -0
- package/dist/localization/core-messages/de-DE.d.ts +3 -0
- package/dist/localization/core-messages/de-DE.js +513 -0
- package/dist/localization/core-messages/en-US.d.ts +513 -0
- package/dist/localization/core-messages/en-US.js +513 -0
- package/dist/localization/core-messages/es-ES.d.ts +3 -0
- package/dist/localization/core-messages/es-ES.js +520 -0
- package/dist/localization/core-messages/fr-FR.d.ts +3 -0
- package/dist/localization/core-messages/fr-FR.js +520 -0
- package/dist/localization/core-messages/hi-IN.d.ts +3 -0
- package/dist/localization/core-messages/hi-IN.js +513 -0
- package/dist/localization/core-messages/ja-JP.d.ts +3 -0
- package/dist/localization/core-messages/ja-JP.js +506 -0
- package/dist/localization/core-messages/ko-KR.d.ts +3 -0
- package/dist/localization/core-messages/ko-KR.js +506 -0
- package/dist/localization/core-messages/pt-BR.d.ts +3 -0
- package/dist/localization/core-messages/pt-BR.js +520 -0
- package/dist/localization/core-messages/zh-CN.d.ts +3 -0
- package/dist/localization/core-messages/zh-CN.js +506 -0
- package/dist/localization/core-messages/zh-TW.d.ts +3 -0
- package/dist/localization/core-messages/zh-TW.js +506 -0
- package/dist/localization/core-messages.d.ts +524 -0
- package/dist/localization/core-messages.js +124 -0
- package/dist/localization/default-messages.d.ts +3 -0
- package/dist/localization/default-messages.js +3 -0
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +11 -11
- package/dist/provider-api/actions/provider-api.d.ts +15 -15
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-chat/plugin-options.d.ts +7 -2
- package/dist/server/agent-chat-plugin.d.ts +10 -0
- package/dist/server/agent-chat-plugin.js +144 -41
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agent-run-context.d.ts +7 -1
- package/dist/server/agent-run-context.js +9 -2
- package/dist/server/agent-teams-run-queue.d.ts +3 -0
- package/dist/server/agent-teams.js +38 -9
- package/dist/server/auth-marketing.d.ts +1 -0
- package/dist/server/auth-marketing.js +0 -2
- package/dist/server/auth.d.ts +1 -0
- package/dist/server/auth.js +18 -0
- package/dist/server/index.d.ts +1 -1
- package/dist/server/onboarding-html.d.ts +1 -0
- package/dist/server/onboarding-html.js +2 -88
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/request-context.d.ts +7 -0
- package/dist/server/request-context.js +15 -0
- package/dist/shared/markdown-block-split.d.ts +16 -0
- package/dist/shared/markdown-block-split.js +81 -5
- package/dist/styles/agent-native.css +13 -0
- package/dist/voice/voice-cleanup-prompt.js +1 -0
- package/docs/content/agent-surfaces.mdx +30 -0
- package/package.json +2 -2
- package/corpus/templates/clips/shared/mic-audio-cleanup.ts +0 -350
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
isHumanReadableDocumentTitle,
|
|
17
17
|
normalizeDocumentTitle,
|
|
18
18
|
} from "@agent-native/core/shared";
|
|
19
|
+
import { ShareTrigger } from "@agent-native/toolkit/sharing";
|
|
19
20
|
import {
|
|
20
21
|
BUILDER_CREDITS_UPGRADE_URL,
|
|
21
22
|
type BuilderCreditsStatus,
|
|
@@ -33,7 +34,6 @@ import {
|
|
|
33
34
|
} from "@shared/share-attribution";
|
|
34
35
|
import type { WorkflowKind } from "@shared/workflow";
|
|
35
36
|
import {
|
|
36
|
-
IconShare3,
|
|
37
37
|
IconArrowLeft,
|
|
38
38
|
IconChevronDown,
|
|
39
39
|
IconCalendar,
|
|
@@ -529,6 +529,18 @@ export default function RecordingPage() {
|
|
|
529
529
|
const canDownloadRecording = Boolean(
|
|
530
530
|
recording?.enableDownloads && recording.videoUrl && !isLoomEmbedBacked,
|
|
531
531
|
);
|
|
532
|
+
// Mirrors the /share/:shareId reshare restriction (same public/org scope):
|
|
533
|
+
// a plain viewer of a public or org clip must not trigger
|
|
534
|
+
// `list-resource-shares` (any read access is enough to call it, and its
|
|
535
|
+
// response includes every individually-shared principal's email) or see a
|
|
536
|
+
// raw video download/open action independent of `enableDownloads`.
|
|
537
|
+
const viewerReshareOnly =
|
|
538
|
+
(role === "viewer" || role === "commenter") &&
|
|
539
|
+
(recording?.visibility === "public" || recording?.visibility === "org");
|
|
540
|
+
const shareVideoUrl =
|
|
541
|
+
canDownloadRecording || isLoomEmbedBacked
|
|
542
|
+
? (recording?.videoUrl ?? null)
|
|
543
|
+
: null;
|
|
532
544
|
const downloadRecording = useCallback(async () => {
|
|
533
545
|
if (!recording?.videoUrl) return;
|
|
534
546
|
setDownloading(true);
|
|
@@ -988,13 +1000,12 @@ export default function RecordingPage() {
|
|
|
988
1000
|
initialVisibility={recording.visibility}
|
|
989
1001
|
initialRole={role}
|
|
990
1002
|
hasPassword={Boolean(recording.hasPassword)}
|
|
1003
|
+
viewerReshareOnly={viewerReshareOnly}
|
|
991
1004
|
>
|
|
992
|
-
<
|
|
993
|
-
{
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
{t("recordingPage.share")}
|
|
997
|
-
</Button>
|
|
1005
|
+
<ShareTrigger
|
|
1006
|
+
label={t("recordingPage.share")}
|
|
1007
|
+
className="shrink-0"
|
|
1008
|
+
/>
|
|
998
1009
|
</ShareRecordingPopover>
|
|
999
1010
|
</header>
|
|
1000
1011
|
|
|
@@ -1550,21 +1561,17 @@ export default function RecordingPage() {
|
|
|
1550
1561
|
recordingTitle={recording.title}
|
|
1551
1562
|
initialVisibility={recording.visibility}
|
|
1552
1563
|
initialRole={role}
|
|
1553
|
-
videoUrl={
|
|
1564
|
+
videoUrl={shareVideoUrl}
|
|
1554
1565
|
thumbnailUrl={recording.thumbnailUrl}
|
|
1555
1566
|
animatedThumbnailUrl={recording.animatedThumbnailUrl}
|
|
1556
1567
|
isLoomRecording={isLoomEmbedBacked}
|
|
1557
1568
|
hasPassword={Boolean(recording.hasPassword)}
|
|
1569
|
+
viewerReshareOnly={viewerReshareOnly}
|
|
1558
1570
|
>
|
|
1559
|
-
<
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
{recording.visibility !== "public" ? (
|
|
1564
|
-
<IconShare3 className="h-4 w-4" />
|
|
1565
|
-
) : null}
|
|
1566
|
-
{t("recordingPage.share")}
|
|
1567
|
-
</Button>
|
|
1571
|
+
<ShareTrigger
|
|
1572
|
+
label={t("recordingPage.share")}
|
|
1573
|
+
className="shrink-0"
|
|
1574
|
+
/>
|
|
1568
1575
|
</ShareRecordingPopover>
|
|
1569
1576
|
) : null}
|
|
1570
1577
|
|
|
@@ -5,10 +5,15 @@ import {
|
|
|
5
5
|
appBasePath,
|
|
6
6
|
appPath,
|
|
7
7
|
} from "@agent-native/core/client/api-path";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
useActionMutation,
|
|
10
|
+
useSession,
|
|
11
|
+
getBrowserTabId,
|
|
12
|
+
} from "@agent-native/core/client/hooks";
|
|
9
13
|
import { useT } from "@agent-native/core/client/i18n";
|
|
10
14
|
import { buildSignInReturnHref } from "@agent-native/core/client/ui";
|
|
11
15
|
import { docsUrl } from "@agent-native/core/shared";
|
|
16
|
+
import { ShareTrigger } from "@agent-native/toolkit/sharing";
|
|
12
17
|
import {
|
|
13
18
|
IconAlertTriangle,
|
|
14
19
|
IconArrowLeft,
|
|
@@ -16,8 +21,8 @@ import {
|
|
|
16
21
|
IconDownload,
|
|
17
22
|
IconDots,
|
|
18
23
|
IconExternalLink,
|
|
24
|
+
IconLock,
|
|
19
25
|
IconLogin2,
|
|
20
|
-
IconShare3,
|
|
21
26
|
} from "@tabler/icons-react";
|
|
22
27
|
import { useQuery } from "@tanstack/react-query";
|
|
23
28
|
import { eq } from "drizzle-orm";
|
|
@@ -87,6 +92,10 @@ import {
|
|
|
87
92
|
isLoomEmbedBackedRecording,
|
|
88
93
|
isLoomRecordingSource,
|
|
89
94
|
} from "../../shared/loom";
|
|
95
|
+
import {
|
|
96
|
+
CLIPS_ACCESS_REQUEST_TOKEN_PREFIX,
|
|
97
|
+
CLIPS_ACCESS_REQUEST_TOKEN_TTL_SECONDS,
|
|
98
|
+
} from "../../shared/recording-link";
|
|
90
99
|
import {
|
|
91
100
|
buildSignupAttributionQuery,
|
|
92
101
|
readShareAttribution,
|
|
@@ -116,16 +125,22 @@ type SharePageLoaderData = {
|
|
|
116
125
|
agentContextUrl: string | null;
|
|
117
126
|
origin: string | null;
|
|
118
127
|
shareUrl: string | null;
|
|
128
|
+
accessDeniedStatus?: 401 | 403;
|
|
129
|
+
accessRequestToken?: string;
|
|
119
130
|
};
|
|
120
131
|
|
|
121
132
|
const CLIPS_AGENT_ACCESS_TTL_SECONDS = 2 * 60 * 60;
|
|
122
133
|
|
|
123
|
-
function emptyLoaderData(
|
|
134
|
+
function emptyLoaderData(
|
|
135
|
+
url: URL,
|
|
136
|
+
accessDeniedStatus?: 401 | 403,
|
|
137
|
+
): SharePageLoaderData {
|
|
124
138
|
return {
|
|
125
139
|
recording: null,
|
|
126
140
|
agentContextUrl: null,
|
|
127
141
|
origin: url.origin,
|
|
128
142
|
shareUrl: null,
|
|
143
|
+
accessDeniedStatus,
|
|
129
144
|
};
|
|
130
145
|
}
|
|
131
146
|
|
|
@@ -224,7 +239,19 @@ export async function loader({ params, url }: LoaderFunctionArgs) {
|
|
|
224
239
|
if (rec.visibility !== "public" && !tokenGrantsAgentAccess) {
|
|
225
240
|
const userEmail = getRequestUserEmail();
|
|
226
241
|
const access = userEmail ? await resolveAccess("recording", id) : null;
|
|
227
|
-
if (!access)
|
|
242
|
+
if (!access) {
|
|
243
|
+
const status = userEmail ? 403 : 401;
|
|
244
|
+
const deniedData = emptyLoaderData(url, status);
|
|
245
|
+
if (userEmail) {
|
|
246
|
+
deniedData.accessRequestToken = signScopedAgentAccessToken({
|
|
247
|
+
resourceKind: CLIPS_ACCESS_REQUEST_TOKEN_PREFIX,
|
|
248
|
+
resourceId: id,
|
|
249
|
+
viewerEmail: userEmail,
|
|
250
|
+
ttlSeconds: CLIPS_ACCESS_REQUEST_TOKEN_TTL_SECONDS,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
return privateShareLoaderData(deniedData, status);
|
|
254
|
+
}
|
|
228
255
|
}
|
|
229
256
|
|
|
230
257
|
const recording: SharePageMetaRecording = {
|
|
@@ -418,6 +445,16 @@ export default function ShareRoute() {
|
|
|
418
445
|
const [pwError, setPwError] = useState<string | null>(null);
|
|
419
446
|
const [currentMs, setCurrentMs] = useState(0);
|
|
420
447
|
const { session, isLoading: sessionLoading } = useSession();
|
|
448
|
+
const requestAccess = useActionMutation<
|
|
449
|
+
{
|
|
450
|
+
alreadyHasAccess: boolean;
|
|
451
|
+
alreadyRequested?: boolean;
|
|
452
|
+
message: string;
|
|
453
|
+
notifiedOwner: boolean;
|
|
454
|
+
ok: true;
|
|
455
|
+
},
|
|
456
|
+
{ accessRequestToken?: string; recordingId: string }
|
|
457
|
+
>("request-recording-access");
|
|
421
458
|
const [signInIntent, setSignInIntent] = useState<"comment" | "react" | null>(
|
|
422
459
|
null,
|
|
423
460
|
);
|
|
@@ -429,6 +466,7 @@ export default function ShareRoute() {
|
|
|
429
466
|
);
|
|
430
467
|
const [downloading, setDownloading] = useState(false);
|
|
431
468
|
const [downloadMenuOpen, setDownloadMenuOpen] = useState(false);
|
|
469
|
+
const [accessRequestSent, setAccessRequestSent] = useState(false);
|
|
432
470
|
const agentAccessToken = useMemo(() => {
|
|
433
471
|
if (typeof window === "undefined") return "";
|
|
434
472
|
return (
|
|
@@ -518,6 +556,16 @@ export default function ShareRoute() {
|
|
|
518
556
|
const transcriptFailureReason =
|
|
519
557
|
dataQ.data?.data?.transcript?.failureReason ?? null;
|
|
520
558
|
const ctas = dataQ.data?.data?.ctas ?? [];
|
|
559
|
+
const apiStatus = dataQ.data?.status;
|
|
560
|
+
const apiAccessDeniedStatus =
|
|
561
|
+
apiStatus === 401 || apiStatus === 403 ? apiStatus : null;
|
|
562
|
+
const accessDeniedStatus =
|
|
563
|
+
apiAccessDeniedStatus ??
|
|
564
|
+
(!recording && loaderData.accessDeniedStatus
|
|
565
|
+
? session
|
|
566
|
+
? 403
|
|
567
|
+
: 401
|
|
568
|
+
: null);
|
|
521
569
|
const firstCta = ctas[0] ?? null;
|
|
522
570
|
const viewerRole = dataQ.data?.data?.viewer?.role as
|
|
523
571
|
| "owner"
|
|
@@ -537,6 +585,15 @@ export default function ShareRoute() {
|
|
|
537
585
|
viewerRole === "editor" ||
|
|
538
586
|
viewerRole === "commenter";
|
|
539
587
|
const viewerIsOwner = Boolean(dataQ.data?.data?.viewer?.isOwner);
|
|
588
|
+
const canReshareLink =
|
|
589
|
+
(viewerRole === "viewer" || viewerRole === "commenter") &&
|
|
590
|
+
(recording?.visibility === "public" || recording?.visibility === "org");
|
|
591
|
+
// A plain viewer only gets a copy-link control: it must not trigger
|
|
592
|
+
// `list-resource-shares` (any read access is enough to call it, and its
|
|
593
|
+
// response includes every individually-shared principal's email) and must
|
|
594
|
+
// not surface the raw video download/open action independent of
|
|
595
|
+
// `enableDownloads`.
|
|
596
|
+
const viewerReshareOnly = canReshareLink && !viewerCanEdit;
|
|
540
597
|
const viewerCanOpenDashboard = Boolean(
|
|
541
598
|
dataQ.data?.data?.viewer?.canOpenDashboard,
|
|
542
599
|
);
|
|
@@ -742,18 +799,58 @@ export default function ShareRoute() {
|
|
|
742
799
|
);
|
|
743
800
|
}
|
|
744
801
|
|
|
745
|
-
if (
|
|
802
|
+
if (accessDeniedStatus === 401 || accessDeniedStatus === 403) {
|
|
803
|
+
const canRequestAccess = accessDeniedStatus === 403 && Boolean(session);
|
|
804
|
+
const requestSent =
|
|
805
|
+
accessRequestSent || Boolean(dataQ.data?.data?.alreadyRequested);
|
|
806
|
+
|
|
746
807
|
return (
|
|
747
808
|
<>
|
|
748
809
|
{agentDiscovery}
|
|
749
810
|
<EndState
|
|
750
|
-
|
|
751
|
-
|
|
811
|
+
icon={<IconLock className="h-5 w-5" aria-hidden="true" />}
|
|
812
|
+
title={t("sharePage.privateClip")}
|
|
813
|
+
message={t(
|
|
814
|
+
canRequestAccess
|
|
815
|
+
? "sharePage.privateClipMessage"
|
|
816
|
+
: "sharePage.privateClipSignedOutMessage",
|
|
817
|
+
)}
|
|
752
818
|
action={
|
|
753
|
-
|
|
819
|
+
canRequestAccess ? (
|
|
820
|
+
<Button
|
|
821
|
+
size="sm"
|
|
822
|
+
disabled={requestAccess.isPending || requestSent}
|
|
823
|
+
onClick={() => {
|
|
824
|
+
if (!shareId || requestSent) return;
|
|
825
|
+
requestAccess.mutate(
|
|
826
|
+
{
|
|
827
|
+
accessRequestToken: loaderData.accessRequestToken,
|
|
828
|
+
recordingId: shareId,
|
|
829
|
+
},
|
|
830
|
+
{
|
|
831
|
+
onSuccess: () => {
|
|
832
|
+
setAccessRequestSent(true);
|
|
833
|
+
toast.success(t("sharePage.accessRequestSent"));
|
|
834
|
+
},
|
|
835
|
+
onError: () => {
|
|
836
|
+
toast.error(t("sharePage.accessRequestFailed"));
|
|
837
|
+
},
|
|
838
|
+
},
|
|
839
|
+
);
|
|
840
|
+
}}
|
|
841
|
+
>
|
|
842
|
+
{requestSent
|
|
843
|
+
? t("sharePage.accessRequested")
|
|
844
|
+
: requestAccess.isPending
|
|
845
|
+
? t("sharePage.requestingAccess")
|
|
846
|
+
: t("sharePage.requestAccess")}
|
|
847
|
+
</Button>
|
|
848
|
+
) : shareId ? (
|
|
754
849
|
<Button asChild size="sm">
|
|
755
850
|
<a
|
|
756
|
-
href={buildSignInReturnHref({
|
|
851
|
+
href={buildSignInReturnHref({
|
|
852
|
+
returnTo: `/share/${shareId}`,
|
|
853
|
+
})}
|
|
757
854
|
className="gap-1.5"
|
|
758
855
|
>
|
|
759
856
|
<IconLogin2 className="h-4 w-4 rtl:-scale-x-100" />
|
|
@@ -767,6 +864,18 @@ export default function ShareRoute() {
|
|
|
767
864
|
);
|
|
768
865
|
}
|
|
769
866
|
|
|
867
|
+
if (dataQ.data?.status === 404) {
|
|
868
|
+
return (
|
|
869
|
+
<>
|
|
870
|
+
{agentDiscovery}
|
|
871
|
+
<EndState
|
|
872
|
+
title={t("sharePage.clipUnavailable")}
|
|
873
|
+
message={t("sharePage.clipUnavailableMessage")}
|
|
874
|
+
/>
|
|
875
|
+
</>
|
|
876
|
+
);
|
|
877
|
+
}
|
|
878
|
+
|
|
770
879
|
if (!recording) {
|
|
771
880
|
return (
|
|
772
881
|
<>
|
|
@@ -899,6 +1008,10 @@ export default function ShareRoute() {
|
|
|
899
1008
|
const canDownloadRecording = Boolean(
|
|
900
1009
|
recording.enableDownloads && recording.videoUrl && !isLoomEmbedBacked,
|
|
901
1010
|
);
|
|
1011
|
+
// Loom-backed clips only ever get an "open player" link (not a raw
|
|
1012
|
+
// download), so they're exempt from the enableDownloads gate here.
|
|
1013
|
+
const shareVideoUrl =
|
|
1014
|
+
canDownloadRecording || isLoomEmbedBacked ? recording.videoUrl : null;
|
|
902
1015
|
|
|
903
1016
|
return (
|
|
904
1017
|
<div className="flex min-h-screen max-w-full flex-col overflow-x-hidden bg-background text-foreground lg:h-screen lg:flex-row lg:overflow-hidden">
|
|
@@ -917,14 +1030,26 @@ export default function ShareRoute() {
|
|
|
917
1030
|
</Link>
|
|
918
1031
|
</Button>
|
|
919
1032
|
) : null}
|
|
920
|
-
<div className="min-w-0 flex-1">
|
|
1033
|
+
<div className="flex min-w-0 flex-1 items-center gap-2">
|
|
1034
|
+
{recording.visibility === "private" ? (
|
|
1035
|
+
<span
|
|
1036
|
+
className="inline-flex shrink-0 text-muted-foreground"
|
|
1037
|
+
role="img"
|
|
1038
|
+
title={t("sharePage.privateClip")}
|
|
1039
|
+
>
|
|
1040
|
+
<IconLock className="h-3.5 w-3.5" aria-hidden="true" />
|
|
1041
|
+
<span className="sr-only">{t("sharePage.privateClip")}</span>
|
|
1042
|
+
</span>
|
|
1043
|
+
) : null}
|
|
921
1044
|
{showTitleSkeleton ? (
|
|
922
1045
|
<Skeleton
|
|
923
1046
|
aria-label={t("sharePage.generatingTitle")}
|
|
924
|
-
className="h-4 w-56 max-w-full"
|
|
1047
|
+
className="h-4 w-56 max-w-full flex-1"
|
|
925
1048
|
/>
|
|
926
1049
|
) : (
|
|
927
|
-
<h1 className="truncate text-sm font-medium">
|
|
1050
|
+
<h1 className="min-w-0 flex-1 truncate text-sm font-medium">
|
|
1051
|
+
{visibleTitle}
|
|
1052
|
+
</h1>
|
|
928
1053
|
)}
|
|
929
1054
|
</div>
|
|
930
1055
|
|
|
@@ -995,24 +1120,23 @@ export default function ShareRoute() {
|
|
|
995
1120
|
onDeleted={() => navigate("/library", { replace: true })}
|
|
996
1121
|
/>
|
|
997
1122
|
) : null}
|
|
998
|
-
{viewerCanEdit ? (
|
|
1123
|
+
{viewerCanEdit || canReshareLink ? (
|
|
999
1124
|
<ShareRecordingPopover
|
|
1000
1125
|
recordingId={recording.id}
|
|
1001
1126
|
recordingTitle={recording.title}
|
|
1002
1127
|
initialVisibility={recording.visibility}
|
|
1003
1128
|
initialRole={viewerIsOwner ? "owner" : undefined}
|
|
1004
|
-
videoUrl={
|
|
1129
|
+
videoUrl={shareVideoUrl}
|
|
1005
1130
|
thumbnailUrl={recording.thumbnailUrl}
|
|
1006
1131
|
animatedThumbnailUrl={recording.animatedThumbnailUrl}
|
|
1007
1132
|
isLoomRecording={isLoomEmbedBacked}
|
|
1008
1133
|
hasPassword={Boolean(recording.hasPassword)}
|
|
1134
|
+
viewerReshareOnly={viewerReshareOnly}
|
|
1009
1135
|
>
|
|
1010
|
-
<
|
|
1011
|
-
{
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
{t("sharePage.share")}
|
|
1015
|
-
</Button>
|
|
1136
|
+
<ShareTrigger
|
|
1137
|
+
label={t("sharePage.share")}
|
|
1138
|
+
className="shrink-0"
|
|
1139
|
+
/>
|
|
1016
1140
|
</ShareRecordingPopover>
|
|
1017
1141
|
) : null}
|
|
1018
1142
|
</div>
|
|
@@ -1337,10 +1461,12 @@ function PublicAgentEmptyState({
|
|
|
1337
1461
|
}
|
|
1338
1462
|
|
|
1339
1463
|
function EndState({
|
|
1464
|
+
icon,
|
|
1340
1465
|
title,
|
|
1341
1466
|
message,
|
|
1342
1467
|
action,
|
|
1343
1468
|
}: {
|
|
1469
|
+
icon?: ReactNode;
|
|
1344
1470
|
title: string;
|
|
1345
1471
|
message: string;
|
|
1346
1472
|
action?: ReactNode;
|
|
@@ -1349,6 +1475,11 @@ function EndState({
|
|
|
1349
1475
|
|
|
1350
1476
|
return (
|
|
1351
1477
|
<div className="flex flex-col items-center justify-center min-h-screen bg-background text-foreground px-6">
|
|
1478
|
+
{icon ? (
|
|
1479
|
+
<div className="mb-3 flex h-10 w-10 items-center justify-center rounded-full border border-border bg-muted text-muted-foreground">
|
|
1480
|
+
{icon}
|
|
1481
|
+
</div>
|
|
1482
|
+
) : null}
|
|
1352
1483
|
<h1 className="text-2xl font-semibold mb-2">{title}</h1>
|
|
1353
1484
|
<p className="mb-6 max-w-md text-center text-sm text-muted-foreground">
|
|
1354
1485
|
{message}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "Agent-Native Clips",
|
|
4
4
|
"short_name": "Clips",
|
|
5
5
|
"version": "0.1.18",
|
|
6
|
-
"description": "Start Clips recordings from Chrome, capture optional diagnostics, and preview Clips links on GitHub.",
|
|
6
|
+
"description": "Start Clips recordings from Chrome, capture optional diagnostics, and preview Clips links on GitHub and Jira.",
|
|
7
7
|
"minimum_chrome_version": "116",
|
|
8
8
|
"action": {
|
|
9
9
|
"default_title": "Record with Clips",
|
|
@@ -35,7 +35,11 @@
|
|
|
35
35
|
],
|
|
36
36
|
"content_scripts": [
|
|
37
37
|
{
|
|
38
|
-
"matches": [
|
|
38
|
+
"matches": [
|
|
39
|
+
"https://github.com/*",
|
|
40
|
+
"https://*.atlassian.net/*",
|
|
41
|
+
"https://*.jira.com/*"
|
|
42
|
+
],
|
|
39
43
|
"js": ["assets/github-preview-content.js"],
|
|
40
44
|
"run_at": "document_idle",
|
|
41
45
|
"all_frames": false
|
|
@@ -48,7 +52,11 @@
|
|
|
48
52
|
},
|
|
49
53
|
{
|
|
50
54
|
"resources": ["src/github-preview.html"],
|
|
51
|
-
"matches": [
|
|
55
|
+
"matches": [
|
|
56
|
+
"https://github.com/*",
|
|
57
|
+
"https://*.atlassian.net/*",
|
|
58
|
+
"https://*.jira.com/*"
|
|
59
|
+
]
|
|
52
60
|
}
|
|
53
61
|
],
|
|
54
62
|
"externally_connectable": {
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
(function
|
|
4
|
-
const ROOT_CLASS = "clips-
|
|
5
|
-
const PROCESSED_ATTR = "data-clips-
|
|
1
|
+
// Clips link previews for GitHub and Jira Cloud. The recording overlay remains
|
|
2
|
+
// activeTab-injected so this content script only handles rendered issue text.
|
|
3
|
+
(function clipsLinkPreviewContent() {
|
|
4
|
+
const ROOT_CLASS = "clips-link-preview";
|
|
5
|
+
const PROCESSED_ATTR = "data-clips-link-preview";
|
|
6
6
|
const SOURCE_ATTR = "data-clips-source-url";
|
|
7
7
|
const EXTENSION_ORIGIN = chrome.runtime.getURL("").replace(/\/$/, "");
|
|
8
8
|
const CLIPS_HOSTS = new Set(["clips.agent-native.com"]);
|
|
9
9
|
const LOCAL_HOSTS = new Set(["localhost", "127.0.0.1"]);
|
|
10
10
|
const ROUTE_SEGMENTS = new Set(["r", "share", "embed"]);
|
|
11
|
+
const JIRA_HOST_SUFFIXES = [".atlassian.net", ".jira.com"];
|
|
11
12
|
const MIN_HEIGHT = 120;
|
|
12
13
|
const MAX_HEIGHT = 720;
|
|
13
14
|
|
|
@@ -115,6 +116,34 @@
|
|
|
115
116
|
);
|
|
116
117
|
}
|
|
117
118
|
|
|
119
|
+
function isJiraPage(): boolean {
|
|
120
|
+
const hostname = location.hostname.toLowerCase();
|
|
121
|
+
return JIRA_HOST_SUFFIXES.some((suffix) => hostname.endsWith(suffix));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function isJiraIssueContentAnchor(anchor: HTMLAnchorElement): boolean {
|
|
125
|
+
if (anchor.closest(`.${ROOT_CLASS}`)) return false;
|
|
126
|
+
return Boolean(
|
|
127
|
+
anchor.closest(
|
|
128
|
+
[
|
|
129
|
+
'[data-testid="issue.views.issue-base.foundation.description.text"]',
|
|
130
|
+
'[data-testid^="issue.views.issue-base.foundation.description"]',
|
|
131
|
+
'[data-testid^="issue.views.issue-base.common.comment"]',
|
|
132
|
+
'[data-testid^="issue.views.issue-base.common.comments"]',
|
|
133
|
+
'[data-testid*="comment"]',
|
|
134
|
+
".ak-renderer-document",
|
|
135
|
+
".ak-renderer-document-container",
|
|
136
|
+
].join(","),
|
|
137
|
+
),
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function isSupportedContentAnchor(anchor: HTMLAnchorElement): boolean {
|
|
142
|
+
return isJiraPage()
|
|
143
|
+
? isJiraIssueContentAnchor(anchor)
|
|
144
|
+
: isGitHubMarkdownAnchor(anchor);
|
|
145
|
+
}
|
|
146
|
+
|
|
118
147
|
function previewInsertionTarget(anchor: HTMLAnchorElement): HTMLElement {
|
|
119
148
|
const block = anchor.closest(
|
|
120
149
|
[
|
|
@@ -145,7 +174,7 @@
|
|
|
145
174
|
return;
|
|
146
175
|
}
|
|
147
176
|
|
|
148
|
-
const frameId = `clips-
|
|
177
|
+
const frameId = `clips-link-preview-${++frameCounter}`;
|
|
149
178
|
const wrapper = document.createElement("div");
|
|
150
179
|
wrapper.className = ROOT_CLASS;
|
|
151
180
|
wrapper.id = `${frameId}-wrapper`;
|
|
@@ -195,7 +224,7 @@
|
|
|
195
224
|
for (const anchor of document.querySelectorAll<HTMLAnchorElement>(
|
|
196
225
|
`a[href]:not([${PROCESSED_ATTR}])`,
|
|
197
226
|
)) {
|
|
198
|
-
if (!
|
|
227
|
+
if (!isSupportedContentAnchor(anchor)) continue;
|
|
199
228
|
const link = parseClipsLink(anchor.href);
|
|
200
229
|
if (!link) continue;
|
|
201
230
|
mountPreview(anchor, link);
|
|
@@ -212,7 +241,7 @@
|
|
|
212
241
|
const data = event.data as
|
|
213
242
|
| { source?: string; frameId?: string; height?: number }
|
|
214
243
|
| undefined;
|
|
215
|
-
if (data?.source !== "clips-
|
|
244
|
+
if (data?.source !== "clips-link-preview") return;
|
|
216
245
|
if (!data.frameId || typeof data.height !== "number") return;
|
|
217
246
|
const frame = document.getElementById(data.frameId);
|
|
218
247
|
if (!(frame instanceof HTMLIFrameElement)) return;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { captureExtensionError, initExtensionSentry } from "./sentry";
|
|
2
2
|
|
|
3
|
-
initExtensionSentry("
|
|
3
|
+
initExtensionSentry("link-preview");
|
|
4
4
|
|
|
5
5
|
const DEFAULT_HOST = "clips.agent-native.com";
|
|
6
6
|
const CLIPS_HOSTS = new Set(["clips.agent-native.com"]);
|
|
@@ -80,7 +80,7 @@ function postHeight(frameId: string): void {
|
|
|
80
80
|
const height = document.documentElement.scrollHeight;
|
|
81
81
|
window.parent.postMessage(
|
|
82
82
|
{
|
|
83
|
-
source: "clips-
|
|
83
|
+
source: "clips-link-preview",
|
|
84
84
|
frameId,
|
|
85
85
|
height,
|
|
86
86
|
},
|
|
@@ -415,7 +415,7 @@ async function main(): Promise<void> {
|
|
|
415
415
|
renderPreview(root, preview, await checkPublicPlayable(preview));
|
|
416
416
|
} catch (error) {
|
|
417
417
|
captureExtensionError(error, {
|
|
418
|
-
tags: { surface: "
|
|
418
|
+
tags: { surface: "link-preview" },
|
|
419
419
|
extra: { pageUrl: location.href },
|
|
420
420
|
});
|
|
421
421
|
}
|
|
@@ -22,7 +22,6 @@ import {
|
|
|
22
22
|
isLikelyPhoneMicLabel,
|
|
23
23
|
type AudioInputFallback,
|
|
24
24
|
} from "@shared/media-device-selection";
|
|
25
|
-
import { createMicAudioCleanup } from "@shared/mic-audio-cleanup";
|
|
26
25
|
import { scheduleReadyChime } from "@shared/recording-audio";
|
|
27
26
|
import {
|
|
28
27
|
SCREEN_CAPTURE_FRAME_RATE,
|
|
@@ -435,14 +434,9 @@ async function getCameraStream(deviceId: string): Promise<MediaStream> {
|
|
|
435
434
|
}
|
|
436
435
|
}
|
|
437
436
|
|
|
438
|
-
//
|
|
439
|
-
//
|
|
440
|
-
//
|
|
441
|
-
// the signal and audibly degrades it. We can't detect "is this device doing
|
|
442
|
-
// its own processing" directly, so we use the device label as a heuristic and
|
|
443
|
-
// ask the browser to SKIP its own noise suppression / AGC for those devices
|
|
444
|
-
// (`{ ideal: false }` — a best-effort request, not a hard requirement). Echo
|
|
445
|
-
// cancellation stays on for everything.
|
|
437
|
+
// Let the browser apply its built-in voice processing. Device labels are not a
|
|
438
|
+
// reliable signal for whether a hardware path already processed its audio, so
|
|
439
|
+
// do not branch capture quality on a label heuristic.
|
|
446
440
|
async function chooseFallbackMicDevice(
|
|
447
441
|
requestedLabel: string,
|
|
448
442
|
avoidDeviceIds: string[] = [],
|
|
@@ -459,13 +453,12 @@ async function chooseFallbackMicDevice(
|
|
|
459
453
|
|
|
460
454
|
function voiceFocusedAudioConstraints(
|
|
461
455
|
deviceId: string,
|
|
462
|
-
|
|
456
|
+
_deviceLabel = "",
|
|
463
457
|
): MediaTrackConstraints {
|
|
464
|
-
const skipRedundantProcessing = isLikelyPhoneMicLabel(deviceLabel);
|
|
465
458
|
const audio: MediaTrackConstraints = {
|
|
466
459
|
echoCancellation: true,
|
|
467
|
-
noiseSuppression:
|
|
468
|
-
autoGainControl:
|
|
460
|
+
noiseSuppression: true,
|
|
461
|
+
autoGainControl: true,
|
|
469
462
|
channelCount: 1,
|
|
470
463
|
};
|
|
471
464
|
if (deviceId) audio.deviceId = { exact: deviceId };
|
|
@@ -811,20 +804,11 @@ async function createMixedAudio(
|
|
|
811
804
|
const audioContext = new AudioContext();
|
|
812
805
|
await audioContext.resume().catch(() => undefined);
|
|
813
806
|
const destination = audioContext.createMediaStreamDestination();
|
|
814
|
-
const micCleanup: Array<{ stop: () => void }> = [];
|
|
815
807
|
for (const input of audioInputs) {
|
|
816
|
-
let track = input.track;
|
|
817
|
-
if (input.isMicrophone) {
|
|
818
|
-
const cleanup = createMicAudioCleanup(new MediaStream([track]), {
|
|
819
|
-
audioContext,
|
|
820
|
-
});
|
|
821
|
-
micCleanup.push(cleanup);
|
|
822
|
-
track = cleanup.stream.getAudioTracks()[0] ?? track;
|
|
823
|
-
}
|
|
824
808
|
// One source per track (not per stream) so each input is isolated in the
|
|
825
809
|
// mix graph and can be detached independently.
|
|
826
810
|
const source = audioContext.createMediaStreamSource(
|
|
827
|
-
new MediaStream([track]),
|
|
811
|
+
new MediaStream([input.track]),
|
|
828
812
|
);
|
|
829
813
|
source.connect(destination);
|
|
830
814
|
// A mixed input track can end mid-recording — most commonly the shared
|
|
@@ -832,7 +816,7 @@ async function createMixedAudio(
|
|
|
832
816
|
// just that dead source so the mixed output keeps carrying the surviving
|
|
833
817
|
// inputs (the microphone). Without this the whole mixed destination can go
|
|
834
818
|
// silent, dropping both the tab audio and the mic for the rest of the clip.
|
|
835
|
-
track.addEventListener("ended", () => {
|
|
819
|
+
input.track.addEventListener("ended", () => {
|
|
836
820
|
try {
|
|
837
821
|
source.disconnect();
|
|
838
822
|
} catch {
|
|
@@ -847,7 +831,6 @@ async function createMixedAudio(
|
|
|
847
831
|
cleanup() {
|
|
848
832
|
if (cleanedUp) return;
|
|
849
833
|
cleanedUp = true;
|
|
850
|
-
for (const cleanup of micCleanup) cleanup.stop();
|
|
851
834
|
},
|
|
852
835
|
};
|
|
853
836
|
}
|