@agent-native/core 0.0.0-beta-20260820214042 → 0.0.0-beta-20260820220930
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/dist/client/agent-page/AgentJobsTab.js +39 -6
- package/dist/client/agent-page/AutomationScheduleDialog.d.ts +10 -1
- package/dist/client/agent-page/AutomationScheduleDialog.js +3 -2
- package/dist/client/agent-page/ScheduledTriggerNotice.d.ts +22 -0
- package/dist/client/agent-page/ScheduledTriggerNotice.js +84 -0
- package/dist/client/agent-page/scheduled-trigger-state.d.ts +28 -0
- package/dist/client/agent-page/scheduled-trigger-state.js +10 -0
- package/dist/client/agent-page/use-jobs.d.ts +7 -0
- package/dist/client/agent-page/use-jobs.js +14 -0
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/deploy/build.d.ts +5 -0
- package/dist/deploy/build.js +12 -1
- package/dist/framework-tools.js +1 -0
- package/dist/jobs/actions/get-scheduled-trigger-status.d.ts +13 -0
- package/dist/jobs/actions/get-scheduled-trigger-status.js +21 -0
- package/dist/localization/default-messages.d.ts +14 -0
- package/dist/localization/default-messages.js +14 -0
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +3 -3
- package/dist/provider-api/actions/custom-provider-registration.d.ts +4 -4
- package/dist/provider-api/actions/provider-api.d.ts +13 -13
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/server/action-discovery.js +4 -0
- package/dist/server/agent-chat/recurring-jobs-runtime.d.ts +42 -1
- package/dist/server/agent-chat/recurring-jobs-runtime.js +102 -9
- package/dist/server/agent-chat-plugin.d.ts +3 -1
- package/dist/server/agent-chat-plugin.js +3 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/vite/client.js +10 -0
- package/docs/content/recurring-jobs.mdx +6 -0
- package/package.json +3 -3
|
@@ -11,7 +11,9 @@ import { AgentEmptyState } from "./AgentEmptyState.js";
|
|
|
11
11
|
import { AgentTabFrame } from "./AgentTabFrame.js";
|
|
12
12
|
import { AutomationDetailsDialog, } from "./AutomationDetailsDialog.js";
|
|
13
13
|
import { AutomationScheduleDialog } from "./AutomationScheduleDialog.js";
|
|
14
|
-
import {
|
|
14
|
+
import { scheduleFiringFor, } from "./scheduled-trigger-state.js";
|
|
15
|
+
import { ScheduledTriggerNotice } from "./ScheduledTriggerNotice.js";
|
|
16
|
+
import { useAutomations, useManageAutomation, useManageRecurringJob, useRunAutomationNow, useRecurringJobs, useScheduledTriggerState, } from "./use-jobs.js";
|
|
15
17
|
function listRecurringJobs(jobs) {
|
|
16
18
|
return jobs.map((resource) => ({
|
|
17
19
|
kind: "recurring",
|
|
@@ -37,7 +39,33 @@ function describeTrigger(entry, t) {
|
|
|
37
39
|
entry.resource.schedule ||
|
|
38
40
|
t("jobs.scheduledTrigger", { defaultValue: "Scheduled" }));
|
|
39
41
|
}
|
|
40
|
-
|
|
42
|
+
/**
|
|
43
|
+
* `nextRun` is computed from the cron expression, not from anything that
|
|
44
|
+
* promises to run it, so it must never be presented with more confidence than
|
|
45
|
+
* the scheduler check earned. Three answers, three phrasings: a known-dead
|
|
46
|
+
* scheduler replaces the date, an unverified one qualifies it, and a confirmed
|
|
47
|
+
* one shows it plainly.
|
|
48
|
+
*/
|
|
49
|
+
function nextRunValue(entry, t, formatDateTime, scheduleFiring, unset) {
|
|
50
|
+
const formatted = formatDateTime(entry.resource.nextRun);
|
|
51
|
+
if (entry.triggerType !== "schedule" || scheduleFiring === "fires") {
|
|
52
|
+
return formatted ?? unset;
|
|
53
|
+
}
|
|
54
|
+
if (scheduleFiring === "never") {
|
|
55
|
+
return t("jobs.nextRunNeverScheduler", {
|
|
56
|
+
defaultValue: "Never — no scheduler in this deploy",
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return formatted
|
|
60
|
+
? t("jobs.nextRunSchedulerUnknown", {
|
|
61
|
+
defaultValue: "{{date}} — unconfirmed, the scheduler check failed",
|
|
62
|
+
date: formatted,
|
|
63
|
+
})
|
|
64
|
+
: t("jobs.nextRunSchedulerUnknownNoDate", {
|
|
65
|
+
defaultValue: "Unknown — the scheduler check failed",
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function detailsFields(entry, t, formatDateTime, scheduleFiring) {
|
|
41
69
|
const resource = entry.resource;
|
|
42
70
|
const unset = t("jobs.notSet", { defaultValue: "—" });
|
|
43
71
|
const fields = [
|
|
@@ -66,7 +94,7 @@ function detailsFields(entry, t, formatDateTime) {
|
|
|
66
94
|
}
|
|
67
95
|
fields.push({
|
|
68
96
|
label: t("jobs.nextRun", { defaultValue: "Next run" }),
|
|
69
|
-
value:
|
|
97
|
+
value: nextRunValue(entry, t, formatDateTime, scheduleFiring, unset),
|
|
70
98
|
}, {
|
|
71
99
|
label: t("jobs.lastRun", { defaultValue: "Last run" }),
|
|
72
100
|
value: formatDateTime(resource.lastRun) ??
|
|
@@ -104,6 +132,11 @@ export function AgentJobsTab({ canManageOrg = false, hideHeader = false, }) {
|
|
|
104
132
|
const personalAutomationsQuery = useAutomations("user");
|
|
105
133
|
const organizationJobsQuery = useRecurringJobs("org");
|
|
106
134
|
const organizationAutomationsQuery = useAutomations("org");
|
|
135
|
+
const scheduledTriggerState = useScheduledTriggerState();
|
|
136
|
+
// Three states, not `!== false`: that expression also swallowed a FAILED
|
|
137
|
+
// status query, which then licensed a confident "Next run" forever. See
|
|
138
|
+
// `scheduleFiringFor` for which way each state leans and why.
|
|
139
|
+
const scheduleFiring = scheduleFiringFor(scheduledTriggerState);
|
|
107
140
|
const personalJobsMutation = useManageRecurringJob("user");
|
|
108
141
|
const personalAutomationsMutation = useManageAutomation("user");
|
|
109
142
|
const organizationJobsMutation = useManageRecurringJob("org");
|
|
@@ -240,7 +273,7 @@ export function AgentJobsTab({ canManageOrg = false, hideHeader = false, }) {
|
|
|
240
273
|
defaultValue: "Create an automation",
|
|
241
274
|
}), label: t("jobs.newAutomation", {
|
|
242
275
|
defaultValue: "New automation",
|
|
243
|
-
}) }), children: [_jsxs("div", { className: "space-y-7", children: [hideHeader ? (_jsx("div", { className: "flex justify-end", children: _jsx(AgentAskPopover, { context: automationCreationContext(), prompt: t("jobs.automationPrompt", {
|
|
276
|
+
}) }), children: [_jsxs("div", { className: "space-y-7", children: [_jsx(ScheduledTriggerNotice, { state: scheduledTriggerState }), hideHeader ? (_jsx("div", { className: "flex justify-end", children: _jsx(AgentAskPopover, { context: automationCreationContext(), prompt: t("jobs.automationPrompt", {
|
|
244
277
|
defaultValue: "Create an automation that does this: ",
|
|
245
278
|
}), title: t("jobs.automationsCreateTitle", {
|
|
246
279
|
defaultValue: "Create an automation",
|
|
@@ -296,9 +329,9 @@ export function AgentJobsTab({ canManageOrg = false, hideHeader = false, }) {
|
|
|
296
329
|
name: runTarget.resource.name,
|
|
297
330
|
scope: runTarget.resource.scope,
|
|
298
331
|
}, { onSuccess: () => setRunTarget(null) });
|
|
299
|
-
}, children: [runAutomationMutation.isPending ? (_jsx(IconLoader2, { className: "size-4 animate-spin" })) : (_jsx(IconPlayerPlay, { className: "size-4" })), t("jobs.runNow", { defaultValue: "Run now" })] })] })] }) }), detailsTarget ? (_jsx(AutomationDetailsDialog, { open: true, name: detailsTarget.resource.name, scope: detailsTarget.resource.scope === "organization" ? "org" : "user", triggerSummary: describeTrigger(detailsTarget, t), fields: detailsFields(detailsTarget, t, formatDateTime), condition: detailsTarget.kind === "automation"
|
|
332
|
+
}, children: [runAutomationMutation.isPending ? (_jsx(IconLoader2, { className: "size-4 animate-spin" })) : (_jsx(IconPlayerPlay, { className: "size-4" })), t("jobs.runNow", { defaultValue: "Run now" })] })] })] }) }), detailsTarget ? (_jsx(AutomationDetailsDialog, { open: true, name: detailsTarget.resource.name, scope: detailsTarget.resource.scope === "organization" ? "org" : "user", triggerSummary: describeTrigger(detailsTarget, t), fields: detailsFields(detailsTarget, t, formatDateTime, scheduleFiring), condition: detailsTarget.kind === "automation"
|
|
300
333
|
? detailsTarget.resource.condition
|
|
301
334
|
: null, instructions: detailsTarget.kind === "automation"
|
|
302
335
|
? detailsTarget.resource.body
|
|
303
|
-
: detailsTarget.resource.instructions, mcpTools: detailsTarget.resource.mcpTools ?? [], lastError: detailsTarget.resource.lastError, formatTimestamp: (value) => formatDateTime(new Date(value).toISOString()) ?? String(value), onClose: () => setDetailsTarget(null) })) : null, scheduleTarget ? (_jsx(AutomationScheduleDialog, { open: true, name: scheduleTarget.resource.name, schedule: scheduleTarget.resource.schedule ?? "", timezone: scheduleTarget.resource.timezone ?? null, saving: mutationPending, error: mutationError ? mutationError.message : null, onCancel: () => setScheduleTarget(null), onSave: (next) => mutateEntry(scheduleTarget, "update", next, () => setScheduleTarget(null)) })) : null] }));
|
|
336
|
+
: detailsTarget.resource.instructions, mcpTools: detailsTarget.resource.mcpTools ?? [], lastError: detailsTarget.resource.lastError, formatTimestamp: (value) => formatDateTime(new Date(value).toISOString()) ?? String(value), onClose: () => setDetailsTarget(null) })) : null, scheduleTarget ? (_jsx(AutomationScheduleDialog, { open: true, name: scheduleTarget.resource.name, schedule: scheduleTarget.resource.schedule ?? "", timezone: scheduleTarget.resource.timezone ?? null, saving: mutationPending, error: mutationError ? mutationError.message : null, scheduledTriggerState: scheduledTriggerState, onCancel: () => setScheduleTarget(null), onSave: (next) => mutateEntry(scheduleTarget, "update", next, () => setScheduleTarget(null)) })) : null] }));
|
|
304
337
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ScheduledTriggerState } from "./scheduled-trigger-state.js";
|
|
1
2
|
export interface AutomationScheduleDialogProps {
|
|
2
3
|
open: boolean;
|
|
3
4
|
name: string;
|
|
@@ -5,10 +6,18 @@ export interface AutomationScheduleDialogProps {
|
|
|
5
6
|
timezone: string | null;
|
|
6
7
|
saving: boolean;
|
|
7
8
|
error?: string | null;
|
|
9
|
+
/**
|
|
10
|
+
* Passed in rather than queried here so this dialog stays presentational, the
|
|
11
|
+
* way the rest of its props already are. Carries the whole query state so a
|
|
12
|
+
* failed check is distinguishable from a healthy one — this dialog is where
|
|
13
|
+
* someone is actively picking a cadence, so an unverified scheduler is exactly
|
|
14
|
+
* what they need told.
|
|
15
|
+
*/
|
|
16
|
+
scheduledTriggerState: ScheduledTriggerState;
|
|
8
17
|
onCancel: () => void;
|
|
9
18
|
onSave: (next: {
|
|
10
19
|
schedule: string;
|
|
11
20
|
timezone: string;
|
|
12
21
|
}) => void;
|
|
13
22
|
}
|
|
14
|
-
export declare function AutomationScheduleDialog({ open, name, schedule, timezone, saving, error, onCancel, onSave, }: AutomationScheduleDialogProps): import("react").JSX.Element;
|
|
23
|
+
export declare function AutomationScheduleDialog({ open, name, schedule, timezone, saving, error, scheduledTriggerState, onCancel, onSave, }: AutomationScheduleDialogProps): import("react").JSX.Element;
|
|
@@ -5,6 +5,7 @@ import { IconLoader2 } from "@tabler/icons-react";
|
|
|
5
5
|
import { useEffect, useState } from "react";
|
|
6
6
|
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "../components/ui/dialog.js";
|
|
7
7
|
import { useT } from "../i18n.js";
|
|
8
|
+
import { ScheduledTriggerNotice } from "./ScheduledTriggerNotice.js";
|
|
8
9
|
import { TimezoneSelect, browserTimezone } from "./TimezoneSelect.js";
|
|
9
10
|
const PRESETS = [
|
|
10
11
|
{ label: "Every hour", cron: "0 * * * *" },
|
|
@@ -16,7 +17,7 @@ const CRON_FIELD_COUNT = 5;
|
|
|
16
17
|
function looksLikeCron(value) {
|
|
17
18
|
return value.trim().split(/\s+/).length === CRON_FIELD_COUNT;
|
|
18
19
|
}
|
|
19
|
-
export function AutomationScheduleDialog({ open, name, schedule, timezone, saving, error, onCancel, onSave, }) {
|
|
20
|
+
export function AutomationScheduleDialog({ open, name, schedule, timezone, saving, error, scheduledTriggerState, onCancel, onSave, }) {
|
|
20
21
|
const t = useT();
|
|
21
22
|
const [value, setValue] = useState(schedule);
|
|
22
23
|
const [zone, setZone] = useState(timezone || browserTimezone());
|
|
@@ -37,7 +38,7 @@ export function AutomationScheduleDialog({ open, name, schedule, timezone, savin
|
|
|
37
38
|
name: name.replace(/-/g, " "),
|
|
38
39
|
}) }), _jsx(DialogDescription, { children: t("jobs.editScheduleDescription", {
|
|
39
40
|
defaultValue: "The clock time below is read in the timezone you pick, so 8:00 means 8:00 there.",
|
|
40
|
-
}) })] }), _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { children: [_jsx("label", { className: "text-xs font-medium text-foreground", htmlFor: "automation-schedule", children: t("jobs.cronExpression", { defaultValue: "Cron expression" }) }), _jsx(Input, { id: "automation-schedule", className: "mt-1 font-mono text-sm", value: value, spellCheck: false, autoComplete: "off", disabled: saving, onChange: (event) => setValue(event.target.value) }), _jsx("p", { className: "mt-1 text-[11px] text-muted-foreground", children: t("jobs.cronFormatHint", {
|
|
41
|
+
}) })] }), _jsxs("div", { className: "space-y-3", children: [_jsx(ScheduledTriggerNotice, { state: scheduledTriggerState, variant: "inline" }), _jsxs("div", { children: [_jsx("label", { className: "text-xs font-medium text-foreground", htmlFor: "automation-schedule", children: t("jobs.cronExpression", { defaultValue: "Cron expression" }) }), _jsx(Input, { id: "automation-schedule", className: "mt-1 font-mono text-sm", value: value, spellCheck: false, autoComplete: "off", disabled: saving, onChange: (event) => setValue(event.target.value) }), _jsx("p", { className: "mt-1 text-[11px] text-muted-foreground", children: t("jobs.cronFormatHint", {
|
|
41
42
|
defaultValue: "minute hour day-of-month month day-of-week",
|
|
42
43
|
}) })] }), _jsx("div", { className: "flex flex-wrap gap-1.5", children: PRESETS.map((preset) => (_jsx(Button, { type: "button", variant: "outline", size: "sm", className: "h-7 cursor-pointer text-[11px]", disabled: saving, onClick: () => setValue(preset.cron), children: preset.label }, preset.cron))) }), _jsxs("div", { children: [_jsx("label", { className: "text-xs font-medium text-foreground", htmlFor: "automation-timezone", children: t("jobs.timezone", { defaultValue: "Timezone" }) }), _jsx("div", { className: "mt-1", children: _jsx(TimezoneSelect, { id: "automation-timezone", value: zone, disabled: saving, onChange: setZone, suggested: [browserTimezone()] }) })] }), trimmed && !valid ? (_jsx("p", { className: "text-xs text-destructive", children: t("jobs.cronFieldCount", {
|
|
43
44
|
defaultValue: "A cron expression needs exactly 5 fields.",
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ScheduledTriggerState } from "./scheduled-trigger-state.js";
|
|
2
|
+
export interface ScheduledTriggerNoticeProps {
|
|
3
|
+
state: ScheduledTriggerState;
|
|
4
|
+
/** `banner` heads the page; `inline` sits inside the schedule dialog. */
|
|
5
|
+
variant?: "banner" | "inline";
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Warns that schedule-triggered automations will not fire in this deploy.
|
|
9
|
+
*
|
|
10
|
+
* Worth a persistent surface rather than a one-time toast: whether a schedule
|
|
11
|
+
* fires is decided by the BUILD, while automations are created at runtime, so
|
|
12
|
+
* the mismatch is invisible at the moment it bites. Without this, a scheduled
|
|
13
|
+
* automation reports "Enabled" with a plausible "Next run" and silently never
|
|
14
|
+
* runs. Shown on the Automations page and again inside the schedule editor,
|
|
15
|
+
* which is the one place a user is actively choosing a cadence.
|
|
16
|
+
*
|
|
17
|
+
* Takes the whole query state, not just a resolved status, because a status
|
|
18
|
+
* check that FAILED has to look different from one that came back healthy: the
|
|
19
|
+
* failure is why the next-run dates alongside it are unverified, and silently
|
|
20
|
+
* rendering nothing would present them as confirmed.
|
|
21
|
+
*/
|
|
22
|
+
export declare function ScheduledTriggerNotice({ state, variant, }: ScheduledTriggerNoticeProps): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { IconAlertCircle, IconAlertTriangle, IconChevronRight, } from "@tabler/icons-react";
|
|
3
|
+
import { useT } from "../i18n.js";
|
|
4
|
+
/**
|
|
5
|
+
* Warns that schedule-triggered automations will not fire in this deploy.
|
|
6
|
+
*
|
|
7
|
+
* Worth a persistent surface rather than a one-time toast: whether a schedule
|
|
8
|
+
* fires is decided by the BUILD, while automations are created at runtime, so
|
|
9
|
+
* the mismatch is invisible at the moment it bites. Without this, a scheduled
|
|
10
|
+
* automation reports "Enabled" with a plausible "Next run" and silently never
|
|
11
|
+
* runs. Shown on the Automations page and again inside the schedule editor,
|
|
12
|
+
* which is the one place a user is actively choosing a cadence.
|
|
13
|
+
*
|
|
14
|
+
* Takes the whole query state, not just a resolved status, because a status
|
|
15
|
+
* check that FAILED has to look different from one that came back healthy: the
|
|
16
|
+
* failure is why the next-run dates alongside it are unverified, and silently
|
|
17
|
+
* rendering nothing would present them as confirmed.
|
|
18
|
+
*/
|
|
19
|
+
export function ScheduledTriggerNotice({ state, variant = "banner", }) {
|
|
20
|
+
const t = useT();
|
|
21
|
+
// Still in flight: resolves in milliseconds, so say nothing rather than
|
|
22
|
+
// flash a caveat at every reader on a perfectly healthy deploy.
|
|
23
|
+
if (state.kind === "loading")
|
|
24
|
+
return null;
|
|
25
|
+
const shell = variant === "banner"
|
|
26
|
+
? "flex items-start gap-2.5 rounded-xl border px-4 py-3"
|
|
27
|
+
: "flex items-start gap-2 rounded-lg border px-3 py-2";
|
|
28
|
+
// Muted rather than amber: "we could not check" is a weaker claim than "this
|
|
29
|
+
// will not run", and dressing it identically would teach readers to discount
|
|
30
|
+
// the one that means something.
|
|
31
|
+
if (state.kind === "unknown") {
|
|
32
|
+
return (_jsxs("div", { role: "status", "data-testid": "scheduled-trigger-notice", "data-reason": "check-failed", className: `${shell} border-border/70 bg-muted/50`, children: [_jsx(IconAlertCircle, { className: "mt-px size-4 shrink-0 text-muted-foreground" }), _jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "text-xs font-medium text-foreground", children: t("jobs.scheduleUnknownTitle", {
|
|
33
|
+
defaultValue: "Couldn't check whether schedules run here",
|
|
34
|
+
}) }), _jsx("p", { className: "mt-0.5 text-[11px] leading-4 text-muted-foreground", children: t("jobs.scheduleUnknownDetail", {
|
|
35
|
+
defaultValue: "The scheduler status check failed, so the next run times below " +
|
|
36
|
+
"are unconfirmed. Reload to check again.",
|
|
37
|
+
}) })] })] }));
|
|
38
|
+
}
|
|
39
|
+
const status = state.status;
|
|
40
|
+
if (status.available)
|
|
41
|
+
return null;
|
|
42
|
+
const headline = status.reason === "local-development"
|
|
43
|
+
? t("jobs.scheduleUnavailableLocalTitle", {
|
|
44
|
+
defaultValue: "Schedules don't run in local development",
|
|
45
|
+
})
|
|
46
|
+
: t("jobs.scheduleUnavailableTitle", {
|
|
47
|
+
defaultValue: "Schedules won't run in this deploy",
|
|
48
|
+
});
|
|
49
|
+
const detail = status.reason === "disabled-by-env"
|
|
50
|
+
? t("jobs.scheduleUnavailableDisabled", {
|
|
51
|
+
defaultValue: "This app was built with recurring jobs turned off, so no scheduled " +
|
|
52
|
+
"automation will fire. Event-triggered automations and Run now still work.",
|
|
53
|
+
})
|
|
54
|
+
: status.reason === "no-platform-scheduler"
|
|
55
|
+
? t("jobs.scheduleUnavailableNoScheduler", {
|
|
56
|
+
defaultValue: "This hosting target has no durable scheduler, so no scheduled automation will fire. Event-triggered automations and Run now still work.",
|
|
57
|
+
})
|
|
58
|
+
: t("jobs.scheduleUnavailableLocal", {
|
|
59
|
+
defaultValue: "Schedules stay off on a dev machine unless you opt in. " +
|
|
60
|
+
"Event-triggered automations and Run now still work.",
|
|
61
|
+
});
|
|
62
|
+
/**
|
|
63
|
+
* Recovery steps, collapsed by default: the notice has to say what is wrong
|
|
64
|
+
* before it says how to change it, and the how is longer than the what. Null
|
|
65
|
+
* where nothing is actually toggleable — `no-platform-scheduler` means the
|
|
66
|
+
* host has no scheduler to enable, so an empty disclosure would promise a fix
|
|
67
|
+
* that does not exist.
|
|
68
|
+
*/
|
|
69
|
+
const fix = status.reason === "disabled-by-env"
|
|
70
|
+
? t("jobs.scheduleUnavailableDisabledFix", {
|
|
71
|
+
defaultValue: "To enable recurring jobs, set AGENT_NATIVE_DISABLE_RECURRING_JOBS=false in the build environment.",
|
|
72
|
+
})
|
|
73
|
+
: status.reason === "local-development"
|
|
74
|
+
? t("jobs.scheduleUnavailableLocalFix", {
|
|
75
|
+
defaultValue: "Set AGENT_NATIVE_ENABLE_LOCAL_RECURRING_JOBS=true to run " +
|
|
76
|
+
"schedules on this machine.",
|
|
77
|
+
})
|
|
78
|
+
: null;
|
|
79
|
+
return (_jsxs("div", { role: "status", "data-testid": "scheduled-trigger-notice", "data-reason": status.reason, className: `${shell} border-amber-500/40 bg-amber-500/10`, children: [_jsx(IconAlertTriangle, { className: "mt-px size-4 shrink-0 text-amber-600 dark:text-amber-400" }), _jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "text-xs font-medium text-amber-700 dark:text-amber-300", children: headline }), _jsx("p", { className: "mt-0.5 text-[11px] leading-4 text-amber-700/90 dark:text-amber-300/90", children: detail }), fix ? (_jsxs("details", { className: "group mt-1", children: [_jsxs("summary", { className: "-ml-1.5 inline-flex w-fit cursor-pointer select-none list-none items-center gap-1 rounded-md px-1.5 py-1 text-[11px] font-medium text-amber-700 transition-colors hover:bg-amber-500/15 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-amber-500/40 dark:text-amber-300 dark:hover:bg-amber-400/15 [&::-webkit-details-marker]:hidden", children: [_jsx(IconChevronRight, { className: "size-3 shrink-0 transition-transform duration-150 group-open:rotate-90" }), _jsx("span", { className: "group-open:hidden", children: t("jobs.scheduleUnavailableFixLabel", {
|
|
80
|
+
defaultValue: "Show more",
|
|
81
|
+
}) }), _jsx("span", { className: "hidden group-open:inline", children: t("jobs.scheduleUnavailableFixLabelOpen", {
|
|
82
|
+
defaultValue: "Show less",
|
|
83
|
+
}) })] }), _jsx("p", { className: "mt-1 whitespace-pre-line text-[11px] leading-4 text-amber-700/90 dark:text-amber-300/90", children: fix })] })) : null] })] }));
|
|
84
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ScheduledTriggerStatus } from "../../jobs/actions/get-scheduled-trigger-status.js";
|
|
2
|
+
export type { ScheduledTriggerStatus };
|
|
3
|
+
/**
|
|
4
|
+
* Three-state view of the scheduler status query.
|
|
5
|
+
*
|
|
6
|
+
* `unknown` exists so a FAILED check cannot pass for a healthy one. The status
|
|
7
|
+
* action can 404 on an older deploy, 403 behind an auth boundary, or fail
|
|
8
|
+
* discovery — none of which say anything about whether schedules fire, and all
|
|
9
|
+
* of which read as "fine" under a plain `data?.available !== false`. `loading`
|
|
10
|
+
* stays separate because it resolves on its own in milliseconds; an error does
|
|
11
|
+
* not.
|
|
12
|
+
*
|
|
13
|
+
* Deliberately kept in its own module, free of the query hook: the pure
|
|
14
|
+
* state→consequence mapping below is what the UI branches on, and it stays real
|
|
15
|
+
* in tests that mock the data layer.
|
|
16
|
+
*/
|
|
17
|
+
export type ScheduledTriggerState = {
|
|
18
|
+
kind: "loading";
|
|
19
|
+
} | {
|
|
20
|
+
kind: "unknown";
|
|
21
|
+
error: Error | null;
|
|
22
|
+
} | {
|
|
23
|
+
kind: "resolved";
|
|
24
|
+
status: ScheduledTriggerStatus;
|
|
25
|
+
};
|
|
26
|
+
/** Whether a schedule-triggered automation will fire, as far as the UI knows. */
|
|
27
|
+
export type ScheduleFiring = "fires" | "never" | "unknown";
|
|
28
|
+
export declare function scheduleFiringFor(state: ScheduledTriggerState): ScheduleFiring;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function scheduleFiringFor(state) {
|
|
2
|
+
if (state.kind === "resolved") {
|
|
3
|
+
return state.status.available ? "fires" : "never";
|
|
4
|
+
}
|
|
5
|
+
// Loading leans optimistic on purpose: accusing a working deploy of being
|
|
6
|
+
// broken mid-flight is worse than showing the warning a beat late. An errored
|
|
7
|
+
// check gets no such benefit — it never resolves, so the optimism would be
|
|
8
|
+
// permanent and would license a confident "Next run" forever.
|
|
9
|
+
return state.kind === "unknown" ? "unknown" : "fires";
|
|
10
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ScheduledTriggerState, ScheduledTriggerStatus } from "./scheduled-trigger-state.js";
|
|
1
2
|
export type JobsScope = "user" | "org";
|
|
2
3
|
export interface RecurringJob {
|
|
3
4
|
id: string;
|
|
@@ -75,6 +76,12 @@ export interface AutomationRun {
|
|
|
75
76
|
finishedAt: number | null;
|
|
76
77
|
error: string | null;
|
|
77
78
|
}
|
|
79
|
+
export type { ScheduledTriggerState, ScheduledTriggerStatus };
|
|
80
|
+
/**
|
|
81
|
+
* Deploy-scoped and fixed for the life of the process, so it is cached far
|
|
82
|
+
* longer than the automation lists.
|
|
83
|
+
*/
|
|
84
|
+
export declare function useScheduledTriggerState(): ScheduledTriggerState;
|
|
78
85
|
export declare function useRecurringJobs(scope: JobsScope): import("@tanstack/react-query").UseQueryResult<NoInfer<RecurringJob[]>, Error>;
|
|
79
86
|
export declare function useAutomations(scope: JobsScope): import("@tanstack/react-query").UseQueryResult<NoInfer<Automation[]>, Error>;
|
|
80
87
|
export declare function useManageRecurringJob(scope: JobsScope): import("@tanstack/react-query").UseMutationResult<{
|
|
@@ -6,6 +6,20 @@ function recurringParams(scope) {
|
|
|
6
6
|
function automationParams(scope) {
|
|
7
7
|
return { scope: scope === "org" ? "organization" : "personal" };
|
|
8
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Deploy-scoped and fixed for the life of the process, so it is cached far
|
|
11
|
+
* longer than the automation lists.
|
|
12
|
+
*/
|
|
13
|
+
export function useScheduledTriggerState() {
|
|
14
|
+
const query = useActionQuery("get-scheduled-trigger-status", {}, { staleTime: 5 * 60_000 });
|
|
15
|
+
// Data first: a cached answer with a failing background refetch is still an
|
|
16
|
+
// answer, and downgrading it to `unknown` would flicker the warning off.
|
|
17
|
+
if (query.data)
|
|
18
|
+
return { kind: "resolved", status: query.data };
|
|
19
|
+
if (query.error)
|
|
20
|
+
return { kind: "unknown", error: query.error };
|
|
21
|
+
return { kind: "loading" };
|
|
22
|
+
}
|
|
9
23
|
export function useRecurringJobs(scope) {
|
|
10
24
|
return useActionQuery("list-recurring-jobs", recurringParams(scope), { staleTime: 5_000 });
|
|
11
25
|
}
|
package/dist/collab/routes.d.ts
CHANGED
|
@@ -26,8 +26,8 @@ export declare const getCollabState: import("h3").EventHandlerWithFetch<import("
|
|
|
26
26
|
* Body: { update: string (base64), requestSource?: string }
|
|
27
27
|
*/
|
|
28
28
|
export declare const postCollabUpdate: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
29
|
-
error: string;
|
|
30
29
|
ok?: undefined;
|
|
30
|
+
error: string;
|
|
31
31
|
} | {
|
|
32
32
|
error?: undefined;
|
|
33
33
|
ok: boolean;
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* Body: { json: any, fieldName?: string, type?: "map"|"array", requestSource?: string }
|
|
14
14
|
*/
|
|
15
15
|
export declare const postCollabJson: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
16
|
-
error: string;
|
|
17
16
|
ok?: undefined;
|
|
17
|
+
error: string;
|
|
18
18
|
} | {
|
|
19
19
|
error?: undefined;
|
|
20
20
|
ok: boolean;
|
package/dist/deploy/build.d.ts
CHANGED
|
@@ -101,6 +101,11 @@ export declare function isDurableBackgroundDeployEnabled(): boolean;
|
|
|
101
101
|
export declare const NETLIFY_INTEGRATION_RECOVERY_FUNCTION_NAME = "server-integration-recovery";
|
|
102
102
|
export declare function isIntegrationDurableDispatchDeployEnabled(): boolean;
|
|
103
103
|
export declare const NETLIFY_RECURRING_JOBS_FUNCTION_NAME = "agent-native-recurring-jobs";
|
|
104
|
+
/**
|
|
105
|
+
* Routed through the same resolver that produces the runtime marker, so the gate
|
|
106
|
+
* that emits the scheduled function and the value the deployed app reports
|
|
107
|
+
* cannot drift: a build that skips the emit always ships `"disabled"`.
|
|
108
|
+
*/
|
|
104
109
|
export declare function isRecurringJobsDeployEnabled(): boolean;
|
|
105
110
|
/**
|
|
106
111
|
* Keep-warm is opt-in because a once-a-minute wake prevents autosuspend on
|
package/dist/deploy/build.js
CHANGED
|
@@ -24,6 +24,7 @@ import { INTEGRATION_RECOVERY_RUNTIME_MARKER, INTEGRATION_RETRY_SWEEP_PATH, INTE
|
|
|
24
24
|
import { isValidCron } from "../jobs/cron.js";
|
|
25
25
|
import { RECURRING_JOBS_SWEEP_PATH, RECURRING_JOBS_SWEEP_TOKEN_SUBJECT, } from "../jobs/scheduler-dispatch.js";
|
|
26
26
|
import { findWorkspaceRoot as findAgentNativeWorkspaceRoot } from "../scripts/utils.js";
|
|
27
|
+
import { RECURRING_JOBS_BUILD_MARKER_ENV_VAR, resolveRecurringJobsBuildMarker, } from "../server/agent-chat/recurring-jobs-runtime.js";
|
|
27
28
|
import { normalizeAppBasePath } from "../server/app-base-path.js";
|
|
28
29
|
import { DEFAULT_SPECULATION_RULES_PATH, resolveSsrCacheHeaders, resolveSsrCacheKeyHeaders, SSR_QUERY_CACHE_KEY_HEADER, } from "../shared/cache-control.js";
|
|
29
30
|
import { mcpEmbedStaticAssetRouteRules } from "../shared/mcp-embed-headers.js";
|
|
@@ -2407,8 +2408,13 @@ function isTruthyEnv(name) {
|
|
|
2407
2408
|
function isDisabledByEnv(name) {
|
|
2408
2409
|
return isTruthyEnv(name);
|
|
2409
2410
|
}
|
|
2411
|
+
/**
|
|
2412
|
+
* Routed through the same resolver that produces the runtime marker, so the gate
|
|
2413
|
+
* that emits the scheduled function and the value the deployed app reports
|
|
2414
|
+
* cannot drift: a build that skips the emit always ships `"disabled"`.
|
|
2415
|
+
*/
|
|
2410
2416
|
export function isRecurringJobsDeployEnabled() {
|
|
2411
|
-
return
|
|
2417
|
+
return resolveRecurringJobsBuildMarker(process.env) === "enabled";
|
|
2412
2418
|
}
|
|
2413
2419
|
/**
|
|
2414
2420
|
* Keep-warm is opt-in because a once-a-minute wake prevents autosuspend on
|
|
@@ -3887,6 +3893,11 @@ export function resolveNitroBuildReplacements(env = process.env, deploymentEnvir
|
|
|
3887
3893
|
// well as in the Vite server bundle.
|
|
3888
3894
|
"process.env.AGENT_NATIVE_RELEASE_MIGRATIONS": JSON.stringify(env.AGENT_NATIVE_RELEASE_MIGRATIONS?.trim() || ""),
|
|
3889
3895
|
"process.env.AGENT_NATIVE_BUILD_DEPLOY_CONTEXT": JSON.stringify(env.CONTEXT?.trim() || env.NETLIFY_CONTEXT?.trim() || ""),
|
|
3896
|
+
// Whether the recurring-jobs scheduled function exists is decided HERE, by
|
|
3897
|
+
// the build env. `scheduledTriggerAvailability` cannot re-derive it later —
|
|
3898
|
+
// a pipeline that sets the kill switch only for the build leaves no runtime
|
|
3899
|
+
// trace of it — so hand the decision to the runtime explicitly.
|
|
3900
|
+
[`process.env.${RECURRING_JOBS_BUILD_MARKER_ENV_VAR}`]: JSON.stringify(resolveRecurringJobsBuildMarker(env)),
|
|
3890
3901
|
...(configuredDeploymentEnvironment
|
|
3891
3902
|
? {
|
|
3892
3903
|
"process.env.AGENT_NATIVE_DEPLOYMENT_ENVIRONMENT": JSON.stringify(configuredDeploymentEnvironment),
|
package/dist/framework-tools.js
CHANGED
|
@@ -126,6 +126,7 @@ export const CORE_ACTION_GROUPS = {
|
|
|
126
126
|
"manage-recurring-job": "automation",
|
|
127
127
|
"run-automation-now": "automation",
|
|
128
128
|
"list-automation-runs": "automation",
|
|
129
|
+
"get-scheduled-trigger-status": "automation",
|
|
129
130
|
"list-automations": "automation",
|
|
130
131
|
"manage-automation": "automation",
|
|
131
132
|
"get-usage-alerts": "automation",
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type ScheduledTriggerAvailability } from "../../server/agent-chat/recurring-jobs-runtime.js";
|
|
2
|
+
export type ScheduledTriggerStatus = ScheduledTriggerAvailability;
|
|
3
|
+
/**
|
|
4
|
+
* Read surface for "will a schedule-triggered automation actually fire here".
|
|
5
|
+
*
|
|
6
|
+
* A separate action rather than a field on `list-automations` / `list-recurring-jobs`:
|
|
7
|
+
* both of those return bare arrays that several callers map over directly, so
|
|
8
|
+
* adding a sibling field means an envelope and a breaking change at every call
|
|
9
|
+
* site. This is deploy-scoped rather than per-automation data anyway — it never
|
|
10
|
+
* varies by row — so it caches independently and for much longer.
|
|
11
|
+
*/
|
|
12
|
+
declare const _default: import("../../action.js").ActionDefinition<Record<string, never>, ScheduledTriggerAvailability>;
|
|
13
|
+
export default _default;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { defineAction } from "../../action.js";
|
|
3
|
+
import { scheduledTriggerAvailability, } from "../../server/agent-chat/recurring-jobs-runtime.js";
|
|
4
|
+
/**
|
|
5
|
+
* Read surface for "will a schedule-triggered automation actually fire here".
|
|
6
|
+
*
|
|
7
|
+
* A separate action rather than a field on `list-automations` / `list-recurring-jobs`:
|
|
8
|
+
* both of those return bare arrays that several callers map over directly, so
|
|
9
|
+
* adding a sibling field means an envelope and a breaking change at every call
|
|
10
|
+
* site. This is deploy-scoped rather than per-automation data anyway — it never
|
|
11
|
+
* varies by row — so it caches independently and for much longer.
|
|
12
|
+
*/
|
|
13
|
+
export default defineAction({
|
|
14
|
+
description: "Report whether schedule-triggered automations can actually fire in this deploy, and which driver (or missing driver) decides that. Used by the Agent Automations page to warn that a schedule will never run.",
|
|
15
|
+
agentTool: false,
|
|
16
|
+
schema: z.object({}),
|
|
17
|
+
http: { method: "GET" },
|
|
18
|
+
readOnly: true,
|
|
19
|
+
parallelSafe: true,
|
|
20
|
+
run: async () => scheduledTriggerAvailability(),
|
|
21
|
+
});
|
|
@@ -667,6 +667,20 @@ declare const messages: {
|
|
|
667
667
|
organization: string;
|
|
668
668
|
settingsSummary: string;
|
|
669
669
|
openAutomations: string;
|
|
670
|
+
nextRunNeverScheduler: string;
|
|
671
|
+
nextRunSchedulerUnknown: string;
|
|
672
|
+
nextRunSchedulerUnknownNoDate: string;
|
|
673
|
+
scheduleUnknownTitle: string;
|
|
674
|
+
scheduleUnknownDetail: string;
|
|
675
|
+
scheduleUnavailableTitle: string;
|
|
676
|
+
scheduleUnavailableLocalTitle: string;
|
|
677
|
+
scheduleUnavailableDisabled: string;
|
|
678
|
+
scheduleUnavailableNoScheduler: string;
|
|
679
|
+
scheduleUnavailableLocal: string;
|
|
680
|
+
scheduleUnavailableDisabledFix: string;
|
|
681
|
+
scheduleUnavailableLocalFix: string;
|
|
682
|
+
scheduleUnavailableFixLabel: string;
|
|
683
|
+
scheduleUnavailableFixLabelOpen: string;
|
|
670
684
|
};
|
|
671
685
|
share: {
|
|
672
686
|
titleWithResource: string;
|
|
@@ -673,6 +673,20 @@ const messages = {
|
|
|
673
673
|
organization: "Organization",
|
|
674
674
|
settingsSummary: "Manage scheduled and event-triggered agent tasks together from the Automations page.",
|
|
675
675
|
openAutomations: "Open Automations",
|
|
676
|
+
nextRunNeverScheduler: "Never — no scheduler in this deploy",
|
|
677
|
+
nextRunSchedulerUnknown: "{{date}} — unconfirmed, the scheduler check failed",
|
|
678
|
+
nextRunSchedulerUnknownNoDate: "Unknown — the scheduler check failed",
|
|
679
|
+
scheduleUnknownTitle: "Couldn't check whether schedules run here",
|
|
680
|
+
scheduleUnknownDetail: "The scheduler status check failed, so the next run times below are unconfirmed. Reload to check again.",
|
|
681
|
+
scheduleUnavailableTitle: "Schedules won't run in this deploy",
|
|
682
|
+
scheduleUnavailableLocalTitle: "Schedules don't run in local development",
|
|
683
|
+
scheduleUnavailableDisabled: "This app was built with recurring jobs turned off, so no scheduled automation will fire. Event-triggered automations and Run now still work.",
|
|
684
|
+
scheduleUnavailableNoScheduler: "This hosting target has no durable scheduler, so no scheduled automation will fire. Event-triggered automations and Run now still work.",
|
|
685
|
+
scheduleUnavailableLocal: "Schedules stay off on a dev machine unless you opt in. Event-triggered automations and Run now still work.",
|
|
686
|
+
scheduleUnavailableDisabledFix: "To enable recurring jobs, set AGENT_NATIVE_DISABLE_RECURRING_JOBS=false in the build environment.",
|
|
687
|
+
scheduleUnavailableLocalFix: "Set AGENT_NATIVE_ENABLE_LOCAL_RECURRING_JOBS=true to run schedules on this machine.",
|
|
688
|
+
scheduleUnavailableFixLabel: "Show more",
|
|
689
|
+
scheduleUnavailableFixLabelOpen: "Show less",
|
|
676
690
|
},
|
|
677
691
|
share: {
|
|
678
692
|
titleWithResource: 'Share "{{title}}"',
|
|
@@ -137,13 +137,13 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
137
137
|
inputSchema: {
|
|
138
138
|
type: string;
|
|
139
139
|
properties: {
|
|
140
|
-
count?: undefined;
|
|
141
140
|
query?: undefined;
|
|
142
141
|
minutes?: undefined;
|
|
143
142
|
limit?: undefined;
|
|
144
143
|
clientHint?: undefined;
|
|
145
144
|
timestamp?: undefined;
|
|
146
145
|
chapterId?: undefined;
|
|
146
|
+
count?: undefined;
|
|
147
147
|
startAt?: undefined;
|
|
148
148
|
endAt?: undefined;
|
|
149
149
|
reason?: undefined;
|
|
@@ -159,7 +159,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
159
159
|
inputSchema: {
|
|
160
160
|
type: string;
|
|
161
161
|
properties: {
|
|
162
|
-
count?: undefined;
|
|
163
162
|
query: {
|
|
164
163
|
type: string;
|
|
165
164
|
description: string;
|
|
@@ -175,6 +174,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
175
174
|
clientHint?: undefined;
|
|
176
175
|
timestamp?: undefined;
|
|
177
176
|
chapterId?: undefined;
|
|
177
|
+
count?: undefined;
|
|
178
178
|
startAt?: undefined;
|
|
179
179
|
endAt?: undefined;
|
|
180
180
|
reason?: undefined;
|
|
@@ -190,7 +190,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
190
190
|
inputSchema: {
|
|
191
191
|
type: string;
|
|
192
192
|
properties: {
|
|
193
|
-
count?: undefined;
|
|
194
193
|
minutes: {
|
|
195
194
|
type: string;
|
|
196
195
|
description: string;
|
|
@@ -200,6 +199,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
200
199
|
clientHint?: undefined;
|
|
201
200
|
timestamp?: undefined;
|
|
202
201
|
chapterId?: undefined;
|
|
202
|
+
count?: undefined;
|
|
203
203
|
startAt?: undefined;
|
|
204
204
|
endAt?: undefined;
|
|
205
205
|
reason?: undefined;
|
|
@@ -216,7 +216,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
216
216
|
type: string;
|
|
217
217
|
required: string[];
|
|
218
218
|
properties: {
|
|
219
|
-
count?: undefined;
|
|
220
219
|
query: {
|
|
221
220
|
type: string;
|
|
222
221
|
description: string;
|
|
@@ -235,6 +234,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
235
234
|
};
|
|
236
235
|
timestamp?: undefined;
|
|
237
236
|
chapterId?: undefined;
|
|
237
|
+
count?: undefined;
|
|
238
238
|
startAt?: undefined;
|
|
239
239
|
endAt?: undefined;
|
|
240
240
|
reason?: undefined;
|
|
@@ -250,7 +250,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
250
250
|
type: string;
|
|
251
251
|
required: string[];
|
|
252
252
|
properties: {
|
|
253
|
-
count?: undefined;
|
|
254
253
|
query?: undefined;
|
|
255
254
|
minutes?: undefined;
|
|
256
255
|
limit?: undefined;
|
|
@@ -264,6 +263,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
264
263
|
description: string;
|
|
265
264
|
};
|
|
266
265
|
chapterId?: undefined;
|
|
266
|
+
count?: undefined;
|
|
267
267
|
startAt?: undefined;
|
|
268
268
|
endAt?: undefined;
|
|
269
269
|
includeMicrophone?: undefined;
|
|
@@ -314,13 +314,13 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
314
314
|
type: string;
|
|
315
315
|
required: string[];
|
|
316
316
|
properties: {
|
|
317
|
-
count?: undefined;
|
|
318
317
|
query?: undefined;
|
|
319
318
|
minutes?: undefined;
|
|
320
319
|
limit?: undefined;
|
|
321
320
|
clientHint?: undefined;
|
|
322
321
|
timestamp?: undefined;
|
|
323
322
|
chapterId?: undefined;
|
|
323
|
+
count?: undefined;
|
|
324
324
|
startAt: {
|
|
325
325
|
type: string;
|
|
326
326
|
description: string;
|
|
@@ -349,13 +349,13 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
349
349
|
type: string;
|
|
350
350
|
required: string[];
|
|
351
351
|
properties: {
|
|
352
|
-
count?: undefined;
|
|
353
352
|
query?: undefined;
|
|
354
353
|
minutes?: undefined;
|
|
355
354
|
limit?: undefined;
|
|
356
355
|
clientHint?: undefined;
|
|
357
356
|
timestamp?: undefined;
|
|
358
357
|
chapterId?: undefined;
|
|
358
|
+
count?: undefined;
|
|
359
359
|
startAt?: undefined;
|
|
360
360
|
endAt?: undefined;
|
|
361
361
|
reason?: undefined;
|
|
@@ -13,13 +13,13 @@
|
|
|
13
13
|
export declare function createNotificationsHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<"" | import("./types.js").Notification[] | {
|
|
14
14
|
count: number;
|
|
15
15
|
updated?: undefined;
|
|
16
|
-
error?: undefined;
|
|
17
16
|
ok?: undefined;
|
|
17
|
+
error?: undefined;
|
|
18
18
|
} | {
|
|
19
19
|
count?: undefined;
|
|
20
20
|
updated: number;
|
|
21
|
-
error?: undefined;
|
|
22
21
|
ok?: undefined;
|
|
22
|
+
error?: undefined;
|
|
23
23
|
} | {
|
|
24
24
|
count?: undefined;
|
|
25
25
|
updated?: undefined;
|
|
@@ -28,6 +28,6 @@ export declare function createNotificationsHandler(): import("h3").EventHandlerW
|
|
|
28
28
|
} | {
|
|
29
29
|
count?: undefined;
|
|
30
30
|
updated?: undefined;
|
|
31
|
-
error?: undefined;
|
|
32
31
|
ok: boolean;
|
|
32
|
+
error?: undefined;
|
|
33
33
|
}>>;
|
|
@@ -41,16 +41,16 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
|
|
|
41
41
|
thumbsUpRate: number;
|
|
42
42
|
avgEvalScore: number;
|
|
43
43
|
} | {
|
|
44
|
-
error?: undefined;
|
|
45
44
|
summary: import("./types.js").TraceSummary;
|
|
46
45
|
spans: import("./types.js").TraceSpan[];
|
|
47
46
|
id?: undefined;
|
|
47
|
+
error?: undefined;
|
|
48
48
|
ok?: undefined;
|
|
49
49
|
} | {
|
|
50
|
-
error?: undefined;
|
|
51
50
|
summary?: undefined;
|
|
52
51
|
spans?: undefined;
|
|
53
52
|
id: string;
|
|
53
|
+
error?: undefined;
|
|
54
54
|
ok?: undefined;
|
|
55
55
|
} | {
|
|
56
56
|
summary?: undefined;
|
|
@@ -59,9 +59,9 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
|
|
|
59
59
|
error: any;
|
|
60
60
|
ok?: undefined;
|
|
61
61
|
} | {
|
|
62
|
-
error?: undefined;
|
|
63
62
|
summary?: undefined;
|
|
64
63
|
spans?: undefined;
|
|
65
64
|
id?: undefined;
|
|
65
|
+
error?: undefined;
|
|
66
66
|
ok: boolean;
|
|
67
67
|
}>>;
|
|
@@ -94,28 +94,28 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
|
|
|
94
94
|
message?: undefined;
|
|
95
95
|
} | {
|
|
96
96
|
id?: undefined;
|
|
97
|
-
count?: undefined;
|
|
98
97
|
deleted?: undefined;
|
|
99
98
|
providers?: undefined;
|
|
99
|
+
count?: undefined;
|
|
100
100
|
found: boolean;
|
|
101
101
|
provider: import("../custom-registry.js").CustomProviderConfig;
|
|
102
102
|
registered?: undefined;
|
|
103
103
|
label?: undefined;
|
|
104
104
|
message?: undefined;
|
|
105
105
|
} | {
|
|
106
|
-
count?: undefined;
|
|
107
106
|
provider?: undefined;
|
|
108
107
|
deleted?: undefined;
|
|
109
108
|
providers?: undefined;
|
|
109
|
+
count?: undefined;
|
|
110
110
|
found: boolean;
|
|
111
111
|
id: string;
|
|
112
112
|
registered?: undefined;
|
|
113
113
|
label?: undefined;
|
|
114
114
|
message?: undefined;
|
|
115
115
|
} | {
|
|
116
|
-
count?: undefined;
|
|
117
116
|
provider?: undefined;
|
|
118
117
|
providers?: undefined;
|
|
118
|
+
count?: undefined;
|
|
119
119
|
found?: undefined;
|
|
120
120
|
deleted: boolean;
|
|
121
121
|
id: string;
|
|
@@ -123,10 +123,10 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
|
|
|
123
123
|
label?: undefined;
|
|
124
124
|
message?: undefined;
|
|
125
125
|
} | {
|
|
126
|
-
count?: undefined;
|
|
127
126
|
provider?: undefined;
|
|
128
127
|
deleted?: undefined;
|
|
129
128
|
providers?: undefined;
|
|
129
|
+
count?: undefined;
|
|
130
130
|
found?: undefined;
|
|
131
131
|
registered: boolean;
|
|
132
132
|
id: string;
|
|
@@ -311,9 +311,9 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
|
|
|
311
311
|
notes?: string | undefined;
|
|
312
312
|
scope?: "org" | "user" | undefined;
|
|
313
313
|
}, {
|
|
314
|
-
message?: undefined;
|
|
315
|
-
deleted?: undefined;
|
|
316
314
|
id?: undefined;
|
|
315
|
+
provider?: undefined;
|
|
316
|
+
deleted?: undefined;
|
|
317
317
|
providers: {
|
|
318
318
|
id: string;
|
|
319
319
|
label: string;
|
|
@@ -324,45 +324,45 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
|
|
|
324
324
|
updatedAt: number;
|
|
325
325
|
}[];
|
|
326
326
|
count: number;
|
|
327
|
-
provider?: undefined;
|
|
328
327
|
found?: undefined;
|
|
329
328
|
registered?: undefined;
|
|
330
329
|
label?: undefined;
|
|
331
|
-
} | {
|
|
332
330
|
message?: undefined;
|
|
333
|
-
|
|
331
|
+
} | {
|
|
332
|
+
count?: undefined;
|
|
334
333
|
id?: undefined;
|
|
334
|
+
deleted?: undefined;
|
|
335
335
|
providers?: undefined;
|
|
336
|
-
count?: 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
|
-
|
|
344
|
-
providers?: undefined;
|
|
341
|
+
} | {
|
|
345
342
|
count?: undefined;
|
|
346
343
|
provider?: undefined;
|
|
344
|
+
deleted?: undefined;
|
|
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
|
count?: undefined;
|
|
355
353
|
provider?: undefined;
|
|
354
|
+
providers?: undefined;
|
|
356
355
|
found?: undefined;
|
|
357
356
|
deleted: boolean;
|
|
358
357
|
id: string;
|
|
359
358
|
registered?: undefined;
|
|
360
359
|
label?: undefined;
|
|
360
|
+
message?: undefined;
|
|
361
361
|
} | {
|
|
362
|
-
deleted?: undefined;
|
|
363
|
-
providers?: undefined;
|
|
364
362
|
count?: undefined;
|
|
365
363
|
provider?: undefined;
|
|
364
|
+
deleted?: undefined;
|
|
365
|
+
providers?: undefined;
|
|
366
366
|
found?: undefined;
|
|
367
367
|
registered: boolean;
|
|
368
368
|
id: string;
|
|
@@ -73,7 +73,6 @@ export declare function createProviderCorpusJobAction(options: CreateProviderCor
|
|
|
73
73
|
offset?: unknown;
|
|
74
74
|
limit?: unknown;
|
|
75
75
|
}, {
|
|
76
|
-
deleted?: undefined;
|
|
77
76
|
jobs: {
|
|
78
77
|
id: string;
|
|
79
78
|
name: string;
|
|
@@ -84,6 +83,7 @@ export declare function createProviderCorpusJobAction(options: CreateProviderCor
|
|
|
84
83
|
updatedAt: string;
|
|
85
84
|
}[];
|
|
86
85
|
total: number;
|
|
86
|
+
deleted?: undefined;
|
|
87
87
|
jobId?: undefined;
|
|
88
88
|
} | {
|
|
89
89
|
jobs?: undefined;
|
|
@@ -91,9 +91,9 @@ export declare function createProviderCorpusJobAction(options: CreateProviderCor
|
|
|
91
91
|
deleted: boolean;
|
|
92
92
|
jobId: string;
|
|
93
93
|
} | {
|
|
94
|
-
deleted?: undefined;
|
|
95
94
|
jobs?: undefined;
|
|
96
95
|
total?: undefined;
|
|
96
|
+
deleted?: undefined;
|
|
97
97
|
jobId?: undefined;
|
|
98
98
|
hits: Record<string, unknown>[];
|
|
99
99
|
offset: number;
|
|
@@ -51,8 +51,8 @@ export declare function handleDeleteResource(event: any): Promise<{
|
|
|
51
51
|
error: string;
|
|
52
52
|
ok?: undefined;
|
|
53
53
|
} | {
|
|
54
|
-
ok: boolean;
|
|
55
54
|
error?: undefined;
|
|
55
|
+
ok: boolean;
|
|
56
56
|
}>;
|
|
57
57
|
/** POST /_agent-native/resources/upload — upload a file as a resource */
|
|
58
58
|
export declare function handleUploadResource(event: any): Promise<import("./store.js").Resource | {
|
|
@@ -73,10 +73,10 @@ export declare function handleUploadResource(event: any): Promise<import("./stor
|
|
|
73
73
|
runId: string | null;
|
|
74
74
|
expiresAt: number | null;
|
|
75
75
|
metadata: string | null;
|
|
76
|
+
error?: undefined;
|
|
76
77
|
url: string;
|
|
77
78
|
provider: string;
|
|
78
79
|
storageSetupRequired?: undefined;
|
|
79
|
-
error?: undefined;
|
|
80
80
|
} | {
|
|
81
81
|
error: string;
|
|
82
82
|
storageSetupRequired: boolean;
|
|
@@ -626,6 +626,10 @@ export async function mergeCoreSharingActions(registry) {
|
|
|
626
626
|
"list-automation-runs",
|
|
627
627
|
() => import("../jobs/actions/list-automation-runs.js"),
|
|
628
628
|
],
|
|
629
|
+
[
|
|
630
|
+
"get-scheduled-trigger-status",
|
|
631
|
+
() => import("../jobs/actions/get-scheduled-trigger-status.js"),
|
|
632
|
+
],
|
|
629
633
|
[
|
|
630
634
|
"list-automations",
|
|
631
635
|
() => import("../triggers/actions/list-automations.js"),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
type RecurringJobsRuntimeEnvKey = "AGENT_NATIVE_DISABLE_RECURRING_JOBS" | "AGENT_NATIVE_ENABLE_LOCAL_RECURRING_JOBS" | "APP_URL" | "BETTER_AUTH_URL" | "CF_PAGES" | "DEPLOY_URL" | "AWS_EXECUTION_ENV" | "AWS_LAMBDA_FUNCTION_NAME" | "NETLIFY" | "NETLIFY_LOCAL" | "NITRO_PRESET" | "NODE_ENV" | "SITE_ID" | "URL" | "VERCEL" | "VITE_APP_URL" | "VITE_WORKSPACE_GATEWAY_URL" | "WORKSPACE_GATEWAY_URL";
|
|
1
|
+
type RecurringJobsRuntimeEnvKey = "AGENT_NATIVE_BUILD_RECURRING_JOBS" | "AGENT_NATIVE_DISABLE_RECURRING_JOBS" | "AGENT_NATIVE_ENABLE_LOCAL_RECURRING_JOBS" | "APP_URL" | "BETTER_AUTH_URL" | "CF_PAGES" | "DEPLOY_URL" | "AWS_EXECUTION_ENV" | "AWS_LAMBDA_FUNCTION_NAME" | "NETLIFY" | "NETLIFY_LOCAL" | "NITRO_PRESET" | "NODE_ENV" | "SITE_ID" | "URL" | "VERCEL" | "VITE_APP_URL" | "VITE_WORKSPACE_GATEWAY_URL" | "WORKSPACE_GATEWAY_URL";
|
|
2
2
|
type RecurringJobsRuntimeEnv = Partial<Record<RecurringJobsRuntimeEnvKey, string | undefined>>;
|
|
3
3
|
export declare function shouldDisableRecurringJobsRuntime(env?: RecurringJobsRuntimeEnv): boolean;
|
|
4
4
|
/**
|
|
@@ -7,4 +7,45 @@ export declare function shouldDisableRecurringJobsRuntime(env?: RecurringJobsRun
|
|
|
7
7
|
* that timer, which is exactly the failure mode the emitted sweep fixes.
|
|
8
8
|
*/
|
|
9
9
|
export declare function isNetlifyRecurringJobsRuntime(env?: RecurringJobsRuntimeEnv): boolean;
|
|
10
|
+
/** What the BUILD decided about recurring jobs, as carried into the runtime. */
|
|
11
|
+
export type RecurringJobsBuildMarker = "enabled" | "disabled";
|
|
12
|
+
/**
|
|
13
|
+
* Env name the build embeds its recurring-jobs decision under.
|
|
14
|
+
*
|
|
15
|
+
* Netlify's scheduled function is emitted — or omitted — while the build runs,
|
|
16
|
+
* and a deployed serverless runtime never sees the build environment. So the
|
|
17
|
+
* build has to hand the decision over explicitly; nothing observable at runtime
|
|
18
|
+
* can reconstruct it. Consumed below as a LITERAL `process.env.<name>` member
|
|
19
|
+
* expression because that is the only form Vite's `define` and Nitro's
|
|
20
|
+
* `replace` inline at build time.
|
|
21
|
+
*/
|
|
22
|
+
export declare const RECURRING_JOBS_BUILD_MARKER_ENV_VAR = "AGENT_NATIVE_BUILD_RECURRING_JOBS";
|
|
23
|
+
/**
|
|
24
|
+
* Build-side resolver: what the build environment decided. Shared with
|
|
25
|
+
* `isRecurringJobsDeployEnabled` in the deploy build so the gate that emits the
|
|
26
|
+
* scheduled function and the marker that reports it cannot disagree.
|
|
27
|
+
*/
|
|
28
|
+
export declare function resolveRecurringJobsBuildMarker(env?: RecurringJobsRuntimeEnv): RecurringJobsBuildMarker;
|
|
29
|
+
export type ScheduledTriggerAvailability = {
|
|
30
|
+
available: true;
|
|
31
|
+
driver: "netlify-scheduled-function" | "in-process";
|
|
32
|
+
} | {
|
|
33
|
+
available: false;
|
|
34
|
+
reason: "disabled-by-env" | "no-platform-scheduler" | "local-development";
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Whether ANY driver will actually fire a schedule-triggered automation in this
|
|
38
|
+
* deploy. Distinct from `shouldDisableRecurringJobsRuntime`, which answers the
|
|
39
|
+
* narrower "should THIS process run an in-process timer" and is therefore `true`
|
|
40
|
+
* on hosted Netlify even though schedules do fire there via the emitted
|
|
41
|
+
* scheduled function — reporting that value to a user would call the one
|
|
42
|
+
* working production runtime broken.
|
|
43
|
+
*
|
|
44
|
+
* Each branch reads the scope that actually decides its driver. The Netlify
|
|
45
|
+
* driver is a BUILD artifact, so it is answered by the build marker; every other
|
|
46
|
+
* driver is an in-process timer started from the RUNTIME env, so it is answered
|
|
47
|
+
* by the runtime flag. Reading runtime-only platform markers for the Netlify
|
|
48
|
+
* branch would report `available` off a build that emitted no trigger at all.
|
|
49
|
+
*/
|
|
50
|
+
export declare function scheduledTriggerAvailability(env?: RecurringJobsRuntimeEnv): ScheduledTriggerAvailability;
|
|
10
51
|
export {};
|
|
@@ -30,20 +30,27 @@ function isLoopbackAppUrl(value) {
|
|
|
30
30
|
}
|
|
31
31
|
return false;
|
|
32
32
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
/**
|
|
34
|
+
* A serverless isolate is not a durable scheduler. Shared so
|
|
35
|
+
* `shouldDisableRecurringJobsRuntime` and `scheduledTriggerAvailability` cannot
|
|
36
|
+
* drift on what counts as serverless.
|
|
37
|
+
*/
|
|
38
|
+
function isServerlessRecurringJobsRuntime(env) {
|
|
39
|
+
return (env.NETLIFY_LOCAL !== "true" &&
|
|
40
40
|
(isTruthyEnv(env.NETLIFY) ||
|
|
41
41
|
env.NITRO_PRESET === "netlify" ||
|
|
42
42
|
Boolean(env.AWS_LAMBDA_FUNCTION_NAME) ||
|
|
43
43
|
env.AWS_EXECUTION_ENV?.startsWith("AWS_Lambda") === true ||
|
|
44
44
|
isTruthyEnv(env.CF_PAGES) ||
|
|
45
|
-
isTruthyEnv(env.VERCEL));
|
|
46
|
-
|
|
45
|
+
isTruthyEnv(env.VERCEL)));
|
|
46
|
+
}
|
|
47
|
+
export function shouldDisableRecurringJobsRuntime(env = process.env) {
|
|
48
|
+
if (isTruthyEnv(env.AGENT_NATIVE_DISABLE_RECURRING_JOBS))
|
|
49
|
+
return true;
|
|
50
|
+
// Keep this check separate from the platform-specific scheduler branch below
|
|
51
|
+
// so a new sweep cannot accidentally start an in-process timer before its
|
|
52
|
+
// platform trigger exists.
|
|
53
|
+
if (isServerlessRecurringJobsRuntime(env))
|
|
47
54
|
return true;
|
|
48
55
|
const isLocalRuntime = env.NODE_ENV === "development" ||
|
|
49
56
|
env.NODE_ENV === "test" ||
|
|
@@ -74,3 +81,89 @@ export function isNetlifyRecurringJobsRuntime(env = process.env) {
|
|
|
74
81
|
return false;
|
|
75
82
|
return Boolean((env.NETLIFY && env.NETLIFY !== "false") || env.SITE_ID);
|
|
76
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* Env name the build embeds its recurring-jobs decision under.
|
|
86
|
+
*
|
|
87
|
+
* Netlify's scheduled function is emitted — or omitted — while the build runs,
|
|
88
|
+
* and a deployed serverless runtime never sees the build environment. So the
|
|
89
|
+
* build has to hand the decision over explicitly; nothing observable at runtime
|
|
90
|
+
* can reconstruct it. Consumed below as a LITERAL `process.env.<name>` member
|
|
91
|
+
* expression because that is the only form Vite's `define` and Nitro's
|
|
92
|
+
* `replace` inline at build time.
|
|
93
|
+
*/
|
|
94
|
+
export const RECURRING_JOBS_BUILD_MARKER_ENV_VAR = "AGENT_NATIVE_BUILD_RECURRING_JOBS";
|
|
95
|
+
/**
|
|
96
|
+
* Build-side resolver: what the build environment decided. Shared with
|
|
97
|
+
* `isRecurringJobsDeployEnabled` in the deploy build so the gate that emits the
|
|
98
|
+
* scheduled function and the marker that reports it cannot disagree.
|
|
99
|
+
*/
|
|
100
|
+
export function resolveRecurringJobsBuildMarker(env = process.env) {
|
|
101
|
+
return isTruthyEnv(env.AGENT_NATIVE_DISABLE_RECURRING_JOBS)
|
|
102
|
+
? "disabled"
|
|
103
|
+
: "enabled";
|
|
104
|
+
}
|
|
105
|
+
function readRecurringJobsBuildMarker(env) {
|
|
106
|
+
const raw = env.AGENT_NATIVE_BUILD_RECURRING_JOBS ??
|
|
107
|
+
// config-ok: this value is INLINED at build time by Vite's `define` /
|
|
108
|
+
// Nitro's `replace`, which rewrite the literal `process.env.<NAME>` member
|
|
109
|
+
// expression and nothing else. A declared app-config field is read at
|
|
110
|
+
// runtime from the deployed environment, which is precisely the scope that
|
|
111
|
+
// cannot see the build's decision — the bug this marker exists to fix.
|
|
112
|
+
// Reading through the aliased `env` parameter would also survive the build
|
|
113
|
+
// unreplaced, so the literal form is load-bearing.
|
|
114
|
+
process.env.AGENT_NATIVE_BUILD_RECURRING_JOBS;
|
|
115
|
+
const value = raw?.trim();
|
|
116
|
+
return value === "enabled" || value === "disabled" ? value : undefined;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Whether ANY driver will actually fire a schedule-triggered automation in this
|
|
120
|
+
* deploy. Distinct from `shouldDisableRecurringJobsRuntime`, which answers the
|
|
121
|
+
* narrower "should THIS process run an in-process timer" and is therefore `true`
|
|
122
|
+
* on hosted Netlify even though schedules do fire there via the emitted
|
|
123
|
+
* scheduled function — reporting that value to a user would call the one
|
|
124
|
+
* working production runtime broken.
|
|
125
|
+
*
|
|
126
|
+
* Each branch reads the scope that actually decides its driver. The Netlify
|
|
127
|
+
* driver is a BUILD artifact, so it is answered by the build marker; every other
|
|
128
|
+
* driver is an in-process timer started from the RUNTIME env, so it is answered
|
|
129
|
+
* by the runtime flag. Reading runtime-only platform markers for the Netlify
|
|
130
|
+
* branch would report `available` off a build that emitted no trigger at all.
|
|
131
|
+
*/
|
|
132
|
+
export function scheduledTriggerAvailability(env = process.env) {
|
|
133
|
+
const buildMarker = readRecurringJobsBuildMarker(env);
|
|
134
|
+
// Netlify: the driver IS the emitted scheduled function. It fires on the
|
|
135
|
+
// platform's clock without consulting the deployed env, and no runtime value
|
|
136
|
+
// can conjure it back once the build omitted it — so the build marker is the
|
|
137
|
+
// only honest input here, in both directions.
|
|
138
|
+
if (isNetlifyRecurringJobsRuntime(env)) {
|
|
139
|
+
if (buildMarker === "disabled") {
|
|
140
|
+
return { available: false, reason: "disabled-by-env" };
|
|
141
|
+
}
|
|
142
|
+
if (buildMarker === "enabled") {
|
|
143
|
+
return { available: true, driver: "netlify-scheduled-function" };
|
|
144
|
+
}
|
|
145
|
+
// No marker: a build predating it, or a caller passing a synthetic env.
|
|
146
|
+
// Fall back to the runtime flag, which only agrees with the build when the
|
|
147
|
+
// pipeline sets the same value in both scopes.
|
|
148
|
+
return isTruthyEnv(env.AGENT_NATIVE_DISABLE_RECURRING_JOBS)
|
|
149
|
+
? { available: false, reason: "disabled-by-env" }
|
|
150
|
+
: { available: true, driver: "netlify-scheduled-function" };
|
|
151
|
+
}
|
|
152
|
+
// Everything below is driven by an in-process timer, and the runtime flag is
|
|
153
|
+
// what `shouldDisableRecurringJobsRuntime` reads before starting it. A build
|
|
154
|
+
// marker cannot speak for this branch: a build with recurring jobs turned off
|
|
155
|
+
// still starts the timer if the deployed env does not repeat the switch.
|
|
156
|
+
if (isTruthyEnv(env.AGENT_NATIVE_DISABLE_RECURRING_JOBS)) {
|
|
157
|
+
return { available: false, reason: "disabled-by-env" };
|
|
158
|
+
}
|
|
159
|
+
// Every other serverless host: the build emits a scheduled trigger only for
|
|
160
|
+
// Netlify, and a frozen isolate cannot hold a timer.
|
|
161
|
+
if (isServerlessRecurringJobsRuntime(env)) {
|
|
162
|
+
return { available: false, reason: "no-platform-scheduler" };
|
|
163
|
+
}
|
|
164
|
+
// Long-lived host. Whatever is left of the runtime gate here is the
|
|
165
|
+
// local/loopback branch, which needs AGENT_NATIVE_ENABLE_LOCAL_RECURRING_JOBS.
|
|
166
|
+
return shouldDisableRecurringJobsRuntime(env)
|
|
167
|
+
? { available: false, reason: "local-development" }
|
|
168
|
+
: { available: true, driver: "in-process" };
|
|
169
|
+
}
|
|
@@ -9,7 +9,7 @@ import { _agentChatPromptSectionsForTests } from "./agent-chat/framework-prompts
|
|
|
9
9
|
import { type AgentChatPluginOptions, type NitroPluginDef } from "./agent-chat/plugin-options.js";
|
|
10
10
|
import { finalizeClaimedAgentChatProcessRunFailure } from "./agent-chat/process-run-failure.js";
|
|
11
11
|
import { loadResourcesForPrompt } from "./agent-chat/prompt-resources.js";
|
|
12
|
-
import { isNetlifyRecurringJobsRuntime, shouldDisableRecurringJobsRuntime } from "./agent-chat/recurring-jobs-runtime.js";
|
|
12
|
+
import { isNetlifyRecurringJobsRuntime, resolveRecurringJobsBuildMarker, scheduledTriggerAvailability, shouldDisableRecurringJobsRuntime } from "./agent-chat/recurring-jobs-runtime.js";
|
|
13
13
|
import { shouldBlockInProductCodeEditingSurface } from "./agent-chat/request-surface.js";
|
|
14
14
|
import { loadRunCodeToolEntries } from "./agent-chat/run-code-tools.js";
|
|
15
15
|
export { loadResourcesForPrompt };
|
|
@@ -34,6 +34,8 @@ export { DEFAULT_DELEGATED_MAX_ITERATIONS, DEFAULT_DELEGATED_MAX_RUN_INPUT_TOKEN
|
|
|
34
34
|
export { shouldBlockInProductCodeEditingSurface };
|
|
35
35
|
export { loadRunCodeToolEntries };
|
|
36
36
|
export { isNetlifyRecurringJobsRuntime };
|
|
37
|
+
export { resolveRecurringJobsBuildMarker };
|
|
38
|
+
export { scheduledTriggerAvailability };
|
|
37
39
|
export { shouldDisableRecurringJobsRuntime };
|
|
38
40
|
export { finalizeClaimedAgentChatProcessRunFailure };
|
|
39
41
|
export declare function resolveInteractiveAgentRunOptions(options?: Pick<AgentChatPluginOptions, "runSoftTimeoutMs" | "runNoProgressTimeoutMs" | "durableBackgroundRuns">): {
|
|
@@ -120,7 +120,7 @@ import { resolveAgentChatMcpOptions } from "./agent-chat/mcp-options.js";
|
|
|
120
120
|
import { resolveA2AAgentDelegationEnabled, } from "./agent-chat/plugin-options.js";
|
|
121
121
|
import { finalizeClaimedAgentChatProcessRunFailure } from "./agent-chat/process-run-failure.js";
|
|
122
122
|
import { loadResourcesForPrompt, promptResourceManifestSections, resourceScopeForOwner, } from "./agent-chat/prompt-resources.js";
|
|
123
|
-
import { isNetlifyRecurringJobsRuntime, shouldDisableRecurringJobsRuntime, } from "./agent-chat/recurring-jobs-runtime.js";
|
|
123
|
+
import { isNetlifyRecurringJobsRuntime, resolveRecurringJobsBuildMarker, scheduledTriggerAvailability, shouldDisableRecurringJobsRuntime, } from "./agent-chat/recurring-jobs-runtime.js";
|
|
124
124
|
import { isLocalhost, shouldBlockInProductCodeEditingSurface, } from "./agent-chat/request-surface.js";
|
|
125
125
|
import { loadRunCodeToolEntries } from "./agent-chat/run-code-tools.js";
|
|
126
126
|
import { createAgentEngineScriptEntries, createAgentLoopSettingsScriptEntries, createCallAgentScriptEntry, createChatScriptEntries, createDbScriptEntries, createDocsScriptEntries, createResourceScriptEntries, } from "./agent-chat/script-entries.js";
|
|
@@ -147,6 +147,8 @@ export { DEFAULT_DELEGATED_MAX_ITERATIONS, DEFAULT_DELEGATED_MAX_RUN_INPUT_TOKEN
|
|
|
147
147
|
export { shouldBlockInProductCodeEditingSurface };
|
|
148
148
|
export { loadRunCodeToolEntries };
|
|
149
149
|
export { isNetlifyRecurringJobsRuntime };
|
|
150
|
+
export { resolveRecurringJobsBuildMarker };
|
|
151
|
+
export { scheduledTriggerAvailability };
|
|
150
152
|
export { shouldDisableRecurringJobsRuntime };
|
|
151
153
|
export { finalizeClaimedAgentChatProcessRunFailure };
|
|
152
154
|
export function resolveInteractiveAgentRunOptions(options) {
|
|
@@ -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 {};
|
|
@@ -26,8 +26,8 @@ export declare function createRealtimeTokenHandler(): import("h3").EventHandlerW
|
|
|
26
26
|
expiresAt?: undefined;
|
|
27
27
|
ttlSeconds?: undefined;
|
|
28
28
|
} | {
|
|
29
|
-
error?: undefined;
|
|
30
29
|
token: string;
|
|
31
30
|
expiresAt: string;
|
|
32
31
|
ttlSeconds: number;
|
|
32
|
+
error?: undefined;
|
|
33
33
|
}>>;
|
package/dist/vite/client.js
CHANGED
|
@@ -10,6 +10,7 @@ import { getViteDevRecoveryScript } from "../client/vite-dev-recovery-script.js"
|
|
|
10
10
|
import { inferAgentNativeDeploymentEnvironment, mergeAgentNativeConfigs, readAgentNativeConfigEnv, resolveAgentNativeConfig, } from "../config.js";
|
|
11
11
|
import { writeAgentNativeNitroPresetMarker } from "../deploy/nitro-preset.js";
|
|
12
12
|
import { findWorkspaceRoot } from "../scripts/utils.js";
|
|
13
|
+
import { RECURRING_JOBS_BUILD_MARKER_ENV_VAR, resolveRecurringJobsBuildMarker, } from "../server/agent-chat/recurring-jobs-runtime.js";
|
|
13
14
|
import { verifyEmbedSessionToken } from "../server/embed-session.js";
|
|
14
15
|
import { EMBED_SESSION_COOKIE, EMBED_TOKEN_QUERY_PARAM, MCP_APP_CHAT_BRIDGE_QUERY_PARAM, } from "../shared/embed-auth.js";
|
|
15
16
|
import { isMcpEmbedCorsOrigin, MCP_EMBED_CORS_ALLOW_HEADERS, MCP_EMBED_STATIC_ASSET_HEADERS, mcpEmbedStaticAssetRouteRules, shouldAllowMcpEmbedCredentials, } from "../shared/mcp-embed-headers.js";
|
|
@@ -2561,6 +2562,10 @@ function createNitroDevPlugin(options, appBasePath) {
|
|
|
2561
2562
|
// injected into deployed Functions. Nitro is a separate server build,
|
|
2562
2563
|
// so it needs the release ownership decision in its own replacement map.
|
|
2563
2564
|
"process.env.AGENT_NATIVE_RELEASE_MIGRATIONS": JSON.stringify(process.env.AGENT_NATIVE_RELEASE_MIGRATIONS?.trim() || ""),
|
|
2565
|
+
// Same reason as the release owner above: the recurring-jobs decision
|
|
2566
|
+
// belongs to the build env, and Nitro is a separate server build with its
|
|
2567
|
+
// own replacement map.
|
|
2568
|
+
[`process.env.${RECURRING_JOBS_BUILD_MARKER_ENV_VAR}`]: JSON.stringify(resolveRecurringJobsBuildMarker(process.env)),
|
|
2564
2569
|
},
|
|
2565
2570
|
// Never auto-load test files as server handlers/plugins/middleware.
|
|
2566
2571
|
// Nitro scans server/{plugins,middleware,routes,api}/*; a co-located
|
|
@@ -2913,6 +2918,11 @@ function createAgentNativeConfig(options = {}, command, userConfig = {}, mode =
|
|
|
2913
2918
|
// netlify.toml environment is available to the build but not injected
|
|
2914
2919
|
// into deployed Functions, so embed the decision in the server bundle.
|
|
2915
2920
|
"process.env.AGENT_NATIVE_RELEASE_MIGRATIONS": JSON.stringify(process.env.AGENT_NATIVE_RELEASE_MIGRATIONS?.trim() || ""),
|
|
2921
|
+
// Recurring jobs are turned off (and their platform scheduled trigger
|
|
2922
|
+
// omitted) by the BUILD environment, which a deployed serverless runtime
|
|
2923
|
+
// never sees. Embed the decision so the Automations page reports what
|
|
2924
|
+
// will actually fire instead of inferring it from runtime-only markers.
|
|
2925
|
+
[`process.env.${RECURRING_JOBS_BUILD_MARKER_ENV_VAR}`]: JSON.stringify(resolveRecurringJobsBuildMarker(process.env)),
|
|
2916
2926
|
...(resolvedAppConfig.deployment?.environment
|
|
2917
2927
|
? {
|
|
2918
2928
|
"process.env.AGENT_NATIVE_DEPLOYMENT_ENVIRONMENT": JSON.stringify(resolvedAppConfig.deployment.environment),
|
|
@@ -233,6 +233,12 @@ The scheduler is a framework plugin (the internal `processRecurringJobs()` routi
|
|
|
233
233
|
|
|
234
234
|
</Callout>
|
|
235
235
|
|
|
236
|
+
<Callout id="doc-block-rj3" tone="warning">
|
|
237
|
+
|
|
238
|
+
**`AGENT_NATIVE_DISABLE_RECURRING_JOBS` is read in two scopes.** In the **build** environment it decides whether the platform's scheduled trigger is emitted at all — on Netlify that emitted function is the only durable driver, so a build with the switch on ships an app where no schedule can fire, whatever the deployed environment says. In the **runtime** environment it gates the in-process `setInterval` loop. Set it in the build environment to turn recurring jobs off for a deploy; the build records its decision so the **Automations** page can say schedules will never run instead of showing a next-run date nothing will honour.
|
|
239
|
+
|
|
240
|
+
</Callout>
|
|
241
|
+
|
|
236
242
|
## Debugging a job {#debugging}
|
|
237
243
|
|
|
238
244
|
- Open the automation's details in **Automations**, or inspect
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.0.0-beta-
|
|
3
|
+
"version": "0.0.0-beta-20260820220930",
|
|
4
4
|
"description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -427,8 +427,8 @@
|
|
|
427
427
|
"y-protocols": "^1.0.7",
|
|
428
428
|
"yjs": "^13.6.32",
|
|
429
429
|
"zod": "^4.3.6",
|
|
430
|
-
"@agent-native/
|
|
431
|
-
"@agent-native/
|
|
430
|
+
"@agent-native/recap-cli": "0.0.0-beta-20260820220930",
|
|
431
|
+
"@agent-native/toolkit": "^0.16.9"
|
|
432
432
|
},
|
|
433
433
|
"devDependencies": {
|
|
434
434
|
"@ai-sdk/anthropic": "^3.0.71",
|