@agent-native/core 0.92.5 → 0.92.6
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 +2 -2
- package/corpus/core/CHANGELOG.md +13 -0
- package/corpus/core/docs/content/template-assets.mdx +139 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/production-agent.ts +96 -6
- package/corpus/core/src/client/AssistantChat.tsx +127 -15
- package/corpus/core/src/client/chat/tool-call-display.tsx +9 -3
- package/corpus/core/src/client/org/TeamPage.tsx +274 -177
- package/corpus/core/src/client/settings/DemoModeSection.tsx +6 -6
- package/corpus/core/src/client/settings/SettingsPanel.tsx +1 -1
- package/corpus/core/src/client/settings/SettingsTabsPage.tsx +49 -1
- package/corpus/core/src/demo/actions/toggle-demo-mode.ts +2 -2
- package/corpus/core/src/demo/config.ts +3 -3
- package/corpus/core/src/demo/redact.ts +720 -0
- package/corpus/core/src/extensions/url-safety.ts +13 -1
- package/corpus/core/src/localization/default-messages.ts +7 -0
- package/corpus/core/src/templates/default/app/i18n/ar-SA.ts +7 -0
- package/corpus/core/src/templates/default/app/i18n/de-DE.ts +7 -0
- package/corpus/core/src/templates/default/app/i18n/en-US.ts +7 -0
- package/corpus/core/src/templates/default/app/i18n/es-ES.ts +7 -0
- package/corpus/core/src/templates/default/app/i18n/fr-FR.ts +7 -0
- package/corpus/core/src/templates/default/app/i18n/hi-IN.ts +7 -0
- package/corpus/core/src/templates/default/app/i18n/ja-JP.ts +7 -0
- package/corpus/core/src/templates/default/app/i18n/ko-KR.ts +7 -0
- package/corpus/core/src/templates/default/app/i18n/pt-BR.ts +7 -0
- package/corpus/core/src/templates/default/app/i18n/zh-CN.ts +7 -0
- package/corpus/core/src/templates/default/app/i18n/zh-TW.ts +7 -0
- package/corpus/templates/analytics/AGENTS.md +6 -3
- package/corpus/templates/analytics/changelog/2026-07-09-uptime-monitors-use-scheduled-workers-in-production-serverless.md +6 -0
- package/corpus/templates/analytics/docs/uptime-monitoring.md +5 -0
- package/corpus/templates/analytics/server/plugins/uptime-monitor-jobs.ts +14 -2
- package/corpus/templates/assets/.agents/skills/image-generation/SKILL.md +35 -0
- package/corpus/templates/assets/.agents/skills/library-management/SKILL.md +19 -0
- package/corpus/templates/assets/AGENTS.md +13 -0
- package/corpus/templates/assets/actions/_generation-preset-settings.ts +73 -0
- package/corpus/templates/assets/actions/_preset-skeleton-validation.ts +63 -0
- package/corpus/templates/assets/actions/create-generation-preset.ts +22 -8
- package/corpus/templates/assets/actions/duplicate-library.ts +49 -1
- package/corpus/templates/assets/actions/generate-image-batch.ts +14 -1
- package/corpus/templates/assets/actions/generate-image.ts +195 -26
- package/corpus/templates/assets/actions/import-asset-from-url.ts +323 -0
- package/corpus/templates/assets/actions/list-generation-presets.ts +1 -1
- package/corpus/templates/assets/actions/rerun-generation-run.ts +37 -0
- package/corpus/templates/assets/actions/update-generation-preset.ts +19 -1
- package/corpus/templates/assets/app/i18n/ar-SA.ts +1 -0
- package/corpus/templates/assets/app/i18n/de-DE.ts +1 -0
- package/corpus/templates/assets/app/i18n/es-ES.ts +1 -0
- package/corpus/templates/assets/app/i18n/fr-FR.ts +1 -0
- package/corpus/templates/assets/app/i18n/hi-IN.ts +1 -0
- package/corpus/templates/assets/app/i18n/ja-JP.ts +1 -0
- package/corpus/templates/assets/app/i18n/ko-KR.ts +1 -0
- package/corpus/templates/assets/app/i18n/pt-BR.ts +1 -0
- package/corpus/templates/assets/app/i18n/zh-CN.ts +1 -0
- package/corpus/templates/assets/app/i18n/zh-TW.ts +41 -0
- package/corpus/templates/assets/app/i18n-data.ts +93 -0
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +66 -11
- package/corpus/templates/assets/app/routes/brand-kits.$id_.presets.$presetId.tsx +959 -24
- package/corpus/templates/assets/changelog/2026-07-07-preset-skeleton-controls-fit-correctly-on-narrower-edit-page.md +6 -0
- package/corpus/templates/assets/changelog/2026-07-09-import-reference-images-from-url.md +6 -0
- package/corpus/templates/assets/changelog/2026-07-09-preset-reference-board.md +6 -0
- package/corpus/templates/assets/server/handlers/assets.ts +7 -27
- package/corpus/templates/assets/server/lib/generation.ts +83 -2
- package/corpus/templates/assets/server/lib/preset-chat-context.ts +17 -4
- package/corpus/templates/assets/server/lib/preset-references.ts +182 -0
- package/corpus/templates/assets/server/lib/upload-validation.ts +38 -0
- package/corpus/templates/assets/shared/api.ts +20 -0
- package/corpus/templates/calendar/.agents/skills/event-management/SKILL.md +6 -0
- package/corpus/templates/calendar/actions/create-event.ts +8 -2
- package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +22 -7
- package/corpus/templates/calendar/changelog/2026-07-09-calendar-events-with-guests-now-automatically-get-a-google-m.md +6 -0
- package/corpus/templates/calendar/changelog/2026-07-09-new-events-now-mark-the-creator-rsvp-as-yes.md +6 -0
- package/corpus/templates/calendar/server/handlers/events.ts +9 -2
- package/corpus/templates/calendar/server/lib/event-video-conferencing.ts +58 -0
- package/corpus/templates/calendar/server/lib/google-calendar.ts +1 -0
- package/corpus/templates/clips/changelog/2026-07-09-meeting-reminder-popovers-can-now-be-dismissed-with-a-top-le.md +6 -0
- package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +3 -1
- package/corpus/templates/clips/desktop/src/styles.css +13 -9
- package/corpus/templates/clips/desktop/src-tauri/src/notifications.rs +5 -2
- package/dist/agent/production-agent.d.ts +34 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +58 -5
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +3 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +96 -15
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/chat/tool-call-display.d.ts.map +1 -1
- package/dist/client/chat/tool-call-display.js +5 -1
- package/dist/client/chat/tool-call-display.js.map +1 -1
- package/dist/client/org/TeamPage.d.ts.map +1 -1
- package/dist/client/org/TeamPage.js +32 -13
- package/dist/client/org/TeamPage.js.map +1 -1
- package/dist/client/settings/DemoModeSection.d.ts +3 -3
- package/dist/client/settings/DemoModeSection.js +4 -4
- package/dist/client/settings/DemoModeSection.js.map +1 -1
- package/dist/client/settings/SettingsPanel.js +1 -1
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/settings/SettingsTabsPage.d.ts.map +1 -1
- package/dist/client/settings/SettingsTabsPage.js +38 -2
- package/dist/client/settings/SettingsTabsPage.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/demo/actions/toggle-demo-mode.js +2 -2
- package/dist/demo/actions/toggle-demo-mode.js.map +1 -1
- package/dist/demo/config.js +3 -3
- package/dist/demo/config.js.map +1 -1
- package/dist/demo/redact.d.ts +1 -1
- package/dist/demo/redact.d.ts.map +1 -1
- package/dist/demo/redact.js +0 -0
- package/dist/demo/redact.js.map +1 -1
- package/dist/extensions/url-safety.d.ts +5 -0
- package/dist/extensions/url-safety.d.ts.map +1 -1
- package/dist/extensions/url-safety.js +10 -0
- package/dist/extensions/url-safety.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/localization/default-messages.d.ts +7 -0
- package/dist/localization/default-messages.d.ts.map +1 -1
- package/dist/localization/default-messages.js +7 -0
- package/dist/localization/default-messages.js.map +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/templates/default/app/i18n/ar-SA.ts +7 -0
- package/dist/templates/default/app/i18n/de-DE.ts +7 -0
- package/dist/templates/default/app/i18n/en-US.ts +7 -0
- package/dist/templates/default/app/i18n/es-ES.ts +7 -0
- package/dist/templates/default/app/i18n/fr-FR.ts +7 -0
- package/dist/templates/default/app/i18n/hi-IN.ts +7 -0
- package/dist/templates/default/app/i18n/ja-JP.ts +7 -0
- package/dist/templates/default/app/i18n/ko-KR.ts +7 -0
- package/dist/templates/default/app/i18n/pt-BR.ts +7 -0
- package/dist/templates/default/app/i18n/zh-CN.ts +7 -0
- package/dist/templates/default/app/i18n/zh-TW.ts +7 -0
- package/docs/content/template-assets.mdx +139 -0
- package/package.json +1 -1
- package/src/templates/default/app/i18n/ar-SA.ts +7 -0
- package/src/templates/default/app/i18n/de-DE.ts +7 -0
- package/src/templates/default/app/i18n/en-US.ts +7 -0
- package/src/templates/default/app/i18n/es-ES.ts +7 -0
- package/src/templates/default/app/i18n/fr-FR.ts +7 -0
- package/src/templates/default/app/i18n/hi-IN.ts +7 -0
- package/src/templates/default/app/i18n/ja-JP.ts +7 -0
- package/src/templates/default/app/i18n/ko-KR.ts +7 -0
- package/src/templates/default/app/i18n/pt-BR.ts +7 -0
- package/src/templates/default/app/i18n/zh-CN.ts +7 -0
- package/src/templates/default/app/i18n/zh-TW.ts +7 -0
|
@@ -247,17 +247,26 @@ export async function createSsrfSafeDispatcher(): Promise<unknown | null> {
|
|
|
247
247
|
* Throws an Error whose message starts with "SSRF blocked:" when a target
|
|
248
248
|
* (initial or via redirect) resolves to a private/internal address, or when the
|
|
249
249
|
* redirect limit is exceeded. Otherwise returns the final Response.
|
|
250
|
+
*
|
|
251
|
+
* `httpsOnly` extends the per-hop validation to the URL scheme: redirects are
|
|
252
|
+
* followed only to `https:` targets, so an HTTPS-only caller cannot be
|
|
253
|
+
* downgraded to plain HTTP by a 30x from the (untrusted) origin.
|
|
250
254
|
*/
|
|
251
255
|
export async function ssrfSafeFetch(
|
|
252
256
|
url: string,
|
|
253
257
|
init: RequestInit = {},
|
|
254
|
-
options: { maxRedirects?: number } = {},
|
|
258
|
+
options: { maxRedirects?: number; httpsOnly?: boolean } = {},
|
|
255
259
|
): Promise<Response> {
|
|
256
260
|
const maxRedirects = options.maxRedirects ?? 3;
|
|
257
261
|
const dispatcher = (await createSsrfSafeDispatcher()) ?? undefined;
|
|
258
262
|
|
|
259
263
|
let currentUrl = url;
|
|
260
264
|
for (let hop = 0; hop <= maxRedirects; hop++) {
|
|
265
|
+
if (options.httpsOnly && new URL(currentUrl).protocol !== "https:") {
|
|
266
|
+
throw new Error(
|
|
267
|
+
`SSRF blocked: refusing to fetch non-HTTPS address (${currentUrl})`,
|
|
268
|
+
);
|
|
269
|
+
}
|
|
261
270
|
if (await isBlockedExtensionUrlWithDns(currentUrl)) {
|
|
262
271
|
throw new Error(
|
|
263
272
|
`SSRF blocked: refusing to fetch private/internal address (${currentUrl})`,
|
|
@@ -273,6 +282,9 @@ export async function ssrfSafeFetch(
|
|
|
273
282
|
if (response.status >= 300 && response.status < 400) {
|
|
274
283
|
const location = response.headers.get("location");
|
|
275
284
|
if (!location) return response;
|
|
285
|
+
// Drain the redirect body so the hop's connection is released instead
|
|
286
|
+
// of being held until GC.
|
|
287
|
+
await response.body?.cancel().catch(() => {});
|
|
276
288
|
currentUrl = new URL(location, currentUrl).href;
|
|
277
289
|
continue;
|
|
278
290
|
}
|
|
@@ -410,9 +410,16 @@ const messages = {
|
|
|
410
410
|
join: "Join",
|
|
411
411
|
invitedByLabel: "Invited by {{name}}",
|
|
412
412
|
you: "You",
|
|
413
|
+
owner: "Owner",
|
|
413
414
|
admin: "Admin",
|
|
414
415
|
member: "Member",
|
|
415
416
|
members: "Members",
|
|
417
|
+
inviteMembers: "Invite members",
|
|
418
|
+
invited: "Invited",
|
|
419
|
+
role: "Role",
|
|
420
|
+
status: "Status",
|
|
421
|
+
actions: "Actions",
|
|
422
|
+
noMembers: "No members yet.",
|
|
416
423
|
memberCount_one: "{{count}} member",
|
|
417
424
|
memberCount_other: "{{count}} members",
|
|
418
425
|
youAreRole: "You are {{role}}",
|
|
@@ -423,9 +423,16 @@ const messages = {
|
|
|
423
423
|
join: "Join",
|
|
424
424
|
invitedByLabel: "Invited by {{name}}",
|
|
425
425
|
you: "You",
|
|
426
|
+
owner: "المالك",
|
|
426
427
|
admin: "مسؤول",
|
|
427
428
|
member: "Member",
|
|
428
429
|
members: "Members",
|
|
430
|
+
inviteMembers: "دعوة أعضاء",
|
|
431
|
+
invited: "تمت الدعوة",
|
|
432
|
+
role: "الدور",
|
|
433
|
+
status: "الحالة",
|
|
434
|
+
actions: "الإجراءات",
|
|
435
|
+
noMembers: "لا يوجد أعضاء بعد.",
|
|
429
436
|
memberCount_one: "{{count}} member",
|
|
430
437
|
memberCount_other: "{{count}} members",
|
|
431
438
|
memberCount_zero: "{{count}} members",
|
|
@@ -410,9 +410,16 @@ const messages = {
|
|
|
410
410
|
join: "Join",
|
|
411
411
|
invitedByLabel: "Invited by {{name}}",
|
|
412
412
|
you: "You",
|
|
413
|
+
owner: "Eigentümer",
|
|
413
414
|
admin: "Admin",
|
|
414
415
|
member: "Member",
|
|
415
416
|
members: "Members",
|
|
417
|
+
inviteMembers: "Mitglieder einladen",
|
|
418
|
+
invited: "Eingeladen",
|
|
419
|
+
role: "Rolle",
|
|
420
|
+
status: "Status",
|
|
421
|
+
actions: "Aktionen",
|
|
422
|
+
noMembers: "Noch keine Mitglieder.",
|
|
416
423
|
memberCount_one: "{{count}} member",
|
|
417
424
|
memberCount_other: "{{count}} members",
|
|
418
425
|
youAreRole: "You are {{role}}",
|
|
@@ -401,9 +401,16 @@ const messages = {
|
|
|
401
401
|
join: "Join",
|
|
402
402
|
invitedByLabel: "Invited by {{name}}",
|
|
403
403
|
you: "You",
|
|
404
|
+
owner: "Owner",
|
|
404
405
|
admin: "Admin",
|
|
405
406
|
member: "Member",
|
|
406
407
|
members: "Members",
|
|
408
|
+
inviteMembers: "Invite members",
|
|
409
|
+
invited: "Invited",
|
|
410
|
+
role: "Role",
|
|
411
|
+
status: "Status",
|
|
412
|
+
actions: "Actions",
|
|
413
|
+
noMembers: "No members yet.",
|
|
407
414
|
memberCount_one: "{{count}} member",
|
|
408
415
|
memberCount_other: "{{count}} members",
|
|
409
416
|
youAreRole: "You are {{role}}",
|
|
@@ -417,9 +417,16 @@ const messages = {
|
|
|
417
417
|
join: "Join",
|
|
418
418
|
invitedByLabel: "Invited by {{name}}",
|
|
419
419
|
you: "You",
|
|
420
|
+
owner: "Propietario",
|
|
420
421
|
admin: "Administrador",
|
|
421
422
|
member: "Member",
|
|
422
423
|
members: "Members",
|
|
424
|
+
inviteMembers: "Invitar miembros",
|
|
425
|
+
invited: "Invitado",
|
|
426
|
+
role: "Rol",
|
|
427
|
+
status: "Estado",
|
|
428
|
+
actions: "Acciones",
|
|
429
|
+
noMembers: "Aún no hay miembros.",
|
|
423
430
|
memberCount_one: "{{count}} member",
|
|
424
431
|
memberCount_other: "{{count}} members",
|
|
425
432
|
memberCount_many: "{{count}} members",
|
|
@@ -415,9 +415,16 @@ const messages = {
|
|
|
415
415
|
join: "Join",
|
|
416
416
|
invitedByLabel: "Invited by {{name}}",
|
|
417
417
|
you: "You",
|
|
418
|
+
owner: "Propriétaire",
|
|
418
419
|
admin: "Admin",
|
|
419
420
|
member: "Member",
|
|
420
421
|
members: "Members",
|
|
422
|
+
inviteMembers: "Inviter des membres",
|
|
423
|
+
invited: "Invité",
|
|
424
|
+
role: "Rôle",
|
|
425
|
+
status: "Statut",
|
|
426
|
+
actions: "Actions",
|
|
427
|
+
noMembers: "Aucun membre pour le moment.",
|
|
421
428
|
memberCount_one: "{{count}} member",
|
|
422
429
|
memberCount_other: "{{count}} members",
|
|
423
430
|
memberCount_many: "{{count}} members",
|
|
@@ -397,9 +397,16 @@ const messages = {
|
|
|
397
397
|
join: "Join",
|
|
398
398
|
invitedByLabel: "Invited by {{name}}",
|
|
399
399
|
you: "You",
|
|
400
|
+
owner: "स्वामी",
|
|
400
401
|
admin: "व्यवस्थापक",
|
|
401
402
|
member: "Member",
|
|
402
403
|
members: "Members",
|
|
404
|
+
inviteMembers: "सदस्यों को आमंत्रित करें",
|
|
405
|
+
invited: "आमंत्रित",
|
|
406
|
+
role: "भूमिका",
|
|
407
|
+
status: "स्थिति",
|
|
408
|
+
actions: "कार्रवाइयां",
|
|
409
|
+
noMembers: "अभी कोई सदस्य नहीं है.",
|
|
403
410
|
memberCount_one: "{{count}} member",
|
|
404
411
|
memberCount_other: "{{count}} members",
|
|
405
412
|
youAreRole: "You are {{role}}",
|
|
@@ -402,9 +402,16 @@ const messages = {
|
|
|
402
402
|
join: "Join",
|
|
403
403
|
invitedByLabel: "Invited by {{name}}",
|
|
404
404
|
you: "You",
|
|
405
|
+
owner: "オーナー",
|
|
405
406
|
admin: "管理者",
|
|
406
407
|
member: "Member",
|
|
407
408
|
members: "Members",
|
|
409
|
+
inviteMembers: "メンバーを招待",
|
|
410
|
+
invited: "招待済み",
|
|
411
|
+
role: "ロール",
|
|
412
|
+
status: "ステータス",
|
|
413
|
+
actions: "操作",
|
|
414
|
+
noMembers: "メンバーはまだいません。",
|
|
408
415
|
memberCount_other: "{{count}} members",
|
|
409
416
|
youAreRole: "You are {{role}}",
|
|
410
417
|
changeRole: "役割を変更する",
|
|
@@ -395,9 +395,16 @@ const messages = {
|
|
|
395
395
|
join: "Join",
|
|
396
396
|
invitedByLabel: "Invited by {{name}}",
|
|
397
397
|
you: "You",
|
|
398
|
+
owner: "소유자",
|
|
398
399
|
admin: "관리자",
|
|
399
400
|
member: "Member",
|
|
400
401
|
members: "Members",
|
|
402
|
+
inviteMembers: "구성원 초대",
|
|
403
|
+
invited: "초대됨",
|
|
404
|
+
role: "역할",
|
|
405
|
+
status: "상태",
|
|
406
|
+
actions: "작업",
|
|
407
|
+
noMembers: "아직 구성원이 없습니다.",
|
|
401
408
|
memberCount_other: "{{count}} members",
|
|
402
409
|
youAreRole: "You are {{role}}",
|
|
403
410
|
changeRole: "역할 변경",
|
|
@@ -414,9 +414,16 @@ const messages = {
|
|
|
414
414
|
join: "Join",
|
|
415
415
|
invitedByLabel: "Invited by {{name}}",
|
|
416
416
|
you: "You",
|
|
417
|
+
owner: "Proprietário",
|
|
417
418
|
admin: "Admin",
|
|
418
419
|
member: "Member",
|
|
419
420
|
members: "Members",
|
|
421
|
+
inviteMembers: "Convidar membros",
|
|
422
|
+
invited: "Convidado",
|
|
423
|
+
role: "Função",
|
|
424
|
+
status: "Status",
|
|
425
|
+
actions: "Ações",
|
|
426
|
+
noMembers: "Ainda não há membros.",
|
|
420
427
|
memberCount_one: "{{count}} member",
|
|
421
428
|
memberCount_other: "{{count}} members",
|
|
422
429
|
memberCount_many: "{{count}} members",
|
|
@@ -383,9 +383,16 @@ const messages = {
|
|
|
383
383
|
join: "Join",
|
|
384
384
|
invitedByLabel: "Invited by {{name}}",
|
|
385
385
|
you: "You",
|
|
386
|
+
owner: "所有者",
|
|
386
387
|
admin: "管理员",
|
|
387
388
|
member: "Member",
|
|
388
389
|
members: "Members",
|
|
390
|
+
inviteMembers: "邀请成员",
|
|
391
|
+
invited: "已邀请",
|
|
392
|
+
role: "角色",
|
|
393
|
+
status: "状态",
|
|
394
|
+
actions: "操作",
|
|
395
|
+
noMembers: "暂无成员。",
|
|
389
396
|
memberCount_other: "{{count}} members",
|
|
390
397
|
youAreRole: "You are {{role}}",
|
|
391
398
|
changeRole: "改变角色",
|
|
@@ -381,9 +381,16 @@ const messages = {
|
|
|
381
381
|
join: "Join",
|
|
382
382
|
invitedByLabel: "由 {{name}} 邀請",
|
|
383
383
|
you: "You",
|
|
384
|
+
owner: "擁有者",
|
|
384
385
|
admin: "管理員",
|
|
385
386
|
member: "Member",
|
|
386
387
|
members: "Members",
|
|
388
|
+
inviteMembers: "邀請成員",
|
|
389
|
+
invited: "已邀請",
|
|
390
|
+
role: "角色",
|
|
391
|
+
status: "狀態",
|
|
392
|
+
actions: "操作",
|
|
393
|
+
noMembers: "尚無成員。",
|
|
387
394
|
memberCount_other: "{{count}} 位成員",
|
|
388
395
|
youAreRole: "你的角色是 {{role}}",
|
|
389
396
|
changeRole: "改變角色",
|
|
@@ -236,9 +236,12 @@ details live in `.agents/skills/`.
|
|
|
236
236
|
`run-monitor-check`, `delete-monitor`. Checks/alerting run server-side in
|
|
237
237
|
`server/lib/uptime-monitors.ts` (sweep job `server/jobs/uptime-monitors.ts`,
|
|
238
238
|
scheduler `server/plugins/uptime-monitor-jobs.ts`) over
|
|
239
|
-
`server/db/schema-monitoring.ts`.
|
|
240
|
-
|
|
241
|
-
|
|
239
|
+
`server/db/schema-monitoring.ts`. Production serverless/Netlify-style
|
|
240
|
+
runtimes skip the in-process interval scheduler and rely on the generated
|
|
241
|
+
scheduled/background worker or external cron instead. Deep links: list
|
|
242
|
+
`?view=uptime`, detail `?view=uptime&monitor=<id>`, create
|
|
243
|
+
`?view=uptime&monitor=new`, edit `?view=uptime&monitor=<id>&edit=1`. See
|
|
244
|
+
`docs/uptime-monitoring.md`.
|
|
242
245
|
- Status pages (`app/pages/monitoring/uptime/status-pages/**`) are a config
|
|
243
246
|
sub-view under Uptime that bundle chosen monitors under a public
|
|
244
247
|
`/status/<slug>` page. Actions: `list-status-pages`, `get-status-page`,
|
|
@@ -122,6 +122,11 @@ external cron with `UPTIME_MONITORS_CRON_SECRET`. Both paths run
|
|
|
122
122
|
monitors whose interval has elapsed, checks them, records results, opens/resolves
|
|
123
123
|
incidents, and prunes old check results.
|
|
124
124
|
|
|
125
|
+
Production serverless function runtimes skip the in-process interval scheduler
|
|
126
|
+
even when `UPTIME_MONITOR_JOBS=1`, because the function may freeze or time out
|
|
127
|
+
mid-sweep. Use the generated scheduled/background worker or an external cron
|
|
128
|
+
calling `/api/uptime-monitors/run` for production serverless checks.
|
|
129
|
+
|
|
125
130
|
Environment flags (mirrors the analytics alert job):
|
|
126
131
|
|
|
127
132
|
| flag | effect |
|
|
@@ -16,9 +16,21 @@ declare global {
|
|
|
16
16
|
var __AGENT_NATIVE_UPTIME_MONITOR_SCHEDULED_RUNTIME__: boolean | undefined;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
function
|
|
19
|
+
function isProductionServerlessRuntime(): boolean {
|
|
20
|
+
if (process.env.NODE_ENV !== "production") return false;
|
|
20
21
|
return (
|
|
21
22
|
process.env.NETLIFY === "true" ||
|
|
23
|
+
Boolean(process.env.NETLIFY_FUNCTION_NAME) ||
|
|
24
|
+
Boolean(process.env.AWS_LAMBDA_FUNCTION_NAME) ||
|
|
25
|
+
Boolean(process.env.LAMBDA_TASK_ROOT) ||
|
|
26
|
+
process.env.AWS_EXECUTION_ENV?.startsWith("AWS_Lambda") === true ||
|
|
27
|
+
process.env.VERCEL === "1"
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function platformSchedulerOwnsMonitors(): boolean {
|
|
32
|
+
return (
|
|
33
|
+
isProductionServerlessRuntime() ||
|
|
22
34
|
globalThis.__AGENT_NATIVE_UPTIME_MONITOR_SCHEDULED_RUNTIME__ === true
|
|
23
35
|
);
|
|
24
36
|
}
|
|
@@ -44,7 +56,7 @@ export default function registerUptimeMonitorJobs(): void {
|
|
|
44
56
|
if (!skippingLogged) {
|
|
45
57
|
console.log(
|
|
46
58
|
platformSchedulerOwnsMonitors()
|
|
47
|
-
? "[uptime-monitors] Skipping in-process cron because
|
|
59
|
+
? "[uptime-monitors] Skipping in-process cron because production serverless runtimes rely on scheduled/background monitor sweeps."
|
|
48
60
|
: "[uptime-monitors] Skipping background cron (set UPTIME_MONITOR_JOBS=1 or RUN_BACKGROUND_JOBS=1 to enable in dev; on by default in production)",
|
|
49
61
|
);
|
|
50
62
|
skippingLogged = true;
|
|
@@ -27,6 +27,9 @@ Use this skill before calling `generate-image`, `generate-image-batch`, or
|
|
|
27
27
|
- Use category-tagged references. Blog heroes should prefer `hero`; diagrams
|
|
28
28
|
should prefer `diagram`; product imagery should include `product` and `logo`
|
|
29
29
|
references.
|
|
30
|
+
- Imported external images with `status: "reference"` are valid generation
|
|
31
|
+
inputs. Use their returned asset IDs in preset reference fills or reference
|
|
32
|
+
boards the same way you would use uploaded reference assets.
|
|
30
33
|
- Keep reference sets small and deterministic. Prefer anchors listed in
|
|
31
34
|
`assetLibraries.settings.canonicalStyleAssetIds` and assets marked
|
|
32
35
|
`assets.metadata.isStyleAnchor` before sampling other relevant references.
|
|
@@ -59,12 +62,44 @@ Use this skill before calling `generate-image`, `generate-image-batch`, or
|
|
|
59
62
|
`generate-image-batch` / `refine-image`. The design team uses the audit log
|
|
60
63
|
to review quality by app, library, model, prompt, and lineage.
|
|
61
64
|
|
|
65
|
+
## Preset-first Generation
|
|
66
|
+
|
|
67
|
+
- Before any ad-hoc generation for a brand kit, call
|
|
68
|
+
`list-generation-presets` and scan titles/descriptions/categories for a
|
|
69
|
+
use-case match. A preset encodes the designer's format, model, layout, and
|
|
70
|
+
reference board; using it is always better than improvising.
|
|
71
|
+
- If one preset clearly matches: use its `presetId`; do not restate its saved
|
|
72
|
+
aspect ratio/size/model/tier. If several plausibly match: pick the best and
|
|
73
|
+
state which one you used; do not ask the user to choose.
|
|
74
|
+
- Match named people/products/backdrops in the request to the preset's
|
|
75
|
+
reference board entry labels in `settings.presetReferences`. Fill required
|
|
76
|
+
variable entries via `presetReferenceFills`: search the library for assets of
|
|
77
|
+
those people first; ask the user for photos only when none exist. Never skip a
|
|
78
|
+
required entry.
|
|
79
|
+
- Route exact visible copy such as event titles, dates, and times to
|
|
80
|
+
`embeddedText` per the existing text rules; keep the creative direction in
|
|
81
|
+
`prompt`.
|
|
82
|
+
- If nothing matches: generate ad-hoc, say that no preset fit, and mention a
|
|
83
|
+
preset could be created for this recurring use case.
|
|
84
|
+
|
|
62
85
|
## Prompting
|
|
63
86
|
|
|
64
87
|
- Treat references as evidence, not decoration.
|
|
65
88
|
- Let the server choose references unless the user named exact assets. Automatic
|
|
66
89
|
generation uses up to 6 relevant current references, seeded by canonical
|
|
67
90
|
style anchors; explicit `referenceAssetIds` are preserved.
|
|
91
|
+
- Preset reference boards live on tagged presets as named entries such as a
|
|
92
|
+
usual host, product, backdrop, style sample, or per-event speaker. Fixed
|
|
93
|
+
entries attach automatically. Variable entries may be replaced for a run with
|
|
94
|
+
`presetReferenceFills`; each fill REPLACES that entry's pinned images rather
|
|
95
|
+
than appending. Required variable entries block generation until you provide
|
|
96
|
+
at least one image.
|
|
97
|
+
- When a tagged preset brief names required variable references, collect the
|
|
98
|
+
needed images from the user's attachments or the library and pass
|
|
99
|
+
`presetReferenceFills: [{ referenceId, assetIds }]` to `generate-image` or
|
|
100
|
+
`generate-image-batch`. Board images are additive to brand style references.
|
|
101
|
+
User-uploaded per-event people/photos should be uploaded as content images
|
|
102
|
+
(`subject` intent/role), not as reusable brand style references.
|
|
68
103
|
- If a collection's style feels underspecified, call `analyze-collection-style`
|
|
69
104
|
and use its vision brand analysis for palette, composition, lighting, subject
|
|
70
105
|
treatment, typography policy, and constraints.
|
|
@@ -90,6 +90,25 @@ Reference images and generated images live in the **same** `image_assets` table,
|
|
|
90
90
|
|
|
91
91
|
The unified table simplifies access control (one `library_id`, one access check) and makes "use a saved generation as a reference for a future generation" a first-class operation — just bump its `role` to `prior-candidate` (planned for v2; v1 just selects from any non-archived asset).
|
|
92
92
|
|
|
93
|
+
## Importing external references
|
|
94
|
+
|
|
95
|
+
Use `import-asset-from-url` when the agent has found a public HTTPS image that
|
|
96
|
+
belongs in a brand kit, such as a blog hero, product shot, logo, campaign image,
|
|
97
|
+
or diagram. Choose the narrowest reference `role` (`style_reference`,
|
|
98
|
+
`subject_reference`, `product_reference`, `background_reference`,
|
|
99
|
+
`logo_reference`, or `diagram_reference`) and preserve a useful title or
|
|
100
|
+
description when known. The deliverable `category` defaults to match the role
|
|
101
|
+
(logo → `logo`, product → `product`, diagram → `diagram`); pass an explicit
|
|
102
|
+
`category` such as `hero` or `campaign` when the image belongs in one of those
|
|
103
|
+
filtered views.
|
|
104
|
+
|
|
105
|
+
For a blog-to-brand-kit workflow: inspect the page, pick the strongest image
|
|
106
|
+
URLs, import each URL into the target `libraryId`, then wire the returned
|
|
107
|
+
`assetId`s into generation preset reference fills or call `set-canonical-logo`
|
|
108
|
+
for the exact logo. Imported assets are stored as `status: "reference"` with
|
|
109
|
+
`sourceUrl` provenance, so downstream generation, preset boards, and logo
|
|
110
|
+
compositing can use them like uploaded reference assets.
|
|
111
|
+
|
|
93
112
|
## When to add a collection
|
|
94
113
|
|
|
95
114
|
Collections are optional. Most users won't create them. Use them when:
|
|
@@ -24,6 +24,8 @@ Detailed library, generation, image, embed, and engine rules live in
|
|
|
24
24
|
- Use the configured generation/engine path for image and asset work. Do not add
|
|
25
25
|
ad hoc provider calls when the app has an action/engine abstraction.
|
|
26
26
|
- Preserve provenance and metadata for generated or imported assets.
|
|
27
|
+
- Ingest external brand or blog imagery with `import-asset-from-url`, then pin
|
|
28
|
+
the returned asset to preset reference boards or set it as the canonical logo.
|
|
27
29
|
- Use `view-screen` when the active library, selected asset, picker, generation,
|
|
28
30
|
or embed target is unclear. The human Library surface is `/library` for
|
|
29
31
|
cross-kit browsing and `/library/:libraryId` for a single brand kit; embedded
|
|
@@ -59,6 +61,17 @@ prompt, aspectRatio }`.
|
|
|
59
61
|
composition, mood, lighting, and subject — then pass the `presetId` to
|
|
60
62
|
`generate-image` / `generate-image-batch` so the saved format/model/tier/logo
|
|
61
63
|
apply automatically. Do not restate those as ad-hoc args.
|
|
64
|
+
- Image requests without a tagged preset are preset-first: the user may not know
|
|
65
|
+
presets exist. Call `list-generation-presets` for the library before
|
|
66
|
+
generating and compare the request against each preset's title, description,
|
|
67
|
+
and category. If one matches the use case (e.g. "livestream poster" -> a
|
|
68
|
+
Livestream Announcement preset), generate with its `presetId` instead of
|
|
69
|
+
ad-hoc settings. Only generate presetless when nothing plausibly matches.
|
|
70
|
+
- Presets may declare a reference board with fixed or variable named entries
|
|
71
|
+
such as a host, guest speaker, product, backdrop, or style. Fixed entries
|
|
72
|
+
attach automatically; collect images for required variable entries and pass
|
|
73
|
+
them via `presetReferenceFills` on `generate-image` /
|
|
74
|
+
`generate-image-batch`. See the `image-generation` skill for fill semantics.
|
|
62
75
|
- For exact visible copy inside a generated image, pass `embeddedText` and
|
|
63
76
|
optional `textPlacement` to `generate-image` or each `generate-image-batch`
|
|
64
77
|
slot. Keep the general `prompt` for creative direction; the structured text
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
|
|
3
|
+
import {
|
|
4
|
+
PRESET_REFERENCE_SUBJECT_IMAGES_ERROR,
|
|
5
|
+
PRESET_REFERENCE_TOTAL_IMAGES_ERROR,
|
|
6
|
+
} from "../server/lib/preset-references.js";
|
|
7
|
+
import { PRESET_REFERENCE_ROLES } from "../shared/api.js";
|
|
8
|
+
|
|
3
9
|
const fractionSchema = z.coerce.number().min(0).max(1);
|
|
4
10
|
const positiveFractionSchema = z.coerce.number().min(0.02).max(1);
|
|
5
11
|
|
|
@@ -40,9 +46,76 @@ export const presetSkeletonSpecSchema = z.object({
|
|
|
40
46
|
.optional(),
|
|
41
47
|
});
|
|
42
48
|
|
|
49
|
+
export const presetReferenceSchema = z.object({
|
|
50
|
+
id: z
|
|
51
|
+
.string()
|
|
52
|
+
.regex(/^[a-z0-9][a-z0-9-]*$/)
|
|
53
|
+
.max(40),
|
|
54
|
+
label: z.string().min(1).max(60),
|
|
55
|
+
role: z.enum(PRESET_REFERENCE_ROLES),
|
|
56
|
+
description: z.string().max(400).optional(),
|
|
57
|
+
assetIds: z.array(z.string().min(1)).max(4),
|
|
58
|
+
variable: z.coerce.boolean(),
|
|
59
|
+
required: z.coerce.boolean(),
|
|
60
|
+
});
|
|
61
|
+
|
|
43
62
|
export const generationPresetSettingsSchema = z
|
|
44
63
|
.object({
|
|
45
64
|
includeLogo: z.coerce.boolean().optional(),
|
|
46
65
|
skeletonSpec: presetSkeletonSpecSchema.nullable().optional(),
|
|
66
|
+
presetReferences: z
|
|
67
|
+
.array(presetReferenceSchema)
|
|
68
|
+
.max(6)
|
|
69
|
+
.nullable()
|
|
70
|
+
.optional(),
|
|
71
|
+
})
|
|
72
|
+
.superRefine((settings, ctx) => {
|
|
73
|
+
const references = settings.presetReferences ?? [];
|
|
74
|
+
const seen = new Set<string>();
|
|
75
|
+
for (const entry of references) {
|
|
76
|
+
if (seen.has(entry.id)) {
|
|
77
|
+
ctx.addIssue({
|
|
78
|
+
code: "custom",
|
|
79
|
+
path: ["presetReferences"],
|
|
80
|
+
message: "Reference entry ids must be unique.",
|
|
81
|
+
});
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
seen.add(entry.id);
|
|
85
|
+
if (entry.required && !entry.variable && entry.assetIds.length === 0) {
|
|
86
|
+
ctx.addIssue({
|
|
87
|
+
code: "custom",
|
|
88
|
+
path: ["presetReferences", entry.id],
|
|
89
|
+
message:
|
|
90
|
+
"A required fixed reference needs at least one image. Pin images or mark it as variable.",
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// Budget with the minimum images each entry consumes at run time: a
|
|
95
|
+
// required entry with no pinned images still needs at least one fill
|
|
96
|
+
// image, so reserve it here or the saved preset can never generate.
|
|
97
|
+
const minimumImages = (entry: { required: boolean; assetIds: string[] }) =>
|
|
98
|
+
Math.max(entry.assetIds.length, entry.required ? 1 : 0);
|
|
99
|
+
const total = references.reduce(
|
|
100
|
+
(sum, entry) => sum + minimumImages(entry),
|
|
101
|
+
0,
|
|
102
|
+
);
|
|
103
|
+
if (total > 8) {
|
|
104
|
+
ctx.addIssue({
|
|
105
|
+
code: "custom",
|
|
106
|
+
path: ["presetReferences"],
|
|
107
|
+
message: PRESET_REFERENCE_TOTAL_IMAGES_ERROR,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
const subjectTotal = references
|
|
111
|
+
.filter((entry) => entry.role === "subject")
|
|
112
|
+
.reduce((sum, entry) => sum + minimumImages(entry), 0);
|
|
113
|
+
if (subjectTotal > 4) {
|
|
114
|
+
ctx.addIssue({
|
|
115
|
+
code: "custom",
|
|
116
|
+
path: ["presetReferences"],
|
|
117
|
+
message: PRESET_REFERENCE_SUBJECT_IMAGES_ERROR,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
47
120
|
})
|
|
48
121
|
.catchall(z.unknown());
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { inArray } from "drizzle-orm";
|
|
2
2
|
|
|
3
3
|
import { schema } from "../server/db/index.js";
|
|
4
|
+
import { normalizePresetReferences } from "../server/lib/preset-references.js";
|
|
4
5
|
|
|
5
6
|
export async function assertPresetSkeletonAssetsValid(input: {
|
|
6
7
|
db: any;
|
|
@@ -62,3 +63,65 @@ export async function assertPresetSkeletonAssetsValid(input: {
|
|
|
62
63
|
);
|
|
63
64
|
}
|
|
64
65
|
}
|
|
66
|
+
|
|
67
|
+
export async function assertPresetReferenceAssetsValid(input: {
|
|
68
|
+
db: any;
|
|
69
|
+
libraryId: string;
|
|
70
|
+
settings: unknown;
|
|
71
|
+
}) {
|
|
72
|
+
const rawReferences =
|
|
73
|
+
input.settings && typeof input.settings === "object"
|
|
74
|
+
? (input.settings as { presetReferences?: unknown }).presetReferences
|
|
75
|
+
: null;
|
|
76
|
+
const references = normalizePresetReferences(rawReferences);
|
|
77
|
+
const assetIds = [...new Set(references.flatMap((entry) => entry.assetIds))];
|
|
78
|
+
if (!assetIds.length) return;
|
|
79
|
+
|
|
80
|
+
const rows = await input.db
|
|
81
|
+
.select({
|
|
82
|
+
id: schema.assets.id,
|
|
83
|
+
libraryId: schema.assets.libraryId,
|
|
84
|
+
mimeType: schema.assets.mimeType,
|
|
85
|
+
status: schema.assets.status,
|
|
86
|
+
})
|
|
87
|
+
.from(schema.assets)
|
|
88
|
+
.where(inArray(schema.assets.id, assetIds));
|
|
89
|
+
const byId = new Map<string, any>(
|
|
90
|
+
(rows as any[]).map((asset) => [asset.id, asset]),
|
|
91
|
+
);
|
|
92
|
+
const valid = assetIds.every((assetId) => {
|
|
93
|
+
const asset = byId.get(assetId);
|
|
94
|
+
return (
|
|
95
|
+
asset &&
|
|
96
|
+
asset.libraryId === input.libraryId &&
|
|
97
|
+
typeof asset.mimeType === "string" &&
|
|
98
|
+
asset.mimeType.startsWith("image/") &&
|
|
99
|
+
asset.status !== "archived" &&
|
|
100
|
+
asset.status !== "failed"
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
if (!valid) {
|
|
104
|
+
throw new Error(
|
|
105
|
+
"Reference board images must be images in this asset library.",
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function assertPresetReferenceModelCompatible(input: {
|
|
111
|
+
model: string | null | undefined;
|
|
112
|
+
settings: unknown;
|
|
113
|
+
}) {
|
|
114
|
+
const rawReferences =
|
|
115
|
+
input.settings && typeof input.settings === "object"
|
|
116
|
+
? (input.settings as { presetReferences?: unknown }).presetReferences
|
|
117
|
+
: null;
|
|
118
|
+
const references = normalizePresetReferences(rawReferences);
|
|
119
|
+
if (
|
|
120
|
+
input.model === "gemini-2.5-flash-image" &&
|
|
121
|
+
references.some((entry) => entry.role === "subject")
|
|
122
|
+
) {
|
|
123
|
+
throw new Error(
|
|
124
|
+
"Subject reference entries need a model with character consistency. Use gemini-3.1-flash-image, gemini-3-pro-image, gpt-image-1, or gpt-image-2.",
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -15,7 +15,11 @@ import {
|
|
|
15
15
|
} from "../shared/api.js";
|
|
16
16
|
import { generationPresetSettingsSchema } from "./_generation-preset-settings.js";
|
|
17
17
|
import { serializeGenerationPreset } from "./_helpers.js";
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
assertPresetReferenceAssetsValid,
|
|
20
|
+
assertPresetReferenceModelCompatible,
|
|
21
|
+
assertPresetSkeletonAssetsValid,
|
|
22
|
+
} from "./_preset-skeleton-validation.js";
|
|
19
23
|
|
|
20
24
|
export default defineAction({
|
|
21
25
|
description:
|
|
@@ -56,10 +60,25 @@ export default defineAction({
|
|
|
56
60
|
throw new Error("Collection does not belong to this asset library.");
|
|
57
61
|
}
|
|
58
62
|
}
|
|
63
|
+
const settings = {
|
|
64
|
+
...(args.settings ?? {}),
|
|
65
|
+
...(args.includeLogo !== undefined
|
|
66
|
+
? { includeLogo: args.includeLogo }
|
|
67
|
+
: {}),
|
|
68
|
+
};
|
|
59
69
|
await assertPresetSkeletonAssetsValid({
|
|
60
70
|
db,
|
|
61
71
|
libraryId: args.libraryId,
|
|
62
|
-
settings
|
|
72
|
+
settings,
|
|
73
|
+
});
|
|
74
|
+
await assertPresetReferenceAssetsValid({
|
|
75
|
+
db,
|
|
76
|
+
libraryId: args.libraryId,
|
|
77
|
+
settings,
|
|
78
|
+
});
|
|
79
|
+
assertPresetReferenceModelCompatible({
|
|
80
|
+
model: args.model,
|
|
81
|
+
settings,
|
|
63
82
|
});
|
|
64
83
|
const now = nowIso();
|
|
65
84
|
const row = {
|
|
@@ -76,12 +95,7 @@ export default defineAction({
|
|
|
76
95
|
model: args.model,
|
|
77
96
|
textPolicy: args.textPolicy,
|
|
78
97
|
referencePolicy: args.referencePolicy,
|
|
79
|
-
settings: stringifyJson(
|
|
80
|
-
...(args.settings ?? {}),
|
|
81
|
-
...(args.includeLogo !== undefined
|
|
82
|
-
? { includeLogo: args.includeLogo }
|
|
83
|
-
: {}),
|
|
84
|
-
}),
|
|
98
|
+
settings: stringifyJson(settings),
|
|
85
99
|
sortOrder: args.sortOrder ?? 100,
|
|
86
100
|
createdAt: now,
|
|
87
101
|
updatedAt: now,
|