@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
|
@@ -77,6 +77,7 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
|
|
|
77
77
|
}, z.core.$strip>>, {
|
|
78
78
|
id?: undefined;
|
|
79
79
|
deleted?: undefined;
|
|
80
|
+
found?: undefined;
|
|
80
81
|
providers: {
|
|
81
82
|
id: string;
|
|
82
83
|
label: string;
|
|
@@ -88,15 +89,14 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
|
|
|
88
89
|
}[];
|
|
89
90
|
count: number;
|
|
90
91
|
provider?: undefined;
|
|
91
|
-
found?: undefined;
|
|
92
92
|
registered?: undefined;
|
|
93
93
|
label?: undefined;
|
|
94
94
|
message?: undefined;
|
|
95
95
|
} | {
|
|
96
96
|
id?: undefined;
|
|
97
97
|
deleted?: undefined;
|
|
98
|
-
count?: undefined;
|
|
99
98
|
providers?: undefined;
|
|
99
|
+
count?: undefined;
|
|
100
100
|
found: boolean;
|
|
101
101
|
provider: import("../custom-registry.js").CustomProviderConfig;
|
|
102
102
|
registered?: undefined;
|
|
@@ -104,8 +104,8 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
|
|
|
104
104
|
message?: undefined;
|
|
105
105
|
} | {
|
|
106
106
|
deleted?: undefined;
|
|
107
|
-
count?: undefined;
|
|
108
107
|
providers?: undefined;
|
|
108
|
+
count?: undefined;
|
|
109
109
|
provider?: undefined;
|
|
110
110
|
found: boolean;
|
|
111
111
|
id: string;
|
|
@@ -113,10 +113,10 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
|
|
|
113
113
|
label?: undefined;
|
|
114
114
|
message?: undefined;
|
|
115
115
|
} | {
|
|
116
|
-
|
|
116
|
+
found?: undefined;
|
|
117
117
|
providers?: undefined;
|
|
118
|
+
count?: undefined;
|
|
118
119
|
provider?: undefined;
|
|
119
|
-
found?: undefined;
|
|
120
120
|
deleted: boolean;
|
|
121
121
|
id: string;
|
|
122
122
|
registered?: undefined;
|
|
@@ -124,10 +124,10 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
|
|
|
124
124
|
message?: undefined;
|
|
125
125
|
} | {
|
|
126
126
|
deleted?: undefined;
|
|
127
|
-
|
|
127
|
+
found?: undefined;
|
|
128
128
|
providers?: undefined;
|
|
129
|
+
count?: undefined;
|
|
129
130
|
provider?: undefined;
|
|
130
|
-
found?: undefined;
|
|
131
131
|
registered: boolean;
|
|
132
132
|
id: string;
|
|
133
133
|
label: string;
|
|
@@ -311,10 +311,10 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
|
|
|
311
311
|
notes?: string | undefined;
|
|
312
312
|
scope?: "org" | "user" | undefined;
|
|
313
313
|
}, {
|
|
314
|
-
message?: undefined;
|
|
315
314
|
id?: undefined;
|
|
316
315
|
provider?: undefined;
|
|
317
316
|
deleted?: undefined;
|
|
317
|
+
found?: undefined;
|
|
318
318
|
providers: {
|
|
319
319
|
id: string;
|
|
320
320
|
label: string;
|
|
@@ -325,45 +325,45 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
|
|
|
325
325
|
updatedAt: number;
|
|
326
326
|
}[];
|
|
327
327
|
count: number;
|
|
328
|
-
found?: undefined;
|
|
329
328
|
registered?: undefined;
|
|
330
329
|
label?: undefined;
|
|
331
|
-
} | {
|
|
332
330
|
message?: undefined;
|
|
333
|
-
|
|
331
|
+
} | {
|
|
334
332
|
id?: undefined;
|
|
335
333
|
deleted?: undefined;
|
|
334
|
+
count?: undefined;
|
|
336
335
|
providers?: undefined;
|
|
337
336
|
found: boolean;
|
|
338
337
|
provider: import("../custom-registry.js").CustomProviderConfig;
|
|
339
338
|
registered?: undefined;
|
|
340
339
|
label?: undefined;
|
|
341
|
-
} | {
|
|
342
340
|
message?: undefined;
|
|
343
|
-
|
|
341
|
+
} | {
|
|
344
342
|
provider?: undefined;
|
|
345
343
|
deleted?: undefined;
|
|
344
|
+
count?: undefined;
|
|
346
345
|
providers?: undefined;
|
|
347
346
|
found: boolean;
|
|
348
347
|
id: string;
|
|
349
348
|
registered?: undefined;
|
|
350
349
|
label?: undefined;
|
|
351
|
-
} | {
|
|
352
350
|
message?: undefined;
|
|
353
|
-
|
|
351
|
+
} | {
|
|
354
352
|
provider?: undefined;
|
|
355
|
-
providers?: undefined;
|
|
356
353
|
found?: undefined;
|
|
354
|
+
count?: undefined;
|
|
355
|
+
providers?: undefined;
|
|
357
356
|
deleted: boolean;
|
|
358
357
|
id: string;
|
|
359
358
|
registered?: undefined;
|
|
360
359
|
label?: undefined;
|
|
360
|
+
message?: undefined;
|
|
361
361
|
} | {
|
|
362
|
-
count?: undefined;
|
|
363
362
|
provider?: undefined;
|
|
364
363
|
deleted?: undefined;
|
|
365
|
-
providers?: undefined;
|
|
366
364
|
found?: undefined;
|
|
365
|
+
count?: undefined;
|
|
366
|
+
providers?: undefined;
|
|
367
367
|
registered: boolean;
|
|
368
368
|
id: string;
|
|
369
369
|
label: string;
|
|
@@ -48,8 +48,8 @@ export declare function handleUpdateResource(event: any): Promise<import("./stor
|
|
|
48
48
|
}>;
|
|
49
49
|
/** DELETE /_agent-native/resources/:id — delete a resource */
|
|
50
50
|
export declare function handleDeleteResource(event: any): Promise<{
|
|
51
|
-
ok?: undefined;
|
|
52
51
|
error: string;
|
|
52
|
+
ok?: undefined;
|
|
53
53
|
} | {
|
|
54
54
|
error?: undefined;
|
|
55
55
|
ok: boolean;
|
package/dist/secrets/routes.d.ts
CHANGED
|
@@ -34,16 +34,16 @@ export declare function createListSecretsHandler(): import("h3").EventHandlerWit
|
|
|
34
34
|
/** POST /_agent-native/secrets/:key — write a secret. */
|
|
35
35
|
export declare function createWriteSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
36
36
|
error: string;
|
|
37
|
-
ok?: undefined;
|
|
38
37
|
status?: undefined;
|
|
38
|
+
ok?: undefined;
|
|
39
39
|
} | {
|
|
40
40
|
ok: boolean;
|
|
41
41
|
status: string;
|
|
42
42
|
error?: undefined;
|
|
43
43
|
} | {
|
|
44
|
-
ok?: undefined;
|
|
45
44
|
error: string;
|
|
46
45
|
removed?: undefined;
|
|
46
|
+
ok?: undefined;
|
|
47
47
|
} | {
|
|
48
48
|
ok: boolean;
|
|
49
49
|
removed: boolean;
|
|
@@ -54,9 +54,9 @@ export declare function createWriteSecretHandler(): import("h3").EventHandlerWit
|
|
|
54
54
|
* or the current stored value without changing anything.
|
|
55
55
|
*/
|
|
56
56
|
export declare function createTestSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
57
|
-
ok?: undefined;
|
|
58
57
|
error: string;
|
|
59
58
|
note?: undefined;
|
|
59
|
+
ok?: undefined;
|
|
60
60
|
} | {
|
|
61
61
|
ok: boolean;
|
|
62
62
|
note?: undefined;
|
|
@@ -7,7 +7,7 @@ import { readBody } from "../server/h3-helpers.js";
|
|
|
7
7
|
import { EMBED_TARGET_HEADER } from "../shared/embed-auth.js";
|
|
8
8
|
import { isMcpEmbedCorsOrigin, MCP_EMBED_CORS_ALLOW_HEADERS, shouldAllowMcpEmbedCredentials, } from "../shared/mcp-embed-headers.js";
|
|
9
9
|
import { actionCallIsReadOnly, notifyActionChange } from "./action-change.js";
|
|
10
|
-
import { seedAgentRunOwnerContext, } from "./agent-run-context.js";
|
|
10
|
+
import { readBrowserSessionIdHeader, seedAgentRunOwnerContext, } from "./agent-run-context.js";
|
|
11
11
|
import { captureError } from "./capture-error.js";
|
|
12
12
|
import { getAllowedCorsOrigin as resolveAllowedCorsOrigin, readCorsAllowedOrigins, } from "./cors-origins.js";
|
|
13
13
|
import { resolveEmbedSessionFromRequest, resolvedEmbedCapabilityScope, } from "./embed-session.js";
|
|
@@ -158,7 +158,7 @@ function handleOptionsRequest(event) {
|
|
|
158
158
|
}
|
|
159
159
|
setResponseHeader(event, "Access-Control-Allow-Methods", "GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS");
|
|
160
160
|
setResponseHeader(event, "Access-Control-Allow-Headers", cors.credentials
|
|
161
|
-
? `Content-Type,Authorization,X-Requested-With,X-Request-Source,X-Agent-Native-CSRF,X-User-Timezone,X-Agent-Native-Tool-Bridge,X-Agent-Native-Tool-Id,X-Agent-Native-Frontend,X-Agent-Native-Client-Compatibility,X-Agent-Native-Build-Id,${EMBED_TARGET_HEADER}`
|
|
161
|
+
? `Content-Type,Authorization,X-Requested-With,X-Request-Source,X-Agent-Native-CSRF,X-User-Timezone,X-Agent-Native-Session-Id,X-Agent-Native-Tool-Bridge,X-Agent-Native-Tool-Id,X-Agent-Native-Frontend,X-Agent-Native-Client-Compatibility,X-Agent-Native-Build-Id,${EMBED_TARGET_HEADER}`
|
|
162
162
|
: `${MCP_EMBED_CORS_ALLOW_HEADERS},X-Agent-Native-Tool-Bridge,X-Agent-Native-Tool-Id,X-Agent-Native-Frontend,X-Agent-Native-Client-Compatibility,X-Agent-Native-Build-Id`);
|
|
163
163
|
}
|
|
164
164
|
setResponseStatus(event, 204);
|
|
@@ -350,12 +350,14 @@ export function mountActionRoutes(nitroApp, actions, options) {
|
|
|
350
350
|
: undefined;
|
|
351
351
|
}
|
|
352
352
|
const timezone = readTimezoneHeader(event);
|
|
353
|
+
const browserSessionId = readBrowserSessionIdHeader(event);
|
|
353
354
|
return runWithRequestContext({
|
|
354
355
|
userEmail,
|
|
355
356
|
userName,
|
|
356
357
|
orgId,
|
|
357
358
|
authCapability,
|
|
358
359
|
timezone,
|
|
360
|
+
browserSessionId,
|
|
359
361
|
requestOrigin: getRequestURL(event).origin,
|
|
360
362
|
// Captured here because this is the last layer that still holds
|
|
361
363
|
// the h3 event; everything below reads it off the request store.
|
|
@@ -41,6 +41,7 @@ import { captureError } from "./capture-error.js";
|
|
|
41
41
|
import { getH3App, markDefaultPluginProvided, trackPluginInit, } from "./framework-request-handler.js";
|
|
42
42
|
import { getOrigin } from "./google-oauth.js";
|
|
43
43
|
import { readBody } from "./h3-helpers.js";
|
|
44
|
+
import { startIntervalJob } from "./interval-job.js";
|
|
44
45
|
import { getModelFamilyOverlay } from "./prompts/index.js";
|
|
45
46
|
import { mountRealtimeVoiceRoutes } from "./realtime-voice.js";
|
|
46
47
|
import { runWithRequestContext, getRequestContext, getRequestOrgId, getRequestUserEmail, getRequestRunContext, ensureRequestRunContext, } from "./request-context.js";
|
|
@@ -4966,35 +4967,31 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
|
|
|
4966
4967
|
setTimeout(() => {
|
|
4967
4968
|
(async () => {
|
|
4968
4969
|
const { UNCLAIMED_BACKGROUND_RUN_FAST_SWEEP_MS } = await import("../agent/run-store.js");
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
})().catch(() => {
|
|
4995
|
-
// best-effort — never break the server
|
|
4996
|
-
});
|
|
4997
|
-
}, UNCLAIMED_BACKGROUND_RUN_FAST_SWEEP_MS);
|
|
4970
|
+
startIntervalJob(async () => {
|
|
4971
|
+
const { listUnclaimedBackgroundRunRows, shouldRedispatchUnclaimedBackgroundRun, reapAllStaleRuns, } = await import("../agent/run-store.js");
|
|
4972
|
+
// The unclaimed-background sweep below only matches
|
|
4973
|
+
// dispatch_mode='background' — handoffs a worker never
|
|
4974
|
+
// claimed. Once a worker CLAIMS a row nothing periodic looked
|
|
4975
|
+
// at it again, so a dead producer was only reaped when some
|
|
4976
|
+
// client request path or an unrelated run's cleanup happened
|
|
4977
|
+
// to notice (prod: 24 minutes after the last heartbeat,
|
|
4978
|
+
// against a 45s window). `reapAllStaleRuns` is per-row,
|
|
4979
|
+
// idempotent, re-checks staleness at UPDATE time and honours
|
|
4980
|
+
// the in-flight grace, so it is safe on this cadence.
|
|
4981
|
+
await reapAllStaleRuns().catch(() => { });
|
|
4982
|
+
let rows;
|
|
4983
|
+
try {
|
|
4984
|
+
rows = await listUnclaimedBackgroundRunRows();
|
|
4985
|
+
}
|
|
4986
|
+
catch {
|
|
4987
|
+
return; // Table may not exist yet on first boot
|
|
4988
|
+
}
|
|
4989
|
+
for (const row of rows) {
|
|
4990
|
+
if (!shouldRedispatchUnclaimedBackgroundRun(row))
|
|
4991
|
+
continue;
|
|
4992
|
+
await attemptUnclaimedBackgroundRunRedispatch(row).catch(() => { });
|
|
4993
|
+
}
|
|
4994
|
+
}, { intervalMs: UNCLAIMED_BACKGROUND_RUN_FAST_SWEEP_MS });
|
|
4998
4995
|
})().catch(() => {
|
|
4999
4996
|
// best-effort — if run-store fails to load, the slow sweep below
|
|
5000
4997
|
// still provides eventual (loud) recovery.
|
|
@@ -27,10 +27,10 @@ export declare function resolveAgentEngineApiKeyWriteTarget(event: H3Event, scop
|
|
|
27
27
|
export declare function createAgentEngineApiKeyHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
28
28
|
error: any;
|
|
29
29
|
} | {
|
|
30
|
-
error?: undefined;
|
|
31
30
|
ok: boolean;
|
|
32
31
|
key: string;
|
|
33
32
|
baseUrlKey?: string;
|
|
34
33
|
scope: AgentEngineApiKeyScope;
|
|
34
|
+
error?: undefined;
|
|
35
35
|
}>>;
|
|
36
36
|
export {};
|
|
@@ -14,9 +14,11 @@ export declare function readAgentRunTimezone(event: H3Event): string | undefined
|
|
|
14
14
|
*
|
|
15
15
|
* Emitted as PostHog's `$session_id` on the run's `$ai_*` events so an agent
|
|
16
16
|
* trace joins to the session replay it happened in. Distinct from
|
|
17
|
-
* `$ai_session_id`, which is the conversation thread.
|
|
17
|
+
* `$ai_session_id`, which is the conversation thread. Read by both the agent
|
|
18
|
+
* run path and the HTTP action route, so one visit correlates across the UI's
|
|
19
|
+
* action calls and the agent's.
|
|
18
20
|
*/
|
|
19
|
-
export declare function
|
|
21
|
+
export declare function readBrowserSessionIdHeader(event: H3Event): string | undefined;
|
|
20
22
|
export declare function seedAgentRunOwnerContext(event: H3Event, ownerContext: AgentRunOwnerContext): AgentRunOwnerContext;
|
|
21
23
|
export declare function seedBackgroundAgentRunOwnerContext(event: H3Event, runId: string): Promise<AgentRunOwnerContext | null>;
|
|
22
24
|
export declare function resolveAgentRunOwnerContext(event: H3Event, options?: {
|
|
@@ -49,9 +49,11 @@ export function readAgentRunTimezone(event) {
|
|
|
49
49
|
*
|
|
50
50
|
* Emitted as PostHog's `$session_id` on the run's `$ai_*` events so an agent
|
|
51
51
|
* trace joins to the session replay it happened in. Distinct from
|
|
52
|
-
* `$ai_session_id`, which is the conversation thread.
|
|
52
|
+
* `$ai_session_id`, which is the conversation thread. Read by both the agent
|
|
53
|
+
* run path and the HTTP action route, so one visit correlates across the UI's
|
|
54
|
+
* action calls and the agent's.
|
|
53
55
|
*/
|
|
54
|
-
export function
|
|
56
|
+
export function readBrowserSessionIdHeader(event) {
|
|
55
57
|
const raw = readHeaderValue(event, "x-agent-native-session-id");
|
|
56
58
|
const value = Array.isArray(raw) ? raw[0] : raw;
|
|
57
59
|
return typeof value === "string" &&
|
|
@@ -140,7 +142,7 @@ export async function resolveAgentRunOrgId(options) {
|
|
|
140
142
|
export async function resolveAgentRunRequestContext(options) {
|
|
141
143
|
const orgId = await resolveAgentRunOrgId(options);
|
|
142
144
|
const timezone = readAgentRunTimezone(options.event);
|
|
143
|
-
const browserSessionId =
|
|
145
|
+
const browserSessionId = readBrowserSessionIdHeader(options.event);
|
|
144
146
|
const waitUntil = requestWaitUntil(options.event);
|
|
145
147
|
const run = {
|
|
146
148
|
...(options.isBackgroundWorker ? { isBackgroundWorker: true } : {}),
|
|
@@ -33,6 +33,7 @@ import { registerBuiltinProviders } from "../tracking/providers.js";
|
|
|
33
33
|
import { validateTrackPayload } from "../tracking/route.js";
|
|
34
34
|
import { createAutomationsHandler } from "../triggers/routes.js";
|
|
35
35
|
import { createAgentEngineApiKeyHandler } from "./agent-engine-api-key-route.js";
|
|
36
|
+
import { readBrowserSessionIdHeader } from "./agent-run-context.js";
|
|
36
37
|
import { getConfiguredAppBasePath, stripAppBasePath } from "./app-base-path.js";
|
|
37
38
|
import { getAppName } from "./app-name.js";
|
|
38
39
|
import { getSession } from "./auth.js";
|
|
@@ -2544,6 +2545,7 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
2544
2545
|
try {
|
|
2545
2546
|
track(validation.name, properties, {
|
|
2546
2547
|
userId: userEmail,
|
|
2548
|
+
sessionId: readBrowserSessionIdHeader(event),
|
|
2547
2549
|
});
|
|
2548
2550
|
}
|
|
2549
2551
|
catch {
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { createServer, type CreateServerOptions, type EnvKeyConfig, } from "./create-server.js";
|
|
2
|
+
export { startIntervalJob, type IntervalJobOptions, type IntervalJobHandle, } from "./interval-job.js";
|
|
2
3
|
export { AGENT_BACKGROUND_PROCESSOR_FIELD, AGENT_BACKGROUND_PROCESSOR_ROUTE, AGENT_BACKGROUND_PROCESSOR_ROUTE_FIELD, dispatchPathTargetsNetlifyBackgroundFunction, isInBackgroundFunctionRuntime, resolveDurableBackgroundDispatchPath, } from "../agent/durable-background.js";
|
|
3
4
|
export { readBody, readBodyWithSizeLimit, streamFile, DEFAULT_CHAT_MAX_BODY_BYTES, DEFAULT_UPLOAD_MAX_FILE_BYTES, MAX_CHAT_ATTACHMENTS_PER_MESSAGE, isAllowedUploadMimeType, } from "./h3-helpers.js";
|
|
4
5
|
export { buildDeepLink, toAbsoluteOpenUrl, toDesktopOpenUrl, toVsCodeOpenUrl, OPEN_ROUTE_SUBPATH, DESKTOP_OPEN_URL, VSCODE_OPEN_URL, type DeepLinkInput, } from "./deep-link.js";
|
package/dist/server/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { createServer, } from "./create-server.js";
|
|
2
|
+
export { startIntervalJob, } from "./interval-job.js";
|
|
2
3
|
export { AGENT_BACKGROUND_PROCESSOR_FIELD, AGENT_BACKGROUND_PROCESSOR_ROUTE, AGENT_BACKGROUND_PROCESSOR_ROUTE_FIELD, dispatchPathTargetsNetlifyBackgroundFunction, isInBackgroundFunctionRuntime, resolveDurableBackgroundDispatchPath, } from "../agent/durable-background.js";
|
|
3
4
|
export { readBody, readBodyWithSizeLimit, streamFile, DEFAULT_CHAT_MAX_BODY_BYTES, DEFAULT_UPLOAD_MAX_FILE_BYTES, MAX_CHAT_ATTACHMENTS_PER_MESSAGE, isAllowedUploadMimeType, } from "./h3-helpers.js";
|
|
4
5
|
export { buildDeepLink, toAbsoluteOpenUrl, toDesktopOpenUrl, toVsCodeOpenUrl, OPEN_ROUTE_SUBPATH, DESKTOP_OPEN_URL, VSCODE_OPEN_URL, } from "./deep-link.js";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface IntervalJobOptions {
|
|
2
|
+
/** Delay in ms between runs. */
|
|
3
|
+
intervalMs: number;
|
|
4
|
+
/** Per-run timeout. Default: `Math.max(timeoutFloorMs, intervalMs * 4)`. */
|
|
5
|
+
timeoutMs?: number;
|
|
6
|
+
/** Floor used by the default `timeoutMs`. Default: 10_000. */
|
|
7
|
+
timeoutFloorMs?: number;
|
|
8
|
+
/** Called when a run throws or times out. Default: swallow (a background job shouldn't crash the host process). */
|
|
9
|
+
onError?: (err: unknown) => void;
|
|
10
|
+
/** Run immediately on start. Default: true. */
|
|
11
|
+
leading?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface IntervalJobHandle {
|
|
14
|
+
stop(): void;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Run a background job on a recurring interval without hand-rolling a
|
|
18
|
+
* `running` overlap guard or a request timeout — see `createPollEngine` for
|
|
19
|
+
* the underlying guarantees. `runOnce` keeps its normal exported signature
|
|
20
|
+
* (serverless schedulers can still call it directly, bypassing the loop);
|
|
21
|
+
* this only replaces the `setInterval` + module-level flag wiring around it.
|
|
22
|
+
* The internal timer is `.unref()`'d so it never keeps the process alive on
|
|
23
|
+
* its own.
|
|
24
|
+
*/
|
|
25
|
+
export declare function startIntervalJob(runOnce: (signal: AbortSignal) => Promise<void>, options: IntervalJobOptions): IntervalJobHandle;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createPollEngine } from "../shared/poll-engine.js";
|
|
2
|
+
/**
|
|
3
|
+
* Run a background job on a recurring interval without hand-rolling a
|
|
4
|
+
* `running` overlap guard or a request timeout — see `createPollEngine` for
|
|
5
|
+
* the underlying guarantees. `runOnce` keeps its normal exported signature
|
|
6
|
+
* (serverless schedulers can still call it directly, bypassing the loop);
|
|
7
|
+
* this only replaces the `setInterval` + module-level flag wiring around it.
|
|
8
|
+
* The internal timer is `.unref()`'d so it never keeps the process alive on
|
|
9
|
+
* its own.
|
|
10
|
+
*/
|
|
11
|
+
export function startIntervalJob(runOnce, options) {
|
|
12
|
+
const engine = createPollEngine(runOnce, options);
|
|
13
|
+
engine.start();
|
|
14
|
+
return {
|
|
15
|
+
stop() {
|
|
16
|
+
engine.stop();
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
package/dist/shared/index.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export { llmConnectionTrackingProperties, normalizeLlmConnection, type LlmConnec
|
|
|
9
9
|
export { DISPATCH_WORKSPACE_ROOT_REDIRECTS, RESERVED_WORKSPACE_APP_IDS, assertValidWorkspaceAppId, getWorkspaceAppIdValidationError, isValidWorkspaceAppIdFormat, } from "./workspace-app-id.js";
|
|
10
10
|
export { DEFAULT_WORKSPACE_APP_AUDIENCE, WORKSPACE_APP_AUDIENCES, normalizeWorkspaceAppAudience, normalizeWorkspaceAppPathList, workspaceAppAudienceFromEnv, workspaceAppAudienceFromPackageJson, workspaceAppRouteAccessFromEnv, workspaceAppRouteAccessFromPackageJson, type WorkspaceAppRouteAccess, type WorkspaceAppRouteAccessFromConfig, type WorkspaceAppAudience, } from "./workspace-app-audience.js";
|
|
11
11
|
export { AGENT_NATIVE_OPEN_PATH, AGENT_SIDEBAR_QUERY_PARAM, AGENT_SIDEBAR_QUERY_VALUE_CLOSED, isAgentNativeOpenDeepLink, withCollapsedAgentSidebarParam, } from "./agent-sidebar-url.js";
|
|
12
|
+
export { createPollEngine, type PollEngineOptions, type PollEngineHandle, } from "./poll-engine.js";
|
|
12
13
|
export { AGENT_NATIVE_DEFAULT_SOCIAL_IMAGE, AGENT_NATIVE_SOCIAL_IMAGE_CACHE_BUSTER, AGENT_NATIVE_SOCIAL_IMAGE_ALT, AGENT_NATIVE_SOCIAL_IMAGE_HEIGHT, AGENT_NATIVE_SOCIAL_IMAGE_PATH, AGENT_NATIVE_SOCIAL_IMAGE_TYPE, AGENT_NATIVE_SOCIAL_IMAGE_WIDTH, defaultSocialImageMeta, withAgentNativeSocialImageCacheBuster, withDefaultSocialImage, type SocialMetaDescriptor, } from "./social-meta.js";
|
|
13
14
|
export { EMBED_MODE_QUERY_PARAM, EMBED_SESSION_COOKIE, EMBED_START_PATH, EMBED_TOKEN_QUERY_PARAM, } from "./embed-auth.js";
|
|
14
15
|
export { AGENT_ACCESS_PARAM, DEFAULT_AGENT_ACCESS_TTL_SECONDS, appendAgentAccessParam, buildAgentAccessApiUrl, buildAgentAccessUrl, normalizeAgentAccessBasePath, normalizeAgentAccessOrigin, scopedAgentAccessResourceId, toAgentAccessUrl, type AgentAccessApiUrlOptions, type AgentAccessResourceScope, type AgentAccessUrlOptions, } from "./agent-access.js";
|
package/dist/shared/index.js
CHANGED
|
@@ -9,6 +9,7 @@ export { llmConnectionTrackingProperties, normalizeLlmConnection, } from "./llm-
|
|
|
9
9
|
export { DISPATCH_WORKSPACE_ROOT_REDIRECTS, RESERVED_WORKSPACE_APP_IDS, assertValidWorkspaceAppId, getWorkspaceAppIdValidationError, isValidWorkspaceAppIdFormat, } from "./workspace-app-id.js";
|
|
10
10
|
export { DEFAULT_WORKSPACE_APP_AUDIENCE, WORKSPACE_APP_AUDIENCES, normalizeWorkspaceAppAudience, normalizeWorkspaceAppPathList, workspaceAppAudienceFromEnv, workspaceAppAudienceFromPackageJson, workspaceAppRouteAccessFromEnv, workspaceAppRouteAccessFromPackageJson, } from "./workspace-app-audience.js";
|
|
11
11
|
export { AGENT_NATIVE_OPEN_PATH, AGENT_SIDEBAR_QUERY_PARAM, AGENT_SIDEBAR_QUERY_VALUE_CLOSED, isAgentNativeOpenDeepLink, withCollapsedAgentSidebarParam, } from "./agent-sidebar-url.js";
|
|
12
|
+
export { createPollEngine, } from "./poll-engine.js";
|
|
12
13
|
export { AGENT_NATIVE_DEFAULT_SOCIAL_IMAGE, AGENT_NATIVE_SOCIAL_IMAGE_CACHE_BUSTER, AGENT_NATIVE_SOCIAL_IMAGE_ALT, AGENT_NATIVE_SOCIAL_IMAGE_HEIGHT, AGENT_NATIVE_SOCIAL_IMAGE_PATH, AGENT_NATIVE_SOCIAL_IMAGE_TYPE, AGENT_NATIVE_SOCIAL_IMAGE_WIDTH, defaultSocialImageMeta, withAgentNativeSocialImageCacheBuster, withDefaultSocialImage, } from "./social-meta.js";
|
|
13
14
|
export { EMBED_MODE_QUERY_PARAM, EMBED_SESSION_COOKIE, EMBED_START_PATH, EMBED_TOKEN_QUERY_PARAM, } from "./embed-auth.js";
|
|
14
15
|
export { AGENT_ACCESS_PARAM, DEFAULT_AGENT_ACCESS_TTL_SECONDS, appendAgentAccessParam, buildAgentAccessApiUrl, buildAgentAccessUrl, normalizeAgentAccessBasePath, normalizeAgentAccessOrigin, scopedAgentAccessResourceId, toAgentAccessUrl, } from "./agent-access.js";
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared recurring-poll engine used by both `usePollLoop` (client) and
|
|
3
|
+
* `startIntervalJob` (server) so every polling loop in the codebase gets the
|
|
4
|
+
* same three properties for free instead of hand-rolling them per call site:
|
|
5
|
+
*
|
|
6
|
+
* 1. Never overlaps: the next attempt is scheduled via `setTimeout` only
|
|
7
|
+
* after the current one settles, not on a fixed `setInterval` cadence.
|
|
8
|
+
* This also avoids the latency `setInterval` + skip-if-busy introduces —
|
|
9
|
+
* a skipped tick there means waiting a full extra cadence before the next
|
|
10
|
+
* retry; here the gap after completion is always exactly `intervalMs`.
|
|
11
|
+
* 2. Never stalls silently: every attempt is bounded by a timeout (paired
|
|
12
|
+
* with an `AbortController` so a `fetch` can actually cancel, not just be
|
|
13
|
+
* abandoned), and that timeout reports through `onError` the moment it
|
|
14
|
+
* fires. An attempt that ignores its `signal` keeps holding the loop —
|
|
15
|
+
* reported, not hidden — because releasing the slot to the next attempt
|
|
16
|
+
* would trade a visible stall for a silent overlap.
|
|
17
|
+
* 3. Never double-fires: a single internal in-flight flag, owned by this
|
|
18
|
+
* engine rather than duplicated in every caller.
|
|
19
|
+
*/
|
|
20
|
+
export interface PollEngineOptions {
|
|
21
|
+
/** Delay in ms before the next attempt. Evaluated fresh before each schedule. */
|
|
22
|
+
intervalMs: number | (() => number);
|
|
23
|
+
/** Per-attempt timeout. Default: `Math.max(timeoutFloorMs, intervalMs * 4)`. */
|
|
24
|
+
timeoutMs?: number | (() => number);
|
|
25
|
+
/** Floor used by the default `timeoutMs`. Default: 10_000. */
|
|
26
|
+
timeoutFloorMs?: number;
|
|
27
|
+
/** Called when an attempt throws or times out. Default: swallow. */
|
|
28
|
+
onError?: (err: unknown) => void;
|
|
29
|
+
/** Run the first attempt immediately on `start()`. Default: true. */
|
|
30
|
+
leading?: boolean;
|
|
31
|
+
}
|
|
32
|
+
export interface PollEngineHandle {
|
|
33
|
+
/** Arm the loop. No-op if already running. */
|
|
34
|
+
start(): void;
|
|
35
|
+
/** Halt the loop and abort any in-flight attempt; its eventual settlement is a no-op. */
|
|
36
|
+
stop(): void;
|
|
37
|
+
/** Cancel the pending wait and run now. No-op if not running or an attempt is already in flight. */
|
|
38
|
+
pollNow(): void;
|
|
39
|
+
/**
|
|
40
|
+
* Re-arm the pending wait using a freshly resolved `intervalMs`, without
|
|
41
|
+
* running an attempt. Use this when `intervalMs` is a function whose
|
|
42
|
+
* result can change externally (e.g. tab visibility) — otherwise a
|
|
43
|
+
* change only takes effect the next time an attempt completes. No-op if
|
|
44
|
+
* there's no pending wait (not running, or an attempt is in flight).
|
|
45
|
+
*/
|
|
46
|
+
reschedule(): void;
|
|
47
|
+
readonly isRunning: boolean;
|
|
48
|
+
}
|
|
49
|
+
export declare function createPollEngine(attempt: (signal: AbortSignal) => Promise<void>, options: PollEngineOptions): PollEngineHandle;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared recurring-poll engine used by both `usePollLoop` (client) and
|
|
3
|
+
* `startIntervalJob` (server) so every polling loop in the codebase gets the
|
|
4
|
+
* same three properties for free instead of hand-rolling them per call site:
|
|
5
|
+
*
|
|
6
|
+
* 1. Never overlaps: the next attempt is scheduled via `setTimeout` only
|
|
7
|
+
* after the current one settles, not on a fixed `setInterval` cadence.
|
|
8
|
+
* This also avoids the latency `setInterval` + skip-if-busy introduces —
|
|
9
|
+
* a skipped tick there means waiting a full extra cadence before the next
|
|
10
|
+
* retry; here the gap after completion is always exactly `intervalMs`.
|
|
11
|
+
* 2. Never stalls silently: every attempt is bounded by a timeout (paired
|
|
12
|
+
* with an `AbortController` so a `fetch` can actually cancel, not just be
|
|
13
|
+
* abandoned), and that timeout reports through `onError` the moment it
|
|
14
|
+
* fires. An attempt that ignores its `signal` keeps holding the loop —
|
|
15
|
+
* reported, not hidden — because releasing the slot to the next attempt
|
|
16
|
+
* would trade a visible stall for a silent overlap.
|
|
17
|
+
* 3. Never double-fires: a single internal in-flight flag, owned by this
|
|
18
|
+
* engine rather than duplicated in every caller.
|
|
19
|
+
*/
|
|
20
|
+
const DEFAULT_TIMEOUT_FLOOR_MS = 10_000;
|
|
21
|
+
function resolve(value) {
|
|
22
|
+
return typeof value === "function" ? value() : value;
|
|
23
|
+
}
|
|
24
|
+
function maybeUnref(timer) {
|
|
25
|
+
if (timer &&
|
|
26
|
+
typeof timer === "object" &&
|
|
27
|
+
"unref" in timer &&
|
|
28
|
+
typeof timer.unref === "function") {
|
|
29
|
+
timer.unref();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export function createPollEngine(attempt, options) {
|
|
33
|
+
const timeoutFloorMs = options.timeoutFloorMs ?? DEFAULT_TIMEOUT_FLOOR_MS;
|
|
34
|
+
const getTimeoutMs = () => options.timeoutMs != null
|
|
35
|
+
? resolve(options.timeoutMs)
|
|
36
|
+
: Math.max(timeoutFloorMs, resolve(options.intervalMs) * 4);
|
|
37
|
+
const onError = options.onError ?? (() => { });
|
|
38
|
+
const leading = options.leading ?? true;
|
|
39
|
+
let generation = 0;
|
|
40
|
+
let running = false;
|
|
41
|
+
let inFlight = false;
|
|
42
|
+
let timer = null;
|
|
43
|
+
let activeController = null;
|
|
44
|
+
function clearTimer() {
|
|
45
|
+
if (timer != null) {
|
|
46
|
+
clearTimeout(timer);
|
|
47
|
+
timer = null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function schedule(gen) {
|
|
51
|
+
clearTimer();
|
|
52
|
+
timer = setTimeout(() => {
|
|
53
|
+
timer = null;
|
|
54
|
+
void tick(gen);
|
|
55
|
+
}, resolve(options.intervalMs));
|
|
56
|
+
maybeUnref(timer);
|
|
57
|
+
}
|
|
58
|
+
async function tick(gen) {
|
|
59
|
+
if (gen !== generation || !running)
|
|
60
|
+
return;
|
|
61
|
+
if (inFlight) {
|
|
62
|
+
// An attempt from a superseded generation is still settling — stop()
|
|
63
|
+
// aborted it but the caller has not returned yet. Its own `finally`
|
|
64
|
+
// sees a stale generation and will not reschedule, so re-arming here
|
|
65
|
+
// is what keeps a stop()/start() cycle (tab hidden then visible) from
|
|
66
|
+
// leaving the engine alive-but-timerless, i.e. permanently dead.
|
|
67
|
+
schedule(gen);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
inFlight = true;
|
|
71
|
+
const controller = new AbortController();
|
|
72
|
+
activeController = controller;
|
|
73
|
+
const timeoutMs = getTimeoutMs();
|
|
74
|
+
const abortTimer = setTimeout(() => controller.abort(), timeoutMs);
|
|
75
|
+
maybeUnref(abortTimer);
|
|
76
|
+
let reported = false;
|
|
77
|
+
const report = (err) => {
|
|
78
|
+
if (reported)
|
|
79
|
+
return;
|
|
80
|
+
reported = true;
|
|
81
|
+
onError(err);
|
|
82
|
+
};
|
|
83
|
+
// The attempt is retained separately from the timeout race below. The
|
|
84
|
+
// timeout reports a slow attempt immediately, but the in-flight slot is
|
|
85
|
+
// only released once the attempt itself settles: releasing on the
|
|
86
|
+
// timeout would let the next tick start while an attempt that ignores
|
|
87
|
+
// `signal` is still doing work, which is the overlap (duplicate external
|
|
88
|
+
// requests, racing writes) this engine exists to prevent. An attempt that
|
|
89
|
+
// never settles blocks further attempts by design — `onError` has already
|
|
90
|
+
// fired, so it is loud rather than silent.
|
|
91
|
+
const settled = Promise.resolve()
|
|
92
|
+
.then(() => attempt(controller.signal))
|
|
93
|
+
.then(() => { }, (err) => report(err));
|
|
94
|
+
try {
|
|
95
|
+
await Promise.race([
|
|
96
|
+
settled,
|
|
97
|
+
new Promise((_, reject) => {
|
|
98
|
+
controller.signal.addEventListener("abort", () => reject(new Error(`poll attempt timed out after ${timeoutMs}ms`)), { once: true });
|
|
99
|
+
}),
|
|
100
|
+
]);
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
report(err);
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
await settled;
|
|
107
|
+
clearTimeout(abortTimer);
|
|
108
|
+
if (activeController === controller)
|
|
109
|
+
activeController = null;
|
|
110
|
+
inFlight = false;
|
|
111
|
+
if (gen === generation && running)
|
|
112
|
+
schedule(gen);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
start() {
|
|
117
|
+
if (running)
|
|
118
|
+
return;
|
|
119
|
+
running = true;
|
|
120
|
+
generation++;
|
|
121
|
+
const gen = generation;
|
|
122
|
+
if (leading)
|
|
123
|
+
void tick(gen);
|
|
124
|
+
else
|
|
125
|
+
schedule(gen);
|
|
126
|
+
},
|
|
127
|
+
stop() {
|
|
128
|
+
running = false;
|
|
129
|
+
generation++;
|
|
130
|
+
clearTimer();
|
|
131
|
+
activeController?.abort();
|
|
132
|
+
},
|
|
133
|
+
pollNow() {
|
|
134
|
+
if (!running || inFlight)
|
|
135
|
+
return;
|
|
136
|
+
clearTimer();
|
|
137
|
+
void tick(generation);
|
|
138
|
+
},
|
|
139
|
+
reschedule() {
|
|
140
|
+
if (!running || inFlight || timer == null)
|
|
141
|
+
return;
|
|
142
|
+
schedule(generation);
|
|
143
|
+
},
|
|
144
|
+
get isRunning() {
|
|
145
|
+
return running;
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
}
|
|
@@ -29,6 +29,11 @@ Templates use `app/i18n/`:
|
|
|
29
29
|
Use BCP-47 filenames from the supported set: `en-US`, `zh-CN`, `es-ES`,
|
|
30
30
|
`fr-FR`, `de-DE`, `ja-JP`, `ko-KR`, `pt-BR`, `hi-IN`, `ar-SA`.
|
|
31
31
|
|
|
32
|
+
If an app only ships translations for a subset of that set, set
|
|
33
|
+
`supportedLocales` on the `AgentNativeI18nCatalog` to that subset. Without it,
|
|
34
|
+
`<LanguagePicker />` lists every framework-supported locale regardless of
|
|
35
|
+
whether `loadMessages` can actually resolve it.
|
|
36
|
+
|
|
32
37
|
## UI Pattern
|
|
33
38
|
|
|
34
39
|
- Wrap apps with `AppProviders i18n={{ catalog: i18nCatalog }}`.
|
|
@@ -29,6 +29,11 @@ Templates use `app/i18n/`:
|
|
|
29
29
|
Use BCP-47 filenames from the supported set: `en-US`, `zh-CN`, `es-ES`,
|
|
30
30
|
`fr-FR`, `de-DE`, `ja-JP`, `ko-KR`, `pt-BR`, `hi-IN`, `ar-SA`.
|
|
31
31
|
|
|
32
|
+
If an app only ships translations for a subset of that set, set
|
|
33
|
+
`supportedLocales` on the `AgentNativeI18nCatalog` to that subset. Without it,
|
|
34
|
+
`<LanguagePicker />` lists every framework-supported locale regardless of
|
|
35
|
+
whether `loadMessages` can actually resolve it.
|
|
36
|
+
|
|
32
37
|
## UI Pattern
|
|
33
38
|
|
|
34
39
|
- Wrap apps with `AppProviders i18n={{ catalog: i18nCatalog }}`.
|