@truefoundry/trueforge-ui 0.3.0-rc.0 → 0.3.0-rc.10

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 (115) hide show
  1. package/CHANGELOG.md +191 -0
  2. package/README.md +35 -12
  3. package/dist/AgentCodeSnippets-RJBB4DDE.js +89 -0
  4. package/dist/AgentCodeSnippets-RJBB4DDE.js.map +1 -0
  5. package/dist/AgentMetricChart-UPGSJ2WP.js +15 -0
  6. package/dist/AgentOverview-U5WIQXZN.js +169 -0
  7. package/dist/AgentOverview-U5WIQXZN.js.map +1 -0
  8. package/dist/AgentSessionEventTimeline-GRGIQTRE.js +88 -0
  9. package/dist/AgentSessionEventTimeline-GRGIQTRE.js.map +1 -0
  10. package/dist/AgentSessionEventTimelineChart-GNF4VOUT.js +591 -0
  11. package/dist/AgentSessionEventTimelineChart-GNF4VOUT.js.map +1 -0
  12. package/dist/AgentSessions-5OTF2BUW.js +323 -0
  13. package/dist/AgentSessions-5OTF2BUW.js.map +1 -0
  14. package/dist/{ConnectorSettings-FXQ5B7TN.js → ConnectorSettings-HDKGLDCY.js} +163 -125
  15. package/dist/ConnectorSettings-HDKGLDCY.js.map +1 -0
  16. package/dist/{DockLayout-LBNQJZNF.js → DockLayout-D2FPQCAL.js} +11 -10
  17. package/dist/{DockLayout-LBNQJZNF.js.map → DockLayout-D2FPQCAL.js.map} +1 -1
  18. package/dist/DrawerLayout-HUV7RAWL.js +186 -0
  19. package/dist/DrawerLayout-HUV7RAWL.js.map +1 -0
  20. package/dist/{ModelSettings-4L4LOQJA.js → ModelSettings-VNQ7HPKH.js} +23 -27
  21. package/dist/ModelSettings-VNQ7HPKH.js.map +1 -0
  22. package/dist/OpenUiFenceBlock-KPZFLUSC.js +9 -0
  23. package/dist/OpenUiFenceBlock-KPZFLUSC.js.map +1 -0
  24. package/dist/{SandboxSettings-Z4ZG6ZX4.js → SandboxSettings-ZLXGMFBF.js} +18 -20
  25. package/dist/SandboxSettings-ZLXGMFBF.js.map +1 -0
  26. package/dist/SchedulesPage-QKYI2Z3A.js +15 -0
  27. package/dist/SchedulesPage-QKYI2Z3A.js.map +1 -0
  28. package/dist/{SettingsBuilder-WF7ZWKQW.js → SettingsBuilder-RVVYXOJW.js} +27 -23
  29. package/dist/SettingsBuilder-RVVYXOJW.js.map +1 -0
  30. package/dist/ShellRouteSync-SJXFKUAJ.js +390 -0
  31. package/dist/ShellRouteSync-SJXFKUAJ.js.map +1 -0
  32. package/dist/SidebarLayout-KYA76FQ7.js +330 -0
  33. package/dist/SidebarLayout-KYA76FQ7.js.map +1 -0
  34. package/dist/{SkillSettings-3EPPC3IT.js → SkillSettings-7OHT7FPI.js} +25 -21
  35. package/dist/SkillSettings-7OHT7FPI.js.map +1 -0
  36. package/dist/{TrueForgeUIWithRouter-NZ4FGPE4.js → TrueForgeUIWithRouter-LKG4XLW6.js} +14 -10
  37. package/dist/{TrueForgeUIWithRouter-NZ4FGPE4.js.map → TrueForgeUIWithRouter-LKG4XLW6.js.map} +1 -1
  38. package/dist/{WidgetLayout-3QZ357FL.js → WidgetLayout-W2RGGOQ3.js} +17 -13
  39. package/dist/WidgetLayout-W2RGGOQ3.js.map +1 -0
  40. package/dist/chunk-2GIZY3JJ.js +1062 -0
  41. package/dist/chunk-2GIZY3JJ.js.map +1 -0
  42. package/dist/chunk-2QTH6JHX.js +217 -0
  43. package/dist/chunk-2QTH6JHX.js.map +1 -0
  44. package/dist/{chunk-FJ5RSSMA.js → chunk-3ZW74FHC.js} +118 -164
  45. package/dist/chunk-3ZW74FHC.js.map +1 -0
  46. package/dist/chunk-67ZO7Z2W.js +184 -0
  47. package/dist/chunk-67ZO7Z2W.js.map +1 -0
  48. package/dist/{chunk-CFLVPV5M.js → chunk-7AAO4UDQ.js} +8 -2
  49. package/dist/chunk-7AAO4UDQ.js.map +1 -0
  50. package/dist/{chunk-B52K63AW.js → chunk-CG75X77Q.js} +2 -2
  51. package/dist/chunk-DJUFS2BW.js +27 -0
  52. package/dist/chunk-DJUFS2BW.js.map +1 -0
  53. package/dist/chunk-DPPOEFBD.js +517 -0
  54. package/dist/chunk-DPPOEFBD.js.map +1 -0
  55. package/dist/chunk-EPAL2HD5.js +1629 -0
  56. package/dist/chunk-EPAL2HD5.js.map +1 -0
  57. package/dist/chunk-GG23N2GW.js +15018 -0
  58. package/dist/chunk-GG23N2GW.js.map +1 -0
  59. package/dist/chunk-JCL3Q7RW.js +587 -0
  60. package/dist/chunk-JCL3Q7RW.js.map +1 -0
  61. package/dist/chunk-JKEBMGLH.js +682 -0
  62. package/dist/chunk-JKEBMGLH.js.map +1 -0
  63. package/dist/chunk-KSGNKS2D.js +296 -0
  64. package/dist/chunk-KSGNKS2D.js.map +1 -0
  65. package/dist/{chunk-WREC7D54.js → chunk-PCI6VMB5.js} +268 -48
  66. package/dist/chunk-PCI6VMB5.js.map +1 -0
  67. package/dist/chunk-PQ7VWBQQ.js +121 -0
  68. package/dist/chunk-PQ7VWBQQ.js.map +1 -0
  69. package/dist/chunk-RDXBMTP5.js +32 -0
  70. package/dist/chunk-RDXBMTP5.js.map +1 -0
  71. package/dist/chunk-T6QQV5LK.js +238 -0
  72. package/dist/chunk-T6QQV5LK.js.map +1 -0
  73. package/dist/chunk-Y6S4OVVQ.js +16 -0
  74. package/dist/chunk-Y6S4OVVQ.js.map +1 -0
  75. package/dist/{createTrueFoundryServer-B72bU-J7.d.ts → createTrueFoundryServer-B_FYhSdn.d.ts} +18 -4
  76. package/dist/index.d.ts +1287 -46
  77. package/dist/index.js +266 -63
  78. package/dist/index.js.map +1 -1
  79. package/dist/plugins/trueforge-agent-server-adapter/index.d.ts +590 -11
  80. package/dist/plugins/trueforge-agent-server-adapter/index.js +607 -204
  81. package/dist/plugins/trueforge-agent-server-adapter/index.js.map +1 -1
  82. package/dist/styles.css +1 -1
  83. package/package.json +10 -8
  84. package/dist/ConnectorSettings-FXQ5B7TN.js.map +0 -1
  85. package/dist/DrawerLayout-Q6UK4VMI.js +0 -159
  86. package/dist/DrawerLayout-Q6UK4VMI.js.map +0 -1
  87. package/dist/ModelSettings-4L4LOQJA.js.map +0 -1
  88. package/dist/OpenUiFenceBlock-6UIIAVBX.js +0 -9
  89. package/dist/SandboxSettings-Z4ZG6ZX4.js.map +0 -1
  90. package/dist/SettingsBuilder-WF7ZWKQW.js.map +0 -1
  91. package/dist/ShellRouteSync-T4WLY57Q.js +0 -172
  92. package/dist/ShellRouteSync-T4WLY57Q.js.map +0 -1
  93. package/dist/SidebarLayout-3WZ3CVON.js +0 -256
  94. package/dist/SidebarLayout-3WZ3CVON.js.map +0 -1
  95. package/dist/SkillSettings-3EPPC3IT.js.map +0 -1
  96. package/dist/WidgetLayout-3QZ357FL.js.map +0 -1
  97. package/dist/chunk-36KOSC2J.js +0 -567
  98. package/dist/chunk-36KOSC2J.js.map +0 -1
  99. package/dist/chunk-3S6KHO7H.js +0 -2121
  100. package/dist/chunk-3S6KHO7H.js.map +0 -1
  101. package/dist/chunk-7L63OK3O.js +0 -91
  102. package/dist/chunk-7L63OK3O.js.map +0 -1
  103. package/dist/chunk-CFLVPV5M.js.map +0 -1
  104. package/dist/chunk-FJ5RSSMA.js.map +0 -1
  105. package/dist/chunk-OSV3HI5M.js +0 -6891
  106. package/dist/chunk-OSV3HI5M.js.map +0 -1
  107. package/dist/chunk-WQULF5AT.js +0 -460
  108. package/dist/chunk-WQULF5AT.js.map +0 -1
  109. package/dist/chunk-WREC7D54.js.map +0 -1
  110. package/dist/chunk-YIWUG6MC.js +0 -101
  111. package/dist/chunk-YIWUG6MC.js.map +0 -1
  112. package/dist/chunk-ZXOADNF3.js +0 -75
  113. package/dist/chunk-ZXOADNF3.js.map +0 -1
  114. /package/dist/{OpenUiFenceBlock-6UIIAVBX.js.map → AgentMetricChart-UPGSJ2WP.js.map} +0 -0
  115. /package/dist/{chunk-B52K63AW.js.map → chunk-CG75X77Q.js.map} +0 -0
@@ -0,0 +1,1629 @@
1
+ import {
2
+ Button,
3
+ ConnectorConnectButton,
4
+ CreatedByCell,
5
+ DEFAULT_TABLE_PAGE_SIZE,
6
+ Dialog,
7
+ DialogContent,
8
+ DialogFooter,
9
+ DialogHeader,
10
+ DialogTitle,
11
+ DropdownMenu,
12
+ DropdownMenuItem,
13
+ EmptyScreen,
14
+ PopoverSelect,
15
+ SearchInput_default,
16
+ SideDrawer,
17
+ Skeleton,
18
+ Table,
19
+ TableBody,
20
+ TableCell,
21
+ TableHead,
22
+ TableHeader,
23
+ TableRow,
24
+ TableTokenPagination,
25
+ Tooltip,
26
+ auiInputClass,
27
+ auiSelectMenuClass,
28
+ auiSelectOptionClass,
29
+ auiSelectTriggerClass,
30
+ hasCreatedBySubject,
31
+ mountName,
32
+ themePortalRoot,
33
+ useResourcePermissions,
34
+ useSlot,
35
+ useToasterOptional
36
+ } from "./chunk-GG23N2GW.js";
37
+ import {
38
+ findLibraryAgent,
39
+ readScheduleShareSearch,
40
+ replaceScheduleShareSearch,
41
+ useSearchAgentsList
42
+ } from "./chunk-T6QQV5LK.js";
43
+ import {
44
+ DraftCatalogProvider,
45
+ Icon,
46
+ PageHeader,
47
+ auiButtonClass,
48
+ useDraftCatalog
49
+ } from "./chunk-3ZW74FHC.js";
50
+ import {
51
+ libraryAgentId,
52
+ useOptionalCatalogServer,
53
+ useOptionalShellMode,
54
+ useScheduleServer,
55
+ useServer
56
+ } from "./chunk-2GIZY3JJ.js";
57
+ import {
58
+ cn
59
+ } from "./chunk-JCL3Q7RW.js";
60
+
61
+ // src/atoms/schedules/SchedulesPage.tsx
62
+ import { useCallback, useEffect as useEffect4, useMemo as useMemo2, useRef as useRef3, useState as useState4 } from "react";
63
+
64
+ // src/atoms/AgentSearchPicker.tsx
65
+ import { useEffect, useId, useLayoutEffect, useRef, useState } from "react";
66
+ import { createPortal } from "react-dom";
67
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
68
+ function AgentSearchPicker({
69
+ value,
70
+ selectedLabel,
71
+ onValueChange,
72
+ onAgentPicked,
73
+ disabled = false,
74
+ onBuildAgent,
75
+ allOption,
76
+ "aria-label": ariaLabel = "Agent",
77
+ placeholder = "Search agent",
78
+ className
79
+ }) {
80
+ const [open, setOpen] = useState(false);
81
+ const [query, setQuery] = useState("");
82
+ const [pos, setPos] = useState(null);
83
+ const rootRef = useRef(null);
84
+ const triggerRef = useRef(null);
85
+ const inputRef = useRef(null);
86
+ const menuRef = useRef(null);
87
+ const listboxId = useId();
88
+ const { agents, isInitialLoading, isSearching, loadingMore, hasMore, error, listRef, sentinelRef } = useSearchAgentsList({
89
+ enabled: open && !disabled,
90
+ query
91
+ });
92
+ useEffect(() => {
93
+ if (!open) setQuery("");
94
+ }, [open]);
95
+ useLayoutEffect(() => {
96
+ if (!open) {
97
+ setPos(null);
98
+ return;
99
+ }
100
+ const update = () => {
101
+ const el = triggerRef.current;
102
+ if (!el) return;
103
+ const rect = el.getBoundingClientRect();
104
+ setPos({
105
+ top: rect.bottom + 4,
106
+ left: rect.left,
107
+ width: rect.width
108
+ });
109
+ };
110
+ update();
111
+ window.addEventListener("scroll", update, true);
112
+ window.addEventListener("resize", update);
113
+ return () => {
114
+ window.removeEventListener("scroll", update, true);
115
+ window.removeEventListener("resize", update);
116
+ };
117
+ }, [open]);
118
+ useEffect(() => {
119
+ if (!open) return;
120
+ const handlePointerDown = (event) => {
121
+ if (!(event.target instanceof Node)) return;
122
+ if (rootRef.current?.contains(event.target)) return;
123
+ if (menuRef.current?.contains(event.target)) return;
124
+ setOpen(false);
125
+ };
126
+ document.addEventListener("mousedown", handlePointerDown);
127
+ return () => document.removeEventListener("mousedown", handlePointerDown);
128
+ }, [open]);
129
+ const close = () => {
130
+ setOpen(false);
131
+ setQuery("");
132
+ inputRef.current?.blur();
133
+ };
134
+ const pick = (agent) => {
135
+ const id = libraryAgentId(agent);
136
+ onValueChange(id);
137
+ onAgentPicked?.(agent);
138
+ close();
139
+ };
140
+ const pickAll = () => {
141
+ if (allOption == null) return;
142
+ onValueChange(allOption.value);
143
+ close();
144
+ };
145
+ const inputValue = open ? query : selectedLabel;
146
+ const queryTrimmed = query.trim();
147
+ const showError = !isInitialLoading && error != null;
148
+ const showEmptyCatalog = !isInitialLoading && error == null && agents.length === 0 && queryTrimmed === "";
149
+ const showNoMatch = !isInitialLoading && error == null && agents.length === 0 && queryTrimmed !== "";
150
+ const menu = open && pos != null && !disabled ? createPortal(
151
+ /* @__PURE__ */ jsxs(
152
+ "div",
153
+ {
154
+ ref: menuRef,
155
+ className: auiSelectMenuClass("fixed z-[200] flex max-h-64 flex-col overflow-hidden"),
156
+ style: {
157
+ top: pos.top,
158
+ left: pos.left,
159
+ width: pos.width
160
+ },
161
+ onMouseDown: (event) => event.stopPropagation(),
162
+ children: [
163
+ /* @__PURE__ */ jsxs(
164
+ "div",
165
+ {
166
+ ref: listRef,
167
+ id: listboxId,
168
+ role: "listbox",
169
+ "aria-label": ariaLabel,
170
+ className: "min-h-0 flex-1 overflow-y-auto p-1",
171
+ children: [
172
+ allOption != null && queryTrimmed === "" ? /* @__PURE__ */ jsxs(
173
+ "button",
174
+ {
175
+ type: "button",
176
+ role: "option",
177
+ "aria-selected": value === allOption.value,
178
+ className: auiSelectOptionClass(),
179
+ onClick: pickAll,
180
+ children: [
181
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: allOption.label }),
182
+ /* @__PURE__ */ jsx(
183
+ Icon,
184
+ {
185
+ name: "check",
186
+ className: cn("ml-auto size-4 shrink-0", value === allOption.value ? "opacity-100" : "opacity-0")
187
+ }
188
+ )
189
+ ]
190
+ }
191
+ ) : null,
192
+ isInitialLoading ? /* @__PURE__ */ jsx("p", { className: "text-text-secondary px-3 py-5 text-center text-sm", role: "status", children: "Loading\u2026" }) : showError ? /* @__PURE__ */ jsx("p", { className: "text-failure-bg px-3 py-5 text-center text-sm", role: "alert", children: error }) : showEmptyCatalog ? /* @__PURE__ */ jsx("p", { className: "text-text-secondary px-3 py-5 text-center text-sm", children: "No Agents created yet" }) : showNoMatch ? /* @__PURE__ */ jsxs("p", { className: "text-text-secondary px-3 py-5 text-center text-sm", role: "status", children: [
193
+ 'No agents match "',
194
+ queryTrimmed,
195
+ '".'
196
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
197
+ isSearching ? /* @__PURE__ */ jsx("p", { className: "sr-only", role: "status", children: "Searching\u2026" }) : null,
198
+ agents.map((agent) => {
199
+ const id = libraryAgentId(agent);
200
+ const selected = value === id;
201
+ return /* @__PURE__ */ jsxs(
202
+ "button",
203
+ {
204
+ type: "button",
205
+ role: "option",
206
+ "aria-selected": selected,
207
+ className: auiSelectOptionClass(),
208
+ onClick: () => pick(agent),
209
+ children: [
210
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: agent.name }),
211
+ /* @__PURE__ */ jsx(
212
+ Icon,
213
+ {
214
+ name: "check",
215
+ className: cn("ml-auto size-4 shrink-0", selected ? "opacity-100" : "opacity-0")
216
+ }
217
+ )
218
+ ]
219
+ },
220
+ id
221
+ );
222
+ }),
223
+ hasMore ? /* @__PURE__ */ jsx("div", { ref: sentinelRef, className: "flex h-6 shrink-0 items-center justify-center", "aria-hidden": true, children: loadingMore ? /* @__PURE__ */ jsx("span", { className: "text-text-secondary text-[11px]", role: "status", children: "Loading more\u2026" }) : null }) : null
224
+ ] })
225
+ ]
226
+ }
227
+ ),
228
+ showEmptyCatalog && onBuildAgent != null ? /* @__PURE__ */ jsx("div", { className: "flex justify-end border-t border-border px-2 pt-2", children: /* @__PURE__ */ jsxs(Button.Ghost, { type: "button", onClick: onBuildAgent, children: [
229
+ /* @__PURE__ */ jsx(Icon, { name: "plus", className: "size-3.5" }),
230
+ "Build Agent"
231
+ ] }) }) : null
232
+ ]
233
+ }
234
+ ),
235
+ themePortalRoot(rootRef.current)
236
+ ) : null;
237
+ return /* @__PURE__ */ jsxs("div", { ref: rootRef, className: cn("relative", className), children: [
238
+ /* @__PURE__ */ jsxs(
239
+ "div",
240
+ {
241
+ ref: triggerRef,
242
+ className: auiSelectTriggerClass(disabled ? "cursor-not-allowed opacity-50" : "cursor-text"),
243
+ children: [
244
+ /* @__PURE__ */ jsx(
245
+ "input",
246
+ {
247
+ ref: inputRef,
248
+ type: "text",
249
+ role: "combobox",
250
+ "aria-label": ariaLabel,
251
+ "aria-expanded": open,
252
+ "aria-controls": open ? listboxId : void 0,
253
+ "aria-autocomplete": "list",
254
+ disabled,
255
+ placeholder,
256
+ value: inputValue,
257
+ className: "min-w-0 flex-1 bg-transparent outline-none placeholder:text-text-secondary/70 disabled:cursor-not-allowed",
258
+ onFocus: () => {
259
+ if (!disabled) setOpen(true);
260
+ },
261
+ onChange: (event) => {
262
+ if (disabled) return;
263
+ setQuery(event.target.value);
264
+ setOpen(true);
265
+ },
266
+ onKeyDown: (event) => {
267
+ if (event.key === "Escape") {
268
+ event.preventDefault();
269
+ setOpen(false);
270
+ inputRef.current?.blur();
271
+ return;
272
+ }
273
+ if (event.key === "ArrowDown" && !open && !disabled) {
274
+ event.preventDefault();
275
+ setOpen(true);
276
+ }
277
+ }
278
+ }
279
+ ),
280
+ /* @__PURE__ */ jsx(Icon, { name: "chevron-down", className: "size-4 shrink-0 text-text-secondary" })
281
+ ]
282
+ }
283
+ ),
284
+ menu
285
+ ] });
286
+ }
287
+
288
+ // src/atoms/schedules/cadence.ts
289
+ var TIMEZONE_OPTIONS = [
290
+ { value: "America/New_York", label: "America/New_York (ET)" },
291
+ { value: "America/Chicago", label: "America/Chicago (CT)" },
292
+ { value: "America/Denver", label: "America/Denver (MT)" },
293
+ { value: "America/Los_Angeles", label: "America/Los_Angeles (PT)" },
294
+ { value: "UTC", label: "UTC" },
295
+ { value: "Europe/London", label: "Europe/London (GMT/BST)" },
296
+ { value: "Europe/Berlin", label: "Europe/Berlin (CET)" },
297
+ { value: "Asia/Kolkata", label: "Asia/Kolkata (IST)" },
298
+ { value: "Asia/Tokyo", label: "Asia/Tokyo (JST)" }
299
+ ];
300
+ function resolveLocalTimezone() {
301
+ try {
302
+ return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
303
+ } catch {
304
+ return "UTC";
305
+ }
306
+ }
307
+ function getTimezoneOptions(selectedTimezone) {
308
+ const localTimezone = resolveLocalTimezone();
309
+ const knownTimezones = new Set(TIMEZONE_OPTIONS.map((option) => option.value));
310
+ const additionalTimezones = [localTimezone, selectedTimezone].flatMap((timezone) => {
311
+ if (timezone == null || timezone.length === 0 || knownTimezones.has(timezone)) return [];
312
+ knownTimezones.add(timezone);
313
+ return [{ value: timezone, label: timezone === localTimezone ? `${timezone} (Local)` : timezone }];
314
+ });
315
+ return [...additionalTimezones, ...TIMEZONE_OPTIONS];
316
+ }
317
+ var WEEKDAY_OPTIONS = [
318
+ { value: 1, label: "Mon" },
319
+ { value: 2, label: "Tue" },
320
+ { value: 3, label: "Wed" },
321
+ { value: 4, label: "Thu" },
322
+ { value: 5, label: "Fri" },
323
+ { value: 6, label: "Sat" },
324
+ { value: 0, label: "Sun" }
325
+ ];
326
+ var TZ_ABBR = {
327
+ "America/New_York": "ET",
328
+ "America/Chicago": "CT",
329
+ "America/Denver": "MT",
330
+ "America/Los_Angeles": "PT",
331
+ UTC: "UTC",
332
+ "Europe/London": "GMT",
333
+ "Europe/Berlin": "CET",
334
+ "Asia/Kolkata": "IST",
335
+ "Asia/Tokyo": "JST"
336
+ };
337
+ function defaultScheduleFormValues() {
338
+ return {
339
+ name: "",
340
+ task: "",
341
+ recurrence: "daily",
342
+ hour: 9,
343
+ minute: 0,
344
+ weekdays: [1],
345
+ customCron: "0 9 * * *",
346
+ timezone: resolveLocalTimezone()
347
+ };
348
+ }
349
+ function timezoneAbbrev(timezone) {
350
+ return TZ_ABBR[timezone] ?? timezone.split("/").pop() ?? timezone;
351
+ }
352
+ function pad2(n) {
353
+ return n.toString().padStart(2, "0");
354
+ }
355
+ function formatClock({ hour, minute }) {
356
+ const period = hour >= 12 ? "PM" : "AM";
357
+ const h12 = hour % 12 === 0 ? 12 : hour % 12;
358
+ return `${String(h12)}:${pad2(minute)} ${period}`;
359
+ }
360
+ function valuesToCron(values) {
361
+ const minute = values.minute;
362
+ const hour = values.hour;
363
+ switch (values.recurrence) {
364
+ case "hourly":
365
+ return `${String(minute)} * * * *`;
366
+ case "daily":
367
+ return `${String(minute)} ${String(hour)} * * *`;
368
+ case "weekly": {
369
+ const days = [...values.weekdays].sort((a, b) => a - b);
370
+ const dow = days.length > 0 ? days.join(",") : "*";
371
+ return `${String(minute)} ${String(hour)} * * ${dow}`;
372
+ }
373
+ case "custom":
374
+ return values.customCron.trim();
375
+ }
376
+ }
377
+ function formatCadenceSummary(input) {
378
+ const abbr = timezoneAbbrev(input.timezone);
379
+ const parts = input.cron.trim().split(/\s+/);
380
+ if (parts.length !== 5) return input.cron;
381
+ const [minuteRaw, hourRaw, dayOfMonthRaw, monthRaw, dowRaw] = parts;
382
+ if (dayOfMonthRaw !== "*" || monthRaw !== "*") return input.cron;
383
+ const minute = Number(minuteRaw);
384
+ const hour = Number(hourRaw);
385
+ if (hourRaw === "*" && dowRaw === "*" && minuteRaw !== void 0 && !Number.isNaN(minute)) {
386
+ return `Hourly at :${pad2(minute)} ${abbr}`;
387
+ }
388
+ if (dowRaw === "*" && hourRaw !== void 0 && minuteRaw !== void 0 && !Number.isNaN(hour) && !Number.isNaN(minute)) {
389
+ return `Daily ${formatClock({ hour, minute })} ${abbr}`;
390
+ }
391
+ if (dowRaw === "1-5" && hourRaw !== void 0 && minuteRaw !== void 0 && !Number.isNaN(hour) && !Number.isNaN(minute)) {
392
+ return `Weekdays ${formatClock({ hour, minute })} ${abbr}`;
393
+ }
394
+ if (dowRaw != null && /^\d(?:,\d)*$/.test(dowRaw) && hourRaw !== void 0 && minuteRaw !== void 0 && !Number.isNaN(hour) && !Number.isNaN(minute)) {
395
+ const labels = dowRaw.split(",").map((d) => WEEKDAY_OPTIONS.find((w) => w.value === Number(d))?.label ?? d);
396
+ if (labels.length === 1) {
397
+ const day = labels[0] === "Mon" ? "Monday" : labels[0];
398
+ return `Every ${day} ${formatClock({ hour, minute })} ${abbr}`;
399
+ }
400
+ return `${labels.join(", ")} ${formatClock({ hour, minute })} ${abbr}`;
401
+ }
402
+ return input.cron;
403
+ }
404
+ function cronToFormValues(input) {
405
+ const base = defaultScheduleFormValues();
406
+ const parts = input.cron.trim().split(/\s+/);
407
+ if (parts.length !== 5) {
408
+ return {
409
+ ...base,
410
+ name: input.name,
411
+ task: input.task,
412
+ timezone: input.timezone,
413
+ recurrence: "custom",
414
+ customCron: input.cron
415
+ };
416
+ }
417
+ const [minuteRaw, hourRaw, dom, month, dowRaw] = parts;
418
+ const minute = Number(minuteRaw);
419
+ const hour = Number(hourRaw);
420
+ if (dom !== "*" || month !== "*") {
421
+ return {
422
+ ...base,
423
+ name: input.name,
424
+ task: input.task,
425
+ timezone: input.timezone,
426
+ recurrence: "custom",
427
+ customCron: input.cron
428
+ };
429
+ }
430
+ if (hourRaw === "*" && dowRaw === "*" && !Number.isNaN(minute)) {
431
+ return {
432
+ ...base,
433
+ name: input.name,
434
+ task: input.task,
435
+ timezone: input.timezone,
436
+ recurrence: "hourly",
437
+ minute,
438
+ customCron: input.cron
439
+ };
440
+ }
441
+ if (dowRaw === "*" && !Number.isNaN(hour) && !Number.isNaN(minute)) {
442
+ return {
443
+ ...base,
444
+ name: input.name,
445
+ task: input.task,
446
+ timezone: input.timezone,
447
+ recurrence: "daily",
448
+ hour,
449
+ minute,
450
+ customCron: input.cron
451
+ };
452
+ }
453
+ if (dowRaw != null && /^\d(?:,\d)*$/.test(dowRaw) && !Number.isNaN(hour) && !Number.isNaN(minute)) {
454
+ return {
455
+ ...base,
456
+ name: input.name,
457
+ task: input.task,
458
+ timezone: input.timezone,
459
+ recurrence: "weekly",
460
+ hour,
461
+ minute,
462
+ weekdays: dowRaw.split(",").map(Number),
463
+ customCron: input.cron
464
+ };
465
+ }
466
+ return {
467
+ ...base,
468
+ name: input.name,
469
+ task: input.task,
470
+ timezone: input.timezone,
471
+ recurrence: "custom",
472
+ customCron: input.cron,
473
+ hour: Number.isNaN(hour) ? base.hour : hour,
474
+ minute: Number.isNaN(minute) ? base.minute : minute
475
+ };
476
+ }
477
+ function formatRelativeTime(iso, nowMs = Date.now()) {
478
+ if (iso == null) return "Never";
479
+ const then = Date.parse(iso);
480
+ if (Number.isNaN(then)) return "Never";
481
+ const deltaSec = Math.round((nowMs - then) / 1e3);
482
+ if (deltaSec < 60) return "just now";
483
+ const mins = Math.round(deltaSec / 60);
484
+ if (mins < 60) return `${String(mins)} min ago`;
485
+ const hours = Math.round(mins / 60);
486
+ if (hours < 48) return `${String(hours)} hour${hours === 1 ? "" : "s"} ago`;
487
+ const days = Math.round(hours / 24);
488
+ return `${String(days)} day${days === 1 ? "" : "s"} ago`;
489
+ }
490
+
491
+ // src/atoms/schedules/ScheduleFormDrawer.tsx
492
+ import { useEffect as useEffect3, useMemo, useRef as useRef2, useState as useState3 } from "react";
493
+
494
+ // src/atoms/schedules/ScheduleFormFields.tsx
495
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
496
+ var RECURRENCE_OPTIONS = [
497
+ { value: "hourly", label: "Hourly" },
498
+ { value: "daily", label: "Daily" },
499
+ { value: "weekly", label: "Weekly" }
500
+ ];
501
+ var HOUR_OPTIONS = Array.from({ length: 24 }, (_, hour) => ({
502
+ value: String(hour),
503
+ label: hour.toString().padStart(2, "0")
504
+ }));
505
+ var MINUTE_OPTIONS = [0, 15, 30, 45].map((minute) => ({
506
+ value: String(minute),
507
+ label: `:${minute.toString().padStart(2, "0")}`
508
+ }));
509
+ function ScheduleFormFields({
510
+ values,
511
+ onChange,
512
+ agentId,
513
+ agentLabel,
514
+ onAgentIdChange,
515
+ onAgentPicked,
516
+ agentPickerDisabled = false,
517
+ onBuildAgent
518
+ }) {
519
+ const cron = valuesToCron(values);
520
+ const cadence = formatCadenceSummary({ cron, timezone: values.timezone });
521
+ const timezoneOptions = getTimezoneOptions(values.timezone);
522
+ const set = (key, value) => {
523
+ onChange({ ...values, [key]: value });
524
+ };
525
+ const toggleWeekday = (day) => {
526
+ const has = values.weekdays.includes(day);
527
+ const weekdays = has ? values.weekdays.filter((d) => d !== day) : [...values.weekdays, day];
528
+ onChange({ ...values, weekdays: weekdays.length > 0 ? weekdays : [day] });
529
+ };
530
+ return /* @__PURE__ */ jsxs2("div", { className: "flex flex-col gap-4 px-5 py-4", children: [
531
+ /* @__PURE__ */ jsxs2("div", { className: "block", children: [
532
+ /* @__PURE__ */ jsx2("span", { className: "mb-1.5 block text-sm font-medium", children: "Agent" }),
533
+ /* @__PURE__ */ jsx2(
534
+ AgentSearchPicker,
535
+ {
536
+ "aria-label": "Agent",
537
+ placeholder: "Search agent",
538
+ value: agentId,
539
+ selectedLabel: agentLabel,
540
+ onValueChange: (value) => onAgentIdChange?.(value),
541
+ onAgentPicked,
542
+ disabled: agentPickerDisabled || onAgentIdChange == null,
543
+ onBuildAgent
544
+ }
545
+ )
546
+ ] }),
547
+ /* @__PURE__ */ jsxs2("label", { className: "block", children: [
548
+ /* @__PURE__ */ jsx2("span", { className: "mb-1.5 block text-sm font-medium", children: "Name" }),
549
+ /* @__PURE__ */ jsx2(
550
+ "input",
551
+ {
552
+ value: values.name,
553
+ onChange: (e) => set("name", e.target.value),
554
+ placeholder: "harness-daily-digest",
555
+ className: auiInputClass("h-8"),
556
+ required: true
557
+ }
558
+ )
559
+ ] }),
560
+ /* @__PURE__ */ jsxs2("label", { className: "block", children: [
561
+ /* @__PURE__ */ jsx2("span", { className: "mb-1.5 block text-sm font-medium", children: "Task" }),
562
+ /* @__PURE__ */ jsx2(
563
+ "textarea",
564
+ {
565
+ value: values.task,
566
+ onChange: (e) => set("task", e.target.value),
567
+ rows: 3,
568
+ placeholder: "Summarise yesterday's harness runs and post the digest\u2026",
569
+ className: auiInputClass("resize-y py-2"),
570
+ required: true
571
+ }
572
+ )
573
+ ] }),
574
+ /* @__PURE__ */ jsxs2("fieldset", { children: [
575
+ /* @__PURE__ */ jsx2("legend", { className: "mb-1.5 text-sm font-medium", children: "Frequency" }),
576
+ /* @__PURE__ */ jsx2("div", { className: "rounded-t-lg border border-border p-4", children: /* @__PURE__ */ jsxs2("div", { className: "flex flex-col gap-4", children: [
577
+ /* @__PURE__ */ jsx2("div", { className: "grid grid-cols-3 gap-1 rounded-lg border border-border bg-primary-bg p-1", children: RECURRENCE_OPTIONS.map((opt) => {
578
+ const selected = values.recurrence === opt.value;
579
+ return /* @__PURE__ */ jsx2(
580
+ "button",
581
+ {
582
+ type: "button",
583
+ className: cn(
584
+ "rounded-md border px-2 py-2 text-sm font-medium transition-colors",
585
+ selected ? "border-primary-button-bg/40 bg-primary-button-bg/10 font-semibold text-primary-button-bg" : "border-transparent text-text-secondary hover:bg-ghost-button-hover"
586
+ ),
587
+ "aria-pressed": selected,
588
+ onClick: () => set("recurrence", opt.value),
589
+ children: opt.label
590
+ },
591
+ opt.value
592
+ );
593
+ }) }),
594
+ values.recurrence === "custom" ? /* @__PURE__ */ jsxs2("div", { className: "rounded-md border border-border bg-secondary-bg/40 px-3 py-2", children: [
595
+ /* @__PURE__ */ jsx2("span", { className: "block text-xs text-text-secondary", children: "Existing custom schedule" }),
596
+ /* @__PURE__ */ jsx2("code", { className: "mt-1 block font-mono text-sm text-text-primary", children: values.customCron }),
597
+ /* @__PURE__ */ jsx2("span", { className: "mt-1 block text-xs text-text-secondary", children: "Select Hourly, Daily, or Weekly to replace this schedule." })
598
+ ] }) : null,
599
+ values.recurrence === "weekly" ? /* @__PURE__ */ jsxs2("fieldset", { children: [
600
+ /* @__PURE__ */ jsx2("legend", { className: "mb-2 text-sm font-medium", children: "Days" }),
601
+ /* @__PURE__ */ jsx2("div", { className: "flex gap-2", children: WEEKDAY_OPTIONS.map((day) => {
602
+ const selected = values.weekdays.includes(day.value);
603
+ return /* @__PURE__ */ jsx2(
604
+ "button",
605
+ {
606
+ type: "button",
607
+ className: cn(
608
+ "min-w-0 flex-1 rounded-md border px-3 py-2 text-[0.8125rem] font-medium",
609
+ selected ? "border-primary-button-bg/40 bg-primary-button-bg/10 font-semibold text-primary-button-bg" : "border-border text-text-secondary hover:bg-ghost-button-hover"
610
+ ),
611
+ "aria-pressed": selected,
612
+ onClick: () => toggleWeekday(day.value),
613
+ children: day.label
614
+ },
615
+ day.value
616
+ );
617
+ }) })
618
+ ] }) : null,
619
+ /* @__PURE__ */ jsxs2("div", { className: "flex gap-4", children: [
620
+ values.recurrence === "daily" || values.recurrence === "weekly" ? /* @__PURE__ */ jsxs2("div", { className: "min-w-0 flex-1", children: [
621
+ /* @__PURE__ */ jsx2("span", { className: "mb-1.5 block text-sm font-medium", children: "Hour" }),
622
+ /* @__PURE__ */ jsx2(
623
+ PopoverSelect,
624
+ {
625
+ "aria-label": "Hour",
626
+ value: String(values.hour),
627
+ options: HOUR_OPTIONS,
628
+ onValueChange: (value) => set("hour", Number(value))
629
+ }
630
+ )
631
+ ] }) : null,
632
+ values.recurrence !== "custom" ? /* @__PURE__ */ jsxs2("div", { className: "min-w-0 flex-1", children: [
633
+ /* @__PURE__ */ jsx2("span", { className: "mb-1.5 block text-sm font-medium", children: "Minute" }),
634
+ /* @__PURE__ */ jsx2(
635
+ PopoverSelect,
636
+ {
637
+ "aria-label": "Minute",
638
+ value: String(values.minute),
639
+ options: MINUTE_OPTIONS,
640
+ onValueChange: (value) => set("minute", Number(value))
641
+ }
642
+ )
643
+ ] }) : null,
644
+ /* @__PURE__ */ jsxs2("div", { className: "min-w-0 flex-1", children: [
645
+ /* @__PURE__ */ jsx2("span", { className: "mb-1.5 block text-sm font-medium", children: "Timezone" }),
646
+ /* @__PURE__ */ jsx2(
647
+ PopoverSelect,
648
+ {
649
+ "aria-label": "Timezone",
650
+ value: values.timezone,
651
+ options: timezoneOptions,
652
+ onValueChange: (value) => set("timezone", value),
653
+ menuClassName: "w-full min-w-0"
654
+ }
655
+ )
656
+ ] })
657
+ ] })
658
+ ] }) }),
659
+ /* @__PURE__ */ jsx2("div", { className: "rounded-b-lg border border-t-0 border-border bg-primary-button-bg/10 px-4 py-3", children: /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2", children: [
660
+ /* @__PURE__ */ jsx2(Icon, { name: "clock", className: "size-4 shrink-0 text-primary-button-bg" }),
661
+ /* @__PURE__ */ jsx2("span", { className: "shrink-0 text-xs font-medium text-text-secondary", children: "Schedule" }),
662
+ /* @__PURE__ */ jsx2("p", { className: "min-w-0 flex-1 truncate text-sm text-text-primary", children: cadence || "\u2014" }),
663
+ /* @__PURE__ */ jsx2("code", { className: "shrink-0 whitespace-nowrap font-mono text-[0.8125rem] font-bold text-text-secondary", children: cron || "\u2014" })
664
+ ] }) })
665
+ ] })
666
+ ] });
667
+ }
668
+
669
+ // src/atoms/schedules/TestScheduleScreen.tsx
670
+ import { useEffect as useEffect2, useState as useState2 } from "react";
671
+
672
+ // src/atoms/schedules/scheduleRuns.ts
673
+ function isManualRun(name) {
674
+ return name.startsWith("manual-");
675
+ }
676
+ function runChipKind(status) {
677
+ if (status === "failed") return "failed";
678
+ return "success";
679
+ }
680
+ function runStatusLabel(status) {
681
+ if (status === "failed") return "Failed";
682
+ if (status === "triggered") return "Triggered";
683
+ return "Scheduled";
684
+ }
685
+ function runTypeLabel(name) {
686
+ return isManualRun(name) ? "Manual test" : "Cron";
687
+ }
688
+ function lastHistoricalRuns(runs, limit = 5) {
689
+ const historical = runs.filter((run) => run.status !== "scheduled");
690
+ return historical.slice(0, limit).reverse();
691
+ }
692
+ function formatScheduleRunInstant(iso) {
693
+ if (iso == null) return "\u2014";
694
+ const date = new Date(iso);
695
+ if (Number.isNaN(date.getTime())) return "\u2014";
696
+ return new Intl.DateTimeFormat(void 0, {
697
+ month: "short",
698
+ day: "numeric",
699
+ year: "numeric",
700
+ hour: "numeric",
701
+ minute: "2-digit",
702
+ hour12: true
703
+ }).format(date);
704
+ }
705
+
706
+ // src/atoms/schedules/ScheduleRunTooltip.tsx
707
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
708
+ function ScheduleRunTooltip({ run }) {
709
+ return /* @__PURE__ */ jsxs3("div", { className: "flex flex-col gap-1 text-left text-xs", children: [
710
+ /* @__PURE__ */ jsx3("p", { className: "text-text-primary font-medium", children: runStatusLabel(run.status) }),
711
+ /* @__PURE__ */ jsx3("p", { className: "text-text-secondary", children: runTypeLabel(run.name) }),
712
+ /* @__PURE__ */ jsxs3("p", { className: "text-text-secondary", children: [
713
+ "Scheduled for: ",
714
+ formatScheduleRunInstant(run.scheduledFor)
715
+ ] }),
716
+ run.triggeredAt != null ? /* @__PURE__ */ jsxs3("p", { className: "text-text-secondary", children: [
717
+ "Triggered at: ",
718
+ formatScheduleRunInstant(run.triggeredAt)
719
+ ] }) : null,
720
+ /* @__PURE__ */ jsxs3("p", { className: "text-text-secondary", children: [
721
+ "Triggered by: ",
722
+ run.triggeredBy
723
+ ] })
724
+ ] });
725
+ }
726
+
727
+ // src/atoms/schedules/ScheduleRunChip.tsx
728
+ import { jsx as jsx4 } from "react/jsx-runtime";
729
+ var CHIP_STYLES = {
730
+ success: "border-emerald-600/30 bg-emerald-500/10 text-emerald-700 dark:border-emerald-400/35 dark:bg-emerald-500/15 dark:text-emerald-300",
731
+ failed: "border-red-600/30 bg-red-500/10 text-red-700 dark:border-red-400/35 dark:bg-red-500/15 dark:text-red-300"
732
+ };
733
+ function ScheduleRunChip({ run }) {
734
+ const kind = runChipKind(run.status);
735
+ const when = formatScheduleRunInstant(run.triggeredAt ?? run.scheduledFor);
736
+ return /* @__PURE__ */ jsx4(Tooltip, { content: /* @__PURE__ */ jsx4(ScheduleRunTooltip, { run }), side: "top", children: /* @__PURE__ */ jsx4(
737
+ "span",
738
+ {
739
+ className: cn("inline-flex size-6 shrink-0 items-center justify-center rounded-md border", CHIP_STYLES[kind]),
740
+ "aria-label": `${runStatusLabel(run.status)} run at ${when}`,
741
+ children: /* @__PURE__ */ jsx4(Icon, { name: kind === "failed" ? "triangle-exclamation" : "check", className: "size-3.5" })
742
+ }
743
+ ) });
744
+ }
745
+
746
+ // src/atoms/schedules/ScheduleStatusBadge.tsx
747
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
748
+ function ScheduleStatusBadge({ status }) {
749
+ const active = status === "active";
750
+ return /* @__PURE__ */ jsxs4(
751
+ "span",
752
+ {
753
+ className: cn(
754
+ "inline-flex items-center gap-1.5 rounded-full border px-2.5 py-0.5 text-xs font-medium",
755
+ active ? "border-emerald-600/30 bg-emerald-500/10 text-emerald-700 dark:border-emerald-400/35 dark:bg-emerald-500/15 dark:text-emerald-300" : "border-amber-600/30 bg-amber-500/10 text-amber-800 dark:border-amber-400/35 dark:bg-amber-500/15 dark:text-amber-300"
756
+ ),
757
+ children: [
758
+ /* @__PURE__ */ jsx5(
759
+ "span",
760
+ {
761
+ className: cn(
762
+ "size-1.5 shrink-0 rounded-full",
763
+ active ? "bg-emerald-600 dark:bg-emerald-400" : "bg-amber-600 dark:bg-amber-400"
764
+ ),
765
+ "aria-hidden": true
766
+ }
767
+ ),
768
+ active ? "Active" : "Paused"
769
+ ]
770
+ }
771
+ );
772
+ }
773
+
774
+ // src/atoms/schedules/TestScheduleScreen.tsx
775
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
776
+ function mountConnector(connectors, name) {
777
+ return connectors.find((connector) => connector.name === name || connector.id === name) ?? null;
778
+ }
779
+ function ScheduleMcpConnect({
780
+ connector,
781
+ onConnected
782
+ }) {
783
+ const catalog = useOptionalCatalogServer();
784
+ if (catalog == null) return null;
785
+ return /* @__PURE__ */ jsx6(ConnectorConnectButton, { connector, onConnected });
786
+ }
787
+ function DetailRow({ label, children }) {
788
+ return /* @__PURE__ */ jsxs5("div", { className: "flex items-start justify-between gap-3 border-b border-border px-3 py-2.5 last:border-b-0", children: [
789
+ /* @__PURE__ */ jsx6("span", { className: "text-text-secondary shrink-0 text-[10px] font-semibold tracking-wide uppercase", children: label }),
790
+ /* @__PURE__ */ jsx6("div", { className: "min-w-0 text-right text-sm text-text-primary", children })
791
+ ] });
792
+ }
793
+ function TestScheduleScreen({ schedule, agentName, mcpMounts, onEditConfiguration }) {
794
+ const scheduleServer = useScheduleServer();
795
+ const toaster = useToasterOptional();
796
+ const { connectors, ensureLoaded, refreshConnectors, loading, error } = useDraftCatalog();
797
+ const [running, setRunning] = useState2(false);
798
+ const [lastTestRun, setLastTestRun] = useState2(null);
799
+ const cadence = formatCadenceSummary({ cron: schedule.cron, timezone: schedule.timezone });
800
+ const handleRunTest = async () => {
801
+ setRunning(true);
802
+ try {
803
+ const run = await scheduleServer.createScheduleRun({ scheduleId: schedule.id });
804
+ setLastTestRun(run);
805
+ toaster?.showSuccess({ title: "Test run started" });
806
+ } catch (caught) {
807
+ toaster?.showError(caught);
808
+ } finally {
809
+ setRunning(false);
810
+ }
811
+ };
812
+ useEffect2(() => {
813
+ ensureLoaded();
814
+ }, [ensureLoaded]);
815
+ useEffect2(() => {
816
+ if (error != null) toaster?.showError(error);
817
+ }, [error, toaster]);
818
+ return /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-4 px-5 py-4", children: [
819
+ /* @__PURE__ */ jsxs5("section", { className: "overflow-hidden rounded-lg border border-border bg-card-bg", children: [
820
+ /* @__PURE__ */ jsx6(DetailRow, { label: "Agent", children: agentName }),
821
+ /* @__PURE__ */ jsx6(DetailRow, { label: "Name", children: schedule.name }),
822
+ /* @__PURE__ */ jsx6(DetailRow, { label: "Status", children: /* @__PURE__ */ jsx6(ScheduleStatusBadge, { status: schedule.status }) }),
823
+ /* @__PURE__ */ jsx6(DetailRow, { label: "Task", children: /* @__PURE__ */ jsx6("span", { className: "whitespace-pre-wrap break-words", children: schedule.task }) }),
824
+ /* @__PURE__ */ jsx6(DetailRow, { label: "Frequency", children: /* @__PURE__ */ jsxs5("div", { className: "flex flex-wrap items-center justify-end gap-1.5", children: [
825
+ /* @__PURE__ */ jsx6("span", { children: cadence }),
826
+ /* @__PURE__ */ jsx6("code", { className: "text-text-secondary font-mono text-xs", children: schedule.cron })
827
+ ] }) }),
828
+ /* @__PURE__ */ jsx6("div", { className: "flex justify-end px-3 py-2.5", children: /* @__PURE__ */ jsxs5(Button.Secondary, { type: "button", size: "small", onClick: onEditConfiguration, children: [
829
+ /* @__PURE__ */ jsx6(Icon, { name: "pencil", className: "size-3.5" }),
830
+ "Edit Configuration"
831
+ ] }) })
832
+ ] }),
833
+ /* @__PURE__ */ jsxs5("section", { className: "overflow-hidden rounded-lg border border-border bg-card-bg", children: [
834
+ /* @__PURE__ */ jsx6("div", { className: "border-b border-border px-3 py-2", children: /* @__PURE__ */ jsx6("h3", { className: "text-text-secondary text-[10px] font-semibold tracking-wide uppercase", children: "Tool Authorization" }) }),
835
+ mcpMounts.length === 0 ? /* @__PURE__ */ jsx6("p", { className: "text-text-secondary px-3 py-3 text-sm", children: "No MCP connectors configured for this agent." }) : /* @__PURE__ */ jsx6("ul", { className: "divide-y divide-border", children: mcpMounts.map((mount) => {
836
+ const connector = mountConnector(connectors, mount.name);
837
+ const authenticated = connector?.authenticated === true;
838
+ const unavailable = connector == null && !loading;
839
+ return /* @__PURE__ */ jsxs5("li", { className: "flex items-center gap-2 px-3 py-2.5", children: [
840
+ /* @__PURE__ */ jsx6(
841
+ "span",
842
+ {
843
+ className: cn(
844
+ "inline-flex size-4 shrink-0 items-center justify-center rounded-full border",
845
+ authenticated ? "border-emerald-600/40 bg-emerald-500/15 text-emerald-700 dark:text-emerald-300" : "border-border text-transparent"
846
+ ),
847
+ "aria-hidden": true,
848
+ children: authenticated ? /* @__PURE__ */ jsx6(Icon, { name: "check", className: "size-3" }) : null
849
+ }
850
+ ),
851
+ /* @__PURE__ */ jsx6("span", { className: "text-text-primary min-w-0 flex-1 truncate text-sm", children: mount.name }),
852
+ authenticated ? /* @__PURE__ */ jsx6("span", { className: "text-success-bg text-xs font-medium", children: "Connected" }) : unavailable ? /* @__PURE__ */ jsx6("span", { className: "text-text-secondary text-xs", children: "Unavailable" }) : connector != null && connector.authenticated === false ? /* @__PURE__ */ jsx6(ScheduleMcpConnect, { connector, onConnected: refreshConnectors }) : loading ? /* @__PURE__ */ jsx6("span", { className: "text-text-secondary text-xs", children: "Loading\u2026" }) : null
853
+ ] }, mount.name);
854
+ }) })
855
+ ] }),
856
+ /* @__PURE__ */ jsxs5("section", { className: "overflow-hidden rounded-lg border border-border bg-card-bg", children: [
857
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between gap-2 border-b border-border px-3 py-2", children: [
858
+ /* @__PURE__ */ jsx6("h3", { className: "text-text-secondary text-[10px] font-semibold tracking-wide uppercase", children: "Test Run" }),
859
+ /* @__PURE__ */ jsxs5(Button.Primary, { type: "button", disabled: running, size: "small", onClick: () => void handleRunTest(), children: [
860
+ /* @__PURE__ */ jsx6(Icon, { name: running ? "loader" : "play", className: cn("size-3.5", running && "animate-spin") }),
861
+ "Run Test"
862
+ ] })
863
+ ] }),
864
+ lastTestRun == null ? /* @__PURE__ */ jsx6("p", { className: "text-text-secondary px-3 py-3 text-sm", children: "Run a one-off test using the schedule task. This does not change the cron pending run." }) : /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-3 px-3 py-3", children: [
865
+ /* @__PURE__ */ jsx6(ScheduleRunChip, { run: lastTestRun }),
866
+ /* @__PURE__ */ jsxs5("div", { className: "min-w-0 text-sm", children: [
867
+ /* @__PURE__ */ jsx6("p", { className: "text-text-primary font-medium", children: runTypeLabel(lastTestRun.name) }),
868
+ /* @__PURE__ */ jsx6("p", { className: "text-text-secondary", children: formatRelativeTime(lastTestRun.triggeredAt ?? lastTestRun.scheduledFor) })
869
+ ] })
870
+ ] })
871
+ ] })
872
+ ] });
873
+ }
874
+
875
+ // src/atoms/schedules/ScheduleFormDrawer.tsx
876
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
877
+ function ScheduleFormDrawerBody({
878
+ open,
879
+ onOpenChange,
880
+ mode,
881
+ schedule,
882
+ initialAgentId = "",
883
+ onSaved
884
+ }) {
885
+ const scheduleServer = useScheduleServer();
886
+ const server = useServer();
887
+ const shell = useOptionalShellMode();
888
+ const toaster = useToasterOptional();
889
+ const [form, setForm] = useState3(defaultScheduleFormValues);
890
+ const [agentId, setAgentId] = useState3(initialAgentId);
891
+ const [selectedAgent, setSelectedAgent] = useState3(null);
892
+ const [view, setView] = useState3({ kind: "form" });
893
+ const [saving, setSaving] = useState3(false);
894
+ const [activating, setActivating] = useState3(false);
895
+ const [error, setError] = useState3(null);
896
+ const agentIdRef = useRef2(agentId);
897
+ agentIdRef.current = agentId;
898
+ const savedFromCreate = view.kind === "form" ? view.saved : view.schedule;
899
+ const isExternalEdit = mode === "edit" && view.kind === "form" && view.saved == null && schedule != null;
900
+ const isCreatedEdit = view.kind === "form" && view.saved != null;
901
+ const isInitialCreate = mode === "create" && view.kind === "form" && view.saved == null;
902
+ const { allows: allowsAgent } = useResourcePermissions({
903
+ resourceType: "agent",
904
+ resourceIds: agentId.length === 0 ? [] : [agentId]
905
+ });
906
+ useEffect3(() => {
907
+ if (!open) {
908
+ setForm(defaultScheduleFormValues());
909
+ setAgentId(initialAgentId);
910
+ setSelectedAgent(null);
911
+ setError(null);
912
+ setView({ kind: "form" });
913
+ setActivating(false);
914
+ return;
915
+ }
916
+ if (mode === "edit" && schedule != null) {
917
+ setForm(
918
+ cronToFormValues({
919
+ name: schedule.name,
920
+ task: schedule.task,
921
+ cron: schedule.cron,
922
+ timezone: schedule.timezone
923
+ })
924
+ );
925
+ setAgentId(schedule.agentId);
926
+ setSelectedAgent(null);
927
+ setView({ kind: "form" });
928
+ return;
929
+ }
930
+ setForm(defaultScheduleFormValues());
931
+ setAgentId(initialAgentId);
932
+ setSelectedAgent(null);
933
+ setView({ kind: "form" });
934
+ }, [open, mode, schedule, initialAgentId]);
935
+ useEffect3(() => {
936
+ if (!open || mode !== "create" || initialAgentId.length === 0) return;
937
+ let cancelled = false;
938
+ const requestedId = initialAgentId;
939
+ void findLibraryAgent({ server, agentKey: requestedId }).then((agent) => {
940
+ if (cancelled || agent == null) return;
941
+ if (agentIdRef.current !== requestedId) return;
942
+ setSelectedAgent(agent);
943
+ setAgentId(libraryAgentId(agent));
944
+ }).catch(() => void 0);
945
+ return () => {
946
+ cancelled = true;
947
+ };
948
+ }, [open, mode, initialAgentId, server]);
949
+ const agentLabel = selectedAgent?.name ?? savedFromCreate?.agentName ?? schedule?.agentName ?? (agentId.length > 0 ? agentId : "");
950
+ const mcpMounts = useMemo(() => {
951
+ const mounts = selectedAgent?.agentSpec?.mcpServers ?? [];
952
+ return mounts.map(mountName).filter((name) => name != null).map((name) => ({ name }));
953
+ }, [selectedAgent]);
954
+ const canSubmit = useMemo(() => {
955
+ const cron = valuesToCron(form);
956
+ return form.name.trim().length > 0 && form.task.trim().length > 0 && cron.length > 0 && agentId.length > 0 && (!isInitialCreate || allowsAgent(agentId, "USE"));
957
+ }, [agentId, allowsAgent, form, isInitialCreate]);
958
+ const enterTestView = (saved) => {
959
+ setView({ kind: "test", schedule: saved });
960
+ setForm(
961
+ cronToFormValues({
962
+ name: saved.name,
963
+ task: saved.task,
964
+ cron: saved.cron,
965
+ timezone: saved.timezone
966
+ })
967
+ );
968
+ setAgentId(saved.agentId);
969
+ };
970
+ const handleSave = async (event) => {
971
+ event.preventDefault();
972
+ if (!canSubmit) return;
973
+ const cron = valuesToCron(form);
974
+ setSaving(true);
975
+ setError(null);
976
+ try {
977
+ if (isExternalEdit && schedule != null) {
978
+ await scheduleServer.updateSchedule({
979
+ id: schedule.id,
980
+ name: form.name.trim(),
981
+ task: form.task.trim(),
982
+ cron,
983
+ timezone: form.timezone,
984
+ status: schedule.status
985
+ });
986
+ onSaved?.();
987
+ onOpenChange(false);
988
+ return;
989
+ }
990
+ if (isCreatedEdit && view.kind === "form" && view.saved != null) {
991
+ const saved2 = await scheduleServer.updateSchedule({
992
+ id: view.saved.id,
993
+ name: form.name.trim(),
994
+ task: form.task.trim(),
995
+ cron,
996
+ timezone: form.timezone,
997
+ status: "paused"
998
+ });
999
+ onSaved?.();
1000
+ enterTestView(saved2);
1001
+ toaster?.showSuccess({
1002
+ title: "Schedule saved as paused",
1003
+ description: formatCadenceSummary({ cron: saved2.cron, timezone: saved2.timezone })
1004
+ });
1005
+ return;
1006
+ }
1007
+ const saved = await scheduleServer.createSchedule({
1008
+ agentId,
1009
+ name: form.name.trim(),
1010
+ task: form.task.trim(),
1011
+ cron,
1012
+ timezone: form.timezone,
1013
+ status: "paused"
1014
+ });
1015
+ onSaved?.();
1016
+ enterTestView(saved);
1017
+ toaster?.showSuccess({
1018
+ title: "Schedule saved as paused",
1019
+ description: formatCadenceSummary({ cron: saved.cron, timezone: saved.timezone })
1020
+ });
1021
+ } catch (caught) {
1022
+ const message = caught instanceof Error ? caught.message : "Failed to save schedule";
1023
+ setError(message);
1024
+ } finally {
1025
+ setSaving(false);
1026
+ }
1027
+ };
1028
+ const handleActivate = async () => {
1029
+ if (view.kind !== "test") return;
1030
+ setActivating(true);
1031
+ setError(null);
1032
+ try {
1033
+ await scheduleServer.updateSchedule({
1034
+ id: view.schedule.id,
1035
+ name: view.schedule.name,
1036
+ task: view.schedule.task,
1037
+ cron: view.schedule.cron,
1038
+ timezone: view.schedule.timezone,
1039
+ status: "active"
1040
+ });
1041
+ onSaved?.();
1042
+ toaster?.showSuccess({ title: "Schedule activated" });
1043
+ onOpenChange(false);
1044
+ } catch (caught) {
1045
+ const message = caught instanceof Error ? caught.message : "Failed to activate schedule";
1046
+ setError(message);
1047
+ toaster?.showError(caught);
1048
+ } finally {
1049
+ setActivating(false);
1050
+ }
1051
+ };
1052
+ const title = view.kind === "test" ? "Test Schedule" : isExternalEdit || isCreatedEdit ? "Edit Schedule" : "New Schedule";
1053
+ const description = view.kind === "test" ? `Recurring unattended runs of ${agentLabel}.` : isExternalEdit || isCreatedEdit ? "Update cadence and task for this schedule." : "Create a recurring unattended run for an agent.";
1054
+ let footer;
1055
+ if (view.kind === "test") {
1056
+ footer = /* @__PURE__ */ jsxs6("div", { className: "flex flex-col gap-2", children: [
1057
+ error != null ? /* @__PURE__ */ jsx7("p", { className: "text-failure-bg text-sm", children: error }) : null,
1058
+ /* @__PURE__ */ jsx7(Button.Secondary, { type: "button", className: "w-full", disabled: activating, onClick: () => void handleActivate(), children: "Activate Schedule" })
1059
+ ] });
1060
+ } else {
1061
+ footer = /* @__PURE__ */ jsxs6("div", { className: "flex flex-col gap-2", children: [
1062
+ error != null ? /* @__PURE__ */ jsx7("p", { className: "text-failure-bg text-sm", children: error }) : null,
1063
+ /* @__PURE__ */ jsxs6("div", { className: "flex justify-end gap-2", children: [
1064
+ /* @__PURE__ */ jsx7(Button.Secondary, { type: "button", onClick: () => onOpenChange(false), disabled: saving, children: "Cancel" }),
1065
+ /* @__PURE__ */ jsx7(Button.Primary, { type: "submit", form: "schedule-form", disabled: !canSubmit || saving, children: "Save" })
1066
+ ] })
1067
+ ] });
1068
+ }
1069
+ return /* @__PURE__ */ jsx7(
1070
+ SideDrawer,
1071
+ {
1072
+ open,
1073
+ onOpenChange,
1074
+ title,
1075
+ description,
1076
+ anchor: "right",
1077
+ size: "xl",
1078
+ headerIcon: /* @__PURE__ */ jsx7("span", { className: "text-primary-button-bg inline-flex size-8 items-center justify-center", children: /* @__PURE__ */ jsx7(
1079
+ Icon,
1080
+ {
1081
+ name: view.kind === "test" || isExternalEdit || isCreatedEdit ? "calendar" : "calendar-plus",
1082
+ className: "size-5"
1083
+ }
1084
+ ) }),
1085
+ footer,
1086
+ children: view.kind === "test" ? /* @__PURE__ */ jsx7(
1087
+ TestScheduleScreen,
1088
+ {
1089
+ schedule: view.schedule,
1090
+ agentName: agentLabel,
1091
+ mcpMounts,
1092
+ onEditConfiguration: () => {
1093
+ setError(null);
1094
+ setView({ kind: "form", saved: view.schedule });
1095
+ }
1096
+ }
1097
+ ) : /* @__PURE__ */ jsx7("form", { id: "schedule-form", onSubmit: (event) => void handleSave(event), children: /* @__PURE__ */ jsx7(
1098
+ ScheduleFormFields,
1099
+ {
1100
+ values: form,
1101
+ onChange: setForm,
1102
+ agentId,
1103
+ agentLabel,
1104
+ onAgentIdChange: isExternalEdit || isCreatedEdit ? void 0 : (nextId) => {
1105
+ setAgentId(nextId);
1106
+ if (nextId.length === 0) setSelectedAgent(null);
1107
+ },
1108
+ onAgentPicked: setSelectedAgent,
1109
+ agentPickerDisabled: isExternalEdit || isCreatedEdit,
1110
+ onBuildAgent: mode === "create" && shell?.isComposerEnabled === true ? () => {
1111
+ onOpenChange(false);
1112
+ shell.openAgentBuilder();
1113
+ } : void 0
1114
+ }
1115
+ ) })
1116
+ }
1117
+ );
1118
+ }
1119
+ function ScheduleFormDrawer(props) {
1120
+ return /* @__PURE__ */ jsx7(DraftCatalogProvider, { children: /* @__PURE__ */ jsx7(ScheduleFormDrawerBody, { ...props }) });
1121
+ }
1122
+
1123
+ // src/atoms/schedules/ScheduleLastRunsCell.tsx
1124
+ import { jsx as jsx8 } from "react/jsx-runtime";
1125
+ function ScheduleLastRunsCell({ runs }) {
1126
+ const history = lastHistoricalRuns(runs);
1127
+ if (history.length === 0) {
1128
+ return /* @__PURE__ */ jsx8("span", { className: "text-text-secondary text-sm", children: "\u2014" });
1129
+ }
1130
+ return /* @__PURE__ */ jsx8("div", { className: "flex items-center gap-1", children: history.map((run) => /* @__PURE__ */ jsx8(ScheduleRunChip, { run }, run.id)) });
1131
+ }
1132
+
1133
+ // src/atoms/schedules/SchedulesPage.tsx
1134
+ import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
1135
+ var STATUS_FILTER_OPTIONS = [
1136
+ { value: "all", label: "All statuses" },
1137
+ { value: "active", label: "Active" },
1138
+ { value: "paused", label: "Paused" }
1139
+ ];
1140
+ var SCHEDULES_PAGE_SIZE_OPTIONS = [10, 25];
1141
+ function clampPageSize(size) {
1142
+ return Math.min(Math.max(size, 1), 25);
1143
+ }
1144
+ function filtersFromSearch(search) {
1145
+ const share = readScheduleShareSearch(search);
1146
+ return {
1147
+ nameQuery: share.q ?? "",
1148
+ statusFilter: share.status ?? "all",
1149
+ agentFilter: share.agent ?? "all"
1150
+ };
1151
+ }
1152
+ function initialDrawerState(agentId) {
1153
+ const share = readScheduleShareSearch(window.location.search);
1154
+ if (!share.isNew) return { kind: "closed" };
1155
+ const initialAgentId = agentId ?? share.agent;
1156
+ return {
1157
+ kind: "create",
1158
+ ...initialAgentId == null ? {} : { agentId: initialAgentId }
1159
+ };
1160
+ }
1161
+ function ScheduleRowActions({
1162
+ schedule,
1163
+ running,
1164
+ canManage,
1165
+ canDelete,
1166
+ onRunNow,
1167
+ onEdit,
1168
+ onTogglePause,
1169
+ onDelete
1170
+ }) {
1171
+ const PermissionGuard = useSlot("PermissionGuard");
1172
+ return /* @__PURE__ */ jsxs7("div", { className: "inline-flex items-center justify-end gap-1.5", children: [
1173
+ /* @__PURE__ */ jsx9(PermissionGuard, { allowed: canManage, children: /* @__PURE__ */ jsxs7(
1174
+ Button.Secondary,
1175
+ {
1176
+ type: "button",
1177
+ disabled: running,
1178
+ "aria-label": `Run now ${schedule.name}`,
1179
+ size: "large",
1180
+ onClick: onRunNow,
1181
+ children: [
1182
+ /* @__PURE__ */ jsx9(Icon, { name: running ? "loader" : "play", className: cn("size-3.5", running && "animate-spin") }),
1183
+ "Run now"
1184
+ ]
1185
+ }
1186
+ ) }),
1187
+ /* @__PURE__ */ jsxs7(
1188
+ DropdownMenu,
1189
+ {
1190
+ align: "end",
1191
+ trigger: /* @__PURE__ */ jsx9(
1192
+ "button",
1193
+ {
1194
+ type: "button",
1195
+ className: auiButtonClass({ variant: "ghost", size: "icon" }),
1196
+ "aria-label": `Actions for ${schedule.name}`,
1197
+ children: /* @__PURE__ */ jsx9(Icon, { name: "ellipsis" })
1198
+ }
1199
+ ),
1200
+ children: [
1201
+ /* @__PURE__ */ jsx9(PermissionGuard, { allowed: canManage, children: /* @__PURE__ */ jsxs7(DropdownMenuItem, { onClick: onEdit, children: [
1202
+ /* @__PURE__ */ jsx9(Icon, { name: "pencil", className: "size-3.5" }),
1203
+ "Edit"
1204
+ ] }) }),
1205
+ /* @__PURE__ */ jsx9(PermissionGuard, { allowed: canManage, children: /* @__PURE__ */ jsxs7(DropdownMenuItem, { onClick: onTogglePause, children: [
1206
+ /* @__PURE__ */ jsx9(Icon, { name: schedule.status === "active" ? "pause" : "play", className: "size-3.5" }),
1207
+ schedule.status === "active" ? "Pause" : "Resume"
1208
+ ] }) }),
1209
+ /* @__PURE__ */ jsx9(PermissionGuard, { allowed: canDelete, children: /* @__PURE__ */ jsxs7(DropdownMenuItem, { className: "text-failure-bg focus:text-failure-bg", onClick: onDelete, children: [
1210
+ /* @__PURE__ */ jsx9(Icon, { name: "trash", className: "size-3.5" }),
1211
+ "Delete"
1212
+ ] }) })
1213
+ ]
1214
+ }
1215
+ )
1216
+ ] });
1217
+ }
1218
+ function SchedulesPage({ agentId }) {
1219
+ const scheduleServer = useScheduleServer();
1220
+ const server = useServer();
1221
+ const toaster = useToasterOptional();
1222
+ const [schedules, setSchedules] = useState4([]);
1223
+ const { allows } = useResourcePermissions({
1224
+ resourceType: "schedule",
1225
+ resourceIds: schedules.map((schedule) => schedule.id)
1226
+ });
1227
+ const [runsByScheduleId, setRunsByScheduleId] = useState4({});
1228
+ const [runsLoading, setRunsLoading] = useState4(false);
1229
+ const [runningScheduleIds, setRunningScheduleIds] = useState4(() => /* @__PURE__ */ new Set());
1230
+ const [agentLabelById, setAgentLabelById] = useState4({});
1231
+ const [loading, setLoading] = useState4(true);
1232
+ const [error, setError] = useState4(null);
1233
+ const [nameQuery, setNameQuery] = useState4(() => filtersFromSearch(window.location.search).nameQuery);
1234
+ const [statusFilter, setStatusFilter] = useState4(
1235
+ () => filtersFromSearch(window.location.search).statusFilter
1236
+ );
1237
+ const [agentFilter, setAgentFilter] = useState4(
1238
+ () => agentId ?? filtersFromSearch(window.location.search).agentFilter
1239
+ );
1240
+ const permissionAgentId = agentId ?? (agentFilter === "all" ? null : agentFilter);
1241
+ const { allows: allowsAgent } = useResourcePermissions({
1242
+ resourceType: "agent",
1243
+ resourceIds: permissionAgentId == null ? [] : [permissionAgentId]
1244
+ });
1245
+ const canCreateSchedule = server.permissions == null || permissionAgentId == null || allowsAgent(permissionAgentId, "USE");
1246
+ const [drawer, setDrawer] = useState4(() => initialDrawerState(agentId));
1247
+ const [pendingDelete, setPendingDelete] = useState4(null);
1248
+ const [pageSize, setPageSize] = useState4(() => clampPageSize(DEFAULT_TABLE_PAGE_SIZE));
1249
+ const [pageToken, setPageToken] = useState4(void 0);
1250
+ const [nextPageToken, setNextPageToken] = useState4(void 0);
1251
+ const [prevTokenStack, setPrevTokenStack] = useState4([]);
1252
+ const loadGenRef = useRef3(0);
1253
+ const didConsumeIsNewRef = useRef3(false);
1254
+ const loadRunsForSchedules = useCallback(
1255
+ async ({ rows, gen }) => {
1256
+ if (rows.length === 0) {
1257
+ if (gen === loadGenRef.current) setRunsByScheduleId({});
1258
+ return;
1259
+ }
1260
+ setRunsLoading(true);
1261
+ try {
1262
+ const results = await Promise.allSettled(
1263
+ rows.map((schedule) => scheduleServer.listScheduleRuns({ scheduleId: schedule.id }))
1264
+ );
1265
+ if (gen !== loadGenRef.current) return;
1266
+ setRunsByScheduleId(
1267
+ Object.fromEntries(
1268
+ rows.map((schedule, index) => {
1269
+ const result = results[index];
1270
+ const runs = result?.status === "fulfilled" ? result.value : [];
1271
+ return [schedule.id, runs];
1272
+ })
1273
+ )
1274
+ );
1275
+ } finally {
1276
+ if (gen === loadGenRef.current) setRunsLoading(false);
1277
+ }
1278
+ },
1279
+ [scheduleServer]
1280
+ );
1281
+ const loadSchedules = useCallback(
1282
+ async ({ token, size, agentId: agentId2 }) => {
1283
+ const gen = ++loadGenRef.current;
1284
+ setLoading(true);
1285
+ setError(null);
1286
+ try {
1287
+ const page = await scheduleServer.listSchedules({
1288
+ limit: clampPageSize(size),
1289
+ ...token === void 0 || token === "" ? {} : { pageToken: token },
1290
+ ...agentId2 === "all" ? {} : { agentIds: [agentId2] }
1291
+ });
1292
+ if (gen !== loadGenRef.current) return;
1293
+ setSchedules(page.data);
1294
+ setNextPageToken(page.nextPageToken);
1295
+ void loadRunsForSchedules({ rows: page.data, gen });
1296
+ } catch (caught) {
1297
+ if (gen !== loadGenRef.current) return;
1298
+ const message = caught instanceof Error ? caught.message : "Failed to load schedules";
1299
+ setError(message);
1300
+ setSchedules([]);
1301
+ setRunsByScheduleId({});
1302
+ setNextPageToken(void 0);
1303
+ } finally {
1304
+ if (gen === loadGenRef.current) setLoading(false);
1305
+ }
1306
+ },
1307
+ [scheduleServer, loadRunsForSchedules]
1308
+ );
1309
+ const resetToFirstPage = useCallback(
1310
+ (next) => {
1311
+ const size = next?.size ?? pageSize;
1312
+ const agentId2 = next?.agentId ?? agentFilter;
1313
+ setPageToken(void 0);
1314
+ setPrevTokenStack([]);
1315
+ void loadSchedules({ token: void 0, size, agentId: agentId2 });
1316
+ },
1317
+ [agentFilter, loadSchedules, pageSize]
1318
+ );
1319
+ useEffect4(() => {
1320
+ replaceScheduleShareSearch({
1321
+ agent: agentId === void 0 && agentFilter !== "all" ? agentFilter : null,
1322
+ status: statusFilter === "all" ? null : statusFilter,
1323
+ q: nameQuery.trim().length === 0 ? null : nameQuery
1324
+ });
1325
+ }, [agentFilter, agentId, statusFilter, nameQuery]);
1326
+ useEffect4(() => {
1327
+ if (agentId === void 0) return;
1328
+ setAgentFilter((current) => {
1329
+ if (current === agentId) return current;
1330
+ setPageToken(void 0);
1331
+ setPrevTokenStack([]);
1332
+ return agentId;
1333
+ });
1334
+ }, [agentId]);
1335
+ useEffect4(() => {
1336
+ if (didConsumeIsNewRef.current) return;
1337
+ const share = readScheduleShareSearch(window.location.search);
1338
+ if (!share.isNew) return;
1339
+ didConsumeIsNewRef.current = true;
1340
+ replaceScheduleShareSearch({ isNew: null });
1341
+ }, []);
1342
+ useEffect4(() => {
1343
+ const syncFromUrl = () => {
1344
+ const next = filtersFromSearch(window.location.search);
1345
+ setNameQuery(next.nameQuery);
1346
+ setStatusFilter(next.statusFilter);
1347
+ setAgentFilter((current) => {
1348
+ const nextAgentFilter = agentId ?? next.agentFilter;
1349
+ if (current === nextAgentFilter) return current;
1350
+ setPageToken(void 0);
1351
+ setPrevTokenStack([]);
1352
+ return nextAgentFilter;
1353
+ });
1354
+ };
1355
+ window.addEventListener("popstate", syncFromUrl);
1356
+ return () => {
1357
+ window.removeEventListener("popstate", syncFromUrl);
1358
+ };
1359
+ }, [agentId]);
1360
+ useEffect4(() => {
1361
+ void loadSchedules({ token: pageToken, size: pageSize, agentId: agentFilter });
1362
+ }, [agentFilter, pageSize, pageToken, loadSchedules]);
1363
+ const filtered = useMemo2(() => {
1364
+ const q = nameQuery.trim().toLowerCase();
1365
+ return schedules.filter((schedule) => {
1366
+ if (statusFilter !== "all" && schedule.status !== statusFilter) return false;
1367
+ if (q.length > 0 && !schedule.name.toLowerCase().includes(q)) return false;
1368
+ return true;
1369
+ });
1370
+ }, [schedules, nameQuery, statusFilter]);
1371
+ const showCreatedByColumn = hasCreatedBySubject(schedules);
1372
+ const hasPageNav = prevTokenStack.length > 0 || nextPageToken != null;
1373
+ const handleTogglePause = async (schedule) => {
1374
+ if (!allows(schedule.id, "MANAGE")) return;
1375
+ const nextStatus = schedule.status === "active" ? "paused" : "active";
1376
+ try {
1377
+ await scheduleServer.updateSchedule({ ...schedule, status: nextStatus });
1378
+ await loadSchedules({ token: pageToken, size: pageSize, agentId: agentFilter });
1379
+ } catch (caught) {
1380
+ toaster?.showError(caught);
1381
+ }
1382
+ };
1383
+ const handleRunNow = async (schedule) => {
1384
+ if (!allows(schedule.id, "MANAGE")) return;
1385
+ setRunningScheduleIds((prev) => new Set(prev).add(schedule.id));
1386
+ try {
1387
+ await scheduleServer.createScheduleRun({ scheduleId: schedule.id });
1388
+ toaster?.showSuccess({ title: "Run started" });
1389
+ const runs = await scheduleServer.listScheduleRuns({ scheduleId: schedule.id });
1390
+ setRunsByScheduleId((prev) => ({ ...prev, [schedule.id]: runs }));
1391
+ } catch (caught) {
1392
+ toaster?.showError(caught);
1393
+ } finally {
1394
+ setRunningScheduleIds((prev) => {
1395
+ const next = new Set(prev);
1396
+ next.delete(schedule.id);
1397
+ return next;
1398
+ });
1399
+ }
1400
+ };
1401
+ const handleDelete = async (schedule) => {
1402
+ if (!allows(schedule.id, "DELETE")) return;
1403
+ setPendingDelete(null);
1404
+ try {
1405
+ await scheduleServer.deleteSchedule({ id: schedule.id });
1406
+ resetToFirstPage();
1407
+ } catch (caught) {
1408
+ toaster?.showError(caught);
1409
+ }
1410
+ };
1411
+ const goNext = () => {
1412
+ if (nextPageToken == null) return;
1413
+ setPrevTokenStack((stack) => [...stack, pageToken ?? ""]);
1414
+ setPageToken(nextPageToken);
1415
+ };
1416
+ const goPrev = () => {
1417
+ if (prevTokenStack.length === 0) return;
1418
+ const stack = [...prevTokenStack];
1419
+ const prev = stack.pop();
1420
+ setPrevTokenStack(stack);
1421
+ setPageToken(prev === "" ? void 0 : prev);
1422
+ };
1423
+ return /* @__PURE__ */ jsxs7("div", { className: "flex h-full min-h-0 flex-col bg-primary-bg", children: [
1424
+ /* @__PURE__ */ jsx9(
1425
+ PageHeader,
1426
+ {
1427
+ title: agentId === void 0 ? "Scheduled Agents" : void 0,
1428
+ end: /* @__PURE__ */ jsxs7(Fragment2, { children: [
1429
+ /* @__PURE__ */ jsx9("div", { className: "w-full sm:w-60", children: /* @__PURE__ */ jsx9(SearchInput_default, { query: nameQuery, setQuery: setNameQuery, placeholder: "Search schedules by name" }) }),
1430
+ /* @__PURE__ */ jsx9(
1431
+ PopoverSelect,
1432
+ {
1433
+ value: statusFilter,
1434
+ onValueChange: setStatusFilter,
1435
+ options: STATUS_FILTER_OPTIONS,
1436
+ className: "sm:w-40",
1437
+ "aria-label": "Filter by status"
1438
+ }
1439
+ ),
1440
+ agentId === void 0 ? /* @__PURE__ */ jsx9(
1441
+ AgentSearchPicker,
1442
+ {
1443
+ value: agentFilter,
1444
+ selectedLabel: agentFilter === "all" ? "All agents" : agentLabelById[agentFilter] ?? agentFilter,
1445
+ onValueChange: (value) => {
1446
+ setAgentFilter(value);
1447
+ setPageToken(void 0);
1448
+ setPrevTokenStack([]);
1449
+ },
1450
+ onAgentPicked: (agent) => {
1451
+ setAgentLabelById((current) => ({
1452
+ ...current,
1453
+ [libraryAgentId(agent)]: agent.name
1454
+ }));
1455
+ },
1456
+ allOption: { value: "all", label: "All agents" },
1457
+ className: "sm:w-48",
1458
+ "aria-label": "Filter by agent",
1459
+ placeholder: "Search agent"
1460
+ }
1461
+ ) : null,
1462
+ /* @__PURE__ */ jsxs7(
1463
+ Button.Primary,
1464
+ {
1465
+ type: "button",
1466
+ disabled: !canCreateSchedule,
1467
+ onClick: () => canCreateSchedule && setDrawer({
1468
+ kind: "create",
1469
+ agentId: agentFilter !== "all" ? agentFilter : void 0
1470
+ }),
1471
+ children: [
1472
+ /* @__PURE__ */ jsx9(Icon, { name: "plus", className: "size-3.5" }),
1473
+ "Create Schedule"
1474
+ ]
1475
+ }
1476
+ )
1477
+ ] })
1478
+ }
1479
+ ),
1480
+ /* @__PURE__ */ jsx9("div", { className: "min-h-0 flex-1 overflow-auto px-4 py-4", children: loading ? /* @__PURE__ */ jsx9("div", { className: "flex flex-col gap-2", role: "status", "aria-label": "Loading schedules", children: Array.from({ length: 5 }, (_, i) => /* @__PURE__ */ jsx9(Skeleton, { className: "h-12 w-full rounded-md" }, i)) }) : error != null ? /* @__PURE__ */ jsx9("p", { className: "text-failure-bg px-3 py-8 text-center text-sm", children: error }) : schedules.length === 0 ? /* @__PURE__ */ jsx9(EmptyScreen, { title: "No Schedules Found", description: "Create one to get started.", className: "min-h-full" }) : filtered.length === 0 ? /* @__PURE__ */ jsxs7("div", { className: "flex min-h-full flex-col", children: [
1481
+ /* @__PURE__ */ jsx9(EmptyScreen, { title: "No Schedules Found", description: "No schedules match your filters.", className: "flex-1" }),
1482
+ hasPageNav ? /* @__PURE__ */ jsx9(
1483
+ TableTokenPagination,
1484
+ {
1485
+ pageSize,
1486
+ rowCount: 0,
1487
+ canPrev: prevTokenStack.length > 0,
1488
+ canNext: nextPageToken != null,
1489
+ onPrev: goPrev,
1490
+ onNext: goNext,
1491
+ pageSizeOptions: SCHEDULES_PAGE_SIZE_OPTIONS,
1492
+ onPageSizeChange: (size) => {
1493
+ const next = clampPageSize(size);
1494
+ setPageSize(next);
1495
+ setPageToken(void 0);
1496
+ setPrevTokenStack([]);
1497
+ }
1498
+ }
1499
+ ) : null
1500
+ ] }) : /* @__PURE__ */ jsxs7("div", { className: "overflow-hidden rounded-lg border border-border", children: [
1501
+ /* @__PURE__ */ jsxs7(Table, { className: "min-w-[48rem]", children: [
1502
+ /* @__PURE__ */ jsx9(TableHeader, { children: /* @__PURE__ */ jsxs7(TableRow, { className: "hover:bg-transparent", children: [
1503
+ /* @__PURE__ */ jsx9(TableHead, { children: "Name" }),
1504
+ /* @__PURE__ */ jsx9(TableHead, { children: "Agent" }),
1505
+ showCreatedByColumn ? /* @__PURE__ */ jsx9(TableHead, { children: "Created by" }) : null,
1506
+ /* @__PURE__ */ jsx9(TableHead, { children: "Frequency" }),
1507
+ /* @__PURE__ */ jsx9(TableHead, { children: "Status" }),
1508
+ /* @__PURE__ */ jsx9(TableHead, { children: "Last 5 runs" }),
1509
+ /* @__PURE__ */ jsx9(TableHead, { children: /* @__PURE__ */ jsx9("span", { className: "sr-only", children: "Actions" }) })
1510
+ ] }) }),
1511
+ /* @__PURE__ */ jsx9(TableBody, { children: filtered.map((schedule) => {
1512
+ const cadence = formatCadenceSummary({ cron: schedule.cron, timezone: schedule.timezone });
1513
+ const agentLabel = schedule.agentName ?? agentLabelById[schedule.agentId] ?? schedule.agentId;
1514
+ return /* @__PURE__ */ jsxs7(TableRow, { children: [
1515
+ /* @__PURE__ */ jsx9(TableCell, { className: "text-text-primary font-medium", children: /* @__PURE__ */ jsx9("span", { className: "text-left !no-underline", children: schedule.name }) }),
1516
+ /* @__PURE__ */ jsx9(TableCell, { children: agentLabel }),
1517
+ showCreatedByColumn ? /* @__PURE__ */ jsx9(TableCell, { children: /* @__PURE__ */ jsx9(CreatedByCell, { subject: schedule.createdBySubject }) }) : null,
1518
+ /* @__PURE__ */ jsx9(TableCell, { children: cadence }),
1519
+ /* @__PURE__ */ jsx9(TableCell, { children: /* @__PURE__ */ jsx9(ScheduleStatusBadge, { status: schedule.status }) }),
1520
+ /* @__PURE__ */ jsx9(TableCell, { children: runsLoading ? /* @__PURE__ */ jsx9("span", { className: "text-text-secondary text-sm", children: "\u2026" }) : /* @__PURE__ */ jsx9(ScheduleLastRunsCell, { runs: runsByScheduleId[schedule.id] ?? [] }) }),
1521
+ /* @__PURE__ */ jsx9(TableCell, { className: "text-right", children: /* @__PURE__ */ jsx9(
1522
+ ScheduleRowActions,
1523
+ {
1524
+ schedule,
1525
+ running: runningScheduleIds.has(schedule.id),
1526
+ canManage: allows(schedule.id, "MANAGE"),
1527
+ canDelete: allows(schedule.id, "DELETE"),
1528
+ onRunNow: () => void handleRunNow(schedule),
1529
+ onEdit: () => {
1530
+ if (allows(schedule.id, "MANAGE")) setDrawer({ kind: "edit", schedule });
1531
+ },
1532
+ onTogglePause: () => void handleTogglePause(schedule),
1533
+ onDelete: () => {
1534
+ if (allows(schedule.id, "DELETE")) setPendingDelete(schedule);
1535
+ }
1536
+ }
1537
+ ) })
1538
+ ] }, schedule.id);
1539
+ }) })
1540
+ ] }),
1541
+ (filtered.length > 0 || hasPageNav) && /* @__PURE__ */ jsx9(
1542
+ TableTokenPagination,
1543
+ {
1544
+ pageSize,
1545
+ rowCount: filtered.length,
1546
+ canPrev: prevTokenStack.length > 0,
1547
+ canNext: nextPageToken != null,
1548
+ onPrev: goPrev,
1549
+ onNext: goNext,
1550
+ pageSizeOptions: SCHEDULES_PAGE_SIZE_OPTIONS,
1551
+ onPageSizeChange: (size) => {
1552
+ const next = clampPageSize(size);
1553
+ setPageSize(next);
1554
+ setPageToken(void 0);
1555
+ setPrevTokenStack([]);
1556
+ }
1557
+ }
1558
+ )
1559
+ ] }) }),
1560
+ drawer.kind === "create" ? /* @__PURE__ */ jsx9(
1561
+ ScheduleFormDrawer,
1562
+ {
1563
+ open: true,
1564
+ mode: "create",
1565
+ initialAgentId: drawer.agentId ?? "",
1566
+ onOpenChange: (open) => {
1567
+ if (!open) setDrawer({ kind: "closed" });
1568
+ },
1569
+ onSaved: () => resetToFirstPage()
1570
+ }
1571
+ ) : null,
1572
+ drawer.kind === "edit" ? /* @__PURE__ */ jsx9(
1573
+ ScheduleFormDrawer,
1574
+ {
1575
+ open: true,
1576
+ mode: "edit",
1577
+ schedule: drawer.schedule,
1578
+ onOpenChange: (open) => {
1579
+ if (!open) setDrawer({ kind: "closed" });
1580
+ },
1581
+ onSaved: () => void loadSchedules({ token: pageToken, size: pageSize, agentId: agentFilter })
1582
+ }
1583
+ ) : null,
1584
+ pendingDelete != null ? /* @__PURE__ */ jsxs7(
1585
+ Dialog,
1586
+ {
1587
+ open: true,
1588
+ onOpenChange: (open) => {
1589
+ if (!open) setPendingDelete(null);
1590
+ },
1591
+ "aria-label": "Delete schedule",
1592
+ className: "max-w-md",
1593
+ children: [
1594
+ /* @__PURE__ */ jsx9(DialogContent, { children: /* @__PURE__ */ jsxs7(DialogHeader, { children: [
1595
+ /* @__PURE__ */ jsx9(DialogTitle, { children: "Delete schedule" }),
1596
+ /* @__PURE__ */ jsxs7("p", { className: "text-text-secondary text-sm", children: [
1597
+ "\u201C",
1598
+ pendingDelete.name,
1599
+ "\u201D will stop running. This cannot be undone."
1600
+ ] })
1601
+ ] }) }),
1602
+ /* @__PURE__ */ jsxs7(DialogFooter, { children: [
1603
+ /* @__PURE__ */ jsx9(Button.Secondary, { type: "button", onClick: () => setPendingDelete(null), children: "Cancel" }),
1604
+ /* @__PURE__ */ jsx9(
1605
+ Button.Destructive,
1606
+ {
1607
+ type: "button",
1608
+ disabled: !allows(pendingDelete.id, "DELETE"),
1609
+ onClick: () => void handleDelete(pendingDelete),
1610
+ children: "Delete"
1611
+ }
1612
+ )
1613
+ ] })
1614
+ ]
1615
+ }
1616
+ ) : null
1617
+ ] });
1618
+ }
1619
+
1620
+ export {
1621
+ ScheduleFormFields,
1622
+ ScheduleRunChip,
1623
+ ScheduleStatusBadge,
1624
+ TestScheduleScreen,
1625
+ ScheduleFormDrawer,
1626
+ ScheduleLastRunsCell,
1627
+ SchedulesPage
1628
+ };
1629
+ //# sourceMappingURL=chunk-EPAL2HD5.js.map