@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
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
import { useEffect, useMemo, useState } from "react";
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import { readClientAppState } from "./application-state.js";
|
|
3
3
|
import { useChangeVersions } from "./use-change-version.js";
|
|
4
|
+
import { usePollLoop } from "./use-poll-loop.js";
|
|
4
5
|
const SAFE_BROWSER_TAB_ID_RE = /^[A-Za-z0-9_-]{1,96}$/;
|
|
5
6
|
const DEFAULT_MAX_SUGGESTIONS = 3;
|
|
7
|
+
const DEFAULT_POLL_MS = 30_000;
|
|
6
8
|
export function normalizeAgentDynamicSuggestionsConfig(option) {
|
|
7
9
|
if (option === false) {
|
|
8
10
|
return {
|
|
9
11
|
enabled: false,
|
|
10
12
|
max: DEFAULT_MAX_SUGGESTIONS,
|
|
11
13
|
includeStatic: true,
|
|
14
|
+
pollMs: DEFAULT_POLL_MS,
|
|
12
15
|
};
|
|
13
16
|
}
|
|
14
17
|
if (option === true || option === undefined) {
|
|
@@ -16,6 +19,7 @@ export function normalizeAgentDynamicSuggestionsConfig(option) {
|
|
|
16
19
|
enabled: true,
|
|
17
20
|
max: DEFAULT_MAX_SUGGESTIONS,
|
|
18
21
|
includeStatic: true,
|
|
22
|
+
pollMs: DEFAULT_POLL_MS,
|
|
19
23
|
};
|
|
20
24
|
}
|
|
21
25
|
return {
|
|
@@ -25,6 +29,9 @@ export function normalizeAgentDynamicSuggestionsConfig(option) {
|
|
|
25
29
|
: DEFAULT_MAX_SUGGESTIONS,
|
|
26
30
|
includeStatic: option.includeStatic !== false,
|
|
27
31
|
...(option.getSuggestions ? { getSuggestions: option.getSuggestions } : {}),
|
|
32
|
+
pollMs: typeof option.pollMs === "number" && Number.isFinite(option.pollMs)
|
|
33
|
+
? Math.max(0, Math.floor(option.pollMs))
|
|
34
|
+
: DEFAULT_POLL_MS,
|
|
28
35
|
};
|
|
29
36
|
}
|
|
30
37
|
function normalizeBrowserTabId(browserTabId) {
|
|
@@ -263,64 +270,59 @@ export function useAgentDynamicSuggestionsResult(options) {
|
|
|
263
270
|
const enabled = options.enabled !== false && config.enabled;
|
|
264
271
|
const [context, setContext] = useState(null);
|
|
265
272
|
const [isLoading, setIsLoading] = useState(false);
|
|
273
|
+
// Invalidates in-flight loads from a stale scope/browserTabId/enabled
|
|
274
|
+
// generation — shared between the leading load below and the safety-net
|
|
275
|
+
// poll so a slow response from an old generation can't clobber a newer one.
|
|
276
|
+
const loadGenerationRef = useRef(0);
|
|
277
|
+
const load = useCallback(async (showLoading) => {
|
|
278
|
+
const generation = loadGenerationRef.current;
|
|
279
|
+
if (showLoading)
|
|
280
|
+
setIsLoading(true);
|
|
281
|
+
try {
|
|
282
|
+
const [navigation, selection, pendingSelection, url] = await Promise.all([
|
|
283
|
+
readScopedAppState("navigation", browserTabId),
|
|
284
|
+
readScopedAppState("selection", browserTabId),
|
|
285
|
+
readScopedAppState("pending-selection-context", browserTabId),
|
|
286
|
+
readScopedAppState("__url__", browserTabId),
|
|
287
|
+
]);
|
|
288
|
+
if (generation !== loadGenerationRef.current)
|
|
289
|
+
return;
|
|
290
|
+
setContext({ navigation, selection, pendingSelection, url, scope });
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
if (generation !== loadGenerationRef.current)
|
|
294
|
+
return;
|
|
295
|
+
setContext({
|
|
296
|
+
navigation: null,
|
|
297
|
+
selection: null,
|
|
298
|
+
pendingSelection: null,
|
|
299
|
+
url: null,
|
|
300
|
+
scope,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
finally {
|
|
304
|
+
if (generation === loadGenerationRef.current && showLoading) {
|
|
305
|
+
setIsLoading(false);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}, [browserTabId, scope]);
|
|
266
309
|
useEffect(() => {
|
|
310
|
+
loadGenerationRef.current += 1;
|
|
267
311
|
if (!enabled) {
|
|
268
312
|
setContext(null);
|
|
269
313
|
setIsLoading(false);
|
|
270
314
|
return;
|
|
271
315
|
}
|
|
272
|
-
let cancelled = false;
|
|
273
|
-
const load = async (showLoading) => {
|
|
274
|
-
if (showLoading)
|
|
275
|
-
setIsLoading(true);
|
|
276
|
-
try {
|
|
277
|
-
const [navigation, selection, pendingSelection, url] = await Promise.all([
|
|
278
|
-
readScopedAppState("navigation", browserTabId),
|
|
279
|
-
readScopedAppState("selection", browserTabId),
|
|
280
|
-
readScopedAppState("pending-selection-context", browserTabId),
|
|
281
|
-
readScopedAppState("__url__", browserTabId),
|
|
282
|
-
]);
|
|
283
|
-
if (cancelled)
|
|
284
|
-
return;
|
|
285
|
-
setContext({
|
|
286
|
-
navigation,
|
|
287
|
-
selection,
|
|
288
|
-
pendingSelection,
|
|
289
|
-
url,
|
|
290
|
-
scope,
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
catch {
|
|
294
|
-
if (cancelled)
|
|
295
|
-
return;
|
|
296
|
-
setContext({
|
|
297
|
-
navigation: null,
|
|
298
|
-
selection: null,
|
|
299
|
-
pendingSelection: null,
|
|
300
|
-
url: null,
|
|
301
|
-
scope,
|
|
302
|
-
});
|
|
303
|
-
}
|
|
304
|
-
finally {
|
|
305
|
-
if (!cancelled && showLoading)
|
|
306
|
-
setIsLoading(false);
|
|
307
|
-
}
|
|
308
|
-
};
|
|
309
316
|
void load(true);
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
return () => {
|
|
320
|
-
cancelled = true;
|
|
321
|
-
clearInterval(interval);
|
|
322
|
-
};
|
|
323
|
-
}, [appStateVersion, browserTabId, enabled, scope, scopeKey]);
|
|
317
|
+
}, [appStateVersion, enabled, load, scopeKey, config]);
|
|
318
|
+
// Slow safety net for app-state updates that don't bump `appStateVersion`
|
|
319
|
+
// (the effect above already handles the event-driven case). `pollMs <= 0`
|
|
320
|
+
// disables it.
|
|
321
|
+
usePollLoop(() => load(false), {
|
|
322
|
+
intervalMs: config.pollMs,
|
|
323
|
+
leading: false,
|
|
324
|
+
enabled: enabled && config.pollMs > 0,
|
|
325
|
+
});
|
|
324
326
|
const suggestions = useMemo(() => {
|
|
325
327
|
if (!enabled) {
|
|
326
328
|
return options.staticSuggestions
|
|
@@ -10,3 +10,4 @@ export { createAgentNativeQueryClient } from "../create-query-client.js";
|
|
|
10
10
|
export { AppProviders, type AppProvidersProps } from "../app-providers.js";
|
|
11
11
|
export { usePinchZoom, type UsePinchZoomOptions } from "../use-pinch-zoom.js";
|
|
12
12
|
export { useAvatarUrl, uploadAvatar, invalidateAvatarCache, } from "../use-avatar.js";
|
|
13
|
+
export { usePollLoop, type UsePollLoopOptions, type UsePollLoopHandle, } from "../use-poll-loop.js";
|
|
@@ -10,3 +10,4 @@ export { createAgentNativeQueryClient } from "../create-query-client.js";
|
|
|
10
10
|
export { AppProviders } from "../app-providers.js";
|
|
11
11
|
export { usePinchZoom } from "../use-pinch-zoom.js";
|
|
12
12
|
export { useAvatarUrl, uploadAvatar, invalidateAvatarCache, } from "../use-avatar.js";
|
|
13
|
+
export { usePollLoop, } from "../use-poll-loop.js";
|
package/dist/client/i18n.d.ts
CHANGED
|
@@ -14,6 +14,12 @@ export interface AgentNativeI18nCatalog {
|
|
|
14
14
|
sourceLocale?: LocaleCode;
|
|
15
15
|
messages?: LocaleMessages;
|
|
16
16
|
loadMessages?: (locale: LocaleCode) => Promise<LocaleMessages | null>;
|
|
17
|
+
/**
|
|
18
|
+
* Locales this app actually ships translations for. Defaults to every
|
|
19
|
+
* framework-supported locale when omitted, which only matches apps whose
|
|
20
|
+
* `loadMessages` covers all of them.
|
|
21
|
+
*/
|
|
22
|
+
supportedLocales?: readonly LocaleCode[];
|
|
17
23
|
}
|
|
18
24
|
export interface AgentNativeI18nProviderProps {
|
|
19
25
|
children: React.ReactNode;
|
|
@@ -31,6 +37,7 @@ interface LocaleContextValue {
|
|
|
31
37
|
metadata: LocaleMetadata;
|
|
32
38
|
setPreference: (preference: LocalePreference) => Promise<void>;
|
|
33
39
|
loading: boolean;
|
|
40
|
+
supportedLocales: readonly LocaleCode[];
|
|
34
41
|
}
|
|
35
42
|
declare global {
|
|
36
43
|
var __AGENT_NATIVE_LOCALE_CONTEXT__: React.Context<LocaleContextValue | null> | undefined;
|
package/dist/client/i18n.js
CHANGED
|
@@ -155,6 +155,7 @@ export function AgentNativeI18nProvider({ children, catalog, initialLocale, init
|
|
|
155
155
|
const sourceLocale = catalog?.sourceLocale ?? DEFAULT_LOCALE;
|
|
156
156
|
const sourceMessages = catalog?.messages ?? {};
|
|
157
157
|
const loadMessages = catalog?.loadMessages;
|
|
158
|
+
const supportedLocales = catalog?.supportedLocales ?? SUPPORTED_LOCALES;
|
|
158
159
|
const hydration = readHydrationPayload();
|
|
159
160
|
const initialState = useMemo(() => resolveInitialState({ initialLocale, initialPreference }), [initialLocale, initialPreference]);
|
|
160
161
|
const [preference, setPreferenceState] = useState(initialState.preference);
|
|
@@ -305,7 +306,15 @@ export function AgentNativeI18nProvider({ children, catalog, initialLocale, init
|
|
|
305
306
|
metadata: LOCALE_METADATA[locale],
|
|
306
307
|
setPreference,
|
|
307
308
|
loading,
|
|
308
|
-
|
|
309
|
+
supportedLocales,
|
|
310
|
+
}), [
|
|
311
|
+
loading,
|
|
312
|
+
locale,
|
|
313
|
+
preference,
|
|
314
|
+
setPreference,
|
|
315
|
+
sourceLocale,
|
|
316
|
+
supportedLocales,
|
|
317
|
+
]);
|
|
309
318
|
return (_jsx(LocaleContext.Provider, { value: context, children: _jsx(I18nextProvider, { i18n: i18n, children: children }) }));
|
|
310
319
|
}
|
|
311
320
|
export function useLocale() {
|
|
@@ -472,7 +481,7 @@ export function useFormatters() {
|
|
|
472
481
|
}), [locale]);
|
|
473
482
|
}
|
|
474
483
|
export function LanguagePicker({ className, includeSystem = true, label, variant = "select", }) {
|
|
475
|
-
const { locale, preference, setPreference } = useLocale();
|
|
484
|
+
const { locale, preference, setPreference, supportedLocales } = useLocale();
|
|
476
485
|
const [open, setOpen] = useState(false);
|
|
477
486
|
const copy = LANGUAGE_PICKER_COPY[locale] ?? LANGUAGE_PICKER_COPY[DEFAULT_LOCALE];
|
|
478
487
|
const resolvedLabel = label ?? copy.label;
|
|
@@ -486,7 +495,7 @@ export function LanguagePicker({ className, includeSystem = true, label, variant
|
|
|
486
495
|
},
|
|
487
496
|
]
|
|
488
497
|
: []),
|
|
489
|
-
...
|
|
498
|
+
...supportedLocales.map((code) => ({
|
|
490
499
|
value: code,
|
|
491
500
|
label: `${LOCALE_METADATA[code].nativeName} (${code})`,
|
|
492
501
|
description: code,
|
package/dist/client/index.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export { DevDatabaseLink, type DevDatabaseLinkProps, } from "./db-admin/DevDatab
|
|
|
18
18
|
export { DbAdminPage } from "./db-admin/DbAdminPage.js";
|
|
19
19
|
export { installRouteChunkRecovery, reloadForStaleChunk, recoverFromStaleChunkError, } from "./route-chunk-recovery.js";
|
|
20
20
|
export { AgentTerminal, type AgentTerminalProps } from "./terminal/index.js";
|
|
21
|
-
export { trackEvent, trackAgentChatLifecycle, trackSessionStatus, configureTracking, setTrackingContentCaptureEnabled, maybeStartSessionReplay, startSessionReplay, stopSessionReplay, getAnalyticsAnonymousId, getAnalyticsSessionId, getFirstTouchAttribution, setSentryUser, setTrackingIdentity, getSessionReplayContext, getSessionReplayUrl, captureError, captureClientException, AGENT_NATIVE_EXCEPTION_EVENT_NAME, addErrorBreadcrumb, captureException, captureMessage, isErrorCaptureInstalled, type CaptureExceptionContext, type CapturedExceptionEvent, type ClientCaptureContext, type AgentChatLifecycleEvent, type ConfigureTrackingOptions, type ErrorCaptureConfigOptions, type ExceptionBreadcrumb, type ExceptionLevel, type FirstTouchAttribution, type SessionReplayOptions, type SessionReplayContext, type SessionReplayLinkOptions, type SessionReplayStartResult, type SessionReplayUrlMatcher, type TrackingIdentityUser, } from "./analytics.js";
|
|
21
|
+
export { trackEvent, trackAgentChatLifecycle, trackSessionStatus, configureTracking, setTrackingContentCaptureEnabled, maybeStartSessionReplay, startSessionReplay, stopSessionReplay, getAnalyticsAnonymousId, getAnalyticsSessionId, setAnalyticsSessionId, clearAnalyticsSessionId, getFirstTouchAttribution, setSentryUser, setTrackingIdentity, getSessionReplayContext, getSessionReplayUrl, captureError, captureClientException, AGENT_NATIVE_EXCEPTION_EVENT_NAME, addErrorBreadcrumb, captureException, captureMessage, isErrorCaptureInstalled, type CaptureExceptionContext, type CapturedExceptionEvent, type ClientCaptureContext, type AgentChatLifecycleEvent, type ConfigureTrackingOptions, type ErrorCaptureConfigOptions, type ExceptionBreadcrumb, type ExceptionLevel, type FirstTouchAttribution, type SessionReplayOptions, type SessionReplayContext, type SessionReplayLinkOptions, type SessionReplayStartResult, type SessionReplayUrlMatcher, type TrackingIdentityUser, } from "./analytics.js";
|
|
22
22
|
export { track } from "./track.js";
|
|
23
23
|
export { useCollaborativeDoc, isReconcileLeadClient, emailToColor, emailToName, dedupeCollabUsersByEmail, type UseCollaborativeDocOptions, type UseCollaborativeDocResult, type CollabUser, } from "../collab/client.js";
|
|
24
24
|
export { AGENT_CLIENT_ID } from "../collab/agent-identity.js";
|
package/dist/client/index.js
CHANGED
|
@@ -18,7 +18,7 @@ export { DevDatabaseLink, } from "./db-admin/DevDatabaseLink.js";
|
|
|
18
18
|
export { DbAdminPage } from "./db-admin/DbAdminPage.js";
|
|
19
19
|
export { installRouteChunkRecovery, reloadForStaleChunk, recoverFromStaleChunkError, } from "./route-chunk-recovery.js";
|
|
20
20
|
export { AgentTerminal } from "./terminal/index.js";
|
|
21
|
-
export { trackEvent, trackAgentChatLifecycle, trackSessionStatus, configureTracking, setTrackingContentCaptureEnabled, maybeStartSessionReplay, startSessionReplay, stopSessionReplay, getAnalyticsAnonymousId, getAnalyticsSessionId, getFirstTouchAttribution, setSentryUser, setTrackingIdentity, getSessionReplayContext, getSessionReplayUrl, captureError, captureClientException,
|
|
21
|
+
export { trackEvent, trackAgentChatLifecycle, trackSessionStatus, configureTracking, setTrackingContentCaptureEnabled, maybeStartSessionReplay, startSessionReplay, stopSessionReplay, getAnalyticsAnonymousId, getAnalyticsSessionId, setAnalyticsSessionId, clearAnalyticsSessionId, getFirstTouchAttribution, setSentryUser, setTrackingIdentity, getSessionReplayContext, getSessionReplayUrl, captureError, captureClientException,
|
|
22
22
|
// First-party, Sentry-style error capture (auto + manual API).
|
|
23
23
|
AGENT_NATIVE_EXCEPTION_EVENT_NAME, addErrorBreadcrumb, captureException, captureMessage, isErrorCaptureInstalled, } from "./analytics.js";
|
|
24
24
|
export { track } from "./track.js";
|
|
@@ -1,37 +1,24 @@
|
|
|
1
|
-
import { useState,
|
|
1
|
+
import { useState, useCallback } from "react";
|
|
2
2
|
import { agentNativePath } from "../api-path.js";
|
|
3
|
+
import { usePollLoop } from "../use-poll-loop.js";
|
|
3
4
|
export function useIntegrationStatus() {
|
|
4
5
|
const [statuses, setStatuses] = useState([]);
|
|
5
6
|
const [loading, setLoading] = useState(true);
|
|
6
|
-
const
|
|
7
|
-
const fetchStatuses = useCallback(async () => {
|
|
7
|
+
const fetchStatuses = useCallback(async (signal) => {
|
|
8
8
|
try {
|
|
9
|
-
const res = await fetch(agentNativePath("/_agent-native/integrations/status"));
|
|
9
|
+
const res = await fetch(agentNativePath("/_agent-native/integrations/status"), { signal });
|
|
10
10
|
if (!res.ok) {
|
|
11
|
-
|
|
12
|
-
setLoading(false);
|
|
11
|
+
setLoading(false);
|
|
13
12
|
return;
|
|
14
13
|
}
|
|
15
14
|
const data = await res.json();
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
setLoading(false);
|
|
19
|
-
}
|
|
15
|
+
setStatuses(Array.isArray(data) ? data : []);
|
|
16
|
+
setLoading(false);
|
|
20
17
|
}
|
|
21
18
|
catch {
|
|
22
|
-
|
|
23
|
-
setLoading(false);
|
|
24
|
-
}
|
|
19
|
+
setLoading(false);
|
|
25
20
|
}
|
|
26
21
|
}, []);
|
|
27
|
-
|
|
28
|
-
mountedRef.current = true;
|
|
29
|
-
fetchStatuses();
|
|
30
|
-
const interval = setInterval(fetchStatuses, 30000);
|
|
31
|
-
return () => {
|
|
32
|
-
mountedRef.current = false;
|
|
33
|
-
clearInterval(interval);
|
|
34
|
-
};
|
|
35
|
-
}, [fetchStatuses]);
|
|
22
|
+
usePollLoop(fetchStatuses, { intervalMs: 30_000 });
|
|
36
23
|
return { statuses, loading, refetch: fetchStatuses };
|
|
37
24
|
}
|
|
@@ -3,7 +3,7 @@ import { IconBell, IconBellRinging, IconLoader2, IconX, } from "@tabler/icons-re
|
|
|
3
3
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
4
4
|
import { agentNativePath, appPath } from "../api-path.js";
|
|
5
5
|
import { Popover, PopoverContent, PopoverTrigger, } from "../components/ui/popover.js";
|
|
6
|
-
import {
|
|
6
|
+
import { usePollLoop } from "../use-poll-loop.js";
|
|
7
7
|
const POLL_MS_DEFAULT = 10_000;
|
|
8
8
|
const SUPPORTS_NOTIFICATION = typeof window !== "undefined" && "Notification" in window;
|
|
9
9
|
/**
|
|
@@ -47,10 +47,10 @@ export function NotificationsBell({ pollMs = POLL_MS_DEFAULT, className, browser
|
|
|
47
47
|
// the popup loop — no second /count request), and pop Notification() for
|
|
48
48
|
// any new ids. When off, we fetch just /count. The unread-list branch also
|
|
49
49
|
// opts out of visibility pause so popups still fire for backgrounded tabs.
|
|
50
|
-
const refresh = useCallback(async () => {
|
|
50
|
+
const refresh = useCallback(async (signal) => {
|
|
51
51
|
if (browserNotifications) {
|
|
52
52
|
try {
|
|
53
|
-
const res = await fetch(agentNativePath("/_agent-native/notifications?unread=true&limit=20"));
|
|
53
|
+
const res = await fetch(agentNativePath("/_agent-native/notifications?unread=true&limit=20"), { signal });
|
|
54
54
|
if (!res.ok)
|
|
55
55
|
return;
|
|
56
56
|
const rows = (await res.json());
|
|
@@ -74,19 +74,21 @@ export function NotificationsBell({ pollMs = POLL_MS_DEFAULT, className, browser
|
|
|
74
74
|
new Notification(n.title, { body: n.body, tag: n.id });
|
|
75
75
|
}
|
|
76
76
|
catch {
|
|
77
|
-
// Safari / restricted contexts may throw even when
|
|
78
|
-
// claims to be granted
|
|
77
|
+
// coercion-ok: Safari / restricted contexts may throw even when
|
|
78
|
+
// permission claims to be granted; one failed OS notification
|
|
79
|
+
// must not abort the unread sweep around it.
|
|
79
80
|
}
|
|
80
81
|
}
|
|
81
82
|
seenIdsRef.current = seen;
|
|
82
83
|
}
|
|
83
84
|
catch {
|
|
84
|
-
//
|
|
85
|
+
// coercion-ok: the bell renders from the rows it already has; a
|
|
86
|
+
// failed sweep is retried by the next poll tick.
|
|
85
87
|
}
|
|
86
88
|
return;
|
|
87
89
|
}
|
|
88
90
|
try {
|
|
89
|
-
const res = await fetch(agentNativePath("/_agent-native/notifications/count"));
|
|
91
|
+
const res = await fetch(agentNativePath("/_agent-native/notifications/count"), { signal });
|
|
90
92
|
if (!res.ok)
|
|
91
93
|
return;
|
|
92
94
|
const data = (await res.json());
|
|
@@ -96,8 +98,11 @@ export function NotificationsBell({ pollMs = POLL_MS_DEFAULT, className, browser
|
|
|
96
98
|
// best-effort
|
|
97
99
|
}
|
|
98
100
|
}, [browserNotifications]);
|
|
99
|
-
|
|
100
|
-
|
|
101
|
+
usePollLoop(refresh, {
|
|
102
|
+
intervalMs: pollMs,
|
|
103
|
+
enabled: pollMs > 0,
|
|
104
|
+
pauseWhenHidden: !browserNotifications,
|
|
105
|
+
});
|
|
101
106
|
useEffect(() => {
|
|
102
107
|
if (!open)
|
|
103
108
|
return;
|
|
@@ -6,7 +6,7 @@ import { agentNativePath } from "../api-path.js";
|
|
|
6
6
|
import { DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, } from "../components/ui/dropdown-menu.js";
|
|
7
7
|
import { useFormatters, useT } from "../i18n.js";
|
|
8
8
|
import { useChangeVersion } from "../use-change-version.js";
|
|
9
|
-
import {
|
|
9
|
+
import { usePollLoop } from "../use-poll-loop.js";
|
|
10
10
|
import { cn } from "../utils.js";
|
|
11
11
|
const RUN_CHANGE_SETTLE_MS = 250;
|
|
12
12
|
function useRunsTrayState({ pollMs = 3000, limit = 5, hideWhenIdle = true, showRecent, }) {
|
|
@@ -14,19 +14,20 @@ function useRunsTrayState({ pollMs = 3000, limit = 5, hideWhenIdle = true, showR
|
|
|
14
14
|
const [runs, setRuns] = useState([]);
|
|
15
15
|
const includeRecent = showRecent ?? !hideWhenIdle;
|
|
16
16
|
const runsVersion = useChangeVersion("runs");
|
|
17
|
-
const refresh = useCallback(async () => {
|
|
17
|
+
const refresh = useCallback(async (signal) => {
|
|
18
18
|
try {
|
|
19
19
|
const query = new URLSearchParams({ limit: String(limit) });
|
|
20
20
|
if (!includeRecent)
|
|
21
21
|
query.set("active", "true");
|
|
22
|
-
const res = await fetch(agentNativePath(`/_agent-native/runs?${query.toString()}`));
|
|
22
|
+
const res = await fetch(agentNativePath(`/_agent-native/runs?${query.toString()}`), { signal });
|
|
23
23
|
if (!res.ok)
|
|
24
24
|
return;
|
|
25
25
|
const rows = (await res.json());
|
|
26
26
|
setRuns(rows);
|
|
27
27
|
}
|
|
28
28
|
catch {
|
|
29
|
-
//
|
|
29
|
+
// coercion-ok: `runs` keeps its last good value rather than being
|
|
30
|
+
// cleared to an empty tray; the next poll tick retries.
|
|
30
31
|
}
|
|
31
32
|
}, [includeRecent, limit]);
|
|
32
33
|
useEffect(() => {
|
|
@@ -38,7 +39,7 @@ function useRunsTrayState({ pollMs = 3000, limit = 5, hideWhenIdle = true, showR
|
|
|
38
39
|
const timeout = window.setTimeout(() => void refresh(), RUN_CHANGE_SETTLE_MS);
|
|
39
40
|
return () => window.clearTimeout(timeout);
|
|
40
41
|
}, [refresh, runsVersion]);
|
|
41
|
-
|
|
42
|
+
usePollLoop(refresh, { intervalMs: pollMs, enabled: pollMs > 0 });
|
|
42
43
|
const dismissRun = useCallback(async (runId) => {
|
|
43
44
|
setRuns((current) => current.filter((run) => run.id !== runId));
|
|
44
45
|
try {
|