@agent-native/core 0.178.1-nightly-20260910175201 → 0.178.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.
Files changed (37) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/CHANGELOG.md +1 -0
  3. package/corpus/templates/calendar/CHANGELOG.md +7 -0
  4. package/corpus/templates/clips/CHANGELOG.md +22 -0
  5. package/corpus/templates/clips/actions/create-intake-agent-link.ts +114 -0
  6. package/corpus/templates/clips/actions/create-intake-recording.ts +208 -0
  7. package/corpus/templates/clips/actions/create-recording-intake-link.ts +85 -0
  8. package/corpus/templates/clips/app/components/bug-report/bug-report-form.tsx +7 -0
  9. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +22 -5
  10. package/corpus/templates/clips/app/hooks/use-video-storage-status.ts +2 -1
  11. package/corpus/templates/clips/app/routes/bug-report.done.tsx +12 -10
  12. package/corpus/templates/clips/app/routes/bug-report.tsx +6 -0
  13. package/corpus/templates/clips/app/routes/record.tsx +269 -100
  14. package/corpus/templates/clips/server/db/schema.ts +24 -0
  15. package/corpus/templates/clips/server/lib/clip-intake.ts +247 -0
  16. package/corpus/templates/clips/server/plugins/auth.ts +8 -0
  17. package/corpus/templates/clips/server/plugins/db.ts +16 -0
  18. package/corpus/templates/clips/server/routes/api/clip-intake.get.ts +61 -0
  19. package/corpus/templates/clips/server/routes/api/clip-intake.post.ts +90 -0
  20. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +19 -4
  21. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +25 -10
  22. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/reset-chunks.post.ts +21 -4
  23. package/corpus/templates/clips/shared/clip-intake.ts +41 -0
  24. package/corpus/templates/clips/shared/finalize-recovery.ts +27 -5
  25. package/corpus/templates/clips/shared/recording-core.ts +4 -4
  26. package/corpus/templates/content/CHANGELOG.md +1 -0
  27. package/corpus/templates/design/CHANGELOG.md +9 -4
  28. package/corpus/templates/factory/CHANGELOG.md +11 -0
  29. package/dist/collab/struct-routes.d.ts +1 -1
  30. package/dist/observability/routes.d.ts +3 -3
  31. package/dist/progress/routes.d.ts +1 -1
  32. package/dist/resources/handlers.d.ts +1 -1
  33. package/dist/secrets/routes.d.ts +3 -3
  34. package/docs/content/template-clips-anonymous-intake.mdx +148 -0
  35. package/docs/content/template-clips-developers.mdx +4 -3
  36. package/docs/content/template-clips.mdx +1 -0
  37. package/package.json +3 -3
package/corpus/README.md CHANGED
@@ -31,4 +31,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
31
31
 
32
32
  ## Generated Counts
33
33
 
34
- - template files: 9127
34
+ - template files: 9134
@@ -7,6 +7,7 @@ time from the command menu (Cmd+K → "What's new") or from Settings.
7
7
 
8
8
  ### Improved
9
9
 
10
+ - Faster analytics dashboard loading
10
11
  - Collaborator avatars use a slimmer border.
11
12
 
12
13
  ## 2026-09-08
@@ -3,10 +3,17 @@
3
3
  All notable user-facing changes to Agent-Native Calendar are documented here. Open it any
4
4
  time from the command menu (Cmd+K → "What's new") or from Settings.
5
5
 
6
+ ## 2026-09-10
7
+
8
+ ### Fixed
9
+
10
+ - Calendar keeps shared and overlaid events read-only instead of reporting a false deletion
11
+
6
12
  ## 2026-09-09
7
13
 
8
14
  ### Improved
9
15
 
16
+ - Calendar's command menu surfaces the right actions for booking links and settings
10
17
  - Calendar can color Google events by meeting type again
11
18
  - Connected account avatars use a slimmer border.
12
19
 
@@ -3,12 +3,34 @@
3
3
  All notable user-facing changes to Clips are documented here. Open it any time
4
4
  from the command menu (Cmd+K → "What's new") or from Settings.
5
5
 
6
+ ## 2026-09-10
7
+
8
+ ### Fixed
9
+
10
+ - Builder and other MCP connections now open setup in a new tab.
11
+
6
12
  ## 2026-09-09
7
13
 
8
14
  ### Improved
9
15
 
10
16
  - Attendee and viewer avatars use a slimmer border.
11
17
 
18
+ ### Fixed
19
+
20
+ - The Agent sidebar now fits below the app toolbar without clipping its composer.
21
+ - Desktop recorder switches now keep their checked state clear and legible in dark mode.
22
+ - Recording pages now open the same contextual Agent panel used throughout the app.
23
+
24
+ ## 2026-09-08
25
+
26
+ ### Improved
27
+
28
+ - The Cmd+K command menu now searches and navigates across recordings, meetings, dictations, folders, and spaces.
29
+
30
+ ### Fixed
31
+
32
+ - Fixed desktop comment threads by keeping them in a full-height viewer tab, polished the side-panel tabs and spacing, added move, minimize, and close controls to desktop Clips windows, corrected the shared New recording action’s split-button corners, made library sub-item selection clear in the sidebar, and added visible folder tiles plus scoped recording actions to library and space views.
33
+
12
34
  ## 2026-09-04
13
35
 
14
36
  ### Fixed
@@ -0,0 +1,114 @@
1
+ import { defineAction, fail } from "@agent-native/core/action";
2
+ import {
3
+ getRequestContext,
4
+ getRequestUserEmail,
5
+ runWithRequestContext,
6
+ } from "@agent-native/core/server";
7
+ import { and, eq } from "drizzle-orm";
8
+ import { z } from "zod";
9
+
10
+ import { getDb, schema } from "../server/db/index.js";
11
+ import { findOwnedClipIntakeSession } from "../server/lib/clip-intake.js";
12
+ import {
13
+ getActiveOrganizationId,
14
+ ownerEmailMatches,
15
+ requireOrganizationAccess,
16
+ } from "../server/lib/recordings.js";
17
+ import { BUG_REPORT_AGENT_ACCESS_TTL_SECONDS } from "../shared/bug-report.js";
18
+ import createRecordingAgentLink from "./create-recording-agent-link.js";
19
+
20
+ export default defineAction({
21
+ description:
22
+ "Create a temporary read-only agent link for a completed Clips intake recording. This is an authenticated host-side exchange; the anonymous intake bearer cannot mint read access.",
23
+ agentTool: false,
24
+ requiresAuth: true,
25
+ http: { method: "POST" },
26
+ maxBodyBytes: 16 * 1024,
27
+ schema: z.object({
28
+ intakeId: z.string().min(16).max(80),
29
+ recordingId: z.string().min(1).max(200),
30
+ ttlSeconds: z
31
+ .number()
32
+ .int()
33
+ .positive()
34
+ .max(BUG_REPORT_AGENT_ACCESS_TTL_SECONDS)
35
+ .optional(),
36
+ }),
37
+ run: async (args) => {
38
+ const ownerEmail = getRequestUserEmail();
39
+ if (!ownerEmail) {
40
+ fail("Sign in to exchange an intake for agent access.", {
41
+ errorCode: "authentication_required",
42
+ statusCode: 401,
43
+ });
44
+ }
45
+ const organizationId = await getActiveOrganizationId();
46
+ const access = await requireOrganizationAccess(organizationId);
47
+ const session = await findOwnedClipIntakeSession(
48
+ args.intakeId,
49
+ access.email,
50
+ access.organizationId,
51
+ );
52
+ if (
53
+ !session ||
54
+ session.recordingId !== args.recordingId ||
55
+ session.status !== "completed"
56
+ ) {
57
+ fail("This intake recording is not available.", {
58
+ errorCode: "intake_recording_unavailable",
59
+ statusCode: 404,
60
+ });
61
+ }
62
+
63
+ const [recording] = await getDb()
64
+ .select({
65
+ id: schema.recordings.id,
66
+ status: schema.recordings.status,
67
+ videoUrl: schema.recordings.videoUrl,
68
+ archivedAt: schema.recordings.archivedAt,
69
+ trashedAt: schema.recordings.trashedAt,
70
+ })
71
+ .from(schema.recordings)
72
+ .where(
73
+ and(
74
+ eq(schema.recordings.id, args.recordingId),
75
+ ownerEmailMatches(schema.recordings.ownerEmail, session.ownerEmail),
76
+ eq(schema.recordings.organizationId, session.organizationId),
77
+ ),
78
+ )
79
+ .limit(1);
80
+ if (
81
+ !recording ||
82
+ recording.status !== "ready" ||
83
+ !recording.videoUrl ||
84
+ recording.archivedAt ||
85
+ recording.trashedAt
86
+ ) {
87
+ fail("This intake recording is not available yet.", {
88
+ errorCode: "intake_recording_not_ready",
89
+ statusCode: 409,
90
+ });
91
+ }
92
+
93
+ const requestOrigin = getRequestContext()?.requestOrigin;
94
+ return runWithRequestContext(
95
+ {
96
+ userEmail: session.ownerEmail,
97
+ orgId: session.organizationId,
98
+ requestOrigin,
99
+ },
100
+ () =>
101
+ createRecordingAgentLink.run(
102
+ {
103
+ recordingId: args.recordingId,
104
+ ttlSeconds: args.ttlSeconds,
105
+ },
106
+ {
107
+ caller: "http",
108
+ userEmail: session.ownerEmail,
109
+ orgId: session.organizationId,
110
+ },
111
+ ),
112
+ );
113
+ },
114
+ });
@@ -0,0 +1,208 @@
1
+ import { defineAction, fail } from "@agent-native/core/action";
2
+ import { runWithRequestContext } from "@agent-native/core/server";
3
+ import { z } from "zod";
4
+
5
+ import {
6
+ abandonClipIntake,
7
+ attachClipIntakeRecording,
8
+ claimClipIntakeRecording,
9
+ findClipIntakeSession,
10
+ findRecoverableClipIntakeRecording,
11
+ releaseClipIntakeCreation,
12
+ } from "../server/lib/clip-intake.js";
13
+ import { getResumableSession } from "../server/lib/resumable-session.js";
14
+ import { BUG_REPORT_SEVERITIES } from "../shared/bug-report.js";
15
+ import { buildClipIntakeUrl } from "../shared/clip-intake.js";
16
+ import createRecording from "./create-recording.js";
17
+ import { createRecordingSchema } from "./lib/create-recording-schema.js";
18
+ import saveBugReportContext from "./save-bug-report-context.js";
19
+ import trashRecording from "./trash-recording.js";
20
+
21
+ const bugReportSchema = z.object({
22
+ projectId: z.string().max(120).nullish(),
23
+ title: z.string().max(500).nullish(),
24
+ description: z.string().max(5_000).nullish(),
25
+ severity: z.enum(BUG_REPORT_SEVERITIES).default("normal"),
26
+ sourceUrl: z.string().max(8_000).nullish(),
27
+ pageTitle: z.string().max(500).nullish(),
28
+ appVersion: z.string().max(120).nullish(),
29
+ environment: z.string().max(120).nullish(),
30
+ reporterEmail: z.string().max(320).nullish(),
31
+ reporterName: z.string().max(200).nullish(),
32
+ reporterId: z.string().max(200).nullish(),
33
+ metadata: z.record(z.string(), z.unknown()).nullish(),
34
+ });
35
+
36
+ const intakeRecordingSchema = createRecordingSchema
37
+ .omit({ id: true, folderId: true, spaceIds: true, organizationId: true })
38
+ .extend({
39
+ intakeId: z.string().min(16).max(80),
40
+ intakeToken: z.string().min(1).max(4096),
41
+ bugReport: bugReportSchema.nullish(),
42
+ });
43
+
44
+ function intakeUploadUrls(
45
+ intakeId: string,
46
+ intakeToken: string,
47
+ recordingId: string,
48
+ ) {
49
+ return {
50
+ uploadChunkUrl: buildClipIntakeUrl("/api/clip-intake", {
51
+ recordingId,
52
+ operation: "chunk",
53
+ intakeId,
54
+ token: intakeToken,
55
+ }),
56
+ resetChunksUrl: buildClipIntakeUrl("/api/clip-intake", {
57
+ recordingId,
58
+ operation: "reset",
59
+ intakeId,
60
+ token: intakeToken,
61
+ }),
62
+ abortUrl: buildClipIntakeUrl("/api/clip-intake", {
63
+ recordingId,
64
+ operation: "abort",
65
+ intakeId,
66
+ token: intakeToken,
67
+ }),
68
+ };
69
+ }
70
+
71
+ export default defineAction({
72
+ description:
73
+ "Create the one recording allowed by a signed Clips intake URL. This is a write-only anonymous capability and never grants library or agent read access.",
74
+ agentTool: false,
75
+ requiresAuth: false,
76
+ http: { method: "POST" },
77
+ maxBodyBytes: 64 * 1024,
78
+ schema: intakeRecordingSchema,
79
+ run: async (args) => {
80
+ const claimed = await claimClipIntakeRecording(
81
+ args.intakeId,
82
+ args.intakeToken,
83
+ );
84
+ if (!claimed) {
85
+ const existingSession = await findClipIntakeSession(
86
+ args.intakeId,
87
+ args.intakeToken,
88
+ );
89
+ const existing = existingSession
90
+ ? await findRecoverableClipIntakeRecording(existingSession)
91
+ : null;
92
+ if (existing && existingSession) {
93
+ const resumableSession = await getResumableSession(existing.id);
94
+ return {
95
+ id: existing.id,
96
+ organizationId: existing.organizationId,
97
+ status: "uploading" as const,
98
+ uploadMode: resumableSession ? ("streaming" as const) : "buffered",
99
+ ...intakeUploadUrls(args.intakeId, args.intakeToken, existing.id),
100
+ };
101
+ }
102
+ fail("This intake URL has expired or has already been used.", {
103
+ errorCode: "intake_unavailable",
104
+ statusCode: 409,
105
+ });
106
+ }
107
+
108
+ const { intakeId, intakeToken, bugReport, ...recordingArgs } = args;
109
+ let created: Awaited<ReturnType<typeof createRecording.run>>;
110
+ try {
111
+ created = await runWithRequestContext(
112
+ {
113
+ userEmail: claimed.ownerEmail,
114
+ orgId: claimed.organizationId,
115
+ },
116
+ () =>
117
+ createRecording.run(
118
+ {
119
+ ...recordingArgs,
120
+ organizationId: claimed.organizationId,
121
+ visibility: "private",
122
+ },
123
+ {
124
+ caller: "http",
125
+ userEmail: claimed.ownerEmail,
126
+ orgId: claimed.organizationId,
127
+ },
128
+ ),
129
+ );
130
+ } catch (error) {
131
+ await releaseClipIntakeCreation(intakeId);
132
+ throw error;
133
+ }
134
+
135
+ const cleanupCreatedRecording = async () => {
136
+ await abandonClipIntake(intakeId).catch((cleanupError: unknown) => {
137
+ console.warn("[clip-intake] session cleanup failed:", cleanupError);
138
+ });
139
+ await Promise.resolve(
140
+ runWithRequestContext(
141
+ {
142
+ userEmail: claimed.ownerEmail,
143
+ orgId: claimed.organizationId,
144
+ },
145
+ () =>
146
+ trashRecording.run(
147
+ { id: created.id, skipIfReady: true },
148
+ {
149
+ caller: "http",
150
+ userEmail: claimed.ownerEmail,
151
+ orgId: claimed.organizationId,
152
+ },
153
+ ),
154
+ ),
155
+ ).catch((cleanupError: unknown) => {
156
+ console.warn("[clip-intake] recording cleanup failed:", cleanupError);
157
+ });
158
+ };
159
+
160
+ if (bugReport) {
161
+ try {
162
+ await runWithRequestContext(
163
+ {
164
+ userEmail: claimed.ownerEmail,
165
+ orgId: claimed.organizationId,
166
+ },
167
+ () =>
168
+ saveBugReportContext.run(
169
+ {
170
+ recordingId: created.id,
171
+ ...bugReport,
172
+ metadata: bugReport.metadata ?? undefined,
173
+ },
174
+ {
175
+ caller: "http",
176
+ userEmail: claimed.ownerEmail,
177
+ orgId: claimed.organizationId,
178
+ },
179
+ ),
180
+ );
181
+ } catch {
182
+ await cleanupCreatedRecording();
183
+ fail(
184
+ "Could not save the bug report context. This intake was not completed. Try again.",
185
+ {
186
+ errorCode: "intake_context_unavailable",
187
+ statusCode: 503,
188
+ },
189
+ );
190
+ }
191
+ }
192
+
193
+ try {
194
+ await attachClipIntakeRecording(intakeId, created.id);
195
+ } catch (error) {
196
+ await cleanupCreatedRecording();
197
+ throw error;
198
+ }
199
+
200
+ return {
201
+ id: created.id,
202
+ organizationId: created.organizationId,
203
+ status: created.status,
204
+ uploadMode: created.uploadMode,
205
+ ...intakeUploadUrls(intakeId, intakeToken, created.id),
206
+ };
207
+ },
208
+ });
@@ -0,0 +1,85 @@
1
+ import { defineAction } from "@agent-native/core/action";
2
+ import {
3
+ buildAgentAccessUrl,
4
+ createScopedAgentAccessGrant,
5
+ getAppProductionUrl,
6
+ getRequestContext,
7
+ getRequestUserEmail,
8
+ } from "@agent-native/core/server";
9
+ import { z } from "zod";
10
+
11
+ import { getDb, schema } from "../server/db/index.js";
12
+ import { getServerAppBasePath } from "../server/lib/public-agent-context.js";
13
+ import {
14
+ getActiveOrganizationId,
15
+ nanoid,
16
+ requireOrganizationAccess,
17
+ } from "../server/lib/recordings.js";
18
+ import {
19
+ CLIP_INTAKE_DEFAULT_TTL_SECONDS,
20
+ CLIP_INTAKE_ID_PARAM,
21
+ CLIP_INTAKE_MAX_TTL_SECONDS,
22
+ CLIP_INTAKE_RESOURCE_KIND,
23
+ CLIP_INTAKE_TOKEN_PARAM,
24
+ } from "../shared/clip-intake.js";
25
+
26
+ function appOrigin(): string {
27
+ const origin = getRequestContext()?.requestOrigin || getAppProductionUrl();
28
+ try {
29
+ return new URL(origin).origin;
30
+ } catch {
31
+ return "http://localhost:3000";
32
+ }
33
+ }
34
+
35
+ export default defineAction({
36
+ description:
37
+ "Create a short-lived, one-recording Clips intake URL. An anonymous visitor can use it to submit one private recording, but the URL cannot read the Clips library or call agent APIs.",
38
+ agentTool: false,
39
+ schema: z.object({
40
+ ttlSeconds: z
41
+ .number()
42
+ .int()
43
+ .min(60)
44
+ .max(CLIP_INTAKE_MAX_TTL_SECONDS)
45
+ .optional()
46
+ .describe("Intake URL lifetime in seconds. Defaults to one hour."),
47
+ }),
48
+ run: async (args) => {
49
+ const ownerEmail = getRequestUserEmail();
50
+ if (!ownerEmail) throw new Error("Sign in to create an intake URL.");
51
+ const organizationId = await getActiveOrganizationId();
52
+ const access = await requireOrganizationAccess(organizationId);
53
+ const intakeId = nanoid(24);
54
+ const ttlSeconds = args.ttlSeconds ?? CLIP_INTAKE_DEFAULT_TTL_SECONDS;
55
+ const expiresAt = new Date(Date.now() + ttlSeconds * 1000).toISOString();
56
+
57
+ await getDb().insert(schema.clipIntakeSessions).values({
58
+ id: intakeId,
59
+ ownerEmail: access.email,
60
+ organizationId: access.organizationId,
61
+ status: "open",
62
+ expiresAt,
63
+ createdAt: new Date().toISOString(),
64
+ updatedAt: new Date().toISOString(),
65
+ });
66
+
67
+ const grant = createScopedAgentAccessGrant({
68
+ resourceKind: CLIP_INTAKE_RESOURCE_KIND,
69
+ resourceId: intakeId,
70
+ ttlSeconds,
71
+ });
72
+ const intakePath = `/bug-report?${new URLSearchParams({
73
+ [CLIP_INTAKE_ID_PARAM]: intakeId,
74
+ }).toString()}`;
75
+ const url = buildAgentAccessUrl({
76
+ path: intakePath,
77
+ origin: appOrigin(),
78
+ basePath: getServerAppBasePath(),
79
+ token: grant.token,
80
+ tokenParam: CLIP_INTAKE_TOKEN_PARAM,
81
+ });
82
+
83
+ return { intakeId, url, expiresAt: grant.expiresAt, ttlSeconds };
84
+ },
85
+ });
@@ -5,6 +5,7 @@ import {
5
5
  type BugReportContext,
6
6
  type BugReportSeverity,
7
7
  } from "@shared/bug-report";
8
+ import type { ClipIntakeParams } from "@shared/clip-intake";
8
9
  import { IconBug, IconShieldCheck } from "@tabler/icons-react";
9
10
  import { useEffect, useState } from "react";
10
11
 
@@ -24,6 +25,7 @@ import { cn } from "@/lib/utils";
24
25
  interface BugReportFormProps {
25
26
  className?: string;
26
27
  initialContext?: BugReportContext | null;
28
+ intake?: ClipIntakeParams | null;
27
29
  onRecorderOpened?: (recorderWindow: Window | null) => void;
28
30
  onRecordingStarted?: () => void;
29
31
  }
@@ -45,6 +47,7 @@ function openRecorder(url: string): Window | null {
45
47
  export function BugReportForm({
46
48
  className,
47
49
  initialContext,
50
+ intake,
48
51
  onRecorderOpened,
49
52
  onRecordingStarted,
50
53
  }: BugReportFormProps) {
@@ -91,6 +94,10 @@ export function BugReportForm({
91
94
  params.set("intent", "bug-report");
92
95
  params.set("mode", "screen");
93
96
  params.set("surface", "browser");
97
+ if (intake) {
98
+ params.set("clip_intake_id", intake.intakeId);
99
+ params.set("clip_intake", intake.token);
100
+ }
94
101
  const recorderWindow = openRecorder(
95
102
  `${appBasePath()}/record?${params.toString()}`,
96
103
  );
@@ -1,6 +1,7 @@
1
1
  import { trackEvent } from "@agent-native/core/client/analytics";
2
2
  import { captureClientException } from "@agent-native/core/client/analytics";
3
3
  import { appBasePath } from "@agent-native/core/client/api-path";
4
+ import { redactBrowserDiagnosticString } from "@shared/browser-diagnostics";
4
5
  import { waitForAcceptedRecordingAfterFinalizeError } from "@shared/finalize-recovery";
5
6
  import {
6
7
  chooseFallbackAudioInput,
@@ -140,6 +141,8 @@ export interface RecorderEngineOptions {
140
141
  uploadUrl?: string;
141
142
  /** Abort URL. Default `/api/uploads/:id/abort`. */
142
143
  abortUrl?: string;
144
+ /** Reset-chunks URL. Defaults to the authenticated recording route. */
145
+ resetUrl?: string;
143
146
  /**
144
147
  * Upload strategy returned by create-recording.
145
148
  * `"streaming"` — server has a resumable session; engine flushes aligned
@@ -505,7 +508,10 @@ function fetchAbortError(signal: AbortSignal, err: unknown): Error {
505
508
 
506
509
  export class RecorderEngine {
507
510
  readonly opts: Required<
508
- Pick<RecorderEngineOptions, "chunkIntervalMs" | "uploadUrl" | "abortUrl">
511
+ Pick<
512
+ RecorderEngineOptions,
513
+ "chunkIntervalMs" | "uploadUrl" | "abortUrl" | "resetUrl"
514
+ >
509
515
  > &
510
516
  RecorderEngineOptions;
511
517
 
@@ -617,6 +623,11 @@ export class RecorderEngine {
617
623
  abortUrl:
618
624
  options.abortUrl ??
619
625
  `${appBasePath()}/api/uploads/${options.recordingId}/abort`,
626
+ resetUrl:
627
+ options.resetUrl ??
628
+ (options.uploadUrl
629
+ ? options.uploadUrl.replace(/\/chunk(?:\?.*)?$/, "/reset-chunks")
630
+ : `${appBasePath()}/api/uploads/${options.recordingId}/reset-chunks`),
620
631
  ...options,
621
632
  };
622
633
  }
@@ -1149,11 +1160,17 @@ export class RecorderEngine {
1149
1160
  recordingId: string;
1150
1161
  uploadUrl: string;
1151
1162
  abortUrl: string;
1163
+ resetUrl?: string;
1152
1164
  uploadMode?: UploadMode;
1153
1165
  }): void {
1154
1166
  this.opts.recordingId = target.recordingId;
1155
1167
  this.opts.uploadUrl = target.uploadUrl;
1156
1168
  this.opts.abortUrl = target.abortUrl;
1169
+ this.opts.resetUrl =
1170
+ target.resetUrl ??
1171
+ (target.uploadUrl.endsWith("/chunk")
1172
+ ? target.uploadUrl.slice(0, -"/chunk".length) + "/reset-chunks"
1173
+ : `${appBasePath()}/api/uploads/${target.recordingId}/reset-chunks`);
1157
1174
  this.opts.uploadMode = target.uploadMode ?? "buffered";
1158
1175
  this.uploadGenerationId = null;
1159
1176
  }
@@ -1614,9 +1631,7 @@ export class RecorderEngine {
1614
1631
  compression: CompressionUploadMeta | null,
1615
1632
  signal?: AbortSignal,
1616
1633
  ): Promise<UploadMode> {
1617
- const resetUrl = `${appBasePath()}/api/uploads/${
1618
- this.opts.recordingId
1619
- }/reset-chunks`;
1634
+ const resetUrl = this.opts.resetUrl;
1620
1635
  const uploadMimeType = compression?.outputMimeType || this.mimeType;
1621
1636
  let resetRes: Response;
1622
1637
  try {
@@ -2404,7 +2419,9 @@ export class RecorderEngine {
2404
2419
  httpStatus: String(res.status),
2405
2420
  },
2406
2421
  extra: {
2407
- url,
2422
+ url: redactBrowserDiagnosticString(url, {
2423
+ redactQueryValues: true,
2424
+ }),
2408
2425
  status: res.status,
2409
2426
  statusText: res.statusText,
2410
2427
  responseBodyTail: text?.slice(0, 2000) ?? "",
@@ -53,10 +53,11 @@ export async function fetchVideoStorageStatus(): Promise<VideoStorageStatus> {
53
53
  };
54
54
  }
55
55
 
56
- export function useVideoStorageStatus() {
56
+ export function useVideoStorageStatus(enabled = true) {
57
57
  return useQuery({
58
58
  queryKey: VIDEO_STORAGE_STATUS_KEY,
59
59
  queryFn: fetchVideoStorageStatus,
60
+ enabled,
60
61
  staleTime: 60_000,
61
62
  });
62
63
  }
@@ -8,6 +8,7 @@ import {
8
8
  createBugReportSubmissionMessage,
9
9
  type BugReportAgentLink,
10
10
  } from "@shared/bug-report";
11
+ import { parseClipIntakeParams } from "@shared/clip-intake";
11
12
  import {
12
13
  IconArrowLeft,
13
14
  IconCheck,
@@ -43,6 +44,7 @@ export default function BugReportDoneRoute() {
43
44
  );
44
45
  const recordingId = params.get("recordingId")?.trim() || null;
45
46
  const returnUrl = params.get("returnUrl")?.trim() || null;
47
+ const intake = useMemo(() => parseClipIntakeParams(params), [params]);
46
48
 
47
49
  const recordingUrl = recordingId
48
50
  ? absoluteAppUrl(`/r/${encodeURIComponent(recordingId)}`)
@@ -55,19 +57,19 @@ export default function BugReportDoneRoute() {
55
57
  let cancelled = false;
56
58
  void (async () => {
57
59
  let agentLink: BugReportAgentLink | null = null;
58
- try {
59
- agentLink = (await callAction(
60
- "create-recording-agent-link" as any,
61
- {
60
+ if (!intake) {
61
+ try {
62
+ agentLink = (await callAction("create-recording-agent-link", {
62
63
  recordingId,
63
64
  ttlSeconds: BUG_REPORT_AGENT_ACCESS_TTL_SECONDS,
64
- } as any,
65
- )) as BugReportAgentLink;
66
- } catch {
67
- agentLink = null;
65
+ })) as BugReportAgentLink;
66
+ } catch (error) {
67
+ // The completion message keeps access explicitly unavailable when
68
+ // the authenticated exchange cannot be completed.
69
+ console.warn("[bug-report] agent link unavailable:", error);
70
+ }
68
71
  }
69
72
  if (cancelled) return;
70
-
71
73
  const message = createBugReportSubmissionMessage({
72
74
  recordingId,
73
75
  recordingUrl,
@@ -88,7 +90,7 @@ export default function BugReportDoneRoute() {
88
90
  return () => {
89
91
  cancelled = true;
90
92
  };
91
- }, [embedUrl, recordingId, recordingUrl, returnUrl]);
93
+ }, [embedUrl, intake, recordingId, recordingUrl, returnUrl]);
92
94
 
93
95
  const copyRecordingUrl = async () => {
94
96
  if (!recordingUrl) return;
@@ -3,6 +3,7 @@ import {
3
3
  isBugReportSubmissionMessage,
4
4
  parseBugReportContext,
5
5
  } from "@shared/bug-report";
6
+ import { parseClipIntakeParams } from "@shared/clip-intake";
6
7
  import { useEffect, useMemo, useRef } from "react";
7
8
  import { useLocation, useOutlet } from "react-router";
8
9
 
@@ -33,6 +34,10 @@ export default function BugReportRoute() {
33
34
  const params = new URLSearchParams(location.search);
34
35
  return parseBugReportContext(params, { allowLoose: true });
35
36
  }, [location.search]);
37
+ const intake = useMemo(
38
+ () => parseClipIntakeParams(new URLSearchParams(location.search)),
39
+ [location.search],
40
+ );
36
41
 
37
42
  const recorderWindowRef = useRef<Window | null>(null);
38
43
  const hostOrigin = originFor(
@@ -66,6 +71,7 @@ export default function BugReportRoute() {
66
71
  <section className="rounded-lg border bg-card p-4 shadow-sm sm:p-5">
67
72
  <BugReportForm
68
73
  initialContext={initialContext}
74
+ intake={intake}
69
75
  onRecorderOpened={(recorderWindow) => {
70
76
  recorderWindowRef.current = recorderWindow;
71
77
  }}