@agent-native/core 0.79.20 → 0.79.22

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.
Files changed (141) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +16 -0
  3. package/corpus/core/docs/content/deployment.md +133 -0
  4. package/corpus/core/docs/content/faq.md +1 -1
  5. package/corpus/core/docs/content/multi-app-workspace.md +2 -0
  6. package/corpus/core/docs/content/tracking.md +3 -3
  7. package/corpus/core/package.json +1 -1
  8. package/corpus/core/src/agent/engine/openai-compatible-endpoint.ts +25 -0
  9. package/corpus/core/src/agent/engine/provider-env-vars.ts +2 -0
  10. package/corpus/core/src/agent/engine/registry.ts +59 -11
  11. package/corpus/core/src/client/FeedbackButton.tsx +11 -1
  12. package/corpus/core/src/client/agent-engine-key.ts +57 -10
  13. package/corpus/core/src/client/analytics.ts +78 -27
  14. package/corpus/core/src/client/blocks/library/HighlightedCode.tsx +1 -1
  15. package/corpus/core/src/client/index.ts +2 -0
  16. package/corpus/core/src/client/session-replay.ts +159 -17
  17. package/corpus/core/src/client/settings/SettingsPanel.tsx +119 -4
  18. package/corpus/core/src/scripts/agent-engines/manage-agent-engine.ts +5 -0
  19. package/corpus/core/src/scripts/agent-engines/set-agent-engine.ts +8 -3
  20. package/corpus/core/src/scripts/agent-engines/test-agent-engine.ts +54 -6
  21. package/corpus/core/src/server/agent-engine-api-key-route.ts +83 -12
  22. package/corpus/core/src/server/core-routes-plugin.ts +26 -2
  23. package/corpus/core/src/styles/blocks.css +23 -7
  24. package/corpus/templates/analytics/actions/list-session-recordings.ts +1 -1
  25. package/corpus/templates/analytics/app/components/layout/MobileNav.tsx +5 -10
  26. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +22 -4
  27. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +64 -50
  28. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/dashboard-layout.ts +38 -0
  29. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/dashboard-sync.ts +89 -0
  30. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +138 -59
  31. package/corpus/templates/analytics/changelog/2026-06-27-dashboard-charts-drag-smoothly-when-rearranging-panels.md +6 -0
  32. package/corpus/templates/analytics/changelog/2026-06-27-dashboard-email-reports-still-send-when-the-live-screenshot-.md +6 -0
  33. package/corpus/templates/analytics/changelog/2026-06-27-fixed-dashboard-edits-so-charts-no-longer-briefly-snap-back-.md +6 -0
  34. package/corpus/templates/analytics/changelog/2026-06-27-mobile-navigation-chrome-and-motion.md +6 -0
  35. package/corpus/templates/analytics/changelog/2026-06-27-session-replay-lists-now-show-only-signed-in-recordings-with.md +6 -0
  36. package/corpus/templates/analytics/changelog/2026-06-27-sidebar-loading-placeholders-are-visible-in-light-mode.md +6 -0
  37. package/corpus/templates/analytics/netlify.toml +4 -0
  38. package/corpus/templates/analytics/scripts/seed-session-replay.ts +1 -0
  39. package/corpus/templates/analytics/server/jobs/dashboard-report.ts +1 -0
  40. package/corpus/templates/analytics/server/lib/dashboard-report.ts +120 -29
  41. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +1 -1
  42. package/corpus/templates/analytics/server/lib/session-replay.ts +158 -46
  43. package/corpus/templates/assets/app/components/layout/Layout.tsx +1 -1
  44. package/corpus/templates/assets/changelog/2026-06-27-mobile-navigation-chrome-and-motion.md +6 -0
  45. package/corpus/templates/clips/app/components/capture-install-options.tsx +0 -9
  46. package/corpus/templates/clips/app/components/recorder/pre-record-panel.tsx +20 -4
  47. package/corpus/templates/clips/app/hooks/use-desktop-promo.ts +6 -2
  48. package/corpus/templates/clips/changelog/2026-06-27-clips-no-longer-shows-desktop-app-prompts-on-mobile-screens.md +6 -0
  49. package/corpus/templates/clips/changelog/2026-06-27-desktop-storage-setup-flow.md +6 -0
  50. package/corpus/templates/clips/changelog/2026-06-27-recorder-download-options-now-show-the-chrome-extension-and-.md +6 -0
  51. package/corpus/templates/clips/changelog/2026-06-27-recorder-setup-mobile-capture-options.md +6 -0
  52. package/corpus/templates/clips/chrome-extension/package.json +1 -1
  53. package/corpus/templates/clips/chrome-extension/src/sentry.ts +17 -1
  54. package/corpus/templates/clips/desktop/package.json +1 -0
  55. package/corpus/templates/clips/desktop/scripts/check-tauri-prereqs.mjs +45 -0
  56. package/corpus/templates/clips/desktop/src/app.tsx +32 -3
  57. package/corpus/templates/clips/desktop/src/lib/sentry.ts +71 -0
  58. package/corpus/templates/clips/desktop/src/main.tsx +2 -0
  59. package/corpus/templates/clips/desktop/src/styles.css +67 -0
  60. package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +2 -1
  61. package/corpus/templates/clips/desktop/src-tauri/build.rs +29 -0
  62. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +3 -0
  63. package/corpus/templates/clips/desktop/src-tauri/src/logfile.rs +1 -0
  64. package/corpus/templates/clips/desktop/src-tauri/src/sentry_report.rs +142 -0
  65. package/corpus/templates/clips/desktop/vite.config.ts +66 -0
  66. package/corpus/templates/design/app/components/layout/Layout.tsx +1 -1
  67. package/corpus/templates/design/changelog/2026-06-27-mobile-navigation-chrome-and-motion.md +6 -0
  68. package/corpus/templates/forms/actions/export-responses.ts +3 -2
  69. package/corpus/templates/forms/actions/list-responses.ts +2 -1
  70. package/corpus/templates/forms/actions/response-insights.ts +13 -4
  71. package/corpus/templates/forms/changelog/2026-06-27-anonymous-feedback-no-longer-shows-internal-agent-native-ano.md +6 -0
  72. package/corpus/templates/forms/server/handlers/submissions.ts +5 -8
  73. package/corpus/templates/forms/server/lib/integrations.ts +9 -6
  74. package/corpus/templates/forms/shared/submitter-email.ts +20 -0
  75. package/corpus/templates/forms/shared/types.ts +1 -1
  76. package/corpus/templates/plan/app/components/plan/wireframe/html-artboard.css +49 -10
  77. package/corpus/templates/slides/app/components/layout/Layout.tsx +1 -1
  78. package/corpus/templates/slides/changelog/2026-06-27-mobile-navigation-chrome-and-motion.md +6 -0
  79. package/dist/agent/engine/openai-compatible-endpoint.d.ts +3 -0
  80. package/dist/agent/engine/openai-compatible-endpoint.d.ts.map +1 -0
  81. package/dist/agent/engine/openai-compatible-endpoint.js +23 -0
  82. package/dist/agent/engine/openai-compatible-endpoint.js.map +1 -0
  83. package/dist/agent/engine/provider-env-vars.d.ts +1 -0
  84. package/dist/agent/engine/provider-env-vars.d.ts.map +1 -1
  85. package/dist/agent/engine/provider-env-vars.js +1 -0
  86. package/dist/agent/engine/provider-env-vars.js.map +1 -1
  87. package/dist/agent/engine/registry.d.ts.map +1 -1
  88. package/dist/agent/engine/registry.js +39 -12
  89. package/dist/agent/engine/registry.js.map +1 -1
  90. package/dist/client/FeedbackButton.d.ts.map +1 -1
  91. package/dist/client/FeedbackButton.js +6 -1
  92. package/dist/client/FeedbackButton.js.map +1 -1
  93. package/dist/client/agent-engine-key.d.ts +14 -0
  94. package/dist/client/agent-engine-key.d.ts.map +1 -1
  95. package/dist/client/agent-engine-key.js +35 -10
  96. package/dist/client/agent-engine-key.js.map +1 -1
  97. package/dist/client/analytics.d.ts.map +1 -1
  98. package/dist/client/analytics.js +71 -24
  99. package/dist/client/analytics.js.map +1 -1
  100. package/dist/client/blocks/library/HighlightedCode.d.ts +1 -1
  101. package/dist/client/blocks/library/HighlightedCode.js +1 -1
  102. package/dist/client/blocks/library/HighlightedCode.js.map +1 -1
  103. package/dist/client/index.d.ts +1 -1
  104. package/dist/client/index.d.ts.map +1 -1
  105. package/dist/client/index.js +1 -1
  106. package/dist/client/index.js.map +1 -1
  107. package/dist/client/session-replay.d.ts +1 -1
  108. package/dist/client/session-replay.d.ts.map +1 -1
  109. package/dist/client/session-replay.js +115 -17
  110. package/dist/client/session-replay.js.map +1 -1
  111. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  112. package/dist/client/settings/SettingsPanel.js +42 -6
  113. package/dist/client/settings/SettingsPanel.js.map +1 -1
  114. package/dist/collab/routes.d.ts +2 -2
  115. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  116. package/dist/notifications/routes.d.ts +1 -1
  117. package/dist/observability/routes.d.ts +7 -7
  118. package/dist/progress/routes.d.ts +1 -1
  119. package/dist/resources/handlers.d.ts +1 -1
  120. package/dist/scripts/agent-engines/manage-agent-engine.d.ts.map +1 -1
  121. package/dist/scripts/agent-engines/manage-agent-engine.js +4 -0
  122. package/dist/scripts/agent-engines/manage-agent-engine.js.map +1 -1
  123. package/dist/scripts/agent-engines/set-agent-engine.d.ts.map +1 -1
  124. package/dist/scripts/agent-engines/set-agent-engine.js +5 -4
  125. package/dist/scripts/agent-engines/set-agent-engine.js.map +1 -1
  126. package/dist/scripts/agent-engines/test-agent-engine.d.ts.map +1 -1
  127. package/dist/scripts/agent-engines/test-agent-engine.js +37 -5
  128. package/dist/scripts/agent-engines/test-agent-engine.js.map +1 -1
  129. package/dist/server/agent-engine-api-key-route.d.ts +5 -5
  130. package/dist/server/agent-engine-api-key-route.d.ts.map +1 -1
  131. package/dist/server/agent-engine-api-key-route.js +71 -11
  132. package/dist/server/agent-engine-api-key-route.js.map +1 -1
  133. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  134. package/dist/server/core-routes-plugin.js +20 -3
  135. package/dist/server/core-routes-plugin.js.map +1 -1
  136. package/dist/styles/blocks.css +23 -7
  137. package/docs/content/deployment.md +133 -0
  138. package/docs/content/faq.md +1 -1
  139. package/docs/content/multi-app-workspace.md +2 -0
  140. package/docs/content/tracking.md +3 -3
  141. package/package.json +1 -1
@@ -0,0 +1,142 @@
1
+ use std::sync::OnceLock;
2
+ use std::time::Duration;
3
+
4
+ static SENTRY_GUARD: OnceLock<&'static sentry::ClientInitGuard> = OnceLock::new();
5
+
6
+ const DIRECT_DSN_ENV: [(&str, Option<&str>); 7] = [
7
+ (
8
+ "CLIPS_DESKTOP_SENTRY_DSN",
9
+ option_env!("CLIPS_DESKTOP_SENTRY_DSN"),
10
+ ),
11
+ ("TAURI_SENTRY_DSN", option_env!("TAURI_SENTRY_DSN")),
12
+ ("SENTRY_DESKTOP_DSN", option_env!("SENTRY_DESKTOP_DSN")),
13
+ ("SENTRY_CLIENT_DSN", option_env!("SENTRY_CLIENT_DSN")),
14
+ (
15
+ "VITE_SENTRY_CLIENT_DSN",
16
+ option_env!("VITE_SENTRY_CLIENT_DSN"),
17
+ ),
18
+ ("VITE_SENTRY_DSN", option_env!("VITE_SENTRY_DSN")),
19
+ ("SENTRY_DSN", option_env!("SENTRY_DSN")),
20
+ ];
21
+
22
+ const KEY_ENV: [(&str, Option<&str>); 3] = [
23
+ (
24
+ "CLIPS_DESKTOP_SENTRY_CLIENT_KEY",
25
+ option_env!("CLIPS_DESKTOP_SENTRY_CLIENT_KEY"),
26
+ ),
27
+ ("SENTRY_CLIENT_KEY", option_env!("SENTRY_CLIENT_KEY")),
28
+ (
29
+ "VITE_SENTRY_CLIENT_KEY",
30
+ option_env!("VITE_SENTRY_CLIENT_KEY"),
31
+ ),
32
+ ];
33
+
34
+ const PROJECT_ENV: [(&str, Option<&str>); 3] = [
35
+ (
36
+ "CLIPS_DESKTOP_SENTRY_PROJECT_ID",
37
+ option_env!("CLIPS_DESKTOP_SENTRY_PROJECT_ID"),
38
+ ),
39
+ ("SENTRY_PROJECT_ID", option_env!("SENTRY_PROJECT_ID")),
40
+ (
41
+ "VITE_SENTRY_PROJECT_ID",
42
+ option_env!("VITE_SENTRY_PROJECT_ID"),
43
+ ),
44
+ ];
45
+
46
+ const HOST_ENV: [(&str, Option<&str>); 3] = [
47
+ (
48
+ "CLIPS_DESKTOP_SENTRY_INGEST_HOST",
49
+ option_env!("CLIPS_DESKTOP_SENTRY_INGEST_HOST"),
50
+ ),
51
+ ("SENTRY_INGEST_HOST", option_env!("SENTRY_INGEST_HOST")),
52
+ (
53
+ "VITE_SENTRY_INGEST_HOST",
54
+ option_env!("VITE_SENTRY_INGEST_HOST"),
55
+ ),
56
+ ];
57
+
58
+ const ENVIRONMENT_ENV: [(&str, Option<&str>); 5] = [
59
+ (
60
+ "CLIPS_DESKTOP_SENTRY_ENVIRONMENT",
61
+ option_env!("CLIPS_DESKTOP_SENTRY_ENVIRONMENT"),
62
+ ),
63
+ ("SENTRY_ENVIRONMENT", option_env!("SENTRY_ENVIRONMENT")),
64
+ ("NETLIFY_CONTEXT", option_env!("NETLIFY_CONTEXT")),
65
+ ("VERCEL_ENV", option_env!("VERCEL_ENV")),
66
+ ("NODE_ENV", option_env!("NODE_ENV")),
67
+ ];
68
+
69
+ pub fn init() {
70
+ if SENTRY_GUARD.get().is_some() {
71
+ return;
72
+ }
73
+
74
+ let Some(dsn) = resolve_sentry_dsn() else {
75
+ return;
76
+ };
77
+ let Ok(dsn) = dsn.parse() else {
78
+ eprintln!("[clips-tray] ignoring invalid Sentry DSN");
79
+ return;
80
+ };
81
+
82
+ let guard = sentry::init(sentry::ClientOptions {
83
+ dsn: Some(dsn),
84
+ environment: Some(resolve_sentry_environment().into()),
85
+ release: Some(format!("clips-desktop@{}", env!("CARGO_PKG_VERSION")).into()),
86
+ send_default_pii: false,
87
+ before_send: Some(std::sync::Arc::new(|mut event| {
88
+ event.user = None;
89
+ Some(event)
90
+ })),
91
+ ..Default::default()
92
+ });
93
+
94
+ if guard.is_enabled() {
95
+ sentry::configure_scope(|scope| {
96
+ scope.set_tag("app", "agent-native-clips");
97
+ scope.set_tag("template", "clips");
98
+ scope.set_tag("runtime", "tauri-native");
99
+ });
100
+ }
101
+
102
+ let _ = SENTRY_GUARD.set(Box::leak(Box::new(guard)));
103
+ }
104
+
105
+ pub fn flush(timeout: Duration) {
106
+ if let Some(guard) = SENTRY_GUARD.get() {
107
+ guard.close(Some(timeout));
108
+ }
109
+ }
110
+
111
+ fn resolve_sentry_dsn() -> Option<String> {
112
+ if let Some(dsn) = first_env(&DIRECT_DSN_ENV) {
113
+ return Some(dsn);
114
+ }
115
+
116
+ let key = first_env(&KEY_ENV)?;
117
+ let project = first_env(&PROJECT_ENV)?;
118
+ let host = first_env(&HOST_ENV)?;
119
+ Some(format!("https://{key}@{host}/{project}"))
120
+ }
121
+
122
+ fn resolve_sentry_environment() -> String {
123
+ first_env(&ENVIRONMENT_ENV).unwrap_or_else(|| "production".to_string())
124
+ }
125
+
126
+ fn first_env(values: &[(&str, Option<&str>)]) -> Option<String> {
127
+ for (name, embedded) in values {
128
+ if let Ok(value) = std::env::var(name) {
129
+ let trimmed = value.trim();
130
+ if !trimmed.is_empty() {
131
+ return Some(trimmed.to_string());
132
+ }
133
+ }
134
+ if let Some(value) = embedded {
135
+ let trimmed = value.trim();
136
+ if !trimmed.is_empty() {
137
+ return Some(trimmed.to_string());
138
+ }
139
+ }
140
+ }
141
+ None
142
+ }
@@ -1,6 +1,65 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { dirname, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
1
5
  import react from "@vitejs/plugin-react";
2
6
  import { defineConfig } from "vite";
3
7
 
8
+ const root = dirname(fileURLToPath(import.meta.url));
9
+ const packageJson = JSON.parse(
10
+ readFileSync(resolve(root, "package.json"), "utf8"),
11
+ ) as { version?: string };
12
+
13
+ function firstNonEmpty(...values: Array<string | undefined>): string {
14
+ for (const value of values) {
15
+ const trimmed = value?.trim();
16
+ if (trimmed) return trimmed;
17
+ }
18
+ return "";
19
+ }
20
+
21
+ function resolveSentryDsn(): string {
22
+ const direct = firstNonEmpty(
23
+ process.env.CLIPS_DESKTOP_SENTRY_DSN,
24
+ process.env.TAURI_SENTRY_DSN,
25
+ process.env.SENTRY_DESKTOP_DSN,
26
+ process.env.SENTRY_CLIENT_DSN,
27
+ process.env.VITE_SENTRY_CLIENT_DSN,
28
+ process.env.VITE_SENTRY_DSN,
29
+ process.env.SENTRY_DSN,
30
+ );
31
+ if (direct) return direct;
32
+
33
+ const key = firstNonEmpty(
34
+ process.env.CLIPS_DESKTOP_SENTRY_CLIENT_KEY,
35
+ process.env.SENTRY_CLIENT_KEY,
36
+ process.env.VITE_SENTRY_CLIENT_KEY,
37
+ );
38
+ const projectId = firstNonEmpty(
39
+ process.env.CLIPS_DESKTOP_SENTRY_PROJECT_ID,
40
+ process.env.SENTRY_PROJECT_ID,
41
+ process.env.VITE_SENTRY_PROJECT_ID,
42
+ );
43
+ const host = firstNonEmpty(
44
+ process.env.CLIPS_DESKTOP_SENTRY_INGEST_HOST,
45
+ process.env.SENTRY_INGEST_HOST,
46
+ process.env.VITE_SENTRY_INGEST_HOST,
47
+ );
48
+ return key && projectId && host ? `https://${key}@${host}/${projectId}` : "";
49
+ }
50
+
51
+ function resolveSentryEnvironment(): string {
52
+ return (
53
+ firstNonEmpty(
54
+ process.env.CLIPS_DESKTOP_SENTRY_ENVIRONMENT,
55
+ process.env.SENTRY_ENVIRONMENT,
56
+ process.env.NETLIFY_CONTEXT,
57
+ process.env.VERCEL_ENV,
58
+ process.env.NODE_ENV,
59
+ ) || "production"
60
+ );
61
+ }
62
+
4
63
  // Tauri expects the frontend to be served from a fixed port during dev.
5
64
  // 1420 is the convention the Tauri docs use; we keep it here so
6
65
  // `tauri dev` and `vite dev` stay in sync out of the box.
@@ -20,6 +79,13 @@ import { defineConfig } from "vite";
20
79
  export default defineConfig({
21
80
  plugins: [react()],
22
81
  clearScreen: false,
82
+ define: {
83
+ __CLIPS_DESKTOP_SENTRY_DSN__: JSON.stringify(resolveSentryDsn()),
84
+ __CLIPS_DESKTOP_SENTRY_ENVIRONMENT__: JSON.stringify(
85
+ resolveSentryEnvironment(),
86
+ ),
87
+ __CLIPS_DESKTOP_VERSION__: JSON.stringify(packageJson.version ?? "0.0.0"),
88
+ },
23
89
  server: {
24
90
  port: 1420,
25
91
  strictPort: true,
@@ -120,7 +120,7 @@ export function Layout({ children }: LayoutProps) {
120
120
  )}
121
121
  <div
122
122
  className={cn(
123
- "agent-layout-left-drawer fixed inset-y-0 start-0 z-50 md:static md:z-auto",
123
+ "agent-layout-left-drawer fixed inset-y-0 start-0 z-50 transition-transform duration-200 ease-out md:static md:z-auto md:transition-none",
124
124
  mobileSidebarOpen
125
125
  ? "translate-x-0"
126
126
  : "-translate-x-full rtl:translate-x-full md:translate-x-0 md:rtl:translate-x-0",
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-06-27
4
+ ---
5
+
6
+ Improved mobile navigation chrome and sidebar drawer motion.
@@ -6,6 +6,7 @@ import { eq, desc } from "drizzle-orm";
6
6
  import { z } from "zod";
7
7
 
8
8
  import { getDb, schema } from "../server/db/index.js";
9
+ import { publicSubmitterEmail } from "../shared/submitter-email.js";
9
10
 
10
11
  export default defineAction({
11
12
  description: "Export form responses to CSV or JSON file.",
@@ -36,7 +37,7 @@ export default defineAction({
36
37
  const data = responses.map((r) => ({
37
38
  id: r.id,
38
39
  submittedAt: r.submittedAt,
39
- submitterEmail: r.submitterEmail ?? null,
40
+ submitterEmail: publicSubmitterEmail(r.submitterEmail),
40
41
  pageUrl: r.pageUrl ?? null,
41
42
  clientSurface: r.clientSurface ?? null,
42
43
  ...JSON.parse(r.data),
@@ -56,7 +57,7 @@ export default defineAction({
56
57
  return [
57
58
  r.id,
58
59
  r.submittedAt,
59
- r.submitterEmail ?? "",
60
+ publicSubmitterEmail(r.submitterEmail) ?? "",
60
61
  r.pageUrl ?? "",
61
62
  r.clientSurface ?? "",
62
63
  ...fields.map((f: any) => {
@@ -4,6 +4,7 @@ import { eq, desc, sql } from "drizzle-orm";
4
4
  import { z } from "zod";
5
5
 
6
6
  import { getDb, schema } from "../server/db/index.js";
7
+ import { publicSubmitterEmail } from "../shared/submitter-email.js";
7
8
  import type { FormResponse } from "../shared/types.js";
8
9
 
9
10
  export default defineAction({
@@ -49,7 +50,7 @@ export default defineAction({
49
50
  formId: r.formId,
50
51
  data: JSON.parse(r.data),
51
52
  submittedAt: r.submittedAt,
52
- submitterEmail: r.submitterEmail ?? null,
53
+ submitterEmail: publicSubmitterEmail(r.submitterEmail),
53
54
  pageUrl: r.pageUrl ?? null,
54
55
  clientSurface: r.clientSurface ?? null,
55
56
  })) as FormResponse[],
@@ -14,6 +14,7 @@ import { and, desc, eq, inArray, isNull, sql } from "drizzle-orm";
14
14
  import { z } from "zod";
15
15
 
16
16
  import { getDb, schema } from "../server/db/index.js";
17
+ import { publicSubmitterEmail } from "../shared/submitter-email.js";
17
18
  import type {
18
19
  FormField,
19
20
  ResponseInsightsChartSeries,
@@ -161,7 +162,9 @@ function buildSpecificFormTable(
161
162
  totalRows: number,
162
163
  ): ResponseInsightsTable {
163
164
  const fields = safeJson<FormField[]>(form.fields, []).slice(0, 7);
164
- const hasSubmitter = responses.some((row) => row.submitterEmail);
165
+ const hasSubmitter = responses.some((row) =>
166
+ publicSubmitterEmail(row.submitterEmail),
167
+ );
165
168
  const columns = [
166
169
  { key: "submittedAt", label: "Submitted" },
167
170
  ...(hasSubmitter ? [{ key: "submitterEmail", label: "Email" }] : []),
@@ -173,7 +176,9 @@ function buildSpecificFormTable(
173
176
  id: response.id,
174
177
  submittedAt: response.submittedAt,
175
178
  ...(hasSubmitter
176
- ? { submitterEmail: response.submitterEmail ?? "" }
179
+ ? {
180
+ submitterEmail: publicSubmitterEmail(response.submitterEmail) ?? "",
181
+ }
177
182
  : {}),
178
183
  ...Object.fromEntries(
179
184
  fields.map((field) => [field.id, cleanText(data[field.id])]),
@@ -198,7 +203,9 @@ function buildAllFormsTable(
198
203
  tableLimit: number,
199
204
  totalRows: number,
200
205
  ): ResponseInsightsTable {
201
- const hasSubmitter = responses.some((row) => row.submitterEmail);
206
+ const hasSubmitter = responses.some((row) =>
207
+ publicSubmitterEmail(row.submitterEmail),
208
+ );
202
209
  const columns = [
203
210
  { key: "submittedAt", label: "Submitted" },
204
211
  { key: "form", label: "Form" },
@@ -210,7 +217,9 @@ function buildAllFormsTable(
210
217
  formId: response.formId,
211
218
  submittedAt: response.submittedAt,
212
219
  form: formsById.get(response.formId)?.title ?? response.formId,
213
- ...(hasSubmitter ? { submitterEmail: response.submitterEmail ?? "" } : {}),
220
+ ...(hasSubmitter
221
+ ? { submitterEmail: publicSubmitterEmail(response.submitterEmail) ?? "" }
222
+ : {}),
214
223
  summary: responseSummary(response, fieldsByForm),
215
224
  }));
216
225
 
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-06-27
4
+ ---
5
+
6
+ Anonymous feedback no longer shows internal Agent Native anonymous-owner emails as submitter addresses.
@@ -17,6 +17,10 @@ import {
17
17
  } from "h3";
18
18
  import { nanoid } from "nanoid";
19
19
 
20
+ import {
21
+ cleanSubmitterEmail,
22
+ publicSubmitterEmail,
23
+ } from "../../shared/submitter-email.js";
20
24
  import type {
21
25
  FormField,
22
26
  FormIntegration,
@@ -42,13 +46,6 @@ function cleanMetaText(value: unknown): string | null {
42
46
  return trimmed.slice(0, MAX_META_TEXT_LENGTH);
43
47
  }
44
48
 
45
- function cleanSubmitterEmail(value: unknown): string | null {
46
- if (typeof value !== "string") return null;
47
- const trimmed = value.trim();
48
- if (!trimmed || trimmed.length > 320 || !trimmed.includes("@")) return null;
49
- return trimmed;
50
- }
51
-
52
49
  // Allowlist the client-surface hint so only known values are stored. Anything
53
50
  // else (including spoofed direct POSTs) is dropped to NULL.
54
51
  const KNOWN_CLIENT_SURFACES = new Set(["web", "electron", "tauri"]);
@@ -340,7 +337,7 @@ export const listResponses = defineEventHandler(async (event: H3Event) => {
340
337
  formId: r.formId,
341
338
  data: JSON.parse(r.data),
342
339
  submittedAt: r.submittedAt,
343
- submitterEmail: r.submitterEmail,
340
+ submitterEmail: publicSubmitterEmail(r.submitterEmail),
344
341
  pageUrl: r.pageUrl ?? null,
345
342
  clientSurface: r.clientSurface ?? null,
346
343
  })) as FormResponse[],
@@ -3,6 +3,7 @@ import {
3
3
  ssrfSafeToolFetch,
4
4
  } from "@agent-native/core/tools/url-safety";
5
5
 
6
+ import { publicSubmitterEmail } from "../../shared/submitter-email.js";
6
7
  import type {
7
8
  FormIntegration,
8
9
  FormField,
@@ -155,6 +156,7 @@ function formatDebugContext(submission: SubmissionPayload): string[] {
155
156
 
156
157
  /** Slack Block Kit message */
157
158
  export function buildSlackPayload(submission: SubmissionPayload) {
159
+ const submitterEmail = publicSubmitterEmail(submission.submitterEmail);
158
160
  const fieldLines = submission.fields
159
161
  .filter((f) => submission.data[f.id] !== undefined)
160
162
  .map((f) => {
@@ -164,8 +166,8 @@ export function buildSlackPayload(submission: SubmissionPayload) {
164
166
  });
165
167
 
166
168
  const tsContext = `Submitted <!date^${Math.floor(new Date(submission.submittedAt).getTime() / 1000)}^{date_short_pretty} at {time}|${submission.submittedAt}>`;
167
- const contextText = submission.submitterEmail
168
- ? `${tsContext} by *${submission.submitterEmail}*`
169
+ const contextText = submitterEmail
170
+ ? `${tsContext} by *${submitterEmail}*`
169
171
  : tsContext;
170
172
  const debugContext = formatDebugContext(submission);
171
173
 
@@ -201,6 +203,7 @@ export function buildSlackPayload(submission: SubmissionPayload) {
201
203
 
202
204
  /** Discord webhook embed */
203
205
  function buildDiscordPayload(submission: SubmissionPayload) {
206
+ const submitterEmail = publicSubmitterEmail(submission.submitterEmail);
204
207
  const discordFields = submission.fields
205
208
  .filter((f) => submission.data[f.id] !== undefined)
206
209
  .map((f) => {
@@ -208,10 +211,10 @@ function buildDiscordPayload(submission: SubmissionPayload) {
208
211
  const display = Array.isArray(val) ? val.join(", ") : String(val);
209
212
  return { name: f.label, value: display, inline: true };
210
213
  });
211
- if (submission.submitterEmail) {
214
+ if (submitterEmail) {
212
215
  discordFields.push({
213
216
  name: "Submitted by",
214
- value: submission.submitterEmail,
217
+ value: submitterEmail,
215
218
  inline: true,
216
219
  });
217
220
  }
@@ -261,7 +264,7 @@ function buildGoogleSheetsPayload(submission: SubmissionPayload) {
261
264
  return {
262
265
  formTitle: submission.formTitle,
263
266
  submittedAt: submission.submittedAt,
264
- submitterEmail: submission.submitterEmail ?? "",
267
+ submitterEmail: publicSubmitterEmail(submission.submitterEmail) ?? "",
265
268
  chatSessionIds: (submission.chatSessionIds ?? []).join(", "),
266
269
  activeRunId: submission.activeRunId ?? "",
267
270
  pageUrl: submission.pageUrl ?? "",
@@ -278,7 +281,7 @@ function buildWebhookPayload(submission: SubmissionPayload) {
278
281
  formTitle: submission.formTitle,
279
282
  responseId: submission.responseId,
280
283
  submittedAt: submission.submittedAt,
281
- submitterEmail: submission.submitterEmail ?? null,
284
+ submitterEmail: publicSubmitterEmail(submission.submitterEmail),
282
285
  chatSessionIds: submission.chatSessionIds ?? [],
283
286
  activeRunId: submission.activeRunId ?? null,
284
287
  pageUrl: submission.pageUrl ?? null,
@@ -0,0 +1,20 @@
1
+ const AGENT_NATIVE_ANONYMOUS_EMAIL_RE = /^anon-[^@]+@agent-native\.com$/i;
2
+
3
+ export function isAgentNativeAnonymousEmail(value: unknown): boolean {
4
+ if (typeof value !== "string") return false;
5
+ return AGENT_NATIVE_ANONYMOUS_EMAIL_RE.test(value.trim());
6
+ }
7
+
8
+ export function cleanSubmitterEmail(value: unknown): string | null {
9
+ if (typeof value !== "string") return null;
10
+ const trimmed = value.trim();
11
+ if (!trimmed || trimmed.length > 320 || !trimmed.includes("@")) return null;
12
+ if (isAgentNativeAnonymousEmail(trimmed)) return null;
13
+ return trimmed;
14
+ }
15
+
16
+ export function publicSubmitterEmail(
17
+ value: string | null | undefined,
18
+ ): string | null {
19
+ return cleanSubmitterEmail(value);
20
+ }
@@ -145,7 +145,7 @@ export interface FormResponse {
145
145
  formId: string;
146
146
  data: Record<string, unknown>;
147
147
  submittedAt: string;
148
- /** Email of the submitter when known (claimed by the client; not verified). */
148
+ /** Real submitter email when known; synthetic anonymous-owner ids are hidden. */
149
149
  submitterEmail?: string | null;
150
150
  /**
151
151
  * URL of the page the respondent was on, forwarded by trusted embeds (e.g.
@@ -279,6 +279,7 @@
279
279
  --wf-muted: var(--plan-muted, hsl(var(--muted-foreground)));
280
280
  --wf-line: var(--plan-line, hsl(var(--border)));
281
281
  --wf-sketch: var(--plan-muted-line, hsl(var(--border)));
282
+ --wf-diagram-line: var(--wf-line);
282
283
  --wf-paper: var(--plan-document, hsl(var(--background)));
283
284
  --wf-card: var(--plan-block, hsl(var(--card)));
284
285
  --wf-accent: hsl(var(--ring));
@@ -306,6 +307,10 @@
306
307
  line-height: 1.4;
307
308
  }
308
309
 
310
+ .plan-diagram-frame[data-theme="dark"] {
311
+ --wf-diagram-line: color-mix(in srgb, var(--wf-sketch) 72%, var(--wf-line));
312
+ }
313
+
309
314
  .plan-diagram-frame[data-style="sketchy"] {
310
315
  --wf-font: var(--wf-font-hand);
311
316
  line-height: 1.32;
@@ -321,12 +326,11 @@
321
326
  .plan-diagram-frame-content {
322
327
  width: 100%;
323
328
  padding: 8px 0;
329
+ direction: inherit;
324
330
  }
325
331
 
326
332
  .plan-diagram-frame-content
327
- > :is(.diagram-panel, .diagram-card, .diagram-box):not([data-rough]):not(
328
- [data-diagram-frame]
329
- ) {
333
+ > .diagram-panel:not([data-rough]):not([data-diagram-frame]) {
330
334
  --rough-skip: 1;
331
335
  background: transparent;
332
336
  border-color: transparent;
@@ -367,7 +371,8 @@
367
371
 
368
372
  .plan-diagram-frame
369
373
  :is(.diagram-panel, .diagram-card, .diagram-node, .diagram-box) {
370
- border: 1.25px solid var(--wf-line);
374
+ --rough-stroke: var(--wf-diagram-line);
375
+ border: 1.25px solid var(--wf-diagram-line);
371
376
  border-radius: var(--wf-radius);
372
377
  background: var(--wf-card);
373
378
  }
@@ -382,7 +387,8 @@
382
387
  [class*="panel"],
383
388
  [class*="box"]
384
389
  ) {
385
- border-color: var(--wf-line) !important;
390
+ --rough-stroke: var(--wf-diagram-line);
391
+ border-color: var(--wf-diagram-line) !important;
386
392
  background: var(--wf-card) !important;
387
393
  color: var(--wf-ink) !important;
388
394
  }
@@ -391,6 +397,19 @@
391
397
  max-width: 100%;
392
398
  }
393
399
 
400
+ /* Base padding so primitive text never touches the box edge, even when an author
401
+ * diagram omits its own padding. The wildcard selectors mirror the theme-color
402
+ * rules above so every box/card/panel variant authors use is covered. Author CSS
403
+ * (higher specificity, scoped to the diagram instance) still overrides this when
404
+ * it sets padding explicitly. */
405
+ .plan-diagram-frame :is(.diagram-node, .diagram-box, [class*="box"]) {
406
+ padding: 8px 12px;
407
+ }
408
+ .plan-diagram-frame
409
+ :is(.diagram-card, .diagram-panel, [class*="card"], [class*="panel"]) {
410
+ padding: 14px 16px;
411
+ }
412
+
394
413
  .plan-diagram-frame :is(.diagram-node, .diagram-box, .diagram-card) small {
395
414
  display: block;
396
415
  max-width: 100%;
@@ -402,7 +421,8 @@
402
421
 
403
422
  .plan-diagram-frame
404
423
  :is(.diagram-pill, [class*="pill"], [class*="badge"], [class*="chip"]) {
405
- border-color: var(--wf-line) !important;
424
+ --rough-stroke: var(--wf-diagram-line);
425
+ border-color: var(--wf-diagram-line) !important;
406
426
  background: var(--wf-paper) !important;
407
427
  color: var(--wf-ink) !important;
408
428
  }
@@ -432,15 +452,24 @@
432
452
  }
433
453
 
434
454
  .plan-diagram-frame[data-style="sketchy"] :is(.diagram-node, .diagram-box) {
435
- padding-inline: 7px;
455
+ padding-inline: 12px;
436
456
  }
437
457
 
438
- .plan-diagram-frame .diagram-pill {
458
+ .plan-diagram-frame
459
+ :is(.diagram-pill, [class*="pill"], [class*="badge"], [class*="chip"]) {
439
460
  display: inline-flex;
440
461
  align-items: center;
441
- border: 1px solid var(--wf-line);
462
+ justify-content: center;
463
+ /* A pill hugs its label. `fit-content` is a definite cross size, so a flex
464
+ * column with the default `align-items: stretch` no longer blows the pill out
465
+ * to full width (the number chips and short status labels stay pill-shaped).
466
+ * The wildcard selectors match the theme-color rules so every pill/badge/chip
467
+ * variant used across the docs diagrams stays pill-shaped, not a full bar. */
468
+ width: fit-content;
469
+ max-width: 100%;
470
+ border: 1px solid var(--wf-diagram-line);
442
471
  border-radius: 999px;
443
- padding: 2px 8px;
472
+ padding: 3px 11px;
444
473
  color: var(--wf-ink);
445
474
  background: var(--wf-paper);
446
475
  }
@@ -480,5 +509,15 @@
480
509
  [data-rough],
481
510
  hr
482
511
  ) {
512
+ border-color: color-mix(
513
+ in srgb,
514
+ var(--wf-diagram-line) 54%,
515
+ transparent
516
+ ) !important;
517
+ }
518
+
519
+ .plan-diagram-frame[data-rough-ready]
520
+ .plan-diagram-frame-content
521
+ > .diagram-panel:not([data-rough]):not([data-diagram-frame]) {
483
522
  border-color: transparent !important;
484
523
  }
@@ -87,7 +87,7 @@ export function Layout({ children }: LayoutProps) {
87
87
  )}
88
88
  <div
89
89
  className={cn(
90
- "agent-layout-left-drawer fixed inset-y-0 start-0 z-50 md:static md:z-auto",
90
+ "agent-layout-left-drawer fixed inset-y-0 start-0 z-50 transition-transform duration-200 ease-out md:static md:z-auto md:transition-none",
91
91
  sidebarOpen
92
92
  ? "translate-x-0"
93
93
  : "-translate-x-full rtl:translate-x-full md:translate-x-0 md:rtl:translate-x-0",
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-06-27
4
+ ---
5
+
6
+ Improved mobile navigation chrome and sidebar drawer motion.
@@ -0,0 +1,3 @@
1
+ export declare const OPENAI_BASE_URL_ENV_VAR = "OPENAI_BASE_URL";
2
+ export declare function normalizeOpenAiBaseUrl(value: string): string;
3
+ //# sourceMappingURL=openai-compatible-endpoint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai-compatible-endpoint.d.ts","sourceRoot":"","sources":["../../../src/agent/engine/openai-compatible-endpoint.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,uBAAuB,oBAAoB,CAAC;AAEzD,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAsB5D"}
@@ -0,0 +1,23 @@
1
+ export const OPENAI_BASE_URL_ENV_VAR = "OPENAI_BASE_URL";
2
+ export function normalizeOpenAiBaseUrl(value) {
3
+ const trimmed = value.trim();
4
+ if (!trimmed) {
5
+ throw new Error("Endpoint URL is required.");
6
+ }
7
+ let url;
8
+ try {
9
+ url = new URL(trimmed);
10
+ }
11
+ catch {
12
+ throw new Error("Endpoint URL must be a valid URL.");
13
+ }
14
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
15
+ throw new Error("Endpoint URL must start with http:// or https://.");
16
+ }
17
+ if (url.username || url.password) {
18
+ throw new Error("Endpoint URL must not include credentials.");
19
+ }
20
+ url.hash = "";
21
+ return url.toString().replace(/\/+$/, "");
22
+ }
23
+ //# sourceMappingURL=openai-compatible-endpoint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai-compatible-endpoint.js","sourceRoot":"","sources":["../../../src/agent/engine/openai-compatible-endpoint.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,uBAAuB,GAAG,iBAAiB,CAAC;AAEzD,MAAM,UAAU,sBAAsB,CAAC,KAAa;IAClD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;IAC/C,CAAC;IAED,IAAI,GAAQ,CAAC;IACb,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;IACvD,CAAC;IAED,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QAC1D,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;IACvE,CAAC;IACD,IAAI,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,QAAQ,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;IAChE,CAAC;IAED,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC;IACd,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AAC5C,CAAC","sourcesContent":["export const OPENAI_BASE_URL_ENV_VAR = \"OPENAI_BASE_URL\";\n\nexport function normalizeOpenAiBaseUrl(value: string): string {\n const trimmed = value.trim();\n if (!trimmed) {\n throw new Error(\"Endpoint URL is required.\");\n }\n\n let url: URL;\n try {\n url = new URL(trimmed);\n } catch {\n throw new Error(\"Endpoint URL must be a valid URL.\");\n }\n\n if (url.protocol !== \"https:\" && url.protocol !== \"http:\") {\n throw new Error(\"Endpoint URL must start with http:// or https://.\");\n }\n if (url.username || url.password) {\n throw new Error(\"Endpoint URL must not include credentials.\");\n }\n\n url.hash = \"\";\n return url.toString().replace(/\\/+$/, \"\");\n}\n"]}
@@ -14,6 +14,7 @@ export interface ProviderEnvMeta {
14
14
  /** Input placeholder hint shown in the LLM picker's key field */
15
15
  placeholder: string;
16
16
  }
17
+ export { OPENAI_BASE_URL_ENV_VAR } from "./openai-compatible-endpoint.js";
17
18
  export declare const PROVIDER_ENV_META: Record<string, ProviderEnvMeta>;
18
19
  export declare const PROVIDER_TO_ENV: Record<string, string>;
19
20
  export declare const PROVIDER_ENV_VARS: readonly string[];
@@ -1 +1 @@
1
- {"version":3,"file":"provider-env-vars.d.ts","sourceRoot":"","sources":["../../../src/agent/engine/provider-env-vars.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,0EAA0E;IAC1E,KAAK,EAAE,MAAM,CAAC;IACd,iEAAiE;IACjE,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAoC7D,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAElD,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,SAAS,MAAM,EACf,CAAC;AAEjC,eAAO,MAAM,yBAAyB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAG1D,CAAC"}
1
+ {"version":3,"file":"provider-env-vars.d.ts","sourceRoot":"","sources":["../../../src/agent/engine/provider-env-vars.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,0EAA0E;IAC1E,KAAK,EAAE,MAAM,CAAC;IACd,iEAAiE;IACjE,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,OAAO,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAE1E,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAoC7D,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAElD,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,SAAS,MAAM,EACf,CAAC;AAEjC,eAAO,MAAM,yBAAyB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAG1D,CAAC"}