@agent-native/core 0.196.0-nightly-20260928172902 → 0.196.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/CHANGELOG.md +17 -0
- package/corpus/templates/assets/CHANGELOG.md +18 -0
- package/corpus/templates/brain/CHANGELOG.md +6 -0
- package/corpus/templates/calendar/CHANGELOG.md +4 -0
- package/corpus/templates/clips/CHANGELOG.md +19 -0
- package/corpus/templates/clips/actions/create-recording-agent-link.ts +8 -2
- package/corpus/templates/clips/actions/update-recording.ts +5 -1
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +10 -3
- package/corpus/templates/clips/changelog/2026-09-28-agent-links-stay-valid-when-clip-details-change.md +6 -0
- package/corpus/templates/clips/changelog/2026-09-28-password-changes-immediately-invalidate-old-recording-links.md +6 -0
- package/corpus/templates/clips/changelog/2026-09-28-password-protected-recording-links-stop-working-when-the-pas.md +6 -0
- package/corpus/templates/clips/server/db/schema.ts +3 -0
- package/corpus/templates/clips/server/lib/player-video-url.ts +9 -1
- package/corpus/templates/clips/server/lib/public-agent-context.ts +12 -4
- package/corpus/templates/clips/server/lib/share-password.ts +25 -0
- package/corpus/templates/clips/server/plugins/db.ts +36 -0
- package/corpus/templates/clips/server/routes/[...page].get.ts +8 -1
- package/corpus/templates/clips/server/routes/api/public-recording.get.ts +33 -6
- package/corpus/templates/clips/server/routes/api/thumbnail/[recordingId].get.ts +34 -6
- package/corpus/templates/clips/server/routes/api/video/[recordingId].get.ts +26 -5
- package/corpus/templates/content/CHANGELOG.md +1 -0
- package/corpus/templates/crm/CHANGELOG.md +12 -0
- package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +265 -3
- package/corpus/templates/design/CHANGELOG.md +21 -0
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +463 -6
- package/corpus/templates/design/app/pages/Index.tsx +1 -1
- package/corpus/templates/design/changelog/2026-09-27-auto-focused-app-fields-no-longer-interrupt-canvas-shortcuts.md +6 -0
- package/corpus/templates/design/changelog/2026-09-28-model-picker-loads-available-models.md +6 -0
- package/corpus/templates/design/changelog/2026-09-28-template-menus-stay-available-on-hover.md +6 -0
- package/corpus/templates/dispatch/CHANGELOG.md +18 -0
- package/corpus/templates/forms/CHANGELOG.md +6 -0
- package/corpus/templates/mail/.env.example +1 -1
- package/corpus/templates/mail/CHANGELOG.md +33 -0
- package/corpus/templates/mail/changelog/2026-09-28-fixed-inbox-loading-stalls-and-showed-progress-during-google-2.md +6 -0
- package/corpus/templates/mail/changelog/2026-09-28-fixed-inbox-loading-stalls-and-showed-progress-during-google.md +6 -0
- package/corpus/templates/mail/server/db/schema.ts +7 -0
- package/corpus/templates/mail/server/lib/ai-filter-backfill.ts +4 -0
- package/corpus/templates/mail/server/lib/jobs.ts +5 -2
- package/corpus/templates/mail/server/plugins/db.ts +16 -0
- package/corpus/templates/mail/server/plugins/mail-jobs.ts +355 -44
- package/corpus/templates/slides/CHANGELOG.md +28 -0
- package/corpus/templates/slides/app/pages/Index.tsx +1 -1
- package/corpus/templates/slides/changelog/2026-09-28-model-picker-loads-available-models.md +6 -0
- package/corpus/templates/slides/changelog/2026-09-28-template-menus-stay-available-on-hover.md +6 -0
- package/dist/automation/index.d.ts +1 -1
- package/dist/client/auth/AuthPage.js +3 -2
- package/dist/client/org/OrgSwitcher.js +31 -19
- package/dist/client/settings/UsageSection.js +3 -3
- package/dist/client/use-action.js +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/jobs/background-automation-runner.d.ts +2 -0
- package/dist/jobs/background-automation-runner.js +20 -1
- package/dist/jobs/sweep-hooks.d.ts +6 -2
- package/dist/jobs/sweep-hooks.js +17 -9
- package/dist/localization/core-messages/ar-SA.js +2 -2
- package/dist/localization/core-messages/de-DE.js +2 -2
- package/dist/localization/core-messages/en-US.d.ts +2 -2
- package/dist/localization/core-messages/en-US.js +2 -2
- package/dist/localization/core-messages/es-ES.js +2 -2
- package/dist/localization/core-messages/fr-FR.js +2 -2
- package/dist/localization/core-messages/hi-IN.js +2 -2
- package/dist/localization/core-messages/ja-JP.js +2 -2
- package/dist/localization/core-messages/ko-KR.js +2 -2
- package/dist/localization/core-messages/pt-BR.js +2 -2
- package/dist/localization/core-messages/zh-CN.js +2 -2
- package/dist/localization/core-messages/zh-TW.js +2 -2
- package/dist/localization/core-messages.d.ts +2 -2
- package/dist/notifications/routes.d.ts +5 -5
- package/dist/observability/routes.d.ts +6 -6
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +11 -11
- package/dist/provider-api/actions/provider-api.d.ts +7 -7
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +4 -4
- package/dist/server/agent-chat-plugin.js +6 -2
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agent-engine-default-model-route.d.ts +1 -1
- package/dist/server/agent-engine-ollama-models-route.d.ts +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/triggers/dispatcher.js +167 -36
- package/dist/triggers/event-queue.d.ts +18 -1
- package/dist/triggers/event-queue.js +143 -10
- package/package.json +4 -4
|
@@ -314,6 +314,7 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
|
|
|
314
314
|
provider?: undefined;
|
|
315
315
|
id?: undefined;
|
|
316
316
|
deleted?: undefined;
|
|
317
|
+
found?: undefined;
|
|
317
318
|
providers: {
|
|
318
319
|
id: string;
|
|
319
320
|
label: string;
|
|
@@ -324,15 +325,14 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
|
|
|
324
325
|
updatedAt: number;
|
|
325
326
|
}[];
|
|
326
327
|
count: number;
|
|
327
|
-
found?: undefined;
|
|
328
328
|
registered?: undefined;
|
|
329
329
|
label?: undefined;
|
|
330
330
|
message?: undefined;
|
|
331
331
|
} | {
|
|
332
332
|
id?: undefined;
|
|
333
|
-
count?: undefined;
|
|
334
333
|
deleted?: undefined;
|
|
335
334
|
providers?: undefined;
|
|
335
|
+
count?: undefined;
|
|
336
336
|
found: boolean;
|
|
337
337
|
provider: import("../custom-registry.js").CustomProviderConfig;
|
|
338
338
|
registered?: undefined;
|
|
@@ -340,9 +340,9 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
|
|
|
340
340
|
message?: undefined;
|
|
341
341
|
} | {
|
|
342
342
|
provider?: undefined;
|
|
343
|
-
count?: undefined;
|
|
344
343
|
deleted?: undefined;
|
|
345
344
|
providers?: undefined;
|
|
345
|
+
count?: undefined;
|
|
346
346
|
found: boolean;
|
|
347
347
|
id: string;
|
|
348
348
|
registered?: undefined;
|
|
@@ -350,9 +350,9 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
|
|
|
350
350
|
message?: undefined;
|
|
351
351
|
} | {
|
|
352
352
|
provider?: undefined;
|
|
353
|
-
count?: undefined;
|
|
354
|
-
providers?: undefined;
|
|
355
353
|
found?: undefined;
|
|
354
|
+
providers?: undefined;
|
|
355
|
+
count?: undefined;
|
|
356
356
|
deleted: boolean;
|
|
357
357
|
id: string;
|
|
358
358
|
registered?: undefined;
|
|
@@ -360,10 +360,10 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
|
|
|
360
360
|
message?: undefined;
|
|
361
361
|
} | {
|
|
362
362
|
provider?: undefined;
|
|
363
|
-
count?: undefined;
|
|
364
363
|
deleted?: undefined;
|
|
365
|
-
providers?: undefined;
|
|
366
364
|
found?: undefined;
|
|
365
|
+
providers?: undefined;
|
|
366
|
+
count?: undefined;
|
|
367
367
|
registered: boolean;
|
|
368
368
|
id: string;
|
|
369
369
|
label: string;
|
|
@@ -39,8 +39,8 @@ export declare function handleUpdateResource(event: any): Promise<import("./stor
|
|
|
39
39
|
error: string;
|
|
40
40
|
}>;
|
|
41
41
|
export declare function handleDeleteResource(event: any): Promise<{
|
|
42
|
-
ok?: undefined;
|
|
43
42
|
error: string;
|
|
43
|
+
ok?: undefined;
|
|
44
44
|
} | {
|
|
45
45
|
error?: undefined;
|
|
46
46
|
ok: boolean;
|
package/dist/secrets/routes.d.ts
CHANGED
|
@@ -55,31 +55,31 @@ export declare function createWriteSecretHandler(): import("h3").EventHandlerWit
|
|
|
55
55
|
} | {
|
|
56
56
|
error: string;
|
|
57
57
|
errorCode?: undefined;
|
|
58
|
-
status?: undefined;
|
|
59
58
|
ok?: undefined;
|
|
59
|
+
status?: undefined;
|
|
60
60
|
} | {
|
|
61
61
|
error: string;
|
|
62
62
|
errorCode: string;
|
|
63
|
-
status?: undefined;
|
|
64
63
|
ok?: undefined;
|
|
64
|
+
status?: undefined;
|
|
65
65
|
} | {
|
|
66
66
|
errorCode?: undefined;
|
|
67
67
|
ok: boolean;
|
|
68
68
|
status: string;
|
|
69
69
|
error?: undefined;
|
|
70
70
|
} | {
|
|
71
|
+
ok?: undefined;
|
|
71
72
|
error: string;
|
|
72
73
|
removed?: undefined;
|
|
73
|
-
ok?: undefined;
|
|
74
74
|
} | {
|
|
75
75
|
ok: boolean;
|
|
76
76
|
removed: boolean;
|
|
77
77
|
error?: undefined;
|
|
78
78
|
}>>;
|
|
79
79
|
export declare function createTestSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
80
|
+
ok?: undefined;
|
|
80
81
|
error: string;
|
|
81
82
|
note?: undefined;
|
|
82
|
-
ok?: undefined;
|
|
83
83
|
} | {
|
|
84
84
|
ok: boolean;
|
|
85
85
|
note?: undefined;
|
|
@@ -33,6 +33,7 @@ import { isProductionServerlessFunctionRuntime, isTransientDatabaseError, } from
|
|
|
33
33
|
import { filterFrameworkToolGroups, resolveFrameworkTools, } from "../framework-tools.js";
|
|
34
34
|
import { verifyInternalToken, extractBearerToken, } from "../integrations/internal-token.js";
|
|
35
35
|
import { RECURRING_JOBS_SWEEP_PATH, RECURRING_JOBS_SWEEP_TOKEN_SUBJECT, } from "../jobs/scheduler-dispatch.js";
|
|
36
|
+
import { RECURRING_SWEEP_BUDGET_MS } from "../jobs/sweep-hooks.js";
|
|
36
37
|
import { McpClientManager, mcpToolsToActionEntries, syncMcpActionEntries, mountMcpServersRoutes, mountMcpHubRoutes, buildMergedConfig, startMcpConfigRefresh, getHubStatus, isHubServeEnabled, } from "../mcp-client/index.js";
|
|
37
38
|
import { declaredMcpToolNames } from "../mcp/build-server.js";
|
|
38
39
|
import { setProgressPreListHook } from "../progress/store.js";
|
|
@@ -5915,6 +5916,9 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
|
|
|
5915
5916
|
error: "Recurring-job sweep reached the synchronous server instead of the durable background worker.",
|
|
5916
5917
|
};
|
|
5917
5918
|
}
|
|
5919
|
+
const sweepContext = {
|
|
5920
|
+
deadlineAt: Date.now() + RECURRING_SWEEP_BUDGET_MS,
|
|
5921
|
+
};
|
|
5918
5922
|
// Stale reaping runs FIRST and site-wide, before the open-ended job
|
|
5919
5923
|
// sweep can spend the platform wall. It is the durable driver the
|
|
5920
5924
|
// in-process fast sweep below cannot be on serverless: that timer is
|
|
@@ -5941,6 +5945,8 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
|
|
|
5941
5945
|
console.error("[agent-chat] durable stale-run reap failed:", error);
|
|
5942
5946
|
return null;
|
|
5943
5947
|
});
|
|
5948
|
+
const { runRecurringSweepHandlers } = await import("../jobs/sweep-hooks.js");
|
|
5949
|
+
const appSweepHandlers = await runRecurringSweepHandlers(sweepContext);
|
|
5944
5950
|
// Rides the same site-tick as the reap above, for the same reason:
|
|
5945
5951
|
// it is the only durable driver on serverless. Never fatal to the
|
|
5946
5952
|
// job sweep, and its own failure is a distinguishable outcome
|
|
@@ -5961,8 +5967,6 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
|
|
|
5961
5967
|
console.error("[agent-chat] durable unclaimed-run sweep failed:", error);
|
|
5962
5968
|
return null;
|
|
5963
5969
|
});
|
|
5964
|
-
const { runRecurringSweepHandlers } = await import("../jobs/sweep-hooks.js");
|
|
5965
|
-
const appSweepHandlers = await runRecurringSweepHandlers();
|
|
5966
5970
|
const triggerAvailability = scheduledTriggerAvailability();
|
|
5967
5971
|
if (unclaimedBackgroundRuns === null) {
|
|
5968
5972
|
setResponseStatus(event, 500);
|
|
@@ -65,6 +65,7 @@ export declare function createAgentEngineApiKeyHandler(): import("h3").EventHand
|
|
|
65
65
|
error: any;
|
|
66
66
|
code: any;
|
|
67
67
|
} | {
|
|
68
|
+
error?: undefined;
|
|
68
69
|
errorCode?: undefined;
|
|
69
70
|
code?: undefined;
|
|
70
71
|
ok: boolean;
|
|
@@ -72,6 +73,5 @@ export declare function createAgentEngineApiKeyHandler(): import("h3").EventHand
|
|
|
72
73
|
baseUrlKey?: string;
|
|
73
74
|
scope: AgentEngineApiKeyScope;
|
|
74
75
|
defaultModel?: import("./agent-engine-default-model-route.js").SavedKeyDefaultModelOutcome;
|
|
75
|
-
error?: undefined;
|
|
76
76
|
}>>;
|
|
77
77
|
export {};
|
|
@@ -8,8 +8,8 @@ export declare const AGENT_ENGINE_API_KEY_ACTION_NAME = "agent-engine-api-key";
|
|
|
8
8
|
* resolveEngine's credential detection.
|
|
9
9
|
*/
|
|
10
10
|
export declare function createAgentEngineDisconnectHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
11
|
-
error: string;
|
|
12
11
|
ok?: undefined;
|
|
12
|
+
error: string;
|
|
13
13
|
scope?: undefined;
|
|
14
14
|
} | {
|
|
15
15
|
ok: boolean;
|
package/dist/server/index.d.ts
CHANGED
|
@@ -97,7 +97,7 @@ export { sendEmail, isEmailConfigured, getEmailReadiness, getEmailProvider, type
|
|
|
97
97
|
export { defineTransactionalEmail, defineTransactionalEmails, replaceTransactionalEmails, listTransactionalEmails, getTransactionalEmail, renderTransactionalEmailPreview, type TransactionalEmailDefinition, type RegisteredTransactionalEmail, } from "../email-catalog/registry.js";
|
|
98
98
|
export { notifyActivity, runActivityNotification, resolveActivityRecipients, type ActivityDeliveryFailure, type ActivityNotificationResult, type ActivityNotificationStatus, type NotifyActivityInput, type ResolveActivityRecipientsInput, } from "./activity-notifications.js";
|
|
99
99
|
export { renderEmail, emailStrong, emailQuote, emailLink, type RenderEmailArgs, type RenderedEmail, type EmailCta, } from "./email-template.js";
|
|
100
|
-
export { hasRecurringSweepHandler, registerRecurringSweepHandler, runRecurringSweepHandlers, type RecurringSweepHandler, } from "../jobs/sweep-hooks.js";
|
|
100
|
+
export { hasRecurringSweepHandler, registerRecurringSweepHandler, runRecurringSweepHandlers, type RecurringSweepContext, type RecurringSweepHandler, } from "../jobs/sweep-hooks.js";
|
|
101
101
|
export { scheduledTriggerAvailability, type ScheduledTriggerAvailability, } from "./agent-chat/recurring-jobs-runtime.js";
|
|
102
102
|
export { getAppProductionUrl, getFirstPartyProdUrl, resolveAppRuntimeUrl, } from "./app-url.js";
|
|
103
103
|
export { getConfiguredAppBasePath, normalizeAppBasePath, withConfiguredAppBasePath, } from "./app-base-path.js";
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { getOwnerActiveApiKey } from "../agent/production-agent.js";
|
|
2
2
|
import { automationMatchesEventOwner, resolveAutomationExecutionIdentity, } from "../automations/service.js";
|
|
3
|
+
import { isProductionServerlessFunctionRuntime } from "../db/client.js";
|
|
3
4
|
import { subscribe, unsubscribe } from "../event-bus/index.js";
|
|
4
5
|
import { isBackgroundAutomationRunActive, runBackgroundAutomation, } from "../jobs/background-automation-runner.js";
|
|
5
6
|
import { buildJobResourceContent, jobBelongsToApp, parseJobResource, patchJobFrontmatterFields, } from "../jobs/frontmatter.js";
|
|
7
|
+
import { registerRecurringSweepHandler, } from "../jobs/sweep-hooks.js";
|
|
6
8
|
import { resourceGetByPath, resourceListAllOwners, resourcePutIfCurrent, } from "../resources/store.js";
|
|
7
9
|
import { startIntervalJob } from "../server/interval-job.js";
|
|
8
10
|
import { evaluateCondition } from "./condition-evaluator.js";
|
|
9
|
-
import { AUTOMATION_TRIGGER_EVENT_PURGE_BATCH_SIZE, MAX_AUTOMATION_TRIGGER_EVENT_FAILURES, claimNextAutomationTriggerEvent, completeAutomationTriggerEvent, enqueueAutomationTriggerEvent, ensureAutomationTriggerEventQueue, failAutomationTriggerEvent, listReadyAutomationTriggerIds, purgeExpiredAutomationTriggerEvents, retryAutomationTriggerEvent, } from "./event-queue.js";
|
|
11
|
+
import { AUTOMATION_TRIGGER_EVENT_EXPIRY_BATCH_SIZE, AUTOMATION_TRIGGER_EVENT_PURGE_BATCH_SIZE, MAX_AUTOMATION_TRIGGER_EVENT_FAILURES, claimNextAutomationTriggerEvent, completeAutomationTriggerEvent, enqueueAutomationTriggerEvent, ensureAutomationTriggerEventQueue, expireAutomationTriggerEvent, expireStaleAutomationTriggerEvents, failAutomationTriggerEvent, getAutomationTriggerSweepCursor, listReadyAutomationTriggerIds, purgeExpiredAutomationTriggerEvents, reserveAutomationTriggerEventPurge, retryAutomationTriggerEvent, scheduleAutomationTriggerEventPurge, setAutomationTriggerSweepCursor, } from "./event-queue.js";
|
|
10
12
|
export function parseTriggerFrontmatter(content) {
|
|
11
13
|
const { meta, body } = parseJobResource(content);
|
|
12
14
|
return {
|
|
@@ -26,9 +28,16 @@ const _dispatchingTriggers = new Set();
|
|
|
26
28
|
const _drainingTriggers = new Map();
|
|
27
29
|
const MAX_TRIGGER_PAYLOAD_PROMPT_CHARS = 4_000;
|
|
28
30
|
const MAX_TRIGGER_META_CHARS = 200;
|
|
31
|
+
const DURABLE_TRIGGER_MAX_EVENT_RUN_MS = 60_000;
|
|
32
|
+
const DURABLE_TRIGGER_RUN_CLEANUP_RESERVE_MS = 30_000;
|
|
33
|
+
const DURABLE_TRIGGER_DRAIN_CONCURRENCY = 4;
|
|
34
|
+
const DURABLE_TRIGGER_READY_PAGE_SIZE = 100;
|
|
35
|
+
const MIN_DURABLE_TRIGGER_RUN_MS = 1_000;
|
|
36
|
+
const DB_QUERY_TIMEOUT_MS = 15_000;
|
|
37
|
+
const MAX_MAIL_TRIGGER_EVENT_AGE_MS = 60 * 60_000;
|
|
38
|
+
const MAIL_RECEIVED_EVENT = "mail.message.received";
|
|
29
39
|
let _deps = null;
|
|
30
40
|
let _triggerQueueWorkerStarted = false;
|
|
31
|
-
let _nextTriggerQueueCleanupAt = 0;
|
|
32
41
|
export function buildAutomationTriggerPrompt(input) {
|
|
33
42
|
let payloadStr;
|
|
34
43
|
try {
|
|
@@ -109,27 +118,21 @@ export async function initTriggerDispatcher(deps) {
|
|
|
109
118
|
_deps = deps;
|
|
110
119
|
await ensureAutomationTriggerEventQueue();
|
|
111
120
|
await refreshEventSubscriptions();
|
|
121
|
+
registerRecurringSweepHandler("automation-trigger-queue", async (context) => {
|
|
122
|
+
await drainReadyTriggerQueue(context);
|
|
123
|
+
});
|
|
112
124
|
startTriggerQueueWorker();
|
|
113
125
|
}
|
|
114
126
|
function startTriggerQueueWorker() {
|
|
115
|
-
if (_triggerQueueWorkerStarted)
|
|
127
|
+
if (_triggerQueueWorkerStarted || isProductionServerlessFunctionRuntime()) {
|
|
116
128
|
return;
|
|
129
|
+
}
|
|
117
130
|
_triggerQueueWorkerStarted = true;
|
|
118
131
|
startIntervalJob(async (signal) => {
|
|
119
132
|
const deps = _deps;
|
|
120
133
|
if (!deps || signal.aborted)
|
|
121
134
|
return;
|
|
122
|
-
|
|
123
|
-
for (const triggerId of triggerIds)
|
|
124
|
-
startTriggerDrain(triggerId);
|
|
125
|
-
if (Date.now() >= _nextTriggerQueueCleanupAt) {
|
|
126
|
-
const purged = await purgeExpiredAutomationTriggerEvents();
|
|
127
|
-
_nextTriggerQueueCleanupAt =
|
|
128
|
-
Date.now() +
|
|
129
|
-
(purged === AUTOMATION_TRIGGER_EVENT_PURGE_BATCH_SIZE
|
|
130
|
-
? 60_000
|
|
131
|
-
: 24 * 60 * 60_000);
|
|
132
|
-
}
|
|
135
|
+
await drainReadyTriggerQueue();
|
|
133
136
|
}, {
|
|
134
137
|
intervalMs: 10_000,
|
|
135
138
|
timeoutMs: 10_000,
|
|
@@ -137,6 +140,106 @@ function startTriggerQueueWorker() {
|
|
|
137
140
|
onError: (error) => console.error("[triggers] Event queue recovery scan failed:", error),
|
|
138
141
|
});
|
|
139
142
|
}
|
|
143
|
+
async function drainReadyTriggerQueue(context) {
|
|
144
|
+
const deps = _deps;
|
|
145
|
+
if (!deps)
|
|
146
|
+
return;
|
|
147
|
+
if (!context) {
|
|
148
|
+
const triggerIds = await listReadyAutomationTriggerIds(deps.appId, DURABLE_TRIGGER_READY_PAGE_SIZE);
|
|
149
|
+
for (const triggerId of triggerIds)
|
|
150
|
+
void startTriggerDrain(triggerId);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const deadline = context.deadlineAt;
|
|
154
|
+
let expiredCount = 0;
|
|
155
|
+
if (deps.appId === "mail") {
|
|
156
|
+
while (Date.now() + DB_QUERY_TIMEOUT_MS < deadline) {
|
|
157
|
+
const expired = await expireStaleAutomationTriggerEvents({
|
|
158
|
+
appId: deps.appId,
|
|
159
|
+
eventName: MAIL_RECEIVED_EVENT,
|
|
160
|
+
emittedBefore: new Date(Date.now() - MAX_MAIL_TRIGGER_EVENT_AGE_MS).toISOString(),
|
|
161
|
+
reason: "Expired because the mail event was older than 60 minutes.",
|
|
162
|
+
});
|
|
163
|
+
expiredCount += expired;
|
|
164
|
+
if (expired < AUTOMATION_TRIGGER_EVENT_EXPIRY_BATCH_SIZE)
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (expiredCount > 0) {
|
|
169
|
+
console.info(`[triggers] Expired ${expiredCount} stale ${MAIL_RECEIVED_EVENT} events.`);
|
|
170
|
+
}
|
|
171
|
+
let reclaimedExpiredCount = 0;
|
|
172
|
+
let cycleStart = await getAutomationTriggerSweepCursor(deps.appId);
|
|
173
|
+
let cursor = cycleStart;
|
|
174
|
+
let wrapped = false;
|
|
175
|
+
let madeProgress = false;
|
|
176
|
+
try {
|
|
177
|
+
while (Date.now() +
|
|
178
|
+
DURABLE_TRIGGER_RUN_CLEANUP_RESERVE_MS +
|
|
179
|
+
MIN_DURABLE_TRIGGER_RUN_MS <
|
|
180
|
+
deadline) {
|
|
181
|
+
const ready = await listReadyAutomationTriggerIds(deps.appId, DURABLE_TRIGGER_READY_PAGE_SIZE, {
|
|
182
|
+
...(cursor === null ? {} : { afterTriggerId: cursor }),
|
|
183
|
+
...(wrapped && cycleStart !== null
|
|
184
|
+
? { throughTriggerId: cycleStart }
|
|
185
|
+
: {}),
|
|
186
|
+
});
|
|
187
|
+
if (ready.length === 0) {
|
|
188
|
+
if (!wrapped && cycleStart !== null) {
|
|
189
|
+
wrapped = true;
|
|
190
|
+
cursor = null;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (madeProgress) {
|
|
194
|
+
cycleStart = await getAutomationTriggerSweepCursor(deps.appId);
|
|
195
|
+
cursor = cycleStart;
|
|
196
|
+
wrapped = false;
|
|
197
|
+
madeProgress = false;
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
const batch = ready.slice(0, DURABLE_TRIGGER_DRAIN_CONCURRENCY);
|
|
203
|
+
cursor = batch.at(-1) ?? cursor;
|
|
204
|
+
if (cursor !== null) {
|
|
205
|
+
await setAutomationTriggerSweepCursor(deps.appId, cursor);
|
|
206
|
+
}
|
|
207
|
+
const outcomes = await Promise.allSettled(batch.map((triggerId) => startTriggerDrain(triggerId, {
|
|
208
|
+
maxEvents: 1,
|
|
209
|
+
skipExisting: true,
|
|
210
|
+
expireStaleMailEvents: true,
|
|
211
|
+
deadline,
|
|
212
|
+
onStaleEventExpired: () => {
|
|
213
|
+
reclaimedExpiredCount += 1;
|
|
214
|
+
},
|
|
215
|
+
})));
|
|
216
|
+
const failures = outcomes.flatMap((outcome) => outcome.status === "rejected" ? [outcome.reason] : []);
|
|
217
|
+
if (failures.length === 1)
|
|
218
|
+
throw failures[0];
|
|
219
|
+
if (failures.length > 1) {
|
|
220
|
+
throw new AggregateError(failures, "Failed to drain queued automation trigger events.");
|
|
221
|
+
}
|
|
222
|
+
madeProgress ||= outcomes.some((outcome) => outcome.status === "fulfilled" && outcome.value);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
finally {
|
|
226
|
+
if (reclaimedExpiredCount > 0) {
|
|
227
|
+
console.info(`[triggers] Expired ${reclaimedExpiredCount} stale ${MAIL_RECEIVED_EVENT} events from reclaimed claims.`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (Date.now() + DB_QUERY_TIMEOUT_MS * 3 < deadline) {
|
|
231
|
+
const reserved = await reserveAutomationTriggerEventPurge(deps.appId);
|
|
232
|
+
if (reserved && Date.now() + DB_QUERY_TIMEOUT_MS * 2 < deadline) {
|
|
233
|
+
const purged = await purgeExpiredAutomationTriggerEvents();
|
|
234
|
+
if (Date.now() + DB_QUERY_TIMEOUT_MS < deadline) {
|
|
235
|
+
await scheduleAutomationTriggerEventPurge(deps.appId, Date.now() +
|
|
236
|
+
(purged === AUTOMATION_TRIGGER_EVENT_PURGE_BATCH_SIZE
|
|
237
|
+
? 60_000
|
|
238
|
+
: 24 * 60 * 60_000));
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
140
243
|
export async function refreshEventSubscriptions() {
|
|
141
244
|
try {
|
|
142
245
|
const jobResources = await resourceListAllOwners("jobs/");
|
|
@@ -198,7 +301,9 @@ async function handleEvent(eventName, payload, eventMeta) {
|
|
|
198
301
|
eventOwner: eventMeta.owner,
|
|
199
302
|
emittedAt: eventMeta.emittedAt,
|
|
200
303
|
});
|
|
201
|
-
|
|
304
|
+
if (!isProductionServerlessFunctionRuntime()) {
|
|
305
|
+
void startTriggerDrain(resource.id);
|
|
306
|
+
}
|
|
202
307
|
}
|
|
203
308
|
}
|
|
204
309
|
catch (err) {
|
|
@@ -206,37 +311,57 @@ async function handleEvent(eventName, payload, eventMeta) {
|
|
|
206
311
|
throw err;
|
|
207
312
|
}
|
|
208
313
|
}
|
|
209
|
-
function startTriggerDrain(triggerId) {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
})
|
|
216
|
-
.finally(() => {
|
|
217
|
-
if (_drainingTriggers.get(triggerId) === drain) {
|
|
314
|
+
function startTriggerDrain(triggerId, options) {
|
|
315
|
+
const existing = _drainingTriggers.get(triggerId);
|
|
316
|
+
if (existing)
|
|
317
|
+
return options?.skipExisting ? Promise.resolve(false) : existing;
|
|
318
|
+
const drain = drainTriggerQueue(triggerId, options?.maxEvents, options?.expireStaleMailEvents, options?.deadline, options?.onStaleEventExpired).finally(() => {
|
|
319
|
+
if (_drainingTriggers.get(triggerId) === drain)
|
|
218
320
|
_drainingTriggers.delete(triggerId);
|
|
219
|
-
|
|
321
|
+
});
|
|
322
|
+
void drain.catch((error) => {
|
|
323
|
+
console.error(`[triggers] Failed to drain queued events for trigger ${triggerId}:`, error);
|
|
220
324
|
});
|
|
221
325
|
_drainingTriggers.set(triggerId, drain);
|
|
326
|
+
return drain;
|
|
222
327
|
}
|
|
223
|
-
async function drainTriggerQueue(triggerId) {
|
|
224
|
-
|
|
328
|
+
async function drainTriggerQueue(triggerId, maxEvents = Number.POSITIVE_INFINITY, expireStaleMailEvents = false, deadline, onStaleEventExpired) {
|
|
329
|
+
let processedEvents = 0;
|
|
330
|
+
while (processedEvents < maxEvents) {
|
|
331
|
+
if (deadline !== undefined &&
|
|
332
|
+
Date.now() +
|
|
333
|
+
DURABLE_TRIGGER_RUN_CLEANUP_RESERVE_MS +
|
|
334
|
+
MIN_DURABLE_TRIGGER_RUN_MS >=
|
|
335
|
+
deadline) {
|
|
336
|
+
return processedEvents > 0;
|
|
337
|
+
}
|
|
225
338
|
const deps = _deps;
|
|
226
339
|
if (!deps)
|
|
227
|
-
return;
|
|
340
|
+
return processedEvents > 0;
|
|
228
341
|
const queued = await claimNextAutomationTriggerEvent(triggerId, deps.appId);
|
|
229
342
|
if (!queued)
|
|
230
|
-
return;
|
|
343
|
+
return processedEvents > 0;
|
|
344
|
+
processedEvents += 1;
|
|
345
|
+
if (expireStaleMailEvents &&
|
|
346
|
+
deps.appId === "mail" &&
|
|
347
|
+
queued.eventName === MAIL_RECEIVED_EVENT &&
|
|
348
|
+
Date.parse(queued.emittedAt) < Date.now() - MAX_MAIL_TRIGGER_EVENT_AGE_MS) {
|
|
349
|
+
await expireAutomationTriggerEvent(queued.id, queued.claimedAt, queued.attempts, "Expired because the mail event was older than 60 minutes.");
|
|
350
|
+
onStaleEventExpired?.();
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
231
353
|
if (queued.failureAttempts >= MAX_AUTOMATION_TRIGGER_EVENT_FAILURES) {
|
|
232
354
|
await failAutomationTriggerEvent(queued.id, queued.claimedAt, queued.attempts, queued.failureAttempts, new Error("Automation event exceeded its retry limit after worker crashes."));
|
|
233
|
-
return;
|
|
355
|
+
return true;
|
|
234
356
|
}
|
|
357
|
+
const hardDeadlineAt = deadline === undefined
|
|
358
|
+
? undefined
|
|
359
|
+
: Math.min(Date.now() + DURABLE_TRIGGER_MAX_EVENT_RUN_MS, deadline - DURABLE_TRIGGER_RUN_CLEANUP_RESERVE_MS);
|
|
235
360
|
try {
|
|
236
|
-
const result = await dispatchQueuedAutomationEvent(queued, deps);
|
|
361
|
+
const result = await dispatchQueuedAutomationEvent(queued, deps, hardDeadlineAt);
|
|
237
362
|
if (result === "retry") {
|
|
238
363
|
await retryAutomationTriggerEvent(queued.id, queued.claimedAt, queued.attempts, queued.failureAttempts, "Automation trigger is busy; the event remains queued.", { delayMs: 5_000, countFailure: false });
|
|
239
|
-
return;
|
|
364
|
+
return true;
|
|
240
365
|
}
|
|
241
366
|
await completeAutomationTriggerEvent(queued.id, queued.claimedAt, queued.attempts);
|
|
242
367
|
}
|
|
@@ -250,11 +375,12 @@ async function drainTriggerQueue(triggerId) {
|
|
|
250
375
|
await retryAutomationTriggerEvent(queued.id, queued.claimedAt, queued.attempts, queued.failureAttempts, error);
|
|
251
376
|
console.error(`[triggers] Queued event ${queued.eventId} will be retried:`, error);
|
|
252
377
|
}
|
|
253
|
-
return;
|
|
378
|
+
return true;
|
|
254
379
|
}
|
|
255
380
|
}
|
|
381
|
+
return processedEvents > 0;
|
|
256
382
|
}
|
|
257
|
-
async function dispatchQueuedAutomationEvent(queued, deps) {
|
|
383
|
+
async function dispatchQueuedAutomationEvent(queued, deps, hardDeadlineAt) {
|
|
258
384
|
const resource = await resourceGetByPath(queued.triggerOwner, queued.triggerPath);
|
|
259
385
|
if (!resource || resource.id !== queued.triggerId) {
|
|
260
386
|
return "completed";
|
|
@@ -321,13 +447,17 @@ async function dispatchQueuedAutomationEvent(queued, deps) {
|
|
|
321
447
|
const dispatchKey = `${resource.owner}:${resource.path}`;
|
|
322
448
|
if (_dispatchingTriggers.has(dispatchKey))
|
|
323
449
|
return "retry";
|
|
450
|
+
if (hardDeadlineAt !== undefined &&
|
|
451
|
+
hardDeadlineAt - Date.now() < MIN_DURABLE_TRIGGER_RUN_MS) {
|
|
452
|
+
return "retry";
|
|
453
|
+
}
|
|
324
454
|
_dispatchingTriggers.add(dispatchKey);
|
|
325
455
|
try {
|
|
326
456
|
const dispatched = await dispatchAgentic(resource, queued.payload, {
|
|
327
457
|
eventId: queued.eventId,
|
|
328
458
|
emittedAt: queued.emittedAt,
|
|
329
459
|
owner: queued.eventOwner,
|
|
330
|
-
}, identity);
|
|
460
|
+
}, identity, hardDeadlineAt);
|
|
331
461
|
return dispatched ? "completed" : "retry";
|
|
332
462
|
}
|
|
333
463
|
finally {
|
|
@@ -399,7 +529,7 @@ export async function dispatchAutomationWebhookTask(task) {
|
|
|
399
529
|
}
|
|
400
530
|
return "completed";
|
|
401
531
|
}
|
|
402
|
-
async function dispatchAgentic(resource, payload, eventMeta, identity) {
|
|
532
|
+
async function dispatchAgentic(resource, payload, eventMeta, identity, hardDeadlineAt) {
|
|
403
533
|
if (!_deps)
|
|
404
534
|
return false;
|
|
405
535
|
const triggerName = resource.path.replace(/^jobs\//, "").replace(/\.md$/, "");
|
|
@@ -494,6 +624,7 @@ async function dispatchAgentic(resource, payload, eventMeta, identity) {
|
|
|
494
624
|
triggerName,
|
|
495
625
|
policyId: runningMeta.delegatedPolicyId,
|
|
496
626
|
},
|
|
627
|
+
...(hardDeadlineAt === undefined ? {} : { hardDeadlineAt }),
|
|
497
628
|
}, _deps);
|
|
498
629
|
await recordTriggerExecutionOutcome(latest, {
|
|
499
630
|
lastStatus: "success",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type MigrationEntry } from "../db/migrations.js";
|
|
2
2
|
export declare const AUTOMATION_TRIGGER_EVENT_DEDUPE_RETENTION_MS: number;
|
|
3
3
|
export declare const AUTOMATION_TRIGGER_EVENT_PURGE_BATCH_SIZE = 1000;
|
|
4
|
+
export declare const AUTOMATION_TRIGGER_EVENT_EXPIRY_BATCH_SIZE = 1000;
|
|
4
5
|
export declare const MAX_AUTOMATION_TRIGGER_EVENT_FAILURES = 8;
|
|
5
6
|
declare const CLAIM_LEASE_MS: () => number;
|
|
6
7
|
export declare const AUTOMATION_TRIGGER_EVENT_MIGRATIONS: MigrationEntry[];
|
|
@@ -37,9 +38,24 @@ export declare function enqueueAutomationTriggerEvent(input: EnqueueAutomationTr
|
|
|
37
38
|
id: string;
|
|
38
39
|
inserted: boolean;
|
|
39
40
|
}>;
|
|
40
|
-
export declare function listReadyAutomationTriggerIds(appId?: string | null, limit?: number
|
|
41
|
+
export declare function listReadyAutomationTriggerIds(appId?: string | null, limit?: number, cursor?: {
|
|
42
|
+
afterTriggerId?: string;
|
|
43
|
+
throughTriggerId?: string;
|
|
44
|
+
}): Promise<string[]>;
|
|
45
|
+
export declare function reserveAutomationTriggerEventPurge(appId?: string | null, now?: number): Promise<boolean>;
|
|
46
|
+
export declare function scheduleAutomationTriggerEventPurge(appId: string | null | undefined, nextAt: number): Promise<void>;
|
|
47
|
+
export declare function getAutomationTriggerSweepCursor(appId?: string | null): Promise<string | null>;
|
|
48
|
+
export declare function setAutomationTriggerSweepCursor(appId: string | null | undefined, triggerId: string | null): Promise<void>;
|
|
41
49
|
export declare function claimNextAutomationTriggerEvent(triggerId: string, appId?: string | null): Promise<QueuedAutomationTriggerEvent | null>;
|
|
42
50
|
export declare function completeAutomationTriggerEvent(id: string, claimedAt: number, attempts: number): Promise<void>;
|
|
51
|
+
export declare function expireAutomationTriggerEvent(id: string, claimedAt: number, attempts: number, reason: string): Promise<void>;
|
|
52
|
+
export declare function expireStaleAutomationTriggerEvents(input: {
|
|
53
|
+
appId: string;
|
|
54
|
+
eventName: string;
|
|
55
|
+
emittedBefore: string;
|
|
56
|
+
reason: string;
|
|
57
|
+
limit?: number;
|
|
58
|
+
}): Promise<number>;
|
|
43
59
|
export declare function retryAutomationTriggerEvent(id: string, claimedAt: number, attempts: number, failureAttempts: number, error: unknown, options?: {
|
|
44
60
|
delayMs?: number;
|
|
45
61
|
countFailure?: boolean;
|
|
@@ -48,6 +64,7 @@ export declare function failAutomationTriggerEvent(id: string, claimedAt: number
|
|
|
48
64
|
export declare function purgeExpiredAutomationTriggerEvents(now?: number): Promise<number>;
|
|
49
65
|
export declare const __automationTriggerEventQueue: {
|
|
50
66
|
table: string;
|
|
67
|
+
sweepStateTable: string;
|
|
51
68
|
claimLeaseMs: typeof CLAIM_LEASE_MS;
|
|
52
69
|
};
|
|
53
70
|
export {};
|