@agent-native/core 0.167.1 → 0.168.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/agent-native-docs/SKILL.md +11 -0
- package/corpus/templates/assets/.agents/skills/agent-native-docs/SKILL.md +11 -0
- package/corpus/templates/brain/.agents/skills/agent-native-docs/SKILL.md +11 -0
- package/corpus/templates/brain/actions/list-sources.ts +17 -7
- package/corpus/templates/brain/app/components/brain/Surface.tsx +4 -2
- package/corpus/templates/brain/app/lib/brain.ts +13 -3
- package/corpus/templates/brain/server/lib/brain-health.ts +5 -8
- package/corpus/templates/brain/server/lib/brain.ts +1 -0
- package/corpus/templates/brain/server/lib/capture-sanitization.ts +5 -0
- package/corpus/templates/brain/server/routes/api/brain/queue/run.post.ts +3 -2
- package/corpus/templates/calendar/.agents/skills/agent-native-docs/SKILL.md +11 -0
- package/corpus/templates/chat/.agents/skills/agent-native-docs/SKILL.md +11 -0
- package/corpus/templates/clips/.agents/skills/agent-native-docs/SKILL.md +11 -0
- package/corpus/templates/clips/app/routes/download.tsx +78 -131
- package/corpus/templates/clips/desktop/src/app.tsx +11 -10
- package/corpus/templates/clips/desktop/src/lib/desktop-oauth-window.ts +23 -0
- package/corpus/templates/content/.agents/skills/agent-native-docs/SKILL.md +11 -0
- package/corpus/templates/crm/.agents/skills/agent-native-docs/SKILL.md +11 -0
- package/corpus/templates/design/.agents/skills/agent-native-docs/SKILL.md +11 -0
- package/corpus/templates/dispatch/.agents/skills/agent-native-docs/SKILL.md +11 -0
- package/corpus/templates/factory/.agents/skills/agent-native-docs/SKILL.md +11 -0
- package/corpus/templates/forms/.agents/skills/agent-native-docs/SKILL.md +11 -0
- package/corpus/templates/macros/.agents/skills/agent-native-docs/SKILL.md +11 -0
- package/corpus/templates/mail/.agents/skills/agent-native-docs/SKILL.md +11 -0
- package/corpus/templates/plan/.agents/skills/agent-native-docs/SKILL.md +11 -0
- package/corpus/templates/slides/.agents/skills/agent-native-docs/SKILL.md +11 -0
- package/corpus/templates/tasks/.agents/skills/agent-native-docs/SKILL.md +11 -0
- package/dist/chat-threads/store.d.ts +7 -0
- package/dist/chat-threads/store.js +15 -0
- package/dist/client/AboutAgentNativeDialog.d.ts +5 -0
- package/dist/client/AboutAgentNativeDialog.js +56 -0
- package/dist/client/AgentPanel.d.ts +5 -1
- package/dist/client/AgentPanel.js +8 -8
- package/dist/client/AssistantChat.d.ts +3 -1
- package/dist/client/AssistantChat.js +22 -6
- package/dist/client/CommandMenu.d.ts +6 -1
- package/dist/client/CommandMenu.js +46 -3
- package/dist/client/EnvironmentBadge.js +4 -1
- package/dist/client/MultiTabAssistantChat.d.ts +3 -1
- package/dist/client/MultiTabAssistantChat.js +5 -4
- package/dist/client/agent-native-version.d.ts +12 -0
- package/dist/client/agent-native-version.js +36 -0
- 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/client/blocks/library/diagram.config.d.ts +4 -1
- package/dist/client/blocks/library/diagram.js +1 -1
- package/dist/client/chat/run-recovery.js +15 -7
- package/dist/client/use-chat-threads.d.ts +7 -0
- package/dist/client/use-chat-threads.js +144 -40
- package/dist/collab/routes.d.ts +1 -1
- package/dist/config.d.ts +3 -1
- package/dist/config.js +14 -9
- 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/core-messages/ar-SA.js +6 -0
- package/dist/localization/core-messages/de-DE.js +6 -0
- package/dist/localization/core-messages/en-US.d.ts +6 -0
- package/dist/localization/core-messages/en-US.js +6 -0
- package/dist/localization/core-messages/es-ES.js +6 -0
- package/dist/localization/core-messages/fr-FR.js +6 -0
- package/dist/localization/core-messages/hi-IN.js +6 -0
- package/dist/localization/core-messages/ja-JP.js +6 -0
- package/dist/localization/core-messages/ko-KR.js +6 -0
- package/dist/localization/core-messages/pt-BR.js +6 -0
- package/dist/localization/core-messages/zh-CN.js +6 -0
- package/dist/localization/core-messages/zh-TW.js +6 -0
- package/dist/localization/core-messages.d.ts +6 -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 +1 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/provider-api/actions/custom-provider-registration.d.ts +1 -1
- package/dist/provider-api/actions/provider-api.d.ts +12 -12
- 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 +115 -24
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/attribution.d.ts +29 -0
- package/dist/server/attribution.js +68 -0
- package/dist/server/auth.js +58 -15
- package/dist/server/better-auth-instance.js +16 -4
- package/dist/server/identity-sso.js +14 -2
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/request-context.d.ts +8 -0
- package/dist/server/request-context.js +4 -0
- package/dist/templates/chat/.agents/skills/agent-native-docs/SKILL.md +11 -0
- package/dist/templates/default/.agents/skills/agent-native-docs/SKILL.md +11 -0
- package/dist/templates/headless/.agents/skills/agent-native-docs/SKILL.md +11 -0
- package/dist/templates/workspace-core/.agents/skills/agent-native-docs/SKILL.md +11 -0
- package/dist/vite/agent-native-config-loader.js +1 -1
- package/dist/vite/client.js +19 -8
- package/dist/vite/package-versions.d.ts +1 -0
- package/dist/vite/package-versions.js +97 -0
- package/docs/content/agent-native-config.mdx +55 -31
- package/docs/content/deployment.mdx +3 -1
- package/docs/content/environment-variables.mdx +51 -14
- package/docs/content/recurring-jobs.mdx +6 -0
- package/package.json +2 -2
- package/src/templates/chat/.agents/skills/agent-native-docs/SKILL.md +11 -0
- package/src/templates/default/.agents/skills/agent-native-docs/SKILL.md +11 -0
- package/src/templates/headless/.agents/skills/agent-native-docs/SKILL.md +11 -0
- package/src/templates/workspace-core/.agents/skills/agent-native-docs/SKILL.md +11 -0
|
@@ -16,12 +16,14 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
16
16
|
* </CommandMenu>
|
|
17
17
|
*/
|
|
18
18
|
import { Command as CommandPrimitive, CommandGroup as CommandGroupPrimitive, CommandInput as CommandInputPrimitive, CommandItem as CommandItemPrimitive, CommandList as CommandListPrimitive, CommandSeparator as CommandSeparatorPrimitive, CommandShortcut as CommandShortcutPrimitive, } from "@agent-native/toolkit/ui/command";
|
|
19
|
-
import { IconBook2, IconExternalLink, IconMessage, IconHistory, } from "@tabler/icons-react";
|
|
19
|
+
import { IconBook2, IconExternalLink, IconInfoCircle, IconMessage, IconHistory, } from "@tabler/icons-react";
|
|
20
20
|
import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, } from "react";
|
|
21
21
|
import { parseChangelog } from "../changelog/parse.js";
|
|
22
|
+
import { AboutAgentNativeDialog } from "./AboutAgentNativeDialog.js";
|
|
22
23
|
import { sendToAgentChat } from "./agent-chat.js";
|
|
23
24
|
import { ChangelogDialog, useChangelogSeen } from "./changelog/Changelog.js";
|
|
24
25
|
import { Dialog, DialogContent, DialogTitle } from "./components/ui/dialog.js";
|
|
26
|
+
import { useT } from "./i18n.js";
|
|
25
27
|
import { cn } from "./utils.js";
|
|
26
28
|
const CommandMenuContext = createContext(null);
|
|
27
29
|
function useCommandMenuContext() {
|
|
@@ -122,15 +124,18 @@ function CommandDocsGroup({ docs, heading = "Docs" }) {
|
|
|
122
124
|
return null;
|
|
123
125
|
return (_jsx(CommandGroup, { heading: heading, children: docs.map((doc) => (_jsxs(CommandItem, { onSelect: () => openDocsHref(doc.href), keywords: [doc.title, doc.description ?? "", ...(doc.keywords ?? [])], deferSelect: false, className: "items-start py-2", children: [_jsx(IconBook2, { className: "mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" }), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "block truncate font-medium", children: doc.title }), doc.description ? (_jsx("span", { className: "mt-0.5 block line-clamp-2 text-xs leading-snug text-muted-foreground", children: doc.description })) : null] }), _jsx(IconExternalLink, { className: "mt-0.5 h-3.5 w-3.5 shrink-0 text-muted-foreground" })] }, doc.href))) }));
|
|
124
126
|
}
|
|
125
|
-
export function CommandMenu({ open, onOpenChange, children, renderResults, placeholder = "Type a command or ask AI...", emptyText: _emptyText = "No commands found.", showAgentFallback = true, className, changelog, changelogLabel = "What's new", changelogKey, }) {
|
|
127
|
+
export function CommandMenu({ open, onOpenChange, children, renderResults, placeholder = "Type a command or ask AI...", emptyText: _emptyText = "No commands found.", showAgentFallback = true, className, changelog, changelogLabel = "What's new", changelogKey, showAbout: showAboutProp, }) {
|
|
126
128
|
const [search, setSearch] = useState("");
|
|
127
129
|
const inputRef = useRef(null);
|
|
128
130
|
const containerRef = useRef(null);
|
|
131
|
+
const t = useT();
|
|
129
132
|
// Built-in "What's new" changelog surface (only active when `changelog` is
|
|
130
133
|
// passed). The dialog is rendered alongside the menu so it survives the menu
|
|
131
134
|
// closing; the unseen dot persists per browser via localStorage.
|
|
132
135
|
const [changelogOpen, setChangelogOpen] = useState(false);
|
|
136
|
+
const [aboutOpen, setAboutOpen] = useState(false);
|
|
133
137
|
const hasChangelog = typeof changelog === "string" && changelog.trim().length > 0;
|
|
138
|
+
const showAbout = showAboutProp ?? hasChangelog;
|
|
134
139
|
const changelogEntries = useMemo(() => (hasChangelog ? parseChangelog(changelog) : []), [hasChangelog, changelog]);
|
|
135
140
|
const latestChangelogId = changelogEntries[0]?.id;
|
|
136
141
|
const { unseen: changelogUnseen, markSeen: markChangelogSeen } = useChangelogSeen(changelogKey ?? "app", latestChangelogId);
|
|
@@ -140,6 +145,11 @@ export function CommandMenu({ open, onOpenChange, children, renderResults, place
|
|
|
140
145
|
// Let the menu close before the dialog opens (avoids overlay flicker).
|
|
141
146
|
setTimeout(() => setChangelogOpen(true), 50);
|
|
142
147
|
}, [onOpenChange, markChangelogSeen]);
|
|
148
|
+
const openAbout = useCallback(() => {
|
|
149
|
+
onOpenChange(false);
|
|
150
|
+
// Let the menu close before the dialog opens (avoids overlay flicker).
|
|
151
|
+
setTimeout(() => setAboutOpen(true), 50);
|
|
152
|
+
}, [onOpenChange]);
|
|
143
153
|
// Focus input when opening; clear search while closed so reopen never renders
|
|
144
154
|
// dynamic results for the previous query.
|
|
145
155
|
useEffect(() => {
|
|
@@ -178,6 +188,36 @@ export function CommandMenu({ open, onOpenChange, children, renderResults, place
|
|
|
178
188
|
.toLowerCase()
|
|
179
189
|
.includes(search.toLowerCase());
|
|
180
190
|
const showChangelogRow = hasChangelog && changelogRowMatches;
|
|
191
|
+
const aboutLabel = t("agentChat.aboutAgentNative.title", {
|
|
192
|
+
defaultValue: "About Agent Native",
|
|
193
|
+
});
|
|
194
|
+
const aboutVersionLabel = t("agentChat.aboutAgentNative.version", {
|
|
195
|
+
defaultValue: "Version",
|
|
196
|
+
});
|
|
197
|
+
const aboutEnvironmentLabel = t("agentChat.aboutAgentNative.environment", {
|
|
198
|
+
defaultValue: "Environment",
|
|
199
|
+
});
|
|
200
|
+
const aboutBuildLabel = t("agentChat.aboutAgentNative.build", {
|
|
201
|
+
defaultValue: "Build",
|
|
202
|
+
});
|
|
203
|
+
const aboutDiagnosticsLabel = t("agentChat.aboutAgentNative.copyDiagnostics", {
|
|
204
|
+
defaultValue: "Copy diagnostics",
|
|
205
|
+
});
|
|
206
|
+
const aboutRowMatches = !search ||
|
|
207
|
+
[
|
|
208
|
+
aboutLabel,
|
|
209
|
+
"agent native",
|
|
210
|
+
aboutVersionLabel,
|
|
211
|
+
"versions",
|
|
212
|
+
"package",
|
|
213
|
+
aboutEnvironmentLabel,
|
|
214
|
+
aboutBuildLabel,
|
|
215
|
+
aboutDiagnosticsLabel,
|
|
216
|
+
]
|
|
217
|
+
.join(" ")
|
|
218
|
+
.toLowerCase()
|
|
219
|
+
.includes(search.toLowerCase());
|
|
220
|
+
const showAboutRow = showAbout && aboutRowMatches;
|
|
181
221
|
// Filter children based on search
|
|
182
222
|
const filterChildren = (nodes) => {
|
|
183
223
|
return React.Children.map(nodes, (child) => {
|
|
@@ -242,7 +282,10 @@ export function CommandMenu({ open, onOpenChange, children, renderResults, place
|
|
|
242
282
|
}, className: cn("fixed left-1/2 top-[15vh] !z-50 !max-h-none -translate-x-1/2 !translate-y-0 !gap-0 w-full max-w-lg", "rounded-lg border border-border bg-popover p-0 text-popover-foreground shadow-lg", className), style: {
|
|
243
283
|
animation: "none",
|
|
244
284
|
transition: "none",
|
|
245
|
-
}, children: [_jsx(DialogTitle, { className: "sr-only", children: placeholder }), _jsx(CommandPrimitive, { loop: true, shouldFilter: false, className: "h-auto rounded-lg", children: _jsxs(CommandMenuContext.Provider, { value: { search, onOpenChange }, children: [_jsx(CommandInputPrimitive, { ref: inputRef, value: search, onValueChange: setSearch, placeholder: placeholder }), _jsxs(CommandListPrimitive, { children: [dynamicResults, hasResults && filteredChildren, showChangelogRow && (_jsxs(_Fragment, { children: [hasResults && _jsx(CommandSeparator, {}), _jsx("div", { className: "p-1", children: _jsxs(CommandItemPrimitive, { className: "cursor-pointer gap-2 py-2", onSelect: openChangelog, children: [_jsx(IconHistory, { className: "h-4 w-4 text-muted-foreground" }), _jsx("span", { children: changelogLabel }), changelogUnseen && (_jsx("span", { className: "ms-auto h-2 w-2 rounded-full bg-primary", "aria-label": "New updates available" }))] }) })] })),
|
|
285
|
+
}, children: [_jsx(DialogTitle, { className: "sr-only", children: placeholder }), _jsx(CommandPrimitive, { loop: true, shouldFilter: false, className: "h-auto rounded-lg", children: _jsxs(CommandMenuContext.Provider, { value: { search, onOpenChange }, children: [_jsx(CommandInputPrimitive, { ref: inputRef, value: search, onValueChange: setSearch, placeholder: placeholder }), _jsxs(CommandListPrimitive, { children: [dynamicResults, hasResults && filteredChildren, showChangelogRow && (_jsxs(_Fragment, { children: [hasResults && _jsx(CommandSeparator, {}), _jsx("div", { className: "p-1", children: _jsxs(CommandItemPrimitive, { className: "cursor-pointer gap-2 py-2", onSelect: openChangelog, children: [_jsx(IconHistory, { className: "h-4 w-4 text-muted-foreground" }), _jsx("span", { children: changelogLabel }), changelogUnseen && (_jsx("span", { className: "ms-auto h-2 w-2 rounded-full bg-primary", "aria-label": "New updates available" }))] }) })] })), showAboutRow && (_jsxs(_Fragment, { children: [(hasResults || showChangelogRow) && _jsx(CommandSeparator, {}), _jsx("div", { className: "p-1", children: _jsxs(CommandItemPrimitive, { className: "cursor-pointer gap-2 py-2", onSelect: openAbout, children: [_jsx(IconInfoCircle, { className: "h-4 w-4 text-muted-foreground" }), _jsx("span", { children: aboutLabel })] }) })] })), showAgentFallback && (_jsxs(_Fragment, { children: [(hasResults ||
|
|
286
|
+
showChangelogRow ||
|
|
287
|
+
showAboutRow ||
|
|
288
|
+
hasDynamicResults) && _jsx(CommandSeparator, {}), _jsx("div", { className: "p-1", children: _jsxs(CommandItemPrimitive, { className: "cursor-pointer gap-2 py-2", onSelect: handleSubmitToAgent, children: [_jsx(IconMessage, { className: "h-4 w-4 text-muted-foreground" }), _jsx("span", { children: search.trim() ? (_jsxs(_Fragment, { children: ["Ask AI:", " ", _jsxs("span", { className: "text-muted-foreground", children: ["\"", search, "\""] })] })) : (_jsx("span", { className: "text-muted-foreground", children: "Ask AI anything..." })) }), search.trim() && (_jsx("span", { className: "ms-auto text-xs text-muted-foreground", children: "\u21B5" }))] }) })] }))] })] }) })] }) }), hasChangelog && (_jsx(ChangelogDialog, { open: changelogOpen, onOpenChange: setChangelogOpen, markdown: changelog, title: changelogLabel })), showAbout && (_jsx(AboutAgentNativeDialog, { open: aboutOpen, onOpenChange: setAboutOpen }))] }));
|
|
246
289
|
}
|
|
247
290
|
// Helper to extract text content from React children
|
|
248
291
|
function getTextContent(children) {
|
|
@@ -6,6 +6,7 @@ import { BETA_OPT_OUT_QUERY_PARAM, BETA_OPT_OUT_STORAGE_KEY, buildEnvironmentOpt
|
|
|
6
6
|
import { trackEvent } from "./analytics.js";
|
|
7
7
|
import { injectedAgentNativeConfig } from "./app-config.js";
|
|
8
8
|
import { useSession } from "./use-session.js";
|
|
9
|
+
import { cn } from "./utils.js";
|
|
9
10
|
export { BETA_OPT_OUT_DURATION_MS, BETA_OPT_OUT_QUERY_PARAM, BETA_OPT_OUT_STORAGE_KEY, buildEnvironmentOptOutUrl, buildEnvironmentUrl, resolveEnvironmentTargets, } from "../shared/environment-lanes.js";
|
|
10
11
|
export function isBuilderIoEmployee(email) {
|
|
11
12
|
return email?.trim().toLowerCase().endsWith("@builder.io") ?? false;
|
|
@@ -88,7 +89,9 @@ function EnvironmentBadgeContent({ environment, targets, }) {
|
|
|
88
89
|
const title = environment === "beta"
|
|
89
90
|
? "You're on Agent Native Beta"
|
|
90
91
|
: "You're on Agent Native Production";
|
|
91
|
-
return (_jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx(Button, { "aria-label": `Open ${title.toLowerCase()} switcher`, className: "fixed bottom-3 right-3 z-[100] h-6 min-w-0 rounded-xl
|
|
92
|
+
return (_jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx(Button, { "aria-label": `Open ${title.toLowerCase()} switcher`, className: cn("fixed bottom-3 right-3 z-[100] h-6 min-w-0 rounded-xl px-2 text-[11px] font-semibold uppercase tracking-[0.5px] shadow-sm backdrop-blur-sm", environment === "beta"
|
|
93
|
+
? "border-primary/80"
|
|
94
|
+
: "border-border/80 bg-background/95 text-foreground"), size: "sm", variant: environment === "beta" ? "default" : "outline", children: label }) }), _jsxs(PopoverContent, { align: "center", className: "w-[280px] p-5", side: "top", sideOffset: 8, children: [_jsx("div", { className: "mb-1 text-sm font-semibold leading-5", children: title }), _jsx("div", { className: "mb-4 text-sm text-muted-foreground", children: "Choose where you want to continue." }), _jsx("div", { className: "grid gap-2", children: environment === "beta" ? (_jsx(EnvironmentLink, { href: productionHref, label: "Switch to production" })) : (_jsx(EnvironmentLink, { href: betaHref, label: "Go to beta" })) })] })] }));
|
|
92
95
|
}
|
|
93
96
|
function ProductionEnvironmentBadge({ targets, }) {
|
|
94
97
|
const { session, status } = useSession();
|
|
@@ -64,9 +64,11 @@ export type MultiTabAssistantChatProps = Omit<AssistantChatProps, "tabId" | "thr
|
|
|
64
64
|
threadUrlSync?: boolean | ChatThreadUrlSyncOptions;
|
|
65
65
|
/** Ambient resource context to show as a composer chip. */
|
|
66
66
|
scope?: ChatThreadScope | null;
|
|
67
|
+
/** Keep app-owned chat history isolated to the supplied scope. */
|
|
68
|
+
isolateHistoryByScope?: boolean;
|
|
67
69
|
/** @deprecated Scope context is now rendered in the composer. */
|
|
68
70
|
showScopeBadge?: boolean;
|
|
69
71
|
/** Cadence for hydrating agent-team sub-agent tab status. Default: 3000. */
|
|
70
72
|
agentTeamPollMs?: number;
|
|
71
73
|
};
|
|
72
|
-
export declare function MultiTabAssistantChat({ showTabBar, renderHeader, renderOverlay, contentHidden, apiUrl, storageKey, restoreActiveThread, browserTabId, threadUrlSync, scope, agentTeamPollMs, ...props }: MultiTabAssistantChatProps): React.JSX.Element;
|
|
74
|
+
export declare function MultiTabAssistantChat({ showTabBar, renderHeader, renderOverlay, contentHidden, apiUrl, storageKey, restoreActiveThread, browserTabId, threadUrlSync, scope, isolateHistoryByScope, agentTeamPollMs, ...props }: MultiTabAssistantChatProps): React.JSX.Element;
|
|
@@ -402,7 +402,7 @@ function runToAgentTeamTabInfo(run) {
|
|
|
402
402
|
status: run.status ?? "unknown",
|
|
403
403
|
};
|
|
404
404
|
}
|
|
405
|
-
export function MultiTabAssistantChat({ showTabBar = true, renderHeader, renderOverlay, contentHidden = false, apiUrl = agentNativePath("/_agent-native/agent-chat"), storageKey, restoreActiveThread = true, browserTabId, threadUrlSync = false, scope = null, agentTeamPollMs = DEFAULT_AGENT_TEAM_POLL_MS, ...props }) {
|
|
405
|
+
export function MultiTabAssistantChat({ showTabBar = true, renderHeader, renderOverlay, contentHidden = false, apiUrl = agentNativePath("/_agent-native/agent-chat"), storageKey, restoreActiveThread = true, browserTabId, threadUrlSync = false, scope = null, isolateHistoryByScope = false, agentTeamPollMs = DEFAULT_AGENT_TEAM_POLL_MS, ...props }) {
|
|
406
406
|
const translate = useT();
|
|
407
407
|
const contextNamespace = scope
|
|
408
408
|
? scope.contextKey?.trim() || `scope:${scope.type}:${scope.id}`
|
|
@@ -520,6 +520,7 @@ export function MultiTabAssistantChat({ showTabBar = true, renderHeader, renderO
|
|
|
520
520
|
routeThreadId: threadUrlSyncEnabled
|
|
521
521
|
? urlThreadId
|
|
522
522
|
: (activeDeepLinkedThreadId ?? undefined),
|
|
523
|
+
isolateHistoryByScope,
|
|
523
524
|
});
|
|
524
525
|
const switchThread = useCallback((threadId, options = {}) => {
|
|
525
526
|
switchThreadState(threadId);
|
|
@@ -1911,7 +1912,7 @@ export function MultiTabAssistantChat({ showTabBar = true, renderHeader, renderO
|
|
|
1911
1912
|
const isActive = tab.id === activeThreadId ||
|
|
1912
1913
|
(tab.id === focusParentId &&
|
|
1913
1914
|
activeTab?.parentThreadId === tab.id);
|
|
1914
|
-
return (_jsxs("div", { ref: isActive ? activeTabRefCb : undefined, className: cn("agent-tab relative flex items-center rounded-md text-[11px] font-medium shrink-0 max-w-[130px]", isActive
|
|
1915
|
+
return (_jsxs("div", { ref: isActive ? activeTabRefCb : undefined, className: cn("agent-tab relative flex items-center rounded-md text-[11px] font-medium shrink-0 min-w-[56px] max-w-[130px]", isActive
|
|
1915
1916
|
? "bg-accent text-foreground ring-1 ring-inset ring-border/60 shadow-sm"
|
|
1916
1917
|
: "text-muted-foreground hover:text-foreground hover:bg-accent/50"), children: [_jsxs("button", { type: "button", onClick: () => switchThread(tab.id), className: "flex items-center gap-1 px-2.5 py-1.5 min-w-0 flex-1 text-start", children: [_jsx("span", { className: "truncate pe-1", children: tab.label }), tab.status === "running" && (_jsx("span", { className: "w-1.5 h-1.5 rounded-full bg-muted-foreground/50 shrink-0 animate-pulse" }))] }), _jsx("button", { type: "button", "aria-label": translate("agentChat.tabs.closeTab"), onClick: (e) => {
|
|
1917
1918
|
e.stopPropagation();
|
|
@@ -1931,7 +1932,7 @@ export function MultiTabAssistantChat({ showTabBar = true, renderHeader, renderO
|
|
|
1931
1932
|
? "bg-accent text-foreground"
|
|
1932
1933
|
: "text-muted-foreground hover:bg-accent hover:text-foreground"), children: translate("agentChat.tabs.main") }), childTabs.map((tab) => (_jsxs("div", { ref: tab.id === activeThreadId
|
|
1933
1934
|
? activeTabRefCb
|
|
1934
|
-
: undefined, className: cn("agent-tab relative flex shrink-0 items-center rounded-md text-[10px] font-medium max-w-[130px]", tab.id === activeThreadId
|
|
1935
|
+
: undefined, className: cn("agent-tab relative flex shrink-0 items-center rounded-md text-[10px] font-medium min-w-[48px] max-w-[130px]", tab.id === activeThreadId
|
|
1935
1936
|
? "bg-accent text-foreground"
|
|
1936
1937
|
: "text-muted-foreground hover:bg-accent hover:text-foreground"), children: [_jsxs("button", { type: "button", onClick: () => switchThread(tab.id), className: "flex items-center gap-1 px-2 py-1 min-w-0 flex-1 text-start", children: [_jsx("span", { className: "truncate pe-1", children: tab.subAgentName || tab.label }), tab.status === "running" && (_jsx("span", { className: "w-1 h-1 rounded-full bg-muted-foreground/50 shrink-0 animate-pulse" }))] }), _jsx("button", { type: "button", "aria-label": translate("agentChat.tabs.closeTab"), onClick: (e) => {
|
|
1937
1938
|
e.stopPropagation();
|
|
@@ -1969,7 +1970,7 @@ export function MultiTabAssistantChat({ showTabBar = true, renderHeader, renderO
|
|
|
1969
1970
|
else {
|
|
1970
1971
|
chatRefs.current.delete(tabId);
|
|
1971
1972
|
}
|
|
1972
|
-
}, threadId: tabId, tabId: tabId, browserTabId: browserTabId, contextNamespace: contextNamespace, isActiveComposer: tabId === activeThreadId, apiUrl: apiUrl, isNewThread: newThreadIds.current.has(tabId) || isNewThread(tabId), isThreadStateLoading: isLoading, onMessageCountChange: (count) => setMessageCounts((prev) => prev[tabId] === count
|
|
1973
|
+
}, threadId: tabId, tabId: tabId, browserTabId: browserTabId, contextScope: scope, contextNamespace: contextNamespace, isolateHistoryByScope: isolateHistoryByScope, isActiveComposer: tabId === activeThreadId, apiUrl: apiUrl, isNewThread: newThreadIds.current.has(tabId) || isNewThread(tabId), isThreadStateLoading: isLoading, onMessageCountChange: (count) => setMessageCounts((prev) => prev[tabId] === count
|
|
1973
1974
|
? prev
|
|
1974
1975
|
: { ...prev, [tabId]: count }), onSaveThread: handleSaveThread, onGenerateTitle: handleGenerateTitle, onSlashCommand: handleSlashCommand, selectedModel: modelSelection?.model, selectedEngine: modelSelection?.engine, selectedEffort: modelSelection?.effort ?? DEFAULT_REASONING_EFFORT, composerSlot: props.composerSlot, defaultModel: defaultModel, availableModels: availableModels, modelListLoading: modelListLoading, onModelChange: handleModelChange, onEffortChange: handleEffortChange, onForkChat: () => handleForkChat(tabId),
|
|
1975
1976
|
// Sub-agent tabs are read-only: sending a new message from the
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface AgentNativePackageVersion {
|
|
2
|
+
name: string;
|
|
3
|
+
version: string;
|
|
4
|
+
}
|
|
5
|
+
export interface AgentNativeDiagnostics {
|
|
6
|
+
buildId: string;
|
|
7
|
+
environment: string;
|
|
8
|
+
packages: Record<string, string>;
|
|
9
|
+
}
|
|
10
|
+
export declare function getAgentNativePackageVersions(): AgentNativePackageVersion[];
|
|
11
|
+
export declare function getAgentNativeDiagnostics(): AgentNativeDiagnostics;
|
|
12
|
+
export declare function formatAgentNativeDiagnostics(): string;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { injectedAgentNativeConfig } from "./app-config.js";
|
|
2
|
+
import { clientBuildId } from "./build-compatibility.js";
|
|
3
|
+
function readPackageVersions(value) {
|
|
4
|
+
if (!value || typeof value !== "object") {
|
|
5
|
+
return {};
|
|
6
|
+
}
|
|
7
|
+
return Object.fromEntries(Object.entries(value)
|
|
8
|
+
.filter(([name, version]) => name.startsWith("@agent-native/") &&
|
|
9
|
+
typeof version === "string" &&
|
|
10
|
+
version.trim().length > 0)
|
|
11
|
+
.map(([name, version]) => [name, version.trim()]));
|
|
12
|
+
}
|
|
13
|
+
function injectedPackageVersions() {
|
|
14
|
+
if (typeof __AGENT_NATIVE_PACKAGE_VERSIONS__ !== "undefined") {
|
|
15
|
+
return readPackageVersions(__AGENT_NATIVE_PACKAGE_VERSIONS__);
|
|
16
|
+
}
|
|
17
|
+
return readPackageVersions(globalThis.__AGENT_NATIVE_PACKAGE_VERSIONS__);
|
|
18
|
+
}
|
|
19
|
+
export function getAgentNativePackageVersions() {
|
|
20
|
+
return Object.entries(injectedPackageVersions())
|
|
21
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
22
|
+
.map(([name, version]) => ({ name, version }));
|
|
23
|
+
}
|
|
24
|
+
export function getAgentNativeDiagnostics() {
|
|
25
|
+
return {
|
|
26
|
+
buildId: clientBuildId() || "development",
|
|
27
|
+
environment: injectedAgentNativeConfig().deployment?.environment ?? "unknown",
|
|
28
|
+
packages: Object.fromEntries(getAgentNativePackageVersions().map(({ name, version }) => [
|
|
29
|
+
name,
|
|
30
|
+
version,
|
|
31
|
+
])),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export function formatAgentNativeDiagnostics() {
|
|
35
|
+
return JSON.stringify(getAgentNativeDiagnostics(), null, 2);
|
|
36
|
+
}
|
|
@@ -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
|
}
|
|
@@ -37,7 +37,10 @@ export interface DiagramData {
|
|
|
37
37
|
*/
|
|
38
38
|
html?: string;
|
|
39
39
|
css?: string;
|
|
40
|
-
/**
|
|
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
|
|
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: [] }),
|