@agent-native/core 0.0.0-beta-20260820172558 → 0.0.0-beta-20260820181038
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/templates/analytics/CHANGELOG.md +6 -13
- package/corpus/templates/analytics/changelog/2026-08-20-staged-data-limit-recovery.md +1 -0
- package/corpus/templates/clips/CHANGELOG.md +11 -10
- package/corpus/templates/clips/app/i18n/en-US.ts +6 -2
- package/corpus/templates/clips/app/routes/download.tsx +111 -17
- package/corpus/templates/clips/changelog/2026-08-20-cross-tab-recording-controls.md +1 -0
- package/corpus/templates/clips/changelog/2026-08-20-overlay-follow-review-fixes.md +1 -0
- package/corpus/templates/clips/changelog/2026-08-20-provider-key-recovery.md +1 -0
- package/corpus/templates/clips/server/routes/api/clips-latest.json.get.ts +109 -46
- package/dist/client/blocks/library/diagram.js +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/deploy/workspace-deploy.js +7 -0
- package/dist/mcp-client/oauth-routes.d.ts +1 -0
- package/dist/mcp-client/oauth-routes.js +15 -2
- package/dist/mcp-client/remote-store.js +83 -43
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/secrets/routes.d.ts +6 -6
- package/dist/server/google-oauth-credentials.js +14 -1
- package/dist/settings/index.d.ts +1 -1
- package/dist/settings/index.js +1 -1
- package/dist/settings/store.d.ts +7 -1
- package/dist/settings/store.js +25 -3
- package/dist/settings/user-settings.d.ts +2 -0
- package/dist/settings/user-settings.js +65 -2
- package/docs/content/deployment.mdx +89 -14
- package/package.json +1 -1
|
@@ -5,6 +5,12 @@ time from the command menu (Cmd+K → "What's new") or from Settings.
|
|
|
5
5
|
|
|
6
6
|
Older updates live in [the changelog folder](./changelog/) and are included in the in-app "What's new" view.
|
|
7
7
|
|
|
8
|
+
## 2026-08-20
|
|
9
|
+
|
|
10
|
+
### Improved
|
|
11
|
+
|
|
12
|
+
- Analytics now explains how to recover when staged data reaches its size limit.
|
|
13
|
+
|
|
8
14
|
## 2026-08-18
|
|
9
15
|
|
|
10
16
|
### Fixed
|
|
@@ -36,16 +42,3 @@ Older updates live in [the changelog folder](./changelog/) and are included in t
|
|
|
36
42
|
|
|
37
43
|
- Feature flags can be managed across apps whose workspace records use different local IDs
|
|
38
44
|
- Kept feature flag details readable by stacking rollout controls at narrow widths.
|
|
39
|
-
|
|
40
|
-
## 2026-08-11
|
|
41
|
-
|
|
42
|
-
### Improved
|
|
43
|
-
|
|
44
|
-
- Full-page chat composers stay at a focused 750px width.
|
|
45
|
-
|
|
46
|
-
### Fixed
|
|
47
|
-
|
|
48
|
-
- Analytics custom blocks keep loading when they use the legacy BigQuery query action name.
|
|
49
|
-
- Analytics dashboards keep the selected tab when reopened
|
|
50
|
-
- Analytics routes company-knowledge and Slack-context questions to Brain
|
|
51
|
-
- Chrome no longer offers to install Analytics as a desktop app.
|
|
@@ -5,6 +5,17 @@ from the command menu (Cmd+K → "What's new") or from Settings.
|
|
|
5
5
|
|
|
6
6
|
Older updates live in [the changelog folder](./changelog/) and are included in the in-app "What's new" view.
|
|
7
7
|
|
|
8
|
+
## 2026-08-20
|
|
9
|
+
|
|
10
|
+
### Improved
|
|
11
|
+
|
|
12
|
+
- Recording controls now follow you across browser tabs and page navigations.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- Clips overlays now follow tab switches during the recording countdown and recover cleanly on tabs opened before Clips.
|
|
17
|
+
- Clips now opens provider setup instead of retrying a rejected key in a loop.
|
|
18
|
+
|
|
8
19
|
## 2026-08-19
|
|
9
20
|
|
|
10
21
|
### Added
|
|
@@ -50,13 +61,3 @@ Older updates live in [the changelog folder](./changelog/) and are included in t
|
|
|
50
61
|
|
|
51
62
|
- Long desktop recordings now complete or abort resumable uploads cleanly, preserve local recovery copies, and show the actual upload error when saving fails.
|
|
52
63
|
- Clips now uses consistent text-only share controls with compact copy actions and an expandable Share with agents section across recordings and meetings.
|
|
53
|
-
|
|
54
|
-
## 2026-08-13
|
|
55
|
-
|
|
56
|
-
### Added
|
|
57
|
-
|
|
58
|
-
- Private meeting notes can be shared with an external agent through a temporary link.
|
|
59
|
-
|
|
60
|
-
### Improved
|
|
61
|
-
|
|
62
|
-
- Comments now render inline Markdown for emphasis, inline code, links, and line breaks while keeping headings out of compact comment surfaces.
|
|
@@ -615,12 +615,16 @@ const messages = {
|
|
|
615
615
|
alsoFor: "Also available for {{platform}}",
|
|
616
616
|
backToLibrary: "Back to library",
|
|
617
617
|
clipsDesktop: "Clips Desktop",
|
|
618
|
+
stable: "Stable",
|
|
619
|
+
nightly: "Nightly",
|
|
620
|
+
switchToNightly: "Switch to Nightly builds",
|
|
621
|
+
switchToStable: "Switch to stable builds",
|
|
622
|
+
retry: "Try again",
|
|
618
623
|
heroDescription:
|
|
619
624
|
"A menu-bar recorder for screen, camera, and screen + camera. One-click start, draggable camera bubble, instant-share link when you stop.",
|
|
620
625
|
versionReleased: "Version {{version}} — released {{date}}",
|
|
621
626
|
version: "Version {{version}}",
|
|
622
|
-
manifestError:
|
|
623
|
-
"Could not load release manifest — pick an installer from the releases page.",
|
|
627
|
+
manifestError: "Could not load release manifest. Try again.",
|
|
624
628
|
loadingRelease: "Loading latest release…",
|
|
625
629
|
chromeTitle: "Chrome extension for browser logs",
|
|
626
630
|
chromeDescription:
|
|
@@ -29,6 +29,7 @@ export function meta() {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
type PlatformId = "mac" | "windows" | "linux";
|
|
32
|
+
type ReleaseChannel = "production" | "nightly";
|
|
32
33
|
|
|
33
34
|
interface PlatformVariant {
|
|
34
35
|
id: PlatformId;
|
|
@@ -48,9 +49,6 @@ interface PlatformVariant {
|
|
|
48
49
|
|
|
49
50
|
const LATEST_JSON_URL = `${appBasePath()}/api/clips-latest.json`;
|
|
50
51
|
|
|
51
|
-
const RELEASE_PAGE_URL =
|
|
52
|
-
"https://github.com/BuilderIO/agent-native/releases?q=clips-v";
|
|
53
|
-
|
|
54
52
|
const VARIANTS: PlatformVariant[] = [
|
|
55
53
|
{
|
|
56
54
|
id: "mac",
|
|
@@ -114,12 +112,18 @@ function primaryDownloadButton(
|
|
|
114
112
|
manifest: Manifest | null,
|
|
115
113
|
manifestError: boolean,
|
|
116
114
|
downloadLabel: string,
|
|
115
|
+
retryLabel: string,
|
|
116
|
+
onRetry: () => void,
|
|
117
117
|
) {
|
|
118
118
|
const asset = pickAsset(manifest, variant);
|
|
119
119
|
const Icon = variant.icon;
|
|
120
120
|
if (asset) {
|
|
121
121
|
return (
|
|
122
|
-
<Button
|
|
122
|
+
<Button
|
|
123
|
+
asChild
|
|
124
|
+
size="lg"
|
|
125
|
+
className="h-12 min-w-[252px] gap-2 px-6 text-base"
|
|
126
|
+
>
|
|
123
127
|
<a href={asset.url} download onClick={markDesktopAppDownloaded}>
|
|
124
128
|
<Icon className="h-5 w-5" />
|
|
125
129
|
{downloadLabel}
|
|
@@ -130,17 +134,28 @@ function primaryDownloadButton(
|
|
|
130
134
|
if (manifest === null && !manifestError) {
|
|
131
135
|
return <Skeleton className="h-12 w-[252px] rounded-md" />;
|
|
132
136
|
}
|
|
137
|
+
if (manifestError) {
|
|
138
|
+
return (
|
|
139
|
+
<Button
|
|
140
|
+
size="lg"
|
|
141
|
+
variant="outline"
|
|
142
|
+
className="h-12 min-w-[252px] gap-2 px-6 text-base"
|
|
143
|
+
onClick={onRetry}
|
|
144
|
+
>
|
|
145
|
+
<Icon className="h-5 w-5" />
|
|
146
|
+
{retryLabel}
|
|
147
|
+
</Button>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
133
150
|
return (
|
|
134
151
|
<Button
|
|
135
|
-
asChild
|
|
136
152
|
size="lg"
|
|
137
153
|
variant="outline"
|
|
138
|
-
className="h-12 gap-2 px-6 text-base"
|
|
154
|
+
className="h-12 min-w-[252px] gap-2 px-6 text-base"
|
|
155
|
+
disabled
|
|
139
156
|
>
|
|
140
|
-
<
|
|
141
|
-
|
|
142
|
-
{downloadLabel}
|
|
143
|
-
</a>
|
|
157
|
+
<Icon className="h-5 w-5" />
|
|
158
|
+
{downloadLabel}
|
|
144
159
|
</Button>
|
|
145
160
|
);
|
|
146
161
|
}
|
|
@@ -150,6 +165,8 @@ function secondaryDownloadButton(
|
|
|
150
165
|
manifest: Manifest | null,
|
|
151
166
|
manifestError: boolean,
|
|
152
167
|
downloadLabel: string,
|
|
168
|
+
retryLabel: string,
|
|
169
|
+
onRetry: () => void,
|
|
153
170
|
) {
|
|
154
171
|
const asset = pickAsset(manifest, variant);
|
|
155
172
|
const Icon = variant.icon;
|
|
@@ -168,12 +185,18 @@ function secondaryDownloadButton(
|
|
|
168
185
|
if (manifest === null && !manifestError) {
|
|
169
186
|
return <Skeleton className="h-7 w-[208px] rounded-md" />;
|
|
170
187
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
<
|
|
188
|
+
if (manifestError) {
|
|
189
|
+
return (
|
|
190
|
+
<Button variant="ghost" className={className} onClick={onRetry}>
|
|
174
191
|
<Icon className="h-4 w-4" />
|
|
175
|
-
{
|
|
176
|
-
</
|
|
192
|
+
{retryLabel}
|
|
193
|
+
</Button>
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
return (
|
|
197
|
+
<Button variant="ghost" className={className} disabled>
|
|
198
|
+
<Icon className="h-4 w-4" />
|
|
199
|
+
{downloadLabel}
|
|
177
200
|
</Button>
|
|
178
201
|
);
|
|
179
202
|
}
|
|
@@ -181,9 +204,11 @@ function secondaryDownloadButton(
|
|
|
181
204
|
export default function DownloadPage() {
|
|
182
205
|
const chromeExtensionEnabled = useClipsChromeExtensionEnabled();
|
|
183
206
|
const t = useT();
|
|
207
|
+
const [channel, setChannel] = useState<ReleaseChannel>("production");
|
|
184
208
|
const [manifest, setManifest] = useState<Manifest | null>(null);
|
|
185
209
|
const [manifestError, setManifestError] = useState(false);
|
|
186
210
|
const [detected, setDetected] = useState<PlatformId | null>(null);
|
|
211
|
+
const [manifestRequest, setManifestRequest] = useState(0);
|
|
187
212
|
|
|
188
213
|
useEffect(() => {
|
|
189
214
|
setDetected(detectPlatform());
|
|
@@ -191,7 +216,13 @@ export default function DownloadPage() {
|
|
|
191
216
|
|
|
192
217
|
useEffect(() => {
|
|
193
218
|
let cancelled = false;
|
|
194
|
-
|
|
219
|
+
setManifest(null);
|
|
220
|
+
setManifestError(false);
|
|
221
|
+
const manifestUrl =
|
|
222
|
+
channel === "nightly"
|
|
223
|
+
? `${LATEST_JSON_URL}?channel=nightly`
|
|
224
|
+
: LATEST_JSON_URL;
|
|
225
|
+
fetch(manifestUrl)
|
|
195
226
|
.then((r) => (r.ok ? r.json() : Promise.reject(new Error(`${r.status}`))))
|
|
196
227
|
.then((json) => {
|
|
197
228
|
if (!cancelled) setManifest(json as Manifest);
|
|
@@ -202,7 +233,17 @@ export default function DownloadPage() {
|
|
|
202
233
|
return () => {
|
|
203
234
|
cancelled = true;
|
|
204
235
|
};
|
|
205
|
-
}, []);
|
|
236
|
+
}, [channel, manifestRequest]);
|
|
237
|
+
|
|
238
|
+
const retryManifest = () => {
|
|
239
|
+
setManifestRequest((request) => request + 1);
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const handleChannelChange = (nextChannel: ReleaseChannel) => {
|
|
243
|
+
setManifest(null);
|
|
244
|
+
setManifestError(false);
|
|
245
|
+
setChannel(nextChannel);
|
|
246
|
+
};
|
|
206
247
|
|
|
207
248
|
const primary = VARIANTS.find((v) => v.id === detected) ?? VARIANTS[0];
|
|
208
249
|
const secondary = VARIANTS.filter((v) => v.id !== primary.id);
|
|
@@ -242,6 +283,14 @@ export default function DownloadPage() {
|
|
|
242
283
|
<div className="flex flex-col items-center text-center">
|
|
243
284
|
<h1 className="text-4xl font-semibold tracking-tight sm:text-5xl">
|
|
244
285
|
{t("downloadRoute.clipsDesktop")}
|
|
286
|
+
{channel === "nightly" && (
|
|
287
|
+
<>
|
|
288
|
+
{" "}
|
|
289
|
+
<span className="text-highlight">
|
|
290
|
+
{t("downloadRoute.nightly")}
|
|
291
|
+
</span>
|
|
292
|
+
</>
|
|
293
|
+
)}
|
|
245
294
|
</h1>
|
|
246
295
|
<p className="mt-4 max-w-xl text-base text-muted-foreground">
|
|
247
296
|
{t("downloadRoute.heroDescription")}
|
|
@@ -253,6 +302,8 @@ export default function DownloadPage() {
|
|
|
253
302
|
manifest,
|
|
254
303
|
manifestError,
|
|
255
304
|
t("downloadRoute.downloadFor", { platform: primary.label }),
|
|
305
|
+
t("downloadRoute.retry"),
|
|
306
|
+
retryManifest,
|
|
256
307
|
)}
|
|
257
308
|
{secondary.map((variant) => (
|
|
258
309
|
<div key={variant.id}>
|
|
@@ -261,6 +312,8 @@ export default function DownloadPage() {
|
|
|
261
312
|
manifest,
|
|
262
313
|
manifestError,
|
|
263
314
|
t("downloadRoute.alsoFor", { platform: variant.label }),
|
|
315
|
+
t("downloadRoute.retry"),
|
|
316
|
+
retryManifest,
|
|
264
317
|
)}
|
|
265
318
|
</div>
|
|
266
319
|
))}
|
|
@@ -282,6 +335,47 @@ export default function DownloadPage() {
|
|
|
282
335
|
<>{t("downloadRoute.loadingRelease")}</>
|
|
283
336
|
)}
|
|
284
337
|
</div>
|
|
338
|
+
<div className="mt-2 flex items-center gap-2 text-xs text-muted-foreground">
|
|
339
|
+
<span>{t("downloadRoute.stable")}</span>
|
|
340
|
+
<button
|
|
341
|
+
type="button"
|
|
342
|
+
role="switch"
|
|
343
|
+
aria-checked={channel === "nightly"}
|
|
344
|
+
aria-label={t(
|
|
345
|
+
channel === "nightly"
|
|
346
|
+
? "downloadRoute.switchToStable"
|
|
347
|
+
: "downloadRoute.switchToNightly",
|
|
348
|
+
)}
|
|
349
|
+
onClick={() =>
|
|
350
|
+
handleChannelChange(
|
|
351
|
+
channel === "nightly" ? "production" : "nightly",
|
|
352
|
+
)
|
|
353
|
+
}
|
|
354
|
+
className={`relative inline-flex h-5 w-9 shrink-0 items-center rounded-full border border-border transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500/50 ${
|
|
355
|
+
channel === "nightly"
|
|
356
|
+
? "bg-foreground"
|
|
357
|
+
: "bg-muted-foreground/20"
|
|
358
|
+
}`}
|
|
359
|
+
>
|
|
360
|
+
<span
|
|
361
|
+
aria-hidden="true"
|
|
362
|
+
className={`block size-3.5 rounded-full bg-primary-foreground shadow-sm transition-transform ${
|
|
363
|
+
channel === "nightly"
|
|
364
|
+
? "translate-x-[18px]"
|
|
365
|
+
: "translate-x-[2px]"
|
|
366
|
+
}`}
|
|
367
|
+
/>
|
|
368
|
+
</button>
|
|
369
|
+
<span
|
|
370
|
+
className={
|
|
371
|
+
channel === "nightly"
|
|
372
|
+
? "font-medium text-foreground"
|
|
373
|
+
: "text-muted-foreground"
|
|
374
|
+
}
|
|
375
|
+
>
|
|
376
|
+
{t("downloadRoute.nightly")}
|
|
377
|
+
</span>
|
|
378
|
+
</div>
|
|
285
379
|
</div>
|
|
286
380
|
|
|
287
381
|
{chromeExtensionEnabled && (
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
createError,
|
|
3
|
+
defineEventHandler,
|
|
4
|
+
getQuery,
|
|
5
|
+
setResponseHeaders,
|
|
6
|
+
} from "h3";
|
|
2
7
|
|
|
3
8
|
/**
|
|
4
9
|
* Same-origin endpoint that tells the download page which user-facing
|
|
@@ -12,14 +17,13 @@ import { defineEventHandler, setResponseHeaders, createError } from "h3";
|
|
|
12
17
|
* /download want the raw installers (.dmg / .msi / .AppImage).
|
|
13
18
|
*
|
|
14
19
|
* This route therefore hits GitHub's REST API, paginates through
|
|
15
|
-
* releases until it finds the most recent published
|
|
16
|
-
* and returns its asset list plus metadata.
|
|
20
|
+
* releases until it finds the most recent published release for the requested
|
|
21
|
+
* channel, and returns its asset list plus metadata.
|
|
17
22
|
*
|
|
18
|
-
* The
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* same build by default.
|
|
23
|
+
* The channel's pointer release is still the release-channel hint: when it
|
|
24
|
+
* has a signed updater manifest, we resolve that manifest's version back to
|
|
25
|
+
* the matching versioned release before scanning. That keeps the manual
|
|
26
|
+
* download page and the in-app updater pointed at the same build by default.
|
|
23
27
|
*
|
|
24
28
|
* ## Rate-limit hardening
|
|
25
29
|
*
|
|
@@ -38,14 +42,30 @@ import { defineEventHandler, setResponseHeaders, createError } from "h3";
|
|
|
38
42
|
|
|
39
43
|
const RELEASES_URL_BASE =
|
|
40
44
|
"https://api.github.com/repos/BuilderIO/agent-native/releases";
|
|
41
|
-
const UPDATER_MANIFEST_URL =
|
|
42
|
-
"https://github.com/BuilderIO/agent-native/releases/download/clips-latest/clips-latest.json";
|
|
43
45
|
const PER_PAGE = 100;
|
|
44
|
-
// Up to 10 pages = 1000 releases. If
|
|
45
|
-
// something else is wrong and the 404 is correct.
|
|
46
|
+
// Up to 10 pages = 1000 releases. If the requested channel has not appeared
|
|
47
|
+
// by then, something else is wrong and the 404 is correct.
|
|
46
48
|
const MAX_PAGES = 10;
|
|
47
49
|
const CACHE_TTL_MS = 5 * 60_000;
|
|
48
50
|
|
|
51
|
+
export type ClipsReleaseChannel = "production" | "nightly";
|
|
52
|
+
|
|
53
|
+
const RELEASE_CHANNEL_CONFIG: Record<
|
|
54
|
+
ClipsReleaseChannel,
|
|
55
|
+
{ releasePrefix: string; updaterManifestUrl: string }
|
|
56
|
+
> = {
|
|
57
|
+
production: {
|
|
58
|
+
releasePrefix: "clips-v",
|
|
59
|
+
updaterManifestUrl:
|
|
60
|
+
"https://github.com/BuilderIO/agent-native/releases/download/clips-latest/clips-latest.json",
|
|
61
|
+
},
|
|
62
|
+
nightly: {
|
|
63
|
+
releasePrefix: "clips-nightly-v",
|
|
64
|
+
updaterManifestUrl:
|
|
65
|
+
"https://github.com/BuilderIO/agent-native/releases/download/clips-nightly-latest/clips-nightly-latest.json",
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
49
69
|
interface GhAsset {
|
|
50
70
|
name: string;
|
|
51
71
|
browser_download_url: string;
|
|
@@ -121,7 +141,9 @@ export function classifyClipsAsset(
|
|
|
121
141
|
}
|
|
122
142
|
|
|
123
143
|
function parseClipsVersion(tagName: string): number[] | null {
|
|
124
|
-
const match = /^clips
|
|
144
|
+
const match = /^clips(?:-nightly)?-v(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/.exec(
|
|
145
|
+
tagName,
|
|
146
|
+
);
|
|
125
147
|
if (!match) return null;
|
|
126
148
|
return match.slice(1, 4).map((part) => Number(part));
|
|
127
149
|
}
|
|
@@ -139,6 +161,22 @@ export function compareClipsReleaseTags(a: string, b: string): number {
|
|
|
139
161
|
return 0;
|
|
140
162
|
}
|
|
141
163
|
|
|
164
|
+
export function isClipsReleaseForChannel(
|
|
165
|
+
release: GhRelease,
|
|
166
|
+
channel: ClipsReleaseChannel,
|
|
167
|
+
): boolean {
|
|
168
|
+
if (release.draft || !hasInstallerAssets(release)) return false;
|
|
169
|
+
if (channel === "production") {
|
|
170
|
+
return (
|
|
171
|
+
!release.prerelease && /^clips-v\d+\.\d+\.\d+$/.test(release.tag_name)
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
return (
|
|
175
|
+
release.prerelease &&
|
|
176
|
+
/^clips-nightly-v\d+\.\d+\.\d+(?:[-+].*)?$/.test(release.tag_name)
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
142
180
|
function isBetterRelease(candidate: GhRelease, current: GhRelease | null) {
|
|
143
181
|
if (!current) return true;
|
|
144
182
|
const versionOrder = compareClipsReleaseTags(
|
|
@@ -158,8 +196,11 @@ function hasInstallerAssets(release: GhRelease) {
|
|
|
158
196
|
);
|
|
159
197
|
}
|
|
160
198
|
|
|
161
|
-
|
|
162
|
-
|
|
199
|
+
const cache = new Map<
|
|
200
|
+
ClipsReleaseChannel,
|
|
201
|
+
{ data: DownloadManifest; ts: number }
|
|
202
|
+
>();
|
|
203
|
+
const inFlight = new Map<ClipsReleaseChannel, Promise<DownloadManifest>>();
|
|
163
204
|
|
|
164
205
|
class UpstreamError extends Error {
|
|
165
206
|
statusCode: number;
|
|
@@ -215,8 +256,10 @@ function isUpdaterManifestLike(value: unknown): value is UpdaterManifest {
|
|
|
215
256
|
return typeof obj.version === "string" && obj.version.length > 0;
|
|
216
257
|
}
|
|
217
258
|
|
|
218
|
-
async function fetchUpdaterManifest(
|
|
219
|
-
|
|
259
|
+
async function fetchUpdaterManifest(
|
|
260
|
+
channel: ClipsReleaseChannel,
|
|
261
|
+
): Promise<UpdaterManifest> {
|
|
262
|
+
const res = await fetch(RELEASE_CHANNEL_CONFIG[channel].updaterManifestUrl, {
|
|
220
263
|
headers: {
|
|
221
264
|
accept: "application/json",
|
|
222
265
|
"user-agent": "clips-download-page",
|
|
@@ -236,35 +279,38 @@ async function fetchUpdaterManifest(): Promise<UpdaterManifest> {
|
|
|
236
279
|
return json;
|
|
237
280
|
}
|
|
238
281
|
|
|
239
|
-
async function findUpdaterPinnedRelease(
|
|
282
|
+
async function findUpdaterPinnedRelease(
|
|
283
|
+
channel: ClipsReleaseChannel,
|
|
284
|
+
): Promise<GhRelease | null> {
|
|
240
285
|
try {
|
|
241
|
-
const manifest = await fetchUpdaterManifest();
|
|
242
|
-
const
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
286
|
+
const manifest = await fetchUpdaterManifest(channel);
|
|
287
|
+
const releasePrefix = RELEASE_CHANNEL_CONFIG[channel].releasePrefix;
|
|
288
|
+
const version = manifest.version
|
|
289
|
+
.replace(/^clips(?:-nightly)?-v/, "")
|
|
290
|
+
.replace(/^v/, "");
|
|
291
|
+
const release = await fetchReleaseByTag(`${releasePrefix}${version}`);
|
|
292
|
+
return release && isClipsReleaseForChannel(release, channel)
|
|
293
|
+
? release
|
|
294
|
+
: null;
|
|
249
295
|
} catch {
|
|
250
296
|
return null;
|
|
251
297
|
}
|
|
252
298
|
}
|
|
253
299
|
|
|
254
|
-
async function findLatestClipsRelease(
|
|
255
|
-
|
|
300
|
+
async function findLatestClipsRelease(
|
|
301
|
+
channel: ClipsReleaseChannel,
|
|
302
|
+
): Promise<GhRelease | null> {
|
|
303
|
+
// Start with the updater's channel pointer so fresh manual installs and
|
|
256
304
|
// auto-updates agree about the channel's current version. Then scan the
|
|
257
305
|
// versioned releases as a fallback/guard and prefer the highest semver
|
|
258
306
|
// tag; a republished older tag must not beat a newer build just because
|
|
259
307
|
// it has a later `published_at`.
|
|
260
|
-
let best: GhRelease | null = await findUpdaterPinnedRelease();
|
|
308
|
+
let best: GhRelease | null = await findUpdaterPinnedRelease(channel);
|
|
261
309
|
for (let page = 1; page <= MAX_PAGES; page++) {
|
|
262
310
|
const batch = await fetchPage(page);
|
|
263
311
|
if (batch.length === 0) break;
|
|
264
312
|
for (const r of batch) {
|
|
265
|
-
if (r
|
|
266
|
-
if (!r.tag_name.startsWith("clips-v")) continue;
|
|
267
|
-
if (!hasInstallerAssets(r)) continue;
|
|
313
|
+
if (!isClipsReleaseForChannel(r, channel)) continue;
|
|
268
314
|
if (isBetterRelease(r, best)) {
|
|
269
315
|
best = r;
|
|
270
316
|
}
|
|
@@ -274,16 +320,21 @@ async function findLatestClipsRelease(): Promise<GhRelease | null> {
|
|
|
274
320
|
return best;
|
|
275
321
|
}
|
|
276
322
|
|
|
277
|
-
async function buildManifest(
|
|
278
|
-
|
|
323
|
+
async function buildManifest(
|
|
324
|
+
channel: ClipsReleaseChannel,
|
|
325
|
+
): Promise<DownloadManifest> {
|
|
326
|
+
const latest = await findLatestClipsRelease(channel);
|
|
279
327
|
if (!latest) {
|
|
280
328
|
throw createError({
|
|
281
329
|
statusCode: 404,
|
|
282
|
-
statusMessage:
|
|
330
|
+
statusMessage:
|
|
331
|
+
channel === "nightly"
|
|
332
|
+
? "No published clips-nightly-v* release found"
|
|
333
|
+
: "No published clips-v* release found",
|
|
283
334
|
});
|
|
284
335
|
}
|
|
285
336
|
return {
|
|
286
|
-
version: latest.tag_name.replace(/^clips
|
|
337
|
+
version: latest.tag_name.replace(/^clips(?:-nightly)?-v/, ""),
|
|
287
338
|
tag: latest.tag_name,
|
|
288
339
|
pub_date: latest.published_at,
|
|
289
340
|
notes: latest.body,
|
|
@@ -296,31 +347,43 @@ async function buildManifest(): Promise<DownloadManifest> {
|
|
|
296
347
|
};
|
|
297
348
|
}
|
|
298
349
|
|
|
299
|
-
async function getManifest(
|
|
350
|
+
async function getManifest(
|
|
351
|
+
channel: ClipsReleaseChannel = "production",
|
|
352
|
+
): Promise<DownloadManifest> {
|
|
300
353
|
const now = Date.now();
|
|
301
|
-
|
|
302
|
-
if (
|
|
303
|
-
|
|
354
|
+
const cached = cache.get(channel);
|
|
355
|
+
if (cached && now - cached.ts < CACHE_TTL_MS) return cached.data;
|
|
356
|
+
const pending = inFlight.get(channel);
|
|
357
|
+
if (pending) return pending;
|
|
358
|
+
const request = (async () => {
|
|
304
359
|
try {
|
|
305
|
-
const data = await buildManifest();
|
|
306
|
-
cache
|
|
360
|
+
const data = await buildManifest(channel);
|
|
361
|
+
cache.set(channel, { data, ts: Date.now() });
|
|
307
362
|
return data;
|
|
308
363
|
} catch (err) {
|
|
309
364
|
// Stale-while-error: if we have an older payload, serve it. Only
|
|
310
365
|
// bubble the error if the cache is empty.
|
|
311
|
-
if (
|
|
366
|
+
if (cached) return cached.data;
|
|
312
367
|
throw err;
|
|
313
368
|
} finally {
|
|
314
|
-
inFlight
|
|
369
|
+
inFlight.delete(channel);
|
|
315
370
|
}
|
|
316
371
|
})();
|
|
317
|
-
|
|
372
|
+
inFlight.set(channel, request);
|
|
373
|
+
return request;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export function normalizeClipsReleaseChannel(
|
|
377
|
+
value: unknown,
|
|
378
|
+
): ClipsReleaseChannel {
|
|
379
|
+
return value === "nightly" ? "nightly" : "production";
|
|
318
380
|
}
|
|
319
381
|
|
|
320
382
|
export default defineEventHandler(async (event) => {
|
|
383
|
+
const channel = normalizeClipsReleaseChannel(getQuery(event).channel);
|
|
321
384
|
let manifest: DownloadManifest;
|
|
322
385
|
try {
|
|
323
|
-
manifest = await getManifest();
|
|
386
|
+
manifest = await getManifest(channel);
|
|
324
387
|
} catch (err) {
|
|
325
388
|
const e = err as {
|
|
326
389
|
statusCode?: number;
|
|
@@ -63,7 +63,7 @@ function HtmlDiagram({ data, ctx, compact, }) {
|
|
|
63
63
|
? scopeDesignCss(safeCss, `[data-plan-diagram-scope="${scopeId}"]`)
|
|
64
64
|
: "";
|
|
65
65
|
}, [data.css, scopeId]);
|
|
66
|
-
return (_jsxs("div", { ref: ref, className: cn("plan-diagram-shell", compact && "plan-diagram-compact"), children: [_jsxs("div", { className: "plan-diagram-frame", dir: ctx.textDirection, "data-theme": isDark ? "dark" : "light", "data-style": style, "data-frame": showFrame ? "show" : "hide", "data-text-direction": ctx.textDirection, "data-plan-diagram-scope": scopeId, children: [scopedCss && _jsx("style", { children: scopedCss }), _jsx("div", { className: "plan-diagram-frame-content", dangerouslySetInnerHTML: { __html: safeHtml } })] }), _jsx(RoughOverlay, { scopeRef: ref, enabled: !designMode && style === "sketchy", drawFrame: showFrame, selector: DIAGRAM_ROUGH_SELECTOR }), data.caption && !compact && (_jsx("p", { className: "mt-3 text-sm leading-6 text-muted-foreground", children: data.caption }))] }));
|
|
66
|
+
return (_jsxs("div", { ref: ref, className: cn("plan-diagram-shell", compact && "plan-diagram-compact"), children: [_jsxs("div", { className: "plan-diagram-frame", dir: ctx.textDirection, "data-theme": isDark ? "dark" : "light", "data-style": style, "data-frame": showFrame ? "show" : "hide", "data-text-direction": ctx.textDirection, "data-plan-diagram-scope": scopeId, children: [scopedCss && _jsx("style", { children: scopedCss }), _jsx("div", { className: "plan-diagram-frame-content", "data-theme": isDark ? "dark" : "light", dangerouslySetInnerHTML: { __html: safeHtml } })] }), _jsx(RoughOverlay, { scopeRef: ref, enabled: !designMode && style === "sketchy", drawFrame: showFrame, selector: DIAGRAM_ROUGH_SELECTOR }), data.caption && !compact && (_jsx("p", { className: "mt-3 text-sm leading-6 text-muted-foreground", children: data.caption }))] }));
|
|
67
67
|
}
|
|
68
68
|
function resolveVisualFrame(frame, ctx) {
|
|
69
69
|
const resolved = frame && frame !== "auto" ? frame : (ctx.visualFrame ?? "show");
|
|
@@ -62,11 +62,11 @@ export declare const postAwareness: import("h3").EventHandlerWithFetch<import("h
|
|
|
62
62
|
error: string;
|
|
63
63
|
states?: undefined;
|
|
64
64
|
} | {
|
|
65
|
+
error?: undefined;
|
|
65
66
|
states: {
|
|
66
67
|
clientId: number;
|
|
67
68
|
state: string;
|
|
68
69
|
}[];
|
|
69
|
-
error?: undefined;
|
|
70
70
|
}>>;
|
|
71
71
|
/**
|
|
72
72
|
* GET /_agent-native/collab/:docId/users
|
|
@@ -77,9 +77,9 @@ export declare const getActiveUsers: import("h3").EventHandlerWithFetch<import("
|
|
|
77
77
|
error: string;
|
|
78
78
|
users?: undefined;
|
|
79
79
|
} | {
|
|
80
|
+
error?: undefined;
|
|
80
81
|
users: {
|
|
81
82
|
clientId: number;
|
|
82
83
|
lastSeen: number;
|
|
83
84
|
}[];
|
|
84
|
-
error?: undefined;
|
|
85
85
|
}>>;
|
|
@@ -27,6 +27,7 @@ import { assertEmittedBackgroundFunctionOnDisk, isRecurringJobsDeployEnabled, }
|
|
|
27
27
|
import { cloneServerBundleForFunction } from "./function-bundle.js";
|
|
28
28
|
import { collectImmutableAssetPaths, IMMUTABLE_ASSET_CACHE_HEADERS, } from "./immutable-assets.js";
|
|
29
29
|
const NETLIFY_WORKSPACE_STATIC_DIR = "_workspace_static";
|
|
30
|
+
const DEFAULT_HOSTED_FEEDBACK_URL = "https://forms.agent-native.com/f/agent-native-feedback/_16ewV";
|
|
30
31
|
const NETLIFY_PUBLIC_ASSET_EXTENSIONS = new Set([
|
|
31
32
|
"avif",
|
|
32
33
|
"css",
|
|
@@ -152,6 +153,12 @@ function buildOneApp(workspaceRoot, app, preset, execFile, workspaceApps) {
|
|
|
152
153
|
AGENT_NATIVE_WORKSPACE_APP_ID: app,
|
|
153
154
|
VITE_AGENT_NATIVE_WORKSPACE: "1",
|
|
154
155
|
VITE_AGENT_NATIVE_WORKSPACE_APP_ID: app,
|
|
156
|
+
...(preset === "netlify"
|
|
157
|
+
? {
|
|
158
|
+
VITE_AGENT_NATIVE_FEEDBACK_URL: process.env.VITE_AGENT_NATIVE_FEEDBACK_URL ??
|
|
159
|
+
DEFAULT_HOSTED_FEEDBACK_URL,
|
|
160
|
+
}
|
|
161
|
+
: {}),
|
|
155
162
|
APP_BASE_PATH: `/${app}`,
|
|
156
163
|
VITE_APP_BASE_PATH: `/${app}`,
|
|
157
164
|
AGENT_NATIVE_WORKSPACE_APP_AUDIENCE: workspaceAppAudience,
|
|
@@ -25,6 +25,7 @@ export interface McpOAuthRoutesOptions {
|
|
|
25
25
|
export declare function redirectWithStagedCookies(event: H3Event, location: string): Response;
|
|
26
26
|
export declare function mountMcpOAuthRoutes(nitroApp: any, options: McpOAuthRoutesOptions): void;
|
|
27
27
|
export declare function resolveMcpOAuthScope(serverUrl: URL, requestedScope: unknown): RemoteMcpScope | null;
|
|
28
|
+
export declare function stripMcpOAuthAppBasePath(path: string, basePath: string): string;
|
|
28
29
|
export declare function resolveManagedMcpOAuthClient(serverUrl: URL): Promise<StoredOAuthClientInformation | undefined>;
|
|
29
30
|
export declare function setMcpOAuthFlowCookie(event: H3Event, flow: McpOAuthFlow, secure: boolean): void;
|
|
30
31
|
export declare function readMcpOAuthFlowCookie(event: H3Event): McpOAuthFlow | null;
|