@agent-native/core 0.77.24 → 0.78.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 +2 -2
- package/corpus/core/CHANGELOG.md +20 -0
- package/corpus/core/docs/content/template-analytics.md +5 -0
- package/corpus/core/docs/content/tracking.md +45 -0
- package/corpus/core/package.json +3 -1
- package/corpus/core/src/agent/types.ts +22 -0
- package/corpus/core/src/client/AgentPanel.tsx +34 -18
- package/corpus/core/src/client/AssistantChat.tsx +40 -0
- package/corpus/core/src/client/agent-chat.ts +173 -0
- package/corpus/core/src/client/analytics.ts +141 -5
- package/corpus/core/src/client/composer/TiptapComposer.tsx +316 -60
- package/corpus/core/src/client/composer/extensions/MentionReference.tsx +3 -0
- package/corpus/core/src/client/composer/types.ts +22 -0
- package/corpus/core/src/client/index.ts +14 -0
- package/corpus/core/src/client/session-replay.ts +800 -0
- package/corpus/core/src/db/schema.ts +12 -0
- package/corpus/core/src/deploy/build.ts +75 -0
- package/corpus/core/src/private-blob/index.ts +18 -0
- package/corpus/core/src/private-blob/registry.ts +241 -0
- package/corpus/core/src/private-blob/types.ts +55 -0
- package/corpus/core/src/scripts/utils.ts +34 -6
- package/corpus/core/src/server/agent-chat-plugin.ts +10 -0
- package/corpus/core/src/server/email.ts +5 -0
- package/corpus/core/src/server/index.ts +1 -0
- package/corpus/core/src/styles/agent-native.css +24 -0
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +1 -1
- package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +43 -0
- package/corpus/templates/analytics/AGENTS.md +32 -1
- package/corpus/templates/analytics/DEVELOPING.md +9 -1
- package/corpus/templates/analytics/actions/delete-dashboard-report-subscription.ts +24 -0
- package/corpus/templates/analytics/actions/get-session-replay-events.ts +50 -0
- package/corpus/templates/analytics/actions/get-session-replay-summary.ts +27 -0
- package/corpus/templates/analytics/actions/list-dashboard-report-subscriptions.ts +24 -0
- package/corpus/templates/analytics/actions/list-session-recordings.ts +59 -0
- package/corpus/templates/analytics/actions/navigate.ts +15 -5
- package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +2 -2
- package/corpus/templates/analytics/actions/save-dashboard-report-subscription.ts +36 -0
- package/corpus/templates/analytics/actions/send-dashboard-report-now.ts +60 -0
- package/corpus/templates/analytics/actions/view-screen.ts +50 -2
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +51 -2
- package/corpus/templates/analytics/app/components/layout/Header.tsx +4 -1
- package/corpus/templates/analytics/app/components/layout/Layout.tsx +14 -2
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +6 -6
- package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +35 -6
- package/corpus/templates/analytics/app/i18n-data.ts +1119 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/EmailReportDialog.tsx +437 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +11 -12
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/dashboard-layout.ts +43 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +136 -43
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/report-filters.ts +26 -0
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +450 -0
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +419 -0
- package/corpus/templates/analytics/app/routes/overview.tsx +13 -6
- package/corpus/templates/analytics/app/routes/sessions.$recordingId.tsx +10 -0
- package/corpus/templates/analytics/app/routes/sessions._index.tsx +10 -0
- package/corpus/templates/analytics/changelog/2026-06-25-analytics-now-opens-directly-to-ask-instead-of-the-old-overv.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-25-dashboard-charts-now-show-reliable-blue-placement-lines-whil.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-26-dashboards-can-now-send-scheduled-daily-email-reports-from-t.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-26-session-replay-browser-sessions-can-now-be-reviewed-from.md +6 -0
- package/corpus/templates/analytics/package.json +5 -2
- package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +131 -0
- package/corpus/templates/analytics/scripts/seed-session-replay.ts +198 -0
- package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +140 -27
- package/corpus/templates/analytics/server/db/index.ts +12 -0
- package/corpus/templates/analytics/server/db/schema.ts +131 -0
- package/corpus/templates/analytics/server/handlers/session-replay.ts +281 -0
- package/corpus/templates/analytics/server/handlers/sql-query.ts +8 -348
- package/corpus/templates/analytics/server/jobs/dashboard-report.ts +66 -0
- package/corpus/templates/analytics/server/jobs/session-replay-retention.ts +29 -0
- package/corpus/templates/analytics/server/lib/dashboard-panel-query.ts +365 -0
- package/corpus/templates/analytics/server/lib/dashboard-report-subscriptions.ts +473 -0
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +405 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +63 -18
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +98 -26
- package/corpus/templates/analytics/server/lib/session-replay.ts +1645 -0
- package/corpus/templates/analytics/server/plugins/auth.ts +1 -1
- package/corpus/templates/analytics/server/plugins/dashboard-report-jobs.ts +47 -0
- package/corpus/templates/analytics/server/plugins/db.ts +200 -0
- package/corpus/templates/analytics/server/plugins/session-replay-retention-jobs.ts +41 -0
- package/corpus/templates/analytics/server/routes/api/analytics/replay.options.ts +1 -0
- package/corpus/templates/analytics/server/routes/api/analytics/replay.post.ts +1 -0
- package/corpus/templates/analytics/server/routes/api/dashboard-reports/run.post.ts +61 -0
- package/corpus/templates/analytics/server/routes/api/session-replay/recordings/[recordingId]/chunks/[seq].get.ts +1 -0
- package/corpus/templates/analytics/server/routes/api/session-replay/recordings/[recordingId]/events.get.ts +1 -0
- package/corpus/templates/analytics/server/routes/api/session-replay/recordings/[recordingId]/manifest.get.ts +1 -0
- package/corpus/templates/analytics/server/routes/api/session-replay/recordings/[recordingId].get.ts +1 -0
- package/corpus/templates/analytics/server/routes/api/session-replay/recordings.get.ts +1 -0
- package/corpus/templates/assets/.agents/skills/asset-generation/SKILL.md +10 -2
- package/corpus/templates/assets/.agents/skills/image-generation/SKILL.md +14 -7
- package/corpus/templates/assets/.agents/skills/library-management/SKILL.md +12 -0
- package/corpus/templates/assets/AGENTS.md +18 -3
- package/corpus/templates/assets/actions/_image-model-default.ts +24 -0
- package/corpus/templates/assets/actions/dismiss-variant-slots.ts +15 -22
- package/corpus/templates/assets/actions/generate-asset.ts +30 -23
- package/corpus/templates/assets/actions/generate-image-batch.ts +76 -29
- package/corpus/templates/assets/actions/generate-image.ts +46 -30
- package/corpus/templates/assets/actions/generate-video.ts +18 -3
- package/corpus/templates/assets/actions/list-assets.ts +39 -8
- package/corpus/templates/assets/actions/list-libraries.ts +24 -2
- package/corpus/templates/assets/actions/navigate.ts +1 -1
- package/corpus/templates/assets/actions/open-asset-picker.ts +38 -1
- package/corpus/templates/assets/actions/refresh-generation-run.ts +10 -0
- package/corpus/templates/assets/actions/save-generated-image.ts +21 -19
- package/corpus/templates/assets/actions/variant-slots.ts +131 -28
- package/corpus/templates/assets/actions/view-screen.ts +9 -0
- package/corpus/templates/assets/app/components/generation/GenerationResults.tsx +425 -0
- package/corpus/templates/assets/app/components/layout/Header.tsx +3 -1
- package/corpus/templates/assets/app/components/layout/Layout.tsx +5 -0
- package/corpus/templates/assets/app/components/layout/Sidebar.tsx +4 -6
- package/corpus/templates/assets/app/hooks/use-navigation-state.ts +47 -21
- package/corpus/templates/assets/app/i18n-data.ts +1815 -0
- package/corpus/templates/assets/app/lib/libraries.ts +6 -0
- package/corpus/templates/assets/app/lib/picker-chat-handoff.ts +71 -0
- package/corpus/templates/assets/app/routes/_index.tsx +4 -0
- package/corpus/templates/assets/app/routes/asset.$id.tsx +2 -2
- package/corpus/templates/assets/app/routes/audit.tsx +1 -1
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +1557 -2164
- package/corpus/templates/assets/app/routes/brand-kits._index.tsx +7 -191
- package/corpus/templates/assets/app/routes/brand-kits.tsx +1 -5
- package/corpus/templates/assets/app/routes/libraries.tsx +4 -4
- package/corpus/templates/assets/app/routes/library.$id.tsx +9 -7
- package/corpus/templates/assets/app/routes/library.tsx +1454 -175
- package/corpus/templates/assets/server/db/index.ts +1 -1
- package/corpus/templates/assets/server/lib/generation.ts +86 -15
- package/corpus/templates/assets/server/plugins/agent-chat.ts +196 -0
- package/corpus/templates/assets/server/plugins/db.ts +5 -0
- package/corpus/templates/assets/shared/api.ts +6 -3
- package/dist/agent/types.d.ts +21 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/client/AgentPanel.d.ts +6 -2
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +10 -4
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +6 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +15 -3
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/agent-chat.d.ts +32 -0
- package/dist/client/agent-chat.d.ts.map +1 -1
- package/dist/client/agent-chat.js +113 -0
- package/dist/client/agent-chat.js.map +1 -1
- package/dist/client/analytics.d.ts +22 -3
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js +96 -4
- package/dist/client/analytics.js.map +1 -1
- package/dist/client/composer/TiptapComposer.d.ts +2 -1
- package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/client/composer/TiptapComposer.js +228 -56
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/composer/extensions/MentionReference.d.ts.map +1 -1
- package/dist/client/composer/extensions/MentionReference.js +3 -0
- package/dist/client/composer/extensions/MentionReference.js.map +1 -1
- package/dist/client/composer/types.d.ts +21 -0
- package/dist/client/composer/types.d.ts.map +1 -1
- package/dist/client/composer/types.js.map +1 -1
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +2 -2
- package/dist/client/index.js.map +1 -1
- package/dist/client/session-replay.d.ts +41 -0
- package/dist/client/session-replay.d.ts.map +1 -0
- package/dist/client/session-replay.js +595 -0
- package/dist/client/session-replay.js.map +1 -0
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +2 -2
- package/dist/db/schema.d.ts +3 -1
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/schema.js +6 -2
- package/dist/db/schema.js.map +1 -1
- package/dist/deploy/build.d.ts +8 -0
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +66 -0
- package/dist/deploy/build.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +8 -8
- package/dist/private-blob/index.d.ts +3 -0
- package/dist/private-blob/index.d.ts.map +1 -0
- package/dist/private-blob/index.js +2 -0
- package/dist/private-blob/index.js.map +1 -0
- package/dist/private-blob/registry.d.ts +10 -0
- package/dist/private-blob/registry.d.ts.map +1 -0
- package/dist/private-blob/registry.js +152 -0
- package/dist/private-blob/registry.js.map +1 -0
- package/dist/private-blob/types.d.ts +51 -0
- package/dist/private-blob/types.d.ts.map +1 -0
- package/dist/private-blob/types.js +2 -0
- package/dist/private-blob/types.js.map +1 -0
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/scripts/utils.d.ts +4 -4
- package/dist/scripts/utils.d.ts.map +1 -1
- package/dist/scripts/utils.js +30 -6
- package/dist/scripts/utils.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +5 -0
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +2 -2
- package/dist/server/email.d.ts +2 -0
- package/dist/server/email.d.ts.map +1 -1
- package/dist/server/email.js +3 -0
- package/dist/server/email.js.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/styles/agent-native.css +24 -0
- package/docs/content/template-analytics.md +5 -0
- package/docs/content/tracking.md +45 -0
- package/package.json +3 -1
- package/corpus/templates/analytics/app/pages/overview/OverviewPage.tsx +0 -209
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createAuthPlugin } from "@agent-native/core/server";
|
|
2
2
|
|
|
3
3
|
export default createAuthPlugin({
|
|
4
|
-
publicPaths: ["/track", "/api/analytics/track"],
|
|
4
|
+
publicPaths: ["/track", "/api/analytics/track", "/api/analytics/replay"],
|
|
5
5
|
marketing: {
|
|
6
6
|
appName: "Agent-Native Analytics",
|
|
7
7
|
tagline:
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { runDashboardReportsOnce } from "../jobs/dashboard-report";
|
|
2
|
+
|
|
3
|
+
const INTERVAL_MS = 60_000;
|
|
4
|
+
let skippingLogged = false;
|
|
5
|
+
|
|
6
|
+
declare global {
|
|
7
|
+
var __AGENT_NATIVE_DASHBOARD_REPORT_SCHEDULED_RUNTIME__: boolean | undefined;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function platformSchedulerOwnsReports(): boolean {
|
|
11
|
+
return (
|
|
12
|
+
process.env.NETLIFY === "true" ||
|
|
13
|
+
globalThis.__AGENT_NATIVE_DASHBOARD_REPORT_SCHEDULED_RUNTIME__ === true
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default function registerDashboardReportJobs(): void {
|
|
18
|
+
const isProd = process.env.NODE_ENV === "production";
|
|
19
|
+
const flag =
|
|
20
|
+
process.env.ANALYTICS_DASHBOARD_REPORT_JOBS ??
|
|
21
|
+
process.env.RUN_BACKGROUND_JOBS;
|
|
22
|
+
const enabled =
|
|
23
|
+
!platformSchedulerOwnsReports() &&
|
|
24
|
+
(flag === "1" || (isProd && flag !== "0"));
|
|
25
|
+
|
|
26
|
+
if (!enabled) {
|
|
27
|
+
if (!skippingLogged) {
|
|
28
|
+
console.log(
|
|
29
|
+
platformSchedulerOwnsReports()
|
|
30
|
+
? "[dashboard-report] Skipping in-process cron because the platform scheduler owns dashboard reports."
|
|
31
|
+
: "[dashboard-report] Skipping background cron (set ANALYTICS_DASHBOARD_REPORT_JOBS=1 or RUN_BACKGROUND_JOBS=1 to enable in dev; on by default in production)",
|
|
32
|
+
);
|
|
33
|
+
skippingLogged = true;
|
|
34
|
+
}
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
setInterval(() => {
|
|
39
|
+
runDashboardReportsOnce().catch((err) =>
|
|
40
|
+
console.error("[dashboard-report] interval failed:", err),
|
|
41
|
+
);
|
|
42
|
+
}, INTERVAL_MS);
|
|
43
|
+
|
|
44
|
+
console.log(
|
|
45
|
+
`[dashboard-report] Recurring dashboard report sweep every ${INTERVAL_MS / 1000}s.`,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
@@ -114,6 +114,9 @@ export default runMigrations(
|
|
|
114
114
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
115
115
|
last_used_at TEXT,
|
|
116
116
|
revoked_at TEXT,
|
|
117
|
+
replay_allowed_origins TEXT NOT NULL DEFAULT '[]',
|
|
118
|
+
replay_max_bytes_per_day INTEGER NOT NULL DEFAULT 104857600,
|
|
119
|
+
replay_max_requests_per_minute INTEGER NOT NULL DEFAULT 120,
|
|
117
120
|
owner_email TEXT NOT NULL DEFAULT 'local@localhost',
|
|
118
121
|
org_id TEXT
|
|
119
122
|
)`,
|
|
@@ -292,6 +295,203 @@ export default runMigrations(
|
|
|
292
295
|
sqlite: `UPDATE analytics_events SET event_date = COALESCE(NULLIF(event_date, ''), substr(timestamp, 1, 10)), user_key = COALESCE(NULLIF(user_key, ''), NULLIF(user_id, ''), NULLIF(anonymous_id, '')) WHERE (event_date IS NULL OR event_date = '' OR user_key IS NULL OR user_key = '') AND timestamp >= date('now', '-400 days')`,
|
|
293
296
|
},
|
|
294
297
|
},
|
|
298
|
+
{
|
|
299
|
+
version: 50,
|
|
300
|
+
sql: {
|
|
301
|
+
postgres: `CREATE TABLE IF NOT EXISTS dashboard_report_subscriptions (
|
|
302
|
+
id TEXT PRIMARY KEY,
|
|
303
|
+
dashboard_id TEXT NOT NULL,
|
|
304
|
+
name TEXT NOT NULL,
|
|
305
|
+
recipients TEXT NOT NULL DEFAULT '[]',
|
|
306
|
+
filters TEXT NOT NULL DEFAULT '{}',
|
|
307
|
+
frequency TEXT NOT NULL DEFAULT 'daily',
|
|
308
|
+
time_of_day TEXT NOT NULL DEFAULT '09:00',
|
|
309
|
+
timezone TEXT NOT NULL DEFAULT 'UTC',
|
|
310
|
+
enabled BOOLEAN NOT NULL DEFAULT true,
|
|
311
|
+
next_run_at TEXT,
|
|
312
|
+
last_run_at TEXT,
|
|
313
|
+
last_status TEXT,
|
|
314
|
+
last_error TEXT,
|
|
315
|
+
created_at TEXT NOT NULL DEFAULT (now()::text),
|
|
316
|
+
updated_at TEXT NOT NULL DEFAULT (now()::text),
|
|
317
|
+
owner_email TEXT NOT NULL DEFAULT 'local@localhost',
|
|
318
|
+
org_id TEXT
|
|
319
|
+
)`,
|
|
320
|
+
sqlite: `CREATE TABLE IF NOT EXISTS dashboard_report_subscriptions (
|
|
321
|
+
id TEXT PRIMARY KEY,
|
|
322
|
+
dashboard_id TEXT NOT NULL,
|
|
323
|
+
name TEXT NOT NULL,
|
|
324
|
+
recipients TEXT NOT NULL DEFAULT '[]',
|
|
325
|
+
filters TEXT NOT NULL DEFAULT '{}',
|
|
326
|
+
frequency TEXT NOT NULL DEFAULT 'daily',
|
|
327
|
+
time_of_day TEXT NOT NULL DEFAULT '09:00',
|
|
328
|
+
timezone TEXT NOT NULL DEFAULT 'UTC',
|
|
329
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
330
|
+
next_run_at TEXT,
|
|
331
|
+
last_run_at TEXT,
|
|
332
|
+
last_status TEXT,
|
|
333
|
+
last_error TEXT,
|
|
334
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
335
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
336
|
+
owner_email TEXT NOT NULL DEFAULT 'local@localhost',
|
|
337
|
+
org_id TEXT
|
|
338
|
+
)`,
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
version: 51,
|
|
343
|
+
sql: `CREATE INDEX IF NOT EXISTS dashboard_report_subscriptions_due_idx ON dashboard_report_subscriptions (enabled, next_run_at)`,
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
version: 52,
|
|
347
|
+
sql: `CREATE INDEX IF NOT EXISTS dashboard_report_subscriptions_owner_dashboard_idx ON dashboard_report_subscriptions (owner_email, org_id, dashboard_id)`,
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
version: 53,
|
|
351
|
+
sql: `CREATE TABLE IF NOT EXISTS session_recordings (
|
|
352
|
+
id TEXT PRIMARY KEY,
|
|
353
|
+
public_key_id TEXT NOT NULL,
|
|
354
|
+
client_recording_id TEXT NOT NULL,
|
|
355
|
+
session_id TEXT NOT NULL,
|
|
356
|
+
user_id TEXT,
|
|
357
|
+
anonymous_id TEXT,
|
|
358
|
+
user_key TEXT,
|
|
359
|
+
started_at TEXT NOT NULL,
|
|
360
|
+
ended_at TEXT,
|
|
361
|
+
duration_ms INTEGER,
|
|
362
|
+
chunk_count INTEGER NOT NULL DEFAULT 0,
|
|
363
|
+
event_count INTEGER NOT NULL DEFAULT 0,
|
|
364
|
+
total_bytes INTEGER NOT NULL DEFAULT 0,
|
|
365
|
+
page_count INTEGER NOT NULL DEFAULT 0,
|
|
366
|
+
error_count INTEGER NOT NULL DEFAULT 0,
|
|
367
|
+
rage_click_count INTEGER NOT NULL DEFAULT 0,
|
|
368
|
+
privacy_mode TEXT NOT NULL DEFAULT 'unknown',
|
|
369
|
+
first_url TEXT,
|
|
370
|
+
last_url TEXT,
|
|
371
|
+
path TEXT,
|
|
372
|
+
hostname TEXT,
|
|
373
|
+
referrer TEXT,
|
|
374
|
+
app TEXT,
|
|
375
|
+
template TEXT,
|
|
376
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
377
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
378
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
379
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
380
|
+
last_ingested_at TEXT,
|
|
381
|
+
owner_email TEXT NOT NULL DEFAULT 'local@localhost',
|
|
382
|
+
org_id TEXT,
|
|
383
|
+
visibility TEXT NOT NULL DEFAULT 'private'
|
|
384
|
+
)`,
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
version: 54,
|
|
388
|
+
sql: `CREATE TABLE IF NOT EXISTS session_replay_chunks (
|
|
389
|
+
id TEXT PRIMARY KEY,
|
|
390
|
+
recording_id TEXT NOT NULL,
|
|
391
|
+
seq INTEGER NOT NULL,
|
|
392
|
+
checksum TEXT NOT NULL,
|
|
393
|
+
byte_length INTEGER NOT NULL DEFAULT 0,
|
|
394
|
+
event_count INTEGER NOT NULL DEFAULT 0,
|
|
395
|
+
started_at TEXT,
|
|
396
|
+
ended_at TEXT,
|
|
397
|
+
storage_kind TEXT NOT NULL,
|
|
398
|
+
storage_ref TEXT,
|
|
399
|
+
inline_data TEXT,
|
|
400
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
401
|
+
owner_email TEXT NOT NULL DEFAULT 'local@localhost',
|
|
402
|
+
org_id TEXT
|
|
403
|
+
)`,
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
version: 55,
|
|
407
|
+
sql: `CREATE TABLE IF NOT EXISTS session_recording_shares (
|
|
408
|
+
id TEXT PRIMARY KEY,
|
|
409
|
+
resource_id TEXT NOT NULL,
|
|
410
|
+
principal_type TEXT NOT NULL,
|
|
411
|
+
principal_id TEXT NOT NULL,
|
|
412
|
+
role TEXT NOT NULL DEFAULT 'viewer',
|
|
413
|
+
created_by TEXT NOT NULL,
|
|
414
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
415
|
+
)`,
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
version: 56,
|
|
419
|
+
sql: `CREATE UNIQUE INDEX IF NOT EXISTS session_recordings_key_client_idx ON session_recordings (public_key_id, client_recording_id)`,
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
version: 57,
|
|
423
|
+
sql: `CREATE UNIQUE INDEX IF NOT EXISTS session_replay_chunks_recording_seq_idx ON session_replay_chunks (recording_id, seq)`,
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
version: 58,
|
|
427
|
+
sql: `CREATE INDEX IF NOT EXISTS session_recordings_scope_started_idx ON session_recordings (org_id, owner_email, started_at)`,
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
version: 59,
|
|
431
|
+
sql: `CREATE INDEX IF NOT EXISTS session_recordings_session_idx ON session_recordings (session_id)`,
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
version: 60,
|
|
435
|
+
sql: `CREATE INDEX IF NOT EXISTS session_recording_shares_resource_idx ON session_recording_shares (resource_id)`,
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
version: 61,
|
|
439
|
+
sql: `ALTER TABLE session_recordings ADD COLUMN IF NOT EXISTS page_count INTEGER NOT NULL DEFAULT 0`,
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
version: 62,
|
|
443
|
+
sql: `ALTER TABLE session_recordings ADD COLUMN IF NOT EXISTS error_count INTEGER NOT NULL DEFAULT 0`,
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
version: 63,
|
|
447
|
+
sql: `ALTER TABLE session_recordings ADD COLUMN IF NOT EXISTS rage_click_count INTEGER NOT NULL DEFAULT 0`,
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
version: 64,
|
|
451
|
+
sql: `ALTER TABLE session_recordings ADD COLUMN IF NOT EXISTS privacy_mode TEXT NOT NULL DEFAULT 'unknown'`,
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
version: 65,
|
|
455
|
+
sql: `ALTER TABLE analytics_public_keys ADD COLUMN IF NOT EXISTS replay_allowed_origins TEXT NOT NULL DEFAULT '[]'`,
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
version: 66,
|
|
459
|
+
sql: `ALTER TABLE analytics_public_keys ADD COLUMN IF NOT EXISTS replay_max_bytes_per_day INTEGER NOT NULL DEFAULT 104857600`,
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
version: 67,
|
|
463
|
+
sql: `ALTER TABLE analytics_public_keys ADD COLUMN IF NOT EXISTS replay_max_requests_per_minute INTEGER NOT NULL DEFAULT 120`,
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
version: 68,
|
|
467
|
+
sql: {
|
|
468
|
+
postgres: `
|
|
469
|
+
ALTER TABLE dashboard_report_subscriptions ALTER COLUMN enabled DROP DEFAULT;
|
|
470
|
+
ALTER TABLE dashboard_report_subscriptions ALTER COLUMN enabled TYPE boolean USING (enabled::text IN ('true', 't', '1'));
|
|
471
|
+
ALTER TABLE dashboard_report_subscriptions ALTER COLUMN enabled SET DEFAULT true;
|
|
472
|
+
`,
|
|
473
|
+
},
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
version: 69,
|
|
477
|
+
sql: `CREATE TABLE IF NOT EXISTS session_replay_ingests (
|
|
478
|
+
id TEXT PRIMARY KEY,
|
|
479
|
+
public_key_id TEXT NOT NULL,
|
|
480
|
+
recording_id TEXT NOT NULL,
|
|
481
|
+
byte_length INTEGER NOT NULL DEFAULT 0,
|
|
482
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
483
|
+
owner_email TEXT NOT NULL DEFAULT 'local@localhost',
|
|
484
|
+
org_id TEXT
|
|
485
|
+
)`,
|
|
486
|
+
},
|
|
487
|
+
{
|
|
488
|
+
version: 70,
|
|
489
|
+
sql: `CREATE INDEX IF NOT EXISTS session_replay_ingests_public_key_created_at_idx ON session_replay_ingests (public_key_id, created_at)`,
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
version: 71,
|
|
493
|
+
sql: `CREATE INDEX IF NOT EXISTS session_replay_ingests_recording_idx ON session_replay_ingests (recording_id)`,
|
|
494
|
+
},
|
|
295
495
|
],
|
|
296
496
|
{ table: "analytics_migrations" },
|
|
297
497
|
);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { runSessionReplayRetentionOnce } from "../jobs/session-replay-retention";
|
|
2
|
+
|
|
3
|
+
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
|
|
4
|
+
let skippingLogged = false;
|
|
5
|
+
|
|
6
|
+
export default function registerSessionReplayRetentionJobs(): void {
|
|
7
|
+
const isProd = process.env.NODE_ENV === "production";
|
|
8
|
+
const flag =
|
|
9
|
+
process.env.ANALYTICS_SESSION_REPLAY_RETENTION_JOBS ??
|
|
10
|
+
process.env.RUN_BACKGROUND_JOBS;
|
|
11
|
+
const enabled = flag === "1" || (isProd && flag !== "0");
|
|
12
|
+
|
|
13
|
+
if (!enabled) {
|
|
14
|
+
if (!skippingLogged) {
|
|
15
|
+
console.log(
|
|
16
|
+
"[session-replay] Skipping retention job (set ANALYTICS_SESSION_REPLAY_RETENTION_JOBS=1 or RUN_BACKGROUND_JOBS=1 to enable in dev; on by default in production)",
|
|
17
|
+
);
|
|
18
|
+
skippingLogged = true;
|
|
19
|
+
}
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
setInterval(() => {
|
|
24
|
+
runSessionReplayRetentionOnce()
|
|
25
|
+
.then((result) => {
|
|
26
|
+
if (
|
|
27
|
+
result.finalized ||
|
|
28
|
+
result.expired ||
|
|
29
|
+
result.chunks ||
|
|
30
|
+
result.blobDeleteFailures
|
|
31
|
+
) {
|
|
32
|
+
console.log("[session-replay] Retention sweep completed", result);
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
.catch((err) =>
|
|
36
|
+
console.error("[session-replay] retention interval failed:", err),
|
|
37
|
+
);
|
|
38
|
+
}, ONE_DAY_MS);
|
|
39
|
+
|
|
40
|
+
console.log("[session-replay] Retention sweep scheduled daily.");
|
|
41
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { handleSessionReplayOptions as default } from "../../../handlers/session-replay";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { handleSessionReplayIngest as default } from "../../../handlers/session-replay";
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { timingSafeEqual } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import { createError, defineEventHandler, getHeader } from "h3";
|
|
4
|
+
|
|
5
|
+
import { runDashboardReportsOnce } from "../../../jobs/dashboard-report";
|
|
6
|
+
|
|
7
|
+
declare global {
|
|
8
|
+
var __AGENT_NATIVE_DASHBOARD_REPORT_SCHEDULED_RUNTIME__: boolean | undefined;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function productionLike(): boolean {
|
|
12
|
+
return (
|
|
13
|
+
process.env.NODE_ENV === "production" || process.env.NETLIFY === "true"
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function scheduledFunctionRuntime(): boolean {
|
|
18
|
+
return (
|
|
19
|
+
globalThis.__AGENT_NATIVE_DASHBOARD_REPORT_SCHEDULED_RUNTIME__ === true
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function cronSecret(): string | null {
|
|
24
|
+
const secret = process.env.DASHBOARD_REPORTS_CRON_SECRET?.trim();
|
|
25
|
+
return secret ? secret : null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function headerMatchesSecret(
|
|
29
|
+
header: string | undefined,
|
|
30
|
+
secret: string,
|
|
31
|
+
): boolean {
|
|
32
|
+
const expected = `Bearer ${secret}`;
|
|
33
|
+
const value = header?.trim() ?? "";
|
|
34
|
+
if (value.length !== expected.length) return false;
|
|
35
|
+
return timingSafeEqual(Buffer.from(value), Buffer.from(expected));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default defineEventHandler(async (event) => {
|
|
39
|
+
const secret = cronSecret();
|
|
40
|
+
const scheduledRuntime = scheduledFunctionRuntime();
|
|
41
|
+
if (!secret && productionLike() && !scheduledRuntime) {
|
|
42
|
+
throw createError({
|
|
43
|
+
statusCode: 503,
|
|
44
|
+
statusMessage: "DASHBOARD_REPORTS_CRON_SECRET is required",
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (
|
|
49
|
+
secret &&
|
|
50
|
+
!scheduledRuntime &&
|
|
51
|
+
!headerMatchesSecret(getHeader(event, "authorization"), secret)
|
|
52
|
+
) {
|
|
53
|
+
throw createError({
|
|
54
|
+
statusCode: 401,
|
|
55
|
+
statusMessage: "Unauthorized",
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const result = await runDashboardReportsOnce();
|
|
60
|
+
return { ok: true, ...result };
|
|
61
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { handleSessionReplayChunkBytes as default } from "../../../../../../handlers/session-replay";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { handleSessionReplayEvents as default } from "../../../../../handlers/session-replay";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { handleSessionReplayManifest as default } from "../../../../../handlers/session-replay";
|
package/corpus/templates/analytics/server/routes/api/session-replay/recordings/[recordingId].get.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { handleSessionReplaySummary as default } from "../../../../handlers/session-replay";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { handleSessionReplayList as default } from "../../../handlers/session-replay";
|
|
@@ -21,12 +21,19 @@ or generated image/video assets that another app can reference by ID and URL.
|
|
|
21
21
|
when `libraryId` is omitted, generates candidates, returns the picker filtered
|
|
22
22
|
to those run IDs, and works in in-app chat plus external MCP hosts.
|
|
23
23
|
- Use `open-asset-picker` when a person should browse, search, or select an
|
|
24
|
-
existing asset
|
|
24
|
+
existing asset inside an embedded picker, or when you want the picker to
|
|
25
|
+
handle generation itself. It still opens `/library` with the iframe/bridge
|
|
26
|
+
contract. The normal human Library workspace is `/library` and `/library/:id`.
|
|
25
27
|
Pass `mediaType: "image"` by default, or `mediaType: "video"` for video
|
|
26
28
|
libraries.
|
|
27
29
|
- Use unattended actions when the agent already knows what to do:
|
|
28
30
|
`search-assets`, `list-assets`, `generate-image`, `generate-image-batch`,
|
|
29
31
|
`generate-video`, `refresh-generation-run`, and `export-asset`.
|
|
32
|
+
- In chat, consume composer `@` references as structured generation inputs:
|
|
33
|
+
`brand-kit` maps to `libraryId`, `preset` maps to `presetId`, and
|
|
34
|
+
`media-type` chooses image generation versus video generation. If no mention
|
|
35
|
+
is available, use `view-screen`, `list-libraries`, and
|
|
36
|
+
`list-generation-presets` to choose explicit args.
|
|
30
37
|
- Use generation presets when the user asks for a repeatable output format
|
|
31
38
|
like social image, blog hero, or diagram. Call `list-generation-presets` for
|
|
32
39
|
the library and pass `presetId` through generation/refinement actions.
|
|
@@ -58,7 +65,8 @@ or generated image/video assets that another app can reference by ID and URL.
|
|
|
58
65
|
`generate-image-batch` returns, use its returned `images` / asset fields
|
|
59
66
|
directly; do not call `get-generation-run`, `refresh-generation-run`, or
|
|
60
67
|
regenerate just to verify image runs.
|
|
61
|
-
4. For preset-backed work, pass `presetId`; for handoff
|
|
68
|
+
4. For preset-backed work, pass a mentioned or selected `presetId`; for handoff
|
|
69
|
+
work, pass `sessionId`.
|
|
62
70
|
5. Let the server choose a small deterministic reference set unless the user
|
|
63
71
|
named exact assets. Canonical style anchors come from
|
|
64
72
|
`assetLibraries.settings.canonicalStyleAssetIds` and
|
|
@@ -10,8 +10,13 @@ Use this skill before calling `generate-image`, `generate-image-batch`, or
|
|
|
10
10
|
|
|
11
11
|
## Rules
|
|
12
12
|
|
|
13
|
-
- Start from
|
|
14
|
-
|
|
13
|
+
- Start from composer `@` mentions when the user tags generation inputs.
|
|
14
|
+
`brand-kit` references map to `libraryId`, `preset` references map to
|
|
15
|
+
`presetId`, and `media-type` references choose image generation versus video
|
|
16
|
+
generation. Call `view-screen` when the user says "this library" or "this
|
|
17
|
+
image" and you need fresh IDs. The image model may default from the composer
|
|
18
|
+
image-model picker; all other generation settings should be explicit action
|
|
19
|
+
args, preset values, or action schema defaults.
|
|
15
20
|
- Use category-tagged references. Blog heroes should prefer `hero`; diagrams
|
|
16
21
|
should prefer `diagram`; product imagery should include `product` and `logo`
|
|
17
22
|
references.
|
|
@@ -20,16 +25,18 @@ Use this skill before calling `generate-image`, `generate-image-batch`, or
|
|
|
20
25
|
`assets.metadata.isStyleAnchor` before sampling other relevant references.
|
|
21
26
|
- Honor library custom instructions. They are persistent prompt guidance and
|
|
22
27
|
should be updated when the user wants durable generation behavior.
|
|
23
|
-
- Generate
|
|
24
|
-
with stable `slotId`s so the
|
|
28
|
+
- Generate the selected candidate count for open-ended requests, usually 2-4.
|
|
29
|
+
Use `generate-image-batch` with stable `slotId`s so the shared generation tray
|
|
30
|
+
can show live slots.
|
|
25
31
|
- `generate-image` and `generate-image-batch` are synchronous for images. One
|
|
26
32
|
batch call should produce the requested candidates and return their asset
|
|
27
33
|
IDs/URLs; do not follow it with `get-generation-run`,
|
|
28
34
|
`refresh-generation-run`, or more generation unless the user asks for another
|
|
29
35
|
direction or the returned slot has `ok: false`.
|
|
30
|
-
- For repeatable deliverables,
|
|
31
|
-
|
|
32
|
-
`
|
|
36
|
+
- For repeatable deliverables, honor a `preset` @mention as `presetId` or call
|
|
37
|
+
`list-generation-presets` when choosing one. Pass the preset through
|
|
38
|
+
`generate-image`, `generate-image-batch`, `refine-image`, or
|
|
39
|
+
`rerun-generation-run`.
|
|
33
40
|
- For designer handoff, preserve `sessionId` and call
|
|
34
41
|
`update-generation-session` after each new candidate so the active asset,
|
|
35
42
|
feedback, and run lineage stay resumable.
|
|
@@ -7,6 +7,14 @@ description: Schema, CRUD, sharing, and cascade-delete patterns for asset librar
|
|
|
7
7
|
|
|
8
8
|
Use this skill before adding fields, changing access checks, or modifying delete behavior.
|
|
9
9
|
|
|
10
|
+
## User surface
|
|
11
|
+
|
|
12
|
+
The human Library workspace is canonical at `/library`. The root selects
|
|
13
|
+
"All assets" and browses assets across every accessible brand kit; `/library/:id`
|
|
14
|
+
opens one kit's management detail. Legacy `/brand-kits` URLs redirect here.
|
|
15
|
+
Embedded picker hosts still load `/library` in an iframe and keep the existing
|
|
16
|
+
bridge contract.
|
|
17
|
+
|
|
10
18
|
## Schema overview
|
|
11
19
|
|
|
12
20
|
```
|
|
@@ -24,6 +32,8 @@ image_libraries — top-level library, has ownableColumns + shares
|
|
|
24
32
|
Every action that touches an ownable resource must scope its queries:
|
|
25
33
|
|
|
26
34
|
- **List queries**: `accessFilter(schema.assetLibraries, schema.assetLibraryShares)` in WHERE.
|
|
35
|
+
Cross-kit asset lists must first resolve the accessible library IDs, then
|
|
36
|
+
query `image_assets` by those IDs and include the parent kit title for UI chips.
|
|
27
37
|
- **Read by id**: `await resolveAccess("asset-library", libraryId)`. The `requireLibrary(id)` helper in `_helpers.ts` wraps this.
|
|
28
38
|
- **Write**: `await assertAccess("asset-library", libraryId, "editor")` for updates / inserts; `"admin"` for deletes.
|
|
29
39
|
|
|
@@ -52,6 +62,7 @@ Example: adding `image_libraries.icon`:
|
|
|
52
62
|
## Sharing
|
|
53
63
|
|
|
54
64
|
Libraries follow the standard framework sharing model:
|
|
65
|
+
|
|
55
66
|
- `visibility: "private" | "org" | "public"`
|
|
56
67
|
- Per-user / per-org grants in `image_library_shares` with `viewer | editor | admin` roles.
|
|
57
68
|
- Use the framework actions `share-resource`, `unshare-resource`, `set-resource-visibility` with `--resourceType=asset-library`. The legacy `image-library` alias remains registered for existing grants.
|
|
@@ -61,6 +72,7 @@ Generated assets and references inherit the parent library's visibility. v1 does
|
|
|
61
72
|
## Cascade delete
|
|
62
73
|
|
|
63
74
|
`delete-library` deletes in order:
|
|
75
|
+
|
|
64
76
|
1. `image_assets WHERE library_id = ?`
|
|
65
77
|
2. `image_generation_runs WHERE library_id = ?`
|
|
66
78
|
3. `image_collections WHERE library_id = ?`
|
|
@@ -21,7 +21,9 @@ Detailed library, generation, image, embed, and engine rules live in
|
|
|
21
21
|
ad hoc provider calls when the app has an action/engine abstraction.
|
|
22
22
|
- Preserve provenance and metadata for generated or imported assets.
|
|
23
23
|
- Use `view-screen` when the active library, selected asset, picker, generation,
|
|
24
|
-
or embed target is unclear. The
|
|
24
|
+
or embed target is unclear. The human Library surface is `/library` for
|
|
25
|
+
cross-kit browsing and `/library/:libraryId` for a single brand kit; embedded
|
|
26
|
+
picker hosts still use `/library` with their iframe/auth bridge params.
|
|
25
27
|
- The Create tab (`/`) is the full-page Assets chat surface. Use the shared
|
|
26
28
|
`assets` chat thread storage there, keep past chats in the left sidebar, and
|
|
27
29
|
use the right agent sidebar only on non-Create routes with view-transition
|
|
@@ -33,8 +35,21 @@ Detailed library, generation, image, embed, and engine rules live in
|
|
|
33
35
|
## Application State
|
|
34
36
|
|
|
35
37
|
- `navigation` exposes library, asset, generation, picker, embed, and selection
|
|
36
|
-
context.
|
|
37
|
-
|
|
38
|
+
context. Human Library state uses
|
|
39
|
+
`{ view: "library", selection: "all" | libraryId, tab, scope, folderId, search }`.
|
|
40
|
+
Embedded picker state keeps `{ view: "picker", mediaType, libraryId, query,
|
|
41
|
+
prompt, aspectRatio }`.
|
|
42
|
+
- Composer `@` mentions are the source of generation inputs. Map
|
|
43
|
+
`brand-kit` references to `libraryId`, `preset` references to `presetId`, and
|
|
44
|
+
`media-type` references to choosing image (`generate-image` /
|
|
45
|
+
`generate-image-batch`) or video (`generate-video`) generation. The current
|
|
46
|
+
library view auto-tags its brand kit as a visible removable chip when the
|
|
47
|
+
composer is empty. The image model is the only remaining composer-side
|
|
48
|
+
default; the image-model picker writes `imageGenerationModel`, which image
|
|
49
|
+
generation actions may use when `model` is omitted.
|
|
50
|
+
- `asset-variants` is the shared live generation tray state. New image
|
|
51
|
+
candidates should appear there through `generate-image` or
|
|
52
|
+
`generate-image-batch`; do not invent page-local progress surfaces.
|
|
38
53
|
- `navigate` moves the UI to picker, library, generation, asset, and settings
|
|
39
54
|
surfaces.
|
|
40
55
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { readAppState } from "@agent-native/core/application-state";
|
|
2
|
+
|
|
3
|
+
import { IMAGE_MODELS, type ImageModel } from "../shared/api.js";
|
|
4
|
+
|
|
5
|
+
export const IMAGE_MODEL_STATE_KEY = "imageGenerationModel";
|
|
6
|
+
|
|
7
|
+
function imageModelValue(value: unknown): ImageModel | undefined {
|
|
8
|
+
return typeof value === "string" &&
|
|
9
|
+
(IMAGE_MODELS as readonly string[]).includes(value)
|
|
10
|
+
? (value as ImageModel)
|
|
11
|
+
: undefined;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function recordValue(value: unknown): Record<string, unknown> {
|
|
15
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
16
|
+
? (value as Record<string, unknown>)
|
|
17
|
+
: {};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function readImageModelDefault(): Promise<ImageModel | undefined> {
|
|
21
|
+
const raw = await readAppState(IMAGE_MODEL_STATE_KEY).catch(() => null);
|
|
22
|
+
const state = recordValue(raw);
|
|
23
|
+
return imageModelValue(state.model) ?? imageModelValue(raw);
|
|
24
|
+
}
|
|
@@ -1,38 +1,36 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core";
|
|
2
|
-
import {
|
|
3
|
-
deleteAppState,
|
|
4
|
-
readAppState,
|
|
5
|
-
writeAppState,
|
|
6
|
-
} from "@agent-native/core/application-state";
|
|
2
|
+
import type { ActionRunContext } from "@agent-native/core/action";
|
|
7
3
|
import { assertAccess } from "@agent-native/core/sharing";
|
|
8
4
|
import { eq } from "drizzle-orm";
|
|
9
5
|
import { z } from "zod";
|
|
10
6
|
|
|
11
7
|
import { getDb, schema } from "../server/db/index.js";
|
|
12
|
-
import
|
|
8
|
+
import {
|
|
9
|
+
deleteVariantState,
|
|
10
|
+
readVariantState,
|
|
11
|
+
writeVariantState,
|
|
12
|
+
} from "./variant-slots.js";
|
|
13
13
|
|
|
14
14
|
export default defineAction({
|
|
15
15
|
description:
|
|
16
|
-
"Clear one or more live candidate slots from application_state.asset-variants. Use slotId for a single slot, scope='failed' to drop every failed slot, or scope='all' to clear the panel. Any underlying asset rows for cleared slots are deleted (requires editor access on the library).",
|
|
16
|
+
"Clear one or more live candidate slots from application_state.asset-variants. Uses the current chat thread when available. Use slotId for a single slot, scope='failed' to drop every failed slot, or scope='all' to clear the panel. Any underlying asset rows for cleared slots are deleted (requires editor access on the library).",
|
|
17
17
|
schema: z
|
|
18
18
|
.object({
|
|
19
19
|
slotId: z.string().optional(),
|
|
20
20
|
scope: z.enum(["failed", "all"]).optional(),
|
|
21
|
+
threadId: z.string().nullable().optional(),
|
|
21
22
|
})
|
|
22
23
|
.refine((v) => Boolean(v.slotId) !== Boolean(v.scope), {
|
|
23
24
|
message: "Provide exactly one of `slotId` or `scope`.",
|
|
24
25
|
}),
|
|
25
|
-
run: async ({ slotId, scope }) => {
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
raw ??
|
|
29
|
-
((await readAppState("image-variants").catch(() => null)) as
|
|
30
|
-
| unknown
|
|
31
|
-
| null);
|
|
32
|
-
const state = (legacyRaw ?? null) as AssetVariantState | null;
|
|
26
|
+
run: async ({ slotId, scope, threadId }, context?: ActionRunContext) => {
|
|
27
|
+
const effectiveThreadId = threadId ?? context?.threadId ?? null;
|
|
28
|
+
const state = await readVariantState(effectiveThreadId);
|
|
33
29
|
if (!state || !Array.isArray(state.slots) || state.slots.length === 0) {
|
|
34
30
|
return { dismissed: 0, assetsDeleted: 0, cleared: true };
|
|
35
31
|
}
|
|
32
|
+
const stateThreadId =
|
|
33
|
+
effectiveThreadId ?? state.variantScopeId ?? state.threadId ?? null;
|
|
36
34
|
|
|
37
35
|
await assertAccess("asset-library", state.libraryId, "editor");
|
|
38
36
|
|
|
@@ -64,8 +62,7 @@ export default defineAction({
|
|
|
64
62
|
const remaining = state.slots.filter((s) => !removed.has(s.slotId));
|
|
65
63
|
|
|
66
64
|
if (remaining.length === 0) {
|
|
67
|
-
await
|
|
68
|
-
await deleteAppState("image-variants").catch(() => {});
|
|
65
|
+
await deleteVariantState(stateThreadId);
|
|
69
66
|
return {
|
|
70
67
|
dismissed: toRemove.length,
|
|
71
68
|
assetsDeleted,
|
|
@@ -75,11 +72,7 @@ export default defineAction({
|
|
|
75
72
|
|
|
76
73
|
state.slots = remaining;
|
|
77
74
|
state.updatedAt = new Date().toISOString();
|
|
78
|
-
await
|
|
79
|
-
"asset-variants",
|
|
80
|
-
state as unknown as Record<string, unknown>,
|
|
81
|
-
);
|
|
82
|
-
await deleteAppState("image-variants").catch(() => {});
|
|
75
|
+
await writeVariantState(state, stateThreadId);
|
|
83
76
|
return {
|
|
84
77
|
dismissed: toRemove.length,
|
|
85
78
|
assetsDeleted,
|