@agent-native/core 0.120.0 → 0.120.2

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 (207) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +16 -0
  3. package/corpus/core/docs/content/extensions.mdx +23 -4
  4. package/corpus/core/package.json +1 -1
  5. package/corpus/core/src/agent/production-agent.ts +4 -0
  6. package/corpus/core/src/agent/run-manager.ts +36 -10
  7. package/corpus/core/src/agent/types.ts +2 -0
  8. package/corpus/core/src/client/MultiTabAssistantChat.tsx +45 -0
  9. package/corpus/core/src/client/extensions/ExtensionsListPage.tsx +16 -3
  10. package/corpus/core/src/client/settings/ExtensionsSettingsContent.tsx +24 -0
  11. package/corpus/core/src/client/settings/SettingsPanel.tsx +10 -0
  12. package/corpus/core/src/client/use-chat-models.ts +45 -0
  13. package/corpus/core/src/deploy/build.ts +3 -0
  14. package/corpus/core/src/server/analytics.ts +164 -20
  15. package/corpus/core/src/server/auth.ts +17 -13
  16. package/corpus/core/src/server/better-auth-instance.ts +13 -1
  17. package/corpus/core/src/templates/chat/app/components/layout/Sidebar.tsx +35 -6
  18. package/corpus/core/src/templates/chat/app/routes/extensions._index.tsx +2 -2
  19. package/corpus/core/src/templates/chat/netlify.toml +1 -0
  20. package/corpus/core/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +16 -0
  21. package/corpus/core/src/vite/client.ts +6 -0
  22. package/corpus/templates/analytics/.agents/skills/adhoc-analysis/SKILL.md +27 -43
  23. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +19 -8
  24. package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +2 -2
  25. package/corpus/templates/analytics/AGENTS.md +20 -8
  26. package/corpus/templates/analytics/actions/get-explorer-dashboard.ts +3 -0
  27. package/corpus/templates/analytics/actions/list-dashboard-usage-stats.ts +140 -46
  28. package/corpus/templates/analytics/actions/migrate-analytics-artifacts.ts +48 -0
  29. package/corpus/templates/analytics/actions/save-analysis.ts +2 -3
  30. package/corpus/templates/analytics/actions/view-screen.ts +1 -1
  31. package/corpus/templates/analytics/app/components/dashboard/DashboardMetadata.tsx +77 -0
  32. package/corpus/templates/analytics/app/components/dashboard/DashboardPanelSkeleton.tsx +7 -1
  33. package/corpus/templates/analytics/app/components/layout/NewDashboardDialog.tsx +1 -0
  34. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +103 -502
  35. package/corpus/templates/analytics/app/global.css +30 -0
  36. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +2 -2
  37. package/corpus/templates/analytics/app/i18n-data.ts +80 -0
  38. package/corpus/templates/analytics/app/pages/Agents.tsx +49 -2
  39. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +54 -11
  40. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +3 -53
  41. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +23 -25
  42. package/corpus/templates/analytics/app/pages/analyses/AnalysisDetail.tsx +2 -2
  43. package/corpus/templates/analytics/app/routes/analyses._index.tsx +3 -2
  44. package/corpus/templates/analytics/app/routes/extensions._index.tsx +2 -2
  45. package/corpus/templates/analytics/changelog/2026-07-23-analytics-now-groups-saved-analyses-and-embedded-extensions-.md +6 -0
  46. package/corpus/templates/analytics/changelog/2026-07-23-dashboard-menus-show-who-created-and-last-updated-each-dashb.md +6 -0
  47. package/corpus/templates/analytics/changelog/2026-07-23-dashboard-usage-now-loads-reliably-for-organizations-with-ma.md +6 -0
  48. package/corpus/templates/analytics/changelog/2026-07-23-long-dashboard-building-requests-now-continue-without-stoppi.md +6 -0
  49. package/corpus/templates/analytics/netlify.toml +4 -0
  50. package/corpus/templates/analytics/server/db/schema.ts +4 -0
  51. package/corpus/templates/analytics/server/lib/agent-chat-plan-mode.ts +0 -1
  52. package/corpus/templates/analytics/server/lib/agent-readable-resource-context.ts +1 -0
  53. package/corpus/templates/analytics/server/lib/migrate-analytics-artifacts.ts +1062 -0
  54. package/corpus/templates/analytics/server/plugins/agent-chat.ts +6 -52
  55. package/corpus/templates/analytics/server/plugins/core-routes.ts +1 -1
  56. package/corpus/templates/analytics/server/plugins/db.ts +10 -0
  57. package/corpus/templates/assets/actions/generate-image-batch.ts +36 -1
  58. package/corpus/templates/assets/actions/generate-image.ts +28 -1
  59. package/corpus/templates/assets/app/components/layout/Sidebar.tsx +47 -9
  60. package/corpus/templates/assets/app/routes/extensions._index.tsx +2 -2
  61. package/corpus/templates/assets/changelog/2026-07-23-assets-image-generation-now-recovers-after-a-completed-tool-.md +6 -0
  62. package/corpus/templates/assets/changelog/2026-07-23-new-chat-aligns-with-sidebar-controls.md +6 -0
  63. package/corpus/templates/assets/netlify.toml +1 -0
  64. package/corpus/templates/brain/app/components/layout/Sidebar.tsx +38 -8
  65. package/corpus/templates/brain/app/routes/extensions._index.tsx +2 -2
  66. package/corpus/templates/brain/netlify.toml +1 -0
  67. package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +43 -5
  68. package/corpus/templates/calendar/app/routes/_app.extensions._index.tsx +2 -16
  69. package/corpus/templates/calendar/netlify.toml +1 -0
  70. package/corpus/templates/chat/app/components/layout/Sidebar.tsx +35 -6
  71. package/corpus/templates/chat/app/routes/extensions._index.tsx +2 -2
  72. package/corpus/templates/chat/netlify.toml +1 -0
  73. package/corpus/templates/clips/app/components/library/library-layout.tsx +52 -6
  74. package/corpus/templates/clips/app/routes/_app.extensions._index.tsx +2 -16
  75. package/corpus/templates/clips/changelog/2026-07-23-agent-settings-are-clearly-labeled-manage-agent-in-the-sideb.md +6 -0
  76. package/corpus/templates/clips/changelog/2026-07-23-meeting-recording-meters-and-dismissed-prompts.md +6 -0
  77. package/corpus/templates/clips/desktop/src/hooks/useMicMeter.ts +1 -1
  78. package/corpus/templates/clips/desktop/src/lib/meeting-notification-dismissal.ts +2 -0
  79. package/corpus/templates/clips/desktop/src/lib/voice-dictation.ts +1 -3
  80. package/corpus/templates/clips/desktop/src/overlays/live-audio-bars.tsx +86 -0
  81. package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +24 -0
  82. package/corpus/templates/clips/desktop/src/overlays/recording-pill.tsx +5 -69
  83. package/corpus/templates/clips/desktop/src/styles.css +35 -3
  84. package/corpus/templates/clips/desktop/src-tauri/src/adhoc_meetings_watcher.rs +13 -44
  85. package/corpus/templates/clips/desktop/src-tauri/src/notifications.rs +119 -17
  86. package/corpus/templates/clips/netlify.toml +1 -0
  87. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +3 -7
  88. package/corpus/templates/content/app/routes/_app.extensions._index.tsx +2 -2
  89. package/corpus/templates/content/changelog/2026-07-23-agent-settings-are-clearly-labeled-manage-agent-in-the-sideb.md +6 -0
  90. package/corpus/templates/content/netlify.toml +1 -0
  91. package/corpus/templates/design/app/components/layout/Sidebar.tsx +55 -17
  92. package/corpus/templates/design/app/routes/extensions._index.tsx +2 -2
  93. package/corpus/templates/design/changelog/2026-07-23-agent-settings-are-clearly-labeled-manage-agent-in-the-sideb.md +6 -0
  94. package/corpus/templates/design/netlify.toml +1 -0
  95. package/corpus/templates/dispatch/app/dispatch-extensions.tsx +1 -1
  96. package/corpus/templates/dispatch/changelog/2026-07-23-agent-settings-are-clearly-labeled-manage-agent-in-the-sideb.md +6 -0
  97. package/corpus/templates/dispatch/netlify.toml +1 -0
  98. package/corpus/templates/forms/.agents/skills/form-publishing/SKILL.md +9 -3
  99. package/corpus/templates/forms/AGENTS.md +11 -1
  100. package/corpus/templates/forms/README.md +24 -1
  101. package/corpus/templates/forms/actions/update-form.ts +12 -5
  102. package/corpus/templates/forms/app/components/builder/FieldPropertiesPanel.tsx +199 -1
  103. package/corpus/templates/forms/app/components/layout/Sidebar.tsx +41 -45
  104. package/corpus/templates/forms/app/i18n/en-US.ts +13 -1
  105. package/corpus/templates/forms/app/pages/FormBuilderPage.tsx +35 -4
  106. package/corpus/templates/forms/app/pages/FormFillPage.tsx +7 -16
  107. package/corpus/templates/forms/app/routes/_app.extensions._index.tsx +2 -2
  108. package/corpus/templates/forms/changelog/2026-07-23-agent-settings-are-clearly-labeled-manage-agent-in-the-sideb.md +6 -0
  109. package/corpus/templates/forms/changelog/2026-07-23-forms-can-branch-into-follow-up-questions-and-route-response.md +6 -0
  110. package/corpus/templates/forms/netlify.toml +1 -0
  111. package/corpus/templates/forms/server/handlers/submissions.ts +22 -31
  112. package/corpus/templates/forms/server/lib/integrations.ts +11 -2
  113. package/corpus/templates/forms/server/lib/public-form-ssr.ts +26 -5
  114. package/corpus/templates/forms/server/lib/validate-fields.ts +48 -11
  115. package/corpus/templates/forms/server/plugins/agent-chat.ts +1 -0
  116. package/corpus/templates/forms/shared/conditional.ts +67 -0
  117. package/corpus/templates/macros/app/components/layout/AppLayout.tsx +36 -5
  118. package/corpus/templates/macros/app/routes/extensions._index.tsx +2 -2
  119. package/corpus/templates/macros/netlify.toml +1 -0
  120. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +0 -9
  121. package/corpus/templates/mail/app/routes/extensions._index.tsx +2 -2
  122. package/corpus/templates/mail/netlify.toml +1 -0
  123. package/corpus/templates/plan/app/components/layout/Sidebar.tsx +38 -5
  124. package/corpus/templates/plan/app/routes/extensions._index.tsx +2 -2
  125. package/corpus/templates/plan/netlify.toml +1 -0
  126. package/corpus/templates/slides/app/components/layout/Sidebar.tsx +47 -4
  127. package/corpus/templates/slides/app/routes/extensions._index.tsx +2 -2
  128. package/corpus/templates/slides/netlify.toml +1 -0
  129. package/corpus/templates/tasks/app/components/layout/Sidebar.tsx +37 -6
  130. package/corpus/templates/tasks/app/routes/extensions._index.tsx +2 -2
  131. package/corpus/templates/tasks/app/routes/settings.tsx +31 -0
  132. package/corpus/templates/tasks/netlify.toml +1 -0
  133. package/dist/agent/production-agent.d.ts.map +1 -1
  134. package/dist/agent/production-agent.js +4 -0
  135. package/dist/agent/production-agent.js.map +1 -1
  136. package/dist/agent/run-manager.d.ts +9 -0
  137. package/dist/agent/run-manager.d.ts.map +1 -1
  138. package/dist/agent/run-manager.js +25 -10
  139. package/dist/agent/run-manager.js.map +1 -1
  140. package/dist/agent/types.d.ts +2 -2
  141. package/dist/agent/types.d.ts.map +1 -1
  142. package/dist/agent/types.js.map +1 -1
  143. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  144. package/dist/client/MultiTabAssistantChat.js +35 -0
  145. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  146. package/dist/client/extensions/ExtensionsListPage.d.ts +5 -1
  147. package/dist/client/extensions/ExtensionsListPage.d.ts.map +1 -1
  148. package/dist/client/extensions/ExtensionsListPage.js +5 -3
  149. package/dist/client/extensions/ExtensionsListPage.js.map +1 -1
  150. package/dist/client/settings/ExtensionsSettingsContent.d.ts +2 -0
  151. package/dist/client/settings/ExtensionsSettingsContent.d.ts.map +1 -0
  152. package/dist/client/settings/ExtensionsSettingsContent.js +10 -0
  153. package/dist/client/settings/ExtensionsSettingsContent.js.map +1 -0
  154. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  155. package/dist/client/settings/SettingsPanel.js +10 -1
  156. package/dist/client/settings/SettingsPanel.js.map +1 -1
  157. package/dist/client/use-chat-models.d.ts +1 -0
  158. package/dist/client/use-chat-models.d.ts.map +1 -1
  159. package/dist/client/use-chat-models.js +33 -0
  160. package/dist/client/use-chat-models.js.map +1 -1
  161. package/dist/collab/awareness.d.ts +2 -2
  162. package/dist/collab/awareness.d.ts.map +1 -1
  163. package/dist/deploy/build.js +1 -0
  164. package/dist/deploy/build.js.map +1 -1
  165. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  166. package/dist/observability/routes.d.ts +3 -3
  167. package/dist/provider-api/actions/custom-provider-registration.d.ts +4 -4
  168. package/dist/provider-api/actions/provider-api.d.ts +2 -2
  169. package/dist/server/analytics.d.ts +17 -4
  170. package/dist/server/analytics.d.ts.map +1 -1
  171. package/dist/server/analytics.js +122 -17
  172. package/dist/server/analytics.js.map +1 -1
  173. package/dist/server/auth.d.ts.map +1 -1
  174. package/dist/server/auth.js +5 -4
  175. package/dist/server/auth.js.map +1 -1
  176. package/dist/server/better-auth-instance.d.ts +8 -0
  177. package/dist/server/better-auth-instance.d.ts.map +1 -1
  178. package/dist/server/better-auth-instance.js +10 -1
  179. package/dist/server/better-auth-instance.js.map +1 -1
  180. package/dist/templates/chat/app/components/layout/Sidebar.tsx +35 -6
  181. package/dist/templates/chat/app/routes/extensions._index.tsx +2 -2
  182. package/dist/templates/chat/netlify.toml +1 -0
  183. package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +16 -0
  184. package/dist/vite/client.d.ts.map +1 -1
  185. package/dist/vite/client.js +2 -0
  186. package/dist/vite/client.js.map +1 -1
  187. package/docs/content/extensions.mdx +23 -4
  188. package/package.json +1 -1
  189. package/src/agent/production-agent.ts +4 -0
  190. package/src/agent/run-manager.ts +36 -10
  191. package/src/agent/types.ts +2 -0
  192. package/src/client/MultiTabAssistantChat.tsx +45 -0
  193. package/src/client/extensions/ExtensionsListPage.tsx +16 -3
  194. package/src/client/settings/ExtensionsSettingsContent.tsx +24 -0
  195. package/src/client/settings/SettingsPanel.tsx +10 -0
  196. package/src/client/use-chat-models.ts +45 -0
  197. package/src/deploy/build.ts +3 -0
  198. package/src/server/analytics.ts +164 -20
  199. package/src/server/auth.ts +17 -13
  200. package/src/server/better-auth-instance.ts +13 -1
  201. package/src/templates/chat/app/components/layout/Sidebar.tsx +35 -6
  202. package/src/templates/chat/app/routes/extensions._index.tsx +2 -2
  203. package/src/templates/chat/netlify.toml +1 -0
  204. package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +16 -0
  205. package/src/vite/client.ts +6 -0
  206. package/corpus/templates/analytics/app/components/layout/NewAnalysisDialog.tsx +0 -165
  207. package/corpus/templates/analytics/app/pages/analyses/AnalysesList.tsx +0 -175
@@ -6,6 +6,7 @@
6
6
 
7
7
  use serde::{Deserialize, Serialize};
8
8
  use serde_json::Value;
9
+ use std::collections::HashMap;
9
10
  use std::sync::atomic::{AtomicBool, Ordering};
10
11
  use std::sync::Mutex;
11
12
  use std::time::Duration;
@@ -28,9 +29,16 @@ const NOTIFICATION_W_LOGICAL: u32 = 504;
28
29
  const NOTIFICATION_H_LOGICAL: u32 = 120;
29
30
  const NOTIFICATION_TOP_MARGIN_LOGICAL: u32 = 44;
30
31
  const NOTIFICATION_RIGHT_MARGIN_LOGICAL: u32 = 0;
32
+ const DISMISSAL_TOMBSTONE_SECS: i64 = 30 * 60;
31
33
 
32
34
  #[derive(Default)]
33
- pub struct MeetingNotificationState(pub Mutex<Option<Value>>);
35
+ pub struct MeetingNotificationState(Mutex<MeetingNotificationStateInner>);
36
+
37
+ #[derive(Default)]
38
+ struct MeetingNotificationStateInner {
39
+ pending: Option<Value>,
40
+ dismissed_until: HashMap<String, i64>,
41
+ }
34
42
 
35
43
  #[allow(dead_code)]
36
44
  #[derive(Debug, Clone, Serialize, Deserialize)]
@@ -176,22 +184,54 @@ pub fn show_meeting_notification_window(app: &AppHandle) -> Result<(), String> {
176
184
  Ok(())
177
185
  }
178
186
 
179
- fn store_pending_meeting_notification(app: &AppHandle, payload: &Value) {
180
- if let Some(state) = app.try_state::<MeetingNotificationState>() {
181
- if let Ok(mut pending) = state.0.lock() {
182
- *pending = Some(payload.clone());
183
- }
184
- }
185
- }
186
-
187
187
  #[tauri::command]
188
188
  pub fn take_pending_meeting_notification(app: AppHandle) -> Result<Option<Value>, String> {
189
189
  let state = app.state::<MeetingNotificationState>();
190
- let mut pending = state
190
+ let mut state = state
191
191
  .0
192
192
  .lock()
193
193
  .map_err(|_| "meeting notification state lock poisoned".to_string())?;
194
- Ok(pending.take())
194
+ let now = chrono::Utc::now().timestamp();
195
+ state.dismissed_until.retain(|_, until| *until > now);
196
+ if state.pending.as_ref().is_some_and(|payload| {
197
+ state
198
+ .dismissed_until
199
+ .contains_key(&notification_key(payload))
200
+ }) {
201
+ state.pending = None;
202
+ }
203
+ Ok(state.pending.take())
204
+ }
205
+
206
+ fn notification_key(payload: &Value) -> String {
207
+ format!(
208
+ "{}|{}|{}",
209
+ payload
210
+ .get("type")
211
+ .and_then(Value::as_str)
212
+ .unwrap_or("calendar"),
213
+ payload
214
+ .get("meetingId")
215
+ .and_then(Value::as_str)
216
+ .unwrap_or_default(),
217
+ payload
218
+ .get("scheduledStart")
219
+ .and_then(Value::as_str)
220
+ .unwrap_or_default(),
221
+ )
222
+ }
223
+
224
+ fn notification_key_from_parts(
225
+ notification_type: &str,
226
+ meeting_id: &str,
227
+ scheduled_start: Option<&str>,
228
+ ) -> String {
229
+ format!(
230
+ "{}|{}|{}",
231
+ notification_type,
232
+ meeting_id,
233
+ scheduled_start.unwrap_or_default()
234
+ )
195
235
  }
196
236
 
197
237
  fn clear_pending_notification(pending: &mut Option<Value>, meeting_id: &str) -> bool {
@@ -211,19 +251,38 @@ pub fn dismiss_meeting_notification(
211
251
  app: AppHandle,
212
252
  meeting_id: String,
213
253
  notification_type: String,
254
+ join_url: Option<String>,
214
255
  platform: Option<String>,
215
256
  scheduled_start: Option<String>,
216
257
  scheduled_end: Option<String>,
217
258
  ) -> Result<(), String> {
218
259
  let state = app.state::<MeetingNotificationState>();
219
- let mut pending = state
260
+ let mut state = state
220
261
  .0
221
262
  .lock()
222
263
  .map_err(|_| "meeting notification state lock poisoned".to_string())?;
223
- clear_pending_notification(&mut pending, &meeting_id);
224
- drop(pending);
264
+ let now = chrono::Utc::now().timestamp();
265
+ state.dismissed_until.retain(|_, until| *until > now);
266
+ state.dismissed_until.insert(
267
+ notification_key_from_parts(&notification_type, &meeting_id, scheduled_start.as_deref()),
268
+ now + DISMISSAL_TOMBSTONE_SECS,
269
+ );
270
+ clear_pending_notification(&mut state.pending, &meeting_id);
271
+ drop(state);
225
272
 
226
- if let Some(platform) = platform.as_deref() {
273
+ let suppression_platform = platform.as_deref().or_else(|| {
274
+ join_url.as_deref().and_then(|url| {
275
+ let url = url.to_ascii_lowercase();
276
+ if url.contains("zoom") {
277
+ Some("zoom")
278
+ } else if url.contains("teams.microsoft") || url.contains("teams") {
279
+ Some("teams")
280
+ } else {
281
+ None
282
+ }
283
+ })
284
+ });
285
+ if let Some(platform) = suppression_platform {
227
286
  crate::adhoc_meetings_watcher::refresh_dismissal_suppression(&app, platform)?;
228
287
  }
229
288
 
@@ -312,14 +371,41 @@ pub async fn notify_meeting_starting(
312
371
  "type": kind,
313
372
  "title": title,
314
373
  "subtitle": body,
315
- "meetingId": meeting_id,
374
+ "meetingId": meeting_id.clone(),
316
375
  "joinUrl": join_url,
317
376
  "platform": platform,
318
377
  "scheduledStart": scheduled_start,
319
378
  "scheduledEnd": scheduled_end,
320
379
  "autoStart": auto_start.unwrap_or(false),
321
380
  });
322
- store_pending_meeting_notification(&app, &payload);
381
+ let state = app.state::<MeetingNotificationState>();
382
+ let should_show = {
383
+ let mut state = state
384
+ .0
385
+ .lock()
386
+ .map_err(|_| "meeting notification state lock poisoned".to_string())?;
387
+ let now = chrono::Utc::now().timestamp();
388
+ state.dismissed_until.retain(|_, until| *until > now);
389
+ if state
390
+ .dismissed_until
391
+ .get(&notification_key(&payload))
392
+ .copied()
393
+ .unwrap_or_default()
394
+ > now
395
+ {
396
+ false
397
+ } else {
398
+ state.pending = Some(payload.clone());
399
+ true
400
+ }
401
+ };
402
+ if !should_show {
403
+ dlog!(
404
+ "[clips-tray] skipped dismissed meeting notification id={}",
405
+ meeting_id
406
+ );
407
+ return Ok(());
408
+ }
323
409
  let _ = app.emit("meetings:show-notification", payload.clone());
324
410
 
325
411
  // Ensure the overlay window exists / is visible for cold starts.
@@ -346,4 +432,20 @@ mod tests {
346
432
  assert!(clear_pending_notification(&mut pending, "meeting-1"));
347
433
  assert!(pending.is_none());
348
434
  }
435
+
436
+ #[test]
437
+ fn dismissal_key_changes_when_a_meeting_is_rescheduled() {
438
+ let first = serde_json::json!({
439
+ "type": "calendar",
440
+ "meetingId": "meeting-1",
441
+ "scheduledStart": "2026-07-23T17:00:00Z"
442
+ });
443
+ let moved = serde_json::json!({
444
+ "type": "calendar",
445
+ "meetingId": "meeting-1",
446
+ "scheduledStart": "2026-07-23T18:00:00Z"
447
+ });
448
+
449
+ assert_ne!(notification_key(&first), notification_key(&moved));
450
+ }
349
451
  }
@@ -6,6 +6,7 @@ functions = "templates/clips/.netlify/functions-internal"
6
6
 
7
7
  [build.environment]
8
8
  GA_MEASUREMENT_ID = "G-ESF7FYXGN9"
9
+ GTM_CONTAINER_ID = "GTM-N3WSTXZ"
9
10
  NITRO_PRESET = "netlify"
10
11
  NPM_CONFIG_PRODUCTION = "false"
11
12
  AUTO_CREATE_DEFAULT_ORG = "1"
@@ -1,10 +1,7 @@
1
1
  import { useCodeMode } from "@agent-native/core/client/agent-chat";
2
2
  import { appPath } from "@agent-native/core/client/api-path";
3
3
  import { DevDatabaseLink } from "@agent-native/core/client/db-admin";
4
- import {
5
- ExtensionSlot,
6
- ExtensionsSidebarSection,
7
- } from "@agent-native/core/client/extensions";
4
+ import { ExtensionSlot } from "@agent-native/core/client/extensions";
8
5
  import {
9
6
  setClientAppState,
10
7
  useActionMutation,
@@ -1293,7 +1290,7 @@ export function DocumentSidebar({
1293
1290
  >
1294
1291
  <IconHierarchy2 size={15} className="shrink-0" />
1295
1292
  <span className="min-w-0 flex-1 truncate text-start">
1296
- {t("navigation.agent")}
1293
+ {t("settings.agentTitle")}
1297
1294
  </span>
1298
1295
  </Link>
1299
1296
  );
@@ -1861,7 +1858,7 @@ export function DocumentSidebar({
1861
1858
  <IconHierarchy2 size={16} />
1862
1859
  </Link>
1863
1860
  </TooltipTrigger>
1864
- <TooltipContent>{t("navigation.agent")}</TooltipContent>
1861
+ <TooltipContent>{t("settings.agentTitle")}</TooltipContent>
1865
1862
  </Tooltip>
1866
1863
  <Tooltip>
1867
1864
  <TooltipTrigger asChild>
@@ -2092,7 +2089,6 @@ export function DocumentSidebar({
2092
2089
  className="px-2 py-2"
2093
2090
  toolClassName="overflow-hidden rounded-md"
2094
2091
  />
2095
- <ExtensionsSidebarSection />
2096
2092
  </div>
2097
2093
 
2098
2094
  <div className="shrink-0 px-3 py-2">
@@ -1,5 +1,5 @@
1
- import { ExtensionsListPage } from "@agent-native/core/client/extensions";
1
+ import { Navigate } from "react-router";
2
2
 
3
3
  export default function ExtensionsRoute() {
4
- return <ExtensionsListPage />;
4
+ return <Navigate to="/settings#extensions" replace />;
5
5
  }
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-07-23
4
+ ---
5
+
6
+ Agent settings are clearly labeled Manage agent in the sidebar.
@@ -6,6 +6,7 @@ functions = "templates/content/.netlify/functions-internal"
6
6
 
7
7
  [build.environment]
8
8
  GA_MEASUREMENT_ID = "G-ESF7FYXGN9"
9
+ GTM_CONTAINER_ID = "GTM-N3WSTXZ"
9
10
  NITRO_PRESET = "netlify"
10
11
  NPM_CONFIG_PRODUCTION = "false"
11
12
  AGENT_RUN_SOFT_TIMEOUT_MS = "55000"
@@ -1,6 +1,5 @@
1
1
  import { appPath } from "@agent-native/core/client/api-path";
2
2
  import { DevDatabaseLink } from "@agent-native/core/client/db-admin";
3
- import { ExtensionsSidebarSection } from "@agent-native/core/client/extensions";
4
3
  import { useT } from "@agent-native/core/client/i18n";
5
4
  import { OrgSwitcher } from "@agent-native/core/client/org";
6
5
  import { FeedbackButton } from "@agent-native/core/client/ui";
@@ -32,7 +31,10 @@ const navItems = [
32
31
  labelKey: "navigation.designSystems",
33
32
  href: "/design-systems",
34
33
  },
35
- { icon: IconHierarchy2, labelKey: "navigation.agent", href: "/agent" },
34
+ ];
35
+
36
+ const bottomNavItems = [
37
+ { icon: IconHierarchy2, labelKey: "settings.agentTitle", href: "/agent" },
36
38
  { icon: IconSettings, labelKey: "navigation.settings", href: "/settings" },
37
39
  ];
38
40
 
@@ -156,25 +158,61 @@ export function Sidebar() {
156
158
  })}
157
159
  </nav>
158
160
 
159
- {!collapsed && (
160
- <div className="mt-auto shrink-0">
161
- <div className="px-2 py-1">
162
- <ExtensionsSidebarSection />
163
- </div>
161
+ <div className="mt-auto shrink-0">
162
+ <nav
163
+ className={cn(
164
+ "grid gap-1",
165
+ collapsed ? "justify-items-center px-1.5 py-1" : "px-2 py-1",
166
+ )}
167
+ >
168
+ {bottomNavItems.map((item) => {
169
+ const Icon = item.icon;
170
+ const isActive = location.pathname.startsWith(item.href);
171
+ const link = (
172
+ <Link
173
+ to={item.href}
174
+ className={cn(
175
+ "flex items-center rounded-lg text-sm",
176
+ collapsed ? "h-9 w-9 justify-center" : "gap-3 px-3 py-2",
177
+ isActive
178
+ ? "bg-sidebar-accent text-sidebar-accent-foreground"
179
+ : "text-muted-foreground hover:bg-sidebar-accent/50 hover:text-foreground",
180
+ )}
181
+ aria-label={collapsed ? t(item.labelKey) : undefined}
182
+ >
183
+ <Icon className="h-4 w-4 shrink-0" />
184
+ {!collapsed && t(item.labelKey)}
185
+ </Link>
186
+ );
187
+ return collapsed ? (
188
+ <Tooltip key={item.href} delayDuration={0}>
189
+ <TooltipTrigger asChild>{link}</TooltipTrigger>
190
+ <TooltipContent side="right">
191
+ {t(item.labelKey)}
192
+ </TooltipContent>
193
+ </Tooltip>
194
+ ) : (
195
+ <div key={item.href}>{link}</div>
196
+ );
197
+ })}
198
+ </nav>
164
199
 
165
- <div className="px-3 py-2">
166
- <OrgSwitcher reserveSpace />
167
- </div>
200
+ {!collapsed && (
201
+ <div className="mt-auto shrink-0">
202
+ <div className="px-3 py-2">
203
+ <OrgSwitcher reserveSpace />
204
+ </div>
168
205
 
169
- <div className="px-3 py-2">
170
- <DevDatabaseLink />
171
- <div className="flex items-center justify-end gap-1">
172
- <FeedbackButton className="min-w-0 flex-1" />
173
- <ThemeToggle className="h-8 w-8 shrink-0" />
206
+ <div className="px-3 py-2">
207
+ <DevDatabaseLink />
208
+ <div className="flex items-center justify-end gap-1">
209
+ <FeedbackButton className="min-w-0 flex-1" />
210
+ <ThemeToggle className="h-8 w-8 shrink-0" />
211
+ </div>
174
212
  </div>
175
213
  </div>
176
- </div>
177
- )}
214
+ )}
215
+ </div>
178
216
  </div>
179
217
  </aside>
180
218
  );
@@ -1,4 +1,4 @@
1
- import { ExtensionsListPage } from "@agent-native/core/client/extensions";
1
+ import { Navigate } from "react-router";
2
2
 
3
3
  import { messagesByLocale } from "@/i18n-data";
4
4
 
@@ -7,5 +7,5 @@ export function meta() {
7
7
  }
8
8
 
9
9
  export default function ExtensionsRoute() {
10
- return <ExtensionsListPage />;
10
+ return <Navigate to="/settings#extensions" replace />;
11
11
  }
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-07-23
4
+ ---
5
+
6
+ Agent settings are clearly labeled Manage agent in the sidebar.
@@ -6,6 +6,7 @@ functions = "templates/design/.netlify/functions-internal"
6
6
 
7
7
  [build.environment]
8
8
  GA_MEASUREMENT_ID = "G-ESF7FYXGN9"
9
+ GTM_CONTAINER_ID = "GTM-N3WSTXZ"
9
10
  NITRO_PRESET = "netlify"
10
11
  NPM_CONFIG_PRODUCTION = "false"
11
12
  AGENT_CHAT_DURABLE_BACKGROUND = "true"
@@ -30,7 +30,7 @@ export const dispatchExtensions = {
30
30
  {
31
31
  id: "agent",
32
32
  to: "/agent",
33
- label: "Agent",
33
+ label: "Manage agent",
34
34
  icon: IconHierarchy2,
35
35
  section: "operations",
36
36
  },
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-07-23
4
+ ---
5
+
6
+ Agent settings are clearly labeled Manage agent in the sidebar.
@@ -6,6 +6,7 @@ functions = "templates/dispatch/.netlify/functions-internal"
6
6
 
7
7
  [build.environment]
8
8
  GA_MEASUREMENT_ID = "G-ESF7FYXGN9"
9
+ GTM_CONTAINER_ID = "GTM-N3WSTXZ"
9
10
  NITRO_PRESET = "netlify"
10
11
  NPM_CONFIG_PRODUCTION = "false"
11
12
 
@@ -77,7 +77,7 @@ Each form has a `settings` JSON object:
77
77
  | `showProgressBar` | boolean | Show progress bar for multi-section forms |
78
78
  | `emailOnNewResponses` | boolean | Email the form owner's account when someone submits a response |
79
79
  | `anonymous` | boolean | Suppress IP, submitter identity, chat/run ids, page URL, and client-surface metadata for every response |
80
- | `integrations` | array | Webhook/Slack/Discord notification configs |
80
+ | `integrations` | array | Webhook/Slack/Discord/Google Sheets notification configs |
81
81
 
82
82
  For a genuinely anonymous form, set `anonymous: true` when creating the form.
83
83
  Do not describe an ordinary published form as anonymous: published forms accept
@@ -91,9 +91,15 @@ Forms can notify external services on submission:
91
91
  | Type | Description |
92
92
  | --------------- | ------------------------------- |
93
93
  | `webhook` | POST JSON to any URL |
94
- | `slack` | Send to a Slack channel |
94
+ | `slack` | Send to a Slack channel through an Incoming Webhook URL |
95
95
  | `discord` | Send to a Discord webhook |
96
- | `google-sheets` | Append row to a Google Sheet |
96
+ | `google-sheets` | Send response JSON to a deployed Apps Script `/exec` URL |
97
+
98
+ These are outbound form destinations configured in the form builder's
99
+ **Integrations** tab. They are separate from the managed Slack/Messaging
100
+ connection. Google Sheets Apps Script handlers should parse
101
+ `JSON.parse(e.postData.contents)`; a spreadsheet URL or `/dev` URL will not
102
+ receive submissions.
97
103
 
98
104
  ## Related Skills
99
105
 
@@ -40,7 +40,17 @@ ladder.
40
40
  - To email the form owner when someone submits a response, set
41
41
  `settings.emailOnNewResponses: true` through `create-form` or `update-form`.
42
42
  Delivery uses the configured framework email provider (`RESEND_API_KEY` or
43
- `SENDGRID_API_KEY`) and sends to the form owner's account email.
43
+ `SENDGRID_API_KEY`) in the form owner's request context and sends to the form
44
+ owner's account email. A public submission can succeed even when delivery
45
+ fails, so check the server logs and provider configuration when debugging.
46
+ - Conditional fields use `conditional: { fieldId, operator, value }`. The
47
+ `fieldId` must reference an earlier field; supported operators are `equals`,
48
+ `not_equals`, and `contains`. Hidden fields and their stale values are not
49
+ persisted or delivered to integrations.
50
+ - Form integrations are outbound webhooks. Slack requires an Incoming Webhook
51
+ URL, and Google Sheets requires a deployed Google Apps Script `/exec` URL that
52
+ parses `JSON.parse(e.postData.contents)` and appends the received values.
53
+ They are separate from the managed Slack/Messaging connection.
44
54
  - Form UX should stay focused: clear labels, sensible validation, minimal
45
55
  required fields, and progressive disclosure for advanced settings.
46
56
  - Public form submission endpoints must be intentionally public; keep management
@@ -17,7 +17,8 @@ them.
17
17
  - Shipped field types: text, email, number, long text, select, multi-select,
18
18
  checkbox, radio, date, rating, and scale.
19
19
  - Submissions stored in SQL with a per-response detail view and dashboard.
20
- - Route submissions to webhooks, Slack, Discord, or Google Sheets.
20
+ - Route submissions to webhooks, Slack, Discord, or Google Sheets via Apps
21
+ Script.
21
22
  - Publish public form URLs with a thank-you message.
22
23
 
23
24
  ## Develop locally
@@ -32,3 +33,25 @@ pnpm dev
32
33
  ```
33
34
 
34
35
  Full docs: [agent-native.com/docs/template-forms](https://agent-native.com/docs/template-forms).
36
+
37
+ ## Submission destinations
38
+
39
+ Open a form's **Integrations** tab and save a destination before publishing.
40
+ Slack uses an Incoming Webhook URL. Google Sheets uses a deployed Apps Script
41
+ web app URL ending in `/exec` (not a spreadsheet URL or `/dev` URL). The script
42
+ should parse the JSON body and append the values, for example:
43
+
44
+ ```ts
45
+ function doPost(e) {
46
+ const payload = JSON.parse(e.postData.contents);
47
+ SpreadsheetApp.getActiveSpreadsheet()
48
+ .getSheets()[0]
49
+ .appendRow([payload.submittedAt, payload.formTitle, payload.responseId]);
50
+ return ContentService.createTextOutput(JSON.stringify({ ok: true }));
51
+ }
52
+ ```
53
+
54
+ Deploy it as a web app that executes as you and allows anyone with the URL to
55
+ access it, then paste the deployed `/exec` URL into Forms. The destination
56
+ receives the form metadata plus one property per field label; duplicate labels
57
+ are disambiguated with the field ID.
@@ -85,22 +85,29 @@ export default defineAction({
85
85
  updates.fields = JSON.stringify(parsedFields);
86
86
  }
87
87
  if (args.settings !== undefined) {
88
- let parsedSettings: FormSettings;
88
+ let incomingSettings: FormSettings;
89
89
  if (typeof args.settings === "string") {
90
90
  try {
91
- parsedSettings = JSON.parse(args.settings) as FormSettings;
92
- updates.settings = args.settings;
91
+ incomingSettings = JSON.parse(args.settings) as FormSettings;
93
92
  } catch {
94
93
  throw new Error("--settings must be valid JSON");
95
94
  }
96
95
  } else {
97
- parsedSettings = args.settings as unknown as FormSettings;
98
- updates.settings = JSON.stringify(args.settings);
96
+ incomingSettings = args.settings as unknown as FormSettings;
99
97
  }
98
+ let existingSettings: FormSettings = {};
99
+ try {
100
+ existingSettings = JSON.parse(existing.settings) as FormSettings;
101
+ } catch {
102
+ // Keep malformed legacy settings recoverable by replacing them with
103
+ // the valid settings supplied by this update.
104
+ }
105
+ const parsedSettings = { ...existingSettings, ...incomingSettings };
100
106
  // Reject blocked integration URLs at save time (private IPs,
101
107
  // cloud-metadata, non-http(s) schemes). fireIntegrations also
102
108
  // re-checks at runtime as defense-in-depth.
103
109
  assertIntegrationUrlsAllowed(parsedSettings);
110
+ updates.settings = JSON.stringify(parsedSettings);
104
111
  }
105
112
  if (args.status !== undefined) updates.status = args.status;
106
113