@agent-native/core 0.167.0 → 0.167.4

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.
Files changed (79) hide show
  1. package/corpus/templates/analytics/.agents/skills/agent-native-docs/SKILL.md +11 -0
  2. package/corpus/templates/assets/.agents/skills/agent-native-docs/SKILL.md +11 -0
  3. package/corpus/templates/brain/.agents/skills/agent-native-docs/SKILL.md +11 -0
  4. package/corpus/templates/calendar/.agents/skills/agent-native-docs/SKILL.md +11 -0
  5. package/corpus/templates/chat/.agents/skills/agent-native-docs/SKILL.md +11 -0
  6. package/corpus/templates/clips/.agents/skills/agent-native-docs/SKILL.md +11 -0
  7. package/corpus/templates/clips/CHANGELOG.md +1 -0
  8. package/corpus/templates/clips/app/routes/download.tsx +78 -131
  9. package/corpus/templates/content/.agents/skills/agent-native-docs/SKILL.md +11 -0
  10. package/corpus/templates/crm/.agents/skills/agent-native-docs/SKILL.md +11 -0
  11. package/corpus/templates/design/.agents/skills/agent-native-docs/SKILL.md +11 -0
  12. package/corpus/templates/dispatch/.agents/skills/agent-native-docs/SKILL.md +11 -0
  13. package/corpus/templates/factory/.agents/skills/agent-native-docs/SKILL.md +11 -0
  14. package/corpus/templates/forms/.agents/skills/agent-native-docs/SKILL.md +11 -0
  15. package/corpus/templates/macros/.agents/skills/agent-native-docs/SKILL.md +11 -0
  16. package/corpus/templates/mail/.agents/skills/agent-native-docs/SKILL.md +11 -0
  17. package/corpus/templates/plan/.agents/skills/agent-native-docs/SKILL.md +11 -0
  18. package/corpus/templates/slides/.agents/skills/agent-native-docs/SKILL.md +11 -0
  19. package/corpus/templates/tasks/.agents/skills/agent-native-docs/SKILL.md +11 -0
  20. package/dist/changelog/parse.js +219 -8
  21. package/dist/chat-threads/store.d.ts +7 -0
  22. package/dist/chat-threads/store.js +15 -0
  23. package/dist/client/AgentPanel.d.ts +5 -1
  24. package/dist/client/AgentPanel.js +8 -8
  25. package/dist/client/AssistantChat.d.ts +3 -1
  26. package/dist/client/AssistantChat.js +22 -6
  27. package/dist/client/MultiTabAssistantChat.d.ts +3 -1
  28. package/dist/client/MultiTabAssistantChat.js +5 -4
  29. package/dist/client/agent-page/AgentJobsTab.js +39 -6
  30. package/dist/client/agent-page/AutomationScheduleDialog.d.ts +10 -1
  31. package/dist/client/agent-page/AutomationScheduleDialog.js +3 -2
  32. package/dist/client/agent-page/ScheduledTriggerNotice.d.ts +22 -0
  33. package/dist/client/agent-page/ScheduledTriggerNotice.js +84 -0
  34. package/dist/client/agent-page/scheduled-trigger-state.d.ts +28 -0
  35. package/dist/client/agent-page/scheduled-trigger-state.js +10 -0
  36. package/dist/client/agent-page/use-jobs.d.ts +7 -0
  37. package/dist/client/agent-page/use-jobs.js +14 -0
  38. package/dist/client/blocks/library/diagram.config.d.ts +4 -1
  39. package/dist/client/blocks/library/diagram.js +1 -1
  40. package/dist/client/chat/run-recovery.js +15 -7
  41. package/dist/client/use-chat-threads.d.ts +7 -0
  42. package/dist/client/use-chat-threads.js +144 -40
  43. package/dist/deploy/build.d.ts +5 -0
  44. package/dist/deploy/build.js +12 -1
  45. package/dist/framework-tools.js +1 -0
  46. package/dist/jobs/actions/get-scheduled-trigger-status.d.ts +13 -0
  47. package/dist/jobs/actions/get-scheduled-trigger-status.js +21 -0
  48. package/dist/localization/default-messages.d.ts +14 -0
  49. package/dist/localization/default-messages.js +14 -0
  50. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  51. package/dist/provider-api/actions/custom-provider-registration.d.ts +4 -4
  52. package/dist/provider-api/actions/provider-api.d.ts +13 -13
  53. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  54. package/dist/secrets/routes.d.ts +9 -9
  55. package/dist/server/action-discovery.js +4 -0
  56. package/dist/server/agent-chat/recurring-jobs-runtime.d.ts +42 -1
  57. package/dist/server/agent-chat/recurring-jobs-runtime.js +102 -9
  58. package/dist/server/agent-chat-plugin.d.ts +3 -1
  59. package/dist/server/agent-chat-plugin.js +115 -24
  60. package/dist/server/attribution.d.ts +29 -0
  61. package/dist/server/attribution.js +68 -0
  62. package/dist/server/auth.js +58 -15
  63. package/dist/server/better-auth-instance.js +16 -4
  64. package/dist/server/identity-sso.js +14 -2
  65. package/dist/server/realtime-token.d.ts +1 -1
  66. package/dist/server/request-context.d.ts +8 -0
  67. package/dist/server/request-context.js +4 -0
  68. package/dist/templates/chat/.agents/skills/agent-native-docs/SKILL.md +11 -0
  69. package/dist/templates/default/.agents/skills/agent-native-docs/SKILL.md +11 -0
  70. package/dist/templates/headless/.agents/skills/agent-native-docs/SKILL.md +11 -0
  71. package/dist/templates/workspace-core/.agents/skills/agent-native-docs/SKILL.md +11 -0
  72. package/dist/vite/client.js +10 -0
  73. package/docs/content/deployment.mdx +3 -1
  74. package/docs/content/recurring-jobs.mdx +6 -0
  75. package/package.json +2 -2
  76. package/src/templates/chat/.agents/skills/agent-native-docs/SKILL.md +11 -0
  77. package/src/templates/default/.agents/skills/agent-native-docs/SKILL.md +11 -0
  78. package/src/templates/headless/.agents/skills/agent-native-docs/SKILL.md +11 -0
  79. package/src/templates/workspace-core/.agents/skills/agent-native-docs/SKILL.md +11 -0
@@ -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 { useAutomations, useManageAutomation, useManageRecurringJob, useRunAutomationNow, useRecurringJobs, } from "./use-jobs.js";
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
- function detailsFields(entry, t, formatDateTime) {
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: formatDateTime(resource.nextRun) ?? unset,
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
  }
@@ -37,7 +37,10 @@ export interface DiagramData {
37
37
  */
38
38
  html?: string;
39
39
  css?: string;
40
- /** `design` forces clean HTML/CSS rendering without the sketch overlay. */
40
+ /**
41
+ * `design` forces clean HTML/CSS rendering without the sketch overlay. Use it
42
+ * for UI-like blocks such as cards, logo walls, tables, and controls.
43
+ */
41
44
  renderMode?: "wireframe" | "design";
42
45
  caption?: string;
43
46
  /** Outer surface frame. `auto` lets the host choose the right default. */
@@ -393,7 +393,7 @@ export const diagramBlock = defineBlock({
393
393
  // edit from a corner button + panel rather than inline.
394
394
  editSurface: "panel",
395
395
  label: "Diagram",
396
- description: "A flexible inline architecture/code diagram. Prefer html/css with SVG or semantic HTML for polished two-dimensional layouts; use renderMode design for clean presentation or .diagram-* primitives and --wf-* tokens for theme/sketch compatibility. Legacy nodes/edges are only for simple previews.",
396
+ description: "A flexible inline architecture/code diagram. Prefer html/css with SVG or semantic HTML for polished two-dimensional layouts; use renderMode design for UI-like blocks such as cards, logo walls, tables, and controls, or .diagram-* primitives and --wf-* tokens for actual diagrams that need theme/sketch compatibility. Legacy nodes/edges are only for simple previews.",
397
397
  // Seed the legacy fallback shape so a fresh block validates while agents can
398
398
  // replace it with html/css when layout quality matters.
399
399
  empty: () => ({ nodes: [{ id: "n1", label: "Module" }], edges: [] }),
@@ -298,17 +298,25 @@ export function RunErrorRecoveryCard({ info, onContinue, onRetry, onFork, onDism
298
298
  return (_jsxs("div", { className: "rounded-lg border border-amber-500/25 bg-amber-500/[0.06] p-3 text-sm", children: [_jsxs("div", { className: "flex items-start gap-2", children: [_jsx("span", { className: "mt-0.5 flex h-6 w-6 shrink-0 items-center justify-center rounded-md bg-amber-500/10 text-amber-700 dark:text-amber-300", children: _jsx(IconAlertTriangle, { size: 14 }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "font-medium text-foreground", children: runErrorHeadline(info, {
299
299
  recoverable: t("agentChat.error.stopped"),
300
300
  terminal: t("agentChat.error.failed"),
301
- }) }), _jsx("p", { className: "mt-1 text-xs leading-relaxed text-muted-foreground", children: localizeKnownChatErrorText(info.message, t) }), isProviderAuthError && (_jsx("div", { className: "mt-3 rounded-md border border-border/70 bg-background/60 p-2.5", children: _jsx(BuilderSetupContent, { layout: "sidebar", onConnected: handleProviderConnected }) })), shouldShowBuilderReconnect && !builderReconnectResolved && (_jsx("p", { className: "mt-2 text-xs leading-relaxed text-muted-foreground", children: t("agentChat.recovery.credentialRejected") })), isConnectionRecoveryError && (_jsx("p", { className: "mt-2 text-xs leading-relaxed text-muted-foreground", children: t("agentChat.recovery.newChatHint") })), (info.runId || info.errorCode || info.details) && (_jsxs("button", { type: "button", onClick: () => setDetailsOpen((v) => !v), className: "mt-2 inline-flex items-center gap-1 text-[11px] font-medium text-muted-foreground hover:text-foreground", children: [_jsx(IconChevronDown, { size: 12, className: cn("transition-transform", detailsOpen && "rotate-180") }), t("agentChat.common.details")] })), detailsOpen && (_jsxs("div", { className: "mt-2 rounded-md border border-border/60 bg-background/70 p-2 font-mono text-[11px] leading-relaxed text-muted-foreground", children: [info.runId && _jsxs("div", { children: ["run: ", info.runId] }), info.errorCode && _jsxs("div", { children: ["code: ", info.errorCode] }), info.details && (_jsx("pre", { className: "mt-2 max-h-28 overflow-auto whitespace-pre-wrap break-words font-mono", children: info.details }))] }))] }), _jsx("button", { type: "button", onClick: onDismiss, "aria-label": t("agentChat.common.dismiss"), className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-md text-muted-foreground hover:bg-background/80 hover:text-foreground", children: _jsx(IconX, { size: 14 }) })] }), _jsxs("div", { className: "mt-3 flex flex-wrap items-center gap-2", children: [shouldShowBuilderReconnect && !builderReconnectResolved && (_jsxs("button", { type: "button", onClick: () => builderReconnect.start(), disabled: builderReconnect.connecting, className: "inline-flex h-8 items-center gap-1.5 rounded-md bg-foreground px-3 text-xs font-medium text-background hover:opacity-90 disabled:cursor-wait disabled:opacity-70", children: [builderReconnect.connecting ? (_jsx(IconLoader2, { size: 13, className: "animate-spin" })) : null, builderReconnect.connecting
301
+ }) }), _jsx("p", { className: "mt-1 text-xs leading-relaxed text-muted-foreground", children: localizeKnownChatErrorText(info.message, t) }), isProviderAuthError && (_jsx("div", { className: "mt-3 rounded-md border border-border/70 bg-background/60 p-2.5", children: _jsx(BuilderSetupContent, { layout: "sidebar", onConnected: handleProviderConnected }) })), shouldShowBuilderReconnect && !builderReconnectResolved && (_jsx("p", { className: "mt-2 text-xs leading-relaxed text-muted-foreground", children: t("agentChat.recovery.credentialRejected") })), isConnectionRecoveryError && (_jsx("p", { className: "mt-2 text-xs leading-relaxed text-muted-foreground", children: t("agentChat.recovery.newChatHint") })), (info.runId || info.errorCode || info.details) && (_jsxs("button", { type: "button", onClick: () => setDetailsOpen((v) => !v), className: "mt-2 inline-flex items-center gap-1 text-[11px] font-medium text-muted-foreground hover:text-foreground", children: [_jsx(IconChevronDown, { size: 12, className: cn("transition-transform", detailsOpen && "rotate-180") }), t("agentChat.common.details")] })), detailsOpen && (_jsxs("div", { className: "mt-2 rounded-md border border-border/60 bg-background/70 p-2 font-mono text-[11px] leading-relaxed text-muted-foreground", children: [info.runId && _jsxs("div", { children: ["run: ", info.runId] }), info.errorCode && _jsxs("div", { children: ["code: ", info.errorCode] }), info.details && (_jsx("pre", { className: "mt-2 max-h-28 overflow-auto whitespace-pre-wrap break-words font-mono", children: info.details }))] }))] }), _jsx("button", { type: "button", onClick: onDismiss, "aria-label": t("agentChat.common.dismiss"), className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-md text-muted-foreground hover:bg-background/80 hover:text-foreground", children: _jsx(IconX, { size: 14 }) })] }), _jsxs("div", { className: "mt-3 flex min-w-0 items-center gap-2", children: [shouldShowBuilderReconnect && !builderReconnectResolved && (_jsxs("button", { type: "button", onClick: () => builderReconnect.start(), disabled: builderReconnect.connecting, className: "inline-flex h-8 items-center gap-1.5 rounded-md bg-foreground px-3 text-xs font-medium text-background hover:opacity-90 disabled:cursor-wait disabled:opacity-70", children: [builderReconnect.connecting ? (_jsx(IconLoader2, { size: 13, className: "animate-spin" })) : null, builderReconnect.connecting
302
302
  ? t("agentChat.recovery.connectingBuilder")
303
- : t("agentChat.recovery.reconnectBuilder")] })), canRecover && (_jsx(_Fragment, { children: _jsxs("button", { type: "button", onClick: onContinue, className: "inline-flex h-8 items-center gap-1.5 rounded-md bg-foreground px-3 text-xs font-medium text-background hover:opacity-90", children: [_jsx(IconPlayerPlay, { size: 13 }), t("agentChat.common.continue")] }) })), canRetry && (_jsxs("button", { type: "button", onClick: onRetry, className: "inline-flex h-8 items-center gap-1.5 rounded-md border border-border bg-background px-3 text-xs font-medium text-foreground hover:bg-accent", children: [_jsx(IconRefresh, { size: 13 }), isQueryError
304
- ? t("agentChat.recovery.diagnoseRetry")
305
- : t("agentChat.common.retry")] })), canRecover && isConnectionRecoveryError && (_jsxs("button", { type: "button", onClick: startNewChat, className: "inline-flex h-8 items-center gap-1.5 rounded-md border border-border bg-background px-3 text-xs font-medium text-foreground hover:bg-accent", children: [_jsx(IconPlus, { size: 13 }), t("agentChat.tabs.newChat")] })), canRecover && onFork && !isConnectionRecoveryError && (_jsxs("button", { type: "button", onClick: handleFork, disabled: forking, title: t("agentChat.recovery.forkDescription"), "aria-label": t("agentChat.recovery.forkDescription"), className: "inline-flex h-8 items-center gap-1.5 rounded-md border border-border bg-background px-3 text-xs font-medium text-foreground hover:bg-accent disabled:cursor-wait disabled:opacity-70", children: [forking ? (_jsx(IconLoader2, { size: 13, className: "animate-spin" })) : (_jsx(IconGitFork, { size: 13 })), forking
306
- ? t("agentChat.recovery.forking")
307
- : t("agentChat.message.forkChat")] })), _jsxs("button", { type: "button", onClick: copyDetails, className: "ms-auto inline-flex h-8 items-center gap-1.5 rounded-md px-2.5 text-xs font-medium text-muted-foreground hover:bg-background/80 hover:text-foreground", children: [copyState === "copied" ? (_jsx(IconCheck, { size: 13 })) : copyState === "failed" ? (_jsx(IconX, { size: 13 })) : (_jsx(IconCopy, { size: 13 })), _jsx("span", { "aria-live": "polite", children: copyState === "copied"
303
+ : t("agentChat.recovery.reconnectBuilder")] })), canRecover && (_jsxs("button", { type: "button", onClick: onContinue, className: "inline-flex min-w-0 flex-1 items-center justify-center gap-1.5 rounded-md bg-foreground px-3 text-xs font-medium text-background hover:opacity-90", children: [_jsx(IconPlayerPlay, { size: 13 }), _jsx("span", { className: "truncate", children: t("agentChat.common.continue") })] })), _jsxs("div", { className: "flex shrink-0 items-center gap-0.5 rounded-md border border-border/70 bg-background/60 p-0.5", children: [canRetry && (_jsx("button", { type: "button", onClick: onRetry, title: isQueryError
304
+ ? t("agentChat.recovery.diagnoseRetry")
305
+ : t("agentChat.common.retry"), "aria-label": isQueryError
306
+ ? t("agentChat.recovery.diagnoseRetry")
307
+ : t("agentChat.common.retry"), className: "inline-flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground", children: _jsx(IconRefresh, { size: 14 }) })), canRecover && isConnectionRecoveryError && (_jsx("button", { type: "button", onClick: startNewChat, title: t("agentChat.tabs.newChat"), "aria-label": t("agentChat.tabs.newChat"), className: "inline-flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground", children: _jsx(IconPlus, { size: 14 }) })), canRecover && onFork && !isConnectionRecoveryError && (_jsx("button", { type: "button", onClick: handleFork, disabled: forking, title: t("agentChat.recovery.forkDescription"), "aria-label": t("agentChat.recovery.forkDescription"), className: "inline-flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground disabled:cursor-wait disabled:opacity-70", children: forking ? (_jsx(IconLoader2, { size: 14, className: "animate-spin" })) : (_jsx(IconGitFork, { size: 14 })) })), _jsxs("button", { type: "button", onClick: copyDetails, title: copyState === "copied"
308
308
  ? t("agentChat.common.copied")
309
309
  : copyState === "failed"
310
310
  ? t("agentChat.recovery.copyFailed")
311
- : copyLabel })] })] }), shouldShowBuilderReconnect && builderReconnect.error && (_jsx("p", { className: "mt-2 text-xs leading-relaxed text-red-500", children: builderReconnect.error })), forkError && (_jsx("p", { className: "mt-2 text-xs leading-relaxed text-red-500", children: forkError }))] }));
311
+ : copyLabel, "aria-label": copyState === "copied"
312
+ ? t("agentChat.common.copied")
313
+ : copyState === "failed"
314
+ ? t("agentChat.recovery.copyFailed")
315
+ : copyLabel, className: "inline-flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground", children: [copyState === "copied" ? (_jsx(IconCheck, { size: 14 })) : copyState === "failed" ? (_jsx(IconX, { size: 14 })) : (_jsx(IconCopy, { size: 14 })), _jsx("span", { className: "sr-only", "aria-live": "polite", children: copyState === "copied"
316
+ ? t("agentChat.common.copied")
317
+ : copyState === "failed"
318
+ ? t("agentChat.recovery.copyFailed")
319
+ : copyLabel })] })] })] }), shouldShowBuilderReconnect && builderReconnect.error && (_jsx("p", { className: "mt-2 text-xs leading-relaxed text-red-500", children: builderReconnect.error })), forkError && (_jsx("p", { className: "mt-2 text-xs leading-relaxed text-red-500", children: forkError }))] }));
312
320
  }
313
321
  // ─── LoopLimitContinueCard ────────────────────────────────────────────────────
314
322
  export function LoopLimitContinueCard({ info, onContinue, }) {
@@ -66,7 +66,14 @@ export interface UseChatThreadsOptions {
66
66
  routeThreadId?: string | null;
67
67
  /** Include connected and other-app chats in list/search results. */
68
68
  includeExternal?: boolean;
69
+ /**
70
+ * Restrict history to the supplied scope. App-owned chat rails use this to
71
+ * keep one app's conversations out of another app's history; ordinary
72
+ * resource scopes intentionally continue to show general chats.
73
+ */
74
+ isolateHistoryByScope?: boolean;
69
75
  }
76
+ export declare function appendChatThreadScopeParams(params: URLSearchParams, scope?: ChatThreadScope | null): void;
70
77
  export declare function useChatThreads(apiUrl?: string, storageKey?: string, scope?: ChatThreadScope | null, options?: UseChatThreadsOptions): {
71
78
  threads: ChatThreadSummary[];
72
79
  activeThreadId: string | null;