@agent-native/core 0.144.1 → 0.144.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/templates/analytics/app/pages/DataSources.tsx +28 -4
- package/corpus/templates/analytics/server/jobs/dashboard-report.ts +7 -3
- package/corpus/templates/analytics/server/plugins/dashboard-report-jobs.ts +13 -3
- package/corpus/templates/calendar/app/components/calendar/GoogleConnectBanner.tsx +64 -23
- package/corpus/templates/calendar/app/hooks/use-google-auth.ts +69 -46
- package/corpus/templates/calendar/app/hooks/use-view-preferences.ts +35 -6
- package/corpus/templates/calendar/app/hooks/use-zoom-auth.ts +24 -3
- package/corpus/templates/clips/app/components/player/transcript-panel.tsx +45 -6
- package/corpus/templates/clips/app/components/recorder/storage-setup-card.tsx +38 -5
- package/corpus/templates/clips/desktop/src/app.tsx +146 -45
- package/corpus/templates/clips/server/jobs/brain-export.ts +13 -6
- package/corpus/templates/clips/server/jobs/builder-media-compression.ts +10 -3
- package/corpus/templates/clips/server/jobs/meeting-reminders.ts +10 -3
- package/corpus/templates/clips/server/jobs/poll-calendars.ts +23 -21
- package/corpus/templates/clips/server/jobs/stale-meeting-sweeper.ts +10 -3
- package/corpus/templates/clips/server/jobs/transactional-emails.ts +10 -3
- package/corpus/templates/content/app/components/sidebar/NotionButton.tsx +1 -0
- package/corpus/templates/design/app/components/design/FusionAppBanner.tsx +15 -2
- package/corpus/templates/mail/app/components/GoogleConnectBanner.tsx +82 -39
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +31 -9
- package/corpus/templates/mail/server/plugins/mail-jobs.ts +36 -17
- package/dist/client/AssistantChat.js +73 -43
- package/dist/client/MultiTabAssistantChat.d.ts +3 -1
- package/dist/client/MultiTabAssistantChat.js +79 -92
- package/dist/client/analytics/index.d.ts +1 -1
- package/dist/client/analytics/index.js +1 -1
- package/dist/client/analytics-session.d.ts +13 -0
- package/dist/client/analytics-session.js +57 -0
- package/dist/client/analytics.d.ts +1 -0
- package/dist/client/analytics.js +1 -0
- package/dist/client/browser-session-bridge.js +97 -54
- package/dist/client/db-admin/useAgentSync.js +34 -43
- package/dist/client/dynamic-suggestions.d.ts +3 -0
- package/dist/client/dynamic-suggestions.js +54 -52
- package/dist/client/hooks/index.d.ts +1 -0
- package/dist/client/hooks/index.js +1 -0
- package/dist/client/i18n.d.ts +7 -0
- package/dist/client/i18n.js +12 -3
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/integrations/useIntegrationStatus.d.ts +1 -1
- package/dist/client/integrations/useIntegrationStatus.js +9 -22
- package/dist/client/notifications/NotificationsBell.js +14 -9
- package/dist/client/progress/RunsTray.js +6 -5
- package/dist/client/settings/useBuilderStatus.js +95 -73
- package/dist/client/track.js +7 -0
- package/dist/client/transcription/BuilderTranscriptionCta.js +27 -31
- package/dist/client/use-action.js +10 -1
- package/dist/client/use-agent-engine-configured.js +21 -3
- package/dist/client/use-pausing-interval.d.ts +6 -0
- package/dist/client/use-pausing-interval.js +6 -0
- package/dist/client/use-poll-loop.d.ts +39 -0
- package/dist/client/use-poll-loop.js +73 -0
- package/dist/client/use-run-stuck-detection.js +41 -13
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/integrations/google-docs-poller.js +26 -7
- package/dist/integrations/pending-tasks-retry-job.js +17 -23
- package/dist/integrations/plugin.js +16 -15
- package/dist/integrations/remote-push-delivery-job.js +16 -25
- package/dist/integrations/remote-retry-job.js +19 -23
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/oauth-tokens/google-refresh.js +12 -19
- package/dist/observability/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +7 -7
- package/dist/provider-api/actions/provider-api.d.ts +11 -11
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +3 -3
- package/dist/server/action-routes.js +4 -2
- package/dist/server/agent-chat-plugin.js +26 -29
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agent-run-context.d.ts +4 -2
- package/dist/server/agent-run-context.js +5 -3
- package/dist/server/core-routes-plugin.js +2 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +1 -0
- package/dist/server/interval-job.d.ts +25 -0
- package/dist/server/interval-job.js +19 -0
- package/dist/shared/index.d.ts +1 -0
- package/dist/shared/index.js +1 -0
- package/dist/shared/poll-engine.d.ts +49 -0
- package/dist/shared/poll-engine.js +148 -0
- package/dist/templates/default/.agents/skills/internationalization/SKILL.md +5 -0
- package/dist/templates/workspace-core/.agents/skills/internationalization/SKILL.md +5 -0
- package/dist/templates/workspace-core/.agents/skills/observability/SKILL.md +5 -1
- package/dist/templates/workspace-core/.agents/skills/tracking/SKILL.md +20 -3
- package/dist/tracking/index.d.ts +1 -1
- package/dist/tracking/providers.js +22 -2
- package/dist/tracking/registry.d.ts +12 -2
- package/dist/tracking/registry.js +27 -3
- package/dist/tracking/types.d.ts +8 -0
- package/docs/content/observability.mdx +16 -0
- package/docs/content/tracking.mdx +17 -2
- package/package.json +4 -3
- package/src/templates/default/.agents/skills/internationalization/SKILL.md +5 -0
- package/src/templates/workspace-core/.agents/skills/internationalization/SKILL.md +5 -0
- package/src/templates/workspace-core/.agents/skills/observability/SKILL.md +5 -1
- package/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +20 -3
|
@@ -427,6 +427,24 @@ function serverUrlForPendingUpload(
|
|
|
427
427
|
// already-connected user to the setup flow.
|
|
428
428
|
type VideoStorageProbe = "configured" | "missing" | "unknown";
|
|
429
429
|
|
|
430
|
+
// Poll cadence for the caller's re-check loop is 5s; bound each probe request
|
|
431
|
+
// well above that so a hung request can't wedge the poll's in-flight guard.
|
|
432
|
+
const VIDEO_STORAGE_PROBE_TIMEOUT_MS = Math.max(10_000, 5000 * 4);
|
|
433
|
+
|
|
434
|
+
async function fetchWithAbortTimeout(
|
|
435
|
+
url: string,
|
|
436
|
+
init: RequestInit,
|
|
437
|
+
timeoutMs: number,
|
|
438
|
+
): Promise<Response> {
|
|
439
|
+
const controller = new AbortController();
|
|
440
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
441
|
+
try {
|
|
442
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
443
|
+
} finally {
|
|
444
|
+
clearTimeout(timer);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
430
448
|
async function hasConfiguredVideoStorage(
|
|
431
449
|
serverUrl: string,
|
|
432
450
|
): Promise<VideoStorageProbe> {
|
|
@@ -437,12 +455,13 @@ async function hasConfiguredVideoStorage(
|
|
|
437
455
|
let sawDefinitiveAnswer = false;
|
|
438
456
|
|
|
439
457
|
try {
|
|
440
|
-
const uploadStatus = await
|
|
458
|
+
const uploadStatus = await fetchWithAbortTimeout(
|
|
441
459
|
`${base}/_agent-native/file-upload/status`,
|
|
442
460
|
{
|
|
443
461
|
credentials: "include",
|
|
444
462
|
cache: "no-store",
|
|
445
463
|
},
|
|
464
|
+
VIDEO_STORAGE_PROBE_TIMEOUT_MS,
|
|
446
465
|
);
|
|
447
466
|
if (uploadStatus.ok) {
|
|
448
467
|
const body = (await uploadStatus.json().catch(() => null)) as {
|
|
@@ -458,10 +477,14 @@ async function hasConfiguredVideoStorage(
|
|
|
458
477
|
}
|
|
459
478
|
|
|
460
479
|
try {
|
|
461
|
-
const builderStatus = await
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
480
|
+
const builderStatus = await fetchWithAbortTimeout(
|
|
481
|
+
`${base}/_agent-native/builder/status`,
|
|
482
|
+
{
|
|
483
|
+
credentials: "include",
|
|
484
|
+
cache: "no-store",
|
|
485
|
+
},
|
|
486
|
+
VIDEO_STORAGE_PROBE_TIMEOUT_MS,
|
|
487
|
+
);
|
|
465
488
|
if (builderStatus.ok) {
|
|
466
489
|
const body = (await builderStatus.json().catch(() => null)) as {
|
|
467
490
|
configured?: boolean;
|
|
@@ -1143,10 +1166,23 @@ export function App() {
|
|
|
1143
1166
|
return;
|
|
1144
1167
|
}
|
|
1145
1168
|
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1169
|
+
let inFlight = false;
|
|
1170
|
+
const tick = () => {
|
|
1171
|
+
if (document.hidden || inFlight) return;
|
|
1172
|
+
inFlight = true;
|
|
1173
|
+
void refreshVideoStorageStatus().finally(() => {
|
|
1174
|
+
inFlight = false;
|
|
1175
|
+
});
|
|
1176
|
+
};
|
|
1177
|
+
const interval = window.setInterval(tick, 5000);
|
|
1178
|
+
const onVisibilityChange = () => {
|
|
1179
|
+
if (!document.hidden) tick();
|
|
1180
|
+
};
|
|
1181
|
+
document.addEventListener("visibilitychange", onVisibilityChange);
|
|
1182
|
+
return () => {
|
|
1183
|
+
window.clearInterval(interval);
|
|
1184
|
+
document.removeEventListener("visibilitychange", onVisibilityChange);
|
|
1185
|
+
};
|
|
1150
1186
|
}, [
|
|
1151
1187
|
authStatus,
|
|
1152
1188
|
localRecordingMode,
|
|
@@ -1630,22 +1666,45 @@ export function App() {
|
|
|
1630
1666
|
return;
|
|
1631
1667
|
}
|
|
1632
1668
|
let cancelled = false;
|
|
1669
|
+
let inFlight = false;
|
|
1633
1670
|
const poll = async () => {
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1671
|
+
if (document.hidden || inFlight) return;
|
|
1672
|
+
inFlight = true;
|
|
1673
|
+
const controller = new AbortController();
|
|
1674
|
+
const abortTimer = setTimeout(
|
|
1675
|
+
() => controller.abort(),
|
|
1676
|
+
Math.max(10_000, 3_000 * 4),
|
|
1638
1677
|
);
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1678
|
+
try {
|
|
1679
|
+
const result = await callClipsAction<{
|
|
1680
|
+
requests?: RewindExtensionRequest[];
|
|
1681
|
+
}>(
|
|
1682
|
+
"list-rewind-extension-requests",
|
|
1683
|
+
{},
|
|
1684
|
+
{ method: "GET", signal: controller.signal },
|
|
1685
|
+
)
|
|
1686
|
+
// coercion-ok: nothing to process this sweep either way; the next
|
|
1687
|
+
// tick re-reads the pending requests.
|
|
1688
|
+
.catch(() => null);
|
|
1689
|
+
if (cancelled) return;
|
|
1690
|
+
for (const request of result?.requests ?? []) {
|
|
1691
|
+
void processRewindExtension(request);
|
|
1692
|
+
}
|
|
1693
|
+
} finally {
|
|
1694
|
+
clearTimeout(abortTimer);
|
|
1695
|
+
inFlight = false;
|
|
1642
1696
|
}
|
|
1643
1697
|
};
|
|
1644
1698
|
void poll();
|
|
1645
1699
|
const timer = window.setInterval(() => void poll(), 3_000);
|
|
1700
|
+
const onVisibilityChange = () => {
|
|
1701
|
+
if (!document.hidden) void poll();
|
|
1702
|
+
};
|
|
1703
|
+
document.addEventListener("visibilitychange", onVisibilityChange);
|
|
1646
1704
|
return () => {
|
|
1647
1705
|
cancelled = true;
|
|
1648
1706
|
window.clearInterval(timer);
|
|
1707
|
+
document.removeEventListener("visibilitychange", onVisibilityChange);
|
|
1649
1708
|
};
|
|
1650
1709
|
}, [
|
|
1651
1710
|
authStatus,
|
|
@@ -1702,41 +1761,63 @@ export function App() {
|
|
|
1702
1761
|
useEffect(() => {
|
|
1703
1762
|
if (featureConfig?.screenMemory?.enabled !== true) return;
|
|
1704
1763
|
let cancelled = false;
|
|
1764
|
+
let inFlight = false;
|
|
1705
1765
|
const sweep = async () => {
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
await
|
|
1724
|
-
|
|
1725
|
-
|
|
1766
|
+
if (document.hidden || inFlight) return;
|
|
1767
|
+
inFlight = true;
|
|
1768
|
+
try {
|
|
1769
|
+
const due = await invoke<DueRewindAgentHandoff[]>(
|
|
1770
|
+
"screen_memory_due_agent_handoffs",
|
|
1771
|
+
)
|
|
1772
|
+
// coercion-ok: handoffs stay due in the local store, so an empty
|
|
1773
|
+
// sweep and a failed one both retry on the next tick.
|
|
1774
|
+
.catch(() => []);
|
|
1775
|
+
if (cancelled) return;
|
|
1776
|
+
for (const item of due) {
|
|
1777
|
+
try {
|
|
1778
|
+
const controller = new AbortController();
|
|
1779
|
+
const abortTimer = setTimeout(
|
|
1780
|
+
() => controller.abort(),
|
|
1781
|
+
Math.max(10_000, 60_000 * 4),
|
|
1782
|
+
);
|
|
1783
|
+
const cleanup = await callClipsAction<{
|
|
1784
|
+
deleted: boolean;
|
|
1785
|
+
reason: string;
|
|
1786
|
+
}>(
|
|
1787
|
+
"delete-agent-recording-if-unpromoted",
|
|
1788
|
+
{ id: item.recordingId },
|
|
1789
|
+
{ signal: controller.signal },
|
|
1790
|
+
).finally(() => clearTimeout(abortTimer));
|
|
1791
|
+
if (cleanup.deleted) {
|
|
1792
|
+
await invoke("screen_memory_mark_agent_handoff_deleted", {
|
|
1793
|
+
requestId: item.requestId,
|
|
1794
|
+
});
|
|
1795
|
+
} else if (cleanup.reason === "promoted") {
|
|
1796
|
+
await invoke("screen_memory_cancel_agent_handoff_cleanup", {
|
|
1797
|
+
requestId: item.requestId,
|
|
1798
|
+
});
|
|
1799
|
+
}
|
|
1800
|
+
} catch (error) {
|
|
1801
|
+
console.warn(
|
|
1802
|
+
"[clips-tray] agent-created Clip cleanup failed:",
|
|
1803
|
+
error,
|
|
1804
|
+
);
|
|
1726
1805
|
}
|
|
1727
|
-
} catch (error) {
|
|
1728
|
-
console.warn(
|
|
1729
|
-
"[clips-tray] agent-created Clip cleanup failed:",
|
|
1730
|
-
error,
|
|
1731
|
-
);
|
|
1732
1806
|
}
|
|
1807
|
+
} finally {
|
|
1808
|
+
inFlight = false;
|
|
1733
1809
|
}
|
|
1734
1810
|
};
|
|
1735
1811
|
void sweep();
|
|
1736
1812
|
const timer = window.setInterval(() => void sweep(), 60_000);
|
|
1813
|
+
const onVisibilityChange = () => {
|
|
1814
|
+
if (!document.hidden) void sweep();
|
|
1815
|
+
};
|
|
1816
|
+
document.addEventListener("visibilitychange", onVisibilityChange);
|
|
1737
1817
|
return () => {
|
|
1738
1818
|
cancelled = true;
|
|
1739
1819
|
window.clearInterval(timer);
|
|
1820
|
+
document.removeEventListener("visibilitychange", onVisibilityChange);
|
|
1740
1821
|
};
|
|
1741
1822
|
}, [callClipsAction, featureConfig?.screenMemory?.enabled]);
|
|
1742
1823
|
|
|
@@ -1967,11 +2048,18 @@ export function App() {
|
|
|
1967
2048
|
if (signInInflightRef.current) return;
|
|
1968
2049
|
signInInflightRef.current = true;
|
|
1969
2050
|
|
|
2051
|
+
let tickInFlight = false;
|
|
2052
|
+
let onVisibilityChange: (() => void) | null = null;
|
|
2053
|
+
|
|
1970
2054
|
function stopPolling() {
|
|
1971
2055
|
if (pollIntervalRef.current !== null) {
|
|
1972
2056
|
clearInterval(pollIntervalRef.current);
|
|
1973
2057
|
pollIntervalRef.current = null;
|
|
1974
2058
|
}
|
|
2059
|
+
if (onVisibilityChange) {
|
|
2060
|
+
document.removeEventListener("visibilitychange", onVisibilityChange);
|
|
2061
|
+
onVisibilityChange = null;
|
|
2062
|
+
}
|
|
1975
2063
|
}
|
|
1976
2064
|
|
|
1977
2065
|
function finishWithError(message: string) {
|
|
@@ -2000,11 +2088,16 @@ export function App() {
|
|
|
2000
2088
|
// Poll the exchange endpoint for the session token.
|
|
2001
2089
|
const start = Date.now();
|
|
2002
2090
|
const TIMEOUT_MS = 180_000; // 3 minutes
|
|
2003
|
-
|
|
2091
|
+
const POLL_ABORT_MS = Math.max(10_000, 1500 * 4);
|
|
2092
|
+
const tick = async () => {
|
|
2093
|
+
if (document.hidden || tickInFlight) return;
|
|
2094
|
+
tickInFlight = true;
|
|
2095
|
+
const controller = new AbortController();
|
|
2096
|
+
const abortTimer = setTimeout(() => controller.abort(), POLL_ABORT_MS);
|
|
2004
2097
|
try {
|
|
2005
2098
|
const xr = await fetch(
|
|
2006
2099
|
`${base}/_agent-native/auth/desktop-exchange?flow_id=${flowId}`,
|
|
2007
|
-
{ credentials: "include" },
|
|
2100
|
+
{ credentials: "include", signal: controller.signal },
|
|
2008
2101
|
);
|
|
2009
2102
|
if (!xr.ok) {
|
|
2010
2103
|
if (Date.now() - start > TIMEOUT_MS) {
|
|
@@ -2030,7 +2123,7 @@ export function App() {
|
|
|
2030
2123
|
// bearer token above is the reliable desktop auth path.
|
|
2031
2124
|
await fetch(
|
|
2032
2125
|
`${base}/_agent-native/auth/session?_session=${xd.token}`,
|
|
2033
|
-
{ credentials: "include" },
|
|
2126
|
+
{ credentials: "include", signal: controller.signal },
|
|
2034
2127
|
);
|
|
2035
2128
|
signInInflightRef.current = false;
|
|
2036
2129
|
setSignInPending(false);
|
|
@@ -2047,8 +2140,16 @@ export function App() {
|
|
|
2047
2140
|
if (Date.now() - start > TIMEOUT_MS) {
|
|
2048
2141
|
finishWithError("Google sign-in timed out. Please try again.");
|
|
2049
2142
|
}
|
|
2143
|
+
} finally {
|
|
2144
|
+
clearTimeout(abortTimer);
|
|
2145
|
+
tickInFlight = false;
|
|
2050
2146
|
}
|
|
2051
|
-
}
|
|
2147
|
+
};
|
|
2148
|
+
pollIntervalRef.current = setInterval(() => void tick(), 1500);
|
|
2149
|
+
onVisibilityChange = () => {
|
|
2150
|
+
if (!document.hidden) void tick();
|
|
2151
|
+
};
|
|
2152
|
+
document.addEventListener("visibilitychange", onVisibilityChange);
|
|
2052
2153
|
} catch (err) {
|
|
2053
2154
|
console.error("[clips-tray] signInExternal failed:", err);
|
|
2054
2155
|
signInInflightRef.current = false;
|
|
@@ -18,6 +18,7 @@ const SWEEP_LIMIT = 4;
|
|
|
18
18
|
const DISCOVERY_LIMIT = 20;
|
|
19
19
|
const BACKFILL_DAYS = 7;
|
|
20
20
|
let skippingLogged = false;
|
|
21
|
+
let running = false;
|
|
21
22
|
|
|
22
23
|
async function seedRecentBrainExports(): Promise<void> {
|
|
23
24
|
const now = new Date();
|
|
@@ -173,12 +174,18 @@ export default function registerBrainExportJob(): void {
|
|
|
173
174
|
return;
|
|
174
175
|
}
|
|
175
176
|
setInterval(() => {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
177
|
+
if (running) return;
|
|
178
|
+
running = true;
|
|
179
|
+
Promise.allSettled([
|
|
180
|
+
reapExpiredUploads().catch((error) =>
|
|
181
|
+
console.error("[uploads] lease reaper interval failed:", error),
|
|
182
|
+
),
|
|
183
|
+
runBrainExportSweepOnce().catch((error) =>
|
|
184
|
+
console.error("[brain-export] interval failed:", error),
|
|
185
|
+
),
|
|
186
|
+
]).finally(() => {
|
|
187
|
+
running = false;
|
|
188
|
+
});
|
|
182
189
|
}, SWEEP_INTERVAL_MS);
|
|
183
190
|
console.log(
|
|
184
191
|
`[brain-export] Recurring recovery sweep every ${SWEEP_INTERVAL_MS / 1000}s.`,
|
|
@@ -13,6 +13,7 @@ import { runBuilderMediaCompressionSweepOnce } from "../lib/builder-media-compre
|
|
|
13
13
|
|
|
14
14
|
const SWEEP_INTERVAL_MS = 5 * 60 * 1000;
|
|
15
15
|
let skippingLogged = false;
|
|
16
|
+
let running = false;
|
|
16
17
|
|
|
17
18
|
export { runBuilderMediaCompressionSweepOnce };
|
|
18
19
|
|
|
@@ -31,9 +32,15 @@ export default function registerBuilderMediaCompressionJob(): void {
|
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
setInterval(() => {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
)
|
|
35
|
+
if (running) return;
|
|
36
|
+
running = true;
|
|
37
|
+
runBuilderMediaCompressionSweepOnce()
|
|
38
|
+
.catch((err) =>
|
|
39
|
+
console.error("[builder-media-compression] interval failed:", err),
|
|
40
|
+
)
|
|
41
|
+
.finally(() => {
|
|
42
|
+
running = false;
|
|
43
|
+
});
|
|
37
44
|
}, SWEEP_INTERVAL_MS);
|
|
38
45
|
console.log(
|
|
39
46
|
`[builder-media-compression] Recurring compression sweep every ${SWEEP_INTERVAL_MS / 1000}s.`,
|
|
@@ -29,6 +29,7 @@ import { getDb, schema } from "../db/index.js";
|
|
|
29
29
|
const REMINDER_INTERVAL_MS = 60 * 1000;
|
|
30
30
|
const REMINDER_WINDOW_MS = 5 * 60 * 1000;
|
|
31
31
|
let skippingLogged = false;
|
|
32
|
+
let running = false;
|
|
32
33
|
|
|
33
34
|
const meetingReminderSchema = z.object({
|
|
34
35
|
meetingId: z.string(),
|
|
@@ -114,9 +115,15 @@ export default function registerMeetingRemindersJob(): void {
|
|
|
114
115
|
return;
|
|
115
116
|
}
|
|
116
117
|
setInterval(() => {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
)
|
|
118
|
+
if (running) return;
|
|
119
|
+
running = true;
|
|
120
|
+
runMeetingRemindersOnce()
|
|
121
|
+
.catch((err) =>
|
|
122
|
+
console.error("[meeting-reminders] interval failed:", err),
|
|
123
|
+
)
|
|
124
|
+
.finally(() => {
|
|
125
|
+
running = false;
|
|
126
|
+
});
|
|
120
127
|
}, REMINDER_INTERVAL_MS);
|
|
121
128
|
console.log(
|
|
122
129
|
`[meeting-reminders] Recurring meeting reminders every ${REMINDER_INTERVAL_MS / 1000}s.`,
|
|
@@ -2,15 +2,16 @@
|
|
|
2
2
|
* poll-calendars — recurring job (every 5 min).
|
|
3
3
|
*
|
|
4
4
|
* Sweeps every connected calendar_accounts row and refreshes its events.
|
|
5
|
-
* Runs as a Nitro plugin via `
|
|
6
|
-
* — works on every host that keeps a long-lived process; on
|
|
7
|
-
* the deployment plugin should call `runPollCalendarsOnce` from
|
|
8
|
-
* scheduled function instead).
|
|
5
|
+
* Runs as a Nitro plugin via `startIntervalJob` (matches the mail-jobs
|
|
6
|
+
* pattern — works on every host that keeps a long-lived process; on
|
|
7
|
+
* serverless the deployment plugin should call `runPollCalendarsOnce` from
|
|
8
|
+
* a scheduled function instead).
|
|
9
9
|
*
|
|
10
10
|
* Token refresh + per-account error capture happens inside the
|
|
11
11
|
* `sync-calendars` action; this job is just a thin scheduler.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
import { startIntervalJob } from "@agent-native/core/server/interval-job";
|
|
14
15
|
import { runWithRequestContext } from "@agent-native/core/server/request-context";
|
|
15
16
|
|
|
16
17
|
import syncCalendars from "../../actions/sync-calendars.js";
|
|
@@ -21,24 +22,21 @@ let skippingLogged = false;
|
|
|
21
22
|
/**
|
|
22
23
|
* Run a single poll pass. Exported so deployment-specific schedulers
|
|
23
24
|
* (e.g. Netlify Scheduled Functions) can invoke it directly without
|
|
24
|
-
* relying on a long-lived process.
|
|
25
|
+
* relying on a long-lived process. Throws on failure so a scheduled
|
|
26
|
+
* invocation records a failed run rather than a silent empty one.
|
|
25
27
|
*/
|
|
26
28
|
export async function runPollCalendarsOnce(): Promise<void> {
|
|
27
29
|
// No request context — this job runs as the system. The sync-calendars
|
|
28
30
|
// action accepts `allAccounts: true` to skip the per-user accessFilter.
|
|
29
31
|
await runWithRequestContext({}, async () => {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
const result = await syncCalendars.run(
|
|
33
|
+
{ allAccounts: true } as any,
|
|
34
|
+
{} as any,
|
|
35
|
+
);
|
|
36
|
+
if (result?.synced) {
|
|
37
|
+
console.log(
|
|
38
|
+
`[poll-calendars] synced ${result.synced} accounts, ${result.events ?? 0} events, ${result.meetings ?? 0} meetings`,
|
|
34
39
|
);
|
|
35
|
-
if (result?.synced) {
|
|
36
|
-
console.log(
|
|
37
|
-
`[poll-calendars] synced ${result.synced} accounts, ${result.events ?? 0} events, ${result.meetings ?? 0} meetings`,
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
} catch (err: any) {
|
|
41
|
-
console.error(`[poll-calendars] sync failed:`, err?.message ?? err);
|
|
42
40
|
}
|
|
43
41
|
});
|
|
44
42
|
}
|
|
@@ -62,11 +60,15 @@ export default function registerPollCalendarsJob(): void {
|
|
|
62
60
|
}
|
|
63
61
|
return;
|
|
64
62
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
63
|
+
// sync-calendars calls Google's Calendar API with no fetch-level timeout of
|
|
64
|
+
// its own, so the overlap guard and the hung-sweep timeout both come from
|
|
65
|
+
// startIntervalJob rather than a module-level `running` flag here.
|
|
66
|
+
startIntervalJob(() => runPollCalendarsOnce(), {
|
|
67
|
+
intervalMs: POLL_INTERVAL_MS,
|
|
68
|
+
leading: false,
|
|
69
|
+
onError: (err: any) =>
|
|
70
|
+
console.error("[poll-calendars] sync failed:", err?.message ?? err),
|
|
71
|
+
});
|
|
70
72
|
console.log(
|
|
71
73
|
`[poll-calendars] Recurring calendar sync every ${POLL_INTERVAL_MS / 1000}s.`,
|
|
72
74
|
);
|
|
@@ -46,6 +46,7 @@ const STALE_THRESHOLD_MS = 60 * 60 * 1000; // 60 min of zero transcript activity
|
|
|
46
46
|
// a slow Gemini call. Mirrors finalize-meeting.ts's force-takeover window.
|
|
47
47
|
const PENDING_STALE_MS = 2 * 60 * 1000; // 2 min
|
|
48
48
|
let skippingLogged = false;
|
|
49
|
+
let running = false;
|
|
49
50
|
|
|
50
51
|
/**
|
|
51
52
|
* Close out a single stranded-live meeting row: stamp actualEnd, set
|
|
@@ -284,9 +285,15 @@ export default function registerStaleMeetingSweeperJob(): void {
|
|
|
284
285
|
return;
|
|
285
286
|
}
|
|
286
287
|
setInterval(() => {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
)
|
|
288
|
+
if (running) return;
|
|
289
|
+
running = true;
|
|
290
|
+
runStaleMeetingSweepOnce()
|
|
291
|
+
.catch((err) =>
|
|
292
|
+
console.error("[stale-meeting-sweeper] interval failed:", err),
|
|
293
|
+
)
|
|
294
|
+
.finally(() => {
|
|
295
|
+
running = false;
|
|
296
|
+
});
|
|
290
297
|
}, SWEEP_INTERVAL_MS);
|
|
291
298
|
console.log(
|
|
292
299
|
`[stale-meeting-sweeper] Recurring stale-meeting reconciliation every ${SWEEP_INTERVAL_MS / 1000}s.`,
|
|
@@ -49,6 +49,7 @@ const SENDING_LEASE_MS = 2 * 60 * 1000;
|
|
|
49
49
|
const MAX_ATTEMPTS = 3;
|
|
50
50
|
const RETRY_BASE_DELAY_MS = 60_000;
|
|
51
51
|
let skippingLogged = false;
|
|
52
|
+
let running = false;
|
|
52
53
|
|
|
53
54
|
type DirectShare = {
|
|
54
55
|
id: string;
|
|
@@ -1304,9 +1305,15 @@ export default function registerTransactionalEmailsJob(): void {
|
|
|
1304
1305
|
return;
|
|
1305
1306
|
}
|
|
1306
1307
|
setInterval(() => {
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
)
|
|
1308
|
+
if (running) return;
|
|
1309
|
+
running = true;
|
|
1310
|
+
runTransactionalEmailsOnce()
|
|
1311
|
+
.catch((error) =>
|
|
1312
|
+
console.error("[transactional-emails] interval failed:", error),
|
|
1313
|
+
)
|
|
1314
|
+
.finally(() => {
|
|
1315
|
+
running = false;
|
|
1316
|
+
});
|
|
1310
1317
|
}, JOB_INTERVAL_MS);
|
|
1311
1318
|
console.log(
|
|
1312
1319
|
`[transactional-emails] Recurring reconciliation and delivery every ${JOB_INTERVAL_MS / 1000}s.`,
|
|
@@ -83,11 +83,24 @@ export function FusionAppBanner({
|
|
|
83
83
|
// ── Building: poll sync-fusion-app on mount + every ~8s while building ────
|
|
84
84
|
const syncMutateRef = useRef(syncMutation.mutate);
|
|
85
85
|
syncMutateRef.current = syncMutation.mutate;
|
|
86
|
+
const syncInFlightRef = useRef(false);
|
|
86
87
|
useEffect(() => {
|
|
87
88
|
if (status !== "building") return;
|
|
88
|
-
|
|
89
|
+
// The leading sync goes through the same guard as the interval ticks: a
|
|
90
|
+
// container check still pending at the first tick would otherwise let a
|
|
91
|
+
// second status sync start and race the provisioning/screen-upsert work.
|
|
92
|
+
const runSync = (): void => {
|
|
93
|
+
if (syncInFlightRef.current) return;
|
|
94
|
+
syncInFlightRef.current = true;
|
|
95
|
+
syncMutateRef.current(
|
|
96
|
+
{ designId } as any,
|
|
97
|
+
{ onSettled: () => (syncInFlightRef.current = false) } as any,
|
|
98
|
+
);
|
|
99
|
+
};
|
|
100
|
+
runSync();
|
|
89
101
|
const interval = window.setInterval(() => {
|
|
90
|
-
|
|
102
|
+
if (document.hidden) return;
|
|
103
|
+
runSync();
|
|
91
104
|
}, SYNC_POLL_INTERVAL_MS);
|
|
92
105
|
return () => window.clearInterval(interval);
|
|
93
106
|
}, [designId, status]);
|