@agent-native/core 0.149.6 → 0.151.0

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 (177) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  3. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +83 -45
  4. package/corpus/templates/analytics/changelog/2026-08-11-analytics-dashboards-keep-the-selected-tab-when-reopened.md +6 -0
  5. package/corpus/templates/assets/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  6. package/corpus/templates/brain/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  7. package/corpus/templates/calendar/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  8. package/corpus/templates/chat/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  9. package/corpus/templates/clips/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  10. package/corpus/templates/clips/changelog/2026-08-11-clips-recovers-from-transient-updates-and-avoids-macos-priva.md +6 -0
  11. package/corpus/templates/clips/desktop/src/app.tsx +32 -176
  12. package/corpus/templates/clips/desktop/src/lib/updater.ts +52 -37
  13. package/corpus/templates/clips/desktop/src/styles.css +1 -166
  14. package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +2 -0
  15. package/corpus/templates/clips/desktop/src-tauri/Info.plist +4 -0
  16. package/corpus/templates/clips/desktop/src-tauri/src/eventkit.rs +28 -1
  17. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +2 -0
  18. package/corpus/templates/clips/desktop/src-tauri/src/native_speech.rs +48 -1
  19. package/corpus/templates/clips/desktop/src-tauri/src/permission_status.rs +3 -0
  20. package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +5 -0
  21. package/corpus/templates/clips/server/plugins/auth.ts +3 -0
  22. package/corpus/templates/content/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  23. package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +2 -1
  24. package/corpus/templates/design/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  25. package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +5 -1
  26. package/corpus/templates/design/changelog/2026-08-11-design-agent-prompts-copy-successfully-in-the-desktop-app.md +6 -0
  27. package/corpus/templates/dispatch/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  28. package/corpus/templates/dispatch/app/global.css +2 -2
  29. package/corpus/templates/dispatch/changelog/2026-08-10-desktop-workspace-sign-in.md +6 -0
  30. package/corpus/templates/dispatch/changelog/2026-08-11-dispatch-no-longer-shows-the-generic-chat-starter-among-defa.md +6 -0
  31. package/corpus/templates/dispatch/changelog/2026-08-11-integrations-and-scheduled-work-open-as-first-class-electron-pages.md +6 -0
  32. package/corpus/templates/dispatch/changelog/2026-08-11-sidebar-text-is-slightly-easier-to-read-in-light-and-dark-mo.md +6 -0
  33. package/corpus/templates/dispatch/server/lib/identity-sso.ts +57 -31
  34. package/corpus/templates/dispatch/server/plugins/feature-flags.ts +5 -0
  35. package/corpus/templates/dispatch/server/plugins/identity-sso.ts +82 -33
  36. package/corpus/templates/dispatch/server/plugins/org-apps-directory.ts +7 -15
  37. package/corpus/templates/dispatch/server/plugins/setup-dispatch.ts +9 -1
  38. package/corpus/templates/dispatch/shared/feature-flags.ts +15 -0
  39. package/corpus/templates/factory/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  40. package/corpus/templates/forms/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  41. package/corpus/templates/forms/.agents/skills/form-responses/SKILL.md +8 -7
  42. package/corpus/templates/forms/app/hooks/use-forms.ts +8 -1
  43. package/corpus/templates/forms/changelog/2026-08-11-fixed-public-forms-so-conditional-questions-stay-hidden-unti.md +6 -0
  44. package/corpus/templates/forms/server/handlers/submissions.ts +2 -1
  45. package/corpus/templates/forms/server/lib/public-form-ssr.ts +37 -2
  46. package/corpus/templates/forms/shared/page-url.ts +54 -0
  47. package/corpus/templates/forms/shared/types.ts +4 -4
  48. package/corpus/templates/mail/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  49. package/corpus/templates/plan/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  50. package/corpus/templates/slides/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  51. package/corpus/templates/slides/actions/add-slide.ts +0 -27
  52. package/corpus/templates/slides/actions/import-file.ts +28 -9
  53. package/corpus/templates/slides/actions/view-screen.ts +4 -4
  54. package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +8 -1
  55. package/corpus/templates/slides/app/components/editor/AiEditingMarker.tsx +25 -0
  56. package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +43 -48
  57. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +9 -6
  58. package/corpus/templates/slides/app/components/editor/GeneratingSlidePreview.tsx +1 -9
  59. package/corpus/templates/slides/app/components/editor/ImageDropPromptPopover.tsx +2 -0
  60. package/corpus/templates/slides/app/components/editor/ImageOverlay.tsx +0 -26
  61. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +904 -217
  62. package/corpus/templates/slides/app/components/editor/canvas-interactions/index.ts +1 -0
  63. package/corpus/templates/slides/app/components/editor/canvas-interactions/slides-canvas-adapter.ts +11 -0
  64. package/corpus/templates/slides/app/components/editor/inline-edit-session.ts +16 -0
  65. package/corpus/templates/slides/app/components/editor/slide-object-interactions.ts +22 -6
  66. package/corpus/templates/slides/app/components/editor/style-clipboard.ts +61 -0
  67. package/corpus/templates/slides/app/components/layout/Layout.tsx +26 -2
  68. package/corpus/templates/slides/app/components/ui/context-menu.tsx +1 -0
  69. package/corpus/templates/slides/app/context/DeckContext.tsx +75 -25
  70. package/corpus/templates/slides/app/global.css +1 -1
  71. package/corpus/templates/slides/app/i18n/en-US.ts +6 -1
  72. package/corpus/templates/slides/app/lib/create-deck-generation.ts +1 -1
  73. package/corpus/templates/slides/app/lib/image-drop-to-agent.ts +12 -12
  74. package/corpus/templates/slides/app/lib/slide-agent-context.ts +52 -0
  75. package/corpus/templates/slides/app/lib/slide-image-replacement.ts +61 -0
  76. package/corpus/templates/slides/app/pages/DeckEditor.tsx +55 -116
  77. package/corpus/templates/slides/app/pages/Index.tsx +28 -3
  78. package/corpus/templates/slides/changelog/2026-08-11-agent-chat-now-follows-the-current-slide-and-selection.md +6 -0
  79. package/corpus/templates/slides/changelog/2026-08-11-deck-titles-now-use-slide-content-instead-of-placeholder-filenames.md +6 -0
  80. package/corpus/templates/slides/changelog/2026-08-11-history-now-opens-from-the-deck-overflow-menu.md +6 -0
  81. package/corpus/templates/slides/changelog/2026-08-11-images-can-be-dropped-directly-onto-slides-and-resized-or-re.md +6 -0
  82. package/corpus/templates/slides/changelog/2026-08-11-new-decks-now-transition-directly-to-a-full-page-editor-load.md +6 -0
  83. package/corpus/templates/slides/changelog/2026-08-11-selected-slide-layers-can-be-nudged-precisely-with-arrow-key.md +6 -0
  84. package/corpus/templates/slides/changelog/2026-08-11-slides-can-copy-and-paste-element-styles-with-cmd-option-c-v.md +6 -0
  85. package/corpus/templates/slides/changelog/2026-08-11-slides-shows-one-compact-ai-marker-and-keeps-your-current-sl.md +6 -0
  86. package/corpus/templates/slides/changelog/2026-08-11-text-edits-preserve-slide-layout-when-no-changes-are-made.md +6 -0
  87. package/corpus/templates/slides/changelog/2026-08-11-the-style-toolbar-now-uses-the-full-available-width.md +6 -0
  88. package/corpus/templates/slides/changelog/2026-08-11-undo-and-redo-now-move-one-visible-slide-state-at-a-time.md +6 -0
  89. package/corpus/templates/slides/server/lib/chat-attachments.ts +6 -2
  90. package/corpus/templates/slides/shared/deck-title.ts +56 -0
  91. package/corpus/templates/tasks/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  92. package/dist/agent/run-store.js +6 -1
  93. package/dist/agent/thread-data-builder.js +19 -44
  94. package/dist/agent/types.d.ts +13 -0
  95. package/dist/catalog.json +1 -0
  96. package/dist/cli/create.js +1 -1
  97. package/dist/client/AssistantChat.js +58 -10
  98. package/dist/client/FileStorageSetupCard.d.ts +6 -0
  99. package/dist/client/FileStorageSetupCard.js +18 -0
  100. package/dist/client/NewWorkspaceAppFlow.js +10 -0
  101. package/dist/client/agent-chat-adapter.js +87 -11
  102. package/dist/client/agent-chat.d.ts +1 -1
  103. package/dist/client/agent-chat.js +8 -3
  104. package/dist/client/chat/attachment-adapters.js +10 -0
  105. package/dist/client/chat/message-components.d.ts +4 -1
  106. package/dist/client/chat/message-components.js +37 -13
  107. package/dist/client/chat/tool-call-display.js +14 -0
  108. package/dist/client/chat-first/apps-rail.d.ts +1 -1
  109. package/dist/client/chat-first/apps-rail.js +6 -6
  110. package/dist/client/chat-first/copy.js +1 -0
  111. package/dist/client/chat-first/types.d.ts +1 -0
  112. package/dist/client/extensions/EmbeddedExtension.js +1 -1
  113. package/dist/client/extensions/ExtensionViewer.js +1 -1
  114. package/dist/client/extensions/InlineExtensionFrame.js +1 -1
  115. package/dist/client/onboarding/FirstRunOnboarding.js +17 -2
  116. package/dist/client/settings/FileStorageSettingsForm.d.ts +2 -0
  117. package/dist/client/settings/FileStorageSettingsForm.js +113 -0
  118. package/dist/client/settings/SettingsPanel.js +2 -1
  119. package/dist/client/sse-event-processor.js +21 -0
  120. package/dist/collab/awareness.d.ts +2 -2
  121. package/dist/collab/struct-routes.d.ts +1 -1
  122. package/dist/extensions/actions.js +2 -2
  123. package/dist/extensions/html-shell.js +3 -1
  124. package/dist/file-upload/pre-upload-attachments.d.ts +15 -10
  125. package/dist/file-upload/pre-upload-attachments.js +68 -24
  126. package/dist/file-upload/registry.d.ts +4 -5
  127. package/dist/file-upload/registry.js +7 -14
  128. package/dist/file-upload/s3.d.ts +9 -0
  129. package/dist/file-upload/s3.js +168 -0
  130. package/dist/file-upload/types.d.ts +3 -3
  131. package/dist/file-upload/types.js +2 -2
  132. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  133. package/dist/notifications/routes.d.ts +6 -6
  134. package/dist/observability/routes.d.ts +3 -3
  135. package/dist/onboarding/app-profile.js +23 -1
  136. package/dist/onboarding/default-steps.js +69 -0
  137. package/dist/onboarding/types.d.ts +2 -0
  138. package/dist/progress/routes.d.ts +1 -1
  139. package/dist/provider-api/actions/custom-provider-registration.d.ts +11 -11
  140. package/dist/provider-api/actions/provider-api.d.ts +8 -8
  141. package/dist/secrets/routes.d.ts +3 -3
  142. package/dist/server/agent-chat/browser-team-tools.js +20 -0
  143. package/dist/server/agent-chat-plugin.js +12 -3
  144. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  145. package/dist/server/auth.js +45 -21
  146. package/dist/server/better-auth-instance.d.ts +2 -0
  147. package/dist/server/better-auth-instance.js +2 -2
  148. package/dist/server/core-routes-plugin.d.ts +1 -0
  149. package/dist/server/core-routes-plugin.js +26 -16
  150. package/dist/server/identity-sso-store.d.ts +3 -0
  151. package/dist/server/identity-sso-store.js +45 -0
  152. package/dist/server/identity-sso.d.ts +20 -10
  153. package/dist/server/identity-sso.js +79 -31
  154. package/dist/server/index.d.ts +1 -1
  155. package/dist/server/index.js +1 -1
  156. package/dist/server/realtime-token.d.ts +1 -1
  157. package/dist/server/transcribe-voice.d.ts +1 -1
  158. package/dist/templates/chat/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  159. package/dist/templates/default/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  160. package/dist/templates/workspace-core/.agents/skills/authentication/SKILL.md +24 -1
  161. package/dist/templates/workspace-core/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  162. package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +6 -2
  163. package/dist/templates/workspace-core/.agents/skills/extensions/references/api.md +1 -1
  164. package/dist/templates/workspace-core/.agents/skills/generative-ui/SKILL.md +3 -1
  165. package/docs/content/authentication.mdx +27 -0
  166. package/docs/content/cross-app-sso.mdx +38 -8
  167. package/docs/content/extensions.mdx +15 -15
  168. package/docs/content/file-uploads.mdx +10 -12
  169. package/docs/content/generative-ui.mdx +6 -1
  170. package/package.json +2 -2
  171. package/src/templates/chat/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  172. package/src/templates/default/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  173. package/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +24 -1
  174. package/src/templates/workspace-core/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  175. package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +6 -2
  176. package/src/templates/workspace-core/.agents/skills/extensions/references/api.md +1 -1
  177. package/src/templates/workspace-core/.agents/skills/generative-ui/SKILL.md +3 -1
@@ -31,6 +31,11 @@ let lastCheckStartedAt = 0;
31
31
 
32
32
  const UPDATE_CHECK_INTERVAL_MS = 60 * 60 * 1000;
33
33
  const UPDATE_FOCUS_CHECK_MIN_INTERVAL_MS = 15 * 60 * 1000;
34
+ const UPDATE_RETRY_DELAYS_MS = [1000, 3000];
35
+
36
+ function waitForRetry(delayMs: number) {
37
+ return new Promise<void>((resolve) => setTimeout(resolve, delayMs));
38
+ }
34
39
 
35
40
  function canRunUpdateChecks() {
36
41
  // Dev and local release builds are for testing the current checkout. Do not
@@ -43,6 +48,44 @@ function setStatus(next: UpdateStatus) {
43
48
  for (const l of listeners) l(next);
44
49
  }
45
50
 
51
+ async function runCheckAttempt(staged: string | null) {
52
+ if (!staged) setStatus({ state: "checking" });
53
+ const update = await check();
54
+ if (!update) {
55
+ // The endpoint answering "current" while a newer bundle sits staged is a
56
+ // contradiction, not a reason to drop the download. Keep it staged.
57
+ if (!staged) setStatus({ state: "not-available" });
58
+ return;
59
+ }
60
+ if (staged && update.version === staged) return;
61
+ const version = update.version;
62
+ const notes = update.body ?? undefined;
63
+ setStatus({ state: "available", version, notes });
64
+
65
+ // Download ONLY — do not install here. On macOS `install` swaps the .app
66
+ // bundle on disk while this process keeps running, which invalidates the
67
+ // running process's Screen Recording (TCC) grant and breaks capture until
68
+ // relaunch. We defer the swap to `installAndRestart()` so a downloaded-but-
69
+ // not-yet-installed update leaves the running app fully functional; the
70
+ // user records normally until they choose to restart.
71
+ let total = 0;
72
+ let downloaded = 0;
73
+ await update.download((event) => {
74
+ if (event.event === "Started") {
75
+ total = event.data.contentLength ?? 0;
76
+ downloaded = 0;
77
+ setStatus({ state: "downloading", version, notes, percent: 0 });
78
+ } else if (event.event === "Progress") {
79
+ downloaded += event.data.chunkLength;
80
+ const percent =
81
+ total > 0 ? Math.min(100, Math.round((downloaded / total) * 100)) : 0;
82
+ setStatus({ state: "downloading", version, notes, percent });
83
+ }
84
+ });
85
+ pendingUpdate = update;
86
+ setStatus({ state: "downloaded", version, notes });
87
+ }
88
+
46
89
  async function runCheck() {
47
90
  if (checkInFlight) return checkInFlight;
48
91
 
@@ -56,44 +99,16 @@ async function runCheck() {
56
99
  lastCheckStartedAt = Date.now();
57
100
  checkInFlight = (async () => {
58
101
  try {
59
- if (!staged) setStatus({ state: "checking" });
60
- const update = await check();
61
- if (!update) {
62
- // The endpoint answering "current" while a newer bundle sits staged is
63
- // a contradiction, not a reason to drop the download. Keep it staged.
64
- if (!staged) setStatus({ state: "not-available" });
65
- return;
66
- }
67
- if (staged && update.version === staged) return;
68
- pendingUpdate = update;
69
- const version = update.version;
70
- const notes = update.body ?? undefined;
71
- setStatus({ state: "available", version, notes });
72
-
73
- // Download ONLY — do not install here. On macOS `install` swaps the .app
74
- // bundle on disk while this process keeps running, which invalidates the
75
- // running process's Screen Recording (TCC) grant and breaks capture until
76
- // relaunch. We defer the swap to `installAndRestart()` so a downloaded-but-
77
- // not-yet-installed update leaves the running app fully functional; the
78
- // user records normally until they choose to restart.
79
- let total = 0;
80
- let downloaded = 0;
81
- await update.download((event) => {
82
- if (event.event === "Started") {
83
- total = event.data.contentLength ?? 0;
84
- downloaded = 0;
85
- setStatus({ state: "downloading", version, notes, percent: 0 });
86
- } else if (event.event === "Progress") {
87
- downloaded += event.data.chunkLength;
88
- const percent =
89
- total > 0
90
- ? Math.min(100, Math.round((downloaded / total) * 100))
91
- : 0;
92
- setStatus({ state: "downloading", version, notes, percent });
93
- } else if (event.event === "Finished") {
94
- setStatus({ state: "downloaded", version, notes });
102
+ for (let attempt = 0; ; attempt += 1) {
103
+ try {
104
+ await runCheckAttempt(staged);
105
+ return;
106
+ } catch (err) {
107
+ const delay = UPDATE_RETRY_DELAYS_MS[attempt];
108
+ if (delay === undefined) throw err;
109
+ await waitForRetry(delay);
95
110
  }
96
- });
111
+ }
97
112
  } catch (err) {
98
113
  const message = err instanceof Error ? err.message : String(err);
99
114
  setStatus({ state: "error", message });
@@ -1010,8 +1010,7 @@ body[data-clips-route="recording-pill"] #root {
1010
1010
  line-height: 1;
1011
1011
  }
1012
1012
 
1013
- .readiness-action svg,
1014
- .rewind-home-state svg {
1013
+ .readiness-action svg {
1015
1014
  width: 11px;
1016
1015
  height: 11px;
1017
1016
  }
@@ -1386,110 +1385,6 @@ body[data-clips-route="recording-pill"] #root {
1386
1385
  font-size: 11px;
1387
1386
  }
1388
1387
 
1389
- /* Rewind is a calm everyday status first; configuration is a drill-in. */
1390
- .rewind-home-card {
1391
- display: grid;
1392
- gap: 0;
1393
- padding: 0;
1394
- border: none;
1395
- border-radius: 0;
1396
- background: transparent;
1397
- }
1398
-
1399
- .rewind-home-summary {
1400
- display: grid;
1401
- grid-template-columns: minmax(0, 1fr) auto;
1402
- align-items: center;
1403
- gap: 8px;
1404
- width: 100%;
1405
- min-height: 36px;
1406
- padding: 7px 12px;
1407
- border: none;
1408
- border-radius: var(--radius-sm);
1409
- background: transparent;
1410
- color: var(--fg-muted);
1411
- cursor: pointer;
1412
- text-align: left;
1413
- transition: color 120ms;
1414
- }
1415
-
1416
- .rewind-home-summary:hover,
1417
- .rewind-home-summary:focus-visible {
1418
- color: var(--fg);
1419
- }
1420
-
1421
- .rewind-home-summary:focus-visible {
1422
- outline: none;
1423
- box-shadow: 0 0 0 3px var(--brand-ring);
1424
- }
1425
-
1426
- .rewind-home-title {
1427
- font-size: 13px;
1428
- font-weight: 500;
1429
- line-height: 1;
1430
- }
1431
-
1432
- .rewind-home-state {
1433
- display: inline-flex;
1434
- align-items: center;
1435
- gap: 4px;
1436
- color: inherit;
1437
- font-size: 11px;
1438
- font-weight: 600;
1439
- line-height: 1;
1440
- }
1441
-
1442
- .rewind-home-details {
1443
- display: grid;
1444
- gap: 10px;
1445
- padding: 0 12px 10px;
1446
- }
1447
-
1448
- .rewind-home-details p {
1449
- margin: 0;
1450
- color: var(--fg-muted);
1451
- font-size: 11px;
1452
- line-height: 1.4;
1453
- }
1454
-
1455
- .rewind-home-detail-actions {
1456
- display: flex;
1457
- align-items: center;
1458
- justify-content: space-between;
1459
- gap: 10px;
1460
- }
1461
-
1462
- .rewind-home-controls {
1463
- display: inline-flex;
1464
- align-items: center;
1465
- gap: 8px;
1466
- margin-left: auto;
1467
- }
1468
-
1469
- .rewind-docs-link {
1470
- display: inline-flex;
1471
- align-items: center;
1472
- gap: 4px;
1473
- padding: 0;
1474
- border: none;
1475
- background: transparent;
1476
- color: var(--fg-muted);
1477
- cursor: pointer;
1478
- font-size: 11px;
1479
- font-weight: 600;
1480
- white-space: nowrap;
1481
- }
1482
-
1483
- .rewind-docs-link:hover {
1484
- color: var(--fg);
1485
- }
1486
-
1487
- .rewind-docs-link:focus-visible {
1488
- outline: none;
1489
- box-shadow: 0 0 0 3px var(--brand-ring);
1490
- border-radius: 4px;
1491
- }
1492
-
1493
1388
  .rewind-settings-entry-main {
1494
1389
  display: grid;
1495
1390
  grid-template-columns: auto minmax(0, 1fr);
@@ -1497,7 +1392,6 @@ body[data-clips-route="recording-pill"] #root {
1497
1392
  gap: 9px;
1498
1393
  }
1499
1394
 
1500
- .rewind-home-lock-note,
1501
1395
  .rewind-capture-lock-note {
1502
1396
  margin: 0;
1503
1397
  color: var(--fg-muted);
@@ -1511,28 +1405,6 @@ body[data-clips-route="recording-pill"] #root {
1511
1405
  background: var(--surface);
1512
1406
  }
1513
1407
 
1514
- .rewind-agent-prompt-copy {
1515
- width: 28px;
1516
- height: 28px;
1517
- display: grid;
1518
- place-items: center;
1519
- padding: 0;
1520
- border: none;
1521
- border-radius: 7px;
1522
- background: transparent;
1523
- color: var(--fg-muted);
1524
- cursor: pointer;
1525
- }
1526
-
1527
- .rewind-agent-prompt-copy:hover {
1528
- color: var(--fg);
1529
- }
1530
-
1531
- .rewind-agent-prompt-copy:focus-visible {
1532
- outline: none;
1533
- box-shadow: 0 0 0 3px var(--brand-ring);
1534
- }
1535
-
1536
1408
  .rewind-home-dot {
1537
1409
  width: 9px;
1538
1410
  height: 9px;
@@ -1548,38 +1420,6 @@ body[data-clips-route="recording-pill"] #root {
1548
1420
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--status-ok) 15%, transparent);
1549
1421
  }
1550
1422
 
1551
- .rewind-home-actions {
1552
- display: grid;
1553
- grid-template-columns: repeat(3, minmax(0, 1fr));
1554
- gap: 6px;
1555
- }
1556
-
1557
- .rewind-home-actions button {
1558
- min-width: 0;
1559
- min-height: 31px;
1560
- display: inline-flex;
1561
- align-items: center;
1562
- justify-content: center;
1563
- gap: 5px;
1564
- border: 1px solid var(--border-strong);
1565
- border-radius: var(--radius-sm);
1566
- background: var(--bg);
1567
- color: var(--fg);
1568
- cursor: pointer;
1569
- font-size: 10.5px;
1570
- font-weight: 600;
1571
- }
1572
-
1573
- .rewind-home-actions button:hover:not(:disabled) {
1574
- background: var(--surface-hover);
1575
- }
1576
-
1577
- .rewind-home-actions button:disabled {
1578
- opacity: 0.42;
1579
- cursor: default;
1580
- }
1581
-
1582
- .rewind-home-actions button:focus-visible,
1583
1423
  .rewind-consent-choices button:focus-visible,
1584
1424
  .rewind-consent-primary:focus-visible,
1585
1425
  .rewind-quiet-button:focus-visible,
@@ -1594,11 +1434,6 @@ body[data-clips-route="recording-pill"] #root {
1594
1434
  box-shadow: 0 0 0 3px var(--brand-ring);
1595
1435
  }
1596
1436
 
1597
- .rewind-home-actions .icon {
1598
- width: 14px;
1599
- height: 14px;
1600
- }
1601
-
1602
1437
  .rewind-consent {
1603
1438
  min-height: 430px;
1604
1439
  align-items: center;
@@ -99,6 +99,8 @@ unicode-segmentation = "1.13"
99
99
  # Powers the "browser" voice-dictation provider (free, instant, no API
100
100
  # key, no server round-trip — same engine WisprFlow uses for offline).
101
101
  objc2-foundation = { version = "0.3", features = [
102
+ "NSBundle",
103
+ "NSDictionary",
102
104
  "NSString",
103
105
  "NSError",
104
106
  "NSLocale",
@@ -8,6 +8,10 @@
8
8
  <string>Clips uses your microphone to record audio with screen recordings.</string>
9
9
  <key>NSScreenCaptureUsageDescription</key>
10
10
  <string>Clips records your screen and system audio when you start a Clip.</string>
11
+ <key>NSCalendarsUsageDescription</key>
12
+ <string>Clips uses your calendars to show upcoming meetings and offer meeting capture.</string>
13
+ <key>NSCalendarsFullAccessUsageDescription</key>
14
+ <string>Clips uses your calendars to show upcoming meetings and offer meeting capture.</string>
11
15
  <key>NSSpeechRecognitionUsageDescription</key>
12
16
  <string>Clips uses speech recognition for on-device voice dictation.</string>
13
17
  <key>NSAccessibilityUsageDescription</key>
@@ -73,13 +73,38 @@ mod macos {
73
73
  use objc2::rc::Retained;
74
74
  use objc2::runtime::{AnyClass, AnyObject, Bool};
75
75
  use objc2::{class, msg_send};
76
- use objc2_foundation::{NSArray, NSString};
76
+ use objc2_foundation::{NSArray, NSBundle, NSString};
77
77
  use tauri::AppHandle;
78
78
 
79
79
  use super::EventKitEvent;
80
80
 
81
81
  /// EKEntityType::Event == 0
82
82
  const EK_ENTITY_TYPE_EVENT: usize = 0;
83
+ const CALENDAR_USAGE_DESCRIPTION_KEYS: [&str; 2] = [
84
+ "NSCalendarsUsageDescription",
85
+ "NSCalendarsFullAccessUsageDescription",
86
+ ];
87
+
88
+ fn has_calendar_usage_descriptions() -> bool {
89
+ let bundle = NSBundle::mainBundle();
90
+ let Some(info) = bundle.infoDictionary() else {
91
+ return false;
92
+ };
93
+ CALENDAR_USAGE_DESCRIPTION_KEYS.iter().all(|key| {
94
+ let key = NSString::from_str(key);
95
+ info.objectForKey(&*key)
96
+ .and_then(|value| value.downcast::<NSString>().ok())
97
+ .is_some_and(|value| !value.is_empty())
98
+ })
99
+ }
100
+
101
+ fn ensure_calendar_usage_descriptions() -> Result<(), String> {
102
+ if has_calendar_usage_descriptions() {
103
+ Ok(())
104
+ } else {
105
+ Err("Clips cannot access macOS calendars because the app bundle is missing calendar usage descriptions.".into())
106
+ }
107
+ }
83
108
 
84
109
  /// Returns Some(NSObject*) for the given class name, or None if the
85
110
  /// runtime doesn't know about it (e.g. the EventKit framework is not
@@ -104,6 +129,7 @@ mod macos {
104
129
  }
105
130
 
106
131
  pub async fn request_access_impl(_app: AppHandle) -> Result<bool, String> {
132
+ ensure_calendar_usage_descriptions()?;
107
133
  let store =
108
134
  unsafe { new_event_store() }.ok_or_else(|| "EventKit not available".to_string())?;
109
135
  let (tx, rx) = mpsc::sync_channel::<bool>(1);
@@ -141,6 +167,7 @@ mod macos {
141
167
  _app: AppHandle,
142
168
  within_hours: u32,
143
169
  ) -> Result<Vec<EventKitEvent>, String> {
170
+ ensure_calendar_usage_descriptions()?;
144
171
  unsafe {
145
172
  let store = new_event_store().ok_or_else(|| "EventKit not available".to_string())?;
146
173
 
@@ -553,6 +553,8 @@ pub fn run() {
553
553
  // otherwise skips Rust destructors — make sure a live
554
554
  // `screencapture` fallback child doesn't survive us.
555
555
  if let tauri::RunEvent::Exit = _event {
556
+ whisper_speech::shutdown(_app_handle);
557
+ native_speech::shutdown();
556
558
  let state = _app_handle.state::<native_screen::NativeFullscreenRecordingState>();
557
559
  native_screen::kill_active_screencapture_child(&state);
558
560
  }
@@ -144,7 +144,7 @@ pub(crate) mod macos {
144
144
  kAudioObjectPropertyElementMain, kAudioObjectPropertyScopeGlobal, kAudioObjectSystemObject,
145
145
  AudioObjectGetPropertyData, AudioObjectID, AudioObjectPropertyAddress,
146
146
  };
147
- use objc2_foundation::{NSArray, NSError, NSLocale, NSString};
147
+ use objc2_foundation::{NSArray, NSBundle, NSError, NSLocale, NSString};
148
148
  use objc2_speech::{
149
149
  SFSpeechAudioBufferRecognitionRequest, SFSpeechRecognitionResult, SFSpeechRecognitionTask,
150
150
  SFSpeechRecognizer, SFSpeechRecognizerAuthorizationStatus,
@@ -154,6 +154,29 @@ pub(crate) mod macos {
154
154
 
155
155
  use screencapturekit::audio_devices::AudioInputDevice;
156
156
 
157
+ const SPEECH_USAGE_DESCRIPTION_KEY: &str = "NSSpeechRecognitionUsageDescription";
158
+ const SPEECH_USAGE_DESCRIPTION_ERROR: &str =
159
+ "Clips cannot start macOS speech recognition because the app bundle is missing NSSpeechRecognitionUsageDescription.";
160
+
161
+ pub(crate) fn has_speech_usage_description() -> bool {
162
+ let bundle = NSBundle::mainBundle();
163
+ let Some(info) = bundle.infoDictionary() else {
164
+ return false;
165
+ };
166
+ let key = NSString::from_str(SPEECH_USAGE_DESCRIPTION_KEY);
167
+ info.objectForKey(&*key)
168
+ .and_then(|value| value.downcast::<NSString>().ok())
169
+ .is_some_and(|value| !value.is_empty())
170
+ }
171
+
172
+ fn ensure_speech_usage_description() -> Result<(), String> {
173
+ if has_speech_usage_description() {
174
+ Ok(())
175
+ } else {
176
+ Err(SPEECH_USAGE_DESCRIPTION_ERROR.into())
177
+ }
178
+ }
179
+
157
180
  /// Who owns an in-flight `SpeechSession`. Meetings fall back to this
158
181
  /// mic-only engine when whisper fails; a Fn/dictation press must not be
159
182
  /// able to silently kill a meeting's live capture (D10). Priority rule:
@@ -416,6 +439,11 @@ pub(crate) mod macos {
416
439
  /// system has an answer. The handler itself only sends a value on a
417
440
  /// channel; no ObjC interop, no UI work.
418
441
  fn ensure_authorized() -> Result<(), String> {
442
+ // macOS aborts the process, rather than returning an error, when
443
+ // Speech.framework is touched without this usage description. This
444
+ // guard keeps `tauri dev` and malformed bundles recoverable.
445
+ ensure_speech_usage_description()?;
446
+
419
447
  // Fast path: already known.
420
448
  let current = unsafe { SFSpeechRecognizer::authorizationStatus() };
421
449
  if current == SFSpeechRecognizerAuthorizationStatus::Authorized {
@@ -1548,6 +1576,20 @@ pub(crate) mod macos {
1548
1576
  Ok(())
1549
1577
  }
1550
1578
 
1579
+ pub fn shutdown() {
1580
+ let session = match session_slot().lock() {
1581
+ Ok(mut slot) => slot.take(),
1582
+ Err(poisoned) => poisoned.into_inner().take(),
1583
+ };
1584
+ if let Some(session) = session {
1585
+ session.cancelled.store(true, Ordering::SeqCst);
1586
+ session.stopped.store(true, Ordering::SeqCst);
1587
+ unsafe { session.task.cancel() };
1588
+ stop_engine_and_remove_tap(&session);
1589
+ }
1590
+ clear_warmed_raw_mic_engine();
1591
+ }
1592
+
1551
1593
  #[cfg(test)]
1552
1594
  mod tests {
1553
1595
  use super::{native_speech_voice_processing_mode, MicVoiceProcessingMode, SessionOwner};
@@ -1569,3 +1611,8 @@ pub(crate) mod macos {
1569
1611
  }
1570
1612
  }
1571
1613
  }
1614
+
1615
+ pub fn shutdown() {
1616
+ #[cfg(target_os = "macos")]
1617
+ macos::shutdown();
1618
+ }
@@ -75,6 +75,9 @@ mod macos {
75
75
  }
76
76
 
77
77
  fn check_speech() -> bool {
78
+ if !crate::native_speech::macos::has_speech_usage_description() {
79
+ return false;
80
+ }
78
81
  unsafe {
79
82
  SFSpeechRecognizer::authorizationStatus()
80
83
  == SFSpeechRecognizerAuthorizationStatus::Authorized
@@ -123,6 +123,11 @@ pub async fn whisper_transcription_stop(app: AppHandle) -> Result<(), String> {
123
123
  }
124
124
  }
125
125
 
126
+ pub fn shutdown(app: &AppHandle) {
127
+ #[cfg(target_os = "macos")]
128
+ macos::stop(app);
129
+ }
130
+
126
131
  #[tauri::command]
127
132
  pub async fn whisper_transcription_reset_timeline() -> Result<(), String> {
128
133
  #[cfg(target_os = "macos")]
@@ -5,6 +5,9 @@ import { PRERENDERED_PUBLIC_PAGE_PATHS } from "../../shared/prerendered-public-p
5
5
  // Clips has public share pages, embeds, and view-event tracking that must
6
6
  // reach unauthenticated viewers. Everything else sits behind auth.
7
7
  export default createAuthPlugin({
8
+ // Keep the native tray token and the Better Auth cookie useful between
9
+ // infrequent recording sessions without making them permanent.
10
+ maxAge: 60 * 60 * 24 * 90,
8
11
  // Clips owns `/_agent-native/google/*` so the same registered Google
9
12
  // callback can handle both normal sign-in and the Calendar connect flow.
10
13
  mountGoogleOAuthRoutes: false,
@@ -49,6 +49,34 @@ sendToAgentChat({
49
49
  });
50
50
  ```
51
51
 
52
+ ### Keep user text separate from injected context
53
+
54
+ Treat the visible `message` as the user's request or the shortest clear
55
+ description of an app-initiated operation. Put context the user did not type —
56
+ IDs, URLs, filenames, current selection or screen state, serialized records,
57
+ upload instructions, and bounded source excerpts — in the `context` field.
58
+ `context` is model input carried through the agent turn; it is stripped from
59
+ the rendered user message, so do not concatenate it into `message` with labels,
60
+ blank-line sections, or `<context>` tags yourself.
61
+
62
+ Use the related surface for each kind of supporting input:
63
+
64
+ | Surface | Use for |
65
+ | --- | --- |
66
+ | `message` | User-authored intent or a concise app action description |
67
+ | `context` | Derived metadata and bounded text needed to carry out that intent |
68
+ | `setAgentChatContextItem` | Context staged for a later user-submitted prompt; keep it keyed so updates replace stale context |
69
+ | `images`, `referenceImagePaths`, attachments | Binary or visual inputs; describe only the handling instructions in `context` |
70
+
71
+ If an app builds a prompt from a form, selection, upload, or editor state, keep
72
+ the visible message short and pass the assembled details as `context`. Preserve
73
+ the user's actual freeform text in `message` when it is the request; do not
74
+ restate it as metadata in `context` unless the agent needs a structured copy.
75
+
76
+ This boundary applies to auto-submitted turns and prefills. A hidden context
77
+ field is not a license to send unbounded records or secrets: cap excerpts,
78
+ prefer stable IDs and URLs, and use an action or resource lookup for full data.
79
+
52
80
  **From the UI, in the background:**
53
81
 
54
82
  ```ts
@@ -435,7 +435,8 @@ export function CommentsSidebar({
435
435
  ? `${t("comments.agentRegardingText", { text: thread.quotedText })}\n\n`
436
436
  : "";
437
437
  sendToAgentChat({
438
- message: `${context}${t("comments.agentThreadHeader")}\n${commentTexts}\n\n${t("comments.agentHelp")}`,
438
+ message: t("comments.agentHelp"),
439
+ context: `${context}${t("comments.agentThreadHeader")}\n${commentTexts}`,
439
440
  });
440
441
  };
441
442
 
@@ -49,6 +49,34 @@ sendToAgentChat({
49
49
  });
50
50
  ```
51
51
 
52
+ ### Keep user text separate from injected context
53
+
54
+ Treat the visible `message` as the user's request or the shortest clear
55
+ description of an app-initiated operation. Put context the user did not type —
56
+ IDs, URLs, filenames, current selection or screen state, serialized records,
57
+ upload instructions, and bounded source excerpts — in the `context` field.
58
+ `context` is model input carried through the agent turn; it is stripped from
59
+ the rendered user message, so do not concatenate it into `message` with labels,
60
+ blank-line sections, or `<context>` tags yourself.
61
+
62
+ Use the related surface for each kind of supporting input:
63
+
64
+ | Surface | Use for |
65
+ | --- | --- |
66
+ | `message` | User-authored intent or a concise app action description |
67
+ | `context` | Derived metadata and bounded text needed to carry out that intent |
68
+ | `setAgentChatContextItem` | Context staged for a later user-submitted prompt; keep it keyed so updates replace stale context |
69
+ | `images`, `referenceImagePaths`, attachments | Binary or visual inputs; describe only the handling instructions in `context` |
70
+
71
+ If an app builds a prompt from a form, selection, upload, or editor state, keep
72
+ the visible message short and pass the assembled details as `context`. Preserve
73
+ the user's actual freeform text in `message` when it is the request; do not
74
+ restate it as metadata in `context` unless the agent needs a structured copy.
75
+
76
+ This boundary applies to auto-submitted turns and prefills. A hidden context
77
+ field is not a license to send unbounded records or secrets: cap excerpts,
78
+ prefer stable IDs and URLs, and use an action or resource lookup for full data.
79
+
52
80
  **From the UI, in the background:**
53
81
 
54
82
  ```ts
@@ -555,9 +555,13 @@ export default function DesignSystemSetup() {
555
555
  }. Present a summary for review.`,
556
556
  );
557
557
 
558
+ const message =
559
+ parts[0] ?? "Set up a design system from the selected sources.";
560
+ const context = parts.slice(1).join("\n");
558
561
  openAgentSidebar();
559
562
  sendToDesignAgentChat({
560
- message: parts.join("\n"),
563
+ message,
564
+ context,
561
565
  submit: true,
562
566
  newTab: true,
563
567
  });
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-08-11
4
+ ---
5
+
6
+ Design agent prompts copy successfully in the desktop app.
@@ -49,6 +49,34 @@ sendToAgentChat({
49
49
  });
50
50
  ```
51
51
 
52
+ ### Keep user text separate from injected context
53
+
54
+ Treat the visible `message` as the user's request or the shortest clear
55
+ description of an app-initiated operation. Put context the user did not type —
56
+ IDs, URLs, filenames, current selection or screen state, serialized records,
57
+ upload instructions, and bounded source excerpts — in the `context` field.
58
+ `context` is model input carried through the agent turn; it is stripped from
59
+ the rendered user message, so do not concatenate it into `message` with labels,
60
+ blank-line sections, or `<context>` tags yourself.
61
+
62
+ Use the related surface for each kind of supporting input:
63
+
64
+ | Surface | Use for |
65
+ | --- | --- |
66
+ | `message` | User-authored intent or a concise app action description |
67
+ | `context` | Derived metadata and bounded text needed to carry out that intent |
68
+ | `setAgentChatContextItem` | Context staged for a later user-submitted prompt; keep it keyed so updates replace stale context |
69
+ | `images`, `referenceImagePaths`, attachments | Binary or visual inputs; describe only the handling instructions in `context` |
70
+
71
+ If an app builds a prompt from a form, selection, upload, or editor state, keep
72
+ the visible message short and pass the assembled details as `context`. Preserve
73
+ the user's actual freeform text in `message` when it is the request; do not
74
+ restate it as metadata in `context` unless the agent needs a structured copy.
75
+
76
+ This boundary applies to auto-submitted turns and prefills. A hidden context
77
+ field is not a license to send unbounded records or secrets: cap excerpts,
78
+ prefer stable IDs and URLs, and use an action or resource lookup for full data.
79
+
52
80
  **From the UI, in the background:**
53
81
 
54
82
  ```ts
@@ -29,7 +29,7 @@
29
29
  --ring: 0 0% 40%;
30
30
  --radius: 0.5rem;
31
31
  --sidebar-background: 0 0% 97%;
32
- --sidebar-foreground: 0 0% 45%;
32
+ --sidebar-foreground: 0 0% 43%;
33
33
  --sidebar-primary: 0 0% 15%;
34
34
  --sidebar-primary-foreground: 0 0% 100%;
35
35
  --sidebar-accent: 0 0% 95%;
@@ -60,7 +60,7 @@
60
60
  --ring: 0 0% 60%;
61
61
  --radius: 0.5rem;
62
62
  --sidebar-background: 0 0% 10%;
63
- --sidebar-foreground: 0 0% 60%;
63
+ --sidebar-foreground: 0 0% 62%;
64
64
  --sidebar-primary: 0 0% 75%;
65
65
  --sidebar-primary-foreground: 0 0% 10%;
66
66
  --sidebar-accent: 0 0% 16%;