@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.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/CHANGELOG.md +1 -0
- package/corpus/templates/calendar/CHANGELOG.md +7 -0
- package/corpus/templates/clips/CHANGELOG.md +22 -0
- package/corpus/templates/clips/actions/create-intake-agent-link.ts +114 -0
- package/corpus/templates/clips/actions/create-intake-recording.ts +208 -0
- package/corpus/templates/clips/actions/create-recording-intake-link.ts +85 -0
- package/corpus/templates/clips/app/components/bug-report/bug-report-form.tsx +7 -0
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +22 -5
- package/corpus/templates/clips/app/hooks/use-video-storage-status.ts +2 -1
- package/corpus/templates/clips/app/routes/bug-report.done.tsx +12 -10
- package/corpus/templates/clips/app/routes/bug-report.tsx +6 -0
- package/corpus/templates/clips/app/routes/record.tsx +269 -100
- package/corpus/templates/clips/server/db/schema.ts +24 -0
- package/corpus/templates/clips/server/lib/clip-intake.ts +247 -0
- package/corpus/templates/clips/server/plugins/auth.ts +8 -0
- package/corpus/templates/clips/server/plugins/db.ts +16 -0
- package/corpus/templates/clips/server/routes/api/clip-intake.get.ts +61 -0
- package/corpus/templates/clips/server/routes/api/clip-intake.post.ts +90 -0
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +19 -4
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +25 -10
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/reset-chunks.post.ts +21 -4
- package/corpus/templates/clips/shared/clip-intake.ts +41 -0
- package/corpus/templates/clips/shared/finalize-recovery.ts +27 -5
- package/corpus/templates/clips/shared/recording-core.ts +4 -4
- package/corpus/templates/content/CHANGELOG.md +1 -0
- package/corpus/templates/design/CHANGELOG.md +9 -4
- package/corpus/templates/factory/CHANGELOG.md +11 -0
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +3 -3
- package/docs/content/template-clips-anonymous-intake.mdx +148 -0
- package/docs/content/template-clips-developers.mdx +4 -3
- package/docs/content/template-clips.mdx +1 -0
- package/package.json +3 -3
|
@@ -10,7 +10,7 @@ export type RecoveredReadyRecording = {
|
|
|
10
10
|
id: string;
|
|
11
11
|
recordingId: string;
|
|
12
12
|
status: "ready";
|
|
13
|
-
videoUrl
|
|
13
|
+
videoUrl?: string;
|
|
14
14
|
durationMs?: number;
|
|
15
15
|
width?: number;
|
|
16
16
|
height?: number;
|
|
@@ -97,6 +97,11 @@ export function publicRecordingStatusUrl(
|
|
|
97
97
|
recordingId: string,
|
|
98
98
|
): string {
|
|
99
99
|
const url = absoluteUploadUrl(uploadUrl);
|
|
100
|
+
if (url.pathname.endsWith("/api/clip-intake")) {
|
|
101
|
+
url.searchParams.set("recordingId", recordingId);
|
|
102
|
+
url.searchParams.set("operation", "status");
|
|
103
|
+
return maybeRelativeUrl(url, uploadUrl);
|
|
104
|
+
}
|
|
100
105
|
const match = url.pathname.match(/^(.*)\/api\/uploads\/[^/]+\/chunk$/);
|
|
101
106
|
const basePath = match?.[1] ?? "";
|
|
102
107
|
url.pathname = `${basePath}/api/public-recording`;
|
|
@@ -132,7 +137,10 @@ function optionalBoolean(value: unknown): boolean | undefined {
|
|
|
132
137
|
export function readyRecordingFromPublicPayload(
|
|
133
138
|
payload: unknown,
|
|
134
139
|
fallbackRecordingId: string,
|
|
135
|
-
options: {
|
|
140
|
+
options: {
|
|
141
|
+
acceptPendingVerification?: boolean;
|
|
142
|
+
acceptReadyWithoutVideoUrl?: boolean;
|
|
143
|
+
} = {},
|
|
136
144
|
): ProbeResult {
|
|
137
145
|
const root =
|
|
138
146
|
payload && typeof payload === "object"
|
|
@@ -178,7 +186,10 @@ export function readyRecordingFromPublicPayload(
|
|
|
178
186
|
|
|
179
187
|
const videoUrl =
|
|
180
188
|
typeof recording.videoUrl === "string" ? recording.videoUrl : "";
|
|
181
|
-
if (
|
|
189
|
+
if (
|
|
190
|
+
status !== "ready" ||
|
|
191
|
+
(!videoUrl && !options.acceptReadyWithoutVideoUrl)
|
|
192
|
+
) {
|
|
182
193
|
return { ready: false, terminal: false, status };
|
|
183
194
|
}
|
|
184
195
|
|
|
@@ -195,7 +206,7 @@ export function readyRecordingFromPublicPayload(
|
|
|
195
206
|
id,
|
|
196
207
|
recordingId: id,
|
|
197
208
|
status: "ready",
|
|
198
|
-
videoUrl,
|
|
209
|
+
...(videoUrl ? { videoUrl } : {}),
|
|
199
210
|
durationMs: optionalNumber(recording.durationMs),
|
|
200
211
|
width: optionalNumber(recording.width),
|
|
201
212
|
height: optionalNumber(recording.height),
|
|
@@ -235,6 +246,7 @@ export async function waitForReadyRecordingAfterFinalizeError(args: {
|
|
|
235
246
|
intervalMs?: number;
|
|
236
247
|
fetchTimeoutMs?: number;
|
|
237
248
|
acceptPendingVerification?: boolean;
|
|
249
|
+
acceptReadyWithoutVideoUrl?: boolean;
|
|
238
250
|
signal?: AbortSignal;
|
|
239
251
|
}): Promise<RecoveredFinalizeRecording | null> {
|
|
240
252
|
const fetchImpl = args.fetchImpl ?? fetch;
|
|
@@ -250,10 +262,19 @@ export async function waitForReadyRecordingAfterFinalizeError(args: {
|
|
|
250
262
|
const attempts = Math.max(1, Math.ceil(timeoutMs / intervalMs));
|
|
251
263
|
let url: string;
|
|
252
264
|
let authenticatedUrl: string | null = null;
|
|
265
|
+
let acceptReadyWithoutVideoUrl = false;
|
|
253
266
|
try {
|
|
254
267
|
url = publicRecordingStatusUrl(args.uploadUrl, args.recordingId);
|
|
268
|
+
const isClipIntake = new URL(
|
|
269
|
+
args.uploadUrl,
|
|
270
|
+
typeof globalThis.location?.origin === "string"
|
|
271
|
+
? globalThis.location.origin
|
|
272
|
+
: "http://localhost",
|
|
273
|
+
).pathname.endsWith("/api/clip-intake");
|
|
274
|
+
acceptReadyWithoutVideoUrl =
|
|
275
|
+
args.acceptReadyWithoutVideoUrl ?? isClipIntake;
|
|
255
276
|
authenticatedUrl =
|
|
256
|
-
args.authToken || args.preferAuthenticated
|
|
277
|
+
!isClipIntake && (args.authToken || args.preferAuthenticated)
|
|
257
278
|
? authenticatedRecordingStatusUrl(args.uploadUrl, args.recordingId)
|
|
258
279
|
: null;
|
|
259
280
|
} catch {
|
|
@@ -298,6 +319,7 @@ export async function waitForReadyRecordingAfterFinalizeError(args: {
|
|
|
298
319
|
args.recordingId,
|
|
299
320
|
{
|
|
300
321
|
acceptPendingVerification: args.acceptPendingVerification,
|
|
322
|
+
acceptReadyWithoutVideoUrl,
|
|
301
323
|
},
|
|
302
324
|
);
|
|
303
325
|
if (probe.ready) return probe.result;
|
|
@@ -127,12 +127,12 @@ export function chunkUploadQuery(params: ChunkUploadParams): string {
|
|
|
127
127
|
return q.toString();
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
/** Full chunk-upload URL:
|
|
131
|
-
*
|
|
132
|
-
* form) with no existing query string. */
|
|
130
|
+
/** Full chunk-upload URL: append the encoded chunk params to the per-recording
|
|
131
|
+
* endpoint, preserving any capability query already present on the URL. */
|
|
133
132
|
export function chunkUploadUrl(
|
|
134
133
|
chunkBaseUrl: string,
|
|
135
134
|
params: ChunkUploadParams,
|
|
136
135
|
): string {
|
|
137
|
-
|
|
136
|
+
const separator = chunkBaseUrl.includes("?") ? "&" : "?";
|
|
137
|
+
return `${chunkBaseUrl}${separator}${chunkUploadQuery(params)}`;
|
|
138
138
|
}
|
|
@@ -11,6 +11,7 @@ time from the command menu (Cmd+K → "What's new").
|
|
|
11
11
|
|
|
12
12
|
### Fixed
|
|
13
13
|
|
|
14
|
+
- Sidebar Search opens the command picker, icon controls have accessible names, and the sidebar can be resized with the keyboard.
|
|
14
15
|
- First title edits no longer hit a stale-version conflict, open databases recover after agent Trash and restore actions, and database settings preserve keyboard focus when closing or returning to Add property. Source-managed database fields stay read-only in tables, page property editors, and agent actions, while local fields remain editable. Property editors stay inside mobile Info so edits can be saved without closing the panel.
|
|
15
16
|
- Remote collaborator cursors in the document editor now stay compact above the text instead of expanding into full-width color bars.
|
|
16
17
|
|
|
@@ -9,15 +9,20 @@ from the command menu (Cmd+K → "What's new") or from Settings.
|
|
|
9
9
|
|
|
10
10
|
- Design editor version history: browse and restore saved checkpoints from the top-left menu or Cmd+K.
|
|
11
11
|
|
|
12
|
-
### Removed
|
|
13
|
-
|
|
14
|
-
- Removed the always-visible design system picker above the design editor chat.
|
|
15
|
-
|
|
16
12
|
### Improved
|
|
17
13
|
|
|
14
|
+
- The Layers panel is denser with smaller text, icons, and row height.
|
|
18
15
|
- Opening or closing the left sidebar (or toggling minimal mode) no longer shifts designs on the canvas.
|
|
19
16
|
- Breakpoint add control keeps a clear gap from the screen edge and shows immediate feedback while adding.
|
|
20
17
|
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- Design canvas chrome now overlays instead of resizing the board, so opening or closing the left panel and minimal mode no longer shifts screens on the canvas.
|
|
21
|
+
|
|
22
|
+
### Removed
|
|
23
|
+
|
|
24
|
+
- Removed the always-visible design system picker above the design editor chat.
|
|
25
|
+
|
|
21
26
|
## 2026-09-09
|
|
22
27
|
|
|
23
28
|
### Improved
|
|
@@ -3,6 +3,17 @@
|
|
|
3
3
|
All notable user-facing changes to Chat are documented here. Open it any
|
|
4
4
|
time from the command menu (Cmd+K → "What's new").
|
|
5
5
|
|
|
6
|
+
## 2026-09-09
|
|
7
|
+
|
|
8
|
+
### Improved
|
|
9
|
+
|
|
10
|
+
- Slack feedback jobs skip threads whose parent already has 👀 or 🤖, instead of tagging Builder again.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Slack feedback keeps a claimed 👀/🤖 marker after polling, so those threads stay out of the review queue.
|
|
15
|
+
- Slack feedback no longer moves an already-started thread to needs-manual just because the parent already has 👀 or 🤖.
|
|
16
|
+
|
|
6
17
|
## 2026-09-08
|
|
7
18
|
|
|
8
19
|
### Fixed
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* Body: { json: any, fieldName?: string, type?: "map"|"array", requestSource?: string }
|
|
14
14
|
*/
|
|
15
15
|
export declare const postCollabJson: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
16
|
-
error: string;
|
|
17
16
|
ok?: undefined;
|
|
17
|
+
error: string;
|
|
18
18
|
} | {
|
|
19
19
|
error?: undefined;
|
|
20
20
|
ok: boolean;
|
|
@@ -41,22 +41,22 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
|
|
|
41
41
|
thumbsUpRate: number;
|
|
42
42
|
avgEvalScore: number;
|
|
43
43
|
} | {
|
|
44
|
-
ok?: undefined;
|
|
45
44
|
summary: import("./types.js").TraceSummary;
|
|
46
45
|
spans: import("./types.js").TraceSpan[];
|
|
46
|
+
ok?: undefined;
|
|
47
47
|
error?: undefined;
|
|
48
48
|
id?: undefined;
|
|
49
49
|
} | {
|
|
50
|
-
ok?: undefined;
|
|
51
50
|
summary?: undefined;
|
|
52
51
|
spans?: undefined;
|
|
53
52
|
id: string;
|
|
53
|
+
ok?: undefined;
|
|
54
54
|
error?: undefined;
|
|
55
55
|
} | {
|
|
56
|
-
ok?: undefined;
|
|
57
56
|
summary?: undefined;
|
|
58
57
|
spans?: undefined;
|
|
59
58
|
error: any;
|
|
59
|
+
ok?: undefined;
|
|
60
60
|
id?: undefined;
|
|
61
61
|
} | {
|
|
62
62
|
summary?: undefined;
|
|
@@ -48,8 +48,8 @@ export declare function handleUpdateResource(event: any): Promise<import("./stor
|
|
|
48
48
|
}>;
|
|
49
49
|
/** DELETE /_agent-native/resources/:id — delete a resource */
|
|
50
50
|
export declare function handleDeleteResource(event: any): Promise<{
|
|
51
|
-
ok?: undefined;
|
|
52
51
|
error: string;
|
|
52
|
+
ok?: undefined;
|
|
53
53
|
} | {
|
|
54
54
|
error?: undefined;
|
|
55
55
|
ok: boolean;
|
package/dist/secrets/routes.d.ts
CHANGED
|
@@ -53,16 +53,16 @@ export declare function createListSecretsHandler(): import("h3").EventHandlerWit
|
|
|
53
53
|
/** POST /_agent-native/secrets/:key — write a secret. */
|
|
54
54
|
export declare function createWriteSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
55
55
|
error: string;
|
|
56
|
-
ok?: undefined;
|
|
57
56
|
status?: undefined;
|
|
57
|
+
ok?: undefined;
|
|
58
58
|
} | {
|
|
59
59
|
error?: undefined;
|
|
60
60
|
ok: boolean;
|
|
61
61
|
status: string;
|
|
62
62
|
} | {
|
|
63
|
-
ok?: undefined;
|
|
64
63
|
error: string;
|
|
65
64
|
removed?: undefined;
|
|
65
|
+
ok?: undefined;
|
|
66
66
|
} | {
|
|
67
67
|
error?: undefined;
|
|
68
68
|
ok: boolean;
|
|
@@ -73,9 +73,9 @@ export declare function createWriteSecretHandler(): import("h3").EventHandlerWit
|
|
|
73
73
|
* or the current stored value without changing anything.
|
|
74
74
|
*/
|
|
75
75
|
export declare function createTestSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
76
|
-
ok?: undefined;
|
|
77
76
|
error: string;
|
|
78
77
|
note?: undefined;
|
|
78
|
+
ok?: undefined;
|
|
79
79
|
} | {
|
|
80
80
|
error?: undefined;
|
|
81
81
|
ok: boolean;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Clips anonymous intake"
|
|
3
|
+
description: "Collect one private Clips recording from an unsigned visitor with a short-lived signed intake URL."
|
|
4
|
+
search: "Clips anonymous intake bug report signed upload token no login"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Anonymous Clips intake
|
|
8
|
+
|
|
9
|
+
Anonymous intake lets a customer submit one private Clips recording without signing in to Clips. The product that creates the intake URL still needs an authenticated Clips session or an authenticated backend action caller.
|
|
10
|
+
|
|
11
|
+
The intake URL is a narrow upload capability. It is not a share link, a library session, or an agent access token.
|
|
12
|
+
|
|
13
|
+
## Capability boundaries
|
|
14
|
+
|
|
15
|
+
| Capability | Allows | Does not allow | Lifetime |
|
|
16
|
+
| --- | --- | --- | --- |
|
|
17
|
+
| Intake URL | Create one private recording, upload its chunks, finalize it, or abort it | List recordings, read media, open the library, or use normal agent APIs | One hour by default. Four hours maximum |
|
|
18
|
+
| Agent context link | Read the completed recording through the normal scoped Clips agent surface after an authenticated host-side exchange | Be minted by the anonymous intake bearer, read any other recording, or act as the recording owner | Seven days for bug-report completions |
|
|
19
|
+
| Clips session | Mint intake URLs and use the normal authenticated Clips UI | Nothing is added to the anonymous visitor's browser | Controlled by normal Clips auth |
|
|
20
|
+
|
|
21
|
+
The intake token is signed with the existing scoped-token mechanism. The token is never stored in SQL. The database stores only the intake id, owner and organization binding, recording id, status, and expiry.
|
|
22
|
+
|
|
23
|
+
## End-to-end flow
|
|
24
|
+
|
|
25
|
+
1. Your authenticated product backend calls `create-recording-intake-link`.
|
|
26
|
+
2. Clips returns a short-lived `/bug-report` URL.
|
|
27
|
+
3. Your product opens that URL in an iframe or popup.
|
|
28
|
+
4. The unsigned visitor fills in the report and records the screen.
|
|
29
|
+
5. Clips creates one private recording and sends the media through its existing chunk and finalize pipeline.
|
|
30
|
+
6. The completion page posts the recording id to the host window. It does not mint read access with the anonymous bearer.
|
|
31
|
+
7. Your authenticated host backend exchanges the completed intake for a separate read-only agent context link.
|
|
32
|
+
|
|
33
|
+
The browser never receives the Clips workspace session. If the create response is lost after Clips attaches the recording, retrying the same signed create request returns that recording's upload URLs instead of creating a second row. Expired or terminal intake URLs remain unusable.
|
|
34
|
+
|
|
35
|
+
## Mint an intake URL
|
|
36
|
+
|
|
37
|
+
`create-recording-intake-link` is a UI and HTTP action. It is not exposed as an agent tool because minting a bearer upload capability is an explicit integration operation.
|
|
38
|
+
|
|
39
|
+
Call it from an authenticated same-origin browser or from an authenticated backend caller:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
const response = await fetch(
|
|
43
|
+
"https://clips.example.com/_agent-native/actions/create-recording-intake-link",
|
|
44
|
+
{
|
|
45
|
+
method: "POST",
|
|
46
|
+
headers: { "Content-Type": "application/json" },
|
|
47
|
+
credentials: "include",
|
|
48
|
+
body: JSON.stringify({ ttlSeconds: 3600 }),
|
|
49
|
+
},
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
if (!response.ok) throw new Error("Could not create the Clips intake URL");
|
|
53
|
+
|
|
54
|
+
const { result } = await response.json();
|
|
55
|
+
// result.url is safe to give to the unsigned visitor.
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`ttlSeconds` is optional. It must be between 60 seconds and four hours. The default is one hour. The response also includes `intakeId`, `expiresAt`, and `ttlSeconds`.
|
|
59
|
+
|
|
60
|
+
The create request is safe to retry with the same `intakeId` and `intakeToken` if its response is lost. Clips returns the existing recording after attachment; it never creates a second recording for the same intake. Do not retry with a different intake token or reuse the upload URLs after an abort.
|
|
61
|
+
|
|
62
|
+
Do not mint this URL in an anonymous browser. Do not put a long-lived Clips session or a service credential into the customer-facing page.
|
|
63
|
+
|
|
64
|
+
## Embed the intake page
|
|
65
|
+
|
|
66
|
+
Pass the returned URL directly to an iframe. The URL already contains the signed `clip_intake_id` and `clip_intake` query parameters.
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
<iframe
|
|
70
|
+
src={result.url}
|
|
71
|
+
allow="display-capture; camera; microphone"
|
|
72
|
+
title="Report a problem"
|
|
73
|
+
style={{ width: 420, height: 560, border: 0 }}
|
|
74
|
+
/>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The recorder opens a top-level popup when the visitor starts capture. Browser media permissions and screen capture require a user gesture in a top-level browsing context. If popups are blocked, the page falls back to navigating the current window.
|
|
78
|
+
|
|
79
|
+
## Receive the completed recording
|
|
80
|
+
|
|
81
|
+
The done page posts `agent-native.clips.bug-report.submitted` to the opener and the embedding parent. For anonymous intake, the message intentionally contains `agentAccessStatus: "unavailable"` and no agent link. Retain the `intakeId` from the mint response on the trusted host, then exchange it server-side after receiving the `recordingId`. Validate both the Clips origin and the message source before using the payload.
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
const clipsOrigin = "https://clips.example.com";
|
|
85
|
+
let recorderWindow: Window | null = null;
|
|
86
|
+
|
|
87
|
+
window.addEventListener("message", (event) => {
|
|
88
|
+
if (event.origin !== clipsOrigin) return;
|
|
89
|
+
if (recorderWindow && event.source !== recorderWindow) return;
|
|
90
|
+
if (event.data?.type !== "agent-native.clips.bug-report.submitted") return;
|
|
91
|
+
|
|
92
|
+
if (event.data.agentAccessStatus !== "unavailable") return;
|
|
93
|
+
// Send event.data.recordingId and the retained intakeId to your trusted
|
|
94
|
+
// backend. Do not send the anonymous clip_intake token from this page.
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The payload includes `recordingId`, `recordingUrl`, `embedUrl`, `agentShareUrl`, `agentContextUrl`, `agentAccessExpiresAt`, and `agentAccessStatus`. For anonymous intake, `agentAccessStatus: "unavailable"` is expected until the trusted host performs the exchange. Do not treat it as an empty recording result or let the visitor call the exchange action.
|
|
99
|
+
|
|
100
|
+
Use the authenticated `create-intake-agent-link` action from the trusted host backend. Keep the `intakeId` returned by `create-recording-intake-link` with the host's authenticated Clips session, and pass the completed `recordingId` from the message:
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
const response = await fetch(
|
|
104
|
+
"https://clips.example.com/_agent-native/actions/create-intake-agent-link",
|
|
105
|
+
{
|
|
106
|
+
method: "POST",
|
|
107
|
+
headers: { "Content-Type": "application/json" },
|
|
108
|
+
credentials: "include",
|
|
109
|
+
body: JSON.stringify({ intakeId, recordingId }),
|
|
110
|
+
},
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
if (!response.ok) throw new Error("Could not create the Clips agent link");
|
|
114
|
+
const { result } = await response.json();
|
|
115
|
+
// Send result.contextUrl to the support agent or backend here.
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The exchange verifies the authenticated owner, organization, intake expiry, completed state, and recording binding before delegating to the normal scoped agent-link implementation. If the host already has an authenticated Clips session with access to the recording, it can also call `create-recording-agent-link` with the `recordingId`.
|
|
119
|
+
|
|
120
|
+
## Self-hosting requirements
|
|
121
|
+
|
|
122
|
+
Anonymous intake uses the same storage and database prerequisites as an authenticated recording:
|
|
123
|
+
|
|
124
|
+
- A persistent `DATABASE_URL`.
|
|
125
|
+
- A configured Builder.io or S3-compatible video storage provider.
|
|
126
|
+
- The normal production auth and scoped-token configuration from [Deployment](/docs/deployment).
|
|
127
|
+
- A public `BETTER_AUTH_URL`, such as `https://clips.example.com`, when the intake URL is minted outside a browser request.
|
|
128
|
+
|
|
129
|
+
The intake action creates the recording with private visibility regardless of the organization's default recording visibility. The normal Clips access checks still protect the recording after intake completes.
|
|
130
|
+
|
|
131
|
+
## Security checklist
|
|
132
|
+
|
|
133
|
+
- Use HTTPS for the Clips app and the host product.
|
|
134
|
+
- Treat the intake URL as a bearer credential. Keep it out of logs, analytics events, referrer data, screenshots, and support tickets.
|
|
135
|
+
- Use the shortest useful TTL. One hour is the default.
|
|
136
|
+
- Validate `event.origin` and `event.source` in the completion listener.
|
|
137
|
+
- Keep the intake id and the authenticated exchange on the trusted host. Store the resulting `agentContextUrl` only where the support agent or backend needs it. It is also a bearer credential.
|
|
138
|
+
- Never expose `/api/uploads/:recordingId/chunk` or `/api/uploads/:recordingId/abort` directly to anonymous users. Intake traffic goes through the signed `/api/clip-intake` transport, which checks the intake id, token, recording binding, owner binding, status, and expiry on every request.
|
|
139
|
+
|
|
140
|
+
## Troubleshooting
|
|
141
|
+
|
|
142
|
+
**The recorder opens a sign-in page.** Make sure the intake URL includes both `clip_intake_id` and `clip_intake`. The `/record` route is public for the intake handoff, while normal recording creation remains session protected.
|
|
143
|
+
|
|
144
|
+
**The recording says the intake URL is unavailable.** The URL may have expired, been aborted, or be mid-creation. Retry the original create request once if its response was lost; otherwise mint a new intake URL. Each intake URL still creates at most one recording.
|
|
145
|
+
|
|
146
|
+
**The completion has no agent link.** That is expected for anonymous intake: the visitor's bearer is write-only. Have the authenticated host exchange the retained `intakeId` and completed `recordingId` with `create-intake-agent-link`. A 409 means the recording is still being verified; retry from the trusted host after it becomes ready.
|
|
147
|
+
|
|
148
|
+
**The page has no storage setup control.** Anonymous visitors cannot configure a Clips storage provider. Configure storage before minting intake URLs.
|
|
@@ -94,11 +94,11 @@ window.addEventListener("message", (event) => {
|
|
|
94
94
|
});
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
-
Always validate both `event.origin` and `event.source` in the host. The completion payload includes `recordingId`, `recordingUrl`, `embedUrl`, `agentShareUrl`, `agentContextUrl`, `agentAccessExpiresAt`, and an explicit `agentAccessStatus` of `ready` or `unavailable`. When access is ready, `agentContextUrl` is a temporary, scoped URL that lets an agent read recording metadata, timestamped transcript segments, recommended frames and frame URLs, and browser diagnostics without receiving the user's workspace session. Normal agent links expire after two hours; embedded bug-report completions use a seven-day scoped link so asynchronous support tickets can be triaged. Treat an unavailable or expired link as an access failure, not an empty recording. `embedUrl` remains subject to the recording's normal viewer access.
|
|
97
|
+
Always validate both `event.origin` and `event.source` in the host. The completion payload includes `recordingId`, `recordingUrl`, `embedUrl`, `agentShareUrl`, `agentContextUrl`, `agentAccessExpiresAt`, and an explicit `agentAccessStatus` of `ready` or `unavailable`. When access is ready, `agentContextUrl` is a temporary, scoped URL that lets an agent read recording metadata, timestamped transcript segments, recommended frames and frame URLs, and browser diagnostics without receiving the user's workspace session. Normal agent links expire after two hours; authenticated embedded bug-report completions use a seven-day scoped link so asynchronous support tickets can be triaged. Anonymous intake completions intentionally report `agentAccessStatus: "unavailable"` until the trusted host exchanges the retained intake id and completed recording id through the authenticated `create-intake-agent-link` action. Treat an unavailable or expired link as an access failure, not an empty recording. `embedUrl` remains subject to the recording's normal viewer access.
|
|
98
98
|
|
|
99
99
|
Bug-report recordings default to workspace visibility instead of public visibility. The saved `recording_bug_reports` sidecar includes the redacted host metadata, while the recording itself remains the canonical media/transcript resource. URLs have credentials, fragments, and query values removed before storage, and the browser-diagnostics pipeline still omits request/response bodies, headers, and cookies. A popup recording only sees diagnostics emitted inside the Clips window. Use the Chrome extension or attach diagnostics from the host product separately when the agent needs console and network context from the source tab.
|
|
100
100
|
|
|
101
|
-
This
|
|
101
|
+
This embedded flow requires the reporter to already have an active session in the Clips workspace that owns the recording. The current cross-origin popup cannot preserve its completion channel through a first-time sign-in redirect, so users should sign in to Clips in a separate tab before launching it. For a no-login customer-support flow, use [Anonymous Clips intake](/docs/template-clips-anonymous-intake). It creates a one-recording, write-only capability and keeps the normal owner-scoped upload endpoints protected.
|
|
102
102
|
|
|
103
103
|
## Data model
|
|
104
104
|
|
|
@@ -273,6 +273,7 @@ Two things live outside this schema on purpose: organization/member/invitation r
|
|
|
273
273
|
| `recording_ctas` | Call-to-action buttons (label, url, color, placement) overlaid on a recording |
|
|
274
274
|
| `recording_comments` | Threaded, timestamped comments with emoji-reaction map and resolved flag |
|
|
275
275
|
| `recording_bug_reports` | Redacted host-product bug report metadata keyed by `recording_id` |
|
|
276
|
+
| `clips_intake_sessions` | One-use anonymous intake capabilities bound to an owner, organization, and recording |
|
|
276
277
|
| `recording_reactions` | Emoji reactions pinned to a video timestamp (anonymous viewers allowed) |
|
|
277
278
|
| `recording_viewers` / `recording_events` | View analytics: per-viewer watch time and completion, plus granular events (view-start, watch-progress, seek, pause, cta-click, reaction) |
|
|
278
279
|
| `recording_views` | Append-only per-view log (who viewed, when) — backs the owner-facing "Viewed by" popover, kept separate from the aggregate counters above |
|
|
@@ -344,7 +345,7 @@ Recordings and transcripts are intentionally separate tables so the library and
|
|
|
344
345
|
Every agent-callable operation is a TypeScript file in `templates/clips/actions/`, auto-mounted at `POST /_agent-native/actions/:name` and runnable from the CLI as `pnpm action <name>`. There are roughly 110 actions, grouped here by purpose:
|
|
345
346
|
|
|
346
347
|
- **Recording lifecycle** — `create-recording`, `finalize-recording`, `update-recording`, `set-thumbnail`, `archive-recording` / `restore-recording` / `trash-recording` / `delete-recording-permanent`, `move-recording`, `tag-recording`, `reprocess-recording`, `import-loom-recording`.
|
|
347
|
-
- **Bug reports** — `save-bug-report-context` stores redacted host-product context for recordings launched from `/bug-report`; `get-recording-player-data` returns the sidecar to editors.
|
|
348
|
+
- **Bug reports** — `save-bug-report-context` stores redacted host-product context for recordings launched from `/bug-report`; `create-recording-intake-link` mints a one-use anonymous intake URL; `create-intake-recording` is the anonymous write step and `create-intake-agent-link` is the authenticated host-side exchange; `get-recording-player-data` returns the sidecar to editors. See [Anonymous Clips intake](/docs/template-clips-anonymous-intake) for the complete flow.
|
|
348
349
|
- **Transcript & AI** — `request-transcript`, `cleanup-transcript`, `save-browser-transcript`, `regenerate-title` / `regenerate-summary` / `regenerate-chapters`, `set-chapters`, `generate-workflow`, `get-clips-ai-prefs` / `update-clips-ai-prefs`, `get-builder-credit-status`. (`cleanup-transcript` and `finalize-meeting` are server-side media-pipeline calls; most other AI features delegate to the agent chat.)
|
|
349
350
|
- **Editing** — non-destructive `trim-recording`, `split-recording`, `remove-filler-words`, `remove-silences`, plus `stitch-recordings`, `undo-edit`, `clear-edits`. Edits accumulate in `edits_json`; the client concatenates/exports via ffmpeg.wasm.
|
|
350
351
|
- **Meetings** — `create-meeting`, `start-meeting-recording` / `stop-meeting-recording`, `finalize-meeting`, `update-meeting`, `get-meeting`, `list-meetings`, `delete-meeting`, plus calendar wiring `connect-calendar` / `disconnect-calendar` / `sync-calendars` / `list-calendar-accounts`.
|
|
@@ -116,6 +116,7 @@ Live demo: [clips.agent-native.com](https://clips.agent-native.com).
|
|
|
116
116
|
- [**Sharing, Teams, and Agent-Readable Clips**](/docs/template-clips-sharing-and-teams) — visibility, passwords, organizations, and pasting a clip link into an agent.
|
|
117
117
|
- [**AI Pipeline, Editing, and Insights**](/docs/template-clips-ai-and-editing) — transcription, non-destructive editing, and view analytics.
|
|
118
118
|
- [**Extending Clips**](/docs/template-clips-developers) — for anyone customizing or extending the template.
|
|
119
|
+
- [**Anonymous Clips intake**](/docs/template-clips-anonymous-intake) — collect one private recording from an unsigned visitor with a short-lived signed URL.
|
|
119
120
|
- [**Templates**](/docs/cloneable-saas) — the Cloneable SaaS model
|
|
120
121
|
- [**Context Awareness**](/docs/context-awareness) — how the agent knows the current clip and playhead
|
|
121
122
|
- [**Agent Teams**](/docs/agent-teams) — delegate transcript cleanup to a specialist sub-agent
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.178.1
|
|
3
|
+
"version": "0.178.1",
|
|
4
4
|
"description": "The agentic application framework for building autonomous agents with intuitive UIs",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -446,8 +446,8 @@
|
|
|
446
446
|
"y-protocols": "^1.0.7",
|
|
447
447
|
"yjs": "^13.6.32",
|
|
448
448
|
"zod": "^4.3.6",
|
|
449
|
-
"@agent-native/
|
|
450
|
-
"@agent-native/
|
|
449
|
+
"@agent-native/recap-cli": "0.5.29",
|
|
450
|
+
"@agent-native/toolkit": "^0.19.7"
|
|
451
451
|
},
|
|
452
452
|
"devDependencies": {
|
|
453
453
|
"@ai-sdk/anthropic": "^3.0.71",
|