@agent-native/core 0.90.9 → 0.90.11

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 (132) hide show
  1. package/README.md +7 -28
  2. package/corpus/README.md +1 -1
  3. package/corpus/core/CHANGELOG.md +18 -0
  4. package/corpus/core/docs/content/agent-native-toolkit.mdx +1 -1
  5. package/corpus/core/docs/content/locales/ar-SA/agent-native-toolkit.mdx +1 -1
  6. package/corpus/core/docs/content/locales/ar-SA/tracking.mdx +3 -3
  7. package/corpus/core/docs/content/locales/de-DE/agent-native-toolkit.mdx +1 -1
  8. package/corpus/core/docs/content/locales/de-DE/tracking.mdx +3 -3
  9. package/corpus/core/docs/content/locales/es-ES/agent-native-toolkit.mdx +1 -1
  10. package/corpus/core/docs/content/locales/es-ES/tracking.mdx +3 -3
  11. package/corpus/core/docs/content/locales/fr-FR/agent-native-toolkit.mdx +1 -1
  12. package/corpus/core/docs/content/locales/fr-FR/tracking.mdx +3 -3
  13. package/corpus/core/docs/content/locales/hi-IN/agent-native-toolkit.mdx +1 -1
  14. package/corpus/core/docs/content/locales/hi-IN/tracking.mdx +3 -3
  15. package/corpus/core/docs/content/locales/ja-JP/agent-native-toolkit.mdx +1 -1
  16. package/corpus/core/docs/content/locales/ja-JP/tracking.mdx +3 -3
  17. package/corpus/core/docs/content/locales/ko-KR/agent-native-toolkit.mdx +1 -1
  18. package/corpus/core/docs/content/locales/ko-KR/tracking.mdx +3 -3
  19. package/corpus/core/docs/content/locales/pt-BR/agent-native-toolkit.mdx +1 -1
  20. package/corpus/core/docs/content/locales/pt-BR/tracking.mdx +3 -3
  21. package/corpus/core/docs/content/locales/zh-CN/agent-native-toolkit.mdx +1 -1
  22. package/corpus/core/docs/content/locales/zh-CN/tracking.mdx +3 -3
  23. package/corpus/core/docs/content/locales/zh-TW/agent-native-toolkit.mdx +1 -1
  24. package/corpus/core/docs/content/locales/zh-TW/tracking.mdx +3 -3
  25. package/corpus/core/docs/content/tracking.mdx +3 -3
  26. package/corpus/core/package.json +1 -1
  27. package/corpus/core/src/agent/durable-background.ts +5 -5
  28. package/corpus/core/src/agent/engine/ai-sdk-engine.ts +40 -8
  29. package/corpus/core/src/agent/engine/anthropic-engine.ts +25 -7
  30. package/corpus/core/src/agent/engine/output-tokens.ts +94 -0
  31. package/corpus/core/src/agent/production-agent.ts +46 -7
  32. package/corpus/core/src/agent/types.ts +3 -2
  33. package/corpus/core/src/client/analytics.ts +17 -3
  34. package/corpus/core/src/client/blocks/library/wireframe-kit.tsx +3 -1
  35. package/corpus/core/src/client/blocks/library/wireframe.tsx +6 -1
  36. package/corpus/core/src/db/client.ts +12 -9
  37. package/corpus/core/src/file-upload/builder.ts +10 -23
  38. package/corpus/core/src/file-upload/types.ts +3 -3
  39. package/corpus/core/src/shared/reasoning-effort.ts +23 -0
  40. package/corpus/core/src/styles/blocks.css +7 -0
  41. package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +9 -4
  42. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +21 -7
  43. package/corpus/templates/analytics/app/entry.client.tsx +6 -1
  44. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +1 -1
  45. package/corpus/templates/analytics/changelog/2026-07-07-analytics-now-recovers-from-stale-route-chunks-after-deploys.md +6 -0
  46. package/corpus/templates/analytics/changelog/2026-07-07-metric-dashboard-cards-now-show-warehouse-backed-numeric-val.md +6 -0
  47. package/corpus/templates/clips/actions/create-recording.ts +1 -1
  48. package/corpus/templates/clips/actions/finalize-recording.ts +2 -2
  49. package/corpus/templates/clips/actions/import-loom-recording.ts +1 -1
  50. package/corpus/templates/clips/actions/lib/ensure-seekable-video.ts +1 -1
  51. package/corpus/templates/clips/actions/regenerate-chapters.ts +7 -0
  52. package/corpus/templates/clips/app/hooks/use-auto-title.ts +15 -5
  53. package/corpus/templates/clips/app/routes/download.tsx +12 -4
  54. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +1 -0
  55. package/corpus/templates/clips/changelog/2026-07-07-auto-generated-chapter-requests-now-open-the-matching-agent-.md +6 -0
  56. package/corpus/templates/clips/changelog/2026-07-07-clip-video-uploads-now-compress-in-the-background-while-keeping.md +6 -0
  57. package/corpus/templates/clips/changelog/2026-07-07-desktop-settings-and-fn-dictation-are-more-reliable.md +6 -0
  58. package/corpus/templates/clips/changelog/2026-07-07-desktop-settings-now-includes-a-manual-update-check-with-res.md +6 -0
  59. package/corpus/templates/clips/changelog/2026-07-07-the-clips-desktop-download-page-now-uses-the-agent-native-ap.md +6 -0
  60. package/corpus/templates/clips/desktop/src/app.tsx +95 -8
  61. package/corpus/templates/clips/desktop/src/lib/updater.ts +60 -43
  62. package/corpus/templates/clips/desktop/src/styles.css +5 -0
  63. package/corpus/templates/clips/desktop/src-tauri/src/shortcuts.rs +93 -23
  64. package/corpus/templates/dispatch/changelog/2026-07-07-overview-is-now-just-ask-dispatch-and-a-list-of-apps.md +6 -0
  65. package/dist/agent/durable-background.d.ts +1 -1
  66. package/dist/agent/durable-background.d.ts.map +1 -1
  67. package/dist/agent/durable-background.js +5 -3
  68. package/dist/agent/durable-background.js.map +1 -1
  69. package/dist/agent/engine/ai-sdk-engine.d.ts.map +1 -1
  70. package/dist/agent/engine/ai-sdk-engine.js +24 -4
  71. package/dist/agent/engine/ai-sdk-engine.js.map +1 -1
  72. package/dist/agent/engine/anthropic-engine.d.ts.map +1 -1
  73. package/dist/agent/engine/anthropic-engine.js +13 -3
  74. package/dist/agent/engine/anthropic-engine.js.map +1 -1
  75. package/dist/agent/engine/output-tokens.d.ts +27 -0
  76. package/dist/agent/engine/output-tokens.d.ts.map +1 -1
  77. package/dist/agent/engine/output-tokens.js +70 -0
  78. package/dist/agent/engine/output-tokens.js.map +1 -1
  79. package/dist/agent/production-agent.d.ts.map +1 -1
  80. package/dist/agent/production-agent.js +41 -7
  81. package/dist/agent/production-agent.js.map +1 -1
  82. package/dist/agent/types.d.ts +3 -2
  83. package/dist/agent/types.d.ts.map +1 -1
  84. package/dist/agent/types.js.map +1 -1
  85. package/dist/client/analytics.d.ts.map +1 -1
  86. package/dist/client/analytics.js +14 -3
  87. package/dist/client/analytics.js.map +1 -1
  88. package/dist/client/blocks/library/wireframe-kit.d.ts +1 -0
  89. package/dist/client/blocks/library/wireframe-kit.d.ts.map +1 -1
  90. package/dist/client/blocks/library/wireframe-kit.js +2 -1
  91. package/dist/client/blocks/library/wireframe-kit.js.map +1 -1
  92. package/dist/client/blocks/library/wireframe.d.ts.map +1 -1
  93. package/dist/client/blocks/library/wireframe.js +6 -1
  94. package/dist/client/blocks/library/wireframe.js.map +1 -1
  95. package/dist/db/client.d.ts.map +1 -1
  96. package/dist/db/client.js +12 -7
  97. package/dist/db/client.js.map +1 -1
  98. package/dist/file-upload/builder.d.ts.map +1 -1
  99. package/dist/file-upload/builder.js +9 -19
  100. package/dist/file-upload/builder.js.map +1 -1
  101. package/dist/file-upload/types.d.ts +3 -3
  102. package/dist/file-upload/types.d.ts.map +1 -1
  103. package/dist/file-upload/types.js.map +1 -1
  104. package/dist/secrets/routes.d.ts +6 -6
  105. package/dist/shared/reasoning-effort.d.ts +1 -0
  106. package/dist/shared/reasoning-effort.d.ts.map +1 -1
  107. package/dist/shared/reasoning-effort.js +18 -0
  108. package/dist/shared/reasoning-effort.js.map +1 -1
  109. package/dist/styles/blocks.css +7 -0
  110. package/docs/content/agent-native-toolkit.mdx +1 -1
  111. package/docs/content/locales/ar-SA/agent-native-toolkit.mdx +1 -1
  112. package/docs/content/locales/ar-SA/tracking.mdx +3 -3
  113. package/docs/content/locales/de-DE/agent-native-toolkit.mdx +1 -1
  114. package/docs/content/locales/de-DE/tracking.mdx +3 -3
  115. package/docs/content/locales/es-ES/agent-native-toolkit.mdx +1 -1
  116. package/docs/content/locales/es-ES/tracking.mdx +3 -3
  117. package/docs/content/locales/fr-FR/agent-native-toolkit.mdx +1 -1
  118. package/docs/content/locales/fr-FR/tracking.mdx +3 -3
  119. package/docs/content/locales/hi-IN/agent-native-toolkit.mdx +1 -1
  120. package/docs/content/locales/hi-IN/tracking.mdx +3 -3
  121. package/docs/content/locales/ja-JP/agent-native-toolkit.mdx +1 -1
  122. package/docs/content/locales/ja-JP/tracking.mdx +3 -3
  123. package/docs/content/locales/ko-KR/agent-native-toolkit.mdx +1 -1
  124. package/docs/content/locales/ko-KR/tracking.mdx +3 -3
  125. package/docs/content/locales/pt-BR/agent-native-toolkit.mdx +1 -1
  126. package/docs/content/locales/pt-BR/tracking.mdx +3 -3
  127. package/docs/content/locales/zh-CN/agent-native-toolkit.mdx +1 -1
  128. package/docs/content/locales/zh-CN/tracking.mdx +3 -3
  129. package/docs/content/locales/zh-TW/agent-native-toolkit.mdx +1 -1
  130. package/docs/content/locales/zh-TW/tracking.mdx +3 -3
  131. package/docs/content/tracking.mdx +3 -3
  132. package/package.json +1 -1
@@ -81,7 +81,14 @@ import {
81
81
  saveBool,
82
82
  saveString,
83
83
  } from "./lib/storage";
84
- import { installAndRestart, isUpdatePendingRestart } from "./lib/updater";
84
+ import {
85
+ canCheckForUpdates,
86
+ installAndRestart,
87
+ isUpdatePendingRestart,
88
+ retryUpdateCheck,
89
+ useUpdateStatus,
90
+ type UpdateStatus,
91
+ } from "./lib/updater";
85
92
  import { normalizeServerUrl } from "./lib/url";
86
93
  import {
87
94
  installDesktopVoiceDictation,
@@ -3521,6 +3528,25 @@ function formatStorageBytes(bytes: number): string {
3521
3528
  return `${Math.max(1, Math.round(mb))} MB`;
3522
3529
  }
3523
3530
 
3531
+ function desktopUpdateStatusText(status: UpdateStatus): string {
3532
+ switch (status.state) {
3533
+ case "idle":
3534
+ return "Clips checks automatically after launch and every 4 hours.";
3535
+ case "checking":
3536
+ return "Checking for updates...";
3537
+ case "not-available":
3538
+ return "Clips is up to date.";
3539
+ case "available":
3540
+ return `Update ${status.version} found. Downloading now...`;
3541
+ case "downloading":
3542
+ return `Downloading update ${status.version}... ${status.percent}%`;
3543
+ case "downloaded":
3544
+ return `Update ${status.version} is ready. Restart to install it.`;
3545
+ case "error":
3546
+ return `Update check failed: ${status.message}`;
3547
+ }
3548
+ }
3549
+
3524
3550
  function Setup({
3525
3551
  initial,
3526
3552
  serverUrl,
@@ -3568,8 +3594,8 @@ function Setup({
3568
3594
  }) {
3569
3595
  const [url, setUrl] = useState(initial ?? DEFAULT_URL);
3570
3596
  const [readinessOpen, setReadinessOpen] = useState(false);
3571
- const inputRef = useRef<HTMLInputElement | null>(null);
3572
3597
  const featureConfig = useFeatureConfig();
3598
+ const updateStatus = useUpdateStatus();
3573
3599
  const voiceEnabled = featureConfig?.voiceEnabled !== false;
3574
3600
  const meetingsEnabled = featureConfig?.meetingsEnabled !== false;
3575
3601
  const launchAtLoginEnabled = featureConfig?.launchAtLoginEnabled !== false;
@@ -3868,10 +3894,6 @@ function Setup({
3868
3894
  );
3869
3895
  }
3870
3896
 
3871
- useEffect(() => {
3872
- inputRef.current?.focus();
3873
- }, []);
3874
-
3875
3897
  useEffect(() => {
3876
3898
  let cancelled = false;
3877
3899
  const refresh = () => {
@@ -4134,6 +4156,32 @@ function Setup({
4134
4156
  if (providerStatus[byokProvider]) return null;
4135
4157
  return `${keyForByokProvider(byokProvider)} is not set — cleanup will fail until configured.`;
4136
4158
  })();
4159
+ const updateChecksSupported = canCheckForUpdates();
4160
+ const updateBusy =
4161
+ updateStatus.state === "checking" ||
4162
+ updateStatus.state === "available" ||
4163
+ updateStatus.state === "downloading";
4164
+ const updateReady = updateStatus.state === "downloaded";
4165
+ const updateStatusClass =
4166
+ updateStatus.state === "error"
4167
+ ? "setup-warning"
4168
+ : updateStatus.state === "downloaded" ||
4169
+ updateStatus.state === "not-available"
4170
+ ? "setup-success"
4171
+ : "setup-hint";
4172
+ const updateCheckLabel = !updateChecksSupported
4173
+ ? "Release builds only"
4174
+ : updateStatus.state === "checking"
4175
+ ? "Checking..."
4176
+ : updateStatus.state === "downloading"
4177
+ ? `Downloading ${updateStatus.percent}%`
4178
+ : "Check now";
4179
+
4180
+ function checkForDesktopUpdate() {
4181
+ retryUpdateCheck().catch((err) => {
4182
+ console.error("[clips-updater] manual check failed:", err);
4183
+ });
4184
+ }
4137
4185
 
4138
4186
  return (
4139
4187
  <form className="setup" onSubmit={handleSubmit}>
@@ -4161,13 +4209,12 @@ function Setup({
4161
4209
  />
4162
4210
  <input
4163
4211
  id="clips-url"
4164
- ref={inputRef}
4165
4212
  type="url"
4166
4213
  value={url}
4167
4214
  onChange={(e) => setUrl(e.target.value)}
4168
4215
  placeholder="http://localhost:8080"
4169
4216
  />
4170
- <button className="primary" type="submit">
4217
+ <button className="secondary setup-connect-button" type="submit">
4171
4218
  Connect
4172
4219
  </button>
4173
4220
  </div>
@@ -4214,6 +4261,46 @@ function Setup({
4214
4261
  </div>
4215
4262
  </div>
4216
4263
 
4264
+ <div className="setup-section">
4265
+ <SettingLabel
4266
+ label="Desktop updates"
4267
+ hint="Check the signed Clips desktop release channel and download any available update."
4268
+ />
4269
+ <div className="setup-button-row">
4270
+ <button
4271
+ type="button"
4272
+ className="secondary"
4273
+ onClick={checkForDesktopUpdate}
4274
+ disabled={!updateChecksSupported || updateBusy || updateReady}
4275
+ >
4276
+ <IconRefresh
4277
+ size={15}
4278
+ stroke={1.9}
4279
+ className={updateBusy ? "update-spinner" : undefined}
4280
+ />
4281
+ {updateCheckLabel}
4282
+ </button>
4283
+ {updateReady ? (
4284
+ <button
4285
+ type="button"
4286
+ className="secondary"
4287
+ onClick={() => {
4288
+ installAndRestart().catch((err) => {
4289
+ console.error("[clips-updater] relaunch failed:", err);
4290
+ });
4291
+ }}
4292
+ >
4293
+ Restart to install
4294
+ </button>
4295
+ ) : null}
4296
+ </div>
4297
+ <p className={updateStatusClass}>
4298
+ {updateChecksSupported
4299
+ ? desktopUpdateStatusText(updateStatus)
4300
+ : "Update checks are available in signed release builds."}
4301
+ </p>
4302
+ </div>
4303
+
4217
4304
  <div className="setup-section-heading">Permissions</div>
4218
4305
 
4219
4306
  <div className="setup-section">
@@ -26,6 +26,13 @@ let cachedStatus: UpdateStatus = { state: "idle" };
26
26
  let pendingUpdate: Update | null = null;
27
27
  const listeners = new Set<StatusListener>();
28
28
  let started = false;
29
+ let checkInFlight: Promise<void> | null = null;
30
+
31
+ function canRunUpdateChecks() {
32
+ // Dev and local release builds are for testing the current checkout. Do not
33
+ // replace them with the published auto-update channel.
34
+ return !import.meta.env.DEV && !__CLIPS_DESKTOP_LOCAL_BUILD__;
35
+ }
29
36
 
30
37
  function setStatus(next: UpdateStatus) {
31
38
  cachedStatus = next;
@@ -33,55 +40,60 @@ function setStatus(next: UpdateStatus) {
33
40
  }
34
41
 
35
42
  async function runCheck() {
36
- try {
37
- setStatus({ state: "checking" });
38
- const update = await check();
39
- if (!update) {
40
- setStatus({ state: "not-available" });
41
- return;
42
- }
43
- pendingUpdate = update;
44
- const version = update.version;
45
- const notes = update.body ?? undefined;
46
- setStatus({ state: "available", version, notes });
47
-
48
- // Download ONLY — do not install here. On macOS `install` swaps the .app
49
- // bundle on disk while this process keeps running, which invalidates the
50
- // running process's Screen Recording (TCC) grant and breaks capture until
51
- // relaunch. We defer the swap to `installAndRestart()` so a downloaded-but-
52
- // not-yet-installed update leaves the running app fully functional; the
53
- // user records normally until they choose to restart.
54
- let total = 0;
55
- let downloaded = 0;
56
- await update.download((event) => {
57
- if (event.event === "Started") {
58
- total = event.data.contentLength ?? 0;
59
- downloaded = 0;
60
- setStatus({ state: "downloading", version, notes, percent: 0 });
61
- } else if (event.event === "Progress") {
62
- downloaded += event.data.chunkLength;
63
- const percent =
64
- total > 0 ? Math.min(100, Math.round((downloaded / total) * 100)) : 0;
65
- setStatus({ state: "downloading", version, notes, percent });
66
- } else if (event.event === "Finished") {
67
- setStatus({ state: "downloaded", version, notes });
43
+ if (checkInFlight) return checkInFlight;
44
+
45
+ checkInFlight = (async () => {
46
+ try {
47
+ setStatus({ state: "checking" });
48
+ const update = await check();
49
+ if (!update) {
50
+ setStatus({ state: "not-available" });
51
+ return;
68
52
  }
69
- });
70
- } catch (err) {
71
- const message = err instanceof Error ? err.message : String(err);
72
- setStatus({ state: "error", message });
73
- }
53
+ pendingUpdate = update;
54
+ const version = update.version;
55
+ const notes = update.body ?? undefined;
56
+ setStatus({ state: "available", version, notes });
57
+
58
+ // Download ONLY — do not install here. On macOS `install` swaps the .app
59
+ // bundle on disk while this process keeps running, which invalidates the
60
+ // running process's Screen Recording (TCC) grant and breaks capture until
61
+ // relaunch. We defer the swap to `installAndRestart()` so a downloaded-but-
62
+ // not-yet-installed update leaves the running app fully functional; the
63
+ // user records normally until they choose to restart.
64
+ let total = 0;
65
+ let downloaded = 0;
66
+ await update.download((event) => {
67
+ if (event.event === "Started") {
68
+ total = event.data.contentLength ?? 0;
69
+ downloaded = 0;
70
+ setStatus({ state: "downloading", version, notes, percent: 0 });
71
+ } else if (event.event === "Progress") {
72
+ downloaded += event.data.chunkLength;
73
+ const percent =
74
+ total > 0
75
+ ? Math.min(100, Math.round((downloaded / total) * 100))
76
+ : 0;
77
+ setStatus({ state: "downloading", version, notes, percent });
78
+ } else if (event.event === "Finished") {
79
+ setStatus({ state: "downloaded", version, notes });
80
+ }
81
+ });
82
+ } catch (err) {
83
+ const message = err instanceof Error ? err.message : String(err);
84
+ setStatus({ state: "error", message });
85
+ } finally {
86
+ checkInFlight = null;
87
+ }
88
+ })();
89
+
90
+ return checkInFlight;
74
91
  }
75
92
 
76
93
  function startUpdateLoop() {
77
94
  if (started) return;
78
95
  started = true;
79
- // Skip update checks in dev — there's no release endpoint to check.
80
- if (import.meta.env.DEV) return;
81
- // Local release builds are for testing the current checkout. Do not replace
82
- // them with the published auto-update channel just because package.json has
83
- // a lower development version.
84
- if (__CLIPS_DESKTOP_LOCAL_BUILD__) return;
96
+ if (!canRunUpdateChecks()) return;
85
97
  // Check 3s after launch (let the popover finish first paint), then every
86
98
  // 4 hours. Matches the cadence used by the Electron app.
87
99
  setTimeout(runCheck, 3000);
@@ -125,11 +137,16 @@ export function isUpdatePendingRestart(): boolean {
125
137
  return cachedStatus.state === "downloaded";
126
138
  }
127
139
 
140
+ export function canCheckForUpdates(): boolean {
141
+ return canRunUpdateChecks();
142
+ }
143
+
128
144
  /**
129
145
  * Manual retry entry point. Used by the UpdateBanner's error state to let
130
146
  * users re-attempt after a signature-verification / download / network
131
147
  * failure. Runs a full check + download pass, same as the periodic loop.
132
148
  */
133
149
  export function retryUpdateCheck(): Promise<void> {
150
+ if (!canRunUpdateChecks()) return Promise.resolve();
134
151
  return runCheck();
135
152
  }
@@ -835,6 +835,11 @@ body[data-clips-route="recording-pill"] #root {
835
835
  cursor: not-allowed;
836
836
  }
837
837
 
838
+ .setup-connect-button {
839
+ height: 38px;
840
+ margin-top: 2px;
841
+ }
842
+
838
843
  .primary.start {
839
844
  margin-top: 2px;
840
845
  }
@@ -624,6 +624,32 @@ fn ensure_fn_event_tap(app: tauri::AppHandle) {
624
624
  install_fn_event_tap(app);
625
625
  }
626
626
 
627
+ #[cfg(target_os = "macos")]
628
+ fn fn_event_tap_is_enabled(tap: &core_graphics::event::CGEventTap<'static>) -> bool {
629
+ use core_foundation::base::TCFType;
630
+
631
+ extern "C" {
632
+ fn CGEventTapIsEnabled(tap: core_foundation::mach_port::CFMachPortRef) -> bool;
633
+ }
634
+
635
+ unsafe { CGEventTapIsEnabled(tap.mach_port().as_concrete_TypeRef()) }
636
+ }
637
+
638
+ #[cfg(target_os = "macos")]
639
+ fn schedule_fn_event_tap_restart(app: tauri::AppHandle, reason: &'static str, delay: Duration) {
640
+ eprintln!("[clips-tray][fn-tap] restarting Fn event tap: {reason}");
641
+ FN_TAP_INSTALL_STARTED.store(false, Ordering::SeqCst);
642
+ if !FN_TAP_ENABLED.load(Ordering::SeqCst) {
643
+ return;
644
+ }
645
+ thread::spawn(move || {
646
+ thread::sleep(delay);
647
+ if FN_TAP_ENABLED.load(Ordering::SeqCst) {
648
+ ensure_fn_event_tap(app);
649
+ }
650
+ });
651
+ }
652
+
627
653
  #[cfg(target_os = "macos")]
628
654
  fn install_fn_event_tap(app: tauri::AppHandle) {
629
655
  use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
@@ -631,7 +657,9 @@ fn install_fn_event_tap(app: tauri::AppHandle) {
631
657
  use std::thread;
632
658
  use std::time::Duration;
633
659
 
634
- use core_foundation::runloop::{kCFRunLoopCommonModes, CFRunLoop};
660
+ use core_foundation::runloop::{
661
+ kCFRunLoopCommonModes, kCFRunLoopDefaultMode, CFRunLoop, CFRunLoopRunResult,
662
+ };
635
663
  use core_graphics::event::{
636
664
  CGEventFlags, CGEventTap, CGEventTapLocation, CGEventTapOptions, CGEventTapPlacement,
637
665
  CGEventType, CallbackResult,
@@ -836,7 +864,6 @@ fn install_fn_event_tap(app: tauri::AppHandle) {
836
864
  t
837
865
  }
838
866
  Err(()) => {
839
- FN_TAP_INSTALL_STARTED.store(false, Ordering::SeqCst);
840
867
  eprintln!(
841
868
  "[clips-tray][fn-tap] CGEventTapCreate returned NULL. Most likely cause: \
842
869
  Input Monitoring is not granted to Clips. Open System Settings → \
@@ -844,6 +871,11 @@ fn install_fn_event_tap(app: tauri::AppHandle) {
844
871
  terminal running `tauri dev`). Note: Accessibility is a separate \
845
872
  permission and is not sufficient for ListenOnly taps."
846
873
  );
874
+ schedule_fn_event_tap_restart(
875
+ app,
876
+ "CGEventTapCreate returned NULL",
877
+ Duration::from_secs(5),
878
+ );
847
879
  return;
848
880
  }
849
881
  };
@@ -853,8 +885,12 @@ fn install_fn_event_tap(app: tauri::AppHandle) {
853
885
  s
854
886
  }
855
887
  Err(()) => {
856
- FN_TAP_INSTALL_STARTED.store(false, Ordering::SeqCst);
857
888
  eprintln!("[clips-tray][fn-tap] CFMachPortCreateRunLoopSource failed");
889
+ schedule_fn_event_tap_restart(
890
+ app,
891
+ "CFMachPortCreateRunLoopSource failed",
892
+ Duration::from_secs(2),
893
+ );
858
894
  return;
859
895
  }
860
896
  };
@@ -865,29 +901,63 @@ fn install_fn_event_tap(app: tauri::AppHandle) {
865
901
  "[clips-tray][fn-tap] tap enabled; entering runloop — press Fn now to test"
866
902
  );
867
903
 
868
- // Run the runloop in repeated short bursts so we can re-enable
869
- // the tap if the OS disables it. We use run_current (blocks
870
- // until something stops the loop) and re-enter on exit. The
871
- // disable callbacks above call CFRunLoop::stop, which makes
872
- // run_current return; we then call tap.enable() and re-enter.
873
- // This is the handy-keys / linespeed pattern.
904
+ // Run the runloop in short slices instead of `run_current()`.
905
+ // macOS can leave a tap created but disabled/inert after TCC or
906
+ // user-input churn; periodic health checks let us re-enable or
907
+ // rebuild it even when no further callback arrives.
908
+ let mut consecutive_reenable_failures = 0_u8;
874
909
  loop {
875
- if needs_reenable.swap(false, Ordering::SeqCst) {
876
- dlog!("[clips-tray] re-enabling Fn event tap");
910
+ if !FN_TAP_ENABLED.load(Ordering::SeqCst) {
911
+ FN_TAP_INSTALL_STARTED.store(false, Ordering::SeqCst);
912
+ return;
913
+ }
914
+
915
+ let reenable_reason = if needs_reenable.swap(false, Ordering::SeqCst) {
916
+ Some("disabled callback")
917
+ } else if !fn_event_tap_is_enabled(&tap) {
918
+ Some("health check")
919
+ } else {
920
+ None
921
+ };
922
+
923
+ if let Some(reason) = reenable_reason {
924
+ eprintln!("[clips-tray][fn-tap] re-enabling Fn event tap ({reason})");
877
925
  tap.enable();
926
+ thread::sleep(Duration::from_millis(20));
927
+ if fn_event_tap_is_enabled(&tap) {
928
+ consecutive_reenable_failures = 0;
929
+ } else {
930
+ consecutive_reenable_failures =
931
+ consecutive_reenable_failures.saturating_add(1);
932
+ if consecutive_reenable_failures >= 2 {
933
+ schedule_fn_event_tap_restart(
934
+ app.clone(),
935
+ "tapEnable did not stick",
936
+ Duration::from_millis(750),
937
+ );
938
+ return;
939
+ }
940
+ }
878
941
  }
879
- CFRunLoop::run_current();
880
- // run_current returned — either we asked it to (a disable
881
- // event flagged needs_reenable above), or something else
882
- // removed our source. In the latter case avoid a tight
883
- // spin, then re-call enable() defensively (it's idempotent
884
- // — calling on an already-enabled tap is a no-op).
885
- if !needs_reenable.load(Ordering::SeqCst) {
886
- thread::sleep(Duration::from_millis(50));
887
- dlog!(
888
- "[clips-tray] Fn runloop exited unexpectedly — re-enabling tap"
889
- );
890
- needs_reenable.store(true, Ordering::SeqCst);
942
+
943
+ match unsafe {
944
+ CFRunLoop::run_in_mode(
945
+ kCFRunLoopDefaultMode,
946
+ Duration::from_millis(500),
947
+ true,
948
+ )
949
+ } {
950
+ CFRunLoopRunResult::Finished => {
951
+ schedule_fn_event_tap_restart(
952
+ app.clone(),
953
+ "runloop finished",
954
+ Duration::from_millis(750),
955
+ );
956
+ return;
957
+ }
958
+ CFRunLoopRunResult::Stopped
959
+ | CFRunLoopRunResult::TimedOut
960
+ | CFRunLoopRunResult::HandledSource => {}
891
961
  }
892
962
  }
893
963
  })
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-07-07
4
+ ---
5
+
6
+ Overview is now just Ask Dispatch and a list of apps you can open.
@@ -103,7 +103,7 @@ export declare function isHostedRuntimeForDurableBackground(): boolean;
103
103
  */
104
104
  export declare function isInBackgroundFunctionRuntime(): boolean;
105
105
  export declare function backgroundRunMarkerExpectsBackgroundRuntime(marker: unknown): boolean;
106
- export declare function shouldUseBackgroundFunctionTimeoutForWorker(marker: unknown): boolean;
106
+ export declare function shouldUseBackgroundFunctionTimeoutForWorker(_marker: unknown): boolean;
107
107
  export declare function backgroundRuntimeDiagnosticDetail(marker: unknown): string;
108
108
  /**
109
109
  * The single gate. True when the env flag is explicitly enabled, or a workspace
@@ -1 +1 @@
1
- {"version":3,"file":"durable-background.d.ts","sourceRoot":"","sources":["../../src/agent/durable-background.ts"],"names":[],"mappings":"AAgDA;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,2CACE,CAAC;AAE3C;;;;;;;;GAQG;AACH,eAAO,MAAM,8BAA8B,4BAA4B,CAAC;AAExE;;;;;;;;;GASG;AACH,eAAO,MAAM,kCAAkC,gDAA0D,CAAC;AAiC1G;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,sCAAsC,IAAI,MAAM,CAQ/D;AAED,wBAAgB,4CAA4C,CAC1D,YAAY,EAAE,MAAM,GACnB,OAAO,CAET;AAED;;;GAGG;AACH,eAAO,MAAM,iCAAiC,kCACb,CAAC;AAElC;;;;;;GAMG;AACH,eAAO,MAAM,+BAA+B,oBAAoB,CAAC;AAEjE;;;;GAIG;AACH,wBAAgB,mCAAmC,IAAI,OAAO,CAsB7D;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,6BAA6B,IAAI,OAAO,CAsBvD;AAED,wBAAgB,2CAA2C,CACzD,MAAM,EAAE,OAAO,GACd,OAAO,CAOT;AAED,wBAAgB,2CAA2C,CACzD,MAAM,EAAE,OAAO,GACd,OAAO,CAKT;AAED,wBAAgB,iCAAiC,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAQzE;AAwCD;;;;;;;GAOG;AACH,wBAAgB,mCAAmC,CAAC,OAAO,CAAC,EAAE;IAC5D,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,GAAG,OAAO,CAWV;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,oCAAoC,qCACb,CAAC;AAiBrC;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,qCAAqC,IAAI,OAAO,CAM/D;AAED,uDAAuD;AACvD,MAAM,MAAM,qBAAqB,GAC7B;IACE,EAAE,EAAE,IAAI,CAAC;IACT,+DAA+D;IAC/D,KAAK,EAAE,MAAM,CAAC;IACd,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B,GACD;IACE,EAAE,EAAE,KAAK,CAAC;IACV,2CAA2C;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAC;IACd;;;;;;OAMG;IACH,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,CAAC;AAEN;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAahE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,OAAO,EACb,UAAU,EAAE,MAAM,GAAG,SAAS,GAC7B,qBAAqB,CAiDvB"}
1
+ {"version":3,"file":"durable-background.d.ts","sourceRoot":"","sources":["../../src/agent/durable-background.ts"],"names":[],"mappings":"AAgDA;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,2CACE,CAAC;AAE3C;;;;;;;;GAQG;AACH,eAAO,MAAM,8BAA8B,4BAA4B,CAAC;AAExE;;;;;;;;;GASG;AACH,eAAO,MAAM,kCAAkC,gDAA0D,CAAC;AAiC1G;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,sCAAsC,IAAI,MAAM,CAQ/D;AAED,wBAAgB,4CAA4C,CAC1D,YAAY,EAAE,MAAM,GACnB,OAAO,CAET;AAED;;;GAGG;AACH,eAAO,MAAM,iCAAiC,kCACb,CAAC;AAElC;;;;;;GAMG;AACH,eAAO,MAAM,+BAA+B,oBAAoB,CAAC;AAEjE;;;;GAIG;AACH,wBAAgB,mCAAmC,IAAI,OAAO,CAsB7D;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,6BAA6B,IAAI,OAAO,CAsBvD;AAED,wBAAgB,2CAA2C,CACzD,MAAM,EAAE,OAAO,GACd,OAAO,CAOT;AAED,wBAAgB,2CAA2C,CACzD,OAAO,EAAE,OAAO,GACf,OAAO,CAKT;AAED,wBAAgB,iCAAiC,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAQzE;AAwCD;;;;;;;GAOG;AACH,wBAAgB,mCAAmC,CAAC,OAAO,CAAC,EAAE;IAC5D,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,GAAG,OAAO,CAWV;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,oCAAoC,qCACb,CAAC;AAiBrC;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,qCAAqC,IAAI,OAAO,CAM/D;AAED,uDAAuD;AACvD,MAAM,MAAM,qBAAqB,GAC7B;IACE,EAAE,EAAE,IAAI,CAAC;IACT,+DAA+D;IAC/D,KAAK,EAAE,MAAM,CAAC;IACd,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B,GACD;IACE,EAAE,EAAE,KAAK,CAAC;IACV,2CAA2C;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAC;IACd;;;;;;OAMG;IACH,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,CAAC;AAEN;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAahE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,OAAO,EACb,UAAU,EAAE,MAAM,GAAG,SAAS,GAC7B,qBAAqB,CAiDvB"}
@@ -225,9 +225,11 @@ export function backgroundRunMarkerExpectsBackgroundRuntime(marker) {
225
225
  marker
226
226
  .backgroundFunctionRuntimeExpected === true);
227
227
  }
228
- export function shouldUseBackgroundFunctionTimeoutForWorker(marker) {
229
- return (isInBackgroundFunctionRuntime() ||
230
- backgroundRunMarkerExpectsBackgroundRuntime(marker));
228
+ export function shouldUseBackgroundFunctionTimeoutForWorker(_marker) {
229
+ // The dispatch marker says which URL the foreground targeted, not where the
230
+ // request actually landed. Only the worker runtime proof can safely lift the
231
+ // hosted 40s clamp to the 15-minute background-function budget.
232
+ return isInBackgroundFunctionRuntime();
231
233
  }
232
234
  export function backgroundRuntimeDiagnosticDetail(marker) {
233
235
  return [