@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
|
@@ -19,16 +19,20 @@
|
|
|
19
19
|
import { sql } from "drizzle-orm";
|
|
20
20
|
import {
|
|
21
21
|
pgTable,
|
|
22
|
+
index as pgIndex,
|
|
22
23
|
text as pgText,
|
|
23
24
|
integer as pgInteger,
|
|
24
25
|
boolean as pgBoolean,
|
|
25
26
|
doublePrecision as pgDoublePrecision,
|
|
27
|
+
uniqueIndex as pgUniqueIndex,
|
|
26
28
|
} from "drizzle-orm/pg-core";
|
|
27
29
|
import {
|
|
28
30
|
sqliteTable,
|
|
31
|
+
index as sqliteIndex,
|
|
29
32
|
text as sqliteText,
|
|
30
33
|
integer as sqliteInteger,
|
|
31
34
|
real as sqliteReal,
|
|
35
|
+
uniqueIndex as sqliteUniqueIndex,
|
|
32
36
|
} from "drizzle-orm/sqlite-core";
|
|
33
37
|
|
|
34
38
|
import { getDialect } from "./client.js";
|
|
@@ -46,6 +50,14 @@ function pg(): boolean {
|
|
|
46
50
|
export const table: typeof sqliteTable = ((...args: any[]) =>
|
|
47
51
|
pg() ? (pgTable as any)(...args) : (sqliteTable as any)(...args)) as any;
|
|
48
52
|
|
|
53
|
+
export const index: typeof sqliteIndex = ((...args: any[]) =>
|
|
54
|
+
pg() ? (pgIndex as any)(...args) : (sqliteIndex as any)(...args)) as any;
|
|
55
|
+
|
|
56
|
+
export const uniqueIndex: typeof sqliteUniqueIndex = ((...args: any[]) =>
|
|
57
|
+
pg()
|
|
58
|
+
? (pgUniqueIndex as any)(...args)
|
|
59
|
+
: (sqliteUniqueIndex as any)(...args)) as any;
|
|
60
|
+
|
|
49
61
|
/**
|
|
50
62
|
* Text column. Works identically in both dialects.
|
|
51
63
|
* Supports `{ enum: [...] }` config in both.
|
|
@@ -1318,6 +1318,16 @@ const SERVERLESS_FFMPEG_STATIC_ARCHES = new Set<NodeJS.Architecture>([
|
|
|
1318
1318
|
"arm64",
|
|
1319
1319
|
"x64",
|
|
1320
1320
|
]);
|
|
1321
|
+
const SERVERLESS_FUNCTION_PACKAGE_DENYLIST = new Set([
|
|
1322
|
+
"@vscode/test-electron",
|
|
1323
|
+
"electron",
|
|
1324
|
+
"electron-builder",
|
|
1325
|
+
"electron-updater",
|
|
1326
|
+
"electron-vite",
|
|
1327
|
+
"fsevents",
|
|
1328
|
+
"node-pty",
|
|
1329
|
+
"playwright",
|
|
1330
|
+
]);
|
|
1321
1331
|
type ServerlessFfmpegStaticArch = "arm64" | "x64";
|
|
1322
1332
|
|
|
1323
1333
|
function serverlessFfmpegStaticTargetArchFromEnv(): ServerlessFfmpegStaticArch | null {
|
|
@@ -1748,6 +1758,70 @@ function copyInstalledFfmpegStaticPackage(serverDir: string | undefined) {
|
|
|
1748
1758
|
);
|
|
1749
1759
|
}
|
|
1750
1760
|
|
|
1761
|
+
/**
|
|
1762
|
+
* Nitro's file tracer can over-include optional desktop/dev packages that are
|
|
1763
|
+
* present in a monorepo install but cannot run in serverless. Netlify installs
|
|
1764
|
+
* the generated per-function package.json before upload; if `electron` remains
|
|
1765
|
+
* there, the function can exceed Netlify's 250 MB unzipped size limit even
|
|
1766
|
+
* though the server bundle never imports Electron at runtime.
|
|
1767
|
+
*/
|
|
1768
|
+
export function sanitizeServerlessFunctionPackageManifest(
|
|
1769
|
+
functionDir: string | undefined,
|
|
1770
|
+
): void {
|
|
1771
|
+
if (!functionDir || !fs.existsSync(functionDir)) return;
|
|
1772
|
+
|
|
1773
|
+
const packageJsonPath = path.join(functionDir, "package.json");
|
|
1774
|
+
if (!fs.existsSync(packageJsonPath)) return;
|
|
1775
|
+
|
|
1776
|
+
let packageJson: Record<string, unknown>;
|
|
1777
|
+
try {
|
|
1778
|
+
packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
1779
|
+
} catch {
|
|
1780
|
+
return;
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
let removed = 0;
|
|
1784
|
+
const depFields = [
|
|
1785
|
+
"dependencies",
|
|
1786
|
+
"optionalDependencies",
|
|
1787
|
+
"devDependencies",
|
|
1788
|
+
"peerDependencies",
|
|
1789
|
+
];
|
|
1790
|
+
for (const field of depFields) {
|
|
1791
|
+
const deps = packageJson[field];
|
|
1792
|
+
if (!deps || typeof deps !== "object" || Array.isArray(deps)) continue;
|
|
1793
|
+
const depRecord = deps as Record<string, unknown>;
|
|
1794
|
+
for (const packageName of SERVERLESS_FUNCTION_PACKAGE_DENYLIST) {
|
|
1795
|
+
if (Object.prototype.hasOwnProperty.call(depRecord, packageName)) {
|
|
1796
|
+
delete depRecord[packageName];
|
|
1797
|
+
removed++;
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
if (Object.keys(depRecord).length === 0) {
|
|
1801
|
+
delete packageJson[field];
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
const nodeModulesDir = path.join(functionDir, "node_modules");
|
|
1806
|
+
for (const packageName of SERVERLESS_FUNCTION_PACKAGE_DENYLIST) {
|
|
1807
|
+
const packageDir = path.join(nodeModulesDir, ...packageName.split("/"));
|
|
1808
|
+
if (fs.existsSync(packageDir)) {
|
|
1809
|
+
fs.rmSync(packageDir, { recursive: true, force: true });
|
|
1810
|
+
removed++;
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
if (removed > 0) {
|
|
1815
|
+
fs.writeFileSync(
|
|
1816
|
+
packageJsonPath,
|
|
1817
|
+
`${JSON.stringify(packageJson, null, 2)}\n`,
|
|
1818
|
+
);
|
|
1819
|
+
console.log(
|
|
1820
|
+
`[deploy] Removed ${removed} desktop-only package reference(s) from ${path.relative(cwd, functionDir)}.`,
|
|
1821
|
+
);
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1751
1825
|
/**
|
|
1752
1826
|
* Create stub directories for dangling platform-specific optional dependency
|
|
1753
1827
|
* symlinks in the pnpm store.
|
|
@@ -2139,6 +2213,7 @@ export default bundle;
|
|
|
2139
2213
|
copyInstalledLibsqlNativePackages(nitro.options.output.serverDir);
|
|
2140
2214
|
copyInstalledResvgPackages(nitro.options.output.serverDir);
|
|
2141
2215
|
copyInstalledFfmpegStaticPackage(nitro.options.output.serverDir);
|
|
2216
|
+
sanitizeServerlessFunctionPackageManifest(nitro.options.output.serverDir);
|
|
2142
2217
|
}
|
|
2143
2218
|
|
|
2144
2219
|
// Durable background agent runs (default-OFF / opt-in; enable with a truthy
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
PrivateBlobDeleteResult,
|
|
3
|
+
PrivateBlobHandle,
|
|
4
|
+
PrivateBlobMetadata,
|
|
5
|
+
PrivateBlobProvider,
|
|
6
|
+
PrivateBlobPutInput,
|
|
7
|
+
PrivateBlobReadResult,
|
|
8
|
+
} from "./types.js";
|
|
9
|
+
export {
|
|
10
|
+
deletePrivateBlob,
|
|
11
|
+
getActivePrivateBlobProvider,
|
|
12
|
+
listPrivateBlobProviders,
|
|
13
|
+
putPrivateBlob,
|
|
14
|
+
readPrivateBlob,
|
|
15
|
+
registerPrivateBlobProvider,
|
|
16
|
+
setPrivateBlobPublicUploadFallbackEnabled,
|
|
17
|
+
unregisterPrivateBlobProvider,
|
|
18
|
+
} from "./registry.js";
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import { uploadFile } from "../file-upload/index.js";
|
|
4
|
+
import {
|
|
5
|
+
decryptSecretValue,
|
|
6
|
+
encryptSecretValue,
|
|
7
|
+
getSecretEncryptionKey,
|
|
8
|
+
} from "../secrets/crypto.js";
|
|
9
|
+
import type {
|
|
10
|
+
PrivateBlobDeleteResult,
|
|
11
|
+
PrivateBlobHandle,
|
|
12
|
+
PrivateBlobProvider,
|
|
13
|
+
PrivateBlobPutInput,
|
|
14
|
+
PrivateBlobReadResult,
|
|
15
|
+
} from "./types.js";
|
|
16
|
+
|
|
17
|
+
interface PrivateBlobGlobals {
|
|
18
|
+
__agentNativePrivateBlobProviders?: Map<string, PrivateBlobProvider>;
|
|
19
|
+
__agentNativePrivateBlobPublicUploadFallback?: { enabled: boolean };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface EncryptedPayload {
|
|
23
|
+
iv: string;
|
|
24
|
+
tag: string;
|
|
25
|
+
ciphertext: Uint8Array;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface EncryptionParams {
|
|
29
|
+
iv: string;
|
|
30
|
+
tag: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface PublicUploadDescriptor {
|
|
34
|
+
kind: "agent-native.private-blob.public-upload";
|
|
35
|
+
version: 1;
|
|
36
|
+
url: string;
|
|
37
|
+
uploadProvider: string;
|
|
38
|
+
uploadId?: string;
|
|
39
|
+
encryption: EncryptionParams;
|
|
40
|
+
mimeType?: string;
|
|
41
|
+
metadata?: PrivateBlobHandle["metadata"];
|
|
42
|
+
size: number;
|
|
43
|
+
createdAt: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const PUBLIC_UPLOAD_HANDLE_PREFIX = "public-upload:v1:";
|
|
47
|
+
const globals = globalThis as typeof globalThis & PrivateBlobGlobals;
|
|
48
|
+
const providers: Map<string, PrivateBlobProvider> =
|
|
49
|
+
(globals.__agentNativePrivateBlobProviders ??= new Map());
|
|
50
|
+
const publicUploadFallbackRef: { enabled: boolean } =
|
|
51
|
+
(globals.__agentNativePrivateBlobPublicUploadFallback ??= {
|
|
52
|
+
enabled: true,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
function toBytes(data: Uint8Array | Buffer): Uint8Array {
|
|
56
|
+
return data instanceof Uint8Array ? data : new Uint8Array(data);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function encryptBytes(data: Uint8Array): EncryptedPayload {
|
|
60
|
+
const iv = randomBytes(12);
|
|
61
|
+
const cipher = createCipheriv("aes-256-gcm", getSecretEncryptionKey(), iv);
|
|
62
|
+
const ciphertext = Buffer.concat([
|
|
63
|
+
cipher.update(Buffer.from(data)),
|
|
64
|
+
cipher.final(),
|
|
65
|
+
]);
|
|
66
|
+
return {
|
|
67
|
+
iv: iv.toString("base64url"),
|
|
68
|
+
ciphertext: new Uint8Array(ciphertext),
|
|
69
|
+
tag: cipher.getAuthTag().toString("base64url"),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function decryptBytes(
|
|
74
|
+
params: EncryptionParams,
|
|
75
|
+
ciphertext: Uint8Array,
|
|
76
|
+
): Uint8Array {
|
|
77
|
+
const decipher = createDecipheriv(
|
|
78
|
+
"aes-256-gcm",
|
|
79
|
+
getSecretEncryptionKey(),
|
|
80
|
+
Buffer.from(params.iv, "base64url"),
|
|
81
|
+
);
|
|
82
|
+
decipher.setAuthTag(Buffer.from(params.tag, "base64url"));
|
|
83
|
+
return new Uint8Array(
|
|
84
|
+
Buffer.concat([decipher.update(Buffer.from(ciphertext)), decipher.final()]),
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function encodePublicUploadDescriptor(
|
|
89
|
+
descriptor: PublicUploadDescriptor,
|
|
90
|
+
): string {
|
|
91
|
+
return `${PUBLIC_UPLOAD_HANDLE_PREFIX}${encryptSecretValue(
|
|
92
|
+
JSON.stringify(descriptor),
|
|
93
|
+
)}`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function decodePublicUploadDescriptor(id: string): PublicUploadDescriptor {
|
|
97
|
+
if (!id.startsWith(PUBLIC_UPLOAD_HANDLE_PREFIX)) {
|
|
98
|
+
throw new Error(
|
|
99
|
+
"Private blob handle is not a public-upload fallback handle",
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
const raw = decryptSecretValue(id.slice(PUBLIC_UPLOAD_HANDLE_PREFIX.length));
|
|
103
|
+
const descriptor = JSON.parse(raw) as PublicUploadDescriptor;
|
|
104
|
+
if (
|
|
105
|
+
descriptor?.kind !== "agent-native.private-blob.public-upload" ||
|
|
106
|
+
descriptor.version !== 1 ||
|
|
107
|
+
typeof descriptor.url !== "string"
|
|
108
|
+
) {
|
|
109
|
+
throw new Error("Private blob handle descriptor is invalid");
|
|
110
|
+
}
|
|
111
|
+
return descriptor;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function isPublicUploadFallbackHandle(handle: PrivateBlobHandle): boolean {
|
|
115
|
+
return handle.id.startsWith(PUBLIC_UPLOAD_HANDLE_PREFIX);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function putViaEncryptedPublicUpload(
|
|
119
|
+
input: PrivateBlobPutInput,
|
|
120
|
+
): Promise<PrivateBlobHandle | null> {
|
|
121
|
+
const bytes = toBytes(input.data);
|
|
122
|
+
const encrypted = encryptBytes(bytes);
|
|
123
|
+
const uploaded = await uploadFile({
|
|
124
|
+
data: Buffer.from(encrypted.ciphertext),
|
|
125
|
+
filename: input.filename ?? input.key ?? "private-blob.bin",
|
|
126
|
+
mimeType: "application/octet-stream",
|
|
127
|
+
ownerEmail: input.ownerEmail,
|
|
128
|
+
});
|
|
129
|
+
if (!uploaded) return null;
|
|
130
|
+
|
|
131
|
+
const descriptor: PublicUploadDescriptor = {
|
|
132
|
+
kind: "agent-native.private-blob.public-upload",
|
|
133
|
+
version: 1,
|
|
134
|
+
url: uploaded.url,
|
|
135
|
+
uploadProvider: uploaded.provider,
|
|
136
|
+
uploadId: uploaded.id,
|
|
137
|
+
encryption: { iv: encrypted.iv, tag: encrypted.tag },
|
|
138
|
+
mimeType: input.mimeType,
|
|
139
|
+
metadata: input.metadata,
|
|
140
|
+
size: bytes.byteLength,
|
|
141
|
+
createdAt: new Date().toISOString(),
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
id: encodePublicUploadDescriptor(descriptor),
|
|
146
|
+
provider: `public-upload:${uploaded.provider}`,
|
|
147
|
+
opaque: true,
|
|
148
|
+
encrypted: true,
|
|
149
|
+
mimeType: input.mimeType,
|
|
150
|
+
size: bytes.byteLength,
|
|
151
|
+
createdAt: descriptor.createdAt,
|
|
152
|
+
metadata: input.metadata,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function readViaEncryptedPublicUpload(
|
|
157
|
+
handle: PrivateBlobHandle,
|
|
158
|
+
): Promise<PrivateBlobReadResult> {
|
|
159
|
+
const descriptor = decodePublicUploadDescriptor(handle.id);
|
|
160
|
+
const response = await fetch(descriptor.url);
|
|
161
|
+
if (!response.ok) {
|
|
162
|
+
throw new Error(
|
|
163
|
+
`Private blob public-upload read failed (${response.status}): ${response.statusText}`,
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
// The uploaded ciphertext is intentionally opaque; the descriptor carries
|
|
167
|
+
// auth tag + IV separately so the backing public URL is useless by itself.
|
|
168
|
+
const ciphertext = new Uint8Array(await response.arrayBuffer());
|
|
169
|
+
return {
|
|
170
|
+
data: decryptBytes(descriptor.encryption, ciphertext),
|
|
171
|
+
mimeType: descriptor.mimeType,
|
|
172
|
+
metadata: descriptor.metadata,
|
|
173
|
+
handle,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function registerPrivateBlobProvider(
|
|
178
|
+
provider: PrivateBlobProvider,
|
|
179
|
+
): void {
|
|
180
|
+
providers.set(provider.id, provider);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function unregisterPrivateBlobProvider(id: string): void {
|
|
184
|
+
providers.delete(id);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function listPrivateBlobProviders(): PrivateBlobProvider[] {
|
|
188
|
+
return [...providers.values()];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function getActivePrivateBlobProvider(): PrivateBlobProvider | null {
|
|
192
|
+
for (const provider of providers.values()) {
|
|
193
|
+
if (provider.isConfigured()) return provider;
|
|
194
|
+
}
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function setPrivateBlobPublicUploadFallbackEnabled(
|
|
199
|
+
enabled: boolean,
|
|
200
|
+
): void {
|
|
201
|
+
publicUploadFallbackRef.enabled = enabled;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export async function putPrivateBlob(
|
|
205
|
+
input: PrivateBlobPutInput,
|
|
206
|
+
): Promise<PrivateBlobHandle | null> {
|
|
207
|
+
const provider = getActivePrivateBlobProvider();
|
|
208
|
+
if (provider) return provider.put(input);
|
|
209
|
+
if (!publicUploadFallbackRef.enabled) return null;
|
|
210
|
+
if (process.env.AGENT_NATIVE_PRIVATE_BLOB_PUBLIC_UPLOAD_FALLBACK === "0") {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
return putViaEncryptedPublicUpload(input);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export async function readPrivateBlob(
|
|
217
|
+
handle: PrivateBlobHandle,
|
|
218
|
+
): Promise<PrivateBlobReadResult> {
|
|
219
|
+
const provider = providers.get(handle.provider);
|
|
220
|
+
if (provider) return provider.read(handle);
|
|
221
|
+
if (isPublicUploadFallbackHandle(handle)) {
|
|
222
|
+
return readViaEncryptedPublicUpload(handle);
|
|
223
|
+
}
|
|
224
|
+
throw new Error(`No private blob provider registered for ${handle.provider}`);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export async function deletePrivateBlob(
|
|
228
|
+
handle: PrivateBlobHandle,
|
|
229
|
+
): Promise<PrivateBlobDeleteResult> {
|
|
230
|
+
const provider = providers.get(handle.provider);
|
|
231
|
+
if (provider) return provider.delete(handle);
|
|
232
|
+
if (isPublicUploadFallbackHandle(handle)) {
|
|
233
|
+
return {
|
|
234
|
+
deleted: false,
|
|
235
|
+
provider: handle.provider,
|
|
236
|
+
reason:
|
|
237
|
+
"delete is not supported by the encrypted public-upload fallback provider",
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
throw new Error(`No private blob provider registered for ${handle.provider}`);
|
|
241
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export interface PrivateBlobMetadata {
|
|
2
|
+
[key: string]: string | number | boolean | null | undefined;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export interface PrivateBlobPutInput {
|
|
6
|
+
/** Private blob contents. */
|
|
7
|
+
data: Uint8Array | Buffer;
|
|
8
|
+
/** Optional caller-owned key for providers that support deterministic paths. */
|
|
9
|
+
key?: string;
|
|
10
|
+
/** Original filename or logical blob name. */
|
|
11
|
+
filename?: string;
|
|
12
|
+
/** MIME type, e.g. "application/json". */
|
|
13
|
+
mimeType?: string;
|
|
14
|
+
/** Optional owner email for provider-specific scoping. */
|
|
15
|
+
ownerEmail?: string;
|
|
16
|
+
/** Low-cardinality metadata safe to store beside the blob. */
|
|
17
|
+
metadata?: PrivateBlobMetadata;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface PrivateBlobHandle {
|
|
21
|
+
/** Opaque provider handle. Consumers must not interpret this as a URL. */
|
|
22
|
+
id: string;
|
|
23
|
+
/** Provider that owns the handle. */
|
|
24
|
+
provider: string;
|
|
25
|
+
/** True for handles that intentionally do not expose fetchable URLs. */
|
|
26
|
+
opaque: true;
|
|
27
|
+
/** True when bytes are encrypted before reaching the backing store. */
|
|
28
|
+
encrypted: boolean;
|
|
29
|
+
mimeType?: string;
|
|
30
|
+
size?: number;
|
|
31
|
+
createdAt?: string;
|
|
32
|
+
metadata?: PrivateBlobMetadata;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface PrivateBlobReadResult {
|
|
36
|
+
data: Uint8Array;
|
|
37
|
+
mimeType?: string;
|
|
38
|
+
metadata?: PrivateBlobMetadata;
|
|
39
|
+
handle: PrivateBlobHandle;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface PrivateBlobDeleteResult {
|
|
43
|
+
deleted: boolean;
|
|
44
|
+
provider: string;
|
|
45
|
+
reason?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface PrivateBlobProvider {
|
|
49
|
+
id: string;
|
|
50
|
+
name: string;
|
|
51
|
+
isConfigured: () => boolean;
|
|
52
|
+
put: (input: PrivateBlobPutInput) => Promise<PrivateBlobHandle>;
|
|
53
|
+
read: (handle: PrivateBlobHandle) => Promise<PrivateBlobReadResult>;
|
|
54
|
+
delete: (handle: PrivateBlobHandle) => Promise<PrivateBlobDeleteResult>;
|
|
55
|
+
}
|
|
@@ -8,29 +8,57 @@ export { parseArgs, camelCaseArgs } from "./parse-args.js";
|
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Load .env files. In an enterprise workspace (detected via
|
|
11
|
-
* `agent-native.workspaceCore` in a parent package.json) this also loads
|
|
12
|
-
*
|
|
13
|
-
* flow to every app without duplication.
|
|
14
|
-
*
|
|
11
|
+
* `agent-native.workspaceCore` in a parent package.json) this also loads the
|
|
12
|
+
* workspace root's .env as a fallback, so shared keys like ANTHROPIC_API_KEY
|
|
13
|
+
* flow to every app without duplication. Shell values win, then app
|
|
14
|
+
* .env.local, app .env, workspace .env.local, and workspace .env.
|
|
15
15
|
*/
|
|
16
16
|
export function loadEnv(envPath?: string): void {
|
|
17
17
|
const appEnv = envPath ?? path.join(process.cwd(), ".env");
|
|
18
|
+
const shellKeys = new Set(Object.keys(process.env));
|
|
18
19
|
// App-level .env first. Dotenv won't clobber already-set process.env, so
|
|
19
20
|
// values that are already present (e.g. set by the shell) still win.
|
|
20
21
|
// `quiet: true` suppresses the dotenv tip line on every load (v17+).
|
|
21
|
-
|
|
22
|
+
loadEnvFile(appEnv);
|
|
23
|
+
loadEnvLocalOverrides(localEnvPathFor(appEnv), shellKeys);
|
|
22
24
|
|
|
23
25
|
// Then workspace root, if any — but only fill in keys the app didn't
|
|
24
26
|
// define. Setting `override: false` is dotenv's default.
|
|
25
27
|
const workspaceRoot = findWorkspaceRoot(path.dirname(appEnv));
|
|
26
28
|
if (workspaceRoot) {
|
|
29
|
+
const beforeWorkspaceKeys = new Set(Object.keys(process.env));
|
|
27
30
|
const wsEnv = path.join(workspaceRoot, ".env");
|
|
28
31
|
if (fs.existsSync(wsEnv) && wsEnv !== appEnv) {
|
|
29
|
-
|
|
32
|
+
loadEnvFile(wsEnv);
|
|
33
|
+
loadEnvLocalOverrides(localEnvPathFor(wsEnv), beforeWorkspaceKeys);
|
|
30
34
|
}
|
|
31
35
|
}
|
|
32
36
|
}
|
|
33
37
|
|
|
38
|
+
function loadEnvFile(filePath: string): void {
|
|
39
|
+
if (fs.existsSync(filePath)) {
|
|
40
|
+
dotenv.config({ path: filePath, quiet: true });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function localEnvPathFor(envPath: string): string {
|
|
45
|
+
return path.basename(envPath) === ".env"
|
|
46
|
+
? path.join(path.dirname(envPath), ".env.local")
|
|
47
|
+
: `${envPath}.local`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function loadEnvLocalOverrides(
|
|
51
|
+
filePath: string,
|
|
52
|
+
protectedKeys: Set<string>,
|
|
53
|
+
): void {
|
|
54
|
+
if (!fs.existsSync(filePath)) return;
|
|
55
|
+
const values = dotenv.parse(fs.readFileSync(filePath));
|
|
56
|
+
for (const [key, value] of Object.entries(values)) {
|
|
57
|
+
if (protectedKeys.has(key)) continue;
|
|
58
|
+
process.env[key] = value;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
34
62
|
/**
|
|
35
63
|
* Locate the nearest enterprise workspace root above `startDir`, identified
|
|
36
64
|
* by the `agent-native.workspaceCore` field in its package.json.
|
|
@@ -6660,6 +6660,11 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
|
|
|
6660
6660
|
refPath?: string;
|
|
6661
6661
|
refId?: string;
|
|
6662
6662
|
section?: string;
|
|
6663
|
+
slotKey?: string;
|
|
6664
|
+
slotLabel?: string;
|
|
6665
|
+
metadata?: Record<string, unknown>;
|
|
6666
|
+
clearsSlots?: string[];
|
|
6667
|
+
relatedReferences?: unknown[];
|
|
6663
6668
|
}
|
|
6664
6669
|
|
|
6665
6670
|
const matchesQuery = (item: MentionItemResponse) =>
|
|
@@ -6796,6 +6801,11 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
|
|
|
6796
6801
|
refPath: item.refPath,
|
|
6797
6802
|
refId: item.refId,
|
|
6798
6803
|
section: provider.label,
|
|
6804
|
+
slotKey: item.slotKey,
|
|
6805
|
+
slotLabel: item.slotLabel,
|
|
6806
|
+
metadata: item.metadata,
|
|
6807
|
+
clearsSlots: item.clearsSlots,
|
|
6808
|
+
relatedReferences: item.relatedReferences,
|
|
6799
6809
|
})),
|
|
6800
6810
|
);
|
|
6801
6811
|
} catch (e) {
|
|
@@ -16,6 +16,8 @@ export interface EmailAttachment {
|
|
|
16
16
|
filename: string;
|
|
17
17
|
content: string | Buffer;
|
|
18
18
|
contentType?: string;
|
|
19
|
+
contentId?: string;
|
|
20
|
+
disposition?: "attachment" | "inline";
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
export interface SendEmailArgs {
|
|
@@ -76,6 +78,7 @@ export async function sendEmail(args: SendEmailArgs): Promise<void> {
|
|
|
76
78
|
? a.content
|
|
77
79
|
: a.content.toString("base64"),
|
|
78
80
|
content_type: a.contentType,
|
|
81
|
+
content_id: a.contentId,
|
|
79
82
|
}));
|
|
80
83
|
}
|
|
81
84
|
const headers: Record<string, string> = {};
|
|
@@ -129,6 +132,8 @@ export async function sendEmail(args: SendEmailArgs): Promise<void> {
|
|
|
129
132
|
? Buffer.from(a.content).toString("base64")
|
|
130
133
|
: a.content.toString("base64"),
|
|
131
134
|
type: a.contentType,
|
|
135
|
+
disposition: a.disposition ?? (a.contentId ? "inline" : undefined),
|
|
136
|
+
content_id: a.contentId,
|
|
132
137
|
}));
|
|
133
138
|
}
|
|
134
139
|
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
opening animates in as reliably as closing animates out. */
|
|
47
47
|
.agent-sidebar-panel[data-agent-sidebar-animation="desktop"] {
|
|
48
48
|
width: 0;
|
|
49
|
+
overflow: clip;
|
|
49
50
|
will-change: width;
|
|
50
51
|
transition: width 260ms cubic-bezier(0.32, 0.72, 0, 1);
|
|
51
52
|
}
|
|
@@ -54,6 +55,22 @@
|
|
|
54
55
|
width: var(--agent-sidebar-width);
|
|
55
56
|
}
|
|
56
57
|
|
|
58
|
+
.agent-sidebar-panel[data-agent-sidebar-animation="desktop"]
|
|
59
|
+
> .agent-sidebar-panel-inner {
|
|
60
|
+
width: var(--agent-sidebar-width);
|
|
61
|
+
min-width: var(--agent-sidebar-width);
|
|
62
|
+
max-width: var(--agent-sidebar-width);
|
|
63
|
+
height: 100%;
|
|
64
|
+
transform: var(--agent-sidebar-inner-closed-transform, translateX(100%));
|
|
65
|
+
will-change: transform;
|
|
66
|
+
transition: transform 260ms cubic-bezier(0.32, 0.72, 0, 1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.agent-sidebar-panel[data-agent-sidebar-animation="desktop"][data-agent-sidebar-state="open"]
|
|
70
|
+
> .agent-sidebar-panel-inner {
|
|
71
|
+
transform: translateX(0);
|
|
72
|
+
}
|
|
73
|
+
|
|
57
74
|
.agent-sidebar-panel[data-agent-sidebar-animation="mobile"] {
|
|
58
75
|
transform: var(
|
|
59
76
|
--agent-sidebar-closed-transform,
|
|
@@ -81,6 +98,11 @@
|
|
|
81
98
|
width: 0;
|
|
82
99
|
}
|
|
83
100
|
|
|
101
|
+
.agent-sidebar-panel[data-agent-sidebar-animation="desktop"][data-agent-sidebar-state="open"]
|
|
102
|
+
> .agent-sidebar-panel-inner {
|
|
103
|
+
transform: var(--agent-sidebar-inner-closed-transform, translateX(100%));
|
|
104
|
+
}
|
|
105
|
+
|
|
84
106
|
.agent-sidebar-panel[data-agent-sidebar-animation="mobile"][data-agent-sidebar-state="open"] {
|
|
85
107
|
transform: var(
|
|
86
108
|
--agent-sidebar-closed-transform,
|
|
@@ -95,6 +117,8 @@
|
|
|
95
117
|
|
|
96
118
|
@media (prefers-reduced-motion: reduce) {
|
|
97
119
|
.agent-sidebar-panel[data-agent-sidebar-animation],
|
|
120
|
+
.agent-sidebar-panel[data-agent-sidebar-animation="desktop"]
|
|
121
|
+
> .agent-sidebar-panel-inner,
|
|
98
122
|
.agent-sidebar-backdrop[data-agent-sidebar-animation] {
|
|
99
123
|
transition: none;
|
|
100
124
|
}
|
|
@@ -257,7 +257,7 @@ For a **first-party analytics** dashboard, prefer `compose-dashboard` over hand-
|
|
|
257
257
|
- Each metric accepts an optional per-metric `window` of `'30d' | '90d' | 'all'` (only affects windowed virality/time metrics) and `title` / `chartType` / `width` overrides.
|
|
258
258
|
- Returns `{ dashboardId, panelCount, createdMetrics, unknownMetrics, invalidMetrics, skippedExistingIds }` — report `panelCount` as proof-of-done.
|
|
259
259
|
|
|
260
|
-
Available metric keys: `total-signups`, `signups-over-time`, `signups-by-template`, `sessions-by-app`, `sessions-over-time`, `signed-in-vs-anon`, `total-template-clicks`, `total-demo-clicks`, `total-cli-copies`, `template-interest-over-time`, `clicks-by-template`, `demo-clicks-by-template`, `cli-copies-by-template`, `cli-copies-over-time`, `pageviews-over-time`, `top-referrer-domains`, `referred-signups-30d`, `viral-signup-share-30d`, `clip-share-signups-30d`, `signups-by-referral-source`, `referred-signups-over-time`, `top-referrers`, `share-funnel-30d`, `viral-participation-rate-90d`, `viral-coefficient-90d`, `activated-referrers-90d`.
|
|
260
|
+
Available metric keys: `total-signups`, `signups-over-time`, `signups-by-template`, `sessions-by-app`, `sessions-over-time`, `replay-sessions`, `replay-chunks-over-time`, `recent-replay-sessions`, `signed-in-vs-anon`, `total-template-clicks`, `total-demo-clicks`, `total-cli-copies`, `template-interest-over-time`, `clicks-by-template`, `demo-clicks-by-template`, `cli-copies-by-template`, `cli-copies-over-time`, `pageviews-over-time`, `top-referrer-domains`, `referred-signups-30d`, `viral-signup-share-30d`, `clip-share-signups-30d`, `signups-by-referral-source`, `referred-signups-over-time`, `top-referrers`, `share-funnel-30d`, `viral-participation-rate-90d`, `viral-coefficient-90d`, `activated-referrers-90d`.
|
|
261
261
|
|
|
262
262
|
```bash
|
|
263
263
|
# Build a large first-party dashboard in ONE call (server generates the panels)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: session-replay
|
|
3
|
+
description: Inspect, troubleshoot, and extend Analytics session replay recordings.
|
|
4
|
+
scope: dev
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Session Replay
|
|
8
|
+
|
|
9
|
+
Use this skill when working on `/sessions`, replay ingest, replay storage, or
|
|
10
|
+
agent answers about browser recordings in the Analytics template.
|
|
11
|
+
|
|
12
|
+
## Source Of Truth
|
|
13
|
+
|
|
14
|
+
- Replay ingest writes `session_recordings` and `session_replay_chunks`.
|
|
15
|
+
- The UI and agent must use `list-session-recordings`,
|
|
16
|
+
`get-session-replay-summary`, and `get-session-replay-events`.
|
|
17
|
+
- `/sessions/:recordingId` is keyed by `session_recordings.id`, not
|
|
18
|
+
`analytics_events.session_id`.
|
|
19
|
+
- Do not add actions that synthesize "sessions" from `analytics_events`.
|
|
20
|
+
Events can be linked beside a recording through `session_id`, but they are not
|
|
21
|
+
playable replay rows by themselves.
|
|
22
|
+
|
|
23
|
+
## Storage And Access
|
|
24
|
+
|
|
25
|
+
- Never expose object-storage URLs or raw `session_replay_chunks` rows to the
|
|
26
|
+
browser or agent.
|
|
27
|
+
- Playback bytes must go through scoped server helpers that check
|
|
28
|
+
`session-recording` access before reading private blob refs.
|
|
29
|
+
- SQL inline chunks are a local/dev fallback only; production should use
|
|
30
|
+
private or encrypted blob storage.
|
|
31
|
+
|
|
32
|
+
## Capture Defaults
|
|
33
|
+
|
|
34
|
+
- Replay is off unless enabled by config/env.
|
|
35
|
+
- Consumers can enable replay directly with
|
|
36
|
+
`configureTracking({ key, endpoint, sessionReplay: { enabled: true } })`.
|
|
37
|
+
- Agent Native templates already call `configureTracking()` in their roots;
|
|
38
|
+
hosted template deployments can usually enable replay with Vite/Netlify env
|
|
39
|
+
vars on the recorded site.
|
|
40
|
+
- Inputs are masked by default. Page text is visible unless marked with
|
|
41
|
+
`.an-mask` or `data-an-mask`.
|
|
42
|
+
- Use `.an-block`, `.an-ignore`, `data-an-block`, or `data-an-ignore` for
|
|
43
|
+
sensitive zones that should not be captured.
|